TLDR; Les opérateurs logiques dans Pandas sont &
, |
et ~
, et les parenthèses (...)
sont importantes!
Python and
, or
et les not
opérateurs logiques sont conçus pour fonctionner avec des scalaires. Les Pandas ont donc dû faire mieux et remplacer les opérateurs binaires pour obtenir une version vectorisée (élément par élément) de cette fonctionnalité.
Donc ce qui suit en python ( exp1
et ce exp2
sont des expressions qui s'évaluent en un résultat booléen) ...
exp1 and exp2 # Logical AND
exp1 or exp2 # Logical OR
not exp1 # Logical NOT
... se traduira par ...
exp1 & exp2 # Element-wise logical AND
exp1 | exp2 # Element-wise logical OR
~exp1 # Element-wise logical NOT
pour les pandas.
Si, lors de l'exécution d'une opération logique, vous obtenez un ValueError
, vous devez utiliser des parenthèses pour le regroupement:
(exp1) op (exp2)
Par exemple,
(df['col1'] == x) & (df['col2'] == y)
Etc.
Indexation booléenne : une opération courante consiste à calculer des masques booléens via des conditions logiques pour filtrer les données. Pandas fournit trois opérateurs:&
pour ET logique,|
pour OU logique et~
pour NON logique.
Considérez la configuration suivante:
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
ET logique
Pour df
ci-dessus, disons que vous souhaitez renvoyer toutes les lignes où A <5 et B> 5. Cela se fait en calculant les masques pour chaque condition séparément, et en les AND.
&
Opérateur bit à bit surchargé
Avant de continuer, veuillez prendre note de cet extrait particulier de la documentation, qui indique
Une autre opération courante est l'utilisation de vecteurs booléens pour filtrer les données. Les opérateurs sont: |
pour or
, &
pour and
et ~
pour not
. Celles-ci doivent être regroupées en utilisant des parenthèses , car par défaut Python évaluera une expression telle df.A > 2 & df.B < 3
que df.A > (2 &
df.B) < 3
, alors que l'ordre d'évaluation souhaité est (df.A > 2) & (df.B <
3)
.
Donc, dans cet esprit, ET logique élément par élément peut être implémenté avec l'opérateur bit à 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
Et l'étape de filtrage suivante est simplement,
df[(df['A'] < 5) & (df['B'] > 5)]
A B C
1 3 7 9
3 4 7 6
Les parenthèses sont utilisées pour remplacer l'ordre de priorité par défaut des opérateurs au niveau du bit, qui ont une priorité plus élevée sur les opérateurs conditionnels <
et >
. Voir la section sur la priorité des opérateurs dans la documentation python.
Si vous n'utilisez pas de parenthèses, l'expression est évaluée de manière incorrecte. Par exemple, si vous tentez accidentellement quelque chose comme
df['A'] < 5 & df['B'] > 5
Il est analysé comme
df['A'] < (5 & df['B']) > 5
Qui devient,
df['A'] < something_you_dont_want > 5
Ce qui devient (voir la documentation python sur la comparaison d'opérateurs chaînés ),
(df['A'] < something_you_dont_want) and (something_you_dont_want > 5)
Qui devient,
# Both operands are Series...
something_else_you_dont_want1 and something_else_you_dont_want2
Qui jette
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
Alors, ne faites pas cette erreur! 1
Éviter le regroupement des parenthèses
Le correctif est en fait assez simple. La plupart des opérateurs ont une méthode liée correspondante pour DataFrames. Si les masques individuels sont créés à l'aide de fonctions au lieu d'opérateurs conditionnels, vous n'aurez plus besoin de regrouper par parenthèses pour spécifier l'ordre d'évaluation:
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
Voir la section sur les comparaisons flexibles. . Pour résumer, nous avons
╒════╤════════════╤════════════╕
│ │ Operator │ Function │
╞════╪════════════╪════════════╡
│ 0 │ > │ gt │
├────┼────────────┼────────────┤
│ 1 │ >= │ ge │
├────┼────────────┼────────────┤
│ 2 │ < │ lt │
├────┼────────────┼────────────┤
│ 3 │ <= │ le │
├────┼────────────┼────────────┤
│ 4 │ == │ eq │
├────┼────────────┼────────────┤
│ 5 │ != │ ne │
╘════╧════════════╧════════════╛
Une autre option pour éviter les parenthèses consiste à utiliser DataFrame.query
(ou eval
):
df.query('A < 5 and B > 5')
A B C
1 3 7 9
3 4 7 6
J'ai largement documenté query
et eval
en évaluation d'expression dynamique chez les pandas en utilisant pd.eval () .
operator.and_
Vous permet d'effectuer cette opération de manière fonctionnelle. Appelle en interne Series.__and__
qui correspond à l'opérateur au niveau du 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
Vous n'en aurez généralement pas besoin, mais il est utile de le savoir.
Généralisation: np.logical_and
(et logical_and.reduce
)
Une autre alternative consiste à utiliser np.logical_and
, qui ne nécessite pas non plus de regroupement de parenthèses:
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
est un ufunc (Universal Functions) , et la plupart des ufuncs ont une reduce
méthode. Cela signifie qu'il est plus facile de généraliser avec logical_and
si vous avez plusieurs masques à AND. Par exemple, pour les masques AND m1
et m2
et m3
avec &
, vous devrez faire
m1 & m2 & m3
Cependant, une option plus simple est
np.logical_and.reduce([m1, m2, m3])
C'est puissant, car cela vous permet de construire sur cela avec une logique plus complexe (par exemple, générer dynamiquement des masques dans une compréhension de liste et les ajouter tous):
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 - Je sais que je persiste sur ce point, mais soyez indulgents avec moi. C'est une erreur très , très courante du débutant, et doit être expliquée très en détail.
OU logique
Pour ce qui df
précède, disons que vous souhaitez renvoyer toutes les lignes où A == 3 ou B == 7.
Surchargé au niveau du bit |
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
Si vous ne l'avez pas encore fait, veuillez également lire la section sur la logique ET ci - dessus, toutes les mises en garde s'appliquent ici.
Alternativement, cette opération peut être spécifiée avec
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_
Appels Series.__or__
sous le capot.
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
Pour deux conditions, utilisez 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
Pour plusieurs masques, utilisez 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
NON logique
Étant donné un masque, tel que
mask = pd.Series([True, True, False])
Si vous devez inverser chaque valeur booléenne (pour que le résultat final soit [False, False, True]
), vous pouvez utiliser l'une des méthodes ci-dessous.
Bitwise ~
~mask
0 False
1 False
2 True
dtype: bool
Encore une fois, les expressions doivent être mises entre parenthèses.
~(df['A'] == 3)
0 True
1 False
2 False
3 True
4 True
Name: A, dtype: bool
Cela appelle en interne
mask.__invert__()
0 False
1 False
2 True
dtype: bool
Mais ne l'utilisez pas directement.
operator.inv
Fait appel __invert__
à la série en interne .
operator.inv(mask)
0 False
1 False
2 True
dtype: bool
np.logical_not
C'est la variante numpy.
np.logical_not(mask)
0 False
1 False
2 True
dtype: bool
Remarque, np.logical_and
peut être remplacé par np.bitwise_and
, logical_or
par bitwise_or
et logical_not
par invert
.