Logique, structures de contrôle et filtrage#

Vidéo: Opérateurs de comparaison#

Opérateurs de comparaison#

Une expression booléenne est une expression qui est soit vraie, soit fausse. Par exemple, l’opérateur == compare les expressions de chaque côté de l’opérateur et renvoie la valeur True (vrai) s’ils sont égaux et False (faux) sinon.

# Opérateur égalité
print(5 == 5)
print(2 == 4)
True
False

Les valeurs True et False appartiennent au type bool :

print(type(True))
print(type(False))
<class 'bool'>
<class 'bool'>

La liste des opérateurs de comparaison est la suivante :

opérateur

description

x == y

x est égal à y

x != y

x n’est pas égal à y

x > y

x est strictement supérieur à y

x < y

x est strictement inférieur à y

x >= y

x est supérieur ou égal à y

x <= y

x est inférieur ou égal à y

Attention

Ces opérateurs semblent familiers. Cependant, les symboles de Python sont différents des symboles mathématiques. En particulier, le signe = des mathématiques n’est pas le symbole = de Python, qui est l’opérateur d’affectation, alors que le symbole == est un opérateur relationnel en Python.

Égalité#

Pour vérifier l’égalité entre 2 expressions on utilise le symbole ==. Les expressions de chaque côté de l’opérateur sont évaluées et comparées. Pour vérifier que 2 expressions ne sont pas égales on utilise l’opérateur !=.

Les exemples suivants renvoient tous le booléen True :

print((7 + 2) == 9)
print("bonjour" != "au revoir")
print(True == True)
print(3 * 5 == 5. + 5. + 5.)
True
True
True
True

Exercice#

D’après vous, quels sont les résultats des comparaisons suivantes. Vérifiez dans la console :

  1. True est égal à False

  2. 43 n’est pas égal à 3 * 15

  3. "Paris" est égal à "paris"

  4. True est égal à "Paris"

Hide code cell source
print(True == False)
print(43 != (3 * 15))
print("Paris" == "paris")
print(True == "Paris")
False
True
False
False

Supérieur et inférieur#

On peut utiliser les opérateurs de comparaison inférieur à < et supérieur à >. Ces symboles typographiques s’appellent des chevrons. On peut aussi les combiner au symbole = pour former les opérateurs inférieur ou égal <= et supérieur ou égal >=. Le signe égal est toujours après le chevron.

Voici quelques exemples qui renvoient tous le booléen True :

print(6 < 23)
print(8 - 3 >= 5)
print("beta" < "theta")
True
True
True

Comme on l’a vu précédemment, pour une valeur de type string (chaîne de charactères), l’ordre croissant est l’ordre alphabétique.

Exercice#

D’après vous, quels sont les résultats des comparaisons suivantes. Vérifiez dans la console :

  1. 10 / 3 est supérieur à 3.33333

  2. "chateau" est supérieur ou égal à "chien"

  3. True est supérieur à False

Hide code cell source
print(10 / 3 > 3.33333)
print("chateau" >= "chien")
print(True > False)
True
False
True

Comparer des tableaux#

Il est possible d’utiliser les opérateurs de comparaison directement avec des tableaux Numpy. Les comparaisons sont effectuées pour chaque élément du tableau. Le résultat est un tableau Numpy contenant les résultats de chacune des comparaisons, c’est-à-dire un tableau de valeurs de type booléen.

Par exemple :

# Importer le module Numpy
import numpy as np

# Créer des tableaux Numpy
A = np.array([1.4,  7.1, 9.0, 2.5, -4.6])
B = np.array([2.6, -5.7, 4.0, 2.5,  5.8])

# Comparaisons
print(A > 5.)
print(B > A)
[False  True  True False False]
[ True False False False  True]

Vérifions que le résultat de la comparaison entre des tableaux Numpy est bien un tableau Numpy contenant des booléens :

# Comparaison entre 2 tableaux Numpy A et B
C = B > A

# Type du résultat : tableau Numpy
print(type(C))

# Type des éléments du tableau : booléens
print(C.dtype)
<class 'numpy.ndarray'>
bool

Exercice#

Les tableaux Numpy suivants contiennent les surfaces (en \(\mathrm{m}^2\)) des pièces de 2 maisons différentes, A et B, dans le même ordre : une chambre, le salon, la cuisine et la salle de bain :

maison_A = np.array([15.5, 22.3, 6.6, 8.5])  # m**2
maison_B = np.array([12.3, 28.8, 12.7, 8.0]) # m**2

En utilisant des opérateurs de comparaison, déterminer :

  1. quelles sont les pièces de la maison A dont la superficie est supérieure ou égal à 10.0 \(\mathrm{m}^2\) ?

  2. quelles sont les pièces de la maison B dont les surfaces sont plus grandes que celles de la maison A ?

  3. la surface totale de la maison A est-elle plus grande que celle de la maison B ? Note : on pourra utiliser l’opérateur Numpy sum().

Hide code cell source
# 1 : Chambre, salon et cuisine
print(maison_A >= 10.0)

# 2 : Salon et cuisine
print(maison_B > maison_A)

# 3 : Non
print(np.sum(maison_A) > np.sum(maison_B))
[ True  True False False]
[False  True  True False]
False

Vidéo: Opérateurs logiques#

Opérateurs logiques#

Il existe trois opérateurs logiques : and, or et not. Il ont le même sens que leur traduction, respectivement : et, ou et non. On peut les utiliser pour combiner des expressions booléennes. Par exemple, l’expression x > 10 and x <= 14 est vraie seulement si x est strictement supérieur à 10 et x est inférieur ou égal à 14.

L’expression n % 5 == 0 or n % 6 == 0 est vraie si n est divisible par 5 ou si n est divisible par 6 ou les deux.

Finalement, l’opérateur not nie une expression. Par exemple not x > y est vraie si x > y est faux, c’est-à-dire si x est inférieur ou égal à y.

Exercice#

Créons les variables masse_terre et masse_mars contenant les masses de la Terre et de Mars en \(\mathrm{kg}\) :

masse_terre = 5.9736e24 # kg
masse_mars  = 6.4185e23 # kg

En utilisant des opérateurs logiques et des expressions booléennes, déterminer :

  1. si la masse de la Terre est strictement comprise entre \(10^{23}\) et \(10^{24} ~\mathrm{kg}\)

  2. si la masse de Mars est supérieure ou égale à \(10^{25} ~\mathrm{kg}\) ou si elle est inférieure ou égale à la masse de la Terre

  3. si la masse de la Terre est strictement comprise entre 1 et 10 fois la masse de Mars

Finalement, déterminer sans l’écrire a priori le résultat de l’expression not(not(masse_terre > 1e23) or not(masse_terre >= masse_mars and masse_mars > 1e25))

Hide code cell source
# 1: faux
print(1e23 < masse_terre and masse_terre < 1e24)

# 2: vrai
print(1e25 <= masse_mars or masse_mars <= masse_terre)

# 3: vrai
print(masse_mars < masse_terre and masse_terre < 10 * masse_mars)

# 4: faux
print(not(not(masse_terre > 1e23) or not(masse_terre >= masse_mars and masse_mars > 1e25)))
False
True
True
False

Numpy et les opérateurs logiques#

Les opérateurs logiques and, or et not ne fonctionnent pas avec les tableaux Numpy. Nous avons vu que le résultat de la comparaison de 2 tableaux Numpy est un tableau Numpy contenant des booléens. Pour effectuer des opérations logiques entre les éléments de 2 tableaux Numpy bool_1 et bool_2 contenant des booléens, il faut utiliser les fonctions Numpy suivantes :

fonction Numpy

opérateur logique

logical_and(bool_1, bool_2)

et

logical_or(bool_1, bool_2)

ou

logical_not(bool)

non

bool_1 et bool_2 peuvent être le résultat d’une comparaison entre des tableaux Numpy. Reprenons les tableaux A et B créés plus haut :

# Créer des tableaux Numpy
A = np.array([1.4,  7.1, 9.0, 2.5, -4.6])
B = np.array([2.6, -5.7, 4.0, 2.5,  5.8])

# Création des tableaux de booléens, résultats de comparaisons
# Ici on cherche tous les éléments de A qui sont strictement inférieurs à ceux de B et à 5.0
bool_1 = A < B
bool_2 = A < 5.0

# Opération logique sur les tableaux de booléens
print(np.logical_and(bool_1, bool_2))
[ True False False False  True]

On voit que seuls les premier et dernier éléments de A sont à la fois strictement inférieurs à ceux de B et à 5.0.

Il existe de nombreuses fonctions logiques dans le module Numpy, la liste est disponible sur le site du module. Parmi les fonctions utiles, on note :

  • all() : teste si tous les éléments d’un tableau sont vrais

  • any() : teste si au moins un des éléments d’un tableau est vrai

Par exemple :

# Teste si au moins un des éléments de A est strictement supérieur à 8 :
print(np.any(A > 8.0))
True

Exercice#

Reprenons les tableaux A et B définis plus haut, contenant les surfaces en \(\mathrm{m}^2\) des pièces de 2 maisons :

# Superficie des pièces : chambre, salon, cuisine, salle de bain
maison_A = np.array([15.5, 22.3, 6.6, 8.5])  # m**2
maison_B = np.array([12.3, 28.8, 12.7, 8.0]) # m**2

Déterminer, grâce aux fonctions logiques de Numpy :

  1. quelles sont les pièces de la maison A dont la surface est strictement comprise entre 8 et 16 \(\mathrm{m}^2\) ?

  2. quelles sont les pièces dont la surface n’est pas strictement inférieure à 10 dans les 2 maisons A et B ?

  3. est-ce qu’au moins une des pièces de la maison A est plus grande que les pièces correspondantes de la maison B ?

Hide code cell source
# 1 : Chambre et salle de bains
bool_1 = maison_A > 8
bool_2 = maison_A < 16
print(np.logical_and(bool_1, bool_2))

# 2 : Chambre et salon
bool_1 = np.logical_not(maison_A < 10)
bool_2 = np.logical_not(maison_B < 10)
print(np.logical_and(bool_1, bool_2))

# 3 : Oui
print(any(A > B))
[ True False False  True]
[ True  True False False]
True

Vidéo: Instructions conditionnelles et filtrage#

Instructions conditionnelles#

Il est très fréquent dans un programme que l’on veuille exécuter une instruction seulement si certaines conditions sont remplies. Pour cela, on utilise l’instruction if :

# Création et affectation de la variable x
x = 2

# Test si x est positif
if x > 0:
    print("x est strictement positif")
x est strictement positif

L’expression booléenne après l’instruction if est appelée condition.

Dans le code ci-dessus, vérifiez ce qu’il se passe si vous changez la valeur de la variable x pour une valeur négative.

Instructions composées

Les instructions if, comme d’autres que nous verrons par la suite, sont des instructions composées : un en-tête, suivi d’un corps indenté contenant une instruction par ligne. Il n’y a pas de limite au nombre d’instructions contenues dans le corps indenté, mais il doit y en avoir au moins une.

Si la condition après l’instruction if n’est pas vérifiée, il est possible d’exécuter une instruction alternative avec l’instruction else :

# Ré-affectation de la variable x
x = 3

# Test si x est pair ou impair
if x % 2 == 0:
    print("x est pair")
else:
    print("x est impair")
x est impair

La condition est soit vraie soit fausse, une des 2 instructions est donc forcément exécutée. Ces 2 alternatives sont appelées des branchements.

Il est possible d’avoir besoin de plus de 2 branches. On écrit alors un enchaînement de conditions avec l’instruction elif :

# Ré-affectation de la variable x
x = 0

# Test si x est positif, négatif ou nul
if x > 0:
    print("x est positif")
elif x < 0:
    print("x est négatif")
else:
    print("x est nul")
x est nul

Il n’y a pas de limite au nombre de conditions qui peuvent être enchaînées, c’est-à-dire que l’on peut mettre autant d’instructions elif que l’on veut. L’instruction else sera toujours à la fin. Même si plusieurs des conditions sont vérifiées, seulement la première qui est vérifiée sera exécutée. Par exemple :

bouton_1 = False
bouton_2 = True

if bouton_1 and bouton_2:
    print("Les 2 boutons sont allumés")
elif bouton_1:
    print("Le bouton 1 est allumé")
elif bouton_2:
    print("Le bouton 2 est allumé")
elif bouton_1 or bouton_2:
    print("Un des 2 boutons est allumé")
else:
    print("Aucun des 2 boutons n'est allumé")
Le bouton 2 est allumé

Dans l’exemple ci-dessus, l’instruction correspondant à la condition bouton_1 or bouton_2 ne sera jamais exécutée, car si elle est vraie alors il y aura forcément une des 2 conditions bouton_1 ou bouton_2 qui est vraie et dont l’instruction sera exécutée plus tôt.

Exercice#

Écrire l’instruction conditionnelle avec les branchements suivants :

  1. si x est un multiple de 5 et de 6 afficher à l’écran “x est un multiple de 5 et de 6”

  2. si x est un multiple de 5 et pas de 6 afficher à l’écran “x est un multiple de 5 mais pas de 6”

  3. si x est un multiple de 6 et pas de 5 afficher à l’écran “x est un multiple de 6 mais pas de 5”

  4. si x n’est un multiple ni de 5 ni de 6 afficher à l’écran “x n’est un multiple ni de 5 ni de 6”

Testez vos conditions avec les nombres 8, 10, 12, et 30.

# Création et affectation de la variable x
x = 30

# Instruction conditionnelle
if x % 5 == 0 and x % 6 == 0:
    print("x est un multiple de 5 et de 6")
elif x % 5 == 0 and x % 6 != 0:
    print("x est un multiple de 5 mais pas de 6")
elif x % 5 != 0 and x % 6 == 0:
    print("x est un multiple de 6 mais pas de 5")
else:
    print("x est un multiple ni de 5 ni de 6")
x est un multiple de 5 et de 6

Filtrer un tableau Numpy#

Pour extraire un sous-tableau Numpy qui vérifie certaines conditions, il est possible d’utiliser un tableau de booléens en indice d’un autre tableau :

# Créer des tableaux Numpy
A = np.array([1.4,  7.1, 9.0, 2.5, -4.6])
B = np.array([2.6, -5.7, 4.0, 2.5,  5.8])

# condition est un tableau Numpy de booléens
condition = A > 4
print(condition)

# Filtrer le tableau A avec le tableau condition
print(A[condition])

# Il est possible de filtrer le tableau B avec la condition sur A
print(B[condition])
[False  True  True False False]
[7.1 9. ]
[-5.7  4. ]

On voit que les éléments filtrés sont ceux qui correspondent aux valeurs True du tableau condition. On peut filtrer les 2 tableaux sans créer le tableau condition intermédiaire :

print(A[A > 4])
print(B[A > 4])
[7.1 9. ]
[-5.7  4. ]

Finalement, on peut utiliser les fonctions logiques de Numpy pour combiner les conditions :

condition = np.logical_and(A > 0, A < 5)
print(A[condition])
[1.4 2.5]

Note : le tableau condition est souvent qualifié de “masque”, ou de “masque d’indices”. Si vous mettez un masque sur les yeux avant d’aller bronzer sur la plage, vous aurez bronzé partout sauf sous le masque : le masque permet de définir une zone qui ne bronze pas. Ici c’est pareil, le masque d’indices permet de sélectionner les éléments du tableau NumPy qu’on garde. On parle par exemple de masques en photolithographie.

Exercice#

  1. Extraire du tableau A toutes les valeurs négatives

  2. Extraire du tableau B toutes les valeurs strictement comprises entre 1 et 3

# 1.
print(A[A < 0])

# 2.
print(B[np.logical_and(B > 1, B < 3)])
[-4.6]
[2.6 2.5]

Filtrer un DataFrame#

Filtrer un objet DataFrame fonctionne de façon similaire au filtrage d’un tableau Numpy. Pour cela il faut extraire un objet Series de l’objet DataFrame, et lui appliquer une condition. On obtient alors un objet Series contenant des booléens, avec lequel on peut filtrer l’objet DataFrame.

Reprenons la liste des pays de l’union européenne sauvegardée dans le module précédent :

# Import du module pandas
import pandas as pd

# Lecture du fichier pickle contenant les données
df_europe = pd.read_pickle('./europe.pkl')

Nous voulons extraire la liste des pays contenant plus de 50 millions d’habitants :

# Afficher les noms des colonnes
print(df_europe.columns)

# On extrait l'objet Series correspondant à la colonne 'population'
sr_population = df_europe['population']

# On applique la condition sur le nombre d'habitants
condition = sr_population > 50e6

# Afficher l'objet condition
condition.head(n = 2)
Index(['pays', 'capitale', 'population', 'date d'adhésion', 'sièges', 'poids'], dtype='object')
1     True
2    False
Name: population, dtype: bool

On voit que la variable condition est un objet de type Series qui contient des booléens.

# On filtre l'objet df_europe avec l'objet condition
df_europe_big = df_europe[condition]

# On affiche le tableau filtré
df_europe_big
pays capitale population date d'adhésion sièges poids
1 allemagne berlin 82162000 1957-01-01 96 1.370336
11 france paris 66661621 1957-01-01 74 1.301915
15 italie rome 60665551 1957-01-01 73 1.411261

De façon beaucoup plus synthétique on aurait pu se passer de l’objet intermédiaire condition en écrivant :

df_europe[ df_europe['population'] > 50e6 ]
pays capitale population date d'adhésion sièges poids
1 allemagne berlin 82162000 1957-01-01 96 1.370336
11 france paris 66661621 1957-01-01 74 1.301915
15 italie rome 60665551 1957-01-01 73 1.411261

De la même manière que pour les tableaux Numpy, il est possible d’utiliser les fonctions logiques de Numpy sur les objets Series. Par exemple, cherchons les pays dont la date d’adhésion est supérieure à 1990 et dont la population est supérieure à 10 millions :

# Conditions
condition1 = df_europe["population"] > 10e6
condition2 = df_europe["date d'adhésion"] > np.datetime64('1990','Y')

# Opération logique et
df_europe[np.logical_and(condition1, condition2)]
pays capitale population date d'adhésion sièges poids
21 pologne varsovie 37967209 2004-01-01 51 1.575391
23 république tchèque prague 10553853 2004-01-01 21 2.333646
24 roumanie bucarest 19759968 2007-01-01 32 1.899287

Le module Pandas a introduit une notation un peu plus pratique pour les opérations logiques :

Opération

notation Pandas

et

&

ou

|

non

~

Par exemple, cherchons les pays dont l’année d’adhésion n’est pas 1957 et dont le nombre de sièges est supérieur à 50 :

df_europe[ ~(df_europe["date d'adhésion"] == np.datetime64('1957','Y')) & (df_europe["sièges"] > 50) ]
pays capitale population date d'adhésion sièges poids
8 espagne madrid 46438422 1986-01-01 54 1.363776
21 pologne varsovie 37967209 2004-01-01 51 1.575391

Attention

Ces opérateurs de logique binaire (&, \|, ~) n’ont pas la même priorité des opérations que les opérateurs de logique booléenne (and, or, not), et en l’occurence ils sont prioritaires devant les opérateurs de comparaison (==, <, >, <=, >=, !=), contrairement aux opérateurs de logique booléenne, donc il est conseillé de mettre des parenthèses. La priorité des opérations peut être vérifiée ici.

Par exemple, ~(df_europe["date d'adhésion"] == np.datetime64('1957','Y')) & df_europe["sièges"] > 50 ne donne pas le bon résultat, parce que Python tente d’abord d’effectuer l’opération binaire & entre ~(df_europe["date d'adhésion"] == np.datetime64('1957','Y')) et df_europe["sièges"] (au lieu de df_europe["sièges"] > 50). Alors que ~(df_europe["date d'adhésion"] == np.datetime64('1957','Y')) & (df_europe["sièges"] > 50) donne le résultat souhaité (regardez bien les parenthèses).

Exercice#

À partir du DataFrame df_europe, extraire :

  1. Les pays dont la lettre commence par une lettre entre ‘c’ et ‘g’ (non inclus). On rappelle que l’ordre croissant pour les objets de type str est l’ordre alphabétique.

  2. Pour les pays qui ont adhéré en 2004, ceux dont le nombre de sièges est 6 ou dont le poids est inférieur à 2.

# 1.
df_europe[ (df_europe["pays"] >= 'd') & (df_europe["pays"] < 'g') ]
pays capitale population date d'adhésion sièges poids
7 danemark copenhague 5659715 1973-01-01 13 2.693863
8 espagne madrid 46438422 1986-01-01 54 1.363776
9 estonie tallinn 1315944 2004-01-01 6 5.347374
10 finlande helsinki 5401267 1995-01-01 13 2.822763
11 france paris 66661621 1957-01-01 74 1.301915
# 2.
condition1 = df_europe["date d'adhésion"] == np.datetime64('2004', 'Y')
condition2 = (df_europe["sièges"] == 6) | (df_europe["poids"] < 2)

df_europe[ condition1 & condition2 ]
pays capitale population date d'adhésion sièges poids
5 chypre nicosie 848319 2004-01-01 6 8.295046
9 estonie tallinn 1315944 2004-01-01 6 5.347374
19 malte la valette 434403 2004-01-01 6 16.198887
21 pologne varsovie 37967209 2004-01-01 51 1.575391