Module 5 — pandas : Series, DataFrame et indexation
pandas est l'outil avec lequel un praticien des données passe le plus clair de ses journées. Conceptuellement, c'est simple : un DataFrame est un tableau NumPy à deux dimensions dont les lignes et les colonnes portent des étiquettes. Toute la puissance — et les quelques pièges — découlent de cette idée.
Series et DataFrame : les deux objets
Une Series est une colonne : des valeurs NumPy plus un index d'étiquettes. Un DataFrame est un ensemble de Series partageant le même index.
import pandas as pd
df = pd.read_csv("commandes.csv", parse_dates=["date"])
df.shape # (12480, 6) — lignes, colonnes
df.dtypes # le type de chaque colonne — À LIRE SYSTÉMATIQUEMENT
df["montant"] # une colonne → Series
df[["client", "montant"]] # plusieurs colonnes → DataFrame
Le premier réflexe sur tout fichier inconnu tient en quatre commandes :
df.head() # les 5 premières lignes — à quoi ressemblent les données ?
df.info() # types, non-nuls, mémoire — les colonnes sont-elles bien typées ?
df.describe() # statistiques des colonnes numériques — ordres de grandeur, extrêmes
df["pays"].value_counts() # distribution d'une catégorielle — modalités, fautes de frappe
Ces quatre commandes révèlent l'essentiel des problèmes avant qu'ils ne deviennent des bugs : la colonne de montants lue comme texte (à cause d'un « N/A » ou d'une virgule décimale), la date restée chaîne, la catégorie orthographiée de trois façons.
loc et iloc : sélectionner sans ambiguïté
pandas offre deux accesseurs explicites, et la discipline de les utiliser évite la majorité des confusions :
df.loc[42, "montant"] # par ÉTIQUETTES : ligne d'index 42, colonne "montant"
df.iloc[0, 3] # par POSITIONS : première ligne, quatrième colonne
df.loc[df["pays"] == "Maroc", ["client", "montant"]] # filtre + colonnes
df.iloc[:100] # les 100 premières lignes
La différence devient critique dès que l'index n'est plus 0, 1, 2… : après un tri ou un filtre, loc[5] désigne l'étiquette 5 (qui peut être n'importe où), iloc[5] la sixième ligne. Règle simple : loc par défaut, iloc quand on raisonne en positions.
Filtrer : les masques booléens, comme NumPy
Le mécanisme du module 4 s'applique tel quel, étiquettes en plus :
grosses = df[df["montant"] > 1000]
cible = df[(df["montant"] > 1000) & (df["pays"] == "Canada")] # parenthèses obligatoires
recents = df[df["date"] >= "2026-01-01"] # comparaisons de dates directes
europe = df[df["pays"].isin(["France", "Belgique", "Suisse"])]
sans_email = df[df["email"].isna()]
isin remplace les chaînes de |, isna()/notna() testent les manquants. La proportion se lit directement : (df["montant"] > 1000).mean().
Créer et transformer des colonnes
df["montant_ttc"] = df["montant"] * 1.15 # vectorisé
df["annee"] = df["date"].dt.year # accesseur dates
df["domaine"] = df["email"].str.split("@").str[1] # accesseur chaînes
df["segment"] = np.where(df["montant"] > 1000, "premium", "standard")
Les accesseurs .dt (dates) et .str (chaînes) vectorisent les opérations qui exigeraient une boucle. apply avec une fonction ligne à ligne existe, mais c'est le dernier recours : cent fois plus lent que les opérations vectorisées, il ne se justifie que pour une logique réellement irréductible.
Modifier le résultat d'un filtre (sous_df = df[filtre] puis sous_df["x"] = …) déclenche le plus célèbre avertissement de pandas : pandas ne garantit pas si vous modifiez une copie ou la table d'origine. Les deux gestes qui l'évitent : .copy() explicite quand on veut un sous-ensemble indépendant, et df.loc[filtre, "x"] = valeur quand on veut modifier l'original. C'est la conséquence directe des vues/références vues aux modules 2 et 4.
Trier, renommer, réindexer
df.sort_values("montant", ascending=False) # tri simple
df.sort_values(["pays", "montant"], ascending=[True, False]) # tri multiple
df.rename(columns={"mnt": "montant"}) # renommage lisible
df.reset_index(drop=True) # index remis à 0..n-1 après filtre/tri
Point d'architecture important : ces méthodes renvoient un nouveau DataFrame au lieu de modifier l'original. Le style idiomatique enchaîne les transformations, chaque étape restant inspectable :
top_clients = (
df[df["date"] >= "2026-01-01"]
.groupby("client")["montant"].sum()
.sort_values(ascending=False)
.head(20)
)
Ce chaînage — filtre, agrégation, tri, tête — est la phrase type de l'analyse pandas ; le groupby qui l'anime est l'objet du module 7.
Ce qu'il faut retenir
- Un DataFrame = un tableau NumPy + des étiquettes de lignes (index) et de colonnes ; une Series = une colonne.
- Sur tout fichier neuf :
head,info,describe,value_counts— quatre commandes qui révèlent types faux, manquants et catégories sales. locpar étiquettes,ilocpar positions ; masques booléens avec parenthèses,isin,isna.- Colonnes nouvelles par opérations vectorisées et accesseurs
.dt/.str;applyen dernier recours. .copy()oudf.loc[filtre, col] = …pour éviter le SettingWithCopyWarning ; transformations chaînées, chaque étape inspectable.
Au module suivant : le nettoyage systématique — valeurs manquantes, doublons et types — l'étape qui décide de la fiabilité de tout ce qui suit.