Le prétraitement des données passe par des opérations simples et reproductibles avec pandas et NumPy (voir documentation pandas.org et numpy.org). Je vous propose 8 astuces concrètes et exemples prêts à l’emploi pour transformer un jeu de données « messy » en table prête à l’analyse.
Comment standardiser rapidement colonnes et chaînes
Standardiser colonnes et chaînes accélère la détection des bugs liés aux espaces invisibles, à la casse et aux caractères spéciaux. J’applique des opérations vectorisées pandas (c’est-à-dire des méthodes qui opèrent sur des arrays entiers, optimisées en C) comme str.strip(), str.lower() et str.replace() pour normaliser noms de colonnes et valeurs textuelles en quelques lignes.
Je crée un jeu de données volontairement messy :
import pandas as pd
df = pd.DataFrame({
' Nom ': [' Alice ', 'BOB', ' José'],
'AGE': [34, ' 28', 45],
'City ': ['Paris', ' new-york', 'São Paulo']
})
print(df)
# Avant :
# Nom AGE City
# 0 Alice 34 Paris
# 1 BOB 28 new-york
# 2 José 45 São Paulo
Renommer toutes les colonnes en snake_case :
df.columns = df.columns.str.strip().str.lower().str.replace(r'\s+', '_', regex=True)
print(df.columns.tolist())
# Après : ['nom', 'age', 'city']
Effet sur les types : Les noms de colonnes sont des strings modifiés ; les types des colonnes restent inchangés.
Nettoyer toutes les colonnes texte (type object) :
# Option 1 : appliquer sur les colonnes objets
df[df.select_dtypes(include=['object']).columns] = df.select_dtypes(include=['object']).apply(lambda s: s.str.strip())
# Option 2 : appliquer sur tout le DataFrame (sûr si mélange types)
df = df.applymap(lambda x: x.strip() if isinstance(x, str) else x)
print(df)
# Après nettoyage espaces :
# nom age city
# 0 Alice 34 Paris
# 1 BOB 28 new-york
# 2 José 45 São Paulo
Effacer les accents / caractères non-ASCII (explication : NFKD est une forme de normalisation Unicode qui décompose les caractères composés en base+diacritiques) :
df['nom_ascii'] = df['nom'].str.normalize('NFKD').str.encode('ascii', errors='ignore').str.decode('ascii')
print(df[['nom','nom_ascii']])
# Avant/Après :
# nom nom_ascii
# 0 Alice Alice
# 1 BOB BOB
# 2 José Jose
Effet sur les types : Les opérations .str.* retournent des Series de type object (strings). L’encodage/décodage produit aussi des strings ASCII.
| Problème courant | Commande pandas | Quand l’utiliser |
| Espaces invisibles en début/fin | .str.strip() |
Nettoyage rapide des chaînes avant jointures ou comparaisons |
| Casse inconsistante | .str.lower() / .str.upper() |
Standardiser pour clés et regroupements |
| Espaces internes / multi-espaces | .str.replace(r'\s+',' ', regex=True) |
Nettoyer séparateurs ou normaliser noms |
| Caractères spéciaux / accents | .str.normalize('NFKD')...encode('ascii',errors='ignore') |
Supprimer accents avant normalisation ou affichage ASCII |
| Noms de colonnes sales | df.columns.str.strip().str.lower().str.replace(...) |
Renommer en snake_case pour code reproductible |
Sources : pandas docs (string methods) ; note : les opérations vectorisées pandas sont nettement plus rapides que apply/applymap ligne par ligne car elles sont implémentées en C et optimisées pour les arrays.
Comment convertir en numérique et gérer les dates sans casser le flux
Convertir des colonnes en numérique ou en datetime sans interrompre le pipeline est un gain de robustesse essentiel. J’utilise pd.to_numeric(…, errors=’coerce’) et pd.to_datetime(…, errors=’coerce’) pour transformer les valeurs invalides en NaN (Not A Number) ou NaT (Not A Time), ce qui évite les exceptions et permet de détecter ensuite les entrées problématiques.
Je fournis un petit jeu de données d’exemple et les étapes de nettoyage :
import pandas as pd
data = {
'price': ['12.5', 'n/a', '15€'],
'quantity': ['3', '-', '4,0'],
'order_date': ['2021-12-01', '01/12/2021', 'Dec 1 2021']
}
df = pd.DataFrame(data)
Je nettoie d’abord les symboles et espaces, puis je convertis :
# Nettoyage préalable : strip, retirer €, remplacer virgule décimale
df['price_clean'] = df['price'].str.strip().str.replace('€', '', regex=False).str.replace(',', '.', regex=False)
df['quantity_clean'] = df['quantity'].str.strip().str.replace('-', '', regex=False).str.replace(',', '.', regex=False)
# Conversion numérique avec coerce pour éviter les erreurs
df['price_num'] = pd.to_numeric(df['price_clean'], errors='coerce')
df['quantity_num'] = pd.to_numeric(df['quantity_clean'], errors='coerce')
# Conversion de dates : dayfirst=True pour formats jour/mois/année, ou format= pour patterns connus
df['order_dt_guess'] = pd.to_datetime(df['order_date'], errors='coerce', dayfirst=True)
df['order_dt_strict'] = pd.to_datetime(df['order_date'], errors='coerce', format='%Y-%m-%d')
Je vérifie les types avant/après et les conversions ratées :
print(df.dtypes) # Voir types
print(df[df['price_num'].isna()]) # Lignes où price n'a pas été converti
print(df[df['order_dt_guess'].isna()]) # Dates non converties
Le traitement des séparateurs décimaux mérite attention. Je remplace souvent la virgule par le point avant to_numeric. Pour des fichiers CSV, pandas.read_csv propose le paramètre decimal=’,’ ce qui est plus robuste. L’utilisation du module locale (locale.setlocale) permet d’utiliser locale.atof, mais cela dépend du système et peut introduire des effets de bord.
Quand lever une erreur vs quand coerce : Je recommande d’utiliser errors=’coerce’ pour les pipelines ETL en production où la disponibilité prime, et de lever une erreur lors de l’ingestion initiale si la qualité de source est critique. Toujours journaliser les transformations et les valeurs rejetées (logs ou fichier de métriques) pour audit et correction ultérieure.
| Commande | Résultat | Cas d’usage |
| pd.to_numeric(…, errors=’coerce’) | Convertit en float/int, invalide → NaN | Nettoyage tolerant, repérage des anomalies |
| pd.to_datetime(…, errors=’coerce’, dayfirst=True) | Convertit en datetime, invalide → NaT | Formats mixtes jour/mois/année |
| str.replace(‘,’, ‘.’) ou read_csv(decimal=’,’) | Gère séparateurs décimaux | Données francophones avec virgule |
Comment imputer intelligemment les valeurs manquantes
J’applique l’imputation selon le type de variable : médiane pour les numériques quand il y a des outliers, mode pour les catégoriques pour préserver la modalité la plus fréquente.
La moyenne (mean) est simple mais sensible aux valeurs extrêmes. La médiane est robuste face aux outliers. Le mode convient aux catégories. Pour des cas plus complexes, j’utilise des méthodes avancées comme KNN (K-Nearest Neighbors, K plus proches voisins) ou des modèles prédictifs entraînés pour deviner la valeur manquante. La bibliothèque scikit-learn (module sklearn.impute) fournit SimpleImputer et IterativeImputer pour ces usages.
Exemple rapide de DataFrame avec NaN :
import pandas as pd
import numpy as np
df = pd.DataFrame({
'age': [25, np.nan, 37, 45, np.nan],
'income': [50000, 60000, np.nan, 80000, 70000],
'city': ['Paris', 'Lyon', None, 'Paris', 'Lyon']
})
Exemples d’imputation basique et pipeline :
- Imputer numérique par médiane :
df['age'].fillna(df['age'].median(), inplace=True) - Imputer catégoriel par mode :
df['city'].fillna(df['city'].mode().iloc[0], inplace=True) - SimpleImputer de scikit-learn pour reproductibilité et pipeline :
from sklearn.impute import SimpleImputer num_imp = SimpleImputer(strategy='median') df['income'] = num_imp.fit_transform(df[['income']]) - Conserver un indicateur de valeur imputée :
df['age_missing'] = df['age'].isna().astype(int)
Attention aux risques : imputation peut introduire du biais (si missing non aléatoire), réduire la variance et gonfler les performances en validation si le procédé fuit l’information. Pour vérifier après imputation, comparer les distributions avant/après (moyenne, médiane, histogramme), effectuer un test Kolmogorov-Smirnov pour distributions, et valider l’impact sur la performance modèle via cross-validation.
| Stratégie | Code | Risques |
| Médiane (numérique) | df[‘col’].fillna(df[‘col’].median()) | Biais si données non représentatives, réduction variance |
| Mode (catégoriel) | df[‘col’].fillna(df[‘col’].mode().iloc[0]) | Sur-représentation de la modalité dominante |
| KNN / Modèles | sklearn.impute / IterativeImputer | Complexité, sur-ajustement, coût calculatoire |
Comment standardiser les catégories et réduire la cardinalité
Uniformiser les catégories réduit la cardinalité, stabilise les statistiques (moyennes, fréquences) et diminue le bruit introduit par les variantes orthographiques ou formatées différemment.
J’applique d’abord un mapping des variantes vers des labels canoniques, je gère ensuite les valeurs inconnues, puis je convertis en type category pour économiser la mémoire et accélérer certaines opérations. Le type category (dtype catégoriel de pandas) stocke les niveaux distincts une seule fois et remplace les valeurs par des codes entiers, ce qui réduit souvent la mémoire de plusieurs fois selon le nombre de valeurs uniques et la longueur des chaînes (voir pandas docs: https://pandas.pydata.org).
| city | product_cat |
| Paris | Electronics |
| paris | Electro |
| PARIS | electronics |
| Lyon | Household |
| Lyonnais | Household |
| Marseille | Electro. |
Exemples de code.
# 1) Mapping simple via dict
city_map = {'paris':'Paris', 'paris ':'Paris', 'paris': 'Paris'}
df['city'] = df['city'].str.strip().str.lower().map(city_map)
# 2) Remplacement par motifs (regex)
df['city'] = df['city'].replace(regex={r'(?i)^paris\\b.*':'Paris', r'(?i)^lyon.*':'Lyon'})
# 3) Compléter les valeurs non mappées
df['city'] = df['city'].fillna('OTHER')
# 4) Convertir en catégorie et mesurer la mémoire
df['city'] = df['city'].astype('category')
print(df.memory_usage(deep=True))
Regrouper les modalités rares améliore la robustesse du modèle et réduit la dimension après one-hot. Exemple de thresholding : remplacer les modalités avec moins de k occurrences par ‘OTHER’.
# Regroupement des modalités rares
k = 5
freq = df['product_cat'].value_counts()
rare = freq[freq < k].index
df['product_cat'] = df['product_cat'].replace(rare, 'OTHER')
Impact sur les encodages : l’one-hot exploding dimension (très coûteux en mémoire et susceptible d’overfitting) alors que le target encoding (remplacement par la moyenne du label) réduit fortement la dimension mais peut introduire fuite si mal appliqué (utiliser CV ou smoothing).
| Méthode | Code court | Impact |
| Mapping dict | df[‘c’]=df[‘c’].map(d) | Nettoyage précis, faible coût mémoire |
| Regex replace | df.replace(regex={…}) | Capture motifs, flexible |
| Fillna | df.fillna(‘OTHER’) | Évite NaN, stable pour ML |
| Category dtype | df.astype(‘category’) | Réduction mémoire significative et vitesse |
| Thresholding | replace(rare,’OTHER’) | Diminue cardinalité, réduit one-hot |
Comment supprimer doublons et construire un pipeline reproductible
Supprimer les doublons est une étape essentielle pour garantir la qualité des données et la fiabilité des modèles.
Définir correctement le subset (liste de colonnes utilisées pour identifier un doublon) permet d’éviter des suppressions intempestives : le subset est la clé de déduplication, keep indique quelle ligne garder (first, last, ou False pour supprimer toutes les occurrences).
J’opte souvent pour une stratégie en trois règles : 1) Détection par clé simple ou composite (ex : id + email). 2) Choix de la ligne à garder selon un timestamp ou un score (garder la valeur la plus récente ou la plus fiable). 3) Enregistrer ces règles dans un script versionné et ajouter des tests.
Exemple concret avec pandas :
import pandas as pd
from datetime import datetime
df = pd.DataFrame([
{'id': 1, 'email': 'a@ex.com', 'value': 10, 'ts': datetime(2021,1,1)},
{'id': 1, 'email': 'a@ex.com', 'value': 12, 'ts': datetime(2021,2,1)},
{'id': 2, 'email': 'b@ex.com', 'value': 5, 'ts': datetime(2021,1,5)},
{'id': 3, 'email': 'b_dup@ex.com', 'value': 7, 'ts': datetime(2021,3,1)},
])
# 1) Déduplication ciblée (garder la dernière occurrence)
df_dedup = df.drop_duplicates(subset=['id','email'], keep='last', inplace=False)
# 2) Règle détachée : trier par date puis drop_duplicates pour garder la version la plus récente
df_sorted = df.sort_values('ts')
df_recent = df_sorted.drop_duplicates(subset=['id'], keep='last')
Pour détecter des doublons proches (fuzzy matching), utilisez des bibliothèques spécialisées comme python-Levenshtein (calcul de distance de Levenshtein — nombre d’opérations pour transformer une chaîne en une autre). Cette étape demande une validation manuelle et un seuil.
Pour la reproductibilité, enchaînez les étapes dans un script ou pipeline. ColumnTransformer (de scikit-learn) est un utilitaire qui applique des transformations par colonne de façon structurée ; une alternative simple est d’utiliser des fonctions pandas nommées et chaînées. Versionnez le script et ajoutez des tests unitaires basiques (par ex. assert df.shape[0] == attendu après déduplication).
Tableau récapitulatif :
| Action | Commande pandas/scikit | Vérifications post-opération |
| Détection doublons | df.duplicated(subset=[…]) | Comparer counts avant/après, lister clés dupliquées |
| Suppression ciblée | df.drop_duplicates(subset=[…], keep=’first’/’last’) | Assert sur le nombre de lignes, contrôler timestamps/score conservés |
| Fuzzy matching | python-Levenshtein ou dedupe | Validation manuelle échantillonnage, seuils documentés |
| Pipeline reproductible | sklearn Pipeline/ColumnTransformer ou fonctions pandas nommées | Versionner le script, ajouter assertions unitaires |
Prêt à appliquer ces astuces pour obtenir des jeux de données propres ?
Le nettoyage et le prétraitement s’appuient sur des opérations simples et reproductibles : normaliser noms et chaînes, convertir en sécurisé les types numériques et dates, imputer selon le contexte, standardiser catégories et supprimer doublons. En automatisant ces étapes via scripts/pipelines vous gagnez fiabilité et temps d’analyse. Bénéfice immédiat : des jeux de données exploitables qui réduisent les erreurs downstream et accélèrent vos analyses.
FAQ
-
Qu’est-ce que le prétraitement des données en pratique ?
Le prétraitement regroupe les étapes de nettoyage et de transformation (normalisation des colonnes, nettoyage des chaînes, conversion de types, gestion des dates, imputation, standardisation des catégories, suppression des doublons) pour rendre un jeu de données prêt à l’analyse ou au machine learning. -
Pourquoi utiliser pd.to_numeric(…, errors=’coerce’) ?
Cette option convertit les valeurs non interprétables en NaN sans lever d’exception, ce qui préserve l’exécution et permet d’identifier puis traiter proprement les valeurs invalides ensuite. -
Quand imputer par la médiane plutôt que la moyenne ?
La médiane est préférée si la distribution contient des outliers car elle est robuste aux valeurs extrêmes. La moyenne convient si la distribution est proche d’une loi normale et qu’on accepte son biais par rapport aux outliers. -
Quel intérêt à convertir une colonne en type ‘category’ ?
Le type ‘category’ réduit la mémoire utilisée et accélère certaines opérations (groupby, merges). Il est utile quand le nombre de modalités est limité par rapport au nombre de lignes. -
Comment garantir que le prétraitement est reproductible ?
Versionnez vos scripts, encapsulez les étapes dans des fonctions ou pipelines (p.ex. scikit-learn ColumnTransformer), ajoutez des tests simples (assertions sur counts) et loggez les transformations appliquées pour pouvoir rejouer exactement le même traitement.
A propos de l’auteur
Je suis Franck Scandolera, expert & formateur en tracking server-side, Analytics Engineering, automatisation No/Low Code (n8n) et intégration de l’IA en entreprise. J’accompagne des clients comme Logis Hôtel, Yelloh Village, BazarChic, Fédération Française de Football et Texdecor. Responsable de l’agence webAnalyste et de l’organisme de formation Formations Analytics. Dispo pour aider votre entreprise => 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.






