Module 7 — Jointures, regroupements et tableaux croisés
Les données réelles arrivent en morceaux : les commandes dans une table, les clients dans une autre, les produits dans une troisième. Et les questions qui intéressent le métier sont des agrégats : chiffre d'affaires par segment, panier moyen par mois. Ce module couvre les deux mécanismes qui relient les morceaux aux réponses — et leurs pièges, qui produisent des chiffres faux sans lever la moindre erreur.
merge : joindre deux tables
commandes_enrichies = commandes.merge(
clients,
on="client_id", # la clé de jointure
how="left", # le type de jointure
)
Les quatre types, et quand les employer :
how= | Ce qui est conservé | Usage typique |
|---|---|---|
inner | Les clés présentes des deux côtés | Croiser deux sources fiables |
left | Toute la table de gauche | Enrichir sans perdre de lignes — le défaut raisonnable |
right | Toute la table de droite | Rare — réécrire en left inversé |
outer | Tout, des deux côtés | Audits de correspondance entre sources |
Avec how="left", les commandes sans client correspondant reçoivent des NaN dans les colonnes clients — visible et traçable. Avec inner, elles disparaissent : un inner irréfléchi est la façon la plus discrète de perdre 8 % de son chiffre d'affaires dans une analyse.
Compter avant et après. Un left doit rendre exactement le nombre de lignes de gauche ; s'il en rend plus, la clé de droite contenait des doublons et chaque ligne de gauche a été démultipliée — les sommes en aval seront gonflées en silence. Compter les non-appariés : resultat["colonne_de_droite"].isna().sum(). Ces quatre lignes de contrôle évitent les deux accidents de jointure les plus coûteux.
assert len(resultat) == len(commandes), "Démultiplication : clé non unique à droite"
print(f"Sans correspondance : {resultat['segment'].isna().mean():.1%}")
Quand les clés n'ont pas le même nom : left_on="client_id", right_on="id". Quand les colonnes homonymes coexistent : suffixes=("", "_client").
groupby : scinder, appliquer, combiner
Le patron conceptuel : scinder la table en groupes, appliquer une agrégation à chaque groupe, combiner les résultats.
df.groupby("segment")["montant"].sum() # une agrégation
df.groupby("segment")["montant"].agg(["count", "mean", "sum"]) # plusieurs
# Plusieurs clés, agrégations nommées — la forme la plus lisible
recap = df.groupby(["segment", "pays"]).agg(
nb_commandes=("commande_id", "count"),
ca=("montant", "sum"),
panier_moyen=("montant", "mean"),
).reset_index()
reset_index() en sortie transforme le résultat indexé en table plate — presque toujours ce qu'on veut pour la suite (jointure, export, graphique).
Le groupby temporel mérite sa mention : combiné à pd.Grouper ou resample, il produit les séries mensuelles qui peuplent tous les rapports :
ca_mensuel = df.set_index("date")["montant"].resample("ME").sum()
Deux réflexes de fiabilité : vérifier les tailles de groupes (.size()) — une moyenne sur trois lignes n'a pas le poids d'une moyenne sur trente mille — et se rappeler que les NaN sont exclus des agrégations, ce qui peut faire diverger count d'une colonne à l'autre.
pivot_table : le tableau croisé
Pour présenter un agrégat en deux dimensions — lignes × colonnes — comme dans un tableur :
tcd = df.pivot_table(
values="montant",
index="segment", # les lignes
columns="annee", # les colonnes
aggfunc="sum",
fill_value=0,
margins=True, # ligne et colonne "All" de totaux
)
pivot_table est un groupby remis en forme : même moteur, présentation différente. Il agrège les doublons (contrairement à pivot, qui échoue s'il y en a) — c'est presque toujours lui qu'il faut. L'opération inverse, melt, déplie un tableau large en format long, celui que préfèrent Seaborn et la plupart des outils.
Composer : la requête d'analyse complète
Les trois outils s'enchaînent naturellement, dans le style chaîné du module 5 :
top_segments = (
commandes
.merge(clients[["client_id", "segment"]], on="client_id", how="left")
.groupby("segment")
.agg(ca=("montant", "sum"), clients=("client_id", "nunique"))
.assign(ca_par_client=lambda d: d["ca"] / d["clients"])
.sort_values("ca_par_client", ascending=False)
)
Joindre, grouper, dériver, trier : c'est l'équivalent pandas d'une requête SQL complète, et la forme que prendront la plupart de vos analyses. Chaque étape du enchaînement peut être exécutée seule pour inspection — c'est le grand avantage de ce style sur la variable écrasée vingt fois.
Ce qu'il faut retenir
mergeavechow="left"par défaut ; toujours compter les lignes avant/après et le taux de non-appariés — la démultiplication par clé dupliquée est le bug le plus coûteux du module.groupby= scinder-appliquer-combiner ; agrégations nommées pour la lisibilité,reset_index()en sortie, un œil sur les tailles de groupes.resamplepour les agrégats temporels ;pivot_tablepour la présentation croisée ;meltpour revenir au format long.- Les analyses réelles composent les trois en chaîne inspectable étape par étape.
Au module suivant : la visualisation — transformer ces agrégats en graphiques qui montrent la distribution, la tendance et l'anomalie.