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é), ouencoding="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=","etthousands="\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=Truepour 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 avecengine="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=Falsesi 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, sinonlatin-1/cp1252) +encoding_errors. - usecols & dtype (types explicites,
categorysi 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: essayezencoding="latin-1"ouencoding_errors="replace".- Colonnes en “object” au lieu de numériques : imposez
dtypeet/oudecimal/thousands. - Dates non reconnues :
parse_dates+dayfirst, ou conversion après lecture avecpd.to_datetime(..., format=..., errors="coerce"). - Colonnes “mixtes” (warnings sur types) :
low_memory=Falseet/ou types imposés, ou nettoyez amont. - Fichier volumineux (RAM insuffisante) :
chunksize,usecols,dtypeserrés, éventuellementdtype_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
skipfooter→ exigeengine="python".sep=None(auto-détection) → exigeengine="python".- Pour éviter les “colonnes mixtes” :
low_memory=Falseou imposezdtype. - Codes/identifiants → lisez en
string(pas enfloat64). - 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.



