Hvad er Polars, og hvorfor taler alle om det?
Hvis du arbejder med data i Python, har du med stor sandsynlighed brugt pandas. Det har været go-to-biblioteket i over et årti, og med god grund. Men der er sket noget interessant de seneste par år: Polars – et DataFrame-bibliotek skrevet i Rust – er begyndt at dukke op overalt. Og det er ikke uden grund.
Polars kan behandle data op til 30 gange hurtigere end pandas. Ja, du læste rigtigt.
Det bruger Apache Arrow som hukommelsesformat, kører multi-threaded eksekvering ud af boksen, og har en virkelig smart lazy evaluation-tilstand, der automatisk optimerer dine forespørgsler. Med version 1.38 (februar 2026) føles biblioteket modent og stabilt – det er ikke længere bare et eksperiment.
I denne guide tager vi dig igennem alt det vigtigste: installation, grundlæggende DataFrame-operationer, lazy evaluation og udtrykssystemet, der gør Polars så specielt. Så lad os komme i gang.
Installation og opsætning
Det her er heldigvis den nemme del. Polars installeres via pip, og du behøver ikke rode med ekstra afhængigheder:
pip install polars
Når det er installeret, kan du verificere det med et hurtigt tjek i din Python-terminal eller Jupyter Notebook:
import polars as pl
print(pl.__version__)
# Output: 1.38.1 (eller nyere)
Du skal bruge Python 3.9 eller nyere. Bruger du Jupyter Notebook, får du automatisk pæn formatering af DataFrames – det er faktisk ret lækkert at arbejde med.
Din første Polars DataFrame
Ligesom pandas kan du oprette DataFrames fra Python-dictionaries. Syntaksen minder om det, du kender, men der foregår noget helt andet under motorhjelmen:
import polars as pl
df = pl.DataFrame({
"navn": ["Anna", "Bo", "Clara", "David"],
"alder": [28, 35, 42, 31],
"by": ["København", "Aarhus", "Odense", "København"],
"løn": [45000, 52000, 48000, 55000]
})
print(df)
Output:
shape: (4, 4)
┌───────┬───────┬───────────┬───────┐
│ navn ┆ alder ┆ by ┆ løn │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ i64 ┆ str ┆ i64 │
╞═══════╪═══════╪═══════════╪═══════╡
│ Anna ┆ 28 ┆ København ┆ 45000 │
│ Bo ┆ 35 ┆ Aarhus ┆ 52000 │
│ Clara ┆ 42 ┆ Odense ┆ 48000 │
│ David ┆ 31 ┆ København ┆ 55000 │
└───────┴───────┴───────────┴───────┘
Læg mærke til, at Polars viser datatyperne direkte i outputtet (str, i64). Det skyldes bibliotekets strenge type-system, og det er faktisk en af grundene til, at det performer så godt.
Indlæsning af data fra filer
I virkeligheden sidder du sjældent og taster data ind manuelt. Du indlæser fra CSV-, Parquet- eller JSON-filer – og Polars klarer dem alle:
CSV-filer
# Eager mode – indlæser hele filen med det samme
df = pl.read_csv("salgsdata.csv")
# Lazy mode – opbygger en forespørgselsplan uden at læse filen
lf = pl.scan_csv("salgsdata.csv")
Parquet-filer (klart anbefalet til store datasæt)
# Parquet er komprimeret og hurtigere at læse
df = pl.read_parquet("salgsdata.parquet")
# Lazy scanning – kun de nødvendige kolonner og rækker indlæses
lf = pl.scan_parquet("salgsdata.parquet")
JSON-filer
df = pl.read_json("salgsdata.json")
# Ny i v1.38: Læs linjebaseret NDJSON
df = pl.read_ndjson("logfil.ndjson")
Et godt råd: Brug altid scan_-funktionerne (fx scan_csv, scan_parquet) i stedet for read_-funktionerne, når du arbejder med store datasæt. Det aktiverer lazy evaluation, og Polars får mulighed for at optimere hele forespørgslen, før den overhovedet rører dine data.
Grundlæggende dataoperationer
Her kommer de operationer, du vil bruge allermest i hverdagen. Lad os tage dem én ad gangen.
Filtrering af rækker
# Filtrer medarbejdere over 30 år
resultat = df.filter(pl.col("alder") > 30)
print(resultat)
I Polars bruger du pl.col() til at referere til kolonner. Der er ingen .loc eller .iloc som i pandas – og ærligt talt synes jeg, det gør koden mere overskuelig. Det var en bevidst designbeslutning fra udviklerne.
Valg af kolonner
# Vælg specifikke kolonner
df.select("navn", "løn")
# Vælg med udtryk
df.select(
pl.col("navn"),
pl.col("løn").alias("månedlig_løn")
)
Tilføjelse af nye kolonner
# Tilføj en beregnet kolonne
df = df.with_columns(
(pl.col("løn") * 12).alias("årsløn"),
(pl.col("alder") > 30).alias("senior")
)
print(df)
Her er noget fedt: with_columns() kører udtrykkene parallelt. Polars bruger automatisk alle dine CPU-kerner til at beregne nye kolonner på én gang. Det mærker du virkelig, når datasættet vokser.
Sortering
# Sorter efter løn (faldende)
df.sort("løn", descending=True)
# Sorter efter flere kolonner
df.sort(["by", "alder"], descending=[False, True])
Gruppering og aggregering
# Gennemsnitsløn pr. by
resultat = df.group_by("by").agg(
pl.col("løn").mean().alias("gennemsnitsløn"),
pl.col("navn").count().alias("antal_medarbejdere")
)
print(resultat)
Udtryk: Polars' hemmelige våben
Okay, nu kommer vi til det, der virkelig gør Polars specielt. Udtryk (expressions) er kernen i hele biblioteket, og når du først forstår dem, ændrer det måden, du tænker om databehandling på.
Kort sagt: et Polars-udtryk er en funktion, der transformerer en Series til en anden Series. De kan kombineres, kædes sammen og – vigtigst af alt – paralleliseres.
# Kombination af udtryk
df.select(
pl.col("navn").str.to_uppercase().alias("navn_stort"),
pl.col("alder").cast(pl.Float64).alias("alder_float"),
pl.col("løn").rank().alias("løn_rangering"),
pl.col("by").n_unique().alias("antal_byer")
)
Det er udtrykssystemet, der gør, at Polars-kode ofte ender med at være både mere læsbar og hurtigere end tilsvarende pandas-kode. Alle udtryk inden for en enkelt operation evalueres parallelt, og Polars kan optimere hele kæden bag kulisserne.
Lazy evaluation: Automatisk optimering
Det her er nok min yndlingsfunktion i Polars. I stedet for at udføre operationer med det samme, opbygger Polars en forespørgselsplan – en slags opskrift – som den analyserer og optimerer, før den eksekverer noget som helst.
Sådan fungerer det i praksis
# Trin 1: Opbyg en lazy forespørgsel
forespørgsel = (
pl.scan_csv("salgsdata.csv")
.filter(pl.col("beløb") > 1000)
.group_by("kategori")
.agg(
pl.col("beløb").sum().alias("total_salg"),
pl.col("beløb").count().alias("antal_transaktioner")
)
.sort("total_salg", descending=True)
)
# Trin 2: Inspicér forespørgselsplanen
print(forespørgsel.explain())
# Trin 3: Eksekvér forespørgslen
resultat = forespørgsel.collect()
print(resultat)
Når du kalder .explain(), kan du faktisk se, hvad Polars gør under motorhjelmen. De typiske optimeringer er:
- Predicate pushdown: Filtre flyttes ned til filindlæsningen, så irrelevante rækker aldrig rammer hukommelsen.
- Projection pushdown: Kun de kolonner, du rent faktisk bruger, bliver indlæst fra filen.
- Slice pushdown: Hvis du kun har brug for de første N rækker, stopper Polars med at læse, når den har nok.
Det er ren magi, når det virker (og det gør det næsten altid).
Praktisk eksempel: Stor CSV-fil
import time
# Pandas-tilgang (eager)
import pandas as pd
start = time.time()
pdf = pd.read_csv("stor_fil.csv")
pdf = pdf[pdf["status"] == "aktiv"]
resultat_pandas = pdf.groupby("region")["omsætning"].sum()
print(f"Pandas: {time.time() - start:.2f}s")
# Polars-tilgang (lazy)
start = time.time()
resultat_polars = (
pl.scan_csv("stor_fil.csv")
.filter(pl.col("status") == "aktiv")
.group_by("region")
.agg(pl.col("omsætning").sum())
.collect()
)
print(f"Polars: {time.time() - start:.2f}s")
Med en CSV-fil på 1 GB vil du typisk se Polars klare opgaven 4-10 gange hurtigere end pandas. Det skyldes kombinationen af parallel eksekvering og forespørgselsoptimering – det er ikke bare et tal fra en markedsføringsside.
Håndtering af manglende data
En ting, der overraskede mig, da jeg begyndte med Polars: det bruger null i stedet for NaN til manglende data. Det lyder måske som en lille ting, men det giver faktisk en mere konsistent opførsel på tværs af alle datatyper.
df = pl.DataFrame({
"produkt": ["A", "B", "C", "D"],
"pris": [100, None, 250, None],
"antal": [5, 3, None, 7]
})
# Udfyld manglende værdier
df_fyldt = df.with_columns(
pl.col("pris").fill_null(pl.col("pris").mean()).alias("pris_fyldt"),
pl.col("antal").fill_null(0).alias("antal_fyldt")
)
# Fjern rækker med manglende værdier
df_ren = df.drop_nulls()
# Tæl manglende værdier pr. kolonne
print(df.null_count())
Sammenføjning af DataFrames
Joins er noget, du gør hele tiden i databehandling, og Polars understøtter alle de gængse typer. Bonus: det er markant hurtigere end pandas til store joins.
kunder = pl.DataFrame({
"kunde_id": [1, 2, 3, 4],
"navn": ["Anna", "Bo", "Clara", "David"]
})
ordrer = pl.DataFrame({
"ordre_id": [101, 102, 103, 104, 105],
"kunde_id": [1, 2, 1, 3, 5],
"beløb": [500, 300, 750, 200, 100]
})
# Inner join
resultat = ordrer.join(kunder, on="kunde_id", how="inner")
# Left join (behold alle ordrer)
resultat = ordrer.join(kunder, on="kunde_id", how="left")
print(resultat)
Hvornår bør du vælge Polars frem for pandas?
Det her er et spørgsmål, jeg får ret ofte. Og svaret er: det kommer an på situationen.
- Vælg Polars når dit datasæt overstiger 500 MB, du bygger automatiserede ETL-pipelines, du har brug for parallel databehandling, eller du bare vil have den tryghed, som strikse typer giver.
- Bliv ved med pandas til hurtig udforskning af små datasæt, når du er afhængig af tæt matplotlib-integration, eller når du har eksisterende pandas-kode, der fungerer fint. Ingen grund til at migrere bare for sjov.
- Brug begge – og det er faktisk det, mange gør i praksis. Polars konverterer nemt til og fra pandas med
df.to_pandas()ogpl.from_pandas(pdf).
Et komplet eksempel: Salgsanalyse-pipeline
Lad os samle det hele i en realistisk datapipeline, der minder om det, du ville bygge i et rigtigt projekt:
import polars as pl
# Indlæs data (lazy)
salg = pl.scan_csv("salgsdata.csv")
produkter = pl.scan_csv("produkter.csv")
# Byg en komplet analysepipeline
rapport = (
salg
.join(produkter, on="produkt_id", how="left")
.filter(pl.col("dato") >= "2026-01-01")
.with_columns(
(pl.col("antal") * pl.col("enhedspris")).alias("total"),
pl.col("dato").str.strptime(pl.Date, "%Y-%m-%d").alias("dato_parsed")
)
.group_by("kategori")
.agg(
pl.col("total").sum().alias("samlet_omsætning"),
pl.col("total").mean().alias("gns_ordreværdi"),
pl.col("ordre_id").n_unique().alias("antal_ordrer")
)
.sort("samlet_omsætning", descending=True)
.collect()
)
print(rapport)
Hele denne pipeline eksekveres som én optimeret forespørgsel. Polars analyserer den samlede plan og finder den mest effektive vej igennem – og det hele sker automatisk.
Ofte stillede spørgsmål
Er Polars en erstatning for pandas?
Ikke nødvendigvis. Polars er et stærkt alternativ, der skinner ved store datasæt og automatiserede pipelines. Men pandas har stadig et bredere økosystem med tættere integration til biblioteker som matplotlib, seaborn og scikit-learn. Mange dataingeniører bruger begge dele – Polars til de tunge transformationer og pandas til analyse og visualisering.
Hvor meget hurtigere er Polars end pandas?
Det afhænger af operationen, men benchmarks viser typisk 5-30x hastighedsforbedringer. CSV-indlæsning kan være op til 10x hurtigere, mens aggregeringer på store datasæt typisk ligger 4-6x hurtigere. Den største gevinst kommer med lazy evaluation på meget store filer, hvor predicate og projection pushdown kan skære hukommelsesforbruget drastisk ned.
Kan jeg bruge Polars i Jupyter Notebook?
Absolut. Polars fungerer helt fint i både Jupyter Notebook og JupyterLab. DataFrames vises automatisk med pæn HTML-formatering. Bare installér med pip install polars og importér med import polars as pl – så er du klar.
Understøtter Polars SQL-forespørgsler?
Ja, det gør det faktisk. Polars har en indbygget SQL-kontekst, hvor du kan skrive SQL direkte mod dine DataFrames via pl.SQLContext(). Det er rigtig praktisk, hvis du kommer fra en database-baggrund og foretrækker SQL-syntaks til visse operationer.
Hvordan konverterer jeg mellem Polars og pandas?
Det er nemt: polars_df.to_pandas() konverterer til pandas, og pl.from_pandas(pandas_df) går den anden vej. Det gør det uproblematisk at integrere Polars i eksisterende workflows, hvor du stadig bruger pandas-kompatible biblioteker.