【问题标题】:Pandas DataFrames with NaNs equality comparison具有 NaN 相等性比较的 Pandas DataFrames
【发布时间】: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 位置,我做错了什么?

比较包括NaNs 在内的系列/数据帧是否相等的最简单方法是什么?

【问题讨论】:

    标签: python pandas equality nan


    【解决方案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 的方法。 验证它们是一个稍微不同的问题。也许我有点迂腐。
    • 这会在对象不相等时给出FutureWarningprint 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 != NaNTrue

    查看this answer,了解使用numexpr 的好方法。

    (a == b) | ((a != a) & (b != b))
    

    这样说(用伪代码):

    a == b or (isnan(a) and isnan(b))
    

    所以,要么a 等于b,要么ab 都是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 表示两个DataFrames 是否相等:

    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) &amp; (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) &amp; 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)

    【讨论】:

    • 如果您在答案中添加更多文字,您的答案会看起来更好,也许可以解释您在做什么。
    • 这种策略对于小案例来说比公认的答案要快,但对于大案例来说要慢得多
    猜你喜欢
    • 2019-02-05
    • 2012-06-04
    • 1970-01-01
    • 2019-02-25
    • 2015-06-10
    • 2023-01-10
    • 2020-11-18
    • 2012-02-16
    • 1970-01-01
    相关资源
    最近更新 更多