Python Pandas - Найдите разницу между двумя фреймами данных


98

У меня есть два фрейма данных df1 и df2, где df2 - это подмножество df1. Как мне получить новый фрейм данных (df3), который является разницей между двумя фреймами данных?

Другими словами, фрейм данных, в котором есть все строки / столбцы в df1, которых нет в df2?

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


3
Самый простой способ сделать это будет зависеть от того, как структурированы ваши фреймы данных (т. Е. Можно ли использовать индексы и т. Д.). Это хороший пример того, почему вы всегда должны включать воспроизводимый пример в вопросы о пандах.
cmaher

Я добавил образец изображения
фрейма данных

Ответы:


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 не будет работать для кадров данных с Nans внутри, как 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' как индекс двух фреймов данных, заданных вопросом. Поскольку у нас одинаковое 'Name' в двух dfs, мы можем просто удалить индекс 'меньшего' df из 'большего' df . Вот код.

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]})

В этом случае вышеупомянутое решение предоставит пустой фрейм данных , вместо этого вы должны использовать concatметод после удаления дубликатов из каждого фрейма данных.

Использовать 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.