Des astuces pandas ciblées permettent de standardiser colonnes, nettoyer chaînes, convertir nombres et dates, et gérer valeurs manquantes pour des pipelines robustes (voir documentation pandas). Lisez pour adopter 7 techniques réutilisables qui évitent les traitements ad‑hoc.
Comment normaliser les noms de colonnes et supprimer les espaces
Normaliser les noms de colonnes en une ligne garantit une convention stable et réduit les erreurs en aval.
Standardiser les noms (strip pour retirer les espaces en tête/queue, lowercase pour homogénéiser la casse, remplacer espaces et caractères spéciaux) évite les bugs lors des jointures, sélections et scripts automatisés.
Exemple simple et sûr pour un DataFrame pandas :
df.columns = df.columns.str.strip().str.lower().str.replace(' ', '_', regex=False)
Variante plus robuste pour supprimer les accents et normaliser les caractères non ASCII en conservant un comportement prévisible :
import unicodedata
clean = lambda s: unicodedata.normalize('NFKD', s).encode('ascii', 'ignore').decode('ascii')
df.columns = [clean(c).strip().lower().replace(' ', '_') for c in df.columns]
Je recommande d’ajouter des vérifications automatiques après normalisation pour attraper les effets indésirables.
Voici quelques pièges courants et contrôles à ajouter :
- Colonnes dupliquées après normalisation : Vérifier les doublons et suffixer automatiquement pour préserver l’unicité.
- Noms vides ou composés uniquement d’underscores : Remplacer par un nom explicite comme col_0, col_1, etc.
- Remplacement involontaire de caractères utiles : Préciser regex=False dans str.replace pour éviter des comportements regex inattendus.
Exemple de correction automatique simple en Python pour suffixer les doublons :
import pandas as pd
cols = pd.Series(df.columns)
if cols.duplicated().any():
counts = {}
new_cols = []
for c in cols:
counts[c] = counts.get(c, 0) + 1
new_cols.append(c if counts[c] == 1 else f"{c}_{counts[c]-1}")
df.columns = new_cols
Tableau synthèse :
| Problème fréquent | Solution en 1 ligne |
| Espaces et casse inconsistante | df.columns = df.columns.str.strip().str.lower().str.replace(‘ ‘, ‘_’, regex=False) |
| Accents et caractères non-ASCII | df.columns = [clean(c).strip().lower().replace(‘ ‘, ‘_’) for c in df.columns] |
| Doublons après normalisation | Suffixer automatiquement les doublons (ex. col, col_1, col_2) |
Comment enlever les espaces dans les valeurs textuelles sans toucher les numériques
J’applique strip uniquement aux colonnes de type chaîne pour enlever les espaces sans toucher les numériques, simple et sûr à implémenter. La raison : appeler .str sur une colonne non textuelle peut corrompre les données ou être indisponible pour les dtypes numériques. Utiliser df.select_dtypes ou applymap évite ces erreurs en ciblant précisément les colonnes texte.
Trois options pratiques, avec avantages et inconvénients :
- df.select_dtypes(include=’object’).apply(lambda x: x.str.strip()) — Méthode concise et vectorisée. Avantage : rapide car x.str.strip() est optimisé. Inconvénient : touche toutes les colonnes object, qui peuvent contenir des valeurs non-string si le dtype est « object ». Voir la doc pandas pour Series.str : https://pandas.pydata.org/pandas-docs/stable/reference/series.html#string-handling
- df[df.select_dtypes([‘object’]).columns] = df.select_dtypes([‘object’]).apply(lambda x: x.str.strip()) — Variante sûre pour réassigner directement dans le DataFrame. Avantage : évite d’oublier la réaffectation. Inconvénient : même attention au dtype object (colonnes mixtes).
- df = df.applymap(lambda v: v.strip() if isinstance(v, str) else v) — Solution exhaustive et robuste. Avantage : ne touche que les vraies chaînes, ne risque pas de convertir des nombres. Inconvénient : beaucoup plus lente car applymap applique une fonction Python par cellule (coût O(n*m)).
Précisions utiles : x.str.strip() est vectorisé et conserve les NaN (il renvoie NaN lorsque l’élément est null). Ne pas appeler .str sur des colonnes mixtes sans vérifier, car le dtype object peut masquer des entiers ou des floats.
import pandas as pd
df = pd.DataFrame({
'name': [' Alice ', 'Bob', None],
'age': [30, 40, 25],
'code': [' 001','002 ','003']
})
# Exemple rapide
df['name'] = df['name'].str.strip()
df[['code']] = df[['code']].apply(lambda x: x.str.strip())
| Avant | Name | Age | Code |
| » Alice « | 30 | » 001″ | |
| « Bob » | 40 | « 002 « | |
| None | 25 | « 003 » | |
| Après | Name | Age | Code |
| « Alice » | 30 | « 001 » | |
| « Bob » | 40 | « 002 » | |
| None | 25 | « 003 » |
Rappel final : nettoyer les espaces améliore le matching, les agrégations et les jointures. Vérifier les dtypes avant transformation réduit les risques et préserve les numériques.
Comment convertir numéros et dates en toute sécurité
Convertir proprement des nombres et des dates évite les exceptions et évite d’introduire des valeurs corrompues dans vos pipelines. J’utilise systématiquement pd.to_numeric et pd.to_datetime avec errors=’coerce’ pour transformer les valeurs valides et marquer les invalides en NaN (pour numérique) ou NaT (pour datetime).
Numérique : Exemple et détection des conversions ratées.
import pandas as pd
df = pd.DataFrame({'raw': ['10', '3.5', 'n/a', '12,34', None]})
# Convertir proprement
df['num'] = pd.to_numeric(df['raw'], errors='coerce')
# Repérer conversions ratées : Null après conversion alors que la source était non-Null
df['failed_convert'] = df['num'].isna() & df['raw'].notna()
# Statistiques rapides
pct_failed = df['failed_convert'].mean() * 100 # pourcentage
Si le pourcentage de coercions est faible (heuristique : <5%), j’impute ou j’exclus. Si élevé (>5%), j’investigue la source (formats locaux, séparateurs décimaux, encodage). L’imputation dépend du contexte : moyenne/median pour variables numériques si distribution stable, sinon exclusion ou correction manuelle.
Date : Exemple et choix de format.
df = pd.DataFrame({'date': ['2020-01-02', '02/01/2020', '2020/01/03', 'foo']})
# Conversion permissive
df['dt'] = pd.to_datetime(df['date'], errors='coerce', dayfirst=False, infer_datetime_format=True)
Paramètre dayfirst indique si le jour vient avant le mois (utile pour formats européens). Paramètre infer_datetime_format tente d’accélérer/normaliser la détection. Formats ambigus (ex. « 01/02/2020 ») demandent une vérification : je compte les formats dominants (regex pour ISO YYYY-MM-DD versus DD/MM/YYYY) et je force un format explicite avec pd.to_datetime(df[‘date’], format=’%d/%m/%Y’, errors=’coerce’) si nécessaire.
Mini-routine pour plusieurs colonnes :
mapping = {'age':'numeric', 'price':'numeric', 'signup':'date'}
for col, typ in mapping.items():
if typ == 'numeric':
df[col+'_conv'] = pd.to_numeric(df[col], errors='coerce')
elif typ == 'date':
df[col+'_conv'] = pd.to_datetime(df[col], errors='coerce', infer_datetime_format=True)
Tableau synthèse :
| Méthode | Comportement erreurs |
| pd.to_numeric(…, errors=’coerce’) | Convertit les valeurs valides, remplace invalides par NaN (évite exceptions) |
| pd.to_datetime(…, errors=’coerce’) | Convertit dates valides, remplace invalides par NaT; dayfirst et format contrôlent l’interprétation |
Comment gérer valeurs manquantes et doublons efficacement
Garder un jeu de données exploitable passe par deux opérations simples mais critiques : imputer les valeurs manquantes intelligemment et supprimer les doublons de façon ciblée.
Privilégier la médiane pour les variables numériques réduit l’impact des outliers parce que la médiane a un « breakdown point » de 50% (résistance aux valeurs extrêmes), contrairement à la moyenne. Pour les variables catégoriques, utiliser la fréquence dominante via mode()[0] retourne la modalité la plus courante. Toujours documenter l’opération pour la traçabilité : ajouter des colonnes indicatrices montre ce qui a été modifié.
Imputation numérique : df['num'] = df['num'].fillna(df['num'].median())
Imputation catégorique : df['cat'] = df['cat'].fillna(df['cat'].mode()[0])
Doublons ciblés : df = df.drop_duplicates(subset=['user_name'], keep='first')
Choix de keep dans drop_duplicates :
- keep=’first’ : Conserver la première occurrence et supprimer les suivantes.
- keep=’last’ : Conserver la dernière occurrence et supprimer les précédentes.
- keep=False : Supprimer toutes les lignes qui ont des doublons (ne conserve aucune).
Résolution de conflits entre colonnes : fusionner en conservant la valeur non‑NA avec combine_first ou bfill. Exemple : df[‘merged’] = df[‘a’].combine_first(df[‘b’]). Pour conserver la meilleure information, prioriser la colonne la plus fiable ou faire une règle métier (concaténer, moyenne, etc.).
Journaliser les suppressions et imputations pour la traçabilité : créer des colonnes booléennes comme _num_imputed, _cat_imputed et _deduped_by (contenant la clé utilisée). Cela permet d’auditer et de recalculer si besoin.
| Type de variable | Stratégie recommandée | Code exemple |
| Numérique | Imputer par la médiane (robuste aux outliers) | df[‘num’] = df[‘num’].fillna(df[‘num’].median()) |
| Catégorique | Imputer par la modalité la plus fréquente (mode()[0]) | df[‘cat’] = df[‘cat’].fillna(df[‘cat’].mode()[0]) |
| Doublons | Supprimer ciblé par clé, garder ‘first’/’last’ ou merge selon règle métier | df = df.drop_duplicates(subset=[‘user_name’], keep=’first’) |
Comment standardiser des catégories et optimiser les performances
Uniformiser les catégories améliore la qualité des données et réduit souvent l’empreinte mémoire et le temps de calcul. Mapper des variantes textuelles vers une valeur canonique permet d’éviter les doublons sémantiques, tandis que le type pandas « category » (type catégoriel) encode les valeurs uniques et référence ces codes, ce qui réduit la redondance pour les colonnes à faible cardinalité.
Logique pratique : appliquer un mapping permet de remplacer les fautes/variantes, puis garder les valeurs inconnues via fillna pour ne pas perdre d’information. Exemple concret :
mapping = {'paris': 'Paris', 'parís': 'Paris'}
df['city_std'] = df['city'].str.lower().map(mapping).fillna(df['city'])
Passer ensuite au type catégoriel rend l’opération efficace en mémoire et en vitesse sur les agrégations. Conversion simple :
df['city_std'] = df['city_std'].astype('category')
Pourquoi choisir ‘category’ ? Parce que pandas stocke la liste des valeurs uniques et n’enregistre pour chaque ligne qu’un code entier. Cela accélère les groupby/merge et réduit la mémoire lorsque le nombre de catégories est bien inférieur au nombre de lignes (faible cardinalité). Voir pandas docs : https://pandas.pydata.org
Bonnes pratiques concrètes :
- Vérifier la cardinalité avant conversion : df[‘col’].nunique() et mesurer la mémoire avec df[‘col’].memory_usage(deep=True).
- Conserver la version string si vous avez besoin du texte original pour le logging ou les exports.
- Utiliser .cat.set_categories([…], ordered=True) pour imposer un ordre logique (utile pour des tranches/ordres naturels).
# Vérifier cardinalité et mémoire
print(df['city_std'].nunique())
print(df['city_std'].memory_usage(deep=True))
# Définir un ordre particulier
df['city_std'] = df['city_std'].cat.set_categories(['Paris','Lyon','Marseille'], ordered=True)
Pour accélérer pandas en général : privilégier les opérations vectorisées (éviter apply/python loops), limiter les conversions répétées, utiliser df.select_dtypes() pour traiter uniquement les colonnes concernées et composer les transformations avec .pipe pour rendre le code réutilisable et testable.
| Opération | Impact robustesse | Impact performance |
| Mapping + fillna | Évite valeurs ambiguës, améliore qualité | Léger coût initial, gains sur downstream |
| Conversion en category | Peut contraindre si cardinalité élevée | Réduit mémoire et accélère groupby/merge (pour faible cardinalité) |
| .cat.set_categories | Apporte ordre et contrôlabilité | Coût négligeable, utile pour comparaisons/tri |
| Vectorisation / select_dtypes / pipe | Renforce lisibilité et testabilité | Améliore nettement les performances vs loops |
Prêt à rendre votre pipeline de données plus fiable et reproductible ?
Ces 7 techniques pandas — normalisation de colonnes, nettoyage des chaînes, conversions sûres (numériques et dates), imputation intelligente, dédoublonnage ciblé, standardisation des catégories et optimisation des types — forment un kit minimal pour industrialiser le prétraitement. En les appliquant systématiquement vous réduisez les bugs, facilitez les analyses et gagnez du temps. Le bénéfice : des pipelines plus fiables, plus rapides à maintenir et reproductibles pour l’équipe.
FAQ
-
Quelles colonnes cibler d’abord pour le nettoyage ?
Commencez par les colonnes de clé (IDs, user_name), les colonnes utilisées pour jointures, et les types string/date. Normaliser les noms de colonnes avant tout évite les erreurs de sélection. -
Quand utiliser errors=’coerce’ pour les conversions ?
Utilisez errors=’coerce’ lorsqu’il est acceptable de marquer les valeurs invalides comme NaN/NaT pour gérer ensuite par imputation ou filtrage sans provoquer d’exception. -
Faut‑il convertir toutes les chaînes en category ?
Non. Convertissez en ‘category’ les colonnes à faible cardinalité (ex. villes, statuts). Pour des identifiants uniques ou colonnes à forte cardinalité, cela peut augmenter la complexité sans gain mémoire. -
Comment vérifier l’impact du nettoyage ?
Journalisez les actions (colonnes imputées, lignes dédupliquées), calculez pourcentage de valeurs converties/coercées et comparez les mesures clés avant/après (counts, agrégats) pour valider l’impact. -
Des bonnes pratiques pour rendre le code réutilisable ?
Packez les opérations en fonctions/pipelines, utilisez df.pipe pour chaîner, ajoutez des tests unitaires pour règles critiques et documentez chaque transformation (README ou journal de prétraitement).
A propos de l’auteur
Franck Scandolera — expert & formateur en Tracking avancé server‑side, Analytics Engineering, Automatisation No/Low Code (n8n) et intégration de l’IA en entreprise. Responsable de l’agence webAnalyste et de l’organisme de formation Formations Analytics. Références clients : Logis Hôtel, Yelloh Village, BazarChic, Fédération Française de Football, Texdecor. Dispo pour aider les entreprises => contactez moi.
⭐ Analytics engineer, Data Analyst et Automatisation IA indépendant ⭐
- Ref clients : Logis Hôtel, Yelloh Village, BazarChic, Fédération Football Français, Texdecor…
Mon terrain de jeu :
- Data Analyst & Analytics engineering : tracking avancé (GTM server, e-commerce, CAPI, RGPD), entrepôt de données (BigQuery, Snowflake, PostgreSQL, ClickHouse), modèles (Airflow, dbt, Dataform), dashboards décisionnels (Looker, Power BI, Metabase, SQL, Python).
- Automatisation IA des taches Data, Marketing, RH, compta etc : conception de workflows intelligents robustes (n8n, App Script, scraping) connectés aux API de vos outils et LLM (OpenAI, Mistral, Claude…).
- Engineering IA pour créer des applications et agent IA sur mesure : intégration de LLM (OpenAI, Mistral…), RAG, assistants métier, génération de documents complexes, APIs, backends Node.js/Python.





