比较两个pandas数据框的差异

86

我有一个脚本可以更新5-10列的数据,但是有时起始csv文件和结束csv文件完全相同,所以我不想写入一个完全相同的csv文件,而是希望它什么也不做。

我该如何比较两个数据框来检查它们是否相同?

csvdata = pandas.read_csv('csvfile.csv')
csvdata_old = csvdata

# ... do stuff with csvdata dataframe

if csvdata_old != csvdata:
    csvdata.to_csv('csvfile.csv', index=False)

有任何想法吗?


1
我不是Pandas专家,但普通的相等比较应该可以工作吧? - aIKid
我已经查看了相等性,但是不确定如何使用该函数,同时在搜索中也找不到任何内容 :( - Ryflex
10个回答

88

您还需要小心创建DataFrame的副本,否则csvdata_old将会被csvdata更新(因为它们指向同一对象):

csvdata_old = csvdata.copy()

要检查它们是否相等,您可以像这个答案中所示使用assert_frame_equal:

from pandas.util.testing import assert_frame_equal
assert_frame_equal(csvdata, csvdata_old)

你可以使用类似以下的函数来包装它:
try:
    assert_frame_equal(csvdata, csvdata_old)
    return True
except:  # appeantly AssertionError doesn't catch all
    return False

有关更好的方法进行了讨论...

1
由于某些原因,我收到了异常:Exception: Can only compare identically-labeled DataFrame objects - Ryflex
4
那么这就给出了答案,对吧?它的意思是标签(行和列的名称/值)不相同,因此DataFrames不能相同。在Andy的封装中再添加一行except代码:except Exception: return False - TomAugspurger
@Hyflex 是的,正如Tom指出的那样,只需移除AssertionError(之前只会在遇到AssertionError时停止并抛出其他异常)... - Andy Hayden
好的,我已经修复了标签(没有意识到我需要重新排序csvdat_old的标签顺序),并尝试将其合并到我的实际数据中,但即使输出的数据相同,它也会更新。有没有办法找到/查看到底是什么“不同”? - Ryflex
1
pandas.util.testing不再可用,现在应该使用pandas.testing。 - Gabi
显示剩余2条评论

49

我不确定以下内容是否有所帮助,但是我编写了一个快速的Python方法,可以返回两个具有相同列和形状的数据框之间的差异。

def get_different_rows(source_df, new_df):
    """Returns just the rows from the new dataframe that differ from the source dataframe"""
    merged_df = source_df.merge(new_df, indicator=True, how='outer')
    changed_rows_df = merged_df[merged_df['_merge'] == 'right_only']
    return changed_rows_df.drop('_merge', axis=1)

4
这正是我在寻找的答案。非常感谢。 - FinancialRadDeveloper
这太棒了!但我可以问一下如何不忽略数据类型吗?例如,如果在source_df上有一个值为4,而在new_df上有一个值为4.0,它仍应返回一个dataframe,因为这意味着存在差异,谢谢! - Ice Bear
1
谢谢您。而且这个实用函数真的很有用! - Suraj

21

没有这样的东西,它的效果非常好。因为它只有一行,比起五行更好。 - Ryflex
5
它在浮点数上如何工作?我希望有一个精度参数。 - jf328
2
一定有比那更好的东西。仅仅一个 false 并不是一个令人满意的答案。 - Nickpick
5
不确定大多数人是否想要这个.. DataFrame.equals 进行非常深入的比较。例如.. 我有两个值相等的数据帧,但事实证明 DataFrames 也有一些名为 Axis 1IntBlockObjectBlock 的参数,在 pd._data 下定义。如果这些在两个对象之间不同.. 将导致返回 False。 - alpha_989
@Nickpick DataFrame.eq 可能很适合用于定位不相等的元素? - subhacom
df.eq(df2).all().all()帮助克服了df.equals的问题,可以仅比较内容。 - citynorman

17

使用以下命令进行检查:df_1.equals(df_2) # 返回True或False下面有详细信息

In [45]: import numpy as np

In [46]: import pandas as pd

In [47]: np.random.seed(5)

In [48]: df_1= pd.DataFrame(np.random.randn(3,3))

In [49]: df_1
Out[49]: 
          0         1         2
0  0.441227 -0.330870  2.430771
1 -0.252092  0.109610  1.582481
2 -0.909232 -0.591637  0.187603

In [50]: np.random.seed(5)

In [51]: df_2= pd.DataFrame(np.random.randn(3,3))

In [52]: df_2
Out[52]: 
          0         1         2
0  0.441227 -0.330870  2.430771
1 -0.252092  0.109610  1.582481
2 -0.909232 -0.591637  0.187603

In [53]: df_1.equals(df_2)
Out[53]: True


In [54]: df_3= pd.DataFrame(np.random.randn(3,3))

In [55]: df_3
Out[55]: 
          0         1         2
0 -0.329870 -1.192765 -0.204877
1 -0.358829  0.603472 -1.664789
2 -0.700179  1.151391  1.857331

In [56]: df_1.equals(df_3)
Out[56]: False

有一个注意点:index.names没有被比较。请参考我的回答以获取更多细节:https://dev59.com/zWIj5IYBdhLWcg3wwHpr#43420842 - Dennis Golomazov

11

更准确的比较应该将索引名称单独检查,因为DataFrame.equals不会测试它。所有其他属性(索引值(单/多索引),值,列,数据类型)都由其正确地检查。

df1 = pd.DataFrame([[1, 'a'], [2, 'b'], [3, 'c']], columns=['num', 'name'])
df1 = df1.set_index('name')
df2 = pd.DataFrame([[1, 'a'], [2, 'b'], [3, 'c']], columns=['num', 'another_name'])
df2 = df2.set_index('another_name')

df1.equals(df2)
True

df1.index.names == df2.index.names
False
注意:使用index.names而不是index.name,也可以适用于具有多级索引的数据框架。

8

要提取对称差异:

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

例如:
df1 = pd.DataFrame({
    'num': [1, 4, 3],
    'name': ['a', 'b', 'c'],
})
df2 = pd.DataFrame({
    'num': [1, 2, 3],
    'name': ['a', 'b', 'd'],
})

将产生:

enter image description here

注意:在下一个pandas版本发布之前,为避免有关如何设置未来排序参数的警告,请添加sort=False参数。 如下所示:

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

7

这个操作比较两个数据框中的,请注意表格之间的行数和列数必须相同。

comparison_array = table.values == expected_table.values
print (comparison_array)

>>>[[True, True, True]
    [True, False, True]]

if False in comparison_array:
    print ("Not the same")

#Return the position of the False values
np.where(comparison_array==False)

>>>(array([1]), array([1]))

您可以使用此索引信息返回表格之间不匹配的值。由于它是从零开始编号的,它指的是第2个数组中的第2个位置,这是正确的。


这里的主要好处是您可以使用np.where返回的索引位置来确定表格不匹配的确切位置,并向用户发出警报。 - Tristan Forward

7
在我的情况下,我遇到了一个奇怪的错误,即使索引、列名和值都相同,DataFrames也不匹配。我追踪到数据类型,似乎pandas有时会使用不同的数据类型,导致这样的问题。
例如: param2 = pd.DataFrame({'a': [1]}) param1 = pd.DataFrame({'a': [1], 'b': [2], 'c': [2], 'step': ['alpha']}) 如果您检查param1.dtypesparam2.dtypes,您会发现'a'对于param1object类型,而对于param2int64类型。现在,如果您使用param1param2的组合进行一些操作,则数据框的其他参数将偏离默认值。
因此,在生成最终数据框之后,即使打印出来的实际值相同,final_df1.equals(final_df2)可能会变成不相等,因为那些小的参数,如Axis 1ObjectBlockIntBlock可能不相同。
解决这个问题并比较值的简单方法是使用 final_df1==final_df2
但是,这将逐个元素进行比较,因此如果您将其用于断言例如在pytest中,则不起作用。
总之,一个好的方法是 all(final_df1 == final_df2)
这将逐个元素进行比较,同时忽略不重要的参数。
如果您的值和索引相同,但final_df1.equals(final_df2)显示为False,则可以使用final_df1._datafinal_df2._data来检查数据框的其余元素。

有人知道为什么从CSV读取的数据框与从Excel工作簿读取的数据框显示不同,即使它们是相同的吗? - Aaron England

1
我希望下面的代码片段能够帮到您!
import pandas as pd
import datacompy

df_old_original = pd.DataFrame([[1, 1, 1, 1], [2, 2, 2, 2], [7, 7, 7, 7], [3, 3, 3, 3], [4, 4, 4, 4], [7, 7, 7, 7], [5, 5, 5, 5], [6, 6, 6, 6]], columns=['A', 'B', 'C', 'D'], index=[0, 1, 2, 3, 4, 5, 6, 7], dtype=object)
df_new_original = pd.DataFrame([[None, None, None, None], [1, 1, 1, 1], [2, 2, 2, 2], [8, 8, 8, 8], [3, 3, 3, 3], [4, 4, 4, 4], [7, 7, 7, 7], [5, 5, 5, 5], [None, None, None, None]], columns=['A', 'B', 'C', 'D'], index=[0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=object)

compare = datacompy.Compare(df_old_original, df_new_original, join_columns=['A', 'B', 'C', 'D'], abs_tol=0, rel_tol=0, df1_name='Old', df2_name='New')
changes_in_old_df = compare.df1_unq_rows
changes_in_new_df = compare.df2_unq_rows
print(changes_in_old_df)
print(changes_in_new_df)
print(Compare.report())

1

迄今为止提供的一些答案的总结和分析

一些提供的答案未能检测到数据集中的差异,因为它们没有考虑索引值。让我们构建两个简单的数据集并分析一些提出的方法:

import pandas as pd

df_1 = pd.DataFrame({'a': [1,1], 'b': [2,1]})
df_1
a b
0 1 2
1 1 1
df_2 = pd.DataFrame({'a': [1,1], 'b': [1,2]})
df_2
a b
0 1 1
1 1 2

这两个数据框之间的差异在于df_1中的第一行明显与df_2中的第一行不同。如果我们应用Tom Chapin提出的方法,它将无法检测到差异,因为它仅比较每行中每列的值的组合,而不考虑行位置(索引)。因此,在这个例子中,由于a和b列的组合(1,2)已经存在于df_1中,当与df_2合并时,它被视为重复项,即使它出现在索引1而不是0。出于同样的原因,leersej提出的方法也失败了(您可以尝试应用这些方法并查看结果)。.equalsSurya提出的方法能够检测到差异,在比较中返回False,但正如Dennis Golomazov指出的那样,如果您还想检查索引的相等性,您应该添加另一个检查,就像他的答案中所示。最终,如果您想确保数据框在相同的索引处具有相同的列值,我认为最好的方法是执行直接匹配,就像alpha_989建议的那样,但是使用all()方法检查两者之间的最终相等性失败了,因为它遍历列名而不是比较结果的布尔值。实际上,all(df_1 == df_2)错误地返回True。归根结底,要安全地检查两个数据框是否相等(仅使用pandas库),您应该:

# 1 compare the two datasets
>>> comparison = (df_1 == df_2)
# 2 - see what columns are matching and what aren't
>>> comparison.all()
a     True
b    False
dtype: bool

# 3 - compare if all columns are matching
>>> comparison.all().all() # equivalent all(comparison.all())
False
# BONUS - row indexes that aren't matching (more info here: https://dev59.com/OFQK5IYBdhLWcg3wQdvI#52173171)
>>> comparison.T.all()[~comparison.T.all()].index
Int64Index([0, 1], dtype='int64')

简而言之

为了检查两个数据框的相等性,同时考虑行索引,您可以使用.equals方法,如此处所示,或者:

comparison = (df_1 == df_2)
equality = comparison.all().all()
not_matching_row_idxs = comparison.T.all()[~comparison.T.all()].index

这种方法还允许您查找不匹配的行索引。

注意: 此方法仅适用于具有相同索引值的数据帧。


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