Pandas中用于布尔索引的逻辑运算符

我正在Pandas中使用布尔索引。

问题是为什么说:

a[(a['some_column']==some_number) & (a['some_other_column']==some_other_number)]

很好,但是

a[(a['some_column']==some_number) and (a['some_other_column']==some_other_number)]

退出错误?

例子:

a = pd.DataFrame({'x':[1,1],'y':[10,20]})


In: a[(a['x']==1)&(a['y']==10)]
Out:    x   y
0  1  10


In: a[(a['x']==1) and (a['y']==10)]
Out: ValueError: The truth value of an array with more than one element is ambiguous.     Use a.any() or a.all()
427973 次浏览

当你说

(a['x']==1) and (a['y']==10)

你是在隐式地要求Python将(a['x']==1)(a['y']==10)转换为布尔值。

NumPy数组(长度大于1)和Pandas对象(例如Series)没有布尔值——换句话说,它们会引发

ValueError:数组的真值不明确。使用a.empty, a.any()或a.all()。

当用作布尔值时。这是因为它是不清楚什么时候应该是真或假。有些用户可能会假设它们是True,如果它们的长度非零,比如Python列表。其他人可能希望它只有在所有的元素为True时才为True。如果任何的元素为True,其他人可能希望它为True。

由于有太多相互冲突的期望,NumPy和Pandas的设计人员拒绝猜测,而是抛出ValueError。

相反,你必须是显式的,通过调用empty()all()any()方法来指示你想要的行为。

然而,在这种情况下,看起来你不想要布尔值,你想要element-wise logical-and。这就是&二进制操作符的作用:

(a['x']==1) & (a['y']==10)

返回一个布尔数组。


顺便说一下,作为alexpmil笔记, 括号是必须的,因为&运算符优先级大于==

如果没有括号,a['x']==1 & a['y']==10将被计算为a['x'] == (1 & a['y']) == 10,这将等价于链式比较(a['x'] == (1 & a['y'])) and ((1 & a['y']) == 10)。这是一个Series and Series形式的表达式。 在两个Series中使用and将再次触发上面相同的ValueError。这就是为什么括号是必须的

TLDR;Pandas中的逻辑运算符是__ABC0, __ABC1和__ABC2,括号(...)很重要!

Python的andornot逻辑运算符被设计用来处理标量。因此Pandas必须做得更好,重写按位操作符来实现此功能的矢量化(按元素的)版本。

因此在python中(exp1exp2是表达式,计算结果为布尔值)…

exp1 and exp2              # Logical AND
exp1 or exp2               # Logical OR
not exp1                   # Logical NOT

...将转化为……

exp1 & exp2                # Element-wise logical AND
exp1 | exp2                # Element-wise logical OR
~exp1                      # Element-wise logical NOT

熊猫。

如果在执行逻辑操作的过程中得到ValueError,则需要使用括号进行分组:

(exp1) op (exp2)

例如,

(df['col1'] == x) & (df['col2'] == y)

等等。


< >强逻辑索引< / >强:一个常见的操作是通过逻辑条件计算布尔掩码来过滤数据。Pandas提供了三个操作符:&用于逻辑与,|用于逻辑或,~用于逻辑非。

考虑以下设置:

np.random.seed(0)
df = pd.DataFrame(np.random.choice(10, (5, 3)), columns=list('ABC'))
df


A  B  C
0  5  0  3
1  3  7  9
2  3  5  2
3  4  7  6
4  8  8  1

逻辑和

对于上面的df,假设你想返回A <5和B >这是通过分别计算每个条件的掩码,并对它们进行and运算来完成的。

< p > 重载位&操作符 < br > 在继续之前,请注意文档的这个特殊摘录,它的状态是

另一个常见的操作是使用布尔向量来过滤 数据。操作符是:|用于or&用于and~用于not。< >强这些 必须使用括号分组,因为默认Python将 将诸如df.A > 2 & df.B < 3这样的表达式求值为df。一个比;(2, df.B) & lt;3,而期望求值顺序为(df. 1)一个比;2),(df。B & lt; 3) < /代码>。< / p >

因此,考虑到这一点,按元素划分的逻辑与可以用按位运算符&实现:

df['A'] < 5


0    False
1     True
2     True
3     True
4    False
Name: A, dtype: bool


df['B'] > 5


0    False
1     True
2    False
3     True
4     True
Name: B, dtype: bool

(df['A'] < 5) & (df['B'] > 5)


0    False
1     True
2    False
3     True
4    False
dtype: bool

接下来的过滤步骤很简单,

df[(df['A'] < 5) & (df['B'] > 5)]


A  B  C
1  3  7  9
3  4  7  6

括号用于覆盖按位操作符的默认优先顺序,其优先级高于条件操作符<>。请参阅python文档中的运算符优先级部分。

如果不使用括号,表达式的求值将不正确。例如,如果你不小心尝试做一些事情

df['A'] < 5 & df['B'] > 5

它被解析为

df['A'] < (5 & df['B']) > 5

变成了,

df['A'] < something_you_dont_want > 5

这就变成了(参见链式运算符比较上的python文档),

(df['A'] < something_you_dont_want) and (something_you_dont_want > 5)

变成了,

# Both operands are Series...
something_else_you_dont_want1 and something_else_you_dont_want2

这把

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

所以,不要犯这样的错误!1

< p > 避免括号分组 < br > 解决方法其实很简单。大多数操作符都有相应的dataframe绑定方法。如果使用函数而不是条件操作符构建单个掩码,则不再需要按paren分组来指定求值顺序:

df['A'].lt(5)


0     True
1     True
2     True
3     True
4    False
Name: A, dtype: bool


df['B'].gt(5)


0    False
1     True
2    False
3     True
4     True
Name: B, dtype: bool

df['A'].lt(5) & df['B'].gt(5)


0    False
1     True
2    False
3     True
4    False
dtype: bool

参见灵活的比较。部分。总之,我们有

╒════╤════════════╤════════════╕
│    │ Operator   │ Function   │
╞════╪════════════╪════════════╡
│  0 │ >          │ gt         │
├────┼────────────┼────────────┤
│  1 │ >=         │ ge         │
├────┼────────────┼────────────┤
│  2 │ <          │ lt         │
├────┼────────────┼────────────┤
│  3 │ <=         │ le         │
├────┼────────────┼────────────┤
│  4 │ ==         │ eq         │
├────┼────────────┼────────────┤
│  5 │ !=         │ ne         │
╘════╧════════════╧════════════╛

避免括号的另一个选项是使用DataFrame.query(或eval):

df.query('A < 5 and B > 5')


A  B  C
1  3  7  9
3  4  7  6

我已经在广泛中记录了queryeval

< p > operator.and_ < br > 允许您以功能性的方式执行此操作。在内部调用Series.__and__,对应于位操作符
import operator


operator.and_(df['A'] < 5, df['B'] > 5)
# Same as,
# (df['A'] < 5).__and__(df['B'] > 5)


0    False
1     True
2    False
3     True
4    False
dtype: bool


df[operator.and_(df['A'] < 5, df['B'] > 5)]


A  B  C
1  3  7  9
3  4  7  6

你通常不需要这个,但是知道它是有用的。

< p > 概括:__ABC0(和logical_and.reduce) < br > 另一种替代方法是使用np.logical_and,它也不需要括号分组:

np.logical_and(df['A'] < 5, df['B'] > 5)


0    False
1     True
2    False
3     True
4    False
Name: A, dtype: bool


df[np.logical_and(df['A'] < 5, df['B'] > 5)]


A  B  C
1  3  7  9
3  4  7  6

np.logical_and是一个ufunc通用函数,大多数ufuncs都有一个reduce方法。这意味着如果你对AND有多个掩码,则更容易泛化logical_and。例如,要用&屏蔽m1m2m3,你必须这样做

m1 & m2 & m3

然而,一个更简单的选择是

np.logical_and.reduce([m1, m2, m3])

这很强大,因为它让你在此基础上构建更复杂的逻辑(例如,在列表理解中动态生成掩码并添加所有掩码):

import operator


cols = ['A', 'B']
ops = [np.less, np.greater]
values = [5, 5]


m = np.logical_and.reduce([op(df[c], v) for op, c, v in zip(ops, cols, values)])
m
# array([False,  True, False,  True, False])


df[m]
A  B  C
1  3  7  9
3  4  7  6

1 -我知道我在这一点上喋喋不休,但请耐心听我说。这是一个veryvery常见的初学者错误,必须非常彻底地解释。


逻辑或

对于上面的df,假设你想返回A == 3或B == 7的所有行。

按位重载|

df['A'] == 3


0    False
1     True
2     True
3    False
4    False
Name: A, dtype: bool


df['B'] == 7


0    False
1     True
2    False
3     True
4    False
Name: B, dtype: bool

(df['A'] == 3) | (df['B'] == 7)


0    False
1     True
2     True
3     True
4    False
dtype: bool


df[(df['A'] == 3) | (df['B'] == 7)]


A  B  C
1  3  7  9
2  3  5  2
3  4  7  6

如果你还没有,也请阅读上面关于逻辑和的部分,所有的警告都适用于这里。

或者,可以使用

df[df['A'].eq(3) | df['B'].eq(7)]


A  B  C
1  3  7  9
2  3  5  2
3  4  7  6
< p > operator.or_ < br >

在引擎盖下面调用Series.__or__
operator.or_(df['A'] == 3, df['B'] == 7)
# Same as,
# (df['A'] == 3).__or__(df['B'] == 7)


0    False
1     True
2     True
3     True
4    False
dtype: bool


df[operator.or_(df['A'] == 3, df['B'] == 7)]


A  B  C
1  3  7  9
2  3  5  2
3  4  7  6
< p > np.logical_or < br > 对于两种情况,使用logical_or:

np.logical_or(df['A'] == 3, df['B'] == 7)


0    False
1     True
2     True
3     True
4    False
Name: A, dtype: bool


df[np.logical_or(df['A'] == 3, df['B'] == 7)]


A  B  C
1  3  7  9
2  3  5  2
3  4  7  6

对于多个掩码,使用logical_or.reduce:

np.logical_or.reduce([df['A'] == 3, df['B'] == 7])
# array([False,  True,  True,  True, False])


df[np.logical_or.reduce([df['A'] == 3, df['B'] == 7])]


A  B  C
1  3  7  9
2  3  5  2
3  4  7  6

逻辑不

给一个面具,如

mask = pd.Series([True, True, False])

如果需要反转每个布尔值(以便最终结果为[False, False, True]),则可以使用下面的任何方法。

~

~mask


0    False
1    False
2     True
dtype: bool

同样,表达式需要加括号。

~(df['A'] == 3)


0     True
1    False
2    False
3     True
4     True
Name: A, dtype: bool

这在内部调用

mask.__invert__()


0    False
1    False
2     True
dtype: bool

但不要直接使用。

< p > operator.inv < br > 在内部调用系列上的__invert__
operator.inv(mask)


0    False
1    False
2     True
dtype: bool
< p > np.logical_not < br > 这是numpy的变体

np.logical_not(mask)


0    False
1    False
2     True
dtype: bool

注意,np.logical_and可以替换np.bitwise_andlogical_or可以替换bitwise_orlogical_not可以替换invert

Pandas中用于布尔索引的逻辑运算符

重要的是要意识到你不能在pandas.Series或__abc4上使用任何Python 逻辑运算符 (andornot)(同样你也不能在有多个元素的__abc5上使用它们)。你不能使用它们的原因是因为它们隐式地在操作数上调用bool,这会抛出一个异常,因为这些数据结构决定了数组的布尔值是模糊的:

>>> import numpy as np
>>> import pandas as pd
>>> arr = np.array([1,2,3])
>>> s = pd.Series([1,2,3])
>>> df = pd.DataFrame([1,2,3])
>>> bool(arr)
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
>>> bool(s)
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
>>> bool(df)
ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

我确实更广泛地覆盖了这个在我对“一个系列的真值”的回答中是模糊的。使用a.empty a.bool (), a.item (), a.any()或所有()“;问+

NumPy的逻辑函数

然而,NumPy为这些操作符提供了元素操作等价物,可以在numpy.arraypandas.Seriespandas.DataFrame或任何其他(符合)numpy.array子类上使用:

因此,本质上,我们应该使用(假设df1df2是Pandas数据框架):

np.logical_and(df1, df2)
np.logical_or(df1, df2)
np.logical_not(df1)
np.logical_xor(df1, df2)

布尔值的位函数和位操作符

然而,如果你有布尔NumPy数组,Pandas Series或Pandas dataframe,你也可以使用逐元素的位函数(对于布尔值,它们是-或至少应该是-与逻辑函数难以区分):

通常使用运算符。然而,当与比较操作符结合使用时,必须记住将比较符括在括号中,因为位操作符有优先级高于比较操作符:

(df1 < 10) | (df2 > 10)  # instead of the wrong df1 < 10 | df2 > 10

这可能令人恼火,因为Python逻辑操作符的优先级比比较操作符低,所以通常编写a < 10 and b > 10(其中ab是简单整数),不需要括号。

逻辑操作和位操作之间的区别(针对非布尔值)

非常重要的是要强调,位操作和逻辑操作仅对布尔NumPy数组(和布尔Series &DataFrames)。如果不包含布尔值,则操作将给出不同的结果。我将包括使用NumPy数组的示例,但熊猫数据结构的结果将是类似的:

>>> import numpy as np
>>> a1 = np.array([0, 0, 1, 1])
>>> a2 = np.array([0, 1, 0, 1])


>>> np.logical_and(a1, a2)
array([False, False, False,  True])
>>> np.bitwise_and(a1, a2)
array([0, 0, 0, 1], dtype=int32)

由于NumPy(和类似的Pandas)对布尔(布尔或“掩码”索引数组)和整数(索引数组)索引做不同的事情,索引的结果也将是不同的:

>>> a3 = np.array([1, 2, 3, 4])


>>> a3[np.logical_and(a1, a2)]
array([4])
>>> a3[np.bitwise_and(a1, a2)]
array([1, 1, 1, 2])

汇总表

Logical operator | NumPy logical function | NumPy bitwise function | Bitwise operator
-------------------------------------------------------------------------------------
and       |  np.logical_and        | np.bitwise_and         |        &
-------------------------------------------------------------------------------------
or        |  np.logical_or         | np.bitwise_or          |        |
-------------------------------------------------------------------------------------
|  np.logical_xor        | np.bitwise_xor         |        ^
-------------------------------------------------------------------------------------
not       |  np.logical_not        | np.invert              |        ~

Where 逻辑运算符不适用于NumPy数组, Pandas Series和Pandas dataframe。其他的则处理这些数据结构(和普通的Python对象)并按元素工作。 但是,要注意普通Python bools的逐位反转,因为bool在此上下文中将被解释为整数(例如~False返回-1~True返回-2)

注意,你也可以使用*来做and:

   In [12]: np.all([a > 20, a < 40], axis=0)
Out[12]:
array([[False,  True, False, False,  True],
[False, False, False, False, False],
[ True,  True, False, False, False],
[False,  True, False, False, False],
[False,  True, False, False, False]])


In [13]: (a > 20) * (a < 40)
Out[13]:
array([[False,  True, False, False,  True],
[False, False, False, False, False],
[ True,  True, False, False, False],
[False,  True, False, False, False],
[False,  True, False, False, False]])

我并不是说这比使用np.all|更好。但它确实有效。