pandas
对不同DataFrame
/ Series
之间的操作的隐式索引匹配非常好,而且大部分时间都可以使用。
但是,我偶然发现了一个不能按预期工作的例子:
import pandas as pd # 0.21.0
import numpy as np # 1.13.3
x = pd.Series([True, False, True, True], index = range(4))
y = pd.Series([False, True, True, False], index = [2,4,3,5])
# logical AND: this works, symmetric as it should be
pd.concat([x, y, x & y, y & x], keys = ['x', 'y', 'x&y', 'y&x'], axis = 1)
# x y x&y y&x
# 0 True NaN False False
# 1 False NaN False False
# 2 True False False False
# 3 True True True True
# 4 NaN True False False
# 5 NaN False False False
# but logical OR is not symmetric anymore (same for XOR: x^y vs. y^x)
pd.concat([x, y, x | y, y | x], keys = ['x', 'y', 'x|y', 'y|x'], axis = 1)
# x y x|y y|x
# 0 True NaN True False <-- INCONSISTENT!
# 1 False NaN False False
# 2 True False True True
# 3 True True True True
# 4 NaN True False True <-- INCONSISTENT!
# 5 NaN False False False
稍微研究一下,我发现两点看似相关:
bool(np.nan)
等于True
,参见https://stackoverflow.com/a/15686477/2965879 |
已解析为np.bitwise_or
,而不是np.logical_or
,参见https://stackoverflow.com/a/37132854/2965879 但最终,踢球者似乎是在某些时候pandas从nan
投射到False
。看看上面的内容,看来在调用np.bitwise_or
后发生这种情况,而我认为这应该在之前发生?
特别是,使用np.logical_or
无效,因为它错过了pandas
所做的索引对齐,而且我也不希望np.nan or False
等于True
。 (换句话说,答案https://stackoverflow.com/a/37132854/2965879没有帮助。)
我认为如果提供这种精彩的语法糖,它应该尽可能一致*,因此|
应该是对称的。调试真的很难(就像我遇到的那样)突然之间总是对称的东西不再存在了。
最后,问题是:是否有任何可行的解决方法(例如重载某些内容)来挽救x|y == y|x
,理想情况是(松散地说)nan | True == True == True | nan
和nan | False == False == False | nan
?
*即使De Morgan定律无论如何都会崩溃 - ~(x&y)
无法完全匹配~y|~x
,因为NaN只会在索引对齐中出现(因此不受先前否定的影响)。< / p>
答案 0 :(得分:2)
在pandas中进行一些探索之后,我发现有一个名为pandas.core.ops._bool_method_SERIES
的函数,它是包装Series对象的布尔运算符的几个工厂函数之一。
>>> f = pandas.Series.__or__
>>> f #the actual function you call when you do x|y
<function _bool_method_SERIES.<locals>.wrapper at 0x107436bf8>
>>> f.__closure__[0].cell_contents
#it holds a reference to the other function defined in this factory na_op
<function _bool_method_SERIES.<locals>.na_op at 0x107436b70>
>>> f.__closure__[0].cell_contents.__closure__[0].cell_contents
#and na_op has a reference to the built-in function or_
<built-in function or_>
这意味着我们理论上可以定义我们自己的方法来执行逻辑或使用正确的逻辑,首先让我们看看它实际会做什么(记住操作函数如果操作可以& #39; t执行)
def test_logical_or(a,b):
print("**** calling logical_or with ****")
print(type(a), a)
print(type(b), b)
print("******")
raise TypeError("my_logical_or isn't implemented")
#make the wrapper method
wrapper = pd.core.ops._bool_method_SERIES(test_logical_or, None,None)
pd.Series.logical_or = wrapper #insert method
x = pd.Series([True, False, True, True], index = range(4))
y = pd.Series([False, True, True, False], index = [2,4,3,5])
z = x.logical_or(y) #lets try it out!
print(x,y,z, sep="\n")
当它运行时(至少与pandas vs 0.19.1)
**** calling logical_or with ****
<class 'numpy.ndarray'> [True False True True nan nan]
<class 'numpy.ndarray'> [False False False True True False]
******
**** calling logical_or with ****
<class 'bool'> True
<class 'bool'> False
******
Traceback (most recent call last):
...
所以看起来它试图用两个numpy数组调用我们的方法,无论出于何种原因,第二个数据已经用nan
取代了False
但不是第一个可能的原因我们的对称性破裂了。然后当它失败时它再次尝试我以元素为单位。
所以作为最低限度的工作,您可以只显式检查两个参数是否为numpy数组,尝试将第一个nan
条目转换为False
然后return np.logical_or(a,b)
。我将假设如果还有其他情况,我们只会提出错误。
def my_logical_or(a,b):
if isinstance(a, np.ndarray) and isinstance(b, np.ndarray):
a[np.isnan(a.astype(float))] = False
b[np.isnan(b.astype(float))] = False
return np.logical_or(a,b)
else:
raise TypeError("custom logical or is only implemented for numpy arrays")
wrapper = pd.core.ops._bool_method_SERIES(my_logical_or, None,None)
pd.Series.logical_or = wrapper
x = pd.Series([True, False, True, True], index = range(4))
y = pd.Series([False, True, True, False], index = [2,4,3,5])
z = pd.concat([x, y, x.logical_or(y), y.logical_or(x)], keys = ['x', 'y', 'x|y', 'y|x'], axis = 1)
print(z)
# x y x|y y|x
# 0 True NaN True True
# 1 False NaN False False <-- same!
# 2 True False True True
# 3 True True True True
# 4 NaN True True True <-- same!
# 5 NaN False False False
因此,这可能是您的解决方法,我不建议您修改Series.__or__
,因为我们不知道还有谁会使用它,并且不想破坏任何需要默认行为的代码
或者,我们可以修改pandas.core.ops
line 943处的源代码,在同一way it does with other
中为NaN
填充self
值为False(或0),因此我们&#39 ; d改变这一行:
return filler(self._constructor(na_op(self.values, other.values),
index=self.index, name=name))
使用filler(self).values
代替self.values
:
return filler(self._constructor(na_op(filler(self).values, other.values),
index=self.index, name=name))
这也解决了or
和xor
不对称的问题,但是,我不推荐这个,因为它可能会破坏其他代码,我个人没有足够的经验大熊猫确定在不同情况下会发生什么变化。