Python

Python : pandas.read_csv — Guide pratique enrichi

Pourquoi read_csv reste incontournable

Parce qu’il transforme un simple fichier texte (CSV, TSV, “CSV Excel” au point-virgule, CSV compressé, flux HTTP/S3…) en DataFrame exploitable en 1 ligne. La clé, c’est de spécifier tôt séparateur, encodage, types et dates pour éviter les mauvaises surprises (colonnes “object”, dates non reconnues, virgule décimale, etc.).


Démarrage express (3 cas fréquents)

1) CSV “classique” (séparateur ,, UTF-8)

import pandas as pd

df = pd.read_csv("data.csv")  # heuristiques par défaut

2) Export Excel “français” (séparateur ;, décimale ,)

df = pd.read_csv(
    "export_excel.csv",
    sep=";",          # point-virgule
    decimal=",",      # virgule décimale
    thousands="\u00A0"  # NBSP souvent présent comme séparateur de milliers
)

3) Dates et types imposés

dtypes = {"id": "Int64", "categorie": "category", "prix_ht": "float64"}

df = pd.read_csv(
    "ventes.csv",
    sep=";",
    decimal=",",
    parse_dates=["date_vente"],
    dayfirst=True,     # format français DD/MM/YYYY
    dtype=dtypes,
    na_values=["", "NA", "N/A", "-", "null"]
)

Les paramètres qui font (vraiment) la différence

Séparateur & entêtes

  • sep=";" pour les exports FR/Excel.
  • header=0 (défaut) si la première ligne contient les noms de colonnes.
  • names=[...] pour imposer les noms (utile si fichier sans en-tête).
  • usecols=[...] charge uniquement les colonnes utiles (mémoire + vitesse).

Encodage & robustesse

  • encoding="utf-8" (recommandé), ou encoding="latin-1" / "cp1252" si accents cassés.
  • encoding_errors="ignore" ou "replace" pour survivre aux caractères illisibles (mieux vaut loguer ces cas pour correction amont).

Nombres & décimales “à la française”

  • decimal="," et thousands="\u00A0" (NBSP) ou " " selon vos fichiers.
  • Évitez de mélanger , et . pour les décimales dans un même champ : normalisez tôt.

Dates & fuseaux

  • parse_dates=[...] + dayfirst=True pour DD/MM/YYYY.
  • Si le format est exotique (horodatage, fuseaux, etc.), chargez d’abord en chaîne puis transformez :
df = pd.read_csv("logs.csv", dtype={"ts": "string"})
df["ts"] = pd.to_datetime(df["ts"], utc=True, errors="coerce")  # format explicite si connu

Booléens & valeurs manquantes

  • true_values=["OUI","Oui","YES","1"], false_values=["NON","No","0"].
  • na_values=[...] pour compléter la liste par défaut (ex. "-", "NULL").
  • keep_default_na=True (défaut) : garde la liste standard de NA.

Qualité & lignes “défectueuses”

  • on_bad_lines="skip" (ou "warn") pour ignorer/avertir sur les lignes mal formées.
    Astuce : ces options sont souvent plus tolérantes avec engine="python" : df = pd.read_csv("cassé.csv", on_bad_lines="skip", engine="python")

Dtypes & backend

  • Imposez les types via dtype={"code":"string", "qte":"Int64", "prix":"float64"} :
    Int64 (nullable), string (pandas), category (faible cardinalité) = RAM plus basse et erreurs plus tôt.
  • Si disponible, dtype_backend="pyarrow" peut réduire l’empreinte mémoire et accélérer certaines opérations (selon votre stack).

Moteur & performance

  • engine="c" (rapide, défaut) ; engine="python" (plus tolérant).
  • Indicateurs utiles :
    • usecols (colonne subset)
    • dtype (évite mixes dtypes)
    • chunksize=... (streaming)
    • memory_map=True (selon OS/fichiers)
    • low_memory=False si vous voulez éviter les colonnes “mixtes” (au prix de plus de RAM).

Gros volumes : lecture en morceaux (streaming)

Agréger un indicateur sans charger tout le fichier

total = 0.0
for chunk in pd.read_csv(
        "big.csv",
        usecols=["montant"],
        dtype={"montant": "float64"},
        chunksize=1_000_000):
    total += chunk["montant"].sum()
print(total)

Chargement partiel + concat

from pathlib import Path
import pandas as pd

dtypes = {"id": "Int64", "montant": "float64", "client": "string"}
files = sorted(Path("data").glob("*.csv"))

df = pd.concat(
    (pd.read_csv(f, sep=";", decimal=",", usecols=["id","client","montant"], dtype=dtypes)
     for f in files),
    ignore_index=True
)

Sources distantes, archives & cloud

  • URL HTTP/HTTPS : df = pd.read_csv("https://exemple.com/datasets/ventes.csv")
  • Fichiers compressés (auto-détectés le plus souvent) : df = pd.read_csv("ventes_2024.csv.gz") # gzip df = pd.read_csv("ventes.zip") # si le ZIP contient un seul CSV
  • Stockage objet (via fsspec) : df = pd.read_csv("s3://mon-bucket/ventes.csv", storage_options={"anon": False}) (Adaptez vos credentials selon l’environnement.)

Nettoyage & transformation dès la lecture

Colonnes à renommer / re-ordonner

df = pd.read_csv("source.csv", usecols=["ID","Nom client","CA (HT)"])
df = df.rename(columns={"ID":"id", "Nom client":"client", "CA (HT)":"ca_ht"})[["id","client","ca_ht"]]

Normaliser du texte & booleans

# Valeurs oui/non hétérogènes -> booléen propre
df = pd.read_csv(
    "clients.csv",
    converters={"actif": lambda x: str(x).strip().lower() in {"oui","yes","true","1"}}
).astype({"actif": "boolean"})

Data quality “à l’entrée”

ATTENDU_COLS = {"id","client","date_vente","montant"}
df = pd.read_csv("ventes.csv", sep=";", decimal=",", parse_dates=["date_vente"])
manquantes = ATTENDU_COLS - set(df.columns)
if manquantes:
    raise ValueError(f"Colonnes manquantes: {sorted(manquantes)}")

# Contrôle types simples
assert pd.api.types.is_datetime64_any_dtype(df["date_vente"])
assert pd.api.types.is_numeric_dtype(df["montant"])

Modèles prêts à copier (recettes)

1) Loader “FR-proof” (séparateur ;, virgule décimale, NBSP)

def read_csv_fr(path, *, cols=None, dtypes=None, parse_dates=None):
    import pandas as pd
    return pd.read_csv(
        path,
        sep=";",
        decimal=",",
        thousands="\u00A0",
        usecols=cols,
        dtype=dtypes,
        parse_dates=parse_dates,
        dayfirst=True,
        na_values=["", "NA", "N/A", "-", "null"]
    )

2) Pipeline gros volumes (sum, count, groupby)

from collections import Counter
import pandas as pd

def stats_par_client(path, chunksize=500_000):
    totaux = Counter()
    for chunk in pd.read_csv(
        path,
        usecols=["client","montant"],
        dtype={"client":"string", "montant":"float64"},
        chunksize=chunksize
    ):
        totaux.update(chunk.groupby("client")["montant"].sum().to_dict())
    return pd.Series(totaux, name="ca_ht").sort_values(ascending=False).to_frame()

3) Lecture tolérante (fichiers “un peu cassés”)

df = pd.read_csv(
    "source_irreguliere.csv",
    engine="python",
    on_bad_lines="skip",
    sep=";"
)

Checklist express (à coller dans vos projets)

  • Déterminer sep / decimal / thousands (FR ≈ sep=";", decimal=",", thousands=NBSP).
  • Fixer encoding (utf-8, sinon latin-1/cp1252) + encoding_errors.
  • usecols & dtype (types explicites, category si faible cardinalité).
  • parse_dates + dayfirst=True (ou conversion après lecture si format complexe).
  • na_values, true_values/false_values.
  • on_bad_lines et/ou engine="python" si fichiers irréguliers.
  • chunksize pour le streaming sur gros volumes.
  • tests de schéma (colonnes attendues, types, valeurs interdites).

Erreurs courantes & remèdes rapides

  • UnicodeDecodeError : essayez encoding="latin-1" ou encoding_errors="replace".
  • Colonnes en “object” au lieu de numériques : imposez dtype et/ou decimal/thousands.
  • Dates non reconnues : parse_dates + dayfirst, ou conversion après lecture avec pd.to_datetime(..., format=..., errors="coerce").
  • Colonnes “mixtes” (warnings sur types) : low_memory=False et/ou types imposés, ou nettoyez amont.
  • Fichier volumineux (RAM insuffisante) : chunksize, usecols, dtype serrés, éventuellement dtype_backend="pyarrow" si votre environnement le permet.

Cas particuliers — pandas.read_csv

1) Encodage incertain, BOM, caractères “bizarres”

# Essai en UTF-8 avec BOM (souvent depuis Excel/Windows)
df = pd.read_csv("data.csv", encoding="utf-8-sig")

# Si accents cassés :
df = pd.read_csv("data.csv", encoding="latin-1")  # ou "cp1252"
# En mode “je préfère charger et corriger après” :
df = pd.read_csv("data.csv", encoding="utf-8", encoding_errors="replace")

2) Séparateur ambigu (, vs ; vs \t) → auto-détection

# Heuristique Sniffer (nécessite engine='python')
df = pd.read_csv("data.csv", sep=None, engine="python")

Si vous connaissez le séparateur, imposez-le (plus fiable).

3) Formats “FR” : ; + virgule décimale + milliers insécables

df = pd.read_csv(
    "export.csv",
    sep=";",
    decimal=",",
    thousands="\u00A0"  # NBSP très fréquent
)

4) Champs multi-lignes entre guillemets

import csv
df = pd.read_csv(
    "notes.csv",
    sep=";",
    engine="python",          # plus tolérant pour sauts de ligne intégrés
    quotechar='"',
    doublequote=True          # "" devient " dans le champ
)

5) Guillemets/échappements atypiques

import csv
df = pd.read_csv(
    "weird.csv",
    sep=";",
    engine="python",
    quotechar="'",            # guillemet simple comme délimiteur
    doublequote=False,
    escapechar="\\",          # séquence d’échappement \'
)

6) En-têtes “pollués”, plusieurs lignes de titres, pied de page

# Sauter des lignes d’en-tête non tabulaires
df = pd.read_csv("rapport.csv", skiprows=3)          # ignore 3 premières

# Multi-index de colonnes (ex. 2 lignes d’en-têtes)
df = pd.read_csv("multiheader.csv", header=[0,1])

# Pied de page (nécessite engine='python')
df = pd.read_csv("rapport.csv", skipfooter=2, engine="python")

7) Colonnes irrégulières (largeur variable, champs manquants)

df = pd.read_csv(
    "messy.csv",
    sep=";",
    engine="python",        # tolère colonnes inégales
    on_bad_lines="skip"     # ou "warn" pour diagnostiquer
)

8) Préserver les zéros en tête (codes, SIRET, CP)

# Lire en chaîne (string) pour ne pas perdre les zéros
df = pd.read_csv("codes.csv", dtype={"code_postal": "string", "siret": "string"})

9) Très grands entiers (IDs) → éviter la notation scientifique

df = pd.read_csv("ids.csv", dtype={"id": "string"})  # pas en float !

10) Dates ambiguës / multi-formats

# Si le format est stable (FR) :
df = pd.read_csv("ventes.csv", sep=";", parse_dates=["date"], dayfirst=True)

# Si les formats varient → lire en string puis convertir :
df = pd.read_csv("ventes.csv", dtype={"date": "string"})
df["date"] = pd.to_datetime(
    df["date"],
    errors="coerce",    # valeurs inconvertibles → NaT
    dayfirst=True       # aide pour 01/02/2025 (1er février)
)

11) Horodatages ISO/Z, fuseaux, ou “epoch”

# ISO 8601 avec 'Z' (UTC)
df = pd.read_csv("log.csv", dtype={"ts": "string"})
df["ts"] = pd.to_datetime(df["ts"], utc=True, errors="coerce")

# Epoch (secondes)
df = pd.read_csv("events.csv", dtype={"ts": "Int64"})
df["ts"] = pd.to_datetime(df["ts"], unit="s", utc=True, errors="coerce")

12) Colonnes numériques “mixtes” (texte + nombres)

# Lire en string, nettoyer, puis convertir
df = pd.read_csv("mix.csv", dtype={"montant": "string"})
df["montant"] = (df["montant"]
                 .str.replace("\u00A0", "", regex=False)  # suppr. NBSP
                 .str.replace(",", ".", regex=False))     # virgule → point
df["montant"] = pd.to_numeric(df["montant"], errors="coerce")

13) Sous-ensemble de colonnes + dtypes imposés (RAM ↓, erreurs ↑ tôt)

dtypes = {"id":"Int64", "client":"string", "ca":"float64"}
df = pd.read_csv("ventes.csv", usecols=["id","client","ca"], dtype=dtypes)

14) Gros volumes (fichier > RAM) : streaming & agrégats

total = 0.0
for chunk in pd.read_csv("big.csv", usecols=["montant"], dtype={"montant":"float64"}, chunksize=1_000_000):
    total += chunk["montant"].sum()

15) Plusieurs CSV à fusionner

from pathlib import Path
import pandas as pd

files = sorted(Path("data").glob("*.csv"))
frames = (pd.read_csv(f, sep=";", decimal=",", usecols=["id","montant"]) for f in files)
df = pd.concat(frames, ignore_index=True)

16) ZIP contenant plusieurs CSV

import zipfile, io, pandas as pd

rows = []
with zipfile.ZipFile("bundle.zip") as z:
    for name in z.namelist():
        if name.lower().endswith(".csv"):
            with z.open(name) as f:
                rows.append(pd.read_csv(io.TextIOWrapper(f, encoding="utf-8")))
df = pd.concat(rows, ignore_index=True)

17) TSV comportant des tabulations dans les champs

import csv
df = pd.read_csv(
    "data.tsv",
    sep="\t",
    quotechar='"',
    engine="python",     # gère mieux les cas tordus
    quoting=csv.QUOTE_MINIMAL
)

18) Lignes de commentaires / méta-données (#)

df = pd.read_csv("mesures.csv", comment="#")  # ignore les lignes commençant par #

19) Espaces parasites après le séparateur

df = pd.read_csv("data.csv", sep=";", skipinitialspace=True)

20) Normaliser “oui/non”, “true/false” hétérogènes

df = pd.read_csv(
    "clients.csv",
    true_values=["OUI","Oui","YES","True","1"],
    false_values=["NON","No","False","0"]
).astype({"actif":"boolean"})

21) Détection/validation de schéma minimale à l’entrée

ATTENDU = {"id":"Int64","date":"datetime64[ns]","montant":"float64"}
df = pd.read_csv("ventes.csv", sep=";", parse_dates=["date"], dtype={"id":"Int64","montant":"float64"})

manquantes = set(ATTENDU) - set(df.columns)
if manquantes:
    raise ValueError(f"Colonnes manquantes : {sorted(manquantes)}")

assert pd.api.types.is_datetime64_any_dtype(df["date"])
assert pd.api.types.is_numeric_dtype(df["montant"])

22) Fichiers irréguliers mais exploitables (stratégie “tolérante”)

df = pd.read_csv(
    "irreg.csv",
    engine="python",
    on_bad_lines="skip",
    sep=None,           # tentative d’auto-détection
    dtype="string"      # tout en string → nettoyage en aval
)

23) Pré-lecture “FR-proof” factorisée (fonction utilitaire)

def read_csv_fr(path, *, cols=None, dtypes=None, parse_dates=None):
    import pandas as pd
    return pd.read_csv(
        path,
        sep=";",
        decimal=",",
        thousands="\u00A0",
        usecols=cols,
        dtype=dtypes,
        parse_dates=parse_dates,
        dayfirst=True,
        na_values=["", "NA", "N/A", "-", "null"]
    )

Rappels utiles

  • skipfooterexige engine="python".
  • sep=None (auto-détection) → exige engine="python".
  • Pour éviter les “colonnes mixtes” : low_memory=False ou imposez dtype.
  • Codes/identifiants → lisez en string (pas en float64).
  • Gros volumes → usecols, dtype, chunksize, agrégations par morceaux.

Mini-FAQ

Différence engine="c" vs "python" ?
"c" est très rapide mais strict ; "python" est plus tolérant (utile avec on_bad_lines) mais plus lent.

Int64 vs int64 ?
Int64 est nullable (accepte NA), int64 ne l’est pas. Préférez Int64 quand des manquants sont possibles.

Quand utiliser category ?
Quand la cardinalité de la colonne est faible (codes, états, pays). Gain de RAM et groupby/joins plus efficaces.


Autres articles

CSV en Python — du téléchargement au...
Le CSV paraît simple, jusqu’au moment où il casse...
En savoir plus
Python : pandas.to_csv — Exporter propre, fiable...
DataFrame.to_csv devient un contrat d’échange : lisible partout, stable dans...
En savoir plus
Pratique de l’apprentissage automatique avec scikit-learn et...
Cet article vous guide, pas à pas, de la préparation...
En savoir plus
Python & finance PME — un kit...
Pour une PME, la finance est un pilotage quotidien —...
En savoir plus
Python pour la finance — les fonctions de base
Python pour la finance — les fonctions...
Python est devenu l’outil “couteau suisse” des analystes et contrôleurs...
En savoir plus
Python & Pickle : manipuler les fichiers...
Pickle est la bibliothèque standard de Python pour sérialiser (convertir...
En savoir plus
Programmation Python dans la Pratique : Gestionnaire...
📌 IntroductionLa programmation en Python est idéale pour développer des...
En savoir plus
Guide Pratique de Programmation en Python
Cet article explore un guide complet en programmation python et...
En savoir plus

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *