Cours Pandas Complet 🐼
Les 12 chapitres essentiels — DataFrame, nettoyage, agrégation, visualisation et analyse
Introduction et installation
pip install pandas
# Import conventionnel
import pandas as pd
import numpy as np
# Lire un fichier CSV
df = pd.read_csv('data.csv')
# Aperçu rapide
df.head() # 5 premières lignes
df.tail(3) # 3 dernières lignes
df.shape # (lignes, colonnes)
df.dtypes # Types de chaque colonne
df.info() # Résumé complet (types, null, mémoire)
df.describe() # Statistiques (mean, std, min, max, quartiles)
df.columns # Noms des colonnes
df.nunique() # Valeurs uniques par colonne
df.sample(5) # 5 lignes aléatoires
df = pd.read_csv('data.csv', sep=';', encoding='utf-8')
df.to_csv('output.csv', index=False)
# Excel
df = pd.read_excel('data.xlsx', sheet_name='Feuille1')
df.to_excel('output.xlsx', index=False)
# JSON
df = pd.read_json('data.json')
df.to_json('output.json', orient='records')
# Parquet (rapide, compressé — recommandé pour gros fichiers)
df = pd.read_parquet('data.parquet')
df.to_parquet('output.parquet')
# SQL
import sqlite3
conn = sqlite3.connect('db.sqlite')
df = pd.read_sql('SELECT * FROM users', conn)
Series et DataFrame
s = pd.Series([10, 20, 30], name='prix')
s = pd.Series()
# DataFrame — 2 dimensions (comme un tableur)
df = pd.DataFrame()
# Accéder à une colonne → retourne une Series
df['nom']
df.nom # Notation point (si pas d'espaces/caractères spéciaux)
# Accéder à plusieurs colonnes → retourne un DataFrame
df[['nom', 'salaire']]
# Ajouter une colonne
df['bonus'] = df['salaire'] * 0.1
# Supprimer une colonne
df = df.drop(columns=['bonus'])
Sélection et filtrage
df.loc[0] # Ligne 0 (par label d'index)
df.loc[0, 'nom'] # Cellule (ligne 0, colonne 'nom')
df.loc[0:2, ['nom', 'age']] # Lignes 0-2, colonnes nom+age
# iloc — par position (index numérique)
df.iloc[0] # Première ligne
df.iloc[0:3, 0:2] # 3 premières lignes, 2 premières colonnes
df.iloc[–1] # Dernière ligne
# Filtrage booléen — le plus utilisé
df[df['age'] > 25] # Age > 25
df[df['ville'] == 'Paris'] # Ville = Paris
df[df['ville'].isin(['Paris', 'Lyon'])] # Paris ou Lyon
df[df['nom'].str.contains('li')] # Nom contient « li »
# Conditions multiples (& = ET, | = OU, ~ = NOT)
df[(df['age'] > 25) & (df['salaire'] > 47000)]
df[(df['ville'] == 'Paris') | (df['age'] < 27)]
# query() — syntaxe plus lisible
df.query('age > 25 and salaire > 47000')
Toujours des parenthèses autour de chaque condition avec & et |. Sans parenthèses, Python évalue dans le mauvais ordre : df[df['age'] > 25 & df['salaire'] > 47000] → erreur. Correct : df[(df['age'] > 25) & (df['salaire'] > 47000)].
Nettoyage des données
df.isna().sum() # Nombre de NaN par colonne
df.isna().sum().sum() # Total de NaN
# Supprimer les lignes avec NaN
df.dropna() # Toute ligne avec au moins 1 NaN
df.dropna(subset=['email']) # Seulement si NaN dans 'email'
# Remplir les NaN
df['age'].fillna(df['age'].median()) # Par la médiane
df['ville'].fillna('Inconnu') # Par une valeur fixe
df['prix'].fillna(method='ffill') # Forward fill (dernière valeur connue)
# Doublons
df.duplicated().sum() # Nombre de doublons
df.drop_duplicates() # Supprimer les doublons
df.drop_duplicates(subset=['email'], keep='last')
# Types
df['age'] = df['age'].astype(int)
df['prix'] = pd.to_numeric(df['prix'], errors='coerce') # Invalides → NaN
# Texte
df['nom'] = df['nom'].str.strip() # Espaces en trop
df['nom'] = df['nom'].str.lower() # Minuscules
df['email'] = df['email'].str.replace(' ', ») # Supprimer espaces
Transformation
df['salaire_net'] = df['salaire'].apply(lambda x: x * 0.75)
# apply sur une ligne entière (axis=1)
df['label'] = df.apply(
lambda row: f » () », axis=1
)
# map — remplacer des valeurs
df['niveau'] = df['age'].map()
# assign — créer des colonnes de manière chaînable
df = (
df
.assign(bonus=lambda x: x['salaire'] * 0.1)
.assign(total=lambda x: x['salaire'] + x['bonus'])
)
# rename — renommer des colonnes
df = df.rename(columns=)
# sort
df.sort_values('salaire', ascending=False)
df.sort_values(['ville', 'salaire'], ascending=[True, False])
# Catégoriser avec cut / qcut
df['tranche_age'] = pd.cut(df['age'], bins=[0, 25, 35, 100],
labels=['jeune', 'moyen', 'senior'])
df['quartile'] = pd.qcut(df['salaire'], q=4, labels=['Q1','Q2','Q3','Q4'])
GroupBy et agrégation
df.groupby('ville')['salaire'].mean() # Salaire moyen par ville
df.groupby('ville')['salaire'].sum() # Somme par ville
df.groupby('ville').size() # Nombre par ville
# Plusieurs agrégations avec agg()
df.groupby('ville')['salaire'].agg(['mean', 'median', 'min', 'max', 'count'])
# Agrégations différentes par colonne
df.groupby('ville').agg()
# Grouper par plusieurs colonnes
df.groupby(['ville', 'tranche_age'])['salaire'].mean()
# value_counts — compteur rapide
df['ville'].value_counts() # Nombre par valeur
df['ville'].value_counts(normalize=True) # Pourcentages
GroupBy suit le pattern split-apply-combine : séparer les données en groupes (split), appliquer une fonction à chaque groupe (apply), recombiner les résultats (combine). C'est l'équivalent de GROUP BY en SQL.
Dates et séries temporelles
df['date'] = pd.to_datetime(df['date'])
df['date'] = pd.to_datetime(df['date'], format='%d/%m/%Y')
# Extraire des composants
df['date'].dt.year # Année
df['date'].dt.month # Mois
df['date'].dt.day # Jour
df['date'].dt.day_name() # « Monday », « Tuesday »…
df['date'].dt.quarter # Trimestre
# Filtrer par date
df[df['date'] > '2024-01-01']
df[df['date'].between('2024-01-01', '2024-12-31')]
# Resample — agréger par période (nécessite index datetime)
df.set_index('date').resample('M')['ventes'].sum() # Par mois
df.set_index('date').resample('W')['ventes'].mean() # Par semaine
df.set_index('date').resample('Q')['ventes'].sum() # Par trimestre
# Rolling — moyenne mobile
df['ma_7j'] = df['ventes'].rolling(window=7).mean()
Jointures et merge
users = pd.DataFrame()
orders = pd.DataFrame()
# Inner join (intersection)
pd.merge(users, orders, on='user_id')
# Left join (garder tous les users)
pd.merge(users, orders, on='user_id', how='left')
# Right join / Outer join
pd.merge(users, orders, on='user_id', how='right')
pd.merge(users, orders, on='user_id', how='outer')
# Colonnes différentes
pd.merge(df1, df2, left_on='id_client', right_on='customer_id')
# concat — empiler des DataFrames
pd.concat([df1, df2]) # Vertical (lignes)
pd.concat([df1, df2], ignore_index=True) # Reset index
pd.concat([df1, df2], axis=1) # Horizontal (colonnes)
| Type | SQL | Pandas |
|---|---|---|
| Intersection | INNER JOIN | how='inner' (défaut) |
| Garder la gauche | LEFT JOIN | how='left' |
| Garder la droite | RIGHT JOIN | how='right' |
| Tout garder | FULL OUTER JOIN | how='outer' |
Visualisation
# Pandas intègre matplotlib directement
df['salaire'].plot(kind='hist', bins=20, title='Distribution salaires')
plt.show()
# Types de graphiques
df.plot(kind='line', x='date', y='ventes') # Ligne
df.plot(kind='bar', x='ville', y='salaire') # Barres
df.plot(kind='scatter', x='age', y='salaire') # Nuage de points
df.plot(kind='box') # Box plot
df['ville'].value_counts().plot(kind='pie') # Camembert
# GroupBy + plot
df.groupby('ville')['salaire'].mean().plot(kind='barh')
Pour des visualisations plus poussées, utilisez Seaborn (basé sur matplotlib, plus beau par défaut) ou Plotly (graphiques interactifs). Pandas .plot() est parfait pour l'exploration rapide.
Pivot et reshape
pd.pivot_table(
df,
values='salaire',
index='ville',
columns='tranche_age',
aggfunc='mean',
fill_value=0,
)
# crosstab — tableau de fréquences
pd.crosstab(df['ville'], df['tranche_age'])
# melt — wide → long (inverse de pivot)
# Avant: nom | jan | fev | mar
# Après: nom | mois | ventes
df_long = pd.melt(
df,
id_vars=['nom'],
value_vars=['jan', 'fev', 'mar'],
var_name='mois',
value_name='ventes',
)
# stack / unstack
df.stack() # Colonnes → lignes
df.unstack() # Lignes → colonnes
Performance
df['age'] = df['age'].astype('int8') # int64 → int8 (si < 128)
df['prix'] = df['prix'].astype('float32') # float64 → float32
df['ville'] = df['ville'].astype('category') # String → category
# 2. Lire seulement les colonnes nécessaires
df = pd.read_csv('big.csv', usecols=['nom', 'salaire'])
# 3. Parquet au lieu de CSV (5-10x plus rapide, compressé)
df.to_parquet('data.parquet')
df = pd.read_parquet('data.parquet')
# 4. Vectoriser au lieu de boucles
# ❌ Lent
for i in range(len(df)):
df.loc[i, 'bonus'] = df.loc[i, 'salaire'] * 0.1
# ✅ Rapide (vectorisé)
df['bonus'] = df['salaire'] * 0.1
# 5. Lire par chunks (très gros fichiers)
for chunk in pd.read_csv('huge.csv', chunksize=10000):
process(chunk)
Pour les très gros datasets (> 1 Go), considérez Polars (syntaxe similaire à Pandas, 10-100x plus rapide, écrit en Rust) ou DuckDB (SQL analytique en mémoire). Pandas reste le standard pour les datasets qui tiennent en mémoire.
Bonnes pratiques
| Outil | Usage |
|---|---|
| Pandas | Manipulation de données tabulaires |
| NumPy | Calcul numérique (arrays, algèbre linéaire) |
| Matplotlib | Visualisation de base |
| Seaborn | Visualisation statistique (beau par défaut) |
| Plotly | Graphiques interactifs |
| Scikit-learn | Machine learning |
| Polars | Alternative rapide à Pandas (Rust) |
| Jupyter Notebook | Exploration interactive |
✅ À FAIRE
• Toujours df.info() + df.describe() d'abord
• Opérations vectorisées (pas de boucles)
• query() pour les filtres complexes
• category dtype pour les colonnes répétitives
• Parquet pour le stockage
• .copy() pour éviter les SettingWithCopy
• Method chaining (assign, query, sort)
• usecols pour les gros CSV
• Jupyter pour l'exploration
❌ À ÉVITER
• Boucles for sur les lignes d'un DataFrame
• iterrows() (très lent)
• Modifier un DataFrame pendant une itération
• Ignorer les types (tout en object/float64)
• CSV pour les gros fichiers (utiliser Parquet)
• df['col'] = … sur une copie (SettingWithCopy)
• inplace=True (chaîner est mieux)
• Ignorer les NaN avant l'analyse
• Tout charger en mémoire (utiliser chunks)
Cours Pandas Complet — DataFrame, nettoyage, agrégation et visualisation
Référence : pandas.pydata.org | Polars
