Создание результирующего набора «различий» из двух кадров данных панд с сгруппированными ключевыми столбцами - PullRequest
1 голос
/ 15 апреля 2019

Я постараюсь сделать это так коротко и точно (с упрощенными данными). У меня есть таблица данных, которая имеет четыре столбца (имейте в виду, что больше столбцов может быть добавлено позже), ни один из которых не является уникальным сам по себе, но эти три столбца вместе «ID», «ID2», «DO» должны быть уникальными как группа. Я перенесу эту таблицу в один фрейм данных, а обновленную версию таблицы - в другой фрейм данных.

Если df - это «исходные данные», а df2 - «обновленные данные», является ли это наиболее точным / эффективным способом выяснить, какие изменения происходят с исходными данными?

import pandas as pd
#Sample Data:
df  = pd.DataFrame({'ID':[546,107,478,546,478], 'ID2':['AUSER','BUSER','CUSER','AUSER','EUSER'], 'DO':[3,6,8,4,6], 'DATA':['ORIG','ORIG','ORIG','ORIG','ORIG']})
df2 = pd.DataFrame({'ID':[107,546,123,546,123], 'ID2':['BUSER','AUSER','DUSER','AUSER','FUSER'], 'DO':[6,3,2,4,3], 'DATA':['CHANGE','CHANGE','CHANGE','ORIG','CHANGE']})
>>> df
   DATA  DO   ID    ID2
0  ORIG   3  546  AUSER
1  ORIG   6  107  BUSER
2  ORIG   8  478  CUSER
3  ORIG   4  546  AUSER
4  ORIG   6  478  EUSER

>>> df2
     DATA  DO   ID    ID2
0  CHANGE   6  107  BUSER
1  CHANGE   3  546  AUSER
2  CHANGE   2  123  DUSER
3    ORIG   4  546  AUSER
4  CHANGE   3  123  FUSER

#Compare Dataframes
merged = df2.merge(df, indicator=True, how='outer')

#Split the merged comparison into:
# - original records that will be updated or deleted 
# - new records that will be inserted or update the original record.
df_original = merged.loc[merged['_merge'] == 'right_only'].drop(columns=['_merge']).copy()
df_new = merged.loc[merged['_merge'] == 'left_only'].drop(columns=['_merge']).copy()

#Create another merge to determine if the new records will either be updates or inserts
check = pd.merge(df_new,df_original, how='left', left_on=['ID','ID2','DO'], right_on = ['ID','ID2','DO'], indicator=True)
in_temp  = check[['ID','ID2','DO']].loc[check['_merge']=='left_only']
upd_temp = check[['ID','ID2','DO']].loc[check['_merge']=='both']

#Create dataframes for each Transaction:
# - removals: Remove records based on provided key values
# - updates:  Update entire record based on key values
# - inserts:  Insert entire record
removals = pd.concat([df_original[['ID','ID2','DO']],df_new[['ID','ID2','DO']],df_new[['ID','ID2','DO']]]).drop_duplicates(keep=False)
updates  = df2.loc[(df2['ID'].isin(upd_temp['ID']))&(df2['ID2'].isin(upd_temp['ID2']))&(df2['DO'].isin(upd_temp['DO']))].copy()
inserts  = df2.loc[(df2['ID'].isin(in_temp['ID']))&(df2['ID2'].isin(in_temp['ID2']))&(df2['DO'].isin(in_temp['DO']))].copy()

Результаты:

>>> removals
    ID    ID2  DO
6  478  CUSER   8
8  478  EUSER   6

>>> updates
     DATA  DO   ID    ID2
0  CHANGE   6  107  BUSER
1  CHANGE   3  546  AUSER

>>> inserts
     DATA  DO   ID    ID2
2  CHANGE   2  123  DUSER
4  CHANGE   3  123  FUSER

Чтобы переформулировать вопросы. Будет ли эта логика последовательно и правильно идентифицировать различия между двумя кадрами данных с указанными ключевыми столбцами? Есть ли более эффективный или питонный подход к этому?

Обновлен пример данных с большим количеством записей и соответствующими результатами.

1 Ответ

0 голосов
/ 15 апреля 2019
import pandas as pd
#Sample Data:
df  = pd.DataFrame({'ID':[546,107,478,546], 'ID2':['AUSER','BUSER','CUSER','AUSER'], 'DO':[3,6,8,4], 'DATA':['ORIG','ORIG','ORIG','ORIG']})
df2 = pd.DataFrame({'ID':[107,546,123,546], 'ID2':['BUSER','AUSER','DUSER','AUSER'], 'DO':[6,3,2,4], 'DATA':['CHANGE','CHANGE','CHANGE','ORIG']})

Для изменений:

#Concat both df and df2 together, and whenever there is two of the same, drop them both
df3 =  pd.concat([df, df2]).drop_duplicates(keep = False)

#Whenever the size of this following group by is 2 or more there was a change.
#Change
df3 = df3.groupby(['ID', 'ID2', 'DO'])['DATA']\
         .size()\
         .reset_index()\
         .query('DATA == 2')

df3.loc[:, 'DATA'] = 'CHANGE'

     ID  ID2    DO    DATA
0   107 BUSER    6   CHANGE
3   546 AUSER    3   CHANGE

Для вставок:

#We can compare the ID comlumn for df and df2 and see whats new in df2

#Inserts
df2[(np.logical_not(df2['ID'].isin(df['ID'])))&
    (np.logical_not(df2['ID2'].isin(df['ID2'])))&
    (np.logical_not(df2['DO'].isin(df['DO'])))]

     ID  ID2    DO   DATA
2   123 DUSER   2   CHANGE

Для съемов:

#Similar logic as above but flipped.

#Removals
df[(np.logical_not(df2['ID'].isin(df['ID'])))&
   (np.logical_not(df2['ID2'].isin(df['ID2'])))&
   (np.logical_not(df2['DO'].isin(df['DO'])))]

     ID  ID2    DO  DATA
2   478 CUSER   8   ORIG

РЕДАКТИРОВАНИЕ

df  = pd.DataFrame({'ID':[546,107,478,546,478], 'ID2':['AUSER','BUSER','CUSER','AUSER','EUSER'], 'DO':[3,6,8,4,6], 'DATA':['ORIG','ORIG','ORIG','ORIG','ORIG']})
df2 = pd.DataFrame({'ID':[107,546,123,546,123], 'ID2':['BUSER','AUSER','DUSER','AUSER','FUSER'], 'DO':[6,3,2,4,3], 'DATA':['CHANGE','CHANGE','CHANGE','ORIG','CHANGE']})

Новые кадры данных.Для изменений мы сделаем это точно так же:

df3 =  pd.concat([df, df2]).drop_duplicates(keep = False)

#Change
Change = df3.groupby(['ID', 'ID2', 'DO'])['DATA']\
         .size()\
         .reset_index()\
         .query('DATA == 2')

Change.loc[:, 'DATA'] = 'CHANGE'

    ID   ID2    DO   DATA
0   107 BUSER   6   CHANGE
5   546 AUSER   3   CHANGE

Для вставок / удалений мы будем выполнять ту же группировку, что и выше, за исключением запроса для тех, которые появляются только один раз.Затем мы проведем внутреннее соединение с df и df2, чтобы увидеть, что было добавлено / удалено.

InsertRemove = df3.groupby(['ID', 'ID2', 'DO'])['DATA']\
                  .size()\
                  .reset_index()\
                  .query('DATA == 1')

#Inserts
Inserts = InsertRemove.merge(df2, how = 'inner',  left_on= ['ID', 'ID2', 'DO'], right_on = ['ID', 'ID2', 'DO'])\
                      .drop('DATA_x', axis = 1)\
                      .rename({'DATA_y':'DATA'}, axis = 1)

     ID  ID2    DO   DATA
0   123 DUSER   2   CHANGE
1   123 FUSER   3   CHANGE

#Removals
Remove  = InsertRemove.merge(df, how = 'inner',  left_on= ['ID', 'ID2', 'DO'], right_on = ['ID', 'ID2', 'DO'])\
                      .drop('DATA_x', axis = 1)\
                      .rename({'DATA_y':'DATA'}, axis = 1)

     ID  ID2    DO  DATA
0   478 CUSER   8   ORIG
1   478 EUSER   6   ORIG
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...