Polars 2.0 est disponible en version candidate depuis le 2 septembre 2026 — et ce n'est pas une sortie de fonctionnalités, c'est un changement de moteur. Le streaming devient le comportement par défaut, l'ordre des lignes n'est plus garanti, et plusieurs corrections silencieuses se transforment en exceptions. La même année, pandas 3.0 a imposé le copy-on-write et un nouveau type texte. Voici ce qui casse, ce qui se remplace, et comment décider si la migration vaut le coup pour votre projet.
Deux versions majeures la même année, sur les deux moteurs qui font tourner l'analyse de données en Python : c'est assez rare pour qu'on s'y arrête. pandas 3.0 a déjà bousculé des habitudes — les pièges liés à pandas 3.0 sont documentés sur ce blog — et Polars 2.0 en prépare d'autres, de nature différente. Ce guide ne remplace pas la documentation officielle : il en extrait ce qui vous fera perdre une après-midi, et ce qui ne vous concerne pas.
Pourquoi 2026 est une année charnière
Les deux bibliothèques ont changé de version majeure, mais pas pour les mêmes raisons. pandas 3.0 a modifié des comportements : le copy-on-write devient la règle, l'écriture chaînée échoue, et les colonnes texte abandonnent le dtype object pour un dtype str [5]. Polars 2.0 change d'exécution : la même requête peut désormais produire un résultat identique en contenu, mais dans un ordre différent [1].
| pandas 3.0 | Polars 2.0 | |
|---|---|---|
| Nature du changement | Sémantique (copy-on-write, dtype str) | Moteur d'exécution (streaming par défaut) |
| Ce qui casse le plus souvent | Écritures en place, tests sur dtype == object | Code qui suppose un ordre de lignes |
| Version candidate ? | Non, 3.0 est sortie | Oui : 2.0rc1 |
| Prérequis | Python ≥ 3.11, NumPy ≥ 1.26 | — |
Cette différence de nature explique pourquoi les deux migrations ne se préparent pas de la même façon. Celle de pandas se joue sur des motifs de code à réécrire ; celle de Polars se joue sur des hypothèses implicites à rendre explicites.
Conseil de pro — Avant de migrer quoi que ce soit, cherchez dans votre code toutes les endroits où vous ne triez pas explicitement après un
joinou ungroup_by. C'est là que se logera le bug, et il sera silencieux.
Le vrai changement : le moteur streaming devient le défaut
C'est l'essentiel de la version, et il tient en une phrase : toutes les requêtes LazyFrame s'exécutent désormais sur le moteur streaming, et collect l'utilise par défaut [1].
Pourquoi cela justifie-t-il une version majeure plutôt qu'une mineure ? Parce que le moteur streaming ne garantit pas l'ordre des lignes, pour des opérations comme join, group_by et unpivot [1]. Or beaucoup de code — et beaucoup de tests — suppose implicitement que la première ligne reste la première ligne. C'est exactement le genre d'hypothèse qui ne se voit pas jusqu'au jour où elle casse.
L'auteur de l'annonce, Ritchie Vink, assume ce choix et en fixe l'ambition : « We don't aim to make a big feature release of Polars 2.0 » — l'objectif affiché est que cette montée de version soit une expérience ennuyeuse. Le contraste avec pandas 3.0 est frappant : ici, la rupture est un effet de bord assumé du changement de moteur, pas une liste de nouveautés.
Reprendre le contrôle de l'ordre
Trois leviers existent, du plus fin au plus global.
import polars as pl
# 1. Ponctuel : demander explicitement un ordre observable sur cette requête.
# "left" conserve l'ordre du DataFrame de gauche.
resultat = lf.join(autre, on="id", how="left").collect(maintain_order="left")
# 2. Ponctuel : revenir au moteur en mémoire pour cette seule requête.
resultat = lf.group_by("categorie").agg(pl.len()).collect(engine="in-memory")
# 3. Global : rétablir le comportement de Polars 1.x pour tout le processus.
# À poser une seule fois, en début de programme.
pl.Config.set_engine_affinity("in-memory")
Le troisième levier est celui qui règle un problème en une ligne, mais c'est aussi celui qui annule tout le bénéfice du changement : vous payez la version 2.0 sans en obtenir le moteur. À réserver au déblocage d'urgence, le temps de reprendre le code proprement.
| Comportement | Polars 1.x | Polars 2.0 (défaut) |
|---|---|---|
Moteur de collect() | En mémoire | Streaming |
Résolution de engine="auto" | En mémoire | Streaming |
Ordre des lignes après join, group_by, unpivot | Garanti | Non garanti sauf maintain_order |
| Empreinte mémoire attendue | — | Plus faible sur les gros volumes |
Exemple concret — Un script qui joint une table de commandes à une table clients, puis écrit un CSV dont le premier lot doit être le plus récent, peut très bien fonctionner en 1.44 et produire un fichier valide mais mal ordonné en 2.0. Aucune exception, aucun avertissement : juste un résultat différent.
Moins de corrections silencieuses, plus d'erreurs
Le second axe de la version 2.0 est une philosophie : mieux vaut une erreur franche qu'une donnée légèrement fausse. La documentation officielle la résume par l'idée de fail fast, et la justifie notamment par le développement assisté par IA — un schéma vérifié tôt évite d'entraîner un modèle pendant une heure sur des données mal typées [1].
Le cas is_in, ou l'erreur à 253
Voici l'exemple le plus parlant, parce qu'il produisait un faux positif silencieux. Auparavant, is_in convertissait les deux côtés vers un type commun même quand cette conversion perdait de l'information.
import polars as pl
# Un entier au-delà de 2**53 (9 007 199 254 740 992), la limite
# au-delà de laquelle un float64 ne représente plus les entiers exactement.
valeur = 9_007_199_254_740_993
liste = pl.Series([float(valeur)]) # le même nombre, en float64
# Avant 2.0 : conversion silencieuse, et le test renvoyait True à tort.
serie = pl.Series([valeur])
serie.is_in(liste)
# Depuis 2.0 : l'opération échoue explicitement.
# InvalidOperationError: 'is_in' cannot check for Int64 values in List(Float64) data.
# La correction : caster soi-même, en connaissance de cause.
serie.cast(pl.Float64).is_in(liste)
Le point important n'est pas que le test échoue : c'est qu'il échouait en ayant l'air de fonctionner. Un entier arrondi au float le plus proche passait pour présent dans la liste.
La concaténation horizontale stricte
Autre correction silencieuse supprimée : concaténer deux DataFrames de hauteurs différentes. Auparavant, le plus court était complété par des valeurs nulles — un DataFrame de 5 lignes concaténé à un de 4 en produisait un de 5, dont la dernière était à moitié vide.
# Avant 2.0 : complétait avec des null, sans rien signaler.
pl.concat([df_5_lignes, df_4_lignes], how="horizontal")
# Depuis 2.0 : erreur explicite.
# ShapeError: cannot concat dataframes with different heights in 'strict' mode
# Le complément par null devient un choix, plus un défaut.
pl.concat([df_5_lignes, df_4_lignes], how="horizontal_extend")
Les conversions supprimées
Deux familles de cast() disparaissent au profit de méthodes dédiées, au nom de la règle « une seule façon évidente de parser des données » [1].
# Entiers vers catégories, et retour.
serie.cat.to(pl.Enum(["a", "b", "c"])) # au lieu de cast(pl.Enum(...))
serie.cat.physical() # au lieu de cast(pl.UInt32)
# Chaînes vers dates.
serie.str.to_date("%Y-%m-%d") # au lieu de cast(pl.Date)
Le gain n'est pas cosmétique : .str.to_date() accepte un format explicite, là où cast(pl.Date) devinait. Deviner fonctionne jusqu'au jour où la source change de format sans prévenir.
Les API supprimées, et ce qui les remplace
La plupart des suppressions concernent des éléments dépréciés depuis longtemps — mais deux exceptions typées ont été ajoutées pour rendre l'erreur lisible : AttributeRemovedError (attribut ou méthode retiré) et ArgumentRemovedError (paramètre retiré) [1]. Le message ne dit pas seulement que ça casse : il indique la nouvelle API.
# AttributeRemovedError, avec la marche à suivre dans le message :
lf.melt(id_vars="a", value_vars="b")
# -> utiliser LazyFrame.unpivot, avec index= au lieu de id_vars
# et on= au lieu de value_vars.
# ArgumentRemovedError :
df.join(df2, on="a", join_nulls=True)
# -> join_nulls a été déprécié en 1.24 puis retiré en 2.0,
# renommé nulls_equal.
| Avant 2.0 | Depuis 2.0 |
|---|---|
melt(id_vars=…, value_vars=…) | unpivot(index=…, on=…) |
join(join_nulls=True) | join(nulls_equal=True) |
cast(pl.Enum(…)) / cast(pl.UInt32) | .cat.to(…) / .cat.physical() |
cast(pl.Date) sur du texte | .str.to_date() / .str.to_datetime() |
| Concat horizontale complétée par des null | how="horizontal_extend" (explicite) |
L'annonce précise que cette liste est un extrait : le guide de migration complet est publié à part [2], et à consulter avant de s'engager. L'auteur invite explicitement à signaler ce dont vous avez encore besoin, ce qui sous-entend qu'une partie des suppressions peut encore bouger.
Conseil de pro — Traitez le guide de migration comme une checklist, pas comme une lecture. Passez-y chaque nom d'API que votre projet utilise : c'est plus rapide que de déboguer les erreurs une par une.
Les performances : ce qui est mesuré, et par qui
Les chiffres qui suivent viennent tous des notes de version de Polars. Aucun n'a été mesuré sur ce blog, et je prends soin de distinguer deux catégories : les mesures publiées avec leur protocole, et les ordres de grandeur annoncés.
Les chiffres mesurés
| Version | Amélioration | Gain publié | Protocole annoncé |
|---|---|---|---|
| 1.43 | Jointures sur données partitionnées Hive | 2,00× | 16 partitions, 1 M lignes par partition et par côté, médiane de 5 exécutions, Apple M4 Pro [4] |
| 1.43 | rolling_min_by / max_by | 1,27× | 2 000 000 lignes, fenêtre d'environ 200 000 [4] |
| 1.44 | when/then/otherwise | 6,10× | 5 000 000 lignes, extraction par expression régulière, 5 % de sélectivité [3] |
| 1.44 | filter | 1,63× | DataFrame de 40 colonnes, masque de 500 fragments [3] |
Deux réserves à assumer, déjà posées par les notes elles-mêmes. La première : le gain de 2,00× sur les jointures Hive est décrit comme un meilleur cas, puisque chaque paire de partitions y trouve des correspondances [4]. La seconde : ces mesures tournent sur un Apple M4 Pro, pas sur le serveur qui exécutera votre pipeline [4].
Un autre chiffre de la 1.44 mérite l'attention pour une raison différente : sur le même cas when/then, le gain tombe à 1,53× à 50 % de sélectivité et devient nul à 100 % [3]. Autrement dit, l'optimisation profite aux requêtes filtrantes et à rien d'autre — c'est le genre de détail qui décide si une montée de version vaut le coup pour votre charge.
Le chiffre annoncé
Reste l'argument principal du moteur streaming : Polars annonce un gain agrégé de l'ordre de 5× par rapport au moteur en mémoire [1]. Ce chiffre n'est assorti d'aucun protocole dans l'annonce — il renvoie à une page de benchmarks séparée. C'est un ordre de grandeur annoncé par l'éditeur, pas une mesure reproductible publiée dans le billet. À traiter comme tel.
Astuce — Face à un « 5× » constructeur, la question utile n'est pas « est-ce vrai ? » mais « sur quelle forme de requête ? ». Le 6,10× vs rien du tout sur
when/thenmontre que l'écart se joue entièrement sur la sélectivité.
Faut-il migrer depuis pandas ?
La question mérite d'être posée précisément, parce que « migrer » recouvre trois choses très différentes : passer de Polars 1.x à 2.0, passer de pandas à Polars, ou faire cohabiter les deux. Seule la première est urgente, et seulement si vous dépendez de l'ordre des lignes.
| Votre situation | Ce que je recommande |
|---|---|
| Vous êtes sur Polars 1.x, vos tests passent | Migrer tôt, sur une branche. Le coût est faible tant que le code est jeune. |
| Vous êtes sur pandas 2.x et tout va bien | Rien ne presse. pandas 3.0 est une vraie rupture : traitez-la comme un projet à part. |
| Vos données ne tiennent plus en mémoire | C'est le cas d'usage du moteur streaming. Polars est le bon outil, et 2.0 le rend meilleur. |
| Votre code dépend de l'ordre des lignes | Migrer, mais en ajoutant maintain_order partout où c'est nécessaire — pas en désactivant le streaming. |
| Vous vivez dans l'écosystème pandas (statsmodels, scikit-learn, visualisation) | Faire cohabiter. L'interopérabilité existe dans les deux sens, et rien n'oblige à trancher. |
| Vous démarrez un projet neuf en 2026 | Évaluer Polars sérieusement, en sachant que 2.0 est encore en version candidate. |
Sur ce dernier point, une réserve : 2.0 n'est pas encore sortie. Ce qui circule depuis le 2 septembre 2026 est une version candidate, et l'annonce indique que la version définitive arrivera dans les semaines suivantes [1]. Installer une RC en production est un choix, pas une évidence — mais la tester maintenant, c'est arriver préparé·e.
Conseil de pro — Ne migrez pas « pandas vers Polars » et « Polars 1.x vers 2.0 » en même temps. Deux ruptures simultanées rendent impossible l'attribution d'une régression à l'une ou à l'autre.
Migrer un projet, concrètement
L'ordre des opérations compte plus que les outils. Quatre étapes suffisent à rendre la migration réversible.
1. Figer l'environnement avant de toucher au code
Une migration sans environnement reproductible n'est pas une migration, c'est une expérience dont on ne peut pas reproduire les résultats. Déclarez la version épinglée et les dépendances dans un fichier de projet, comme pour n'importe quel chantier Python — la partie outillage de mon article sur les erreurs fréquentes détaille cette étape.
# Installer la version candidate dans un environnement jetable.
uv add polars==2.0rc1
# La version définitive, une fois publiée :
# uv add polars
# Verrouiller : la migration doit être rejouable à l'identique.
uv lock
2. Chercher les hypothèses d'ordre
Avant même d'installer la 2.0, passez votre code au crible : tout join, tout group_by, tout unpivot dont le résultat est consommé sans tri explicite est un candidat. C'est là que se concentre le risque réel de cette montée de version.
3. Tester, puis migrer les API retirées
Lancez la suite de tests sur la version candidate et laissez les nouvelles exceptions faire le travail : AttributeRemovedError et ArgumentRemovedError pointent directement la ligne et le remplacement [1]. Complétez avec le guide de migration officiel pour ce que les tests ne couvrent pas [2].
4. Ne pas désactiver le streaming par confort
Le réflexe le plus tentant est de poser pl.Config.set_engine_affinity("in-memory") en tête de programme et de considérer la migration terminée. C'est efficace, et c'est une dette : vous restez sur la version 2.0 en vous privant de sa raison d'être, et le jour où le levier sera retiré, le travail sera à refaire.
Les notes de version listent d'ailleurs ce qui reste à venir pour la 2.x : un vrai support hors mémoire pour le moteur streaming, un nouveau design de plugins d'entrée-sortie, un planificateur de coûts avec réordonnancement des jointures, et la suppression du mmap au profit de pipelines entièrement asynchrones [1]. Autrement dit, 2.0 est un point de départ, pas un point d'arrivée.
Ce que ce guide ne dit pas
Une réserve à assumer, comme pour tout ce qui touche à une version qui n'est pas encore sortie.
- Aucun chiffre n'est mesuré par ce blog. Les gains cités viennent des notes de version de l'éditeur [3] [4], et le « 5× » du moteur streaming [1] n'est assorti d'aucun protocole.
- 2.0 est une version candidate. Le comportement décrit ici peut encore changer avant la sortie définitive ; l'annonce invite d'ailleurs à signaler les retraits problématiques [1].
- Aucune migration réelle n'a été menée pour écrire cet article. Les exemples de code illustrent les changements documentés ; ils n'ont pas été exécutés sur un projet existant.
- Le volet pandas 3.0 est résumé, pas traité. Pour les pièges de cette version, voir le détail déjà publié sur ce blog et les notes officielles [5].
Conclusion
Polars 2.0 ne se résume pas à une liste de nouveautés, et c'est ce qui la rend intéressante : c'est une version qui déplace une hypothèse implicite — l'ordre des lignes — vers une décision explicite. Les changements d'API se corrigent en une après-midi avec les nouvelles exceptions. Les hypothèses d'ordre, elles, demandent de relire son code.
Quant à la question de départ : faut-il migrer depuis pandas ? Pas nécessairement, et pas maintenant. Le bon signal n'est pas la sortie d'une version, c'est le moment où vos données cessent de tenir en mémoire ou où votre pipeline passe plus de temps à déplacer des fichiers qu'à les analyser. À ce moment-là, Polars sera là — et la 2.0 aura cessé d'être une candidate.
Ressources pour apprendre
- Formations vidéo : Data Science avec Python et Apprendre la data science avec R, sur Udemy.
- Livre : Python pour la Data Science, aux éditions ENI.
- Pour choisir son langage : mon comparatif Python vs R en data science, chiffres datés et sourcés à l'appui.
- Pour structurer son apprentissage : la roadmap data science, du niveau débutant au niveau professionnel.
- Pour la suite : les notes de version officielles de Polars sur GitHub [6], et la documentation de uv [7] pour figer vos environnements avant de migrer.
Questions fréquentes
Qu'est-ce que Polars 2.0 ?
Une nouvelle version majeure de Polars, la bibliothèque d'analyse de données en Python qui s'appuie sur Apache Arrow. Son changement principal n'est pas une fonctionnalité mais un moteur : toutes les requêtes LazyFrame s'exécutent désormais sur le moteur streaming, et collect() l'utilise par défaut. L'annonce précise que l'objectif n'est pas une grosse sortie de nouveautés : « We don't aim to make a big feature release of Polars 2.0 ».
Polars 2.0 est-il sorti, ou s'agit-il d'une version candidate ?
Il s'agit d'une version candidate (2.0rc1), publiée le 2 septembre 2026 par Ritchie Vink. L'annonce indique que la version définitive arrivera dans les semaines suivantes. Installer une version candidate en production reste un choix : le comportement décrit ici peut encore évoluer avant la sortie finale, et l'auteur invite explicitement à signaler les suppressions d'API qui poseraient problème.
Pourquoi Polars 2.0 change-t-il l'ordre des lignes ?
Parce que le moteur streaming devient le moteur par défaut, et qu'il ne garantit pas l'ordre des lignes pour les opérations comme join, group_by ou unpivot. C'est la raison pour laquelle cette évolution a nécessité une version majeure et non mineure : beaucoup de code suppose implicitement que la première ligne reste la première. Sans tri explicite après l'opération, le résultat peut être correct en contenu mais différent en ordre.
Comment garder le comportement de Polars 1.x ?
Trois leviers existent. Le plus fin est maintain_order sur la requête concernée, par exemple collect(maintain_order="left") ; collect(engine="in-memory") force le moteur en mémoire pour une seule requête. Le plus global est pl.Config.set_engine_affinity("in-memory"), qui rétablit le comportement de la version 1.x pour tout le processus. Ce dernier est à réserver au déblocage d'urgence : il annule le bénéfice de la version 2.0.
Qu'est-ce que AttributeRemovedError ?
Une exception introduite avec Polars 2.0 pour rendre lisibles les API supprimées. AttributeRemovedError signale une méthode ou un attribut retiré, ArgumentRemovedError un paramètre retiré. Le message ne se contente pas de constater l'échec : il indique le remplacement. Par exemple, lf.melt(id_vars="a", value_vars="b") renvoie vers LazyFrame.unpivot, avec index= au lieu de id_vars et on= au lieu de value_vars.
Faut-il migrer de pandas à Polars en 2026 ?
Pas par principe, et pas à cause d'une date de sortie. Le bon signal est technique : le jour où vos données cessent de tenir en mémoire, ou où votre pipeline passe plus de temps à déplacer des fichiers qu'à les analyser, Polars devient le bon outil. Si votre écosystème est pandas — scikit-learn, statsmodels, visualisation — rien n'oblige à trancher : l'interopérabilité fonctionne dans les deux sens.
Polars remplace-t-il pandas ?
Non, et la question se pose rarement en ces termes. Les deux bibliothèques ont des forces différentes : pandas reste la référence de l'écosystème scientifique Python et de l'analyse en mémoire sur des volumes modestes, Polars vise les pipelines larges et le traitement hors mémoire. En 2026, les deux ont d'ailleurs connu une version majeure — pandas 3.0 et Polars 2.0 — ce qui fait de la cohabitation un choix plus courant que du remplacement.
Combien de temps prend une migration vers Polars 2.0 ?
Les changements d'API se corrigent vite : les nouvelles exceptions AttributeRemovedError et ArgumentRemovedError indiquent la ligne fautive et son remplacement, et le guide de migration officiel liste les cas restants. Le vrai travail est ailleurs — relire le code qui dépend implicitement de l'ordre des lignes. C'est cette partie, et non la mise à jour des appels, qui détermine la durée d'une migration.
Sources
- Pre-release of Polars 2.0Polars (Ritchie Vink)
Billet du 2 septembre 2026 annonçant la version candidate 2.0rc1 : passage du moteur streaming en défaut, absence de garantie d'ordre pour join/group_by/unpivot et leviers maintain_order, collect(engine="in-memory") et pl.Config.set_engine_affinity. Documente aussi le durcissement de is_in (l'exemple Int64 au-delà de 2^53), la concaténation horizontale stricte, la suppression des cast vers Enum/Categorical et vers les types temporels, les exceptions AttributeRemovedError et ArgumentRemovedError, le renommage join_nulls vers nulls_equal, le gain agrégé « 5x » annoncé pour le moteur streaming, et la feuille de route 2.x.
- Upgrade to Polars 2Polars
Guide de migration officiel vers la version 2, cité par le billet d'annonce comme la référence complète : l'annonce ne présente qu'une sélection de changements et renvoie ici pour le reste.
- Announcing Polars 1.44Polars (Thijs Nieuwdorp)
Notes de version du 26 août 2026 : gain de 6,10x sur when/then/otherwise à 5 % de sélectivité (1,53x à 50 %, inchangé à 100 %) mesuré sur 5 000 000 de lignes, 1,63x sur filter avec un DataFrame de 40 colonnes et un masque de 500 fragments, subqueries corrélées en SQL, et limiteur de débit adaptatif pour les entrées-sorties cloud.
- Announcing Polars 1.43Polars (Thijs Nieuwdorp)
Notes de version du 23 juillet 2026 : gain de 2,00x sur les jointures de données partitionnées Hive (16 partitions, 1 M lignes par partition et par côté, médiane de 5 exécutions sur Apple M4 Pro, décrit comme un meilleur cas) et 1,27x sur rolling_min_by / rolling_max_by (2 000 000 de lignes, fenêtre d'environ 200 000).
- What's new in 3.0.0 — pandas documentationpandas
Notes de version officielles de pandas 3.0, pour le volet comparatif : copy-on-write activé par défaut, écriture chaînée qui échoue, nouveau dtype str pour les colonnes texte, et prérequis Python ≥ 3.11 et NumPy ≥ 1.26. Sert à situer la double montée de version majeure de 2026, pas à documenter Polars.
- Releases — pola-rs/polarsGitHub
Page des publications du dépôt officiel, où sont attachés les journaux de modification complets de chaque version, cités par les billets 1.43 et 1.44.
- uv — documentation officielleAstral
Documentation de l'outil de gestion d'environnements et de dépendances Python utilisé dans la section « Migrer un projet » pour épingler la version candidate et verrouiller l'environnement avant migration.
Chiffres et affirmations vérifiés sur ces sources le .



