Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Pandas

La bibliothèque pandas permet de manipuler et d’analyser facilement des données, grâce à des outils rapides et efficaces. L’élément principal de pandas s’appelle le DataFrame. On peut imaginer le DataFrame comme un tableau à deux dimensions, similaire à une feuille de calcul Excel, avec des noms de colonnes et des indices de lignes.

Grâce à pandas, on peut faire beaucoup de choses que l’on fait habituellement dans Excel :

  • créer des tableaux croisés dynamiques,

  • faire des calculs sur des colonnes,

  • créer des graphiques,

  • regrouper les données selon les valeurs d’une colonne,

  • ou encore rassembler plusieurs tableaux comme dans les bases de données (SQL).

Pandas est aussi très pratique pour travailler avec des séries temporelles (des données organisées par dates, par exemple).

À savoir avant de commencer : Pandas fonctionne très souvent avec une autre bibliothèque nommée NumPy. Si tu ne connais pas NumPy, il est conseillé de jeter un œil à son tutoriel avant de commencer.

Mise en place

Pour commencer, il faut importer la bibliothèque pandas. On la renomme souvent pd pour aller plus vite :

import pandas as pd

Les objets Series

La bibliothèque pandas propose plusieurs types de structures de données très pratiques :

  • Les objets Series : On va en parler tout de suite. Une Series, c’est comme une colonne dans un tableau Excel : une liste de valeurs (une seule dimension), avec un nom de colonne et des étiquettes pour les lignes.

  • Les objets DataFrame : C’est un tableau à deux dimensions, donc plusieurs colonnes et plusieurs lignes, comme un tableau Excel complet.

  • Les objets Panel : Ce sont comme des dictionnaires de DataFrame. Ils sont très peu utilisés, donc on n’en parle pas ici.

Créer une Series

Voyons comment créer notre première Series :

Ici, on crée une liste de nombres : 2, -1, 3 et 5. C’est tout ! Tu peux imaginer ça comme une colonne de chiffres dans Excel.

s = pd.Series([2, -1, 3, 5])
s
Loading...

Presque comme un tableau NumPy

Les objets Series ressemblent beaucoup aux tableaux de la bibliothèque NumPy (qu’on appelle ndarray). On peut donc souvent utiliser des fonctions NumPy sur une Series :

import numpy as np
np.exp(s)  # On applique la fonction exponentielle à chaque nombre de la Series
Loading...

On peut aussi faire des calculs directement : Quand tu ajoutes des listes ou des nombres, ça s’applique à chaque élément, automatiquement :

s + [1000, 2000, 3000, 4000]  # Additionne chaque valeur avec la liste correspondante
Loading...

Si tu ajoutes un seul nombre, il sera ajouté à tous les éléments : C’est ce qu’on appelle la diffusion (“broadcasting”).

s + 1000  # Ajoute 1000 à chaque valeur
Loading...

C’est pareil pour toutes les opérations : multiplication, division, ou même des conditions :

s < 0  # Retourne True ou False pour chaque valeur selon si elle est négative
Loading...

Les étiquettes d’index (index labels)

Chaque valeur d’une Series a un identifiant unique appelé étiquette d’index. Par défaut, ce sont juste des numéros (0, 1, 2, …). Mais tu peux choisir des étiquettes personnalisées :

s2 = pd.Series([9, 10, 3, 8], index=["Etoo", "Mboma", "Wome", "Njitap"])
s2
Loading...

Ici, chaque valeur correspond à un nom (par exemple, “Wome”).

Tu peux alors utiliser la Series comme un dictionnaire :

s2["Wome"]  # Donne la valeur associée à “Wome”
np.int64(3)

Pour être sûr de ne pas te tromper, il vaut mieux utiliser :

  • .loc pour accéder à une valeur par son étiquette

  • .iloc pour accéder par son numéro d’ordre

Exemples :

s2.loc["Wome"]    # Avec l’étiquette “Wome”
np.int64(3)
s2.iloc[1]       # Avec la position numéro 1 (la deuxième)
np.int64(10)

Tu peux aussi “découper” une Series (on appelle ça “slicing”) pour prendre une partie des valeurs. Les étiquettes suivront le découpage :

s2.iloc[1:3]  # Prend les lignes 1 et 2 (donc “bob” et “charles”)
Loading...

Mais fais attention quand tu utilises les indices numériques : Regarde cet exemple :

surprise = pd.Series([1000, 1001, 1002, 1003])
surprise
Loading...
surprise_slice = surprise[2:]
surprise_slice
Loading...

Ici, la première valeur du découpage a pour étiquette “2”, car on a commencé à la troisième valeur de la liste de base.

Si tu essaies d’accéder à surprise_slice[0], tu obtiens une erreur, car il n’y a plus d’étiquette “0” dans cette nouvelle liste :

try:
    surprise_slice[0]
except KeyError as e:
    print("Erreur de clé :", e)
Erreur de clé : 0

Mais tu peux toujours accéder aux éléments par leur position avec .iloc :

C’est pour ça qu’il est toujours recommandé d’utiliser .loc et .iloc pour accéder aux valeurs d’une Series.

surprise_slice.iloc[0]  # Prend la première valeur du découpage (qui a l’étiquette “2”)
np.int64(1002)

Créer une Series à partir d’un dictionnaire

On peut créer un objet Series directement à partir d’un dictionnaire. Les clés deviendront les étiquettes (index) :

poids_personnes = {"Brice": 70, "Estelle": 58, "Valery": 83, "Nathalie": 64}
serie_poids = pd.Series(poids_personnes)
serie_poids
Loading...

Ici, chaque personne (par exemple, “Brice”) a son poids associé.


On peut aussi choisir quels éléments on veut inclure, et dans quel ordre, en précisant les index souhaités :

serie_selection = pd.Series(poids_personnes, index=["Valery", "Brice"])
serie_selection
Loading...

Alignement automatique

Quand tu fais des opérations avec plusieurs objets Series, pandas aligne automatiquement les éléments en utilisant les étiquettes d’index.

Exemple :

notes_exam = pd.Series([14, 17, 12, 16], index=["Brice", "Estelle", "Valery", "Nathalie"])
print(notes_exam.keys())
print(serie_poids.keys())

notes_exam + serie_poids
Index(['Brice', 'Estelle', 'Valery', 'Nathalie'], dtype='object')
Index(['Brice', 'Estelle', 'Valery', 'Nathalie'], dtype='object')
Loading...

Le résultat va contenir toutes les étiquettes présentes dans au moins une des deux Series. Si une personne est absente dans une Series, le résultat sera “NaN” (c’est-à-dire “Non défini”, “donnée manquante”).

Fais attention : Si les étiquettes n’ont rien à voir, le résultat sera une Series remplie de “NaN” :

serie_faux = pd.Series([1000, 1000, 1000, 1000])
print("notes_exam =", notes_exam.values)
print("serie_faux =", serie_faux.values)

notes_exam + serie_faux
notes_exam = [14 17 12 16]
serie_faux = [1000 1000 1000 1000]
Loading...

Ici, pandas n’a pas pu faire correspondre les index, donc tout est “NaN”.

Initialiser une Series avec un seul nombre

On peut aussi créer une Series où toutes les valeurs sont identiques, en utilisant un nombre et une liste d’index :

secret_code = pd.Series(237, ["cameroun", "monde", "univers"])
secret_code
Loading...

Ajouter un nom à une Series

On peut donner un nom à notre Series pour savoir ce qu’elle représente :

serie_poids_nommee = pd.Series([83, 89], index=["Valery", "Brice"], name="Poids en kg")
serie_poids_nommee
Loading...

Tracer une Series (créer un graphique)

Pandas permet de tracer facilement des graphiques à partir d’une Series. Il suffit d’utiliser matplotlib et d’appeler la méthode plot() :

import matplotlib.pyplot as plt

temperature_yaounde = [22, 23, 24, 24, 25, 26, 25, 24, 23, 22, 21, 21]
mois = [
    "Janvier", "Février", "Mars", "Avril", "Mai", "Juin",
    "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"
]
serie_temperature = pd.Series(temperature_yaounde, index=mois, name="Température à Yaoundé")
serie_temperature.plot()
plt.show()
<Figure size 640x480 with 1 Axes>

Il existe plein de façons d’afficher tes données (histogrammes, camemberts, etc.). Pour découvrir toutes les options, regarde la documentation officielle de pandas : Documentation sur la visualisation

Gérer le temps avec pandas

Très souvent, les données comportent des dates et heures. Pandas est très pratique pour tout ce qui touche aux séries temporelles :

  • gérer les périodes (par exemple : “2023Q3” = troisième trimestre 2023),

  • convertir des périodes en dates exactes et inversement,

  • regrouper ou “ré-échantillonner” des données (par semaine, par mois, etc.),

  • gérer les fuseaux horaires.

Créer une série temporelle

On peut facilement créer une liste de dates avec pd.date_range() :

heures_journee = pd.date_range('2025/06/14 6:00', periods=12, freq='h')
heures_journee
DatetimeIndex(['2025-06-14 06:00:00', '2025-06-14 07:00:00', '2025-06-14 08:00:00', '2025-06-14 09:00:00', '2025-06-14 10:00:00', '2025-06-14 11:00:00', '2025-06-14 12:00:00', '2025-06-14 13:00:00', '2025-06-14 14:00:00', '2025-06-14 15:00:00', '2025-06-14 16:00:00', '2025-06-14 17:00:00'], dtype='datetime64[ns]', freq='h')

Ici, on crée une suite de 12 heures à partir de 6h du matin le 14 juin 2023.

On peut ensuite utiliser ces dates comme index dans une Series :

temperatures_journee = [22, 22, 23, 24, 25, 26, 27, 27, 26, 25, 24, 23]
serie_temp_journee = pd.Series(temperatures_journee, index=heures_journee)
serie_temp_journee
Loading...

Pour voir l’évolution sur un graphique :

serie_temp_journee.plot(kind="bar")
plt.grid(True)
plt.show()
<Figure size 640x480 with 1 Axes>

Ré-échantillonnage (Resampling)

Supposons que tu veuilles résumer ces températures toutes les 2 heures au lieu d’avoir une valeur par heure. On utilise la méthode resample() :

# On prépare la transformation, mais ce n’est pas encore une vraie Series
temp_2h = serie_temp_journee.resample("2h")

# Maintenant on calcule la moyenne pour chaque bloc de 2 heures
temp_2h_moyenne = temp_2h.mean()
temp_2h_moyenne
Loading...

On peut aussi afficher ce résultat :

temp_2h_moyenne.plot(kind="bar")
plt.show()
<Figure size 640x480 with 1 Axes>

On aurait aussi pu calculer, par exemple, le minimum pour chaque bloc de 2 heures :

temp_2h_min = serie_temp_journee.resample("2h").min()
temp_2h_min
Loading...

Ou utiliser une fonction personnalisée :

temp_2h_custom = serie_temp_journee.resample("2h").apply("min")
temp_2h_custom
Loading...

Sur-échantillonnage et interpolation

Tout à l’heure, on a vu comment réduire la fréquence des mesures (par exemple, regrouper par 2 heures). Mais on peut aussi augmenter la fréquence (par exemple, passer de toutes les heures à toutes les 15 minutes). Cela crée des « trous » dans les données : il n’y a pas de valeur pour les heures intermédiaires.

# Ici, on veut une température toutes les 15 minutes, mais nos mesures de base sont toutes les heures
temp_15min = serie_temp_journee.resample("15Min").mean()
temp_15min.head(10)  # Affiche les 10 premiers résultats
Loading...

Pour remplir les trous, on peut utiliser l’interpolation : pandas va « deviner » les valeurs manquantes. Par défaut, il utilise l’interpolation linéaire (en ligne droite), mais tu peux choisir d’autres méthodes, comme “cubic” (plus lisse) :

temp_15min = serie_temp_journee.resample("15Min").interpolate(method="cubic")
temp_15min.head(10)
Loading...

On peut afficher les deux séries sur un graphique pour voir la différence :

serie_temp_journee.plot(label="Période : 1 heure")
temp_15min.plot(label="Période : 15 minutes")
plt.legend()
plt.show()
<Figure size 640x480 with 1 Axes>

Les fuseaux horaires

Par défaut, les dates et heures dans pandas ne précisent pas le fuseau horaire. Par exemple, “2023-06-14 08:00” peut vouloir dire 8h à Douala ou 8h à Paris. On peut rendre ces dates “conscientes” du fuseau en utilisant tz_localize :

serie_temp_douala = serie_temp_journee.tz_localize("Africa/Douala")
serie_temp_douala
Loading...

On voit alors le décalage UTC : pour Douala, c’est souvent “+01:00”.

On peut convertir cette série à l’heure de Paris :

serie_temp_paris = serie_temp_douala.tz_convert("Europe/Paris")
serie_temp_paris
Loading...

Si tu enlèves le fuseau horaire, les heures deviennent à nouveau “ambiguës” :

serie_temp_paris_naive = serie_temp_paris.tz_localize(None)
serie_temp_paris_naive
Loading...

Si tu essaies de remettre le fuseau, parfois pandas te prévient qu’il y a ambiguïté (comme lors des changements d’heure été/hiver) :

try:
    serie_temp_paris_naive.tz_localize("Europe/Paris")
except Exception as e:
    print(type(e))
    print(e)

Mais on peut dire à pandas de deviner le bon sens avec ambiguous="infer" :

serie_temp_paris_naive.tz_localize("Europe/Paris", ambiguous="infer")
Loading...

Les périodes

La fonction pd.period_range() permet de créer une liste de périodes (par exemple, les trimestres ou les mois) au lieu de dates précises.

Par exemple, on peut lister tous les trimestres sur 2 ans :

trimestres = pd.period_range('2022Q1', periods=8, freq='Q')
trimestres
PeriodIndex(['2022Q1', '2022Q2', '2022Q3', '2022Q4', '2023Q1', '2023Q2', '2023Q3', '2023Q4'], dtype='period[Q-DEC]')

On peut “décaler” toutes les périodes :

trimestres + 2  # Décale chaque trimestre de 2 trimestres
PeriodIndex(['2022Q3', '2022Q4', '2023Q1', '2023Q2', '2023Q3', '2023Q4', '2024Q1', '2024Q2'], dtype='period[Q-DEC]')

Avec asfreq(), on change la “précision” des périodes : Par exemple, transformer chaque trimestre en mois (on « zoome » dans la période) :

trimestres.asfreq("M")  # Affiche le dernier mois de chaque trimestre
PeriodIndex(['2022-03', '2022-06', '2022-09', '2022-12', '2023-03', '2023-06', '2023-09', '2023-12'], dtype='period[M]')

On peut aussi demander le premier mois avec how="start" :

trimestres.asfreq("M", how="start")
PeriodIndex(['2022-01', '2022-04', '2022-07', '2022-10', '2023-01', '2023-04', '2023-07', '2023-10'], dtype='period[M]')

Ou encore “dézoomer” pour voir l’année :

trimestres.asfreq("Y")
PeriodIndex(['2022', '2022', '2022', '2022', '2023', '2023', '2023', '2023'], dtype='period[Y-DEC]')

On peut créer une Series avec ces périodes : Supposons qu’on ait les revenus trimestriels d’une petite entreprise au Cameroun :

revenus_trimestriels = pd.Series(
    [2_500_000, 2_600_000, 2_700_000, 2_800_000, 2_900_000, 3_000_000, 2_850_000, 3_100_000],
    index=trimestres
)
revenus_trimestriels
Loading...

On peut les tracer :

revenus_trimestriels.plot(kind="line")
plt.show()
<Figure size 640x480 with 1 Axes>

On peut transformer ces périodes en dates précises avec to_timestamp() : (par défaut, ça donne le premier jour de chaque période, mais on peut demander le dernier)

dernieres_heures = revenus_trimestriels.to_timestamp(how="end", freq="h")
dernieres_heures
Loading...

Et on peut revenir à une périodicité avec to_period() :

dernieres_heures.to_period()
Loading...

Pandas propose beaucoup d’autres fonctions liées au temps : Par exemple, pour obtenir le dernier jour ouvré (jour travaillé) de chaque mois à 8h du matin en 2022 :

mois_2022 = pd.period_range("2022", periods=12, freq="M")
lendemain_dernier_jour = mois_2022.asfreq("D") + 1
dernier_jour_ouvre = lendemain_dernier_jour.to_timestamp() - pd.tseries.offsets.BDay()
dernier_jour_ouvre.to_period("h") + 8
PeriodIndex(['2022-01-31 08:00', '2022-02-28 08:00', '2022-03-31 08:00', '2022-04-29 08:00', '2022-05-31 08:00', '2022-06-30 08:00', '2022-07-29 08:00', '2022-08-31 08:00', '2022-09-30 08:00', '2022-10-31 08:00', '2022-11-30 08:00', '2022-12-30 08:00'], dtype='period[h]')

Les objets DataFrame

Un objet DataFrame représente un tableau, un peu comme une feuille Excel :

  • Il a des valeurs dans des cases (cellules),

  • des noms de colonnes (ex : “poids”, “année de naissance”),

  • et des étiquettes de lignes (ex : noms des personnes).

Avec un DataFrame, tu peux facilement faire des calculs, créer des tableaux croisés, grouper les lignes, tracer des graphiques, etc. On peut voir un DataFrame comme un dictionnaire de Series (chaque colonne est une Series).

Créer un DataFrame

Pour créer un DataFrame, on peut passer un dictionnaire de Series :

infos_personnes = {
    "poids_kg": pd.Series([70, 83, 92], index=["Brice", "Estelle", "Mamadou"]),
    "annee_naissance": pd.Series([1995, 1998, 2000], index=["Estelle", "Brice", "Mamadou"], name="année"),
    "enfants": pd.Series([2, 1], index=["Mamadou", "Estelle"]),
    "loisir": pd.Series(["Football", "Danse"], index=["Brice", "Estelle"]),
}
personnes = pd.DataFrame(infos_personnes)
personnes
Loading...

À retenir :

  • Les Series sont automatiquement alignées grâce aux noms (Brice, Estelle, etc.).

  • Les valeurs manquantes sont indiquées par NaN.

  • Le nom de la Series (“année”) est ignoré.

  • Dans les notebooks (comme Jupyter), les DataFrame s’affichent très bien.

Tu peux accéder à une colonne simplement comme ceci :

personnes["annee_naissance"]
Loading...

Pour avoir plusieurs colonnes à la fois :

personnes[["annee_naissance", "loisir"]]
Loading...

Si tu veux t’assurer que seules certaines colonnes ou lignes existent (dans un certain ordre), tu peux les préciser lors de la création :

d2 = pd.DataFrame(
    infos_personnes,
    columns=["annee_naissance", "poids_kg", "taille_cm"],
    index=["Estelle", "Brice", "Patrice"]
)
d2
Loading...

Autre manière de créer un DataFrame : En passant une liste de listes ou un tableau NumPy, et en précisant les noms de colonnes et de lignes :

valeurs = [
    [1998, 1, "Danse", 83],
    [1995, None, "Football", 70],
    [2000, 2, None, 92]
]
d3 = pd.DataFrame(
    valeurs,
    columns=["annee_naissance", "enfants", "loisir", "poids_kg"],
    index=["Estelle", "Brice", "Mamadou"]
)
d3
Loading...

Ici, None indique une valeur manquante.

Tu peux aussi utiliser les “masked arrays” de NumPy pour indiquer des valeurs absentes :

masked_array = np.ma.asarray(valeurs, dtype=object)
masked_array[(0, 2), (1, 2)] = np.ma.masked
d3 = pd.DataFrame(
    masked_array,
    columns=["annee_naissance", "enfants", "loisir", "poids_kg"],
    index=["Estelle", "Brice", "Mamadou"]
)
d3
Loading...

Tu peux créer un DataFrame à partir d’un autre DataFrame :

d4 = pd.DataFrame(
    d3,
    columns=["loisir", "enfants"],
    index=["Estelle", "Brice"]
)
d4
Loading...

Ou même à partir d’un dictionnaire de dictionnaires :

personnes = pd.DataFrame({
    "annee_naissance": {"Brice": 1995, "Estelle": 1998, "Mamadou": 2000},
    "loisir": {"Brice": "Football", "Estelle": "Danse"},
    "poids_kg": {"Brice": 70, "Estelle": 83, "Mamadou": 92},
    "enfants": {"Estelle": 1, "Mamadou": 2}
})
personnes
Loading...

Multi-index

Si toutes les colonnes sont des tuples de la même taille, pandas comprend qu’il s’agit d’un multi-index (plusieurs niveaux de colonnes/indices). Par exemple :

d5 = pd.DataFrame(
    {
        ("public", "annee_naissance"): {("Douala", "Brice"): 1995, ("Douala", "Estelle"): 1998, ("Yaoundé", "Mamadou"): 2000},
        ("public", "loisir"): {("Douala", "Brice"): "Football", ("Douala", "Estelle"): "Danse"},
        ("prive", "poids_kg"): {("Douala", "Brice"): 70, ("Douala", "Estelle"): 83, ("Yaoundé", "Mamadou"): 92},
        ("prive", "enfants"): {("Douala", "Brice"): None, ("Douala", "Estelle"): 1, ("Yaoundé", "Mamadou"): 2}
    }
)
d5
Loading...

On peut alors récupérer facilement toutes les colonnes “public” :

d5["public"]
d5["public", "loisir"]
Loading...

Supprimer un niveau

On peut supprimer un niveau de colonnes avec droplevel() :

d5.columns = d5.columns.droplevel(level=0)
d5
Loading...

Transposer

On peut inverser colonnes et lignes avec .T :

d6 = d5.T
d6
Loading...

“Stack” et “Unstack”

  • stack() regroupe les colonnes vers les lignes (on “empile” un niveau de colonne vers l’index).

  • unstack() fait l’inverse.

d7 = d6.stack()
d7
<ipython-input-78-3230766657>:1: FutureWarning: The previous implementation of stack is deprecated and will be removed in a future version of pandas. See the What's New notes for pandas 2.1.0 for details. Specify future_stack=True to adopt the new implementation and silence this warning.
  d7 = d6.stack()
Loading...
d8 = d7.unstack()
d8
Loading...
d9 = d8.unstack()
d9
Loading...
d10 = d9.unstack(level=(0,1))
d10
Loading...

À retenir : La plupart des méthodes pandas (comme stack et unstack) ne modifient pas l’objet original, mais renvoient une nouvelle copie modifiée.

Accéder aux lignes

Revenons à notre DataFrame personnes :

personnes
Loading...

Pour accéder à une ligne par son nom (étiquette), utilise loc :

personnes.loc["Mamadou"]
Loading...

Pour accéder par la position (numéro de la ligne), utilise iloc :

personnes.iloc[2]
Loading...

Pour obtenir plusieurs lignes à la fois :

personnes.iloc[1:3]
Loading...

Tu peux aussi utiliser des conditions pour filtrer :

personnes[personnes["annee_naissance"] < 1998]
Loading...

Ajouter et supprimer des colonnes

Un DataFrame fonctionne comme un dictionnaire de Series. Voici quelques exemples :

# On affiche le tableau de base
personnes
Loading...

Ajouter une colonne « âge » calculée automatiquement (exemple avec l’année 2024) :

personnes["age"] = 2024 - personnes["annee_naissance"]  # Ajoute une colonne "age"
personnes["plus_de_25_ans"] = personnes["age"] > 25     # Ajoute une colonne booléenne
personnes
Loading...

Retirer une colonne et la stocker ailleurs :

annees_naissance = personnes.pop("annee_naissance")

Supprimer une colonne simplement :

del personnes["enfants"]
personnes
Loading...

Ajouter une colonne, même si certains noms sont absents ou en trop : (Les lignes manquantes auront NaN, les lignes en trop sont ignorées.)

personnes["animaux"] = pd.Series({"Estelle": 0, "Mamadou": 2, "Patrice": 1})  # Brice est absent, Patrice sera ignoré
personnes
Loading...

Pour insérer une colonne à une position précise (par exemple en 2ème colonne) :

personnes.insert(1, "taille_cm", [165, 172, 180])
personnes
Loading...

Créer de nouvelles colonnes avec assign

On peut créer de nouvelles colonnes sans modifier le DataFrame original :

personnes_mod = personnes.assign(
    imc = personnes["poids_kg"] / (personnes["taille_cm"] / 100) ** 2,
    a_des_animaux = personnes["animaux"] > 0
)
personnes_mod
Loading...

Attention, tu ne peux pas utiliser une colonne que tu viens tout juste de créer dans la même commande :

try:
    personnes.assign(
        imc = personnes["poids_kg"] / (personnes["taille_cm"] / 100) ** 2,
        surpoids = personnes["imc"] > 25
    )
except KeyError as e:
    print("Erreur de clé :", e)
Erreur de clé : 'imc'

Il faut séparer les deux :

tmp = personnes.assign(imc = personnes["poids_kg"] / (personnes["taille_cm"] / 100) ** 2)
tmp.assign(surpoids = tmp["imc"] > 25)
Loading...

Pour éviter les variables intermédiaires, tu peux utiliser des fonctions (lambda) dans assign :

(personnes
    .assign(imc = lambda df: df["poids_kg"] / (df["taille_cm"] / 100) ** 2)
    .assign(surpoids = lambda df: df["imc"] > 25)
)
Loading...

Évaluer une expression

Pandas permet aussi d’utiliser la méthode eval pour faire des calculs sur les colonnes, un peu comme dans Excel :

On peut même utiliser des variables Python dans l’expression avec @ :

seuil_surpoids = 28
personnes.eval("surpoids = imc > @seuil_surpoids", inplace=True)
personnes
Loading...

Explication du symbole @ dans ce code

En Python, le symbole @ a différentes significations selon le contexte.

personnes.eval("surpoids = imc > @seuil_surpoids", inplace=True)

Rôle du @ dans pandas.eval() :

Ici, @ est utilisé pour référencer une variable Python (seuil_surpoids) à l’intérieur de l’expression évaluée par pandas.eval().

  • Sans @, eval() chercherait une colonne appelée seuil_surpoids dans le DataFrame.

  • Avec @, eval() comprend qu’il faut utiliser la variable externe seuil_surpoids (qui vaut 28 dans votre exemple).

import pandas as pd

# Exemple de DataFrame
personnes = pd.DataFrame({"imc": [25, 30, 22, 28.5]})

# Variable externe (seuil personnalisé)
seuil_surpoids = 28

# Utilisation de eval() avec @ pour référencer la variable
personnes.eval("surpoids = imc > @seuil_surpoids", inplace=True)

Résultat :

imcsurpoids
25.0False
30.0True
22.0False
28.5True

Filtrer un DataFrame avec query

Pour ne garder que certaines lignes selon une condition :

personnes.query("age > 25 and animaux == 0")
Loading...

Trier un DataFrame

Pour trier selon l’index (les noms), par ordre décroissant :

personnes.sort_index(ascending=False)
Loading...

Pour modifier le DataFrame directement, on peut ajouter inplace=True, et pour trier les colonnes au lieu des lignes, ajoute axis=1 :

personnes.sort_index(axis=1, inplace=True)
personnes
Loading...

Pour trier selon les valeurs d’une colonne (ex : “age”) :

personnes.sort_values(by="age", inplace=True)
personnes
Loading...

Tracer un DataFrame

Comme pour les Series, tu peux tracer rapidement des courbes :

personnes.sort_values(by="imc", inplace=True)
personnes.plot(kind="line", x="imc", y=["taille_cm", "poids_kg"])
plt.show()
<Figure size 640x480 with 1 Axes>

Créer un nuage de points (scatter plot) avec taille différente pour chaque personne :

personnes.plot(kind="scatter", x="taille_cm", y="poids_kg", s=[40, 120, 200])
plt.show()
<Figure size 640x480 with 1 Axes>

Il y a plein d’options, va voir la page visualisation pandas pour tout découvrir !

Opérations sur les DataFrames

Même si un DataFrame ne fonctionne pas exactement comme un tableau NumPy, on peut appliquer des fonctions mathématiques à toutes ses valeurs.

Exemple avec des notes d’examen :

notes_array = np.array([[12, 13, 15], [14, 15, 12], [10, 8, 9], [16, 14, 15]])
notes = pd.DataFrame(notes_array, columns=["janv", "fév", "mars"], index=["Brice", "Estelle", "Mamadou", "Patrice"])
notes
Loading...

Prendre la racine carrée de toutes les notes :

np.sqrt(notes)
Loading...

Ajouter 1 point à tout le monde :

notes + 1
Loading...

Comparer toutes les notes à 10 (True ou False) :

notes >= 10
Loading...

Calculer la moyenne de chaque colonne :

notes.mean()
Loading...

Savoir dans quels mois tout le monde a eu plus de 10 :

(notes > 10).all()
Loading...

Savoir qui a eu au moins une note de 15 :

(notes == 15).any(axis=1)
Loading...

On peut aussi soustraire la moyenne de chaque colonne à chaque note :

notes - notes.mean()
Loading...

Si tu veux soustraire la moyenne globale :

notes - notes.values.mean()
Loading...

Alignement automatique

Quand tu fais des opérations avec plusieurs DataFrames, pandas aligne automatiquement les noms de lignes et de colonnes. Par exemple, pour ajouter un tableau de bonus aux notes :

bonus_array = np.array([[0, np.nan, 2], [np.nan, 1, 0], [1, 1, 0], [2, 2, 1]])
bonus = pd.DataFrame(bonus_array, columns=["fév", "mars", "avr"], index=["Estelle", "Mamadou", "Patrice", "Brice"])
notes + bonus

Certaines cases sont vides (NaN) car il n’y avait pas la même personne ou le même mois dans les deux tableaux.

Gérer les valeurs manquantes

Dans la vie réelle, il manque souvent des données. Avec pandas, tu peux remplacer toutes les valeurs manquantes (NaN) par la valeur de ton choix avec fillna :

(notes + bonus).fillna(0)

Gérer les valeurs manquantes de façon équitable

C’est un peu injuste de remplacer toutes les notes manquantes par zéro ! Imaginons plutôt que les notes absentes sont vraiment absentes, mais que les bonus absents doivent être considérés comme zéro.

bonus_corriges = bonus.fillna(0)
bonus_corriges.insert(0, "janv", 0)
bonus_corriges.loc["Brice"] = 0
notes + bonus_corriges

Comme ça, on a ajouté des bonus là où il en manque, sans inventer de notes là où on n’en a pas.

Interpoler les données manquantes

Une autre solution pour compléter les données manquantes est l’interpolation. Par exemple, si un élève a eu 0 bonus en janvier et 2 en mars, pandas peut “deviner” combien il a eu en février en faisant la moyenne.

bonus.interpolate(axis=1)

Mais s’il manque une valeur au tout début (par exemple, janvier), l’interpolation ne fonctionne pas. On peut régler ça en ajoutant d’abord une colonne avec zéro pour janvier :

bonus_mieux = bonus.copy()
bonus_mieux.insert(0, "janv", 0)
bonus_mieux.loc["Brice"] = 0
bonus_mieux = bonus_mieux.interpolate(axis=1)
bonus_mieux
Loading...

Maintenant, toutes les valeurs sont raisonnables. On peut calculer les notes finales :

notes["mars"] = np.nan
notes_finales = notes + bonus_mieux
notes_finales
Loading...

Nettoyer les données

Si une colonne entière ou une ligne entière est vide (NaN), on peut l’enlever pour clarifier les résultats.

Pour enlever les lignes sans aucune note :

notes_finales_propres = notes_finales.dropna(how="all")
notes_finales_propres
Loading...

Pour enlever les colonnes totalement vides :

notes_finales_propres = notes_finales_propres.dropna(axis=1, how="all")
notes_finales_propres
Loading...

Grouper et agréger (GroupBy)

Comme en SQL, pandas permet de regrouper les données selon une colonne, puis d’effectuer des calculs pour chaque groupe. Par exemple, ajoutons les loisirs de chaque élève :

import numpy as np
notes_finales["loisir"] = ["Football", "Danse", np.nan, "Danse"]
notes_finales
Loading...

On groupe par loisir :

groupes_notes = notes_finales.groupby("loisir")
groupes_notes
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x794d4327bb90>

Calculer la moyenne par loisir :

groupes_notes.mean()
Loading...

Les valeurs manquantes (NaN) sont ignorées pour la moyenne.

Tableaux croisés dynamiques (Pivot Table)

Pandas gère très bien les tableaux croisés, comme dans Excel. Imaginons ce DataFrame simplifié :

import numpy as np
plus_de_notes = notes_finales_propres.stack().reset_index()
plus_de_notes.columns = ["nom", "mois", "note"]
plus_de_notes["bonus"] = [np.nan, np.nan, 0, 2, 3, 0, 0, 1]
display(plus_de_notes)
Loading...

Créer une table croisée avec la moyenne des notes et bonus par élève :

pd.pivot_table(plus_de_notes[["nom", "note", "bonus"]], index="nom")
Loading...

Changer la fonction d’agrégation, par exemple prendre le maximum :

pd.pivot_table(plus_de_notes, index="nom", values=["note", "bonus"], aggfunc="max")
Loading...

Afficher les notes par élève et par mois, avec le total :

pd.pivot_table(plus_de_notes, index="nom", values="note", columns="mois", margins=True)
Loading...

Créer une table avec plusieurs niveaux d’index :

pd.pivot_table(plus_de_notes, index=("nom", "mois"), margins=True)
Loading...

Fonctions pour explorer un DataFrame

Quand tu as beaucoup de données, c’est utile de voir rapidement le contenu.

Créons un grand DataFrame de test :

beaucoup_de_donnees = np.fromfunction(lambda x, y: (x + y*y) % 17 * 11, (10000, 26))
df_geant = pd.DataFrame(beaucoup_de_donnees, columns=list("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))
df_geant[df_geant % 16 == 0] = np.nan
df_geant.insert(3, "texte", "Blabla")
df_geant
Loading...

Afficher les 5 premières lignes :

df_geant.head()
Loading...

Ou les 2 dernières :

df_geant.tail(2)
Loading...

Avoir un résumé rapide de chaque colonne :

df_geant.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10000 entries, 0 to 9999
Data columns (total 27 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   A       8823 non-null   float64
 1   B       8824 non-null   float64
 2   C       8824 non-null   float64
 3   texte   10000 non-null  object 
 4   D       8824 non-null   float64
 5   E       8822 non-null   float64
 6   F       8824 non-null   float64
 7   G       8824 non-null   float64
 8   H       8822 non-null   float64
 9   I       8823 non-null   float64
 10  J       8823 non-null   float64
 11  K       8822 non-null   float64
 12  L       8824 non-null   float64
 13  M       8824 non-null   float64
 14  N       8822 non-null   float64
 15  O       8824 non-null   float64
 16  P       8824 non-null   float64
 17  Q       8824 non-null   float64
 18  R       8823 non-null   float64
 19  S       8824 non-null   float64
 20  T       8824 non-null   float64
 21  U       8824 non-null   float64
 22  V       8822 non-null   float64
 23  W       8824 non-null   float64
 24  X       8824 non-null   float64
 25  Y       8822 non-null   float64
 26  Z       8823 non-null   float64
dtypes: float64(26), object(1)
memory usage: 2.1+ MB

Avoir des statistiques descriptives (moyenne, min, max, etc.) :

df_geant.describe()
Loading...
  • count : nombre de valeurs non manquantes

  • mean : moyenne

  • std : écart-type

  • min : valeur minimale

  • 25%, 50%, 75% : percentiles (quartiles)

  • max : valeur maximale

Sauvegarder et charger des données avec pandas

Pandas permet de sauvegarder un DataFrame dans plusieurs formats : CSV, Excel, JSON, HTML, HDF5, ou même dans une base SQL.

Créons d’abord un petit DataFrame :

infos = pd.DataFrame(
    [["Football", 72.5, 1999, np.nan], ["Danse", 64.1, 2002, 1]],
    columns=["loisir", "poids", "annee_naissance", "enfants"],
    index=["Brice", "Estelle"]
)
infos
Loading...

Sauvegarde

Enregistrer dans différents formats :

infos.to_csv("infos.csv")
infos.to_html("infos.html")
infos.to_json("infos.json")

Le fichier CSV aura une colonne d’index (par défaut, sans nom). On peut la nommer avec infos.index.name = "prenom" avant la sauvegarde.

Pour Excel, il faut installer la bibliothèque openpyxl :

try:
    infos.to_excel("infos.xlsx", sheet_name='Personnes')
except ImportError as e:
    print(e)

Chargement

Pour charger un fichier CSV en DataFrame :

infos_chargees = pd.read_csv("infos.csv", index_col=0)
infos_chargees
Loading...

On peut aussi charger du JSON, de l’Excel, du HTML, etc. grâce à pd.read_json, pd.read_excel, pd.read_html, etc.

On peut même charger des données sur Internet directement (par exemple, un CSV sur GitHub) :

url = "https://raw.githubusercontent.com/plotly/datasets/master/us-cities-top-1k.csv"
villes_us = pd.read_csv(url, index_col=0)
villes_us.head()
Loading...

Pour plus de détails sur tous les formats et options : voir la documentation pandas sur l’entrée/sortie.

Fusionner des DataFrames (jointures comme en SQL)

Pandas permet de “joindre” des tableaux comme dans les bases de données :

  • “inner join” (intersection)

  • “outer join” (union)

  • “left/right join” (garder seulement les lignes du DataFrame de gauche/droite)

Exemple

Supposons deux DataFrames : villes camerounaises et leurs coordonnées, puis population.

coord_villes = pd.DataFrame([
    ["CM", "Douala", 4.0511, 9.7679],
    ["CM", "Yaoundé", 3.8480, 11.5021],
    ["CM", "Garoua", 9.3264, 13.3936]
], columns=["pays", "ville", "lat", "long"])

pop_villes = pd.DataFrame([
    [3000000, "Douala", "Littoral"],
    [2800000, "Yaoundé", "Centre"],
    [700000, "Bafoussam", "Ouest"]
], columns=["population", "ville", "region"])

coord_villes
pop_villes
Loading...

Fusion simple sur la colonne “ville” :

pd.merge(left=coord_villes, right=pop_villes, on="ville")
Loading...

Pour garder toutes les villes des deux tableaux (full outer join) :

pd.merge(left=coord_villes, right=pop_villes, on="ville", how="outer")
Loading...

Pour garder toutes les villes de gauche (“left join”) :

pd.merge(left=coord_villes, right=pop_villes, on="ville", how="left")
Loading...

Fusion par l’index ou colonnes différentes

Si la clé de jointure se trouve dans l’index ou que les noms diffèrent :

pop2 = pop_villes.copy()
pop2.columns = ["population", "nom_ville", "region"]
pd.merge(left=coord_villes, right=pop2, left_on="ville", right_on="nom_ville")
Loading...

Concaténer des DataFrames

Pour “empiler” (ajouter à la suite) plusieurs DataFrames :

resultat_concat = pd.concat([coord_villes, pop_villes])
resultat_concat
Loading...

Pour réinitialiser les indices :

pd.concat([coord_villes, pop_villes], ignore_index=True)
Loading...

Pour ne garder que les colonnes communes :

pd.concat([coord_villes, pop_villes], join="inner")
Loading...

Les catégories

Il arrive qu’une colonne représente des catégories : par exemple, “1” pour femme et “2” pour homme, ou “A” (Bon), “B” (Moyen), “C” (Insuffisant). Pandas gère ça facilement :

pop_villes["code_eco"] = [1, 2, 3]
pop_villes["eco"] = pd.Categorical(
    pop_villes["code_eco"].map({1: "Industriel", 2: "Administratif", 3: "Commercial"})
)
pop_villes
Loading...

Pour voir les catégories :

pop_villes["eco"].cat.categories
Index(['Administratif', 'Commercial', 'Industriel'], dtype='object')

Pour renommer une catégorie :

pop_villes["eco"] = pop_villes["eco"].cat.rename_categories({"Industriel": "Production"})
pop_villes
Loading...