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.


AZ

Share
Published by
AZ

Recent Posts

Questions pièges en entretien d’embauche : méthodes et réponses pour réussir

Un entretien d’embauche ressemble rarement à une conversation ordinaire. Derrière des questions en apparence simples…

1 semaine ago

Questions entretien d’embauche commercial : exemples et réponses efficaces

Préparez efficacement votre entretien commercial avec 140 questions clés et 12 mises en situation concrètes…

1 semaine ago

Questions d’entretien d’embauche : exemples, réponses et conseils pour réussir

Un entretien d’embauche crée souvent une impression particulière. Quelques minutes avant d’entrer dans la salle…

1 semaine ago

Modèle de Devis Bâtiment Excel

Dans le bâtiment, un devis représente souvent le premier véritable contact entre une entreprise et…

2 semaines ago

Lettre de motivation EHPAD : guide complet, exemples concrets et conseils pour une candidature réussie

Dans un EHPAD, chaque candidature révèle une manière d’être autant qu’un savoir-faire. Derrière la lettre…

2 semaines ago

Lettre de motivation mutation interne : Modèles & Exemples Word

Télécharger des modèles et exemples Word de lettres de motivation pour mutation interne ⬇️ Au…

2 semaines ago

This website uses cookies.