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
exp1 | exp2
~exp1
对于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)
变成:
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中动态评估公式中的表达式中详细记录了
query
和
eval
。
允许您以功能性方式执行此操作。内部调用与位运算符对应的
Series.__and__
函数。
import operator
operator.and_(df['A'] < 5, 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
更容易泛化。例如,要使用&
对掩码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
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
在幕后调用
Series.__or__
。
operator.or_(df['A'] == 3, 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
对于两个条件,请使用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])
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__()
,但不要直接使用它。
在Series上内部调用
__invert__
方法。
operator.inv(mask)
0 False
1 False
2 True
dtype: bool
这是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
。
and != &
。Python中的and
运算符无法被覆盖,而&
运算符(__and__
)可以。因此,在numpy和pandas中选择使用&
。 - Steven Rumbalski