Programmation Python — Séance 7 : pandas

NumPy calcule sur des tableaux de nombres.
Télécharger le notebook Ouvrir dans Colab Les sorties sont celles enregistrées dans le notebook : rien n'est réexécuté.

DMI · FST · UCAD

NumPy calcule sur des tableaux de nombres. pandas, bâti sur NumPy, ajoute des colonnes nommées et des lignes étiquetées : le DataFrame, la table de données de la bio-informatique. On y manipule notre jeu de morphométrie de pinsons — cette fois avec une colonne espece. Exécutez chaque cellule avec Maj + Entrée. Les cellules À vous sont à compléter.


1. Series et DataFrame

Une Series est une colonne : des valeurs avec une étiquette (l’index). Un DataFrame est un ensemble de colonnes partageant le même index — une table. On importe pandas sous le nom pd.

import pandas as pd

masse_moy = pd.Series([15.2, 21.5, 28.0],
                      index=["fuliginosa", "fortis", "magnirostris"])
print(masse_moy)
fuliginosa      15.2
fortis          21.5
magnirostris    28.0
dtype: float64

À vous. Créez une Series aile_moy valant [67.4, 74.8, 80.0] avec le même index des trois espèces, puis affichez-la.

# À vous : creez aile_moy = pd.Series([67.4, 74.8, 80.0], index=["fuliginosa", "fortis", "magnirostris"])

2. Créer un DataFrame

On construit un DataFrame à partir d’un dictionnaire : chaque clé devient une colonne. Ici, 15 spécimens de 3 espèces, avec 4 mesures. head() en montre les premières lignes.

import numpy as np

data = {
    "espece":   ["fuliginosa"]*5 + ["fortis"]*6 + ["magnirostris"]*4,
    "bec_long": [9.1, 9.4, 8.9, 9.6, 9.2, 12.8, 13.2, 12.5, 13.5, 12.9, 13.0, 16.1, 15.7, 16.5, 15.9],
    "bec_haut": [8.0, 8.3, 7.8, 8.5, 8.1, 10.5, 10.9, 10.2, 11.1, 10.6, 10.7, 14.2, 13.8, 14.6, 14.0],
    "aile":     [67, 68, 66, 69, 67, 74, 75, np.nan, 76, 74, 75, 80, 79, 81, 80],
    "masse":    [15, 16, 14, 16, 15, 21, 22, 20, 23, 21, 22, 28, 27, 29, 28],
}
df = pd.DataFrame(data)
print(df.head())
       espece  bec_long  bec_haut  aile  masse
0  fuliginosa       9.1       8.0  67.0     15
1  fuliginosa       9.4       8.3  68.0     16
2  fuliginosa       8.9       7.8  66.0     14
3  fuliginosa       9.6       8.5  69.0     16
4  fuliginosa       9.2       8.1  67.0     15

En pratique, les données arrivent souvent dans un fichier CSV. On sauvegarde avec to_csv et on recharge avec read_csv — le point de départ de presque toute analyse.

df.to_csv("pinsons.csv", index=False)
df2 = pd.read_csv("pinsons.csv")
print(df2.head())
       espece  bec_long  bec_haut  aile  masse
0  fuliginosa       9.1       8.0  67.0     15
1  fuliginosa       9.4       8.3  68.0     16
2  fuliginosa       8.9       7.8  66.0     14
3  fuliginosa       9.6       8.5  69.0     16
4  fuliginosa       9.2       8.1  67.0     15

À vous. Construisez un petit DataFrame oiseaux à partir du dictionnaire {"nom": ["merle", "pinson"], "taille": [24, 15]}, puis affichez-le.

# À vous : creez oiseaux = pd.DataFrame({"nom": ["merle", "pinson"], "taille": [24, 15]}) puis affichez-le

3. Explorer une table

Avant tout calcul, on regarde la table : shape (dimensions), info() (colonnes et types), describe() (résumé statistique des colonnes numériques).

print(df.shape)
(15, 5)
df.info()
<class 'pandas.DataFrame'>
RangeIndex: 15 entries, 0 to 14
Data columns (total 5 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   espece    15 non-null     str    
 1   bec_long  15 non-null     float64
 2   bec_haut  15 non-null     float64
 3   aile      14 non-null     float64
 4   masse     15 non-null     int64  
dtypes: float64(3), int64(1), str(1)
memory usage: 732.0 bytes
print(df.describe().round(2))
       bec_long  bec_haut   aile  masse
count     15.00     15.00  14.00  15.00
mean      12.55     10.75  73.64  21.13
std        2.75      2.41   5.36   5.18
min        8.90      7.80  66.00  14.00
25%        9.50      8.40  68.25  16.00
50%       12.90     10.60  74.50  21.00
75%       14.60     12.45  78.25  25.00
max       16.50     14.60  81.00  29.00

describe() livre d’un coup moyenne, écart-type, minimum, maximum et quartiles de chaque mesure. On remarque que aile n’a que 14 valeurs (count) au lieu de 15 : une mesure manque — on y reviendra.

À vous. Affichez la shape du DataFrame df, puis ses 3 premières lignes avec df.head(3).

# À vous : affichez df.shape puis df.head(3)

4. Sélectionner des colonnes et des lignes

Une colonne se prend par son nom : df["masse"] (c’est une Series). Plusieurs colonnes : une liste de noms. Une ligne se prend par son étiquette avec loc.

print(df["masse"].head())
0    15
1    16
2    14
3    16
4    15
Name: masse, dtype: int64
print(df[["espece", "masse"]].head())
       espece  masse
0  fuliginosa     15
1  fuliginosa     16
2  fuliginosa     14
3  fuliginosa     16
4  fuliginosa     15
print(df.loc[0])
print(df.loc[0, "espece"])
espece      fuliginosa
bec_long           9.1
bec_haut           8.0
aile              67.0
masse               15
Name: 0, dtype: object
fuliginosa

À vous. Affichez la colonne bec_long (avec .head()), puis les deux colonnes espece et aile ensemble (avec .head()).

# À vous : affichez df["bec_long"].head() puis df[["espece", "aile"]].head()

5. Filtrer avec un masque

Comme avec NumPy, une comparaison produit un masque booléen ; placé entre crochets, il ne garde que les lignes qui vérifient la condition. Ici, les spécimens de plus de 25 g.

print(df[df["masse"] > 25])
          espece  bec_long  bec_haut  aile  masse
11  magnirostris      16.1      14.2  80.0     28
12  magnirostris      15.7      13.8  79.0     27
13  magnirostris      16.5      14.6  81.0     29
14  magnirostris      15.9      14.0  80.0     28

Tous les spécimens de plus de 25 g sont des magnirostris : le filtre a isolé la plus grande espèce, sans qu’on l’ait nommée.

À vous. Affichez les spécimens dont la bec_long est supérieure à 14 mm.

# À vous : affichez df[df["bec_long"] > 14]

6. Créer une colonne

On calcule une nouvelle colonne à partir des colonnes existantes — l’opération est vectorisée, exactement comme avec NumPy. Ici, un rapport de forme du bec (longueur sur hauteur).

df["rapport_bec"] = (df["bec_long"] / df["bec_haut"]).round(2)
print(df[["espece", "bec_long", "bec_haut", "rapport_bec"]].head())
       espece  bec_long  bec_haut  rapport_bec
0  fuliginosa       9.1       8.0         1.14
1  fuliginosa       9.4       8.3         1.13
2  fuliginosa       8.9       7.8         1.14
3  fuliginosa       9.6       8.5         1.13
4  fuliginosa       9.2       8.1         1.14

À vous. Créez une colonne masse_mg égale à la masse convertie en milligrammes (df["masse"] * 1000), puis affichez df[["espece", "masse", "masse_mg"]].head().

# À vous : df["masse_mg"] = df["masse"] * 1000, puis affichez df[["espece", "masse", "masse_mg"]].head()

7. Trier

sort_values("colonne") réordonne les lignes selon une colonne. Par défaut, du plus petit au plus grand.

print(df.sort_values("masse").head())
       espece  bec_long  bec_haut  aile  masse  rapport_bec
2  fuliginosa       8.9       7.8  66.0     14         1.14
0  fuliginosa       9.1       8.0  67.0     15         1.14
4  fuliginosa       9.2       8.1  67.0     15         1.14
1  fuliginosa       9.4       8.3  68.0     16         1.13
3  fuliginosa       9.6       8.5  69.0     16         1.13

À vous. Affichez les lignes triées par bec_long (avec .head()).

# À vous : affichez df.sort_values("bec_long").head()

8. Les valeurs manquantes

Une mesure absente est notée NaN. isna() la repère, .sum() compte les valeurs manquantes. On peut ensuite supprimer les lignes concernées (dropna) ou combler le trou (fillna).

print(df["aile"].isna().sum())
1
print(df[df["aile"].isna()])
   espece  bec_long  bec_haut  aile  masse  rapport_bec
7  fortis      12.5      10.2   NaN     20         1.23

Une seule mesure manque : l’aile d’un fortis. Plutôt que de jeter toute la ligne, on peut combler le trou par la moyenne des ailes connues.

aile_pleine = df["aile"].fillna(df["aile"].mean())
print(round(df["aile"].mean(), 2))
print(aile_pleine.isna().sum())
73.64
0

À vous. Comptez les valeurs manquantes de la colonne bec_long avec df["bec_long"].isna().sum().

# À vous : affichez df["bec_long"].isna().sum()

9. Grouper : le cœur de pandas

value_counts() compte les occurrences de chaque valeur d’une colonne. groupby("espece") rassemble les lignes par espèce ; on applique ensuite une agrégation (mean, sum…) à chaque groupe.

print(df["espece"].value_counts())
espece
fortis          6
fuliginosa      5
magnirostris    4
Name: count, dtype: int64
print(df.groupby("espece")["masse"].mean().round(2))
espece
fortis          21.5
fuliginosa      15.2
magnirostris    28.0
Name: masse, dtype: float64
print(df.groupby("espece")[["bec_long", "aile", "masse"]].mean().round(2))
              bec_long  aile  masse
espece                             
fortis           12.98  74.8   21.5
fuliginosa        9.24  67.4   15.2
magnirostris     16.05  80.0   28.0

En une ligne, groupby révèle le profil de chaque espèce : magnirostris a le bec le plus long, l’aile la plus grande et la masse la plus élevée ; fuliginosa est la plus petite sur les trois mesures. C’est exactement le genre de résumé qu’on cherche en analyse de données.

À vous. Affichez la hauteur de bec moyenne par espèce avec df.groupby("espece")["bec_haut"].mean().round(2).

# À vous : affichez df.groupby("espece")["bec_haut"].mean().round(2)

10. Pièges fréquents

  • Une colonne est une Series : df["masse"] (crochets simples). Avec une liste de noms, df[["espece", "masse"]], on obtient un DataFrame (crochets doubles).
  • loc (étiquette) vs iloc (position) : df.loc[0] cible l’étiquette 0 ; df.iloc[0] la première ligne, quelle que soit son étiquette.
  • NaN se propage : une valeur manquante n’est pas 0 ; il faut la traiter (dropna, fillna) avant certains calculs.
  • Un filtre ne modifie pas la table : df[df["masse"] > 25] renvoie une nouvelle vue ; pour garder le résultat, on l’affecte à une variable.

Récapitulatif

  • DataFrame : une table à colonnes nommées, bâtie sur NumPy ; Series : une colonne.
  • Créer / charger : pd.DataFrame({...}), read_csv, to_csv.
  • Explorer : shape, info(), describe(), head().
  • Sélectionner : df["col"], df[["c1", "c2"]], df.loc[...].
  • Filtrer : masque booléen df[df["col"] > v].
  • Créer une colonne : à partir des colonnes existantes (vectorisé).
  • Trier : sort_values("col").
  • Valeurs manquantes : isna, dropna, fillna.
  • Grouper : value_counts(), groupby("col").mean().

Prochaine séance — S8 : visualiser ces données (histogrammes, nuages de points) et faire le pont vers le Machine Learning.

Retour au sommet