Pandas 中布尔索引的逻辑运算符
- 2024-11-26 08:36:00
- admin 原创
- 156
问题描述:
我正在使用 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()
解决方案 1:
当你说
(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
。使用and
和两个系列将再次触发ValueError
与上述相同的操作。这就是为什么括号是必需的。
解决方案 2:
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 运算来实现的。
重载&
位运算符
在继续之前,请注意文档中的这一特定摘录,其中指出
另一个常见操作是使用布尔向量来过滤数据。运算符包括:
|
foror
、&
forand
和~
fornot
。这些必须使用括号分组df.A > 2 & df.B < 3
,因为默认情况下 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 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().
所以,不要犯这个错误!(我知道我在反复强调这一点,但请忍耐一下。这是一个非常非常常见的初学者错误,必须非常彻底地解释。)
避免使用括号分组
修复其实很简单。大多数运算符都有相应的 DataFrames 绑定方法。如果使用函数而不是条件运算符构建单个掩码,则不再需要按括号分组来指定评估顺序:
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
我已经广泛地记录query
并eval
在Pandas 中根据公式动态评估表达式。
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
是一个ufunc(通用函数),大多数 ufunc 都有一个reduce
方法。这意味着如果您有多个掩码要进行 AND,则更容易进行泛化logical_and
。例如,要将掩码m1
和m2
和m3
与进行 AND &
,您必须执行
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
内部调用__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
用bitwise_or
, 和logical_not
替代invert
。
解决方案 3:
Pandas 中布尔索引的逻辑运算符
重要的是要意识到您不能在或s 上使用任何 Python逻辑运算符(and
、or
或not
)(同样,您不能在具有多个元素的 s 上使用它们)。您不能使用这些的原因是因为它们隐式调用它们的操作数,这会引发异常,因为这些数据结构决定数组的布尔值是不明确的:pandas.Series
`pandas.DataFramenumpy.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().
我在回答“系列的真值不明确。请使用 a.empty、a.bool()、a.item()、a.any() 或 a.all()”问答时更广泛地介绍了这一点。
NumPy 的逻辑函数
但是,NumPy提供了与这些运算符等效的元素操作,作为可以在numpy.array
、pandas.Series
、pandas.DataFrame
或任何其他(符合)numpy.array
子类上使用的函数:
and
有np.logical_and
or
有np.logical_or
not
有np.logical_not
numpy.logical_xor
Python 中没有对应的函数,但它是一个逻辑上的“排他或”运算
因此,本质上应该使用(假设df1
和df2
是 Pandas DataFrames):
np.logical_and(df1, df2)
np.logical_or(df1, df2)
np.logical_not(df1)
np.logical_xor(df1, df2)
布尔值的按位函数和按位运算符
但是如果你有布尔 NumPy 数组、Pandas Series 或 Pandas DataFrames,你也可以使用元素位函数(对于布尔值,它们 - 或者至少应该是 - 与逻辑函数没有区别):
按位与:
np.bitwise_and
或&
运算符按位或:
np.bitwise_or
或|
运算符按位非:(
np.invert
或别名np.bitwise_not
)或~
运算符按位异或:
np.bitwise_xor
或^
运算符
通常使用运算符。但是,当与比较运算符结合使用时,必须记住将比较括在括号中,因为按位运算符的优先级高于比较运算符:
(df1 < 10) | (df2 > 10) # instead of the wrong df1 < 10 | df2 > 10
这可能会令人恼火,因为 Python 逻辑运算符的优先级低于比较运算符,所以您通常写a < 10 and b > 10
(其中a
和b
例如是简单的整数)而不需要括号。
逻辑运算和按位运算之间的差异(非布尔值)
需要强调的是,位运算和逻辑运算仅对布尔 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
returns-1
和~True
returns -2
)。
解决方案 4:
请注意,您还可以使用以下*
方法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
或更好|
。但它确实有效。