TLDR; Operadores lógicos em Pandas são &
, |
e ~
, e parênteses (...)
é importante!
Python é and
, or
e not
operadores lógicos são projetados para trabalhar com escalares. Portanto, o Pandas teve que fazer um melhor e substituir os operadores bit a bit para obter a versão vetorizada (elemento a elemento) dessa funcionalidade.
Portanto, o seguinte em python ( exp1
e exp2
são expressões que avaliam um resultado booleano) ...
exp1 and exp2 # Logical AND
exp1 or exp2 # Logical OR
not exp1 # Logical NOT
... será traduzido para ...
exp1 & exp2 # Element-wise logical AND
exp1 | exp2 # Element-wise logical OR
~exp1 # Element-wise logical NOT
para pandas.
Se no processo de execução da operação lógica você obtiver a ValueError
, precisará usar parênteses para agrupar:
(exp1) op (exp2)
Por exemplo,
(df['col1'] == x) & (df['col2'] == y)
E assim por diante.
Indexação booleana : Uma operação comum é calcular máscaras booleanas através de condições lógicas para filtrar os dados. Pandas fornece três operadores:&
para lógicas E,|
para OR lógico, e~
para lógicas não.
Considere a seguinte configuração:
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
AND lógico
Por df
acima, digamos que você gostaria de retornar todas as linhas onde a <5 e B> 5. Isso é feito por máscaras para cada condição de computação separadamente, e Anding eles.
&
Operador Bitwise sobrecarregado
Antes de continuar, observe este trecho específico dos documentos, que declaram
Outra operação comum é o uso de vetores booleanos para filtrar os dados. Os operadores são: |
para or
, &
para and
e ~
para not
. Eles devem ser agrupados usando parênteses , pois, por padrão, o Python avaliará uma expressão df.A > 2 & df.B < 3
como df.A > (2 &
df.B) < 3
, enquanto a ordem de avaliação desejada é (df.A > 2) & (df.B <
3)
.
Portanto, com isso em mente, o elemento elemento lógico AND pode ser implementado com o operador bit a bit &
:
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
E a etapa de filtragem subsequente é simplesmente,
df[(df['A'] < 5) & (df['B'] > 5)]
A B C
1 3 7 9
3 4 7 6
Os parênteses são usados para substituir a ordem de precedência padrão dos operadores bit a bit, que têm maior precedência sobre os operadores condicionais <
e >
. Consulte a seção Precedência do operador nos documentos python.
Se você não usar parênteses, a expressão será avaliada incorretamente. Por exemplo, se você tentar acidentalmente algo como
df['A'] < 5 & df['B'] > 5
É analisado como
df['A'] < (5 & df['B']) > 5
O que se torna,
df['A'] < something_you_dont_want > 5
Que se torna (consulte os documentos python sobre comparação de operadores encadeados ),
(df['A'] < something_you_dont_want) and (something_you_dont_want > 5)
O que se torna,
# Both operands are Series...
something_else_you_dont_want1 and something_else_you_dont_want2
Que lança
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
Então, não cometa esse erro! 1
Evitando o agrupamento de parênteses
A correção é realmente bastante simples. A maioria dos operadores possui um método associado correspondente para DataFrames. Se as máscaras individuais forem construídas usando funções em vez de operadores condicionais, você não precisará mais agrupar por parens para especificar a ordem de avaliação:
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
Veja a seção Comparações flexíveis. . Para resumir, temos
╒════╤════════════╤════════════╕
│ │ Operator │ Function │
╞════╪════════════╪════════════╡
│ 0 │ > │ gt │
├────┼────────────┼────────────┤
│ 1 │ >= │ ge │
├────┼────────────┼────────────┤
│ 2 │ < │ lt │
├────┼────────────┼────────────┤
│ 3 │ <= │ le │
├────┼────────────┼────────────┤
│ 4 │ == │ eq │
├────┼────────────┼────────────┤
│ 5 │ != │ ne │
╘════╧════════════╧════════════╛
Outra opção para evitar parênteses é usar DataFrame.query
(ou eval
):
df.query('A < 5 and B > 5')
A B C
1 3 7 9
3 4 7 6
Eu documentei extensivamentequery
e eval
em Avaliação de Expressão Dinâmica em pandas usando pd.eval () .
operator.and_
Permite que você execute esta operação de maneira funcional. Chama internamente Series.__and__
que corresponde ao operador bit a bit.
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
Você normalmente não precisará disso, mas é útil saber.
Generalizando: np.logical_and
(e logical_and.reduce
)
Outra alternativa é usar np.logical_and
, que também não precisa de agrupamento de parênteses:
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
é um ufunc (Universal Functions) , e a maioria dos ufuncs tem um reduce
método. Isso significa que é mais fácil generalizar logical_and
se você tiver várias máscaras para AND. Por exemplo, para máscaras AND m1
e m2
e m3
com &
, você teria que fazer
m1 & m2 & m3
No entanto, uma opção mais fácil é
np.logical_and.reduce([m1, m2, m3])
Isso é poderoso, porque permite que você desenvolva uma lógica mais complexa (por exemplo, gerando dinamicamente máscaras em uma compreensão de lista e adicionando todas elas):
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 - Eu sei que estou falando sério sobre esse ponto, mas por favor, tenha paciência comigo. Este é um muito , muito erro de principiante comum, e deve ser explicado muito bem.
OR lógico
Pelo df
exposto, suponha que você gostaria de retornar todas as linhas em que A == 3 ou B == 7.
Bitwise Sobrecarregado |
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
Se ainda não o fez, leia também a seção Lógica E acima, todas as advertências se aplicam aqui.
Como alternativa, esta operação pode ser especificada com
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_
Chamadas Series.__or__
sob o capô.
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
Para duas condições, use 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
Para várias máscaras, use 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
NÃO lógico
Dada uma máscara, como
mask = pd.Series([True, True, False])
Se você precisar inverter todos os valores booleanos (para que o resultado final seja [False, False, True]
), poderá usar qualquer um dos métodos abaixo.
Bit a bit ~
~mask
0 False
1 False
2 True
dtype: bool
Novamente, as expressões precisam estar entre parênteses.
~(df['A'] == 3)
0 True
1 False
2 False
3 True
4 True
Name: A, dtype: bool
Isso chama internamente
mask.__invert__()
0 False
1 False
2 True
dtype: bool
Mas não o use diretamente.
operator.inv
Chama internamente __invert__
a série.
operator.inv(mask)
0 False
1 False
2 True
dtype: bool
np.logical_not
Essa é a variante numpy.
np.logical_not(mask)
0 False
1 False
2 True
dtype: bool
Observe que np.logical_and
pode ser substituído por np.bitwise_and
, logical_or
com bitwise_or
e logical_not
com invert
.