Introduction
En 2026, Polars s'impose comme l'outil incontournable pour le traitement de données à grande échelle en Python, surpassant Pandas par sa vitesse (jusqu'à 30x plus rapide sur des datasets >1M lignes) grâce à son cœur en Rust et son exécution multithread. Contrairement à Pandas qui utilise un seul thread et des copies mémoire coûteuses, Polars optimise les queries via un système d'expressions vectorisées et une évaluation paresseuse (lazy), idéale pour les pipelines ETL ou l'analyse exploratoire sur des téraoctets.
Ce tutoriel intermediate vous guide pas à pas : de la création de DataFrames à des opérations avancées comme les joins multi-tables et les agrégations window, avec des exemples concrets sur un dataset de ventes e-commerce (10k lignes). Vous apprendrez à éviter les pièges de performance et à scaler vers du lazy scanning de Parquet/CSV. À la fin, vous manipulerez Polars comme un pro, prêt pour des projets réels en data engineering.
Prérequis
- Python 3.10 ou supérieur installé
- pip à jour (
python -m pip install --upgrade pip) - Connaissances de base en Pandas et DataFrames
- Un éditeur comme VS Code avec extension Python
- Dataset test : téléchargez
ventes.csv(exemple ci-dessous) ou créez-le via code
Installation de Polars
pip install polars
pip install pyarrow # Pour Parquet/Arrow support
# Vérification
python -c "import polars as pl; print(pl.__version__)"Cette commande installe Polars et PyArrow pour une compatibilité optimale avec les formats columnar comme Parquet. Évitez pip install pandas car Polars est indépendant ; la vérification confirme l'installation sans conflits.
Créer et explorer un DataFrame
Commençons par générer un DataFrame réaliste simulant des ventes e-commerce : 10k lignes avec colonnes produit, categorie, quantite, prix_unitaire, date_vente. Utilisez pl.DataFrame() pour une création rapide, puis explorez avec describe(), head() et shape. Polars gère nativement les types (Utf8, Int64, Float64, Datetime), inférant mieux que Pandas pour éviter les conversions coûteuses.
Génération et inspection du DataFrame
import polars as pl
import numpy as np
from datetime import datetime, timedelta
# Génération de données réalistes (10k lignes)
np.random.seed(42)
n_rows = 10000
dates = [datetime(2026, 1, 1) + timedelta(days=i//100) for i in range(n_rows)]
produits = np.random.choice(['Laptop', 'Souris', 'Clavier', 'Ecran'], n_rows)
categories = np.random.choice(['Informatique', 'Peripherique'], n_rows)
quantites = np.random.randint(1, 10, n_rows)
prix = np.random.uniform(10, 2000, n_rows)
# Création DataFrame
df = pl.DataFrame({
'produit': produits,
'categorie': categories,
'quantite': quantites,
'prix_unitaire': prix,
'date_vente': dates
})
# Inspection
print(df.head())
print(df.shape)
print(df.describe())Ce script crée un DataFrame complet avec 10k lignes, infère les types automatiquement (Datetime pour dates). describe() donne stats précises (min/max/mean/std) sans boucle, contrairement à Pandas. Attention : utilisez numpy pour génération rapide, mais Polars excelle sur données existantes.
Filtrage et sélection avec expressions
Les expressions Polars (pl.col()) sont la clé de la performance : vectorisées et typées, elles évitent les masques booléens lents de Pandas. Filtrez sur conditions multiples, sélectionnez/transformez colonnes en une passe. Analogie : comme SQL mais en mémoire, avec optimisation query planner.
Filtrage avancé et transformations
import polars as pl
# DataFrame du script précédent (df)
# Filtrage multi-conditions : ventes >100€ en Janvier 2026, catégorie Informatique
filtres = (
(pl.col('prix_unitaire') * pl.col('quantite') > 100) &
(pl.col('date_vente').dt.month() == 1) &
(pl.col('categorie') == 'Informatique')
)
df_filtre = df.filter(filtres)
# Sélection + transformation : CA total par ligne, top 5 produits
resultat = df_filtre.select([
pl.col('produit'),
pl.col('quantite') * pl.col('prix_unitaire').alias('ca_ligne'),
pl.col('date_vente').dt.strftime('%Y-%m').alias('mois')
]).sort('ca_ligne', descending=True).head(5)
print(resultat)Les &/| opèrent sur lazy expressions, évaluées en une fois pour zéro copie. .alias() renomme sans overhead ; .dt pour dates. Piège : n'utilisez pas df['col'] == val (crée Series lente), toujours pl.col().
GroupBy et agrégations window
Polars excelle en groupby grâce à son hashmap optimisé (plus rapide que Pandas sur >100k lignes). Ajoutez des window functions pour rankings ou cumsums par groupe, comme over() pour partitions dynamiques. Exemple : CA par catégorie/mois, avec top produits.
GroupBy avec agrégations et windows
import polars as pl
# Sur df_filtre du script précédent
# GroupBy basique : CA total par catégorie et mois
grouped = df_filtre.group_by([
pl.col('categorie'),
pl.col('date_vente').dt.month().alias('mois')
]).agg([
pl.col('quantite') * pl.col('prix_unitaire').sum().alias('ca_total'),
pl.col('quantite').sum().alias('qte_totale')
])
# Avec window : rank des produits par CA dans chaque catégorie
def avec_window(df):
return df.with_columns([
(pl.col('quantite') * pl.col('prix_unitaire')).rank(
method='dense', descending=True
).over('categorie').alias('rank_ca')
]).filter(pl.col('rank_ca') <= 3)
window_result = avec_window(df_filtre)
print(grouped)
print(window_result.head()).agg() applique multiples fonctions en parallèle ; .over() partitionne comme SQL window sans shuffle coûteux. method='dense' évite gaps dans ranks. Évitez apply() (lent) : préférez expressions natives.
Joins efficaces entre DataFrames
Polars supporte tous types de joins (inner, left, etc.) avec index hash ultra-rapide. Pour datasets asymétriques, utilisez join() sur expressions. Exemple : joindre un DF clients à ventes pour enrichir.
Joins multi-colonnes
import polars as pl
# DF ventes (df)
# Nouveau DF clients (simulé)
clients = pl.DataFrame({
'produit': ['Laptop', 'Souris', 'Clavier'],
'fournisseur': ['Dell', 'Logitech', 'Logitech'],
'marge': [0.25, 0.15, 0.20]
})
# Left join sur produit + filtre post-join
joined = df.join(
clients,
on='produit',
how='left'
).with_columns([
(pl.col('quantite') * pl.col('prix_unitaire') * pl.col('marge')).alias('benefice')
]).filter(
pl.col('benefice').is_not_null()
).group_by('fournisseur').agg(
pl.col('benefice').sum()
)
print(joined)Join sur string exact (hashé) ; how='left' garde toutes ventes. Post-join, calculez sans recopie. Piège : sur gros datasets, trinez avant join (sort()) pour optimiser.
Évaluation lazy pour scalabilité
Lazy mode : planifiez une query complète, optimisée par le query planner (fusions pushdown/down). Idéal pour >1GB : scannez CSV/Parquet sans charger tout en RAM. Sauvegardez en Parquet pour 10x compression.
Pipeline lazy complet
import polars as pl
# Sauvegarde df en Parquet (pour test)
df.write_parquet('ventes.parquet')
# Lazy scan + full pipeline
lazy_df = (
pl.scan_parquet('ventes.parquet')
.filter(
(pl.col('prix_unitaire') * pl.col('quantite') > 100) &
(pl.col('categorie') == 'Informatique')
)
.group_by('produit')
.agg(pl.col('quantite').sum().alias('qte_totale'))
.sort('qte_totale', descending=True)
.head(10)
)
# Collecte (exécution)
result_lazy = lazy_df.collect()
print(result_lazy)
# Export
result_lazy.write_csv('top_produits.csv').scan_parquet() lit metadata-only ; planner fusionne filtre/group/sort. .collect() exécute tout d'un coup, minimisant RAM. Pour 2026 : utilisez sur S3 via pl.scan_parquet('s3://...').
Bonnes pratiques
- Toujours lazy pour >100k lignes :
.collect()seulement à la fin. - Utilisez expressions pl.col() au lieu d'indexation df['col'] (10x plus lent).
- Parquet first : compression + predicate pushdown pour scans.
- Profilez avec
.explain()sur lazy pour optimiser plans. - Batch en streaming pour datasets >RAM :
.sink_parquet().
Erreurs courantes à éviter
- Charger tout en eager mode sur big data : OOM crash ; passez lazy.
- apply() ou map_elements() : lent (Python UDF), remplacez par vectorisées.
- Joins sans sort préalable sur non-hashables : fallback lent à nested loop.
- Ignorer types : forcez
pl.Utf8oupl.Datetimepour éviter inférences erronées.
Pour aller plus loin
- Docs officielles : pola.rs
- User Guide avancé : expressions conditionnelles, SQL context (
df.sql()) - Perf benchmarks vs Pandas/DuckDB
- Formations Learni Dev : Data Engineering avec Polars & Rust.
- GitHub repo Polars pour contribs.