Polars
Une bibliothèque de DataFrames écrite en Rust, pensée pour être beaucoup plus rapide que pandas tout en gardant une API familière. Multi-threadée par défaut, capable de traiter des fichiers plus gros que la RAM grâce au mode lazy.
À quoi ça sert
Polars résout le problème classique de pandas : dès qu'un fichier dépasse quelques millions de lignes, pandas devient lent et consomme énormément de mémoire (tout est chargé d'un coup, sur un seul cœur CPU). Polars fait le même genre d'opérations — charger, filtrer, grouper, joindre — mais en utilisant tous les cœurs de la machine et un moteur optimisé écrit en Rust.
Polars apporte aussi un mode lazy : tu décris ce que tu veux faire, et Polars planifie l'exécution complète avant de la lancer. Résultat : il peut supprimer les colonnes inutiles, réordonner les opérations, et même traiter un fichier qui ne tient pas en RAM (streaming).
- API expressions — au lieu de
df["col"] * 2, on écritpl.col("col") * 2. Plus verbeux, mais Polars peut optimiser et paralléliser. - Lazy vs Eager —
scan_csvne charge rien tant que tu n'appelles pas.collect().read_csvcharge tout de suite, comme pandas. - Lecture rapide — Parquet, CSV, JSON, Arrow, Excel.
- Compatible Arrow — partage la mémoire avec DuckDB, PySpark, etc. sans copie.
pandas reste l'écosystème le plus mature (intégré partout : scikit-learn, matplotlib, etc.) et suffit pour des fichiers de quelques Go. Polars est imbattable sur une seule machine quand les données font 1 à 100 Go : souvent 5 à 30× plus rapide. PySpark ne devient utile que sur un vrai cluster, quand les données dépassent ce qu'une seule grosse machine peut gérer.
Un exemple d'usage
Même cas que la fiche pandas — un CSV de ventes — pour pouvoir comparer. On veut les 10 produits qui rapportent le plus :
import polars as pl
# Lecture lazy : rien n'est chargé tout de suite
totaux = (
pl.scan_csv("ventes.csv")
.filter(pl.col("montant") > 100)
.group_by("produit")
.agg(pl.col("montant").sum().alias("total"))
.sort("total", descending=True)
.head(10)
.collect() # déclenche l'exécution
)
print(totaux)
Tout est chaîné dans une seule expression lisible de haut en bas. Polars regarde
le plan complet avant d'exécuter : il sait qu'on n'a besoin que des colonnes
produit et montant, donc il ignore les autres dès la
lecture du CSV. C'est ce qu'on appelle predicate pushdown et
projection pushdown.
How-to : installer et utiliser Polars
-
Installer Polars dans ton projet
Comme pour pandas, Polars est une dépendance principale (cf. UV) :
bashuv add polarsSi tu veux la lecture Excel ou le format Delta, des extras sont disponibles :
bashuv add "polars[excel,deltalake]" -
Charger un fichier (eager vs lazy)
Deux familles de fonctions selon que tu veux exécuter tout de suite (
read_*) ou différer (scan_*) :pythonimport polars as pl # Eager : renvoie un DataFrame, lit tout en RAM df = pl.read_csv("data.csv") df = pl.read_parquet("data.parquet") df = pl.read_excel("data.xlsx") # Lazy : renvoie un LazyFrame, ne lit rien tant que .collect() lf = pl.scan_csv("data.csv") lf = pl.scan_parquet("data.parquet")Quand utiliser lazy ?À peu près tout le temps en pipeline de données. L'eager est utile pour de l'exploration rapide en Jupyter, quand on veut voir le résultat ligne par ligne. En production (script ETL, batch), préfère le lazy : moins de mémoire, plus rapide, et passe à l'échelle.
-
Explorer un DataFrame
pythondf.head() # 5 premières lignes df.tail(10) # 10 dernières df.shape # (nb_lignes, nb_colonnes) df.columns # noms des colonnes df.dtypes # types Polars (Int64, Utf8, Float64…) df.describe() # stats sur les colonnes numériques df.null_count() # nombre de null par colonne df.schema # dict {colonne: type} -
Sélectionner et filtrer avec des expressions
C'est la différence syntaxique majeure avec pandas. En Polars, on manipule des expressions (
pl.col(...)) à l'intérieur de méthodes commeselect,filter,with_columns:python# Sélectionner des colonnes df.select(["produit", "montant"]) df.select(pl.col("^.*_id$")) # par regex # Filtrer (équivalent du df[df["x"] > 0] de pandas) df.filter(pl.col("montant") > 100) # Conditions multiples — pas besoin de parenthèses pénibles df.filter( (pl.col("montant") > 100) & (pl.col("produit") == "A") ) -
Créer et modifier des colonnes
Polars ne modifie jamais un DataFrame en place : chaque opération renvoie un nouveau DataFrame.
with_columnssert à ajouter ou remplacer des colonnes.python# Ajouter une colonne calculée df = df.with_columns( (pl.col("montant") * 1.20).alias("ttc") ) # Plusieurs colonnes d'un coup df = df.with_columns([ (pl.col("montant") * 1.20).alias("ttc"), pl.col("produit").str.to_uppercase().alias("produit_maj"), ]) # Renommer df = df.rename({"montant": "montant_ht"}) # Supprimer df = df.drop("colonne_inutile") -
Grouper et agréger (group_by)
La syntaxe est plus uniforme qu'en pandas : on passe toujours des expressions dans
agg().python# Total des ventes par produit df.group_by("produit").agg( pl.col("montant").sum() ) # Plusieurs agrégations en même temps df.group_by("produit").agg([ pl.col("montant").sum().alias("total"), pl.col("montant").mean().alias("moyenne"), pl.col("montant").count().alias("nb_ventes"), ]) # Grouper par plusieurs colonnes df.group_by(["region", "produit"]).agg( pl.col("montant").sum() ) -
Gérer les valeurs manquantes (null)
Polars utilise
null(et pasNaNcomme pandas) pour les valeurs manquantes — c'est plus propre, surtout pour les chaînes et les dates.python# Compter les nulls df.null_count() # Supprimer les lignes avec des null df.drop_nulls() # Remplir les nulls df.with_columns( pl.col("montant").fill_null(0) ) # Remplir par la moyenne de la colonne df.with_columns( pl.col("age").fill_null(pl.col("age").mean()) ) -
Sauvegarder le résultat
pythondf.write_csv("resultat.csv") df.write_parquet("resultat.parquet") df.write_json("resultat.json")Note la convention
write_*(et pasto_*comme pandas) — un petit piège pour qui jongle entre les deux. À chaque doute : lecture =read_ouscan_, écriture =write_.
Aide-mémoire
pl.read_csv("f.csv", separator=";")
pl.read_parquet("f.parquet")
pl.scan_csv("f.csv") # lazy
df.write_csv("out.csv")
df.head(), df.tail(), df.shape, df.columns, df.dtypes
df.describe(), df.null_count(), df.schema
df["col"].value_counts()
df["col"].unique()
df.sort("col", descending=True)
df.filter(pl.col("x") > 0)
df.with_columns((pl.col("a") + pl.col("b")).alias("c"))
df.group_by("cat").agg(pl.col("val").sum())
df.join(other, on="id", how="left")
pl.concat([df1, df2])
df.pivot(index="r", on="c", values="v")
df.with_columns(
pl.col("date").str.to_date("%Y-%m-%d")
)
df.with_columns([
pl.col("date").dt.year().alias("annee"),
pl.col("date").dt.month().alias("mois"),
])
lf = pl.scan_parquet("big.parquet")
result = (
lf.filter(pl.col("x") > 0)
.group_by("cat")
.agg(pl.col("val").sum())
.collect() # exécute le plan
)
# Inspecter le plan optimisé sans l'exécuter
print(lf.explain())
Polars et le reste de l'écosystème
Polars se branche naturellement aux outils que tu utilises déjà — il est conçu pour cohabiter avec eux, pas pour les remplacer en bloc.
-
pandas — conversion immédiate
dans les deux sens :
df.to_pandas()etpl.from_pandas(df_pd). Utile quand une lib (scikit-learn, matplotlib) n'accepte que des DataFrames pandas. - PySpark — même philosophie de plan logique optimisé et d'opérations lazy, mais Polars tient sur une machine. La règle d'usage : Polars jusqu'à ~100 Go sur une grosse VM, PySpark au-delà sur un cluster.
- Jupyter — l'affichage par défaut d'un DataFrame Polars est plus compact que pandas, avec les types affichés en en-tête. Idéal pour l'exploration.
-
MinIO — Polars lit directement
depuis S3/MinIO via
scan_parquet("s3://bucket/file.parquet", storage_options={...}). Pas besoin de télécharger d'abord. - MLflow — pour l'entraînement, on convertit souvent en pandas juste avant de passer le dataset à scikit-learn. Polars sert au pipeline de préparation (où la rapidité compte), pandas à l'interface modèle.
Quand on migre du code pandas, on a tendance à écrire
df["col"] et à enchaîner les modifications en place.
Polars refuse : il faut passer par des expressions
(pl.col("col")) à l'intérieur de
select/filter/with_columns. C'est plus verbeux au début, mais
c'est ce qui permet à Polars d'optimiser et de paralléliser.
Pour aller plus loin
- Site officiel : pola.rs
- User Guide complet : docs.pola.rs/user-guide
- Référence Python : docs.pola.rs/api/python
- Modern Polars (comparatif pandas) : kevinheavey.github.io/modern-polars