La préversion de Polars 2.0, une bibliothèque DataFrame ultra-rapide écrite en Rust qui permet de manipuler des données structurées, est disponible pour Python, R et NodeJS
La version Release Candidate de Polars 2.0, la bibliothèque DataFrame destinée à la manipulation de données structurées, est désormais disponible pour Python, R et Node.js. Cette mise à jour définit le moteur de streaming comme le moteur par défaut pour toutes les requêtes LazyFrame, ce qui promet un gain de performances d'environ cinq fois supérieur et des améliorations significatives en termes de consommation de mémoire pour la plupart des utilisateurs. Ce changement majeur de version s'accomp
La version Release Candidate de Polars 2.0, la bibliothèque DataFrame destinée à la manipulation de données structurées, est désormais disponible pour Python, R et Node.js. Cette mise à jour définit le moteur de streaming comme le moteur par défaut pour toutes les requêtes LazyFrame, ce qui promet un gain de performances d'environ cinq fois supérieur et des améliorations significatives en termes de consommation de mémoire pour la plupart des utilisateurs. Ce changement majeur de version s'accompagne également d'un comportement par défaut plus strict : les coercitions de types avec perte dans is_in, les concaténations horizontales de longueurs différentes et les conversions de types ambiguës génèrent désormais des erreurs au lieu de produire silencieusement des résultats erronés. Enfin, les API supprimées génèrent de nouvelles exceptions indiquant leurs remplacements.
L'objectif de Polars est de fournir une bibliothèque DataFrame ultra-rapide qui exploite tous les cœurs de processeur disponibles sur une machine, tout en optimisant les requêtes pour réduire au minimum les opérations superflues et les allocations de mémoire. La bibliothèque est également capable de gérer des ensembles de données dont la taille dépasse la mémoire vive (RAM) disponible du système, et elle fournit une API cohérente et prévisible. De plus, Polars suit un schéma strict qui exige que les types de données soient connus avant l’exécution des requêtes.
Polars est écrit en Rust, ce qui lui confère les performances du C/C++ et lui permet de contrôler entièrement les éléments critiques pour les performances d'un moteur de requêtes.
Un exemple d'utilisation de la bibliothèque Polars avec Python est fourni ci-après :
1 | import polars as pl
q = (
pl.scan_csv("docs/assets/data/iris.csv")
.filter(pl.col("sepal_length") > 5)
.group_by("species")
.agg(pl.all().sum())
)
df = q.collect() |
Le 2 septembre 2026, l'équipe de Polars a annoncé la première version Release Candidate de Polars 2.0, la version finale 2.0 devant être publiée dans les semaines à venir. L'équipe a précisé que Polars 2.0 n'est pas destiné à être une mise à jour riche en fonctionnalités. Cette version vise surtout à supprimer certaines contraintes de conception existantes et à améliorer les paramètres par défaut afin de proposer des réglages plus judicieux profitant à un public plus large.
« Le principal changement par défaut réside dans le fait que toutes les requêtes LazyFrame s'exécuteront désormais sur le moteur de streaming. Les utilisateurs occasionnels de Polars peuvent donc s'attendre à des améliorations considérables en termes d'utilisation de la mémoire et de performances. Globalement, nous prévoyons que le moteur de streaming sera facilement cinq fois plus rapide », a indiqué Ritchie Vink, confondateur du projet Polars, dans un communiqué.
Le moteur de streaming devient le moteur par défaut
Il s'agit du changement le plus significatif de la version 2.0. L'appel de la méthode collect sur un LazyFrame s'appuiera désormais par défaut sur le moteur de streaming, ce qui se traduira par des améliorations considérables en termes de mémoire et de performances pour la plupart des requêtes des utilisateurs. La raison pour laquelle cela a nécessité un changement de version majeur est que le moteur de streaming ne garantit pas par défaut l'ordre des lignes pour certaines opérations comme join, group_by, unpivot, etc.. Si un utilisateur a besoin d’un ordre observable dans ces opérations, il peut activer cette fonctionnalité en définissant maintain_order=True.
Les utilisateurs qui souhaitent continuer à utiliser l'ancien moteur "en mémoire" par défaut peuvent le faire en configurant l'affinité du moteur : pl.Config.set_engine_affinity("in-memory") pour un réglage global du processus et .collect(engine="in-memory") pour un réglage par requête.
1 | lf = pl.LazyFrame({"k": [2, 1, 0], "v": ["a", "b", "c"]})
other = pl.LazyFrame({"k": [0, 1, 2], "r": ["x", "y", "z"]})
# 2.0: engine="auto" now resolves to the streaming engine.
# Row order is no longer guaranteed for joins, group_by, unpivot, ...
(
lf
.join(other, on="k", how="left")
.collect()
)
# ┌─────┬─────┬─────┐
# │ k ┆ v ┆ r │ <- order may not match `lf`'s original row order
# └─────┴─────┴─────┘
# Opt in to observable order for this query:
(
lf
.join(other, on="k", how="left", maintain_order="left")
.collect()
)
# Or keep the old in-memory engine as the default, process-wide:
pl.Config.set_engine_affinity("in-memory")
# ...or per query:
(
lf
.join(other, on="k", how="left")
.collect(engine="in-memory")
) |
Polars plus rigoureux
Polars vise à être plus rigoureux et renforce son approche de fail-fast. Idéalement, les erreurs devraient être signalées dès le début, et non pas au bout de 20 minutes dans un pipeline. Le comportement implicite en cas de non-correspondance des données devrait être activable, et non pas par défaut, car ces incohérences peuvent masquer des bogues. Cette rigueur est devenue encore plus précieuse avec l’essor du développement piloté par l’IA. Les agents IA peuvent valider la structure d’une requête dès le début en appelant la fonction collect_schema(), qui résout les types et détecte les incohérences au niveau du schéma sans matérialiser aucune donnée. Cela garantit un retour d’information rapide aux agents, ce qui leur permet d’itérer plus rapidement. Toutes les erreurs ne peuvent pas être détectées lors de la compilation du plan de requête, certaines dépendant des données. Dans ces cas-là, Polars adopte par défaut un comportement plus strict afin de s’assurer que les incohérences soient détectées, plutôt que de produire silencieusement des résultats différents.
Voici quelques exemples illustrant les domaines dans lesquels Polars est devenu plus strict :
Coercition de type sans perte dans is_in
Si vous exécutez une expression is_in sur des types de données différents, Polars avait pour habitude de convertir les deux types en leur supertype commun, même si cette conversion entraînait une perte d'informations. Vous trouverez ci-dessous un exemple avec des identifiants d'utilisateurs qui peut poser problème en raison d'incompatibilités silencieuses entre les types de données.
1 | # Checking if a user ID matches a list of "flagged" account IDs # (flagged_ids loaded from a JSON export, where large IDs became floats) flagged_ids = pl.Series([9007199254740992.0]) user_id = pl.Series([9007199254740993]) # Int64 -> a different ID, off by 1 user_id.is_in(flagged_ids) |
Avant la version 2.0, user_id était converti en Float64 pour correspondre à flagged_ids. Mais 9007199254740993 est supérieur à 2^53 (9007199254740992), le plus grand entier que le type Float64 puisse représenter exactement ; il est donc arrondi silencieusement à la baisse vers 9007199254740992.0, ce qui génère un faux positif.
Dans la version 2.0, cela génère l'erreur suivante : InvalidOperationError: 'is_in' cannot check for Int64 values in List(Float64) data., les utilisateurs doivent effectuer un transtypage explicite pour gérer la conversion de type avec perte.
Concaténation stricte
La concaténation horizontale vérifie désormais les longueurs au lieu de remplir silencieusement avec des null.
1 | # Joining per-day transaction counts with per-day fraud-flag counts,
transactions = pl.DataFrame({"day": [1, 2, 3, 4, 5], "count": [120, 98, 143, 87, 156]})
# Upstream job for day 5 failed silently
fraud_flags = pl.DataFrame({"flagged": [2, 0, 5, 1]}) # only 4 rows
pl.concat([transactions, fraud_flags], how="horizontal") |
1 | shape: (5, 2) ┌─────┬───────┬─────────┐ │ day ┆ count ┆ flagged │ │ 1 ┆ 120 ┆ 2 │ │ 2 ┆ 98 ┆ 0 │ │ 3 ┆ 143 ┆ 5 │ │ 4 ┆ 87 ┆ 1 │ │ 5 ┆ 156 ┆ null │ <- day 5 silently has no flag count └─────┴───────┴─────────┘ |
Dans la version 2.0, cela provoquera l'erreur ShapeError suivante :
ShapeError: cannot concat dataframes with different heights in 'strict' mode
Si le remplissage est intentionnel, vous devez l'indiquer explicitement en utilisant l'attribut how="horizontal_extend". Cela permet de signaler clairement cette intention dans le code.
Suppression des conversions de type ambiguës au profit de méthodes/constructeurs dédiés
Il convient également de mentionner la suppression de nombreux cast qui étaient ambigus ou qui devaient être appliqués via leur expression d'analyse dédiée, ce qui permet désormais d'analyser les données d'une manière univoque.
Le cast entre entier et Enum/Categorical a été supprimé : pour convertir un entier en Categorical, il faut utilisez .cat.to(dtype), et pour passer d'un Categorical à un entier utilisez .cat.physical()....
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.