Lecture : 11 minutes · Niveau : intermédiaire · Mise à jour : avril 2026
pandas est la bibliothèque Python qui transforme la manipulation de données tabulaires en une activité productive. Là où Excel atteint ses limites (gros volumes, opérations répétitives, automatisation), pandas prend le relais avec une syntaxe puissante. Ce tutoriel se concentre sur les patterns vraiment utiles en PME, sans dériver vers le data science sophistiqué.
Voir aussi → Python pour PME : guide pratique.
Sommaire
- Lire un fichier proprement
- Inspecter et explorer
- Sélectionner et filtrer
- Nettoyer les données
- Agrégations et groupby
- Joindre plusieurs sources
- Pivot tables et tableaux croisés
- Écrire un Excel formaté
- Pièges classiques et solutions
- FAQ
1. Lire un fichier proprement
import pandas as pd
# CSV simple
df = pd.read_csv("ventes.csv")
# CSV avec subtilités
df = pd.read_csv(
"ventes.csv",
encoding="utf-8", # ou "latin-1" pour des fichiers anciens
sep=";", # séparateur français
decimal=",", # virgule décimale française
parse_dates=["date_vente"],
dtype={"code_produit": str}, # éviter conversion en int
na_values=["N/A", "-", ""],
)
# Excel avec feuille spécifique
df = pd.read_excel("rapport.xlsx", sheet_name="Ventes 2026")
# Toutes les feuilles d'un fichier Excel
feuilles = pd.read_excel("rapport.xlsx", sheet_name=None)
# feuilles est un dict {nom_feuille: DataFrame}
Encoding : le piège classique
Les fichiers exportés depuis Excel sur Windows sont souvent en cp1252 ou latin-1, pas UTF-8. Si vous voyez é qui devient é, vous avez un problème d’encoding.
# Détecter automatiquement
import chardet
with open("ventes.csv", "rb") as f:
encoding = chardet.detect(f.read())["encoding"]
df = pd.read_csv("ventes.csv", encoding=encoding)
2. Inspecter et explorer
Avant tout traitement, comprendre la structure du DataFrame.
# Aperçu
df.head(10) # 10 premières lignes
df.tail(5) # 5 dernières
df.sample(20) # 20 lignes aléatoires
# Dimensions
df.shape # (lignes, colonnes)
len(df) # nombre de lignes
# Métadonnées
df.dtypes # type de chaque colonne
df.info() # vue compacte : type + non-null count
df.describe() # statistiques pour les colonnes numériques
df.describe(include="object") # idem pour les colonnes texte
# Valeurs uniques
df["region"].unique()
df["region"].value_counts() # comptage par valeur
# Valeurs manquantes
df.isna().sum() # nombre de NaN par colonne
df.isna().sum().sort_values(ascending=False)
df.info() est souvent la première commande à lancer : elle révèle d’un coup les colonnes mal typées (numérique stocké en string, dates en string).
3. Sélectionner et filtrer
# Une colonne (Series)
df["client"]
# Plusieurs colonnes (DataFrame)
df[["client", "produit", "montant"]]
# Filtre booléen
df[df["montant"] > 1000]
# Filtres combinés
df[(df["montant"] > 1000) & (df["region"] == "Dakar")]
df[(df["region"] == "Dakar") | (df["region"] == "Thiès")]
df[df["region"].isin(["Dakar", "Thiès", "Saint-Louis"])]
# Texte : startswith, contains
df[df["client"].str.contains("SARL", case=False, na=False)]
df[df["produit"].str.startswith("PRD-")]
# Dates
df[df["date_vente"] >= "2026-01-01"]
df[df["date_vente"].dt.year == 2026]
df[df["date_vente"].dt.month == 4]
# Sélection par index avec loc
df.loc[df["region"] == "Dakar", ["client", "montant"]]
loc permet de combiner sélection de lignes et de colonnes en une seule opération, plus lisible que les filtres successifs.
4. Nettoyer les données
Doublons
# Détecter
df.duplicated().sum()
df[df.duplicated(keep=False)] # voir les doublons
# Supprimer
df = df.drop_duplicates()
df = df.drop_duplicates(subset=["email"]) # selon une colonne
Valeurs manquantes
# Supprimer les lignes avec NaN
df = df.dropna()
df = df.dropna(subset=["email"]) # seulement si colonnes critiques NaN
# Remplir
df["montant"] = df["montant"].fillna(0)
df["region"] = df["region"].fillna("Inconnue")
df["prix"] = df["prix"].fillna(df["prix"].mean()) # moyenne
Texte
# Strip + casse
df["client"] = df["client"].str.strip().str.upper()
# Remplacer
df["region"] = df["region"].replace({"DKR": "Dakar", "THS": "Thiès"})
# Regex
df["telephone"] = df["telephone"].str.replace(r"\s+", "", regex=True)
df["email"] = df["email"].str.lower()
Types
# Conversion
df["montant"] = df["montant"].astype(float)
df["date_vente"] = pd.to_datetime(df["date_vente"], errors="coerce")
df["code"] = df["code"].astype(str).str.zfill(6) # pad avec zéros
errors="coerce" transforme les valeurs invalides en NaN au lieu de planter — précieux pour des données sales.
5. Agrégations et groupby
# Total par région
df.groupby("region")["montant"].sum()
# Plusieurs agrégations
df.groupby("region").agg(
total=("montant", "sum"),
moyenne=("montant", "mean"),
nombre=("montant", "count"),
max=("montant", "max"),
)
# Plusieurs colonnes de groupement
df.groupby(["region", "produit"])["montant"].sum()
# Avec tri
df.groupby("region")["montant"].sum().sort_values(ascending=False)
# Top 5 produits par CA
df.groupby("produit")["montant"].sum().nlargest(5)
# Agrégation sur date par mois
df.groupby(df["date_vente"].dt.to_period("M"))["montant"].sum()
groupby + agg couvre 80 % des besoins de reporting : sommes, moyennes, comptages, par dimension. C’est l’équivalent SQL GROUP BY mais beaucoup plus flexible.
6. Joindre plusieurs sources
Cas typique : un fichier de ventes avec un code client, un fichier clients avec les détails.
ventes = pd.read_csv("ventes.csv")
clients = pd.read_csv("clients.csv")
# Inner join (seulement les correspondances)
fusion = ventes.merge(clients, on="code_client", how="inner")
# Left join (toutes les ventes, infos clients si dispo)
fusion = ventes.merge(clients, on="code_client", how="left")
# Clés différentes des deux côtés
fusion = ventes.merge(
clients,
left_on="ref_client",
right_on="id_client",
how="left",
)
# Vérifier après merge
print(f"Lignes ventes: {len(ventes)}, après merge: {len(fusion)}")
# Si le total a explosé : problème de clé dupliquée
Vérifier l’intégrité de la jointure
# Quels codes clients sont dans ventes mais pas clients ?
manquants = set(ventes["code_client"]) - set(clients["code_client"])
print(f"Codes clients orphelins : {len(manquants)}")
Toujours vérifier le nombre de lignes avant et après merge — c’est l’erreur la plus courante en pandas (jointure qui multiplie les lignes silencieusement).
7. Pivot tables et tableaux croisés
L’équivalent du tableau croisé dynamique Excel.
# CA par produit (lignes) et par mois (colonnes)
pivot = df.pivot_table(
values="montant",
index="produit",
columns=df["date_vente"].dt.to_period("M"),
aggfunc="sum",
fill_value=0,
margins=True, # ajoute les totaux ligne et colonne
margins_name="Total",
)
# Plusieurs valeurs
pivot = df.pivot_table(
values=["montant", "quantite"],
index="region",
columns="produit",
aggfunc={"montant": "sum", "quantite": "sum"},
)
Le résultat est un DataFrame qui peut directement être exporté en Excel pour présentation.
Inverse : depivot avec melt
Quand on reçoit un tableau « wide » (mois en colonnes) et qu’on veut le format « long » (une ligne par observation) :
df_long = df.melt(
id_vars=["produit"],
value_vars=["jan", "fev", "mar"],
var_name="mois",
value_name="ventes",
)
8. Écrire un Excel formaté
# Simple
df.to_excel("rapport.xlsx", index=False)
# Plusieurs feuilles dans le même fichier
with pd.ExcelWriter("rapport.xlsx") as writer:
df_ventes.to_excel(writer, sheet_name="Ventes", index=False)
df_synthese.to_excel(writer, sheet_name="Synthèse", index=False)
df_top.to_excel(writer, sheet_name="Top 10", index=False)
Mise en forme avec openpyxl
from openpyxl import load_workbook
from openpyxl.styles import Font, PatternFill, Alignment
# D'abord écrire avec pandas
df.to_excel("rapport.xlsx", index=False)
# Puis ouvrir et mettre en forme
wb = load_workbook("rapport.xlsx")
ws = wb.active
# En-tête en gras avec fond
header_font = Font(bold=True, color="FFFFFF")
header_fill = PatternFill("solid", fgColor="305496")
for cell in ws[1]:
cell.font = header_font
cell.fill = header_fill
cell.alignment = Alignment(horizontal="center")
# Largeur auto des colonnes
for col in ws.columns:
max_len = max(len(str(c.value)) for c in col if c.value)
ws.column_dimensions[col[0].column_letter].width = max_len + 2
wb.save("rapport.xlsx")
Investir 10 minutes dans une fonction utilitaire de formatage que tous les scripts réutilisent fait la différence entre des rapports pro et des fichiers bruts.
9. Pièges classiques et solutions
SettingWithCopyWarning
Le warning le plus déroutant pour les débutants. Survient quand on modifie un sous-DataFrame sans utiliser .copy() ou .loc.
# Mauvais
sub = df[df["region"] == "Dakar"]
sub["bonus"] = 10 # warning
# Bon
sub = df[df["region"] == "Dakar"].copy()
sub["bonus"] = 10
Lecture de gros fichiers
Pour des CSV de plusieurs Go, la lecture brute échoue par manque de mémoire. Solutions : chunksize pour lire par paquets, usecols pour ne charger que les colonnes nécessaires, ou passer à polars ou duckdb.
# Lire par chunks de 100k lignes
total = 0
for chunk in pd.read_csv("gros.csv", chunksize=100_000):
total += chunk["montant"].sum()
Dates en format inconnu
# Si le format n'est pas standard
df["date"] = pd.to_datetime(df["date"], format="%d/%m/%Y", errors="coerce")
Forcer le format évite les surprises (pandas peut interpréter 01/02/2026 comme 1er février OU 2 janvier selon la locale).
Apply lent
df.apply(fonction, axis=1) est tentant mais lent sur gros volumes. Préférer les opérations vectorisées quand possible.
# Lent
df["total"] = df.apply(lambda row: row["prix"] * row["quantite"], axis=1)
# Rapide (vectorisé)
df["total"] = df["prix"] * df["quantite"]
Voir aussi → Python pour scripting admin système pour orchestrer ces traitements avec des scripts robustes.
10. FAQ
pandas vs Excel : quand basculer ?
Quelques signaux : Excel rame, le fichier dépasse 100k lignes, le même traitement se répète plus d’une fois par mois, les formules deviennent illisibles, plusieurs personnes touchent au même fichier. Si l’un de ces points apparaît, pandas est rentable.
Faut-il vraiment apprendre pandas si on a SQL ?
Pour les opérations SQL pures (filtres, agrégations, jointures), SQL est souvent plus simple. Mais pandas excelle dans : la manipulation de fichiers (Excel, CSV), les transformations complexes en mémoire, l’intégration avec d’autres bibliothèques Python. Les deux sont complémentaires, pas concurrents.
Comment partager un script pandas avec un collègue qui n’a pas Python ?
Plusieurs options : déployer le script sur un serveur avec une planification automatique qui envoie le résultat par email, créer une mini-interface Streamlit (streamlit.io) consultable dans un navigateur, ou empaqueter en exécutable autonome avec PyInstaller. Streamlit est le compromis le plus accessible.
Quelle version de pandas en 2026 ?
pandas 2.x avec backend pyarrow est la version moderne recommandée. Plus rapide que pandas 1.x, gestion des types plus stricte. Migrer un projet pandas 1.x vers 2.x peut nécessiter quelques ajustements mineurs mais pas de refonte.
polars peut-il remplacer pandas ?
polars (pola.rs) est plus rapide, plus strict sur les types, et a une API plus moderne. Pour un nouveau projet sans contrainte d’écosystème, c’est un excellent choix. Pour un projet existant ou un projet qui s’intègre fortement avec d’autres bibliothèques (matplotlib, scikit-learn), pandas reste plus mature et mieux supporté.
Comment déboguer un script pandas qui produit des résultats faux ?
Réflexes : df.shape à chaque étape pour vérifier la taille, df.head() pour voir le contenu, df.dtypes pour les types, df.isna().sum() pour les valeurs manquantes. Faire chaque transformation séparément et vérifier les intermédiaires. Les notebooks Jupyter sont parfaits pour ce mode exploratoire.
Articles liés (cluster Python pour PME)
- 👉 Python pour PME : guide pratique data, scripting, automatisation (pillar)
- 👉 Python pour scripting admin système
- 👉 Python mini-API avec FastAPI
Article mis à jour le 25 avril 2026. Pour signaler une erreur ou suggérer une amélioration, écrivez-nous.