Pre-release van Polars 2.0
Vandaag brengen we de eerste release candidate voor Polars 2.0 uit. De definitieve 2.0-release volgt in de komende weken. We streven er niet naar om van Polars 2.0 een grote feature-release te maken; sterker nog, we hopen dat het voor jullie een "saaie" ervaring zal zijn.
De reden dat we dit hoofdversienummer verhogen, is dat we hiermee ontwerpbeslissingen uit het verleden kunnen verwijderen die ons momenteel blokkeren. Daarnaast willen we de standaardinstellingen wijzigen naar meer zinvolle instellingen die een groter publiek ten goede komen. De belangrijkste wijziging in de standaardinstellingen is dat alle LazyFrame-queries nu op de streaming-engine zullen draaien. Incidentele Polars-gebruikers kunnen hierdoor enorme verbeteringen in geheugengebruik en prestaties verwachten. In totaal verwachten we dat de streaming-engine eenvoudigweg 5x sneller is.
Om gebruikers te helpen bij de overstap naar 2.0, hebben we een volledige migratiegids gepubliceerd. In dit bericht behandelen we enkele van de belangrijkste punten.
Streaming-engine als standaard
Dit is de wijziging met de grootste impact in versie 2.0. Het aanroepen van collect op een LazyFrame zal nu standaard gebruikmaken van de streaming-engine, wat voor de meeste gebruikers leidt tot enorme verbeteringen in geheugen en prestaties bij de meeste queries.
De reden dat dit een verhoging van de hoofdversie vereiste, is dat de streaming-engine standaard geen rijvolgorde garandeert voor bepaalde bewerkingen (zoals join, groupby, unpivot, etc.). Als u een observeerbare rijvolgorde vereist bij deze bewerkingen, kunt u dit activeren door maintainorder=True in te stellen.
Gebruikers die de "in-memory" engine als standaard willen blijven gebruiken, kunnen dit doen door de engine-affiniteit in te stellen.
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" resolveert nu naar de streaming engine.
# Rijvolgorde wordt niet langer gegarandeerd voor joins, group_by, unpivot, ...
(
lf
.join(other, on="k", how="left")
.collect()
)
# ┌─────┬─────┬─────┐
# │ k ┆ v ┆ r │ <- volgorde komt mogelijk niet overeen met de oorspronkelijke rijvolgorde van `lf`
# └─────┴─────┴─────┘
# Opt-in voor observeerbare volgorde voor deze query:
(
lf
.join(other, on="k", how="left", maintain_order="left")
.collect()
)
# Of behoud de oude in-memory engine als standaard voor het gehele proces:
pl.Config.set_engine_affinity("in-memory")
# ...of per query:
(
lf
.join(other, on="k", how="left")
.collect(engine="in-memory")
)
Een striktere Polars
Polars streeft ernaar strikt te zijn en snel fouten te melden (fail fast). Fouten zouden idealiter direct aan het begin moeten worden gemeld, en niet pas 20 minuten in een pipeline. Impliciet gedrag bij data-mismatches zou een bewuste keuze (opt-in) moeten zijn en geen standaardinstelling, aangezien dergelijke mismatches bugs kunnen verbergen.
Deze striktheid is nog waardevoller geworden met de opkomst van AI-gestuurde ontwikkeling. Agents kunnen de structuur van een query vroegtijdig valideren door collect_schema() aan te roepen. Dit lost types op en vangt mismatches op schemaniveau op zonder dat er data hoeft te worden gematerialiseerd. Dit zorgt voor snelle feedback voor de agents, waardoor ze sneller kunnen itereren.
Niet alle fouten kunnen worden gevangen tijdens de compilatie van het queryplan; sommige zijn afhankelijk van de data. In deze gevallen hanteert Polars strikter gedrag om ervoor te zorgen dat inconsistenties worden opgemerkt in plaats van dat er stilletjes verschillende resultaten worden geproduceerd.
Hieronder volgen enkele voorbeelden waar Polars strikter is geworden:
Verliesvrije type-coërcie bij is_in
Als u een is_in-expressie uitvoert op verschillende datatypes, gebruikte Polars voorheen beide types om te zetten naar hun gemeenschappelijke supertype, zelfs als die conversie verliesgevend was.
Hieronder staat een voorbeeld met gebruikers-id's waarbij het mis kan gaan door stille data-type mismatches:
# Controleren of een gebruikers-ID overeenkomt met een lijst van "gemarkeerde" account-ID's
# (flagged_ids geladen uit een JSON-export, waarbij grote ID's floats zijn geworden)
flagged_ids = pl.Series([9007199254740992.0])
user_id = pl.Series([9007199254740993]) # Int64 -> een andere ID, verschilt met 1
user_id.is_in(flagged_ids)
Vóór versie 2.0 wordt userid omgezet naar Float64 om overeen te komen met flaggedids. Omdat 9007199254740993 echter boven de $2^{53}$ (9007199254740992) ligt — het grootste gehele getal dat float64 exact kan representeren — wordt het stilletjes naar beneden afgerond naar 9007199254740992.0, wat resulteert in een fout-positieve match.
In 2.0 veroorzaakt dit: InvalidOperationError: 'is_in' cannot check for Int64 values in List(Float64) data. Gebruikers moeten nu expliciet casten om met verliesgevende typeconversies om te gaan.
Strikte concatenatie
Horizontale concatenatie (horizontal concat) controleert nu op lengte in plaats van dat er stilletjes met null wordt opgevuld.
# Het samenvoegen van transactietellingen per dag met fraudevlag-tellingen per dag,
transactions = pl.DataFrame({"day": [1, 2, 3, 4, 5], "count": [120, 98, 143, 87, 156]})
# Upstream job voor dag 5 is stilletjes mislukt
fraud_flags = pl.DataFrame({"flagged": [2, 0, 5, 1]}) # slechts 4 rijen
pl.concat([transactions, fraud_flags], how="horizontal")
# Resultaat vóór 2.0:
# shape: (5, 2)
# ┌─────┬───────┬─────────┐
# │ day ┆ count ┆ flagged │
# │ 1 ┆ 120 ┆ 2 │
# │ 2 ┆ 98 ┆ 0 │
# │ 3 ┆ 143 ┆ 5 │
# │ 4 ┆ 87 ┆ 1 │
# │ 5 ┆ 156 ┆ null │ <- dag 5 heeft stilletjes geen fraudevlag-telling
# └─────┴───────┴─────────┘
In 2.0 zal dit de volgende fout opleveren: ShapeError: cannot concat dataframes with different heights in 'strict' mode
Als opvulling (padding) is wat u wenst, moet u dit expliciet activeren met how="horizontal_extend". Dit maakt de intentie voor de lezer duidelijk.
Verwijdering van casts ten gunste van specifieke methoden/constructoren
Een ander belangrijk punt is de verwijdering van veel casts die ambigu waren of via hun eigen specifieke parsing-expressie zouden moeten worden toegepast. Dit leidt tot één duidelijke manier om data te parsen.
Enums/Categoricals $\leftrightarrow$ integers
pl.Series([None, 1, 0, 2], dtype=pl.UInt32).cast(pl.Enum(["a", "b", "c"]))
# ComputeError: casting from u32 to enum is not supported.
Gebruik in plaats daarvan: .cat.to(dtype) voor int → categorical, en .cat.physical() voor categorical → int.
Strings parsen naar temporele data-types
pl.Series(["2022-08-30"]).cast(pl.Date)
# InvalidOperationError: casting from string to date is not supported.
Gebruik in plaats daarvan: .str.todate() of .str.todatetime(). Hiermee kunt u een parsing-formaat toepassen, wat u meer controle geeft over hoe de data wordt geparsed.
Dit waren slechts enkele voorbeelden, maar we hebben veel meer verbeteringen in striktheid doorgevoerd. Bekijk ze allemaal in de migratiegids.
Informatieve foutmeldingen tonen
We hebben veel moeite gedaan om ervoor te zorgen dat u als gebruiker, of uw agent, verder kunt als u oude parameters gebruikt die niet meer worden ondersteund. We hebben hiervoor twee nieuwe getypeerde uitzonderingen toegevoegd: polars.exceptions.AttributeRemovedError (voor verwijderde attributen en methoden) en polars.exceptions.ArgumentRemovedError (voor verwijderde parameters).
De foutmeldingen zouden u moeten verwijzen naar de nieuwe API. Hieronder tonen we twee voorbeelden:
>>> lf.melt(id_vars="a", value_vars="b")
polars.exceptions.AttributeRemovedError: `melt` was removed in version 2.0;
use `LazyFrame.unpivot` instead, with `index` instead of `id_vars`
and `on` instead of `value_vars`
>>> df.join(df, on="a", join_nulls=True)
polars.exceptions.ArgumentRemovedError: the argument 'join_nulls' for
'DataFrame.join' was deprecated in version 1.24 and has been removed
in 2.0.0. It was renamed to 'nulls_equal' in version 2.0.
Het meeste verwijderde functionaliteit is al lange tijd afgeschreven (deprecated) en zou uw pipelines niet mogen hebben beïnvloed als u up-to-date bent gebleven. Neem contact met ons op als u denkt dat we functionaliteit die u nodig heeft hadden moeten behouden.
Slotwoord
Polars 2.0 draait om betere standaardinstellingen (vooral de streaming-engine) en een betere API. We hopen dat deze release vrij onopvallend is. We koppelen nieuwe functies niet aan hoofdversies, maar brengen ze uit zodra ze klaar zijn.
Maak u geen vergissing: Polars 2.x zal veel beter zijn dan 1.x. Er is veel in ontwikkeling waar we publiekelijk nog niet genoeg over hebben gesproken: volledige out-of-core ondersteuning voor de streaming-engine, een nieuw IO-plugin ontwerp, wat volgens ons de snelste S3-reader zal zijn, grote verbeteringen in SQL-dekking, een cost-based planner, join reordering en de verwijdering van mmap, waardoor onze pipelines volledig async van begin tot eind worden.
Probeer de release candidate door pip install polars==2.0rc1 te installeren. Test het uit en neem contact met ons op via GitHub Issues of via Discord.
Groetjes,