具有 NaN 相等性比较的 Pandas DataFrames
Posted
技术标签:
【中文标题】具有 NaN 相等性比较的 Pandas DataFrames【英文标题】:Pandas DataFrames with NaNs equality comparison 【发布时间】:2013-10-19 18:59:48 【问题描述】:在对某些函数进行单元测试的情况下,我正在尝试使用 python pandas 建立 2 个 DataFrame 的相等性:
ipdb> expect
1 2
2012-01-01 00:00:00+00:00 NaN 3
2013-05-14 12:00:00+00:00 3 NaN
ipdb> df
identifier 1 2
timestamp
2012-01-01 00:00:00+00:00 NaN 3
2013-05-14 12:00:00+00:00 3 NaN
ipdb> df[1][0]
nan
ipdb> df[1][0], expect[1][0]
(nan, nan)
ipdb> df[1][0] == expect[1][0]
False
ipdb> df[1][1] == expect[1][1]
True
ipdb> type(df[1][0])
<type 'numpy.float64'>
ipdb> type(expect[1][0])
<type 'numpy.float64'>
ipdb> (list(df[1]), list(expect[1]))
([nan, 3.0], [nan, 3.0])
ipdb> df1, df2 = (list(df[1]), list(expect[1])) ;; df1 == df2
False
鉴于我试图测试整个 expect
与整个 df
,包括 NaN
位置,我做错了什么?
比较包括NaN
s 在内的系列/数据帧是否相等的最简单方法是什么?
【问题讨论】:
【参考方案1】:您可以将 assert_frame_equals 与 check_names=False 一起使用(以免检查索引/列名称),如果它们不相等,则会引发:
In [11]: from pandas.testing import assert_frame_equal
In [12]: assert_frame_equal(df, expected, check_names=False)
您可以将其包装在一个函数中,例如:
try:
assert_frame_equal(df, expected, check_names=False)
return True
except AssertionError:
return False
在最近的 pandas 中,此功能已添加为 .equals
:
df.equals(expected)
【讨论】:
我接受了这个而不是其他的(这很有用!),因为我的问题的真正症结在于比较 DataFrames - 我已经对其进行了一些编辑以使其更清楚。 仅供参考,这是最慢的方法,因为它是递归完成的而不是矢量化的(我们用它来测试) @StevePike 不确定我是否理解这一点:“......因为我的问题的真正症结是关于比较 DataFrames”。目前存在的所有解决方案都显示了比较DataFrames
的方法。 验证它们是一个稍微不同的问题。也许我有点迂腐。
这会在对象不相等时给出FutureWarning
:print assert_frame_equal(X, df)
给出输出None
和警告/usr/local/lib/python2.7/site-packages/numpy/core/numeric.py:2367: FutureWarning: numpy equal will not check object identity in the future. The comparison did not return the same result as suggested by the identity (
is)) and will change. return bool(asarray(a1 == a2).all())
@FooBar 有趣,我希望这将在 pandas 0.16 中得到修复。 (即将推出)【参考方案2】:
NaN
的属性之一是NaN != NaN
是True
。
查看this answer,了解使用numexpr
的好方法。
(a == b) | ((a != a) & (b != b))
这样说(用伪代码):
a == b or (isnan(a) and isnan(b))
所以,要么a
等于b
,要么a
和b
都是NaN
。
如果你有小框架,那么assert_frame_equal
就可以了。但是,对于大帧(10M 行)assert_frame_equal
几乎没用。我不得不打断它,它花了很长时间。
In [1]: df = DataFrame(rand(1e7, 15))
In [2]: df = df[df > 0.5]
In [3]: df2 = df.copy()
In [4]: df
Out[4]:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 10000000 entries, 0 to 9999999
Columns: 15 entries, 0 to 14
dtypes: float64(15)
In [5]: timeit (df == df2) | ((df != df) & (df2 != df2))
1 loops, best of 3: 598 ms per loop
timeit
(可能是)所需的单个bool
表示两个DataFrame
s 是否相等:
In [9]: timeit ((df == df2) | ((df != df) & (df2 != df2))).values.all()
1 loops, best of 3: 687 ms per loop
【讨论】:
大概之后你需要.all().all() ? 使用np.all()
要快得多,因为这种比较保证了布尔结果。它使运行时间又增加了约 100 毫秒。如果您使用 pandas 对象的 all()
方法,则性能大约是 3.5 倍。
“保证”的意思是我假设有人没有使用可以从 __eq__
/__ne__
返回 NaN
的子类。
你可以做((df == df2) | ((df != df) & (df2 != df2))).values.ravel().all()
我会把它添加到timeit
。【参考方案3】:
类似于@PhillipCloud 的回答,但写得更多
In [26]: df1 = DataFrame([[np.nan,1],[2,np.nan]])
In [27]: df2 = df1.copy()
它们真的是等价的
In [28]: result = df1 == df2
In [29]: result[pd.isnull(df1) == pd.isnull(df2)] = True
In [30]: result
Out[30]:
0 1
0 True True
1 True True
df1 中不存在的 df2 中的 nan
In [31]: df2 = DataFrame([[np.nan,1],[np.nan,np.nan]])
In [32]: result = df1 == df2
In [33]: result[pd.isnull(df1) == pd.isnull(df2)] = True
In [34]: result
Out[34]:
0 1
0 True True
1 False True
你也可以填写一个你知道不在框架中的值
In [38]: df1.fillna(-999) == df1.fillna(-999)
Out[38]:
0 1
0 True True
1 True True
【讨论】:
如果你有两个不相等的值,我认为这将给出 True 因为它们都是非空的:s @Andy Hayden 是正确的:result[pd.isnull(df1) == pd.isnull(df2)] = True
应该是 result[pd.isnull(df1) & pd.isnull(df2)] = True
【参考方案4】:
df.fillna(0) == df2.fillna(0)
您可以使用fillna()
。 Documenation here.
from pandas import DataFrame
# create a dataframe with NaNs
df = DataFrame(['a': 1, 'b': 2, 'a': 5, 'b': 10, 'c': 20])
df2 = df
# comparison fails!
print df == df2
# all is well
print df.fillna(0) == df2.fillna(0)
【讨论】:
0 感觉一般来说是一件危险的事情,但用一些唯一的字符串或标识符填充听起来是个好策略。对于小型案例,此策略比公认的答案要快,但对于大型案例要慢得多【参考方案5】:任何使用 == 与 np.NaN 的相等比较都是 False,甚至 np.NaN == np.NaN 也是 False。
简单地说,df1.fillna('NULL') == df2.fillna('NULL')
,如果 'NULL' 不是原始数据中的值。
为了安全起见,请执行以下操作:
示例 a) 比较两个具有 NaN 值的数据帧
bools = (df1 == df2)
bools[pd.isnull(df1) & pd.isnull(df2)] = True
assert bools.all().all()
示例 b) 过滤 df1 中与 df2 不匹配的行
bools = (df1 != df2)
bools[pd.isnull(df1) & pd.isnull(df2)] = False
df_outlier = df1[bools.all(axis=1)]
(注意:这是错误的 - bools[pd.isnull(df1) == pd.isnull(df2)] = False)
【讨论】:
如果您在答案中添加更多文字,您的答案会看起来更好,也许可以解释您在做什么。 这种策略对于小案例来说比公认的答案要快,但对于大案例来说要慢得多以上是关于具有 NaN 相等性比较的 Pandas DataFrames的主要内容,如果未能解决你的问题,请参考以下文章