Comment réussir le prétraitement des données en Python ?

Le prétraitement des données en Python se réalise efficacement avec pandas et numpy : normalisation des colonnes, nettoyage des chaînes, conversions sûres, parsing de dates, imputation, standardisation des catégories et déduplication (voir documentation pandas pour ces méthodes). Poursuivez pour des exemples pratiques et robustes.

Comment préparer un jeu de données désordonné

Je commence toujours par charger pandas et numpy puis créer ou explorer un DataFrame jouet qui expose les problèmes typiques (espaces, formats numériques incohérents, dates erronées, valeurs manquantes, variantes textuelles, doublons).

Exemple de jeu de données brut :

import pandas as pd
import numpy as np

df = pd.DataFrame({
    ' User Name ': [' Alice ', 'bob', 'Bob ', None, 'alice'],
    'Age': ['25', ' 30', 'thirty', None, '22'],
    'JoinDate': ['2020-01-01', '01/02/2020', 'Feb 3 2020', 'not a date', None],
    'City': [' New York', 'NYC', 'New york ', 'Paris', 'paris '],
    'score': [10, 10, 10, 15, 10]
})

Les commandes clés pour un diagnostic rapide sont :

  • df.info() — Permet d’identifier les types de colonnes (dtype), la présence de valeurs nulles et la mémoire utilisée.
  • df.head() — Permet de voir un échantillon des lignes et repérer les espaces ou formats visibles.
  • df.describe(include=’all’) — Fournit des statistiques descriptives pour toutes les colonnes, y compris les object; utile pour détecter les valeurs uniques et les fréquences.
  • df.isna().sum() — Compte les valeurs manquantes par colonne pour prioriser le nettoyage.
  • df.duplicated() — Renvoie un masque booléen des doublons; utile pour mesurer et supprimer les doublons.

Je recommande de fixer une graine pour un échantillon reproductible avec random_state (entier servant de seed, c’est la « graine » aléatoire).

Avant tout nettoyage, sauvegarder le brut pour traçabilité :

# Sauvegarde brute
df.to_parquet('raw_df.parquet', index=False)
df.to_csv('raw_df.csv.gz', index=False, compression='gzip')
# Exemple d'échantillon reproductible
sample = df.sample(frac=0.4, random_state=42)

Je recommande de documenter les anomalies trouvées (colonne, type d’anomalie, lignes exemples, action prévue) dans un fichier README ou un ticket.

Problème Action corrective
Espaces et casse inconsistante dans les noms strip() + lower()/title() selon besoin
Formats numériques stockés en string pd.to_numeric(errors=’coerce’) puis imputation ou suppression
Dates hétérogènes ou invalides pd.to_datetime(errors=’coerce’, dayfirst=…) et normalisation
Valeurs manquantes Imputation documentée ou suppression selon impact
Doublons df.drop_duplicates() après analyse des clés

Comment normaliser les noms de colonnes et les chaînes

Je normalise d’abord les noms de colonnes puis j’élimine les espaces dans toutes les colonnes de type object pour éviter des erreurs en aval et garantir des pipelines réutilisables. Cette normalisation réduit les bugs liés à des appels de colonnes différents (majuscule/minuscule, espaces, accents) et facilite l’automatisation et la reproductibilité des notebooks ou des jobs ETL.

Je privilégie une règle simple et reproductible : tout en minuscule, underscores pour séparer les mots, pas d’espaces. Cette convention est proche de PEP8 (style Python) et diminue les risques d’erreur lors des jointures, des sélections par attribut ou des export CSV/SQL.

df.columns = df.columns.str.strip().str.lower().str.replace(' ', '_', regex=False)

df = df.apply(lambda s: s.str.strip() if s.dtype == 'object' else s)

Je recommande quelques variantes selon les besoins :

  • Enlever les accents : Utiliser unicodedata.normalize pour transformer les caractères UTF-8 accentués en formes décomposées puis filtrer les marques diacritiques. Ceci évite des erreurs quand des systèmes ne gèrent pas UTF-8.
  • Remplacer les caractères non alphanumériques : Utiliser re.sub ou df.columns.str.replace avec regex=True pour remplacer tout ce qui n’est pas [a-z0-9_] par un underscore. Le terme regex signifie « regular expression » (expression régulière), un langage pour matcher des motifs de texte.
  • Préférer snake_case : Cette convention facilite la lecture, l’usage en code (pas de quotes obligatoires) et l’alignement avec les bibliothèques Python.
def normalize_columns(df):
    # Exemple complet : strip, lower, déaccentuation, remplace non-alphanum par _
    import unicodedata, re
    cols = []
    for c in df.columns:
        s = unicodedata.normalize('NFKD', str(c))
        s = ''.join(ch for ch in s if not unicodedata.combining(ch))
        s = s.strip().lower()
        s = re.sub(r'[^a-z0-9]+', '_', s).strip('_')
        cols.append(s)
    df.columns = cols
    df = df.apply(lambda s: s.str.strip() if s.dtype == 'object' else s)
    return df

Je versionne toujours cette fonction dans le dépôt (utils/data.py) pour traçabilité et réutilisabilité, et je couvre les cas limites par des tests unitaires.

Avant (colonne) Après (colonne)
Client ID client_id
Référence-Produit reference_produit
Montant (€) montant_eur
Avant (valeur chaîne) Après (valeur chaîne)
 » Alice  » « Alice »

Comment convertir les numériques et parser les dates sans erreur

Je commence par rappeler pourquoi astype() peut échouer et pourquoi prefèrer errors=’coerce’.

Avec astype(), Pandas tente une conversion stricte et lèvera une ValueError dès qu’une valeur n’est pas compatible, ce qui stoppe le pipeline de prétraitement.

Avec pd.to_numeric(…, errors=’coerce’) et pd.to_datetime(…, errors=’coerce’), les valeurs invalides deviennent NaN (Not a Number) ou NaT (Not a Time) au lieu de provoquer une erreur, ce qui facilite le logging, le diagnostic et la correction automatique.

  • Exemples concrets pour l’utilisation directe :
df['age'] = pd.to_numeric(df['age'], errors='coerce')
df['joindate'] = pd.to_datetime(df['joindate'], errors='coerce', dayfirst=False)

Pour les dates, le paramètre dayfirst indique si le jour vient avant le mois (True pour format européen).

Pour forcer un format précis, utiliser format=’%d/%m/%Y’ dans pd.to_datetime afin d’améliorer la vitesse et la robustesse.

Comment détecter et logger les valeurs échouées ?

Les lignes converties en NaN se détectent avec df[‘col’].isna().

Pour lister les lignes affectées et conserver la valeur brute il est utile de garder une colonne raw et d’utiliser par exemple :

df[df['age'].isna() & df['age_raw'].notna()]

Impact sur les types : les colonnes numériques contenant NaN deviennent float, car le type int standard ne supporte pas NaN.

Pour garder un entier nullable, convertir ensuite en dtype nullable de Pandas avec .astype(‘Int64’) (Int64 avec I majuscule permet NA).

Stratégie de correction automatique pour cas fréquents :

  • Supprimer les séparateurs de milliers et remplacer la virgule décimale par un point avant conversion.
# Exemple pour "1.234,56" -> "1234.56"
s = df['price'].astype(str)
s = s.str.replace('.', '', regex=False).str.replace(',', '.', regex=False)
df['price'] = pd.to_numeric(s, errors='coerce')

Pour formats mixtes, appliquer plusieurs règles conditionnelles ou utiliser dateutil.parser via pd.to_datetime sans format, puis contrôler les ambiguités avec dayfirst.

Opération Risque évité Action recommandée
Conversion numérique stricte Plantage du pipeline pd.to_numeric(…, errors=’coerce’) puis log des NaN
Parsing dates ambiguës Mauvaises dates (jj/mm vs mm/jj) pd.to_datetime(…, dayfirst=…, format=…) et vérifications
Présence de séparateurs Valeurs non converties Nettoyage (remove thousands, replace comma) avant conversion
Entiers avec NaN Perte du type entier Utiliser le dtype nullable Int64

Comment imputer les manquants et standardiser les catégories

J’impute les valeurs manquantes avec des agrégats pertinents pour limiter les biais et garder la cohérence du jeu de données. La médiane est préférée à la moyenne lorsque la distribution est dissymétrique ou contient des valeurs extrêmes, car la médiane est robuste aux outliers. La moyenne peut être utile si la distribution est symétrique et si l’on souhaite préserver la somme des valeurs. Pour les variables catégoriques, le mode (valeur la plus fréquente) est un choix simple et interprétable.

df['age'] = df['age'].fillna(df['age'].median())
df['city'] = df['city'].fillna(df['city'].mode()[0])

J’uniformise ensuite les catégories avec des dictionnaires de mapping pour consolider les variantes textuelles et réduire la cardinalité. La normalisation en minuscules aide avant le mapping, et fillna permet de conserver les valeurs inconnues propres (trimmed).

city_map = {'new york': 'NYC', 'nyc': 'NYC', 'paris': 'PARIS'}
df['city'] = df['city'].str.lower().map(city_map).fillna(df['city'].str.strip())

Pour gérer les valeurs rares, je regroupe les catégories peu fréquentes en ‘OTHER’ afin d’éviter le sur-ajustement et les niveaux inutiles dans les modèles. Méthode simple :

counts = df['city'].value_counts()
rare = counts[counts <= 10].index
df['city'] = df['city'].replace(rare, 'OTHER')

Pour mesurer l’impact, je compare les effectifs avant/après avec df['city'].value_counts() et je calcule les proportions pour vérifier que la transformation n’altère pas trop la distribution cible. Pour la performance et la mémoire, je préfère le type category de pandas quand la cardinalité est faible à modérée (gain mémoire et opérations groupby/merge plus rapides selon la pandas documentation).

Pour des pipelines plus avancés et reproductibles, sklearn.impute.SimpleImputer est utile (gestion par colonne, stratégies multiples, intégration à ColumnTransformer). L’exemple ci‑dessus reste centré sur pandas pour la simplicité.

Stratégie Cas d’usage Avantages Inconvénients
Médiane Numérique asymétrique ou outliers Robuste aux valeurs extrêmes Ne tient pas compte de la variance
Moyenne Numérique symétrique Préserve la moyenne globale Sensible aux outliers
Mode Catégorique Simple et interprétable Peut biaiser si catégorie dominante
Constante / OTHER Valeurs rares ou segmenter missing Réduit la cardinalité, stable pour ML Ajoute un niveau artificiel

Comment gérer les doublons et rendre le processus reproductible

Pour éliminer les doublons rapidement, j’utilise pandas avec une instruction simple :

df = df.drop_duplicates(subset=['user_name'], keep='first')

Cette commande conserve la première ligne pour chaque valeur de user_name et supprime les autres. Pour dédupliquer sur plusieurs colonnes, je précise le subset avec une liste de colonnes. Pour garder la ligne la plus récente, je trie d’abord :

# Garder la ligne la plus récente selon 'timestamp'
df = df.sort_values('timestamp', ascending=False)
df = df.drop_duplicates(subset=['user_id'], keep='first')

Pour marquer plutôt que supprimer, j’ajoute une colonne booléenne :

df['is_dup'] = df.duplicated(subset=['user_name'], keep='first')

Parfois il vaut mieux regrouper et agréger pour conserver l’information (par exemple conserver la somme des achats ou la date la plus récente) :

df = df.groupby(['user_id']).agg({
    'purchase_amount': 'sum',
    'timestamp': 'max',
    'user_name': 'first'
}).reset_index()

Pour rendre le prétraitement reproductible, j’écris des fonctions pures et réutilisables : normalize_columns, clean_strings, convert_types, impute_values, map_categories, deduplicate. Ces fonctions prennent et renvoient un DataFrame sans effets de bord, ce qui facilite les tests.

# Exemple de pipeline simple
def normalize_columns(df): ...
def clean_strings(df): ...
def deduplicate(df): ...

df = (df.pipe(normalize_columns)
        .pipe(clean_strings)
        .pipe(convert_types)
        .pipe(impute_values)
        .pipe(map_categories)
        .pipe(deduplicate))

J’ajoute des assertions unitaires pour valider les invariants (type, non-null, cardinalité). Exemple :

assert df['age'].dtype.name in ('float64', 'Int64')
assert df['user_id'].is_unique

J’archive des snapshots en Parquet pour audit et rollbacks, car Parquet conserve schéma et compresse efficacement (gain souvent > 10x vs CSV selon la colonne).

Étape Entrée attendue Sortie garantie Tests simples
Normalize Columns Colonnes brutes, noms inconsistants Noms cohérents, minuscules Assert colonnes attendues présentes
Convert Types Strings numériques Types numériques corrects Assert dtype pour chaque colonne
Deduplicate Doublons possibles Doublons gérés (supprimés ou marqués) Assert unicité ou colonne is_dup

Prêt à appliquer un prétraitement reproductible et fiable sur vos données ?

Le prétraitement solide repose sur une suite simple d’étapes : préparer un jeu d’exemple, normaliser les noms et chaînes, convertir numériquement et en dates sans erreur, imputer intelligemment, standardiser les catégories et dédupliquer de façon contrôlée. En encapsulant ces opérations dans des fonctions/pipelines testés vous réduisez les erreurs, gagnez du temps et augmentez la fiabilité des analyses. Bénéfice direct : des données exploitables et des décisions métier plus précises.

FAQ

  • Qu’est-ce que le prétraitement des données en pratique ?
    Le prétraitement regroupe les opérations pour rendre les données exploitables : nettoyage des chaînes, normalisation des colonnes, conversions numériques et de dates, imputation des manquants, standardisation des catégories et suppression des doublons.
  • Pourquoi utiliser errors=’coerce’ avec pandas ?
    errors=’coerce’ force les conversions invalides en NaN/NaT au lieu de lever une exception. C’est sécurisé pour des jeux hétérogènes et permet d’identifier facilement les valeurs à corriger.
  • Comment choisir entre moyenne et médiane pour imputer ?
    La médiane est recommandée en présence d’outliers car elle est robuste; la moyenne est utile si la distribution est symétrique et que les outliers sont peu probables. Mesurez l’impact avant/après.
  • Que faire si les catégories ont de nombreuses variantes rares ?
    Regroupez les valeurs rares en ‘OTHER’ ou utilisez un seuil de fréquence pour conserver les catégories principales. Cela réduit la cardinalité et stabilise les modèles.
  • Comment vérifier qu’on n’a pas cassé les données après nettoyage ?
    Automatisez des tests simples : types attendus, absence de duplicata si voulu, plages valides pour les numériques, et comparaisons d’effectifs avant/après. Versionnez les snapshots (parquet) pour audits.

 

 

A propos de l’auteur

Je suis Franck Scandolera, expert & formateur en tracking server-side, Analytics Engineering et automatisation No/Low Code. J’accompagne les entreprises sur l’implémentation de pipelines de données robustes, l’intégration d’IA et le SEO technique. Références : Logis Hôtel, Yelloh Village, BazarChic, Fédération Française de Football, Texdecor. Responsable de l’agence webAnalyste et de l’organisme de formation Formations Analytics. Dispo pour aider votre équipe — contactez moi.

Retour en haut
MetricsMag