merge, join, concat : comment fusionner des DataFrames pandas
Vos ventes sont dans un fichier, vos clients dans un autre, et vous voulez une seule table pour analyser le chiffre d'affaires par ville. Avec pandas, trois outils permettent de combiner des DataFrames : pd.concat(), pd.merge() et DataFrame.join(). Ils ne font pas la même chose, et choisir le mauvais est la première cause de lignes dupliquées ou de valeurs manquantes.
La règle en une phrase : concat empile des tables qui ont la même structure, merge relie deux tables par une colonne commune, join fait la même chose que merge en s'appuyant sur l'index.
merge, join ou concat : le tableau récapitulatif
Outil | Ce qu'il fait | À utiliser quand | Équivalent Excel / Power Query |
pd.concat() | Empile des lignes (ou colle des colonnes) | Vos tables ont les mêmes colonnes : un fichier par mois, par agence… | Ajouter des requêtes |
pd.merge() | Jointure sur une ou plusieurs colonnes | Vous rapprochez deux tables via une clé : id client, code produit… | RECHERCHEV / Fusionner des requêtes |
DataFrame.join() | Jointure sur l'index | La table de référence a déjà la clé en index | Fusionner des requêtes |
Les données d'exemple
Tout au long de l'article, on travaille avec deux tables : les ventes de janvier et de février, et la liste des clients.
import pandas as pd
janvier = pd.DataFrame({"id_vente": [101, 102], "id_client": [1, 2], "montant": [250, 120]})
fevrier = pd.DataFrame({"id_vente": [103, 104, 105], "id_client": [2, 3, 5], "montant": [80, 310, 90]})
clients = pd.DataFrame({
"id_client": [1, 2, 3, 4],
"nom": ["Alice", "Bruno", "Chloé", "David"],
"ville": ["Paris", "Lyon", "Nantes", "Lille"],
})Deux pièges sont volontairement glissés dans ces données : le client 5 a acheté mais n'existe pas dans la table clients, et le client 4 (David) existe mais n'a rien acheté. Ce sont exactement les cas qui posent problème dans la vraie vie.
pd.concat() : empiler des tables
concat() met des DataFrames bout à bout. C'est l'outil à utiliser quand vous recevez les mêmes données découpées en plusieurs fichiers : un export par mois, par magasin ou par région.
ventes = pd.concat([janvier, fevrier], ignore_index=True)
print(ventes)
id_vente id_client montant
0 101 1 250
1 102 2 120
2 103 2 80
3 104 3 310
4 105 5 90ignore_index=True renumérote les lignes de 0 à n. Sans lui, vous gardez les index d'origine (0, 1, puis 0, 1, 2) : des doublons d'index qui posent problème dès que vous sélectionnez des lignes avec loc et iloc.
Garder la trace de l'origine avec keys
print(pd.concat([janvier, fevrier], keys=["janvier", "février"]))
id_vente id_client montant
janvier 0 101 1 250
1 102 2 120
février 0 103 2 80
1 104 3 310
2 105 5 90Coller des colonnes avec axis=1
Avec axis=1, concat() place les tables côte à côte en alignant les lignes sur l'index, et non sur une colonne. Pour rapprocher deux tables par un identifiant, c'est merge() qu'il faut utiliser.
Bon à savoir : DataFrame.append() a été supprimé dans pandas 2.0. concat() est désormais la façon standard d'ajouter des lignes à un DataFrame. Voir aussi la fiche concat() du lexique Python.
pd.merge() : faire une jointure sur une colonne
merge() rapproche deux tables à partir d'une clé commune, comme une RECHERCHEV dans Excel ou une fusion de requêtes dans Power Query.
print(pd.merge(ventes, clients, on="id_client"))
id_vente id_client montant nom ville
0 101 1 250 Alice Paris
1 102 2 120 Bruno Lyon
2 103 2 80 Bruno Lyon
3 104 3 310 Chloé NantesPar défaut, merge() fait une jointure interne (how="inner") : seules les lignes dont la clé existe des deux côtés sont conservées. La vente 105, rattachée au client 5 inconnu, a disparu, tout comme David, qui n'a rien acheté.
Choisir le type de jointure avec how
how | Lignes conservées | Dans notre exemple |
"inner" (défaut) | Les clés présentes dans les deux tables | 4 lignes : la vente 105 et David disparaissent |
"left" | Toutes les lignes de la table de gauche | 5 lignes : la vente 105 a un nom et une ville vides (NaN) |
"right" | Toutes les lignes de la table de droite | 5 lignes : David apparaît sans vente |
"outer" | Toutes les lignes des deux tables | 6 lignes : la vente 105 et David sont conservés |
En pratique, how="left" est le plus utilisé : on part de la table des faits (les ventes) et on lui ajoute des informations, sans jamais perdre de lignes.
print(pd.merge(ventes, clients, on="id_client", how="left"))
id_vente id_client montant nom ville
0 101 1 250 Alice Paris
1 102 2 120 Bruno Lyon
2 103 2 80 Bruno Lyon
3 104 3 310 Chloé Nantes
4 105 5 90 NaN NaNQuand les clés n'ont pas le même nom
clients2 = clients.rename(columns={"id_client": "code_client"})
pd.merge(ventes, clients2, left_on="id_client", right_on="code_client", how="left")Les deux colonnes de clé sont conservées dans le résultat. Supprimez la seconde ensuite avec drop(columns="code_client").
Contrôler la jointure avec indicator=True
print(pd.merge(ventes, clients, on="id_client", how="outer", indicator=True))
id_vente id_client montant nom ville _merge
0 101.0 1 250.0 Alice Paris both
1 102.0 2 120.0 Bruno Lyon both
2 103.0 2 80.0 Bruno Lyon both
3 104.0 3 310.0 Chloé Nantes both
4 NaN 4 NaN David Lille right_only
5 105.0 5 90.0 NaN NaN left_onlyLa colonne _merge indique d'où vient chaque ligne. Filtrer sur left_only est le moyen le plus rapide de retrouver les ventes rattachées à un client inconnu. Remarquez aussi que id_vente et montant sont passés en décimaux : une colonne d'entiers qui reçoit des NaN est convertie en float. Plus de détails dans la fiche merge() du lexique Python.
DataFrame.join() : la jointure sur l'index
join() est un raccourci de merge() qui utilise l'index de la table de droite comme clé. Contrairement à merge(), il fait par défaut une jointure à gauche.
clients_idx = clients.set_index("id_client")
print(ventes.join(clients_idx, on="id_client"))
id_vente id_client montant nom ville
0 101 1 250 Alice Paris
1 102 2 120 Bruno Lyon
2 103 2 80 Bruno Lyon
3 104 3 310 Chloé Nantes
4 105 5 90 NaN NaNSi vos deux tables ont déjà la clé en index, df1.join(df2) suffit. Dans les autres cas, merge() est plus lisible et plus explicite.
Les 4 erreurs les plus fréquentes
1. Des lignes en trop après le merge
Si la clé apparaît plusieurs fois des deux côtés, merge() produit toutes les combinaisons possibles et le nombre de lignes explose. Ajoutez validate pour que pandas vérifie la relation attendue :
pd.merge(ventes, clients, on="id_client", how="left", validate="many_to_one")Si un client apparaît deux fois dans la table clients, pandas lève une MergeError au lieu de dupliquer silencieusement vos ventes.
2. Des clés de types différents
Une clé lue comme texte d'un côté ("1") et comme nombre de l'autre (1) bloque la jointure : pandas lève une ValueError (« You are trying to merge on int64 and object columns »). Convertissez d'abord la colonne, comme expliqué dans notre article pour changer le type d'une colonne pandas.
clients["id_client"] = clients["id_client"].astype(int)3. Des colonnes suffixées _x et _y
Quand les deux tables ont une colonne de même nom en dehors de la clé, pandas les renomme montant_x et montant_y. Donnez-leur des noms explicites avec suffixes :
objectifs = pd.DataFrame({"id_client": [1, 2, 3], "montant": [300, 100, 300]})
print(pd.merge(ventes, objectifs, on="id_client", suffixes=("_réel", "_objectif")))
id_vente id_client montant_réel montant_objectif
0 101 1 250 300
1 102 2 120 100
2 103 2 80 100
3 104 3 310 3004. Des NaN inattendus après un left
Une jointure à gauche laisse des NaN sur les lignes sans correspondance. Avant de les remplir, utilisez indicator=True pour comprendre pourquoi la clé ne trouve pas son équivalent : faute de frappe, espace en trop, majuscules, zéro initial perdu dans un code postal…
Fusionner plus de deux DataFrames
merge() ne prend que deux tables à la fois. Pour en combiner plusieurs, enchaînez les appels ou utilisez functools.reduce :
from functools import reduce
segments = pd.DataFrame({"id_client": [1, 2, 3, 4], "segment": ["Pro", "Particulier", "Pro", "Pro"]})
resultat = reduce(lambda g, d: pd.merge(g, d, on="id_client", how="left"), [ventes, clients, segments])Quel outil choisir ?
Mêmes colonnes, plusieurs fichiers → pd.concat()
Deux tables reliées par un identifiant → pd.merge(..., how="left")
Clé déjà en index → DataFrame.join()
Besoin de vérifier les lignes sans correspondance → indicator=True
Peur des doublons → validate="many_to_one"
Fusionner des tables, traiter les valeurs manquantes, regrouper avec groupby : c'est le cœur de la préparation de données en Python. C'est aussi ce que nous pratiquons sur des cas concrets dans notre formation Python – Les bases + approfondissement data.
Questions fréquentes
Quelle est la différence entre merge et join dans pandas ?
merge() joint deux tables sur une ou plusieurs colonnes, avec une jointure interne par défaut. join() joint sur l'index de la table de droite, avec une jointure à gauche par défaut. Avec les mêmes clés et le même type de jointure, les deux donnent le même résultat.
Comment faire une RECHERCHEV en Python ?
Avec pd.merge(table, reference, on="cle", how="left"). Contrairement à RECHERCHEV, merge() ramène plusieurs colonnes d'un coup et accepte des clés composées de plusieurs colonnes, par exemple on=["region", "produit"].
Comment ajouter des lignes à un DataFrame ?
Avec pd.concat([df, nouvelles_lignes], ignore_index=True). La méthode append() a été supprimée dans pandas 2.0.
Pourquoi mon merge crée-t-il des doublons ?
Parce que la clé n'est pas unique dans au moins une des deux tables. Vérifiez-le avec df["cle"].duplicated().sum(), et ajoutez validate="many_to_one" pour que pandas vous alerte.


