Assembler plusieurs DataFrames en un seul objet propre paraît simple, mais c’est souvent là que se cachent les problèmes d’index, de colonnes manquantes et de performance. Dans la logique de dataframe concat, l’enjeu n’est pas seulement de “coller” des blocs de données : il faut savoir quand empiler des lignes, quand aligner des colonnes et comment garder un schéma exploitable. Je vais aller droit au but avec les paramètres qui comptent, les pièges que je vois le plus souvent et les cas où `merge` ou `join` font mieux le travail.
Les points à garder en tête avant d’assembler les tables
- `concat(..., axis=0)` empile des lignes ; `axis=1` aligne des colonnes sur l’index.
- `ignore_index=True` est utile quand l’ancien index n’a plus de valeur métier.
- `join="outer"` conserve tout ; `join="inner"` ne garde que les colonnes communes.
- `keys` ajoute une provenance claire et construit un index hiérarchique si besoin.
- Si la relation entre données est métier, `merge` ou `join` est souvent plus juste que `concat`.
Pourquoi concat est souvent le bon réflexe
Je vois `concat` comme un outil de composition structurelle. On l’utilise quand les blocs de données sont déjà compatibles et qu’il suffit de les réunir sans logique de correspondance métier. C’est typiquement le cas pour des lots mensuels, des exports successifs, des pages d’API ou des journaux applicatifs découpés en fragments.
La documentation pandas recommande d’ailleurs de préparer les morceaux dans une liste puis de faire une seule concaténation, plutôt que d’ajouter des lignes une par une. En pratique, c’est plus lisible et nettement plus efficace qu’une construction incrémentale qui reconstruit l’objet à chaque tour de boucle.
import pandas as pd
frames = [df_jan, df_fev, df_mar]
result = pd.concat(frames, ignore_index=True)Ce point est important : `concat` ne modifie pas les objets d’entrée, il renvoie un nouvel objet. Je préfère donc penser en termes de blocs à assembler plutôt qu’en termes d’objet à enrichir petit à petit. Une fois ce cadre posé, le vrai choix devient l’axe de concaténation, et c’est lui qui change le sens de l’opération.

Empiler des lignes ou aligner des colonnes
Le paramètre `axis` est celui qui détermine le plus visiblement le résultat. Par défaut, `axis=0` empile les objets les uns sous les autres. Avec `axis=1`, on juxtapose des colonnes, en conservant l’alignement par index. C’est le bon endroit pour éviter les malentendus entre “ajouter des lignes” et “fusionner des données de contexte”.
| Configuration | Effet | Cas d’usage typique | Risque principal |
|---|---|---|---|
| `axis=0` | Empile les lignes | Exports successifs, lots de traitement, logs, données batch | Index dupliqués si on conserve les labels d’origine |
| `axis=1` | Aligne les colonnes sur l’index | Enrichissement d’un même jeu de lignes avec des features ou des métadonnées | Valeurs manquantes si les index ne correspondent pas |
Quand je combine des lignes, j’utilise souvent `ignore_index=True` si l’index précédent n’a aucune signification métier. Quand je combine des colonnes, je vérifie d’abord que l’index représente bien la même entité des deux côtés, sinon le résultat peut sembler correct tout en étant faux.
# Empiler des lignes
result_rows = pd.concat([df_a, df_b], axis=0, ignore_index=True)
# Aligner des colonnes
result_cols = pd.concat([df_base, df_meta], axis=1)Une fois l’axe choisi, les autres paramètres deviennent des réglages fins qui servent surtout à sécuriser le résultat ou à le rendre plus lisible.
Les paramètres qui changent vraiment le résultat
La plupart des bugs que je rencontre viennent moins de `concat` lui-même que d’un mauvais réglage de ses options. Les six paramètres ci-dessous couvrent l’essentiel des cas concrets en production.
| Paramètre | Ce qu’il change | Quand je le choisis | Vigilance |
|---|---|---|---|
| `ignore_index` | Renumérote l’axe concaténé de `0` à `n-1` | Quand l’index d’origine n’a pas de valeur métier | Ne touche pas aux autres axes |
| `join` | `outer` garde l’union, `inner` garde l’intersection | Quand les schémas varient d’une source à l’autre | `inner` peut supprimer des colonnes utiles sans bruit |
| `keys` | Ajoute une provenance et crée un index hiérarchique | Quand je veux savoir d’où vient chaque bloc | Peut surprendre si on attend un index plat |
| `names` | Nomme les niveaux d’index créés par `keys` | Quand la lisibilité du MultiIndex compte | Utile surtout si plusieurs lots sont empilés |
| `verify_integrity` | Vérifie les doublons sur l’axe concaténé | Quand un doublon d’index serait une erreur critique | La vérification peut coûter cher sur de gros volumes |
| `sort` | Trie l’axe non concaténé | Quand l’ordre des colonnes doit être stabilisé | Je le laisse généralement à `False` pour préserver l’ordre choisi |
| `copy` | Plus d’effet utile dans les pandas récents | Je ne le règle plus pour piloter le comportement | Dans pandas récent, ce paramètre est ignoré |
Le cas `keys` est particulièrement utile en ETL : il permet de conserver la provenance sans ajouter une colonne manuelle. La documentation pandas rappelle aussi que `copy` n’a plus d’impact dans les versions récentes, ce qui évite de s’accrocher à un faux levier de performance. Une fois ces réglages en tête, le plus important devient la préparation des objets avant l’assemblage.
Préparer les données avant de concaténer
Je préfère toujours normaliser les blocs avant de les combiner. Dans un pipeline backend ou dans un traitement d’analytics, cela veut dire harmoniser les noms de colonnes, convertir les types, sélectionner les champs utiles et seulement ensuite empiler les morceaux. Cette discipline évite les `NaN` surprenants, les colonnes en trop et les dtypes incohérents.
- Je renomme les colonnes pour obtenir un schéma commun.
- Je convertis les dates, les nombres et les identifiants dans le bon type.
- Je garde les blocs dans une liste Python, pas dans un DataFrame intermédiaire.
- Je fais une seule concaténation à la fin.
frames = []
for source in sources:
df = pd.read_csv(source)
df = df.rename(columns={"userId": "user_id"})
df["created_at"] = pd.to_datetime(df["created_at"], errors="coerce")
df = df[["user_id", "email", "created_at"]]
frames.append(df)
result = pd.concat(frames, ignore_index=True)Si je dois garder la trace de chaque source, j’ajoute volontiers `keys` au lieu d’injecter une colonne artificielle. C’est plus propre quand on veut auditer l’origine des lots plus tard. Une fois le pipeline propre, il reste à vérifier si `concat` est vraiment l’outil adapté, ou si une jointure métier serait plus juste.
Quand concat ne remplace pas merge
Je vois souvent `concat` utilisé à la place de `merge`, alors que les deux résolvent des problèmes différents. `concat` assemble des blocs compatibles le long d’un axe. `merge` fait une jointure sur des clés. `join`, lui, est très pratique pour raccorder des DataFrames par l’index, surtout quand l’index porte déjà la bonne sémantique.
| Outil | Logique | Quand je l’utilise | Point d’attention |
|---|---|---|---|
| `concat` | Assemblage vertical ou horizontal sans correspondance métier | Lignes de plusieurs lots, colonnes déjà alignées | Ne fait pas de rapprochement intelligent sur des clés |
| `merge` | Jointure de type SQL sur des colonnes ou des index | Client, commande, identifiant d’utilisateur, référentiel métier | Le choix des clés influence directement la qualité du résultat |
| `join` | Raccordement orienté index, souvent sur les colonnes | Quand l’index représente déjà l’entité à relier | Moins explicite si les clés sont dans des colonnes normales |
Si je dois enrichir des lignes avec un `customer_id`, je pense d’abord à `merge`. Si je dois simplement empiler des exportations journalières, `concat` est le bon choix. Cette distinction paraît évidente quand on la formule ainsi, mais elle fait gagner beaucoup de temps sur des projets où les données viennent de plusieurs services ou de plusieurs API.
Les erreurs que je surveille en priorité
Les résultats “qui ont l’air bons” sont souvent les plus dangereux. Je vérifie donc systématiquement quelques points avant de considérer une concaténation comme propre. C’est particulièrement vrai dans un flux d’intégration où les données arrivent depuis plusieurs systèmes et ne respectent pas exactement le même schéma.
Colonnes absentes et valeurs manquantes
Avec `join="outer"`, pandas conserve toutes les colonnes présentes dans les blocs d’entrée. C’est pratique, mais cela remplit aussi les trous avec des valeurs manquantes. Si une source perd une colonne ou si un export change de structure, le résultat peut sembler valide tout en introduisant des `NaN` partout. Dans ce cas, je préfère souvent normaliser les colonnes en amont plutôt que de masquer le problème par un assemblage trop permissif.
Indices dupliqués et provenance floue
Quand on garde l’index d’origine, les doublons sont fréquents. Ce n’est pas forcément un bug, mais c’est souvent une source d’ambiguïté dans les sélections et dans les jointures suivantes. Si l’index n’a pas de valeur métier, `ignore_index=True` simplifie beaucoup la suite. Si, au contraire, les doublons doivent être interdits, j’active `verify_integrity=True` pour les détecter tôt.
Concaténer dans une boucle
C’est l’erreur la plus classique. Ajouter un DataFrame à un autre à chaque itération finit par coûter nettement plus cher qu’une collecte en liste suivie d’une seule concaténation. Je ne garde la version incrémentale que pour des cas marginaux ; dans un vrai pipeline, je rassemble les morceaux d’abord, je combine ensuite.
Lire aussi : Meilleur IDE 2026 - Lequel choisir pour votre projet ?
Schémas trop différents
Si les blocs n’ont pas la même structure, il faut décider ce qu’on accepte de perdre. `join="inner"` est utile quand seules les colonnes communes comptent. `join="outer"` est plus souple, mais il peut produire un tableau très large et rempli de valeurs manquantes. Dans un projet solide, je préfère toujours rendre ce choix explicite avant l’exécution, plutôt que de le découvrir après coup dans un notebook ou dans un job planifié.
Avec ces pièges en tête, la meilleure approche consiste souvent à s’imposer une petite checklist avant d’envoyer le traitement en production.
Ce que je verrouille avant de l’utiliser en production
- Je confirme si l’opération consiste à empiler des lignes ou à aligner des colonnes.
- Je décide si l’index doit survivre ou s’il doit être reconstruit.
- Je normalise les noms de colonnes et les types avant de réunir les blocs.
- Je garde la provenance avec `keys` quand plusieurs sources cohabitent.
- Je choisis volontairement entre `outer` et `inner`, au lieu de laisser le défaut décider à ma place.
- Je teste les cas avec colonnes manquantes, index dupliqués et lots hétérogènes.
Au fond, je traite `concat` comme un outil de structure, pas comme un substitut universel aux jointures. Dès que l’opération consiste seulement à réunir des morceaux compatibles, je l’utilise sans hésiter ; dès qu’il faut faire correspondre des clés métier, je passe à `merge` ou `join`. Cette séparation simple me donne des pipelines plus lisibles, moins fragiles et beaucoup plus faciles à tester.