比较两个数据框并获取差异

139

我有两个数据框。例子:

df1:
Date       Fruit  Num  Color 
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange  8.6 Orange
2013-11-24 Apple   7.6 Green
2013-11-24 Celery 10.2 Green

df2:
Date       Fruit  Num  Color 
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange  8.6 Orange
2013-11-24 Apple   7.6 Green
2013-11-24 Celery 10.2 Green
2013-11-25 Apple  22.1 Red
2013-11-25 Orange  8.6 Orange

每个数据框都有日期作为索引。两个数据框具有相同的结构。

我想要做的是比较这两个数据框,并找出df2中不在df1中的行。我想要比较日期(索引)和第一列(香蕉,苹果等),以查看它们是否存在于df2与df1中。

我尝试了以下方法:

对于第一种方法,我得到了这个错误:"Exception: Can only compare identically-labeled DataFrame objects"。我已经尝试过删除日期作为索引,但仍然出现相同的错误。

第三种方法中,我得到了assert返回False,但是无法弄清楚如何实际查看不同的行。

欢迎任何指针。


如果你按照这个链接所示的方法进行操作:http://www.cookbook-r.com/Manipulating_data/Renaming_columns_in_a_data_frame/,那么它会消除“标签相同的DataFrame对象”异常吗? - Anthony Kong
我已经多次更改列名以尝试解决问题,但没有成功。 - Eric D. Brown D.Sc.
1
就我所知,我已经在两个数据框中将列名更改为“a,b,c,d”,并收到了相同的错误消息。 - Eric D. Brown D.Sc.
16个回答

137

这种方法 df1 != df2,仅适用于行和列完全相同的数据帧。实际上,所有数据帧轴都使用 _indexed_same 方法进行比较,如果发现差异,甚至包括列/索引顺序,就会引发异常。

如果我理解正确,您想找到不同之处,而不是找到变化。为此,一种方法可能是连接数据帧:

>>> df = pd.concat([df1, df2])
>>> df = df.reset_index(drop=True)

按照分组

>>> df_gpby = df.groupby(list(df.columns))

获取唯一记录的索引

>>> idx = [x[0] for x in df_gpby.groups.values() if len(x) == 1]

筛选

>>> df.reindex(idx)
         Date   Fruit   Num   Color
9  2013-11-25  Orange   8.6  Orange
8  2013-11-25   Apple  22.1     Red

这就是答案。我移除了“日期”索引并按照这种方法进行操作,现在输出正确。 - Eric D. Brown D.Sc.
13
有没有简单的方法可以添加一个标记,以便查看从df1到df2哪些行被删除/添加/更改了? - pyCthon
@alko 我在想,这个 pd.concat 只会添加 df1 中缺失的项吗?还是会完全用 df2 替换 df1 - jake wong
@jakewong pd.concat - 在这里使用 - 执行外连接。换句话说,它将两个数据框的所有索引连接起来,这实际上是 pd.concat() 的默认行为,这是文档 http://pandas.pydata.org/pandas-docs/stable/merging.html 中的说明。 - Thanos
Pandas最多可以比较多少条记录? - Pyd
显示剩余4条评论

96

ling 的评论更新并放置在更容易被他人找到的地方,这是对 jur 上面回复的补充。

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

使用这些数据框进行测试:

# with import pandas as pd

df1 = pd.DataFrame({
    'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24'],
    'Fruit':['Banana','Orange','Apple','Celery'],
    'Num':[22.1,8.6,7.6,10.2],
    'Color':['Yellow','Orange','Green','Green'],
    })

df2 = pd.DataFrame({
    'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24','2013-11-25','2013-11-25'],
    'Fruit':['Banana','Orange','Apple','Celery','Apple','Orange'],
    'Num':[22.1,8.6,7.6,10.2,22.1,8.6],
    'Color':['Yellow','Orange','Green','Green','Red','Orange'],
    })

这将导致如下结果:

# for df1

         Date   Fruit   Num   Color
0  2013-11-24  Banana  22.1  Yellow
1  2013-11-24  Orange   8.6  Orange
2  2013-11-24   Apple   7.6   Green
3  2013-11-24  Celery  10.2   Green


# for df2

         Date   Fruit   Num   Color
0  2013-11-24  Banana  22.1  Yellow
1  2013-11-24  Orange   8.6  Orange
2  2013-11-24   Apple   7.6   Green
3  2013-11-24  Celery  10.2   Green
4  2013-11-25   Apple  22.1     Red
5  2013-11-25  Orange   8.6  Orange


# for df_diff

         Date   Fruit   Num   Color
4  2013-11-25   Apple  22.1     Red
5  2013-11-25  Orange   8.6  Orange

1
到目前为止,这是最好的答案!非常直截了当。 - Jane Kathambi
1
但是这个答案不会显示重复行,如果这些重复行在同一个DataFrame中。例如,如果df1包含两行相同的数据,但是df2不包含任何这些数据。 - Bohdan Pylypenko
@BohdanPylypenko - 确实如此!但我认为人们在比较不同数据集之前,会将每个数据集内的数据视为唯一的。如果他们没有这样做,那么他们就会在源代码和跨源代码中遇到混乱的问题,需要一次性解决所有问题。 - leerssej
这是最直接的答案。 - Amila Viraj

28
# THIS WORK FOR ME

# Get all diferent values
df3 = pd.merge(df1, df2, how='outer', indicator='Exist')
df3 = df3.loc[df3['Exist'] != 'both']


# If you like to filter by a common ID
df3  = pd.merge(df1, df2, on="Fruit", how='outer', indicator='Exist')
df3  = df3.loc[df3['Exist'] != 'both']

这是最好的答案。 - moshevi
这对于多列数据框非常有效。 - Amadeus Stevenson
我最喜欢这个答案 - undefined

26

将数据框以字典形式传递给concat函数,会得到一个多级索引的数据框,您可以轻松删除重复项,从而得到一个多级索引的数据框,其中包含数据框之间的差异:

import sys
if sys.version_info[0] < 3:
    from StringIO import StringIO
else:
    from io import StringIO
import pandas as pd

DF1 = StringIO("""Date       Fruit  Num  Color 
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange  8.6 Orange
2013-11-24 Apple   7.6 Green
2013-11-24 Celery 10.2 Green
""")
DF2 = StringIO("""Date       Fruit  Num  Color 
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange  8.6 Orange
2013-11-24 Apple   7.6 Green
2013-11-24 Celery 10.2 Green
2013-11-25 Apple  22.1 Red
2013-11-25 Orange  8.6 Orange""")


df1 = pd.read_table(DF1, sep='\s+')
df2 = pd.read_table(DF2, sep='\s+')
#%%
dfs_dictionary = {'DF1':df1,'DF2':df2}
df=pd.concat(dfs_dictionary)
df.drop_duplicates(keep=False)

结果:

             Date   Fruit   Num   Color
DF2 4  2013-11-25   Apple  22.1     Red
    5  2013-11-25  Orange   8.6  Orange

1
这是一种更简单的方法,只需要再进行一次修订就可以使它更加容易。不需要在字典中连接,使用df = pd.concat([df1,df2])即可实现相同的功能。 - ling
不要覆盖内置关键字dict - denfromufa
有没有办法添加代码来确定哪个数据框包含了唯一的行? - jlewkovich
你可以通过多级索引中的第一级来确定数据框中字典的键(我已更新输出,包含正确的键)。 - jur

20
自从pandas >= 1.1.0版本,我们拥有了DataFrame.compareSeries.compare方法。
注意:该方法只能比较标签完全相同的DataFrame对象,这意味着行和列标签完全相同的DataFrames。
df1 = pd.DataFrame({'A': [1, 2, 3],
                    'B': [4, 5, 6],
                    'C': [7, np.NaN, 9]})

df2 = pd.DataFrame({'A': [1, 99, 3],
                    'B': [4, 5, 81],
                    'C': [7, 8, 9]})

   A  B    C
0  1  4  7.0
1  2  5  NaN
2  3  6  9.0 

    A   B  C
0   1   4  7
1  99   5  8
2   3  81  9

df1.compare(df2)

     A          B          C      
  self other self other self other
1  2.0  99.0  NaN   NaN  NaN   8.0
2  NaN   NaN  6.0  81.0  NaN   NaN

谢谢您提供这些信息。我还没有升级到1.1版本,但这对我很有帮助。 - Eric D. Brown D.Sc.
2
比较只有在两个数据框的大小相同时才有效,对吗? - Rebin
1
是的,请查看我的回答中的注释@Rebin。 - Erfan

6

在 alko 的答案基础上,我进行了改进。除了过滤步骤(此处出现错误:ValueError: cannot reindex from a duplicate axis),以下是我最终使用的解决方案:

# join the dataframes
united_data = pd.concat([data1, data2, data3, ...])
# group the data by the whole row to find duplicates
united_data_grouped = united_data.groupby(list(united_data.columns))
# detect the row indices of unique rows
uniq_data_idx = [x[0] for x in united_data_grouped.indices.values() if len(x) == 1]
# extract those unique values
uniq_data = united_data.iloc[uniq_data_idx]

答案很好,谢谢。 - Eric D. Brown D.Sc.
1
当我尝试运行第三行时,出现了错误 'IndexError: index out of bounds' - Moondra

5
df2中获取现有数据,并加入到df1中:
dfe = df2[df2["Fruit"].isin(df1["Fruit"])]

df2 中获取不存在的数据到 df1
dfn = df2[~ df2["Fruit"].isin(df1["Fruit"])]

您可以使用多个比较。


非常好!谢谢。 - dimButTries

4

在这里找到一个简单的解决方案:

https://dev59.com/z6bja4cB1Zd3GeqPhoZU#47132808

pd.concat([df1, df2]).loc[df1.index.symmetric_difference(df2.index)]

将df1和df2连接起来,然后找到两个数据框中索引不同的行。

1
欢迎来到Stack Overflow Tom2shoes。请不要提供仅链接的答案,尝试从链接中提取内容并将其仅作为参考留下(因为链接中的内容可能会被删除或链接本身可能会断开)。有关更多信息,请参阅"如何编写好答案?"。如果您认为此问题已在另一个问题中得到解答,请将其标记为重复。 - GGG

3
# given
df1=pd.DataFrame({'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24'],
    'Fruit':['Banana','Orange','Apple','Celery'],
    'Num':[22.1,8.6,7.6,10.2],
    'Color':['Yellow','Orange','Green','Green']})
df2=pd.DataFrame({'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24','2013-11-25','2013-11-25'],
    'Fruit':['Banana','Orange','Apple','Celery','Apple','Orange'],
    'Num':[22.1,8.6,7.6,1000,22.1,8.6],
    'Color':['Yellow','Orange','Green','Green','Red','Orange']})

# find which rows are in df2 that aren't in df1 by Date and Fruit
df_2notin1 = df2[~(df2['Date'].isin(df1['Date']) & df2['Fruit'].isin(df1['Fruit']) )].dropna().reset_index(drop=True)

# output
print('df_2notin1\n', df_2notin1)
#      Color        Date   Fruit   Num
# 0     Red  2013-11-25   Apple  22.1
# 1  Orange  2013-11-25  Orange   8.6

3

有一个更简单、更快速、更好的解决方案,如果数字不同,甚至可以给出数量差异:

df1_i = df1.set_index(['Date','Fruit','Color'])
df2_i = df2.set_index(['Date','Fruit','Color'])
df_diff = df1_i.join(df2_i,how='outer',rsuffix='_').fillna(0)
df_diff = (df_diff['Num'] - df_diff['Num_'])

这里的 df_diff 是差异概要。您甚至可以使用它来查找数量上的差异。在您的示例中:

enter image description here

说明: 与比较两个列表类似,为了高效地完成此操作,我们应该首先对它们进行排序,然后进行比较(将列表转换为集合/哈希也会很快;这两种方法都是简单的O(N ^ 2)双重比较循环的巨大改进)
注意:以下代码生成表格:
df1=pd.DataFrame({
    'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24'],
    'Fruit':['Banana','Orange','Apple','Celery'],
    'Num':[22.1,8.6,7.6,10.2],
    'Color':['Yellow','Orange','Green','Green'],
})
df2=pd.DataFrame({
    'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24','2013-11-25','2013-11-25'],
    'Fruit':['Banana','Orange','Apple','Celery','Apple','Orange'],
    'Num':[22.1,8.6,7.6,10.2,22.1,8.6],
    'Color':['Yellow','Orange','Green','Green','Red','Orange'],
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接