0%
Pandas : Manipuler et Analyser des Données en Python

Pandas : Manipuler et Analyser des Données en Python

Guide complet de Pandas pour charger, nettoyer, filtrer, agréger et exporter des données tabulaires en Python : Series, DataFrame, groupby, merge et dates.

I

InSkillCoach

· min

Pandas : Manipuler et Analyser des Données en Python

Pandas est la bibliothèque de référence pour la manipulation de données tabulaires en Python. Construite sur NumPy, elle fournit deux structures de données essentielles, la Series et le DataFrame, ainsi qu’un arsenal complet pour charger des fichiers, nettoyer des valeurs manquantes, filtrer, agréger, joindre et exporter des données. Ce guide couvre le flux de travail complet d’une analyse : de la lecture d’un CSV jusqu’à l’export des résultats, en passant par les pièges classiques à éviter.

Series et DataFrame : les deux structures fondamentales

La Series : une colonne étiquetée

Une Series est un tableau unidimensionnel dont chaque valeur est associée à une étiquette d’index.

import pandas as pd

s = pd.Series([15, 12, 17, 9], index=["Alice", "Bob", "Chloé", "David"])
print(s)
# Alice    15
# Bob      12
# Chloé    17
# David     9
# dtype: int64

# Accès par étiquette ou par position
print(s["Chloé"])       # 17
print(s.iloc[0])        # 15

# Les opérations sont vectorisées, comme avec NumPy
print(s.mean())         # 13.25
print(s + 1)            # chaque note augmentée de 1

Le DataFrame : un tableau à deux dimensions

Un DataFrame est un ensemble de colonnes (des Series) partageant le même index de lignes.

df = pd.DataFrame({
    "nom": ["Alice", "Bob", "Chloé", "David"],
    "age": [25, 32, 28, 41],
    "ville": ["Paris", "Lyon", "Paris", "Nantes"],
    "salaire": [42000, 38000, 51000, 46000],
})
print(df)
#      nom  age   ville  salaire
# 0  Alice   25   Paris    42000
# 1    Bob   32    Lyon    38000
# 2  Chloé   28   Paris    51000
# 3  David   41  Nantes    46000

# Une colonne est une Series
print(df["age"].dtype)    # int64
print(df.shape)           # (4, 4)
print(df.columns.tolist())  # ['nom', 'age', 'ville', 'salaire']

Lecture de fichiers avec read_csv

read_csv est la fonction la plus utilisée de Pandas. Ses options couvrent la quasi-totalité des formats CSV rencontrés en pratique.

# Lecture simple
df = pd.read_csv("ventes.csv")

# Options courantes
df = pd.read_csv(
    "ventes.csv",
    sep=";",                     # séparateur (point-virgule fréquent en France)
    decimal=",",                 # virgule décimale
    encoding="utf-8",            # ou "latin-1" pour d'anciens fichiers
    usecols=["date", "produit", "montant"],   # ne charger que certaines colonnes
    dtype={"produit": "string"}, # forcer un type
    parse_dates=["date"],        # convertir en datetime dès la lecture
    na_values=["N/A", "inconnu"],  # valeurs à traiter comme manquantes
    nrows=1000,                  # limiter le nombre de lignes (utile pour explorer)
)

Pandas lit aussi de nombreux autres formats : read_excel, read_json, read_parquet, read_sql. Pour les gros volumes, le format Parquet est nettement plus rapide et plus compact que le CSV.

Inspection : head, info, describe

Les trois premières commandes à lancer sur tout nouveau jeu de données :

# Aperçu des premières lignes
print(df.head())        # 5 premières lignes (df.tail() pour les dernières)

# Structure : types, valeurs non nulles, mémoire
df.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 4 entries, 0 to 3
# Data columns (total 4 columns):
#  #   Column   Non-Null Count  Dtype
# ---  ------   --------------  -----
#  0   nom      4 non-null      object
#  1   age      4 non-null      int64
#  2   ville    4 non-null      object
#  3   salaire  4 non-null      int64

# Statistiques descriptives des colonnes numériques
print(df.describe())
#              age       salaire
# count   4.000000      4.000000
# mean   31.500000  44250.000000
# std     6.952218   5560.275773
# min    25.000000  38000.000000
# max    41.000000  51000.000000

# Comptage des valeurs distinctes d'une colonne
print(df["ville"].value_counts())
# Paris     2
# Lyon      1
# Nantes    1

Sélection avec loc et iloc

Deux accesseurs complémentaires : loc sélectionne par étiquettes, iloc par positions entières.

# loc[lignes, colonnes] par étiquettes
print(df.loc[0, "nom"])                  # Alice
print(df.loc[0:2, ["nom", "ville"]])     # lignes 0 à 2 INCLUSES, deux colonnes

# iloc[lignes, colonnes] par positions
print(df.iloc[0, 0])                     # Alice
print(df.iloc[0:2, 0:2])                 # lignes 0 et 1 (borne exclue), colonnes 0 et 1

# Toutes les lignes, une colonne
print(df.loc[:, "salaire"])

# Dernière ligne
print(df.iloc[-1])

Attention à la différence de convention : loc[0:2] inclut la borne 2 (logique d’étiquettes), alors que iloc[0:2] l’exclut (logique de positions, comme les listes Python).

Filtrage booléen

# Une condition
seniors = df[df["age"] > 30]
print(seniors)
#      nom  age   ville  salaire
# 1    Bob   32    Lyon    38000
# 3  David   41  Nantes    46000

# Conditions combinées : & (et), | (ou), ~ (non), parenthèses obligatoires
cible = df[(df["ville"] == "Paris") & (df["salaire"] >= 45000)]
print(cible)
#      nom  age  ville  salaire
# 2  Chloé   28  Paris    51000

# Appartenance à une liste
grandes_villes = df[df["ville"].isin(["Paris", "Lyon"])]

# Filtrage sur du texte
noms_a = df[df["nom"].str.startswith("A")]

# query : syntaxe alternative lisible
resultat = df.query("age > 30 and salaire < 47000")

Colonnes calculées

# Nouvelle colonne à partir des existantes
df["salaire_mensuel"] = df["salaire"] / 12

# Colonne conditionnelle avec numpy.where
import numpy as np
df["categorie"] = np.where(df["age"] < 30, "junior", "senior")

# Transformation par fonction (à réserver aux cas non vectorisables)
df["initiale"] = df["nom"].apply(lambda n: n[0])

# assign : créer des colonnes en chaînant les opérations
df2 = df.assign(
    salaire_k=df["salaire"] / 1000,
    age_en_2030=df["age"] + 4,
)
print(df2[["nom", "salaire_k", "age_en_2030"]].head(2))
#      nom  salaire_k  age_en_2030
# 0  Alice       42.0           29
# 1    Bob       38.0           36

Privilégiez toujours les opérations vectorisées (df["a"] + df["b"], np.where, .str, .dt) : apply avec une lambda est souvent 10 à 100 fois plus lent.

Valeurs manquantes : isna, fillna, dropna

df_nan = pd.DataFrame({
    "produit": ["A", "B", "C", "D"],
    "prix": [10.0, None, 25.0, None],
    "stock": [5, 3, None, 8],
})

# Détecter les valeurs manquantes
print(df_nan.isna())
print(df_nan.isna().sum())     # nombre de NaN par colonne
# produit    0
# prix       2
# stock      1

# Remplacer par une valeur fixe
df_rempli = df_nan.fillna({"prix": 0.0, "stock": 0})

# Remplacer par une statistique
df_nan["prix"] = df_nan["prix"].fillna(df_nan["prix"].mean())
print(df_nan["prix"])
# 0    10.0
# 1    17.5
# 2    25.0
# 3    17.5

# Supprimer les lignes contenant au moins un NaN
df_propre = df_nan.dropna()

# Supprimer seulement si une colonne précise est manquante
df_propre = df_nan.dropna(subset=["stock"])

Choisissez la stratégie selon le contexte : supprimer des lignes fait perdre de l’information, imputer une moyenne peut biaiser une analyse. Documentez toujours votre choix.

groupby et agrégations

Le pattern « split-apply-combine » : découper les données par groupe, appliquer un calcul, recombiner les résultats.

ventes = pd.DataFrame({
    "region": ["Nord", "Sud", "Nord", "Sud", "Nord"],
    "produit": ["A", "A", "B", "B", "A"],
    "montant": [100, 150, 200, 120, 80],
})

# Agrégation simple
print(ventes.groupby("region")["montant"].sum())
# region
# Nord    380
# Sud     270

# Plusieurs statistiques à la fois
print(ventes.groupby("region")["montant"].agg(["sum", "mean", "count"]))
#         sum        mean  count
# region
# Nord    380  126.666667      3
# Sud     270  135.000000      2

# Groupement sur plusieurs colonnes
print(ventes.groupby(["region", "produit"])["montant"].sum())
# region  produit
# Nord    A          180
#         B          200
# Sud     A          150
#         B          120

# Agrégations nommées : colonnes de sortie explicites
resume = ventes.groupby("region").agg(
    total=("montant", "sum"),
    panier_moyen=("montant", "mean"),
)
print(resume)
#         total  panier_moyen
# region
# Nord      380    126.666667
# Sud       270    135.000000

Pour transformer un résultat groupé en tableau croisé, pivot_table est souvent plus direct :

tableau = ventes.pivot_table(index="region", columns="produit",
                             values="montant", aggfunc="sum", fill_value=0)
print(tableau)
# produit    A    B
# region
# Nord     180  200
# Sud      150  120

Combiner des tables : merge et concat

merge : les jointures façon SQL

clients = pd.DataFrame({
    "client_id": [1, 2, 3],
    "nom": ["Alice", "Bob", "Chloé"],
})
commandes = pd.DataFrame({
    "client_id": [1, 1, 2, 4],
    "montant": [50, 30, 70, 20],
})

# Jointure interne : uniquement les correspondances
interne = pd.merge(clients, commandes, on="client_id", how="inner")
print(interne)
#    client_id    nom  montant
# 0          1  Alice       50
# 1          1  Alice       30
# 2          2    Bob       70

# Jointure gauche : tous les clients, même sans commande
gauche = pd.merge(clients, commandes, on="client_id", how="left")
# Chloé apparaît avec montant NaN

# how accepte aussi "right" et "outer"

concat : empiler des tables

janvier = pd.DataFrame({"produit": ["A", "B"], "ventes": [10, 20]})
fevrier = pd.DataFrame({"produit": ["A", "B"], "ventes": [15, 25]})

# Empilement vertical avec réindexation
annee = pd.concat([janvier, fevrier], ignore_index=True)
print(annee)
#   produit  ventes
# 0       A      10
# 1       B      20
# 2       A      15
# 3       B      25

Tris

# Tri sur une colonne
print(df.sort_values("age"))

# Tri décroissant sur plusieurs colonnes
print(df.sort_values(["ville", "salaire"], ascending=[True, False]))

# Tri sur l'index
print(df.sort_index())

# Les n plus grandes valeurs (plus rapide qu'un tri complet)
print(df.nlargest(2, "salaire")[["nom", "salaire"]])
#      nom  salaire
# 2  Chloé    51000
# 3  David    46000

Dates : to_datetime et resample

journal = pd.DataFrame({
    "date": ["2026-01-05", "2026-01-12", "2026-02-03", "2026-02-20", "2026-03-01"],
    "ca": [120, 80, 200, 150, 90],
})

# Conversion en datetime
journal["date"] = pd.to_datetime(journal["date"])
print(journal["date"].dtype)     # datetime64[ns]

# L'accesseur .dt expose les composantes
journal["mois"] = journal["date"].dt.month
journal["jour_semaine"] = journal["date"].dt.day_name()

# resample : agrégation temporelle (nécessite un index datetime)
serie = journal.set_index("date")["ca"]
mensuel = serie.resample("ME").sum()     # "ME" = fin de mois (pandas >= 2.2)
print(mensuel)
# date
# 2026-01-31    200
# 2026-02-28    350
# 2026-03-31     90

# Filtrer une plage de dates
recentes = journal[journal["date"] >= "2026-02-01"]

Si le format n’est pas standard, précisez-le : pd.to_datetime(s, format="%d/%m/%Y") évite les ambiguïtés jour/mois.

Export des résultats

# CSV (index=False pour ne pas écrire la colonne d'index)
df.to_csv("resultats.csv", index=False, sep=";", encoding="utf-8")

# Excel (nécessite openpyxl)
df.to_excel("resultats.xlsx", sheet_name="Analyse", index=False)

# Parquet : rapide, compact, types préservés (nécessite pyarrow)
df.to_parquet("resultats.parquet")

# JSON
df.to_json("resultats.json", orient="records", force_ascii=False)

Pièges courants : le SettingWithCopyWarning

Ce warning apparaît quand vous modifiez le résultat d’une sélection dont Pandas ne sait pas s’il s’agit d’une vue ou d’une copie.

# MAUVAIS : indexation chaînée, la modification peut être perdue
sous_ensemble = df[df["ville"] == "Paris"]
sous_ensemble["salaire"] = 0        # SettingWithCopyWarning

# BON : modifier le DataFrame original en une seule opération loc
df.loc[df["ville"] == "Paris", "salaire"] = 0

# BON : travailler sur une copie explicite et assumée
sous_ensemble = df[df["ville"] == "Paris"].copy()
sous_ensemble["salaire"] = 0        # aucun warning, df original intact

Autres pièges fréquents :

  • Comparer avec NaN : df["col"] == np.nan renvoie toujours False ; utilisez df["col"].isna().
  • Types object silencieux : une colonne de nombres contenant une chaîne devient object ; vérifiez avec df.dtypes et convertissez avec pd.to_numeric(s, errors="coerce").
  • inplace=True : déconseillé par l’équipe Pandas ; préférez la réaffectation df = df.dropna().

Bonnes pratiques

  • Inspectez systématiquement chaque nouveau jeu de données avec head, info et describe avant toute transformation.
  • Utilisez loc/iloc explicitement pour toute écriture, et bannissez l’indexation chaînée (df[...][...] = ...).
  • Vectorisez : évitez apply et les boucles quand une opération sur colonne entière existe.
  • Parsez les dates dès la lecture avec parse_dates, plutôt que de convertir après coup.
  • Nommez vos agrégations avec agg(total=("montant", "sum")) pour obtenir des colonnes de sortie claires.
  • Préférez Parquet au CSV pour les fichiers intermédiaires : plus rapide, plus compact et sans perte de types.
  • Enchaînez les transformations de façon lisible avec assign, query et pipe plutôt que de multiplier les variables intermédiaires.

Conclusion

Pandas couvre l’intégralité du cycle de vie d’une analyse de données : chargement robuste avec read_csv, exploration avec info et describe, nettoyage des valeurs manquantes, filtrage booléen, colonnes calculées, agrégations avec groupby, jointures avec merge, gestion fine des dates et export multi-formats. En comprenant la distinction entre loc et iloc et en évitant l’indexation chaînée, vous éliminez la grande majorité des bugs subtils. Les étapes suivantes naturelles sont la visualisation de vos DataFrames avec Matplotlib et l’analyse statistique avec SciPy, deux bibliothèques qui s’intègrent nativement avec Pandas.

InSkillCoach

À propos de InSkillCoach

Expert en formation et technologies

Coach spécialisé dans les technologies avancées et l'IA, porté par GNeurone Inc.

Certifications:

  • AWS Certified Solutions Architect – Professional
  • Certifications Google Cloud
  • Microsoft Certified: DevOps Engineer Expert
  • Certified Kubernetes Administrator (CKA)
  • CompTIA Security+
1.4k
201

Commentaires

Les commentaires sont alimentés par GitHub Discussions

Connectez-vous avec GitHub pour participer à la discussion

Lien copié !