Home » Analytics » Comment nettoyer et prétraiter des données en Python ?

Comment nettoyer et prétraiter des données en Python ?

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.

Retour en haut
DataMarket AI