pandas.DataFrame.equals的契约

8 投票
2 回答
11885 浏览
提问于 2025-04-29 12:00

我有一个简单的测试案例,里面有一个函数,它会返回一个可能包含NaN(缺失值)的数据框(df)。我在测试这个输出和预期输出是否相等。

>>> output
Out[1]: 
      r   t  ts  tt  ttct
0  2048  30   0  90     1
1  4096  90   1  30     1
2     0  70   2  65     1

[3 rows x 5 columns]
>>> expected
Out[2]: 
      r   t  ts  tt  ttct
0  2048  30   0  90     1
1  4096  90   1  30     1
2     0  70   2  65     1

[3 rows x 5 columns]
>>> output == expected
Out[3]: 
      r     t    ts    tt  ttct
0  True  True  True  True  True
1  True  True  True  True  True
2  True  True  True  True  True

不过,我不能仅仅依靠==这个运算符,因为NaN的存在让我无法这样做。我原以为,使用equals方法是解决这个问题的合适方式。根据文档

pandas.DataFrame.equals
DataFrame.equals(other)
Determines if two NDFrame objects contain the same elements. NaNs in the same location are considered equal.

然而:

>>> expected.equals(log_events)
Out[4]: False

稍微深入研究一下,就会发现这两个数据框之间的差异:

>>> output._data
Out[5]: 
BlockManager
Items: Index([u'r', u't', u'ts', u'tt', u'ttct'], dtype='object')
Axis 1: Int64Index([0, 1, 2], dtype='int64')
FloatBlock: [r], 1 x 3, dtype: float64
IntBlock: [t, ts, tt, ttct], 4 x 3, dtype: int64
>>> expected._data
Out[6]: 
BlockManager
Items: Index([u'r', u't', u'ts', u'tt', u'ttct'], dtype='object')
Axis 1: Int64Index([0, 1, 2], dtype='int64')
IntBlock: [r, t, ts, tt, ttct], 5 x 3, dtype: int64

如果把输出的浮点数强制转换为整数,或者把预期的整数强制转换为浮点数,测试就能通过。

显然,对于相等的理解有不同的角度,而DataFrame.equals所执行的测试在某些情况下可能是有用的。不过,==DataFrame.equals之间的差异让我感到困惑,似乎存在不一致的地方。在伪代码中,我本来期待它们的行为是一致的:

(self.index == other.index).all() \
and (self.columns == other.columns).all() \
and (self.values.fillna(SOME_MAGICAL_VALUE) == other.values.fillna(SOME_MAGICAL_VALUE)).all().all()

然而,事实并非如此。我是不是想错了,还是说这是Pandas API中的一个不一致之处?另外,考虑到可能存在NaN,我应该进行什么样的测试呢?

暂无标签

2 个回答

0

我使用了一种变通的方法,深入研究了MagicMock实例:

assert mock_instance.call_count == 1
call_args = mock_instance.call_args[0]
call_kwargs = mock_instance.call_args[1]
pd.testing.assert_frame_equal(call_kwargs['dataframe'], pd.DataFrame())
6

.equals() 的意思就是它会检查两个东西是否完全相等。它会比较元素的值、NaN(不是一个数字)和NaT(不是时间)的位置、数据类型是否相同,以及索引是否一致。可以把它想象成在检查 df is df2 这种类型的测试,但它们不一定要是同一个对象。换句话说,df.equals(df.copy()) 总是会返回 True。

你的例子之所以失败,是因为不同的数据类型是不相等的(虽然它们可能是等价的)。所以你可以使用 com.array_equivalent 来处理这个问题,或者如果没有 nans 的话,可以用 (df == df2).all().all()

这个方法是用来替代 np.array_equal 的,因为后者在处理 NaN 的位置检测时有问题(还有对象数据类型)。

这个方法主要是内部使用的。不过,如果你希望有一个改进,让它在元素等价(比如在 == 的意义上相等,并且 NaN 的位置也匹配)时也能工作,请在 GitHub 上提个问题。(如果能提交一个 PR 就更好了!)

撰写回答