Bienvenue dans ce module où tu vas découvrir Polars, la bibliothèque DataFrame ultra-rapide qui révolutionne le traitement de données en Python. Tu apprendras pourquoi Polars surpasse Pandas, comment exploiter son moteur d’exécution lazy, et comment construire des pipelines ETL performants !
Prérequis
Niveau
Compétence
✅ Requis
Connaissances de base en Python
✅ Requis
Avoir utilisé Pandas (même basiquement)
💡 Recommandé
Avoir suivi les modules précédents du bootcamp
Objectifs du module
À la fin de ce module, tu seras capable de :
Comprendre pourquoi Polars est 5-100x plus rapide que Pandas
Maîtriser l’architecture columnar et le format Apache Arrow
Utiliser les expressions Polars pour des transformations efficaces
Exploiter l’exécution Lazy pour des pipelines optimisés
Migrer du code Pandas vers Polars
Construire des pipelines ETL performants en production
1. Polars vs Pandas : Pourquoi changer ?
Avant de plonger dans Polars, comprenons pourquoi cette bibliothèque existe et ce qu’elle apporte.
1.1 Les limitations de Pandas
Pandas est formidable pour l’exploration de données, mais il a des limitations structurelles :
Limitation
Explication
Impact
Single-threaded
Le GIL Python bloque le parallélisme
N’utilise qu’1 CPU
Row-based en mémoire
Données stockées par ligne
Cache CPU inefficace
Eager execution
Chaque opération s’exécute immédiatement
Pas d’optimisation globale
Copies fréquentes
Beaucoup d’opérations copient les données
RAM x2 ou x3
Mémoire gourmande
~5-10x la taille du fichier
Limite les gros datasets
1.2 Les forces de Polars
Aspect
Pandas
Polars
Backend
NumPy (C)
Rust 🦀
Threading
Single (GIL)
Multi-threaded
Mémoire
Row-based
Columnar (Arrow)
Execution
Eager only
Eager + Lazy
Vitesse
Baseline
5-100x plus rapide
Out-of-core
❌
✅ (streaming)
Optimiseur
❌
✅ Query planner
💡 En résumé : Polars est conçu dès le départ pour la performance et les gros volumes, là où Pandas a été conçu pour l’exploration interactive.
ℹ️ Le savais-tu ?
Polars a été créé en 2020 par Ritchie Vink, un ingénieur néerlandais frustré par la lenteur de Pandas.
Le nom “Polars” fait référence à l’ours polaire (🐻❄️) — un clin d’œil à Pandas (🐼) tout en étant plus rapide et adapté aux environnements “froids” (haute performance).
Polars est écrit en Rust, un langage réputé pour sa vitesse et sa sécurité mémoire.
import polars as plimport time# Benchmark Polars (Eager)start = time.time()df_polars = pl.read_csv("data/benchmark.csv")result_polars = ( df_polars .group_by("category") .agg( pl.col("amount").sum().alias("amount_sum"), pl.col("quantity").mean().alias("quantity_mean") ))polars_time = time.time() - startprint(f"🐻❄️ Polars : {polars_time:.3f} secondes")print(f"⚡ Polars est {pandas_time/polars_time:.1f}x plus rapide !")print(result_polars)
Voir le code
# Benchmark Polars (Lazy) - encore plus rapide !start = time.time()result_lazy = ( pl.scan_csv("data/benchmark.csv") # Lazy ! .group_by("category") .agg( pl.col("amount").sum().alias("amount_sum"), pl.col("quantity").mean().alias("quantity_mean") ) .collect() # Exécution optimisée)lazy_time = time.time() - startprint(f"🚀 Polars Lazy : {lazy_time:.3f} secondes")print(f"⚡ Polars Lazy est {pandas_time/lazy_time:.1f}x plus rapide que Pandas !")
2. Comprendre l’architecture de Polars
Pour bien utiliser Polars, il faut comprendre pourquoi il est si rapide.
2.1 Columnar vs Row-based
ROW-BASED (Pandas) COLUMNAR (Polars)
══════════════════ ═════════════════
┌─────┬──────┬─────┐ ┌───────────────────┐
│ id │ name │ age │ │ id: [1, 2, 3] │
├─────┼──────┼─────┤ ├───────────────────┤
│ 1 │ Ana │ 25 │ ← Ligne 1 │ name: [A, B, C] │
├─────┼──────┼─────┤ ├───────────────────┤
│ 2 │ Bob │ 30 │ ← Ligne 2 │ age: [25, 30, 22]│
├─────┼──────┼─────┤ └───────────────────┘
│ 3 │ Cat │ 22 │ ← Ligne 3 ↑
└─────┴──────┴─────┘ Colonnes contiguës
↑ en mémoire
Lignes contiguës
en mémoire
Pourquoi columnar est plus rapide ?
Avantage
Explication
Cache CPU
Données contiguës = moins de cache misses
SIMD
Opérations vectorisées sur colonnes entières
Compression
Colonnes homogènes = meilleure compression
Sélection
Lire seulement les colonnes nécessaires
2.2 Apache Arrow : le format sous-jacent
Polars utilise Apache Arrow comme format mémoire :
Avantage
Description
Zero-copy
Partage de données sans copie
Interopérabilité
Compatible Spark, DuckDB, PyArrow
Standardisé
Format ouvert et documenté
2.3 Eager vs Lazy execution
Mode
Description
Quand l’utiliser
Eager
Exécute immédiatement chaque opération
Exploration, debug, petits datasets
Lazy
Construit un plan, optimise, puis exécute
Production, gros fichiers, pipelines
# Eager : résultat immédiatdf = pl.read_csv("data.csv") # Lit maintenantdf = df.filter(pl.col("x") >5) # Filtre maintenant# Lazy : plan d'exécutionlf = pl.scan_csv("data.csv") # Crée un planlf = lf.filter(pl.col("x") >5) # Ajoute au plandf = lf.collect() # Exécute tout (optimisé)
2.4 Query Optimizer
Le Query Optimizer de Polars applique automatiquement des optimisations :
Optimisation
Description
Predicate pushdown
Filtres appliqués le plus tôt possible
Projection pruning
Colonnes inutiles jamais lues
Common subexpression
Calculs redondants factorisés
Parallelization
Opérations distribuées sur tous les CPUs
PLAN ORIGINAL PLAN OPTIMISÉ
═════════════ ═════════════
scan_csv(all cols) scan_csv(only needed cols)
│ │
▼ ▼
with_columns(...) filter(amount > 100) ← Pushdown!
│ │
▼ ▼
filter(amount > 100) with_columns(...)
│ │
▼ ▼
result result
3. Installation & Configuration
Installation
# Installation de basepip install polars# Avec toutes les features (recommandé)pip install 'polars[all]'# Features spécifiquespip install 'polars[pyarrow,pandas,numpy,fsspec]'
Vérification
Voir le code
import polars as plprint(f"✅ Polars version : {pl.__version__}")# Configuration de l'affichagepl.Config.set_tbl_rows(10) # Lignes affichéespl.Config.set_tbl_cols(12) # Colonnes affichéespl.Config.set_fmt_str_lengths(50) # Longueur des strings# Voir le nombre de threads utilisésprint(f"🔧 Threads disponibles : {pl.thread_pool_size()}")
4. Charger & Exporter des données
4.1 Formats supportés
Format
Read (Eager)
Scan (Lazy)
Write
CSV
read_csv()
scan_csv()
write_csv()
Parquet
read_parquet()
scan_parquet()
write_parquet()
JSON
read_json()
scan_ndjson()
write_json()
Excel
read_excel()
❌
write_excel()
Database
read_database()
❌
❌
IPC/Feather
read_ipc()
scan_ipc()
write_ipc()
4.2 Lecture Eager vs Lazy
Voir le code
import polars as pl# ============ EAGER (tout en mémoire) ============df = pl.read_csv("data/benchmark.csv")print("Eager - Type:", type(df))print(df.head(3))print("\n"+"="*50+"\n")# ============ LAZY (plan d'exécution) ============lf = pl.scan_csv("data/benchmark.csv")print("Lazy - Type:", type(lf))print(lf) # Affiche le plan, pas les données
Voir le code
# Créer plusieurs fichiers pour l'exempleimport osos.makedirs("data/multi", exist_ok=True)for i inrange(3): pl.DataFrame({"id": range(i*100, (i+1)*100),"value": [i*10+ j for j inrange(100)] }).write_csv(f"data/multi/file_{i}.csv")print("✅ Fichiers créés")# Lire plusieurs fichiers avec glob patternlf = pl.scan_csv("data/multi/*.csv")print(f"\nNombre de lignes : {lf.collect().height}")
Voir le code
# Écrituredf = pl.DataFrame({"name": ["Alice", "Bob", "Charlie"],"age": [25, 30, 35],"city": ["Paris", "Lyon", "Marseille"]})# CSVdf.write_csv("data/output.csv")# Parquet (recommandé pour la production)df.write_parquet("data/output.parquet")# JSONdf.write_json("data/output.json")print("✅ Fichiers exportés")# Vérifier avec Parquetdf_parquet = pl.read_parquet("data/output.parquet")print(df_parquet)
5. Expressions Polars — Le cœur du moteur
Les expressions sont ce qui rend Polars si puissant. C’est un changement de paradigme par rapport à Pandas.
🎯 C’est ce qui rend Polars adapté à la production et aux gros volumes.
7.1 Créer un LazyFrame
Voir le code
# Depuis un fichier (recommandé)lf = pl.scan_csv("data/benchmark.csv")print("Type:", type(lf))print("\nLazyFrame (pas encore exécuté) :")print(lf)
Voir le code
# Depuis un DataFrame existantdf = pl.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})lf = df.lazy()print("Converti en LazyFrame:", type(lf))
7.2 Construire le pipeline
Voir le code
# Pipeline complet en Lazypipeline = ( pl.scan_csv("data/benchmark.csv") .filter(pl.col("amount") >100) .with_columns( (pl.col("amount") * pl.col("quantity")).alias("total"), pl.col("category").str.to_uppercase().alias("CATEGORY") ) .group_by("CATEGORY") .agg( pl.col("total").sum().alias("total_revenue"), pl.len().alias("transaction_count") ) .sort("total_revenue", descending=True))print("Pipeline défini (pas encore exécuté) :")print(pipeline)print("\n⚠️ Rien n'a été lu ou calculé !")
7.3 Exécuter avec .collect()
Voir le code
import timestart = time.time()result = pipeline.collect() # MAINTENANT ça s'exécuteprint(f"⏱️ Temps d'exécution : {time.time() - start:.3f}s")print("\nRésultat :")print(result)
7.4 Voir le plan d’exécution
Voir le code
# Plan logique (ce que tu as écrit)print("=== PLAN LOGIQUE ===")print(pipeline.explain())print("\n"+"="*60+"\n")# Plan optimisé (ce que Polars exécute réellement)print("=== PLAN OPTIMISÉ ===")print(pipeline.explain(optimized=True))
# ❌ MAUVAIS : read_csv sur plusieurs fichiers séparément# dfs = [pl.read_csv(f) for f in files] # Pas optimisé# ✅ BON : scan_csv avec globlf = pl.scan_csv("data/multi/*.csv")print("✅ Scan avec glob :")print(lf.collect())
✅ Bonnes pratiques
Pratique
Pourquoi
Utiliser Lazy en production
Optimisation automatique
Préférer Parquet
10x plus rapide que CSV, compression
Chaîner les expressions
Plus lisible, plus optimisé
Éviter .apply()
Utiliser expressions natives
Profiler avec .explain()
Comprendre l’exécution
Toujours .alias()
Noms de colonnes explicites
scan_* pour gros fichiers
Lazy = optimisations
Streaming pour > RAM
collect(streaming=True)
Quiz de fin de module
Réponds aux questions suivantes pour vérifier tes acquis.
❓ Q1. Quel est le principal avantage de l’architecture columnar de Polars ?
Plus facile à lire pour les humains
Opérations vectorisées plus rapides et meilleure utilisation du cache CPU
Compatible avec Excel
Utilise moins de colonnes
💡 Voir la réponse
✅ Réponse : b — Le stockage columnar permet des opérations vectorisées (SIMD) et une meilleure utilisation du cache CPU car les données d’une colonne sont contiguës en mémoire.
❓ Q2. Quelle est la différence entre pl.read_csv() et pl.scan_csv() ?
read_csv est plus rapide
scan_csv crée un LazyFrame et permet l’optimisation
scan_csv ne supporte pas les gros fichiers
Aucune différence
💡 Voir la réponse
✅ Réponse : b — scan_csv crée un LazyFrame (plan d’exécution) qui sera optimisé avant exécution, tandis que read_csv charge immédiatement tout en mémoire.
❓ Q3. Comment ajouter une nouvelle colonne en Polars ?
df["new"] = df["old"] * 2
df.with_columns((pl.col("old") * 2).alias("new"))
df.add_column("new", df["old"] * 2)
df.insert("new", df["old"] * 2)
💡 Voir la réponse
✅ Réponse : b — En Polars, on utilise with_columns() avec des expressions. La syntaxe df["col"] style Pandas ne fonctionne pas.
❓ Q4. Que fait le Query Optimizer avec “predicate pushdown” ?
Supprime les colonnes inutiles
Applique les filtres le plus tôt possible dans le pipeline
Parallélise les calculs
Compresse les données
💡 Voir la réponse
✅ Réponse : b — Le predicate pushdown déplace les filtres le plus tôt possible, réduisant ainsi la quantité de données à traiter dans les étapes suivantes.
❓ Q5. Quand utiliser .collect() ?
Après chaque opération
À la fin du pipeline Lazy pour déclencher l’exécution
Pour convertir en Pandas
Pour écrire un fichier
💡 Voir la réponse
✅ Réponse : b — .collect() déclenche l’exécution d’un LazyFrame et retourne un DataFrame. Sans .collect(), rien n’est calculé.
❓ Q6. Pourquoi éviter .apply() en Polars ?
Ce n’est pas supporté
C’est lent car ça passe par Python pour chaque ligne
Ça modifie les données en place
Ça consomme trop de mémoire
💡 Voir la réponse
✅ Réponse : b — .apply() (ou map_rows) passe par Python pour chaque ligne, perdant tous les avantages du moteur Rust vectorisé. Préférer les expressions natives.
❓ Q7. Quel format de fichier est recommandé en production avec Polars ?
CSV
JSON
Parquet
Excel
💡 Voir la réponse
✅ Réponse : c — Parquet est columnar (comme Polars), compressé, et supporte les types. Il est 10x+ plus rapide que CSV.
❓ Q8. Comment voir le plan d’exécution optimisé d’un LazyFrame ?
lf.show_plan()
lf.explain(optimized=True)
lf.describe()
print(lf)
💡 Voir la réponse
✅ Réponse : b — .explain(optimized=True) affiche le plan d’exécution après les optimisations du Query Optimizer.
Mini-projet : Pipeline ETL Polars
Objectif
Construire un pipeline ETL complet en mode Lazy qui : - Lit plusieurs fichiers CSV - Nettoie et transforme les données - Agrège par catégorie et période - Exporte en Parquet