17 - Pandas (ouverture)

Introduction

Dans le chapitre sur les fichiers CSV, on a chargé une table sous forme de liste de dictionnaires, puis on l’a parcourue avec des boucles pour filtrer, trier ou calculer une moyenne. Cette méthode fonctionne, mais chaque traitement demande d’écrire sa propre boucle.

Pandas est une bibliothèque Python consacrée à l’analyse de données. Elle charge une table dans un objet qui sait déjà faire ces traitements : une seule ligne suffit pour calculer la moyenne d’une colonne, garder les lignes qui vérifient une condition ou trier la table. Pandas ne fait pas partie de Python : il faut l’installer, mais il est déjà présent dans la plupart des environnements (Capytale, Basthon, Anaconda) et dans les exemples de cette page.

On l’importe sous le nom court pd, que tout le monde utilise :

import pandas as pd

Ce chapitre est une ouverture : il présente les opérations les plus courantes, sans chercher à être complet.

La table d’exemple

Le fichier notes.csv contient les notes de six élèves de deux classes :

prénom,classe,maths,nsi,physique
Alice,1A,14,17,12
Bruno,1B,9,13,11
Chloé,1A,16,15,
David,1B,11,18,14
Emma,1A,8,12,10
Farid,1B,15,,13

Deux notes manquent : Chloé n’a pas de note de physique et Farid n’a pas de note de NSI. Entre deux virgules, ou après la dernière, il n’y a rien.

Lire un fichier CSV

La fonction read_csv() lit le fichier et renvoie un DataFrame, c’est-à-dire un tableau à deux dimensions : chaque colonne porte le nom donné par l’en-tête, et chaque ligne reçoit un numéro, appelé index, qui commence à 0.

df = pd.read_csv("notes.csv")
print(df)
  prénom classe  maths   nsi  physique
0  Alice     1A     14  17.0      12.0
1  Bruno     1B      9  13.0      11.0
2  Chloé     1A     16  15.0       NaN
3  David     1B     11  18.0      14.0
4   Emma     1A      8  12.0      10.0
5  Farid     1B     15   NaN      13.0

Trois remarques :

  • le nom df (pour DataFrame) est une habitude ; on peut choisir un autre nom ;
  • Pandas reconnaît seul les nombres : les colonnes maths, nsi et physique contiennent des nombres, pas des chaînes de caractères comme avec le module csv ;
  • une valeur manquante s’affiche NaN (pour Not a Number, « pas un nombre »). Une colonne qui contient NaN est stockée en flottants, d’où l’écriture 17.0.

Si le fichier utilise le point-virgule comme délimiteur, on le précise : pd.read_csv("notes.csv", sep=";").

Un premier aperçu

Une vraie table compte souvent des milliers de lignes. Avant tout traitement, on regarde sa taille et ses colonnes :

print(df.shape)
print(list(df.columns))
(6, 5)
['prénom', 'classe', 'maths', 'nsi', 'physique']

shape est un tuple (nombre de lignes, nombre de colonnes). La méthode head(n) renvoie les n premières lignes, et tail(n) les n dernières :

print(df.head(3))
  prénom classe  maths   nsi  physique
0  Alice     1A     14  17.0      12.0
1  Bruno     1B      9  13.0      11.0
2  Chloé     1A     16  15.0       NaN

Les colonnes

On obtient une colonne en écrivant son nom entre crochets, comme la valeur associée à une clé dans un dictionnaire. Le résultat est un objet Series : une suite de valeurs, chacune accompagnée de son index.

maths = df["maths"]
print(maths)
0    14
1     9
2    16
3    11
4     8
5    15
Name: maths, dtype: int64

Une série connaît les calculs usuels : mean() (moyenne), max(), min(), sum(), count() (nombre de valeurs présentes). Il n’y a aucune boucle à écrire :

print(f"Moyenne : {round(df['maths'].mean(), 1)}")
print(f"Note la plus haute : {df['maths'].max()}")
print(f"Note la plus basse : {df['maths'].min()}")
Moyenne : 12.2
Note la plus haute : 16
Note la plus basse : 8

Pour garder plusieurs colonnes, on donne la liste de leurs noms, d’où les doubles crochets :

print(df[["prénom", "nsi"]])
  prénom   nsi
0  Alice  17.0
1  Bruno  13.0
2  Chloé  15.0
3  David  18.0
4   Emma  12.0
5  Farid   NaN

Les lignes

df.loc[i] renvoie la ligne d’index i, et df.loc[i, "colonne"] une seule case :

print(df.loc[2])
print(df.loc[2, "nsi"])
prénom      Chloé
classe         1A
maths          16
nsi          15.0
physique      NaN
Name: 2, dtype: object
15.0

Sélectionner selon une condition

Pour garder les lignes qui vérifient une condition, on écrit la condition sur une colonne, puis on la place entre crochets après df. Voici les élèves qui ont au moins 12 en mathématiques :

print(df[df["maths"] >= 12])
  prénom classe  maths   nsi  physique
0  Alice     1A     14  17.0      12.0
2  Chloé     1A     16  15.0       NaN
5  Farid     1B     15   NaN      13.0

L’expression df["maths"] >= 12 compare chaque note à 12 et produit une série de booléens ; seules les lignes associées à True sont gardées. Pour combiner deux conditions, on remplace and par & et or par |, et on entoure chaque condition de parenthèses :

print(df[(df["classe"] == "1A") & (df["nsi"] >= 15)])
  prénom classe  maths   nsi  physique
0  Alice     1A     14  17.0      12.0
2  Chloé     1A     16  15.0       NaN

Trier

La méthode sort_values() trie les lignes selon une colonne, par ordre croissant. Le paramètre ascending=False donne l’ordre décroissant :

print(df.sort_values(by="nsi", ascending=False))
  prénom classe  maths   nsi  physique
3  David     1B     11  18.0      14.0
0  Alice     1A     14  17.0      12.0
2  Chloé     1A     16  15.0       NaN
1  Bruno     1B      9  13.0      11.0
4   Emma     1A      8  12.0      10.0
5  Farid     1B     15   NaN      13.0

Chaque ligne garde son index d’origine, et les valeurs manquantes sont placées à la fin.

Comme fillna() et dropna() ci-dessous, sort_values() ne modifie pas df : elle renvoie un nouveau DataFrame. Pour conserver le résultat, on l’affecte à une variable, par exemple classement = df.sort_values(by="nsi").

Les valeurs manquantes

Les données réelles sont rarement complètes. La méthode isna() repère les cases vides, et sum() les compte colonne par colonne :

print(df.isna().sum())
prénom      0
classe      0
maths       0
nsi         1
physique    1
dtype: int64

On peut alors remplacer chaque valeur manquante par une valeur choisie avec fillna(), ou supprimer les lignes incomplètes avec dropna() :

print(df.dropna())
  prénom classe  maths   nsi  physique
0  Alice     1A     14  17.0      12.0
1  Bruno     1B      9  13.0      11.0
3  David     1B     11  18.0      14.0
4   Emma     1A      8  12.0      10.0

Le choix dépend de la situation. Ici, df.fillna(0) donnerait 0 à Chloé en physique, ce qui serait injuste : une note absente n’est pas une note nulle.

Ajouter une colonne

On crée une colonne comme on ajoute une clé à un dictionnaire, par une affectation. Le calcul s’applique à toutes les lignes à la fois. La méthode mean(axis=1) calcule la moyenne de chaque ligne (l’option axis=1 indique que l’on parcourt les colonnes d’une même ligne) en ignorant les valeurs manquantes, et round(1) arrondit au dixième :

df["moyenne"] = df[["maths", "nsi", "physique"]].mean(axis=1).round(1)
print(df)
  prénom classe  maths   nsi  physique  moyenne
0  Alice     1A     14  17.0      12.0     14.3
1  Bruno     1B      9  13.0      11.0     11.0
2  Chloé     1A     16  15.0       NaN     15.5
3  David     1B     11  18.0      14.0     14.3
4   Emma     1A      8  12.0      10.0     10.0
5  Farid     1B     15   NaN      13.0     14.0

La moyenne de Chloé est calculée sur ses deux notes seulement.

Regrouper

La méthode groupby() rassemble les lignes qui ont la même valeur dans une colonne, puis applique un calcul à chaque groupe. Voici la moyenne de chaque matière, classe par classe :

print(df.groupby("classe")[["maths", "nsi", "physique"]].mean().round(1))
        maths   nsi  physique
classe                       
1A       12.7  14.7      11.0
1B       11.7  15.5      12.7

Sans Pandas, ce calcul demanderait un dictionnaire de listes et plusieurs boucles.

Résumé statistique

La méthode describe() donne en une fois, pour chaque colonne numérique, l’effectif des valeurs présentes (count), la moyenne (mean), l’écart type (std), le minimum, les quartiles (25%, 50% pour la médiane, 75%) et le maximum :

print(df.describe().round(1))
       maths   nsi  physique  moyenne
count    6.0   5.0       5.0      6.0
mean    12.2  15.0      12.0     13.2
std      3.3   2.5       1.6      2.2
min      8.0  12.0      10.0     10.0
25%      9.5  13.0      11.0     11.8
50%     12.5  15.0      12.0     14.2
75%     14.8  17.0      13.0     14.3
max     16.0  18.0      14.0     15.5

Tracer un graphique

La méthode plot() trace un graphique à partir d’un DataFrame, grâce à la bibliothèque matplotlib. Le paramètre kind choisit le type de graphique : "bar" pour des barres, "line" pour une courbe, "scatter" pour un nuage de points. Dans un programme, le graphique ne s’affiche qu’à l’appel de plt.show().

import matplotlib.pyplot as plt

df.plot(kind="bar", x="prénom", y=["maths", "nsi", "physique"])
plt.ylabel("Note sur 20")
plt.show()

Chaque élève reçoit un groupe de trois barres, une par matière. Une note manquante ne donne pas de barre.

Bilan

BesoinInstruction
Lire un fichier CSVdf = pd.read_csv("fichier.csv")
Taille de la tabledf.shape
Premières lignesdf.head(n)
Une colonnedf["nom"]
Plusieurs colonnesdf[["nom1", "nom2"]]
Une ligne, une casedf.loc[i], df.loc[i, "nom"]
Lignes qui vérifient une conditiondf[df["nom"] >= 12]
Trierdf.sort_values(by="nom", ascending=False)
Valeurs manquantesdf.isna().sum(), df.fillna(v), df.dropna()
Nouvelle colonnedf["nouvelle"] = ...
Calcul par groupedf.groupby("nom")["autre"].mean()
Résumé statistiquedf.describe()
Graphiquedf.plot(kind="bar", x=..., y=...)