Python Pandas - Знайдіть різницю між двома кадрами даних


98

У мене є два кадри даних df1 і df2, де df2 - це підмножина df1. Як отримати новий фрейм даних (df3), який є різницею між двома фреймами даних?

Іншими словами, кадр даних, який містить усі рядки / стовпці в df1, а не в df2?

введіть тут опис зображення


3
Найпростіший спосіб зробити це буде залежати від того, як структуровані ваші фрейми даних (тобто, чи можна використовувати індекси тощо). Це хороший приклад того, чому ви завжди повинні включати відтворюваний приклад у питаннях панди.
cmaher

Я додав зразок зразка даних
userPyGeo

Відповіді:


153

За допомогою drop_duplicates

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

Update :

Above method only working for those dataframes they do not have duplicate itself, For example

df1=pd.DataFrame({'A':[1,2,3,3],'B':[2,3,4,4]})
df2=pd.DataFrame({'A':[1],'B':[2]})

Він виведеться, як показано нижче, що неправильно

Неправильний результат:

pd.concat([df1, df2]).drop_duplicates(keep=False)
Out[655]: 
   A  B
1  2  3

Правильний вихід

Out[656]: 
   A  B
1  2  3
2  3  4
3  3  4

Як цього досягти?

Спосіб 1: Використання за isinдопомогоюtuple

df1[~df1.apply(tuple,1).isin(df2.apply(tuple,1))]
Out[657]: 
   A  B
1  2  3
2  3  4
3  3  4

Спосіб 2: mergeсindicator

df1.merge(df2,indicator = True, how='left').loc[lambda x : x['_merge']!='both']
Out[421]: 
   A  B     _merge
1  2  3  left_only
2  3  4  left_only
3  3  4  left_only

3
Ви також можете визначити, які стовпці слід враховувати, шукаючи дублікати:pd.concat([df1,df2]).drop_duplicates(subset = ['col1','col2'], keep=False)
Szpaqn

@Szpaqn зауважте, що цей метод не буде обробляти особливий випадок. :-)
BEN_YO

Зверніть увагу, що це може спричинити несподівані рядки у результаті, якщо один із ваших типів даних float(тому що 12.00000000001 != 12). Кращою практикою є знайти перетин перетину ідентифікаторів у двох кадрах даних і отримати різницю на основі цього.
Jiāgěng

1
@DtechNet потрібно зробити так, щоб два кадри даних мали однакову назву
BEN_YO

2
Метод 2 ( indicator=True) - це дуже універсальний та корисний інструмент, я хотів би бачити його у верхній частині цієї відповіді, але приєднавшись „зовнішнім“, а не „лівим“, щоб охопити всі 3 ситуації.
mirekphd

32

Для рядків спробуйте це, де Nameстовпець спільного індексу (може бути списком для кількох загальних стовпців, або вказати left_onі right_on):

m = df1.merge(df2, on='Name', how='outer', suffixes=['', '_'], indicator=True)

indicator=TrueПараметр корисний , оскільки він додає стовпець _mergeз усіма змінами між df1і df2, класифікованих в 3 -х можливих видів: «left_only», «right_only» або «як».

Для стовпців спробуйте наступне:

set(df1.columns).symmetric_difference(df2.columns)

8
Хочете прокоментувати коментарів? mergewith indicator=Trueє класичним рішенням для порівняння кадрів даних за заданими полями.
jpp

9

Прийнята відповідь Метод 1 не буде працювати для фреймів даних з NaN всередині, як pd.np.nan != pd.np.nan. Я не впевнений, що це найкращий спосіб, але цього можна уникнути

df1[~df1.astype(str).apply(tuple, 1).isin(df2.astype(str).apply(tuple, 1))]

6

edit2, я знайшов нове рішення без необхідності встановлення індексу

newdf=pd.concat[df1,df2].drop_duplicates(keep=False)

Гаразд, я знайшов, що відповідь на найвищу оцінку вже містить те, що я вже зрозумів. Так, ми можемо використовувати цей код лише за умови, що в кожному з двох dfs немає дублікатів.


У мене є хитрий метод. Спочатку ми встановлюємо `` Name '' як індекс двох кадрів даних, заданий питанням. . Ось код.

df1.set_index('Name',inplace=True)
df2.set_index('Name',inplace=True)
newdf=df1.drop(df2.index)

ви, мабуть, мали на увазі pd.concat ([df1, df2]). drop_duplicates (keep = False)
Манаслу

4
import pandas as pd
# given
df1 = pd.DataFrame({'Name':['John','Mike','Smith','Wale','Marry','Tom','Menda','Bolt','Yuswa',],
    'Age':[23,45,12,34,27,44,28,39,40]})
df2 = pd.DataFrame({'Name':['John','Smith','Wale','Tom','Menda','Yuswa',],
    'Age':[23,12,34,44,28,40]})

# find elements in df1 that are not in df2
df_1notin2 = df1[~(df1['Name'].isin(df2['Name']) & df1['Age'].isin(df2['Age']))].reset_index(drop=True)

# output:
print('df1\n', df1)
print('df2\n', df2)
print('df_1notin2\n', df_1notin2)

# df1
#     Age   Name
# 0   23   John
# 1   45   Mike
# 2   12  Smith
# 3   34   Wale
# 4   27  Marry
# 5   44    Tom
# 6   28  Menda
# 7   39   Bolt
# 8   40  Yuswa
# df2
#     Age   Name
# 0   23   John
# 1   12  Smith
# 2   34   Wale
# 3   44    Tom
# 4   28  Menda
# 5   40  Yuswa
# df_1notin2
#     Age   Name
# 0   45   Mike
# 1   27  Marry
# 2   39   Bolt

Що означає '~'?
Піотрек Лешняк

'~' не для булевого індексування. Див .: pandas.pydata.org/pandas-docs/stable/user_guide/…
SpeedCoder5

3

Можливо, простіший однолінійний, з однаковими або різними назвами стовпців. Працювало навіть тоді, коли df2 ['Name2'] містив повторювані значення.

newDf = df1.set_index('Name1')
           .drop(df2['Name2'], errors='ignore')
           .reset_index(drop=False)

2
простий та ефективний. Додано помилки = 'ігнорувати', щоб вирішити проблему у випадку, коли цільові значення не знаходяться у джерелі (тобто перетині), а скидання індексу в кінці призводить df, подібний до вихідного.
MrE

0

Невелика варіація рішення nice @ liangli, яка не вимагає зміни індексу існуючих кадрів даних:

newdf = df1.drop(df1.join(df2.set_index('Name').index))

0

Знаходження різниці за індексом. Припускаючи, що df1 є підмножиною df2, а індекси переносяться вперед при підмножинні

df1.loc[set(df1.index).symmetric_difference(set(df2.index))].dropna()

# Example

df1 = pd.DataFrame({"gender":np.random.choice(['m','f'],size=5), "subject":np.random.choice(["bio","phy","chem"],size=5)}, index = [1,2,3,4,5])

df2 =  df1.loc[[1,3,5]]

df1

 gender subject
1      f     bio
2      m    chem
3      f     phy
4      m     bio
5      f     bio

df2

  gender subject
1      f     bio
3      f     phy
5      f     bio

df3 = df1.loc[set(df1.index).symmetric_difference(set(df2.index))].dropna()

df3

  gender subject
2      m    chem
4      m     bio


0

На додаток до прийнятої відповіді, я хотів би запропонувати ще одне більш широке рішення, яке можна знайти двовимірну різницю в двох кадрах даних з будь-яким index/ columns(вони можуть не збігатися для обох кадрів даних). Також метод дозволяє встановити допуск для floatелементів для порівняння фреймів даних (він використовує np.isclose)


import numpy as np
import pandas as pd

def get_dataframe_setdiff2d(df_new: pd.DataFrame, 
                            df_old: pd.DataFrame, 
                            rtol=1e-03, atol=1e-05) -> pd.DataFrame:
    """Returns set difference of two pandas DataFrames"""

    union_index = np.union1d(df_new.index, df_old.index)
    union_columns = np.union1d(df_new.columns, df_old.columns)

    new = df_new.reindex(index=union_index, columns=union_columns)
    old = df_old.reindex(index=union_index, columns=union_columns)

    mask_diff = ~np.isclose(new, old, rtol, atol)

    df_bool = pd.DataFrame(mask_diff, union_index, union_columns)

    df_diff = pd.concat([new[df_bool].stack(),
                         old[df_bool].stack()], axis=1)

    df_diff.columns = ["New", "Old"]

    return df_diff

Приклад:

In [1]

df1 = pd.DataFrame({'A':[2,1,2],'C':[2,1,2]})
df2 = pd.DataFrame({'A':[1,1],'B':[1,1]})

print("df1:\n", df1, "\n")

print("df2:\n", df2, "\n")

diff = get_dataframe_setdiff2d(df1, df2)

print("diff:\n", diff, "\n")
Out [1]

df1:
   A  C
0  2  2
1  1  1
2  2  2 

df2:
   A  B
0  1  1
1  1  1 

diff:
     New  Old
0 A  2.0  1.0
  B  NaN  1.0
  C  2.0  NaN
1 B  NaN  1.0
  C  1.0  NaN
2 A  2.0  NaN
  C  2.0  NaN 

0

Як було згадано тут , що

df1[~df1.apply(tuple,1).isin(df2.apply(tuple,1))]

є правильним рішенням, але це призведе до неправильного результату, якщо

df1=pd.DataFrame({'A':[1],'B':[2]})
df2=pd.DataFrame({'A':[1,2,3,3],'B':[2,3,4,4]})

У цьому випадку вищевказане рішення дасть Empty DataFrame , замість цього ви повинні використовуватиconcat метод після видалення дублікатів з кожного datframe.

Використовуйте concate with drop_duplicates

df1=df1.drop_duplicates(keep="first") 
df2=df2.drop_duplicates(keep="first") 
pd.concat([df1,df2]).drop_duplicates(keep=False)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.