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 pdLes 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. UneSeries, 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 deDataFrame. 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])
sPresque 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 SeriesOn 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 correspondanteSi 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 valeurC’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égativeLes é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"])
s2Ici, 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 :
.locpour accéder à une valeur par son étiquette.ilocpour 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”)Mais fais attention quand tu utilises les indices numériques : Regarde cet exemple :
surprise = pd.Series([1000, 1001, 1002, 1003])
surprisesurprise_slice = surprise[2:]
surprise_sliceIci, 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_poidsIci, 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_selectionAlignement 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_poidsIndex(['Brice', 'Estelle', 'Valery', 'Nathalie'], dtype='object')
Index(['Brice', 'Estelle', 'Valery', 'Nathalie'], dtype='object')
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_fauxnotes_exam = [14 17 12 16]
serie_faux = [1000 1000 1000 1000]
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_codeAjouter 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_nommeeTracer 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()
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_journeeDatetimeIndex(['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_journeePour voir l’évolution sur un graphique :
serie_temp_journee.plot(kind="bar")
plt.grid(True)
plt.show()
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_moyenneOn peut aussi afficher ce résultat :
temp_2h_moyenne.plot(kind="bar")
plt.show()
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_minOu utiliser une fonction personnalisée :
temp_2h_custom = serie_temp_journee.resample("2h").apply("min")
temp_2h_customSur-é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ésultatsPour 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)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()
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_doualaOn 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_parisSi 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_naiveSi 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")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')
trimestresPeriodIndex(['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 trimestresPeriodIndex(['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 trimestrePeriodIndex(['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_trimestrielsOn peut les tracer :
revenus_trimestriels.plot(kind="line")
plt.show()
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_heuresEt on peut revenir à une périodicité avec to_period() :
dernieres_heures.to_period()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") + 8PeriodIndex(['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À 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"]Pour avoir plusieurs colonnes à la fois :
personnes[["annee_naissance", "loisir"]]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"]
)
d2Autre 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"]
)
d3Ici, 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"]
)
d3Tu peux créer un DataFrame à partir d’un autre DataFrame :
d4 = pd.DataFrame(
d3,
columns=["loisir", "enfants"],
index=["Estelle", "Brice"]
)
d4Ou 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}
})
personnesMulti-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}
}
)
d5On peut alors récupérer facilement toutes les colonnes “public” :
d5["public"]
d5["public", "loisir"]Supprimer un niveau¶
On peut supprimer un niveau de colonnes avec droplevel() :
d5.columns = d5.columns.droplevel(level=0)
d5Transposer¶
On peut inverser colonnes et lignes avec .T :
d6 = d5.T
d6“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()
d8 = d7.unstack()
d8d9 = d8.unstack()
d9d10 = d9.unstack(level=(0,1))
d10À 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 :
personnesPour accéder à une ligne par son nom (étiquette), utilise loc :
personnes.loc["Mamadou"]Pour accéder par la position (numéro de la ligne), utilise iloc :
personnes.iloc[2]Pour obtenir plusieurs lignes à la fois :
personnes.iloc[1:3]Tu peux aussi utiliser des conditions pour filtrer :
personnes[personnes["annee_naissance"] < 1998]Ajouter et supprimer des colonnes¶
Un DataFrame fonctionne comme un dictionnaire de Series. Voici quelques exemples :
# On affiche le tableau de base
personnesAjouter 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
personnesRetirer une colonne et la stocker ailleurs :
annees_naissance = personnes.pop("annee_naissance")Supprimer une colonne simplement :
del personnes["enfants"]personnesAjouter 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é
personnesPour insérer une colonne à une position précise (par exemple en 2ème colonne) :
personnes.insert(1, "taille_cm", [165, 172, 180])
personnesCré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_modAttention, 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)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)
)É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)
personnesExplication 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éeseuil_surpoidsdans le DataFrame.Avec
@,eval()comprend qu’il faut utiliser la variable externeseuil_surpoids(qui vaut28dans 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 :¶
| imc | surpoids |
|---|---|
| 25.0 | False |
| 30.0 | True |
| 22.0 | False |
| 28.5 | True |
Filtrer un DataFrame avec query¶
Pour ne garder que certaines lignes selon une condition :
personnes.query("age > 25 and animaux == 0")Trier un DataFrame¶
Pour trier selon l’index (les noms), par ordre décroissant :
personnes.sort_index(ascending=False)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)
personnesPour trier selon les valeurs d’une colonne (ex : “age”) :
personnes.sort_values(by="age", inplace=True)
personnesTracer 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()
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()
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"])
notesPrendre la racine carrée de toutes les notes :
np.sqrt(notes)Ajouter 1 point à tout le monde :
notes + 1Comparer toutes les notes à 10 (True ou False) :
notes >= 10Calculer la moyenne de chaque colonne :
notes.mean()Savoir dans quels mois tout le monde a eu plus de 10 :
(notes > 10).all()Savoir qui a eu au moins une note de 15 :
(notes == 15).any(axis=1)On peut aussi soustraire la moyenne de chaque colonne à chaque note :
notes - notes.mean()Si tu veux soustraire la moyenne globale :
notes - notes.values.mean()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 + bonusCertaines 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_corrigesComme ç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_mieuxMaintenant, toutes les valeurs sont raisonnables. On peut calculer les notes finales :
notes["mars"] = np.nan
notes_finales = notes + bonus_mieux
notes_finalesNettoyer 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_propresPour enlever les colonnes totalement vides :
notes_finales_propres = notes_finales_propres.dropna(axis=1, how="all")
notes_finales_propresGrouper 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_finalesOn 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()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)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")Changer la fonction d’agrégation, par exemple prendre le maximum :
pd.pivot_table(plus_de_notes, index="nom", values=["note", "bonus"], aggfunc="max")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)Créer une table avec plusieurs niveaux d’index :
pd.pivot_table(plus_de_notes, index=("nom", "mois"), margins=True)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_geantAfficher les 5 premières lignes :
df_geant.head()Ou les 2 dernières :
df_geant.tail(2)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()count: nombre de valeurs non manquantesmean: moyennestd: écart-typemin: valeur minimale25%,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"]
)
infosSauvegarde¶
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_chargeesOn 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()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_villesFusion simple sur la colonne “ville” :
pd.merge(left=coord_villes, right=pop_villes, on="ville")Pour garder toutes les villes des deux tableaux (full outer join) :
pd.merge(left=coord_villes, right=pop_villes, on="ville", how="outer")Pour garder toutes les villes de gauche (“left join”) :
pd.merge(left=coord_villes, right=pop_villes, on="ville", how="left")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")Concaténer des DataFrames¶
Pour “empiler” (ajouter à la suite) plusieurs DataFrames :
resultat_concat = pd.concat([coord_villes, pop_villes])
resultat_concatPour réinitialiser les indices :
pd.concat([coord_villes, pop_villes], ignore_index=True)Pour ne garder que les colonnes communes :
pd.concat([coord_villes, pop_villes], join="inner")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_villesPour voir les catégories :
pop_villes["eco"].cat.categoriesIndex(['Administratif', 'Commercial', 'Industriel'], dtype='object')Pour renommer une catégorie :
pop_villes["eco"] = pop_villes["eco"].cat.rename_categories({"Industriel": "Production"})
pop_villes