Un merge n’est pas un VLOOKUP. Si la clé à gauche apparaît n fois et la même clé à droite m fois, vous obtenez n × m lignes pour cette clé. Une table « clients » avec deux lignes pour le même client_id (doublon non traité) transforme 100 000 commandes en 200 000 — ou en deux millions si le doublon est systémique.
C’est un produit cartésien par clé, pas une erreur de Pandas. Le résultat est mathématiquement juste et métierment faux dès que vous croyiez à une relation n-1.
Vous comptez avant, vous comptez après, vous ne croyez pas head().
n0 = len(commandes)
out = commandes.merge(clients, on="client_id", how="left")
assert len(out) == n0, (len(out), n0, out["client_id"].value_counts().head())Si l’assertion saute, clients["client_id"].duplicated().sum() et un value_counts sur la clé vous donnent le coupable en dix secondes.
commandes.merge(clients, on="client_id", how="left", validate="m:1")validate="m:1" (ou 1:1, 1:m) fait échouer le many-to-many accidentel. C’est le paramètre que les candidats oublient et que l’intervieweur attend à ce niveau.
indicator=True distingue « pas de match » et « trop de matchs » une fois que vous avez isolé les clés chaudes.
ville au lieu de ville_id, nom au lieu de client_id).cross ou oubli de on : produit cartésien global, pas seulement par clé.explode maîtrisé, ou double explode croisé.Vous déclarez la cardinalité avant d’écrire le merge. Référentiel unique → drop_duplicates assumé ou échec. Si le many-to-many est réel (commande × ligne de commande déjà dénormalisée des deux côtés), vous changez de grain avant de joindre, ou vous agrégez un côté.
La phrase senior : une jointure qui « enrichit » ne doit pas changer le nombre de lignes du fait, sauf si vous avez dit que vous changiez de grain.