Aller au contenu principal

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.

L'avertissement SettingWithCopyWarning

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.
  • loc par étiquettes, iloc par positions ; masques booléens avec parenthèses, isin, isna.
  • Colonnes nouvelles par opérations vectorisées et accesseurs .dt / .str ; apply en dernier recours.
  • .copy() ou df.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.