两个数据框之间的差异

77

我需要逐行比较两个不同大小的数据框,并打印出不匹配的行。 我们来看下面这两个:

df1 = DataFrame({
'Buyer': ['Carl', 'Carl', 'Carl'],
'Quantity': [18, 3, 5, ]})

df2 = DataFrame({
'Buyer': ['Carl', 'Mark', 'Carl', 'Carl'],
'Quantity': [2, 1, 18, 5]})
什么是遍历df2行并打印出不在df1中的行的最有效方式,例如?
Buyer     Quantity 
Carl         2
Mark         1

重要提示:我不希望有行:

Buyer     Quantity 
Carl         3

在差异中包含的内容:

我已经尝试过:逐行比较两个长度不同的数据框,并为每一行添加具有相等值的列比较两个数据帧并将它们的差异输出到一侧,但这些都与我的问题不匹配。

8个回答

135

merge方法可将两个数据框使用'outer'方式合并,并传递参数indicator=True,这将告诉您行是否同时存在于两个数据框中、只存在于左侧或只存在于右侧。之后,您可以过滤合并后的数据框。

In [22]:
merged = df1.merge(df2, indicator=True, how='outer')
merged[merged['_merge'] == 'right_only']

Out[22]:
  Buyer  Quantity      _merge
3  Carl         2  right_only
4  Mark         1  right_only

1
如果一行在一个数据框中出现了一次,在另一个数据框中出现了两次,这种方法将无法检测到。理想情况下,其中一个条目应标记为“仅在右侧”,而另一个条目应标记为“存在于两个数据框中”。 - Tobias Bergkvist
1
请参考@TobiasBergkvist下面的答案,以解决重复行问题。 - PeJota

30

你可能会发现这是最好的:

df2[ ~df2.isin(df1)].dropna()

如果DataFrame中存在缺失值,则此方法无法正常工作。 - csteel

11

@EdChum的答案已经很清楚了。但是使用not 'both'的条件更有意义,而且您不需要关注比较的顺序,这才是真正的差异所应该具备的特点。为了回答你的问题:

merged = df1.merge(df2, indicator=True, how='outer')
merged.loc = [merged['_merge'] != 'both']

1
这是一个完整的反连接,OP要求右反连接。 - cs95
3
第二行代码有误,应该替换为:merged.loc[merged['_merge'] != 'both'] - John Stud

9
diff = set(zip(df2.Buyer, df2.Quantity)) - set(zip(df1.Buyer, df1.Quantity))

这是我首先想到的解决方案。然后,您可以将差异集放回DF以供演示。

9

8
这在索引不相等的情况下不起作用(也就是说,两个数据框行数不同)。 - Ali Naderi
1
是的,根据文档页面中的注释:只能比较标记相同(即形状相同,行和列标签相同)的数据帧。 - gwelter

4
如果您只关心将新买家添加到其他数据框中,请尝试以下操作:
df_delta=df2[df2['Buyer'].apply(lambda x: x not in df1['Buyer'].values)]

2

一个重要的边界情况

考虑以下情况,第二个数据帧中有一个额外的重复条目:('Carl', 5)

df1 = DataFrame({ 'Buyer':    ['Carl', 'Carl', 'Carl'],
                  'Quantity': [   18 ,     3 ,     5 ]  })

df2 = DataFrame({ 'Buyer':    ['Carl', 'Mark', 'Carl', 'Carl', 'Carl'],
                  'Quantity': [    2 ,     1 ,    18 ,     5 ,     5 ]  })

EdChum的回答将为您提供以下内容:

merged = df1.merge(df2, indicator=True, how='outer')
print(merged[merged['_merge'] == 'right_only'])

  Buyer  Quantity      _merge
4  Carl         2  right_only
5  Mark         1  right_only

正如您所看到的,该解决方案忽略了额外的重复值,这取决于您正在做什么,这是您想要避免的。

以下是更有可能满足您需求的解决方案:

df1['duplicate_counter'] = df1.groupby(list(df1.columns)).cumcount()
df2['duplicate_counter'] = df2.groupby(list(df2.columns)).cumcount()
merged = df1.merge(df2, indicator=True, how='outer')
merged[merged['_merge'] == 'right_only']

  Buyer  Quantity  duplicate_counter      _merge
3  Carl         2                  0  right_only
4  Mark         1                  0  right_only
5  Carl         5                  1  right_only

重复计数器确保每行都是唯一的,这意味着不会删除重复值。合并后,您可以删除重复计数器。

1

还有datacompy等工具。它可以导出一些基于字符串的比较报告,就像这样:

DataComPy Comparison
--------------------

DataFrame Summary
-----------------

  DataFrame  Columns  Rows
0  original        5     6
1       new        4     5

Column Summary
--------------

Number of columns in common: 4
Number of columns in original but not in new: 1
Number of columns in new but not in original: 0

Row Summary
-----------

Matched on: acct_id
Any duplicates on match values: Yes
Absolute Tolerance: 0.0001
Relative Tolerance: 0
Number of rows in common: 5
Number of rows in original but not in new: 1
Number of rows in new but not in original: 0

Number of rows with some compared columns unequal: 5
Number of rows with all compared columns equal: 0

Column Comparison
-----------------

Number of columns compared with some values unequal: 3
Number of columns compared with all values equal: 1
Total number of values which compare unequal: 7

Columns with Unequal Values or Types
------------------------------------

       Column original dtype new dtype  # Unequal  Max Diff  # Null Diff
0  dollar_amt        float64   float64          1    0.0500            0
1   float_fld        float64   float64          4    0.0005            3
2        name         object    object          2    0.0000            0

Sample Rows with Unequal Values
-------------------------------

       acct_id  dollar_amt (original)  dollar_amt (new)
0  10000001234                 123.45             123.4

       acct_id  float_fld (original)  float_fld (new)
0  10000001234            14530.1555        14530.155
5  10000001238                   NaN          111.000
2  10000001236                   NaN            1.000
1  10000001235                1.0000              NaN

       acct_id name (original)            name (new)
0  10000001234  George Maharis  George Michael Bluth
3  10000001237      Bob Loblaw         Robert Loblaw

Sample Rows Only in original (First 10 Columns)
-----------------------------------------------

       acct_id  dollar_amt           name  float_fld    date_fld
4  10000001238        1.05  Lucille Bluth        NaN  2017-01-01

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