Module 6 — Nettoyage des données
Le module 8 du cours d'introduction l'a établi : la préparation des données absorbe l'essentiel d'un projet, et aucun algorithme ne rattrape des données fausses. Ce module transforme ce constat en gestes concrets. Le fil conducteur : chaque décision de nettoyage est un choix méthodologique qui mérite une ligne de justification, pas un réflexe automatique.
D'abord diagnostiquer, ensuite traiter
Le tort le plus courant est de nettoyer avant d'avoir mesuré. Trois commandes dressent l'état des lieux :
df.isna().mean().sort_values(ascending=False) # taux de manquants PAR COLONNE
df.duplicated().sum() # lignes strictement identiques
df.dtypes # types réels vs types attendus
Complétées par value_counts() sur chaque catégorielle (fautes de frappe, variantes de casse) et describe() sur chaque numérique (valeurs impossibles : âges négatifs, montants nuls, dates dans le futur).
Valeurs manquantes : trois questions avant tout traitement
Pourquoi manquent-elles ? La réponse change tout. Un capteur en panne une semaine (absence accidentelle), un champ facultatif du formulaire (absence structurelle), un revenu non déclaré précisément par les hauts revenus (absence informative). Dans le troisième cas, supprimer ou imputer efface un signal réel — il vaut parfois mieux créer une colonne revenu_manquant booléenne.
Combien manquent-elles ? À 2 %, presque tout traitement raisonnable convient. À 40 %, la colonne elle-même est en question. Entre les deux, l'arbitrage dépend de l'importance de la variable.
Supprimer ou imputer ?
df = df.dropna(subset=["montant"]) # supprimer les lignes — si rares et non informatives
df["age"] = df["age"].fillna(df["age"].median()) # imputer la médiane — robuste aux extrêmes
df["pays"] = df["pays"].fillna("Inconnu") # catégorie explicite
df["temperature"] = df["temperature"].interpolate() # séries temporelles
Si les données servent à entraîner un modèle, les statistiques d'imputation (médiane, moyenne) doivent être calculées sur le jeu d'entraînement seulement, puis appliquées au jeu de test. Les calculer sur l'ensemble complet est une fuite de données — le cours d'introduction (module 5) a montré ce que cela coûte. C'est exactement ce que les pipelines scikit-learn automatisent.
Doublons : exacts et approximatifs
df = df.drop_duplicates() # lignes strictement identiques
df = df.drop_duplicates(subset=["client_id", "date"]) # doublons "métier"
df = df.sort_values("maj").drop_duplicates(subset=["client_id"], keep="last") # garder le plus récent
Le doublon strict est facile. Le doublon métier — deux lignes différentes qui décrivent la même réalité (même client, adresses saisies différemment) — exige de définir la clé d'unicité avec le métier, et la règle de survie (keep). Question préalable dans tous les cas : le doublon est-il une erreur de collecte ou une réalité (un client peut légitimement commander deux fois le même jour) ?
Types : la colonne qui ment sur sa nature
Une colonne mal typée fausse silencieusement tout l'aval : les montants texte ne s'additionnent pas, les dates texte ne se comparent pas, describe() les ignore.
df["montant"] = pd.to_numeric(df["montant"], errors="coerce") # non convertible → NaN
df["date"] = pd.to_datetime(df["date"], errors="coerce")
df["code_postal"] = df["code_postal"].astype("string") # un code n'est PAS un nombre
df["segment"] = df["segment"].astype("category") # catégorielle : mémoire et sémantique
errors="coerce" transforme l'inconvertible en NaN que l'on recompte ensuite : si 3 000 valeurs deviennent nulles, le problème est en amont (séparateur décimal, « N/A » littéraux, unités collées aux nombres) et se corrige à la source, souvent dès read_csv (decimal=",", na_values=["N/A", "-"]).
Cas particulier des identifiants : codes postaux, SIRET, numéros de téléphone sont des chaînes. Les typer en entier détruit les zéros de tête — dommage classique et irréversible une fois le fichier réécrit.
Incohérences de saisie : l'accesseur .str
df["ville"] = df["ville"].str.strip().str.title() # espaces, casse
df["pays"] = df["pays"].replace({"FR": "France", "france": "France"})
La méthode de travail : value_counts() avant pour voir les variantes, normalisation, value_counts() après pour vérifier. Sur des référentiels importants (pays, régions), la table de correspondance explicite bat les corrections au fil de l'eau : elle est relisible et réutilisable.
Tracer ce qu'on a fait
Un nettoyage honnête laisse une trace. La forme minimale — quelques lignes de journal dans le script ou le notebook :
n0 = len(df)
df = df.dropna(subset=["montant"])
print(f"Manquants montant : {n0 - len(df)} lignes supprimées ({(n0-len(df))/n0:.1%})")
Si un résultat d'analyse surprend, la première question sera « qu'a fait le nettoyage ? » ; ces traces répondent en secondes. Les fonctions de nettoyage regroupées dans un module (nettoyage.py, module 3) rendent le tout rejouable sur les données du mois prochain — c'est le critère qui distingue un nettoyage d'une bidouille.
Ce qu'il faut retenir
- Diagnostiquer avant de traiter : taux de manquants par colonne, doublons, types réels,
value_countsdes catégorielles. - Manquants : comprendre le mécanisme d'absence avant de choisir entre suppression, imputation ou indicateur ; imputer depuis l'entraînement seul si un modèle suit.
- Doublons métier : définir la clé d'unicité et la règle de survie avec le métier.
to_numeric/to_datetimeavecerrors="coerce"puis recompte des NaN ; identifiants en chaînes, jamais en entiers.- Chaque décision tracée d'une ligne ; le nettoyage rejouable vaut dix nettoyages manuels.
Au module suivant : combiner les tables — jointures, regroupements et tableaux croisés, le trio qui transforme des fichiers épars en réponses.