Comment nettoyer un CSV avec Python étape par étape ?

Je nettoie un CSV avec Python en partant du brut, pas d’une version déjà arrangée. L’idée est simple : charger, inspecter, uniformiser, dédupliquer, convertir, valider, exporter. C’est là que pandas devient vraiment utile, surtout quand les données clients partent dans tous les sens.

Pourquoi inspecter le CSV avant de le nettoyer ?

Inspecter le CSV avant de le nettoyer évite de corriger à l’aveugle. Je préfère toujours regarder le fichier avant d’écrire la moindre règle, parce que les données brutes cachent souvent des problèmes simples, mais très bloquants : valeurs manquantes, espaces invisibles, doublons, dates invalides, nombres stockés comme du texte, libellés incohérents.

Avec un fichier client comme messy_customers.csv, l’inspection me donne une photo de départ. Je sais combien j’ai de lignes, combien j’ai de colonnes, comment les colonnes s’appellent vraiment, quels types pandas détecte, et si des lignes sont copiées-collées à l’identique. C’est basique, oui. Mais c’est souvent là que les erreurs commencent.

import pandas as pd

df = pd.read_csv("messy_customers.csv")

print(df.shape)
print(df.columns)
print(df.dtypes)
print(df.duplicated().sum())

Dans cet exemple, df.shape indique que le fichier contient 10 lignes et 8 colonnes. Ça permet déjà de vérifier qu’on a bien chargé le bon fichier, pas un export vide ou tronqué.

df.columns retourne les colonnes suivantes : Customer ID , Full Name , AGE, Email Address , Join Date, City, Membership, Total Spend. Je remarque tout de suite les espaces autour de certains noms. Ce n’est pas grave, mais ça casse vite du code si j’appelle une colonne sans l’espace invisible.

df.dtypes montre que toutes les colonnes sont vues comme object. En pandas, object veut souvent dire “texte” ou “type pas clair”. Et là, je me méfie. Je vois souvent des équipes lancer une analyse alors que l’âge, la dépense ou la date d’inscription sont encore du texte. Résultat, les moyennes sont fausses, les tris sont bizarres, et les filtres ne répondent pas comme prévu.

df.duplicated().sum() indique qu’il y a 1 ligne exactement dupliquée. Là aussi, c’est une info simple, mais importante. Si je compte les clients sans supprimer ce doublon, je gonfle mes chiffres.

Contrôle Question métier évitée
df.shape Est-ce que j’analyse le bon volume de données ?
df.columns Est-ce que les noms de colonnes vont casser mon code à cause d’espaces ou de libellés étranges ?
df.dtypes Est-ce que mes âges, dates et montants sont vraiment utilisables pour calculer ?
df.duplicated().sum() Est-ce que je compte deux fois le même client ou la même ligne ?

Comment charger le fichier sans perdre les détails ?

Je préfère charger le fichier sans laisser pandas décider trop tôt de ce qui est vide ou invalide. C’est un petit détail, mais il change beaucoup de choses quand on veut comprendre vraiment la qualité d’un CSV avant de le nettoyer.

import pandas as pd

df = pd.read_csv("messy_customers.csv", keep_default_na=False)

print(df.head())
print(df.dtypes)

Avec keep_default_na=False, pandas ne remplace pas automatiquement certaines chaînes par des valeurs manquantes. Par défaut, il peut interpréter des valeurs comme N/A, NA ou des cellules vides comme des NaN. Un NaN, c’est la valeur spéciale utilisée par pandas pour dire “donnée manquante”.

Ici, je ne veux pas ça tout de suite. Je veux voir ce qu’il y a réellement dans le fichier. Si un client a écrit N/A, si une date contient not a date, si un champ contient unknown, je veux le voir tel quel avant de décider quoi en faire. C’est souvent là qu’on comprend les vrais problèmes du fichier.

Dans ce cas précis, pandas lit toutes les colonnes en object, donc comme du texte. Ce n’est pas une erreur. C’est un signal. Ça veut dire que les âges, les dates et les montants devront être convertis proprement plus tard, avec des règles claires. J’ai déjà vu des scripts “rapides” transformer des montants en erreurs silencieuses juste parce qu’un symbole € ou une virgule traînait dans une cellule. Ça marche sur trois lignes, puis ça casse en production.

Le but ici, ce n’est pas de charger vite pour avoir un joli script. Le but, c’est de charger proprement pour construire un nettoyage reproductible. Le CSV client contient volontairement plusieurs problèmes de qualité :

  • Des espaces dans les noms de colonnes.
  • Des valeurs vides.
  • Des placeholders comme N/A, unknown ou not a date.
  • Des doublons exacts.
  • Des formats de texte incohérents.

Une fois le fichier chargé sans perdre ces détails, je peux commencer à le rendre manipulable. Et la première chose à corriger, ce sont les noms de colonnes. Tant qu’ils contiennent des espaces, des majuscules incohérentes ou des caractères pénibles, le code reste fragile.

Comment normaliser colonnes et valeurs manquantes ?

Je normalise d’abord les noms de colonnes, parce que ça évite une tonne de petits bugs pénibles après. Un espace caché dans Email Address , une majuscule oubliée dans Join Date, et vous perdez du temps sur un problème qui n’a rien à voir avec la donnée. Je vise trois choses simples :

  • Supprimer les espaces au début et à la fin.
  • Passer tous les noms en minuscules.
  • Remplacer les espaces internes par des underscores.
df.columns = (
    df.columns
      .str.strip()
      .str.lower()
      .str.replace(" ", "_")
)

print(df.columns.tolist())

Le résultat attendu est propre et prévisible : customer_id, full_name, age, email_address, join_date, city, membership, total_spend. C’est bête, mais chez un client, j’ai déjà vu trois versions d’une même colonne juste à cause d’espaces et de majuscules. Le nettoyage derrière devenait flou.

Je fais ensuite la même chose avec les valeurs manquantes. Le but, c’est de ne pas avoir dix façons différentes de dire “je n’ai pas l’info”. Une cellule vide, une cellule avec seulement des espaces, N/A, unknown, ou même not a date, tout ça doit devenir une vraie valeur manquante pandas : pd.NA. pd.NA, c’est la valeur standard de pandas pour représenter proprement l’absence d’information.

import pandas as pd

df = df.replace(r"^\s*$", pd.NA, regex=True)

df = df.replace(
    ["N/A", "n/a", "NA", "unknown", "not a date"],
    pd.NA
)

missing_counts = df.isna().sum()
print(missing_counts)

Après ce nettoyage, je peux compter ce qui manque vraiment, sans confondre une chaîne vide avec une vraie valeur. Le résultat attendu est celui-ci :

Colonne Valeurs manquantes
customer_id 1
full_name 1
age 1
email_address 0
join_date 1
city 2
membership 1
total_spend 1

Maintenant qu’on sait ce qui manque vraiment, on peut avancer proprement. On peut supprimer les doublons et nettoyer les textes sans mélanger les problèmes.

Comment supprimer les doublons et nettoyer les textes ?

Je commence par les doublons exacts, parce que c’est le nettoyage le plus simple et souvent le plus rentable. Deux lignes strictement identiques n’apportent rien. Elles peuvent même fausser un comptage de clients, gonfler des dépenses, ou donner l’impression qu’une ville ou un segment pèse plus lourd qu’en réalité.

rows_before = len(df)

df = df.drop_duplicates()

rows_after = len(df)

print(f"Lignes avant : {rows_before}")
print(f"Lignes après : {rows_after}")
print(f"Lignes supprimées : {rows_before - rows_after}")

Sur mon exemple, j’ai 10 lignes avant, 9 lignes après, donc 1 ligne exacte supprimée. La fonction drop_duplicates() supprime uniquement les lignes entièrement identiques. Elle ne fusionne pas deux clients qui se ressemblent, elle ne devine pas que “Jean Dupont” et “J. Dupont” sont peut-être la même personne. Ici, je reste volontairement sur du propre et simple.

Ensuite, je nettoie les colonnes texte. C’est souvent là que les petits écarts pénibles se cachent. Un espace avant, deux espaces au milieu, une majuscule différente, et votre reporting commence à raconter n’importe quoi.

text_cols = [
    "customer_id",
    "full_name",
    "email_address",
    "city",
    "membership"
]

for col in text_cols:
    df[col] = (
        df[col]
        .astype("string")
        .str.strip()
        .str.replace(r"\s+", " ", regex=True)
    )

df["full_name"] = df["full_name"].str.title()
df["city"] = df["city"].str.title()

df["email_address"] = df["email_address"].str.lower()
df["membership"] = df["membership"].str.lower()

Je convertis d’abord ces colonnes en texte avec astype(« string »). Puis strip() enlève les espaces au début et à la fin. La regex r »\s+ » remplace plusieurs espaces internes par un seul espace. C’est utile pour transformer “Jean Dupont” en “Jean Dupont”.

Pour les noms et les villes, je passe en Title Case, donc chaque mot commence par une majuscule. Pour les emails et les abonnements, je passe en minuscules. Comme ça, paris , PARIS et Paris deviennent une seule valeur exploitable : Paris.

Ce genre d’écart a l’air minuscule. Sur le terrain, je l’ai vu créer trois lignes différentes dans un reporting pour une seule réalité. Une ligne “Paris”, une ligne “PARIS”, une ligne “ paris ”. Et derrière, les analyses par ville ou par segment deviennent fausses.

Les textes sont maintenant propres. Il reste encore à convertir correctement les colonnes numériques et les dates, parce qu’un CSV garde souvent tout sous forme de texte au départ.

Comment convertir valider et exporter le CSV propre ?

À ce stade, je veux sortir du bricolage et obtenir un fichier vraiment exploitable. Souvent, pandas charge les colonnes d’un CSV en object, c’est-à-dire en texte générique. Ça passe pour afficher les données, mais pas pour faire une analyse fiable. Un âge doit être un nombre, une date d’inscription doit être une date, un montant doit être numérique.

# Convertir les colonnes numériques
df["age"] = pd.to_numeric(df["age"], errors="coerce")
df["total_spend"] = pd.to_numeric(df["total_spend"], errors="coerce")

# Convertir la colonne de date
df["join_date"] = pd.to_datetime(df["join_date"], errors="coerce")

J’utilise errors= »coerce » volontairement. Quand pandas tombe sur une valeur impossible, comme “unknown”, “N/A”, ou “abc” dans une colonne de montant, il ne casse pas tout le script. Il transforme la valeur en donnée manquante. Pour un nombre, ça donne NaN. Pour une date, ça donne NaT, l’équivalent d’une date manquante. C’est beaucoup plus propre pour repérer les anomalies ensuite.

Je fais aussi un contrôle simple sur les emails. Le but ici, c’est de repérer les emails manifestement invalides, pas de certifier qu’une adresse existe vraiment ou qu’elle peut recevoir un message. Pour ça, il faudrait une vérification de délivrabilité côté serveur ou via un service spécialisé.

# Validation simple du format email
email_regex = r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"

df["email_is_valid"] = df["email"].fillna("").str.match(email_regex)

# Voir les lignes avec un email invalide
invalid_emails = df[df["email_is_valid"] == False]

Une fois les types convertis et les emails contrôlés, j’exporte le fichier final. Je mets index=False pour éviter que pandas ajoute une colonne d’index inutile dans le CSV. Sinon, vous vous retrouvez souvent avec une colonne “Unnamed: 0” au prochain chargement, et franchement, on n’a pas besoin de ça.

# Export du fichier propre
df.to_csv("clean_customers.csv", index=False)

Le pipeline complet ressemble à ça.

Étape Objectif
Charger Lire le CSV avec pandas.
Inspecter Comprendre les colonnes, les types et les valeurs suspectes.
Normaliser les colonnes Obtenir des noms propres et cohérents.
Remplacer les placeholders Transformer “N/A”, “unknown” ou “-” en valeurs manquantes.
Supprimer les doublons Éviter de compter deux fois les mêmes clients.
Nettoyer les textes Retirer les espaces, harmoniser la casse et corriger les formats.
Convertir les types Passer les dates, nombres et montants dans les bons formats.
Valider les emails Identifier les adresses clairement invalides.
Exporter Créer un CSV propre, stable et réutilisable.

Au final, j’ai un fichier propre, documenté par le code, et beaucoup plus simple à analyser ou à brancher dans une automatisation. C’est ça le vrai gain. Moins de corrections manuelles, moins de surprises, et des données sur lesquelles on peut enfin travailler sérieusement.

Et si votre CSV devenait enfin exploitable ?

Nettoyer un CSV avec Python, ce n’est pas juste faire disparaître deux espaces et trois doublons. Je pars du fichier brut, j’inspecte, je normalise les colonnes, je rends les valeurs manquantes explicites, je supprime les doublons exacts, je standardise les textes, je convertis les types, je valide les emails et j’exporte un fichier propre. Ce flux a un gros avantage : il est répétable. Vous pouvez le relancer sur un nouveau fichier client sans tout refaire à la main. Au final, vous gagnez du temps, vous réduisez les erreurs et vos analyses reposent sur des données vraiment exploitables.

FAQ

  • Pourquoi nettoyer un CSV avant de l’analyser avec Python ?
    Parce qu’un CSV brut contient souvent des valeurs manquantes, des formats incohérents, des doublons, des textes mal saisis, des dates invalides ou des nombres stockés comme du texte. Si je lance une analyse directement dessus, les résultats peuvent être faux même si le code fonctionne.
  • Pourquoi utiliser keep_default_na=False avec pd.read_csv ?
    Je l’utilise pour garder la main sur l’interprétation des valeurs manquantes. Pandas peut convertir automatiquement certaines chaînes en valeurs manquantes. Avec keep_default_na=False, je peux inspecter les valeurs telles qu’elles sont dans le fichier, puis décider moi-même quels placeholders remplacer par pd.NA.
  • Comment repérer les doublons dans un fichier CSV avec pandas ?
    Je peux compter les lignes exactement dupliquées avec df.duplicated().sum(), puis les supprimer avec df.drop_duplicates(). Dans l’exemple, le fichier passe de 10 lignes à 9 lignes, donc une ligne entièrement identique a été retirée.
  • Pourquoi toutes les colonnes sont parfois en object dans pandas ?
    Ça arrive quand pandas lit les données comme du texte, souvent parce que les formats sont mélangés ou pas assez propres. Ce n’est pas bloquant. Il faut ensuite convertir les colonnes utiles avec pd.to_numeric pour les nombres et pd.to_datetime pour les dates.
  • Comment sauvegarder le CSV nettoyé avec Python ?
    Une fois le nettoyage terminé, j’exporte le résultat avec df.to_csv(« clean_customers.csv », index=False). Le paramètre index=False évite d’ajouter une colonne d’index inutile dans le fichier final.

 

 

A propos de l’auteur

Je suis Franck Scandolera, responsable de l’agence webAnalyste et de l’organisme Formations Analytics. J’accompagne les entreprises sur le tracking avancé server-side, l’Analytics Engineering, l’automatisation No/Low Code avec n8n, l’intégration de l’IA, le SEO et le GEO. J’ai travaillé pour des clients comme Logis Hôtel, Yelloh Village, BazarChic, la Fédération Française de Football ou Texdecor. Le nettoyage de données, je le vois souvent côté terrain : avant de parler IA ou dashboard, il faut déjà des fichiers propres. Si vous voulez fiabiliser vos données et automatiser vos traitements, contactez-moi.

Retour en haut