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

293

我正在使用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()

13
这是因为NumPy数组和Pandas序列使用位运算符而不是逻辑运算符进行比较,因为您正在将数组/序列中的每个元素与另一个元素进行比较。在这种情况下使用逻辑运算符是没有意义的。请参阅相关链接:https://dev59.com/VWoy5IYBdhLWcg3wUcZL - EdChum
18
在Python中,and != &。Python中的and运算符无法被覆盖,而&运算符(__and__)可以。因此,在numpy和pandas中选择使用& - Steven Rumbalski
标题或关键词列表应包括numpy,但编辑队列已满。 - Rainald62
4个回答

337
当你说
(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或False方面存在不明确。一些用户可能会假设如果它们的长度非零,则为True,就像Python列表一样。其他人可能希望只有所有元素都为True时才为True。还有些人可能希望只要任何一个元素为True,它就为True。
由于存在如此多的冲突期望,NumPy和Pandas的设计者拒绝猜测,而是引发ValueError异常。
相反,你必须明确地调用empty()all()any()方法来指示你所期望的行为。
然而,在这种情况下,看起来你不想要布尔运算,而是想要逐个元素进行逻辑与操作。这就是&二进制运算符的作用。
(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。这就是为什么括号是必需的原因。

3
如果NumPy数组的长度为1,它们确实具有这个属性。只有pandas开发人员(顽固地)拒绝猜测 :p - Andy Hayden
4
“&”符号和“and”一样具有歧义性吗?为什么当涉及到“&”时,所有用户突然都同意它应该是逐元素的,而当他们看到“and”时,他们的期望却不同? - Indominus
21
Python语言本身要求表达式x and y会触发对bool(x)bool(y)的评估。 Python“首先计算x;如果x为false,则返回其值;否则,计算y并返回结果值。”因此,无法使用s and y 进行逐元素逻辑与运算,因为只能返回xy中的一个值。相反,x & y会触发x.__and__(y),而__and __方法可以被定义为返回任何我们想要的值。 - unutbu
2
需要注意的是:在 == 子句周围的括号是必须的a['x']==1 & a['y']==10 返回与问题中相同的错误。 - Alex P. Miller
1
" | " 用于什么? - Euler_Salter
1
@Euler_Salter | 是按位或运算符。Python 运算符文档在此处。 - Kyle C

226

TLDR: Pandas中的逻辑运算符是&|~,括号(...)也很重要!

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

对于pandas。
如果在执行逻辑操作的过程中出现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 > 5 的行。这可以通过分别计算每个条件的掩码,并进行逻辑与操作来实现。

重载的位运算符 &

在继续之前,请注意文档中的以下摘录:

另一个常见的操作是使用布尔向量来过滤数据。运算符有:|表示&表示~表示。由于默认情况下Python会按照如下方式计算表达式df.A > 2 & df.B < 3必须使用括号进行分组,而期望的计算顺序是(df.A > 2) & (df.B < 3)

所以,考虑到这一点,逐元素的逻辑与操作可以使用位运算符&来实现:
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

括号用于覆盖默认的运算符优先级,其中位运算符具有比比较运算符(<>)更高的优先级。

如果不使用括号,则表达式将被错误地计算。例如,如果您意外地尝试执行以下操作:

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 <b>and</b> 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().

所以,不要犯那个错误!(我知道我一直在强调这一点,但请忍耐一下。这是一个非常非常常见的初学者错误,必须解释得非常透彻。)
避免使用括号分组
修复实际上非常简单。大多数运算符都有对应的DataFrame绑定方法。如果使用函数而不是条件运算符来构建各个掩码,您将不再需要通过括号进行分组以指定评估顺序。
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

我在Pandas中动态评估公式中的表达式中详细记录了queryeval

operator.and_

允许您以功能性方式执行此操作。内部调用与位运算符对应的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

你通常不需要这个,但了解它会很有用。
概括来说:np.logical_and(以及logical_and.reduce
另一种选择是使用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 是一个通用函数(Universal Functions),大多数通用函数都有一个reduce方法。这意味着如果你有多个掩码需要进行逻辑与操作,使用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

逻辑或

对于上述的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

operator.or_

在幕后调用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

np.logical_or

对于两个条件,请使用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__(),但不要直接使用它。

operator.inv

在Series上内部调用__invert__方法。
operator.inv(mask)

0    False
1    False
2     True
dtype: bool

np.logical_not

这是numpy的变体。
np.logical_not(mask)

0    False
1    False
2     True
dtype: bool

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

在TLDR中,@cs95提倡使用|进行逐元素布尔OR运算,这等同于numpy.bitwise_or,而不是numpy.logical_or。请问为什么?numpy.logical_or不是专门设计用于此任务吗?为什么要增加每对元素进行位运算的负担呢? - flow2k
@flow2k,你能引用相关的文本吗?我找不到你所指的内容。顺便说一句,我认为logical_*是运算符的正确函数等效形式。 - cs95
1
@flow2k,这个在官方文档中有明确说明:“另一个常见的操作是使用布尔向量来筛选数据。运算符有:|表示或、&表示与,~表示非。” - cs95
我不太清楚的是:在第一个numpy文档中,提到numpy.bitwise_or等同于|。但他们没有说numpy.bitwise_or在功能上等同于numpy.logical_or。那么我们怎么确定它们是等价的呢?前者是按位操作,所以它不取决于NumPy对布尔值的二进制表示方式吗? - flow2k
@flow2k 布尔值由8位数字表示,但仅使用1位。这是众所周知的。 - cs95
显示剩余8条评论

9

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

需要注意的是,您不能在pandas.Seriespandas.DataFrame上使用任何Python 逻辑运算符(例如andornot)(类似地,您也不能在具有多个元素的numpy.array上使用它们)。之所以不能使用这些运算符,是因为它们隐式地对操作数调用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().

我在回答"Series的真值是不明确的。使用a.empty、a.bool()、a.item()、a.any()或a.all()"的问答中更详细地讨论了这个问题 (链接)

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 DataFrames,你也可以使用逐元素的位运算函数(对于布尔值,它们与逻辑函数无法区分):

通常使用运算符。但是,当与比较运算符结合使用时,必须记住将比较运算符括在括号中,因为位运算符的优先级高于比较运算符

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

这可能会让人感到烦恼,因为Python逻辑运算符的优先级低于比较运算符,所以通常写成a < 10 and b > 10(其中ab是简单整数的示例),不需要括号。
逻辑操作和按位操作(非布尔值)之间的区别
需要强调的是,对于布尔NumPy数组(以及布尔Series和DataFrames),位和逻辑操作只是等价的。如果它们不包含布尔值,则操作将产生不同的结果。我将使用NumPy数组来举例,但pandas数据结构的结果也类似。
>>> 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              |        ~

当涉及到NumPy数组、Pandas Series和pandas DataFrames时,逻辑运算符不起作用。其他运算符可以在这些数据结构(以及纯Python对象)上按元素进行操作。 但是,在纯Python bool上使用位反转时要小心,因为在此上下文中,bool将被解释为整数(例如~False返回-1~True返回-2)。


0
请注意,您还可以使用*来进行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|更好。但它确实有效。


1
此外,对于或操作,在 Pandas Series 中使用它可以避免使用括号。 - Fernando Wittmann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接