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

当你说

(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。

由于有太多相互冲突的期望,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']))和((1 & a['y']) ==10)。这是级数和级数的表达式。 与两个Series一起使用and将再次触发与上面相同的ValueError。这就是为什么括号是必须的。


TLDR;Pandas中的逻辑运算符是&,|和~,括号(…)很重要!

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

所以在python中(exp1和exp2是表达式,计算结果为布尔值)…

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 > 5的所有行。这是通过分别计算每个条件的掩码,并对它们进行and运算来完成的。

重载位&操作符 在继续之前,请注意这个文档的特别摘录,其中说明

另一个常用操作是使用布尔向量来过滤 数据。操作符为:|表示或,&表示与,~表示非。这些 必须使用括号分组,因为默认Python将 计算一个表达式,比如df。A > 2 & df。B < 3,即df。A > (2 & df. b) < 3,而期望求值顺序为(df. b)。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

括号用于覆盖按位操作符的默认优先顺序,按位操作符的优先级高于条件操作符<和>。请参阅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

避免括号分组 解决方法其实很简单。大多数操作符都有相应的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。查询(或eval):

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

   A  B  C
1  3  7  9
3  4  7  6

我使用pd.eval()在pandas的动态表达式求值中广泛记录了查询和eval。

operator.and_ 允许您以功能性的方式执行此操作。内部调用系列。__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是一个ufunc(通用函数),大多数ufuncs都有一个reduce方法。这意味着如果对AND有多个掩码,则使用logical_and更容易泛化。例如,用&来AND遮罩m1和m2和m3,你必须这样做

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 -我知道我在这一点上喋喋不休,但请耐心听我说。这是初学者经常犯的错误,必须解释清楚。


逻辑或

对于上面的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_ 电话系列。解析:在引擎盖下。

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__()

0    False
1    False
2     True
dtype: bool

但不要直接使用。

operator.inv 在内部调用系列上的__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_and, logical_or with bitwise_or, logical_not with invert。


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

重要的是要意识到不能在pandas上使用任何Python逻辑运算符(and, or or not)。系列或熊猫。dataframe(同样,你不能在numpy上使用它们。具有多个元素的数组)。你不能使用它们的原因是因为它们隐式地在操作数上调用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()或所有()Q +。

NumPy的逻辑函数

但是NumPy为这些运算符提供了元素操作等价物,作为可以在NumPy上使用的函数。数组,熊猫。系列中,熊猫。DataFrame,或任何其他(符合)numpy。数组子类:

并且有np.logical_and 或有np.logical_or Not有np.logical_not numpy。logical_xor,它在Python中没有等价的东西,但它是逻辑上的“排他或”操作

因此,本质上,我们应该使用(假设df1和df2是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,你也可以使用按元素的位函数(对于布尔值,它们是-或至少应该是-与逻辑函数难以区分):

位和:np。Bitwise_and或&运算符 按位或:np。Bitwise_or或|操作符 位非:np。反转(或别名np.bitwise_not)或~操作符 位xor: np。Bitwise_xor或^运算符

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

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

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

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

必须强调的是,位操作和逻辑操作仅对布尔NumPy数组(以及布尔系列和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              |        ~

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


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

   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更好。全部或|。但它确实有效。