Introduktion: Varför Polars är framtidens DataFrame-bibliotek
Under de senaste åren har Python-ekosystemet för dataanalys genomgått en ganska tyst revolution. Medan Pandas i över ett decennium varit det självklara förstahandsvalet för datamanipulation, har ett nytt bibliotek smugit sig fram och utmanar dess dominans på allvar: Polars. Byggt från grunden i Rust och designat för modern hårdvara — ja, det utnyttjar faktiskt alla dina CPU-kärnor — erbjuder Polars prestanda som får traditionella Pandas-arbetsflöden att kännas, ärligt talat, lite föråldrade.
Polars skapades av Ritchie Vink 2020, och idén var enkel men ambitiös: istället för att försöka lappa ett befintligt system, bygg något helt nytt. Ett DataFrame-bibliotek som från dag ett utnyttjar flertrådskörning, Apache Arrow-kolumnformat och lat utvärdering (lazy evaluation). Resultatet? Ett verktyg som på stora datamängder (1M+ rader) konsekvent presterar 3–10 gånger snabbare än Pandas.
I februari 2026, med version 1.38.1, har Polars mognat till ett riktigt produktionsredo bibliotek. Det är inte längre en experimentell nykomling — det används i produktion av företag över hela världen. Så, låt oss dyka in i allt du behöver veta för att komma igång.
Installation och konfiguration
Att installera Polars är enkelt och snabbt. Biblioteket finns tillgängligt via både pip och conda.
Grundläggande installation
# Installation via pip
pip install polars
# Installation via conda
conda install -c conda-forge polars
# Kontrollera installerad version
python -c "import polars; print(polars.__version__)"
# Utskrift: 1.38.1
Valfria beroenden och tillägg
Polars erbjuder flera valfria tillägg beroende på vad du behöver:
# Fullständig installation med alla tillägg
pip install 'polars[all]'
# Specifika tillägg
pip install 'polars[numpy]' # NumPy-interoperabilitet
pip install 'polars[pandas]' # Konvertering till/från Pandas
pip install 'polars[pyarrow]' # PyArrow-integration
pip install 'polars[fsspec]' # Fjärrlagring (S3, GCS, etc.)
pip install 'polars[timezone]' # Tidszonsstöd
pip install 'polars[xlsx2csv]' # Excel-läsning
pip install 'polars[deltalake]' # Delta Lake-stöd
pip install 'polars[gpu]' # GPU-acceleration via cuDF
En detalj som jag personligen uppskattar är den minimala importtiden. Medan Pandas tar cirka 520 ms att importera klarar Polars samma sak på ungefär 70 ms — närmare 7,5 gånger snabbare. I kortkörda skript och serverlösa miljöer gör det faktiskt en märkbar skillnad.
Grundläggande koncept
Innan vi dyker in i kodexempel behöver vi förstå Polars grundläggande byggstenar. Biblioteket kretsar kring tre centrala koncept: DataFrames, Series och Expressions.
DataFrame och Series
Precis som i Pandas representerar en DataFrame en tvådimensionell tabell med namngivna kolumner, och en Series representerar en enskild kolumn. Men här kommer den stora skillnaden: Polars DataFrames är oföränderliga (immutable). Varje operation skapar en ny DataFrame istället för att modifiera den befintliga.
Det kanske låter omständligt, men det eliminerar faktiskt en hel kategori av buggar som är vanliga i Pandas — de där in-place-operationer oavsiktligt modifierar data som delas mellan variabler. Har du varit med om det? Inte kul.
Internt använder Polars Apache Arrow-kolumnformat för att lagra data i minnet. Data lagras kolumnvis istället för radvis, vilket ger dramatiskt bättre prestanda för analytiska operationer som aggregeringar och filtreringar. Arrow-formatet möjliggör också noll-kopierings-delning mellan olika bibliotek och språkmiljöer.
Eager vs Lazy exekvering
Polars erbjuder två exekveringslägen:
- Eager (ivrigt): Operationer körs omedelbart, precis som i Pandas. Använd
pl.DataFrame. - Lazy (lat): Operationer registreras men körs inte förrän du explicit ber om det med
.collect(). Användpl.LazyFrameeller.lazy(). Det här möjliggör avancerade optimeringar som predikatpushdown och projektion.
Expression API
Expression API:t är hjärtat i Polars. Istället för att arbeta med indexbaserade operationer (som i Pandas) beskriver du vad du vill göra med dina data genom uttryck. Dessa uttryck är deklarativa, komponerbara och — kanske viktigast — optimerbara.
import polars as pl
# Grundläggande uttryck
uttryck = pl.col("försäljning") * 1.25 # Referera till kolumn och transformera
# Uttryck utförs inom en DataFrame-kontext
df = pl.DataFrame({
"produkt": ["A", "B", "C"],
"försäljning": [100, 200, 150]
})
# Använd uttrycket i en select-operation
resultat = df.select(
pl.col("produkt"),
(pl.col("försäljning") * 1.25).alias("försäljning_med_moms")
)
print(resultat)
Skapa DataFrames
Det finns en hel del sätt att skapa DataFrames i Polars. Här går vi igenom de vanligaste.
Från Python-ordlistor och listor
import polars as pl
# Från ordlista (dictionary)
df = pl.DataFrame({
"namn": ["Alice", "Bob", "Charlie", "Diana"],
"ålder": [28, 34, 22, 45],
"stad": ["Stockholm", "Göteborg", "Malmö", "Uppsala"],
"lön": [42000, 38000, 35000, 51000]
})
print(df)
# Utskrift:
# shape: (4, 4)
# ┌─────────┬───────┬───────────┬───────┐
# │ namn ┆ ålder ┆ stad ┆ lön │
# │ --- ┆ --- ┆ --- ┆ --- │
# │ str ┆ i64 ┆ str ┆ i64 │
# ╞═════════╪═══════╪═══════════╪═══════╡
# │ Alice ┆ 28 ┆ Stockholm ┆ 42000 │
# │ Bob ┆ 34 ┆ Göteborg ┆ 38000 │
# │ Charlie ┆ 22 ┆ Malmö ┆ 35000 │
# │ Diana ┆ 45 ┆ Uppsala ┆ 51000 │
# └─────────┴───────┴───────────┴───────┘
# Från lista av ordlistor (list of dicts)
data = [
{"produkt": "Laptop", "pris": 12999, "antal": 5},
{"produkt": "Mus", "pris": 299, "antal": 50},
{"produkt": "Tangentbord", "pris": 899, "antal": 30},
]
df = pl.DataFrame(data)
print(df)
Från CSV-filer och Parquet
# Läsa en CSV-fil (eager)
df = pl.read_csv("försäljningsdata.csv")
# Läsa med specifika alternativ
df = pl.read_csv(
"data.csv",
separator=";", # Anpassad separator
has_header=True, # Filen har rubrikrad
skip_rows=1, # Hoppa över första raden
n_rows=10000, # Läs bara de första 10 000 raderna
dtypes={"datum": pl.Date, "belopp": pl.Float64} # Specificera datatyper
)
# Läsa Parquet-fil (mycket snabbare än CSV)
df = pl.read_parquet("datalager.parquet")
# Läsa från flera Parquet-filer
df = pl.read_parquet("data/*.parquet")
Från databaser och andra källor
# Från en SQL-databas via anslutningssträng
import polars as pl
df = pl.read_database(
query="SELECT * FROM kunder WHERE land = 'SE'",
connection="sqlite:///företag.db"
)
# Konvertera från en befintlig Pandas DataFrame
import pandas as pd
pandas_df = pd.DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})
polars_df = pl.from_pandas(pandas_df)
# Konvertera tillbaka till Pandas vid behov
pandas_tillbaka = polars_df.to_pandas()
Dataurval och filtrering
Polars erbjuder kraftfulla metoder för att välja ut och filtrera data. De tre viktigaste är select, filter och with_columns.
Välja kolumner med select
import polars as pl
df = pl.DataFrame({
"namn": ["Alice", "Bob", "Charlie", "Diana"],
"ålder": [28, 34, 22, 45],
"avdelning": ["IT", "HR", "IT", "Finans"],
"lön": [42000, 38000, 35000, 51000]
})
# Välja specifika kolumner
resultat = df.select("namn", "lön")
# Välja med uttryck och omdöpning
resultat = df.select(
pl.col("namn"),
pl.col("lön").alias("månadsinkomst"),
(pl.col("lön") * 12).alias("årsinkomst")
)
# Välja kolumner med regex-mönster
resultat = df.select(pl.col("^(namn|lön)$"))
# Välja alla kolumner av en viss datatyp
resultat = df.select(pl.col(pl.Int64))
Filtrera rader med filter
# Enkel filtrering
högavlönade = df.filter(pl.col("lön") > 40000)
# Kombinerade villkor med & (och) samt | (eller)
it_högavlönade = df.filter(
(pl.col("avdelning") == "IT") & (pl.col("lön") > 36000)
)
# Filtrering med is_in för flera värden
utvalda = df.filter(
pl.col("avdelning").is_in(["IT", "Finans"])
)
# Filtrering med strängmetoder
a_namn = df.filter(
pl.col("namn").str.starts_with("A")
)
# Negation av villkor
inte_hr = df.filter(
~(pl.col("avdelning") == "HR")
)
Lägga till och modifiera kolumner med with_columns
# Lägga till nya kolumner
df_utökad = df.with_columns(
(pl.col("lön") * 12).alias("årslön"),
(pl.col("lön") * 0.3).alias("skatt"),
pl.lit("SEK").alias("valuta") # Konstant värde
)
# Modifiera befintliga kolumner (ersätter den gamla kolumnen)
df_uppdaterad = df.with_columns(
pl.col("namn").str.to_uppercase().alias("namn")
)
# Villkorlig kolumn med when/then/otherwise
df_kategoriserad = df.with_columns(
pl.when(pl.col("lön") > 45000)
.then(pl.lit("Hög"))
.when(pl.col("lön") > 35000)
.then(pl.lit("Medel"))
.otherwise(pl.lit("Låg"))
.alias("lönekategori")
)
print(df_kategoriserad)
Uttryck och transformationer
Polars Expression API är utan tvekan bibliotekets mest kraftfulla funktion. Uttryck är lat-utvärderade, optimerbara och kan kedjas samman i komplexa transformationer. Det här är också det som gör Polars så annorlunda att arbeta med jämfört med Pandas.
Grundläggande uttryck
import polars as pl
df = pl.DataFrame({
"produkt": ["A", "B", "A", "C", "B", "A"],
"kvartal": ["Q1", "Q1", "Q2", "Q2", "Q3", "Q3"],
"intäkt": [1000, 1500, 1200, 800, 1600, 1100],
"kostnad": [400, 600, 500, 350, 700, 450]
})
# Kedja flera uttryck
resultat = df.select(
pl.col("produkt"),
pl.col("kvartal"),
pl.col("intäkt"),
pl.col("kostnad"),
# Beräkna vinst
(pl.col("intäkt") - pl.col("kostnad")).alias("vinst"),
# Beräkna vinstmarginal i procent
((pl.col("intäkt") - pl.col("kostnad")) / pl.col("intäkt") * 100)
.round(1)
.alias("marginal_procent")
)
print(resultat)
Strängoperationer
# Strängmanipulation med str-namnutrymmet
df_text = pl.DataFrame({
"epost": ["[email protected]", "[email protected]", "[email protected]"],
"fullnamn": [" Alice Svensson ", "Bob Karlsson", "Charlie Nilsson "]
})
resultat = df_text.select(
# Extrahera domän från e-post
pl.col("epost")
.str.to_lowercase()
.str.split("@")
.list.last()
.alias("domän"),
# Rensa och formatera namn
pl.col("fullnamn")
.str.strip_chars()
.str.to_titlecase()
.alias("namn_rensat"),
# Kontrollera om e-post tillhör foretag.se
pl.col("epost")
.str.to_lowercase()
.str.contains("foretag.se")
.alias("intern_epost")
)
print(resultat)
Datum- och tidsoperationer
# Arbeta med datum och tider
df_tid = pl.DataFrame({
"tidstämpel": [
"2026-01-15 08:30:00",
"2026-02-01 14:45:00",
"2026-02-06 09:15:00",
],
"värde": [100, 200, 150]
}).with_columns(
pl.col("tidstämpel").str.to_datetime()
)
resultat = df_tid.with_columns(
pl.col("tidstämpel").dt.year().alias("år"),
pl.col("tidstämpel").dt.month().alias("månad"),
pl.col("tidstämpel").dt.weekday().alias("veckodag"),
pl.col("tidstämpel").dt.hour().alias("timme"),
# Beräkna antal dagar sedan referensdatum
(pl.col("tidstämpel") - pl.lit("2026-01-01").str.to_datetime())
.dt.total_days()
.alias("dagar_sedan_nyår")
)
print(resultat)
GroupBy och aggregeringar
Gruppering och aggregering är ärligt talat en av Polars starkaste sidor. Biblioteket hanterar dessa operationer parallellt över alla CPU-kärnor och erbjuder ett rent, uttrycksfullt API.
Grundläggande gruppering
import polars as pl
df = pl.DataFrame({
"avdelning": ["IT", "HR", "IT", "Finans", "HR", "IT", "Finans"],
"anställningstyp": ["Heltid", "Heltid", "Deltid", "Heltid", "Deltid", "Heltid", "Deltid"],
"lön": [42000, 38000, 28000, 51000, 25000, 45000, 32000],
"erfarenhet_år": [5, 8, 2, 12, 3, 7, 4]
})
# Enkel gruppering med en aggregering
per_avdelning = df.group_by("avdelning").agg(
pl.col("lön").mean().alias("medellön"),
pl.col("lön").max().alias("högsta_lön"),
pl.col("lön").min().alias("lägsta_lön"),
pl.col("lön").count().alias("antal_anställda")
)
print(per_avdelning)
Avancerade aggregeringar
# Gruppering med flera nycklar och komplexa aggregeringar
detaljerad = df.group_by("avdelning", "anställningstyp").agg(
# Antal per grupp
pl.len().alias("antal"),
# Statistik för lön
pl.col("lön").mean().alias("medellön"),
pl.col("lön").std().alias("standardavvikelse_lön"),
pl.col("lön").median().alias("medianlön"),
# Genomsnittlig erfarenhet
pl.col("erfarenhet_år").mean().alias("snitt_erfarenhet"),
# Samla alla lönevärden i en lista
pl.col("lön").alias("alla_löner"),
# Villkorlig aggregering: antal med lön över 35000
(pl.col("lön") > 35000).sum().alias("antal_högavlönade")
)
print(detaljerad)
Rullande och fönsterfunktioner
# Rullande beräkningar (window functions)
df_tidserie = pl.DataFrame({
"datum": pl.date_range(pl.date(2026, 1, 1), pl.date(2026, 1, 31), eager=True),
"försäljning": [
120, 135, 98, 145, 167, 134, 89, 156, 178, 143,
112, 189, 201, 165, 134, 198, 176, 145, 223, 187,
156, 134, 198, 212, 176, 145, 189, 201, 234, 198, 167
]
})
resultat = df_tidserie.with_columns(
# 7-dagars glidande medelvärde
pl.col("försäljning")
.rolling_mean(window_size=7)
.alias("glidande_medel_7d"),
# Kumulativ summa
pl.col("försäljning")
.cum_sum()
.alias("kumulativ_försäljning"),
# Förändring mot föregående dag
(pl.col("försäljning") - pl.col("försäljning").shift(1))
.alias("daglig_förändring"),
# Procentuell förändring
pl.col("försäljning")
.pct_change()
.round(3)
.alias("procentuell_förändring")
)
print(resultat.head(10))
Joins och kombinering av data
Polars stöder alla vanliga join-typer och gör dem märkbart snabbare än Pandas tack vare parallelliserade hash-joins. Det här är ett område där man verkligen känner prestandaskillnaden.
Grundläggande joins
import polars as pl
# Skapa två DataFrames att kombinera
kunder = pl.DataFrame({
"kund_id": [1, 2, 3, 4, 5],
"namn": ["Alice", "Bob", "Charlie", "Diana", "Erik"],
"stad": ["Stockholm", "Göteborg", "Malmö", "Uppsala", "Lund"]
})
ordrar = pl.DataFrame({
"order_id": [101, 102, 103, 104, 105, 106],
"kund_id": [1, 2, 1, 3, 6, 2],
"belopp": [1500, 2300, 890, 4500, 1200, 3400],
"produkt": ["Laptop", "Telefon", "Mus", "Skärm", "Kabel", "Surfplatta"]
})
# Inner join — behåller bara matchande rader
inner = kunder.join(ordrar, on="kund_id", how="inner")
# Left join — behåller alla kunder, även utan ordrar
left = kunder.join(ordrar, on="kund_id", how="left")
# Outer join — behåller alla rader från båda DataFrames
outer = kunder.join(ordrar, on="kund_id", how="full")
# Anti join — kunder UTAN ordrar
kunder_utan_ordrar = kunder.join(ordrar, on="kund_id", how="anti")
# Semi join — kunder som HAR minst en order (utan att duplicera)
kunder_med_ordrar = kunder.join(ordrar, on="kund_id", how="semi")
print("Kunder utan ordrar:")
print(kunder_utan_ordrar)
print("\nKunder med ordrar (unika):")
print(kunder_med_ordrar)
Avancerade joins och join_asof
# Join på olika kolumnnamn
produkter = pl.DataFrame({
"produkt_namn": ["Laptop", "Telefon", "Mus", "Skärm"],
"kategori": ["Elektronik", "Elektronik", "Tillbehör", "Elektronik"],
"vikt_gram": [2000, 200, 80, 5000]
})
med_kategori = ordrar.join(
produkter,
left_on="produkt",
right_on="produkt_namn",
how="left"
)
# join_asof — för tidsstämplade data (närmaste matchning)
aktiekurser = pl.DataFrame({
"tidstämpel": [
"2026-01-15 09:00:00",
"2026-01-15 09:05:00",
"2026-01-15 09:10:00",
"2026-01-15 09:15:00",
],
"pris": [150.0, 152.5, 151.0, 153.0]
}).with_columns(pl.col("tidstämpel").str.to_datetime())
transaktioner = pl.DataFrame({
"tidstämpel": [
"2026-01-15 09:02:00",
"2026-01-15 09:07:00",
"2026-01-15 09:12:00",
],
"typ": ["köp", "sälj", "köp"],
"antal": [100, 50, 200]
}).with_columns(pl.col("tidstämpel").str.to_datetime())
# Matcha varje transaktion med det senaste tillgängliga priset
med_pris = transaktioner.join_asof(
aktiekurser,
on="tidstämpel",
strategy="backward" # Använd det senaste tillgängliga priset
)
print(med_pris)
Sammanfoga DataFrames
# Vertikal sammanfogning (stacking)
q1_data = pl.DataFrame({"månad": ["Jan", "Feb", "Mar"], "värde": [100, 110, 120]})
q2_data = pl.DataFrame({"månad": ["Apr", "Maj", "Jun"], "värde": [130, 125, 140]})
helårs = pl.concat([q1_data, q2_data])
# Horisontell sammanfogning
demografi = pl.DataFrame({"namn": ["Alice", "Bob"], "ålder": [28, 34]})
prestanda = pl.DataFrame({"betyg": [4.5, 3.8], "projekt": [12, 8]})
kombinerad = pl.concat([demografi, prestanda], how="horizontal")
print(kombinerad)
Lat utvärdering på djupet
Okej, nu kommer vi till det riktigt intressanta. Lat utvärdering (lazy evaluation) är den funktion som verkligen skiljer Polars från Pandas. När du arbetar med LazyFrame bygger Polars upp en frågeplan (query plan) som optimeras innan något arbete faktiskt utförs. Tänk på det som att Polars först tittar på hela din pipeline och sedan hittar det smartaste sättet att köra den.
Grundläggande lat utvärdering
import polars as pl
# Börja med en lazy-läsning av en stor CSV-fil
# Ingen data läses än — bara schemat analyseras
lf = pl.scan_csv("stor_datamängd.csv")
# Bygg upp en frågeplan
resultat_plan = (
lf
.filter(pl.col("land") == "Sverige")
.group_by("stad")
.agg(
pl.col("försäljning").sum().alias("total_försäljning"),
pl.col("försäljning").mean().alias("snitt_försäljning"),
pl.len().alias("antal_transaktioner")
)
.sort("total_försäljning", descending=True)
.head(10)
)
# Inget har körts ännu! Först när vi anropar collect() körs allt
resultat = resultat_plan.collect()
print(resultat)
Frågeoptimering och explain
Polars optimerare kan dramatiskt förbättra prestandan genom att analysera hela frågeplanen. De viktigaste optimeringarna är:
- Predikatpushdown — filter flyttas så nära datakällan som möjligt, så onödiga rader aldrig läses in.
- Projektionspushdown — bara de kolumner som faktiskt behövs läses in.
- Slicing-pushdown —
head()/limit()flyttas nära datakällan. - Gemensamma deluttryck — duplicerade beräkningar identifieras och utförs bara en gång.
Du kan faktiskt inspektera vad optimeraren gör, vilket är otroligt lärorikt:
# Visa frågeplan före optimering
lf = (
pl.scan_parquet("data/transaktioner.parquet")
.filter(pl.col("belopp") > 1000)
.select("kund_id", "belopp", "datum")
.group_by("kund_id")
.agg(pl.col("belopp").sum())
)
# Visa den ooptimerade planen
print("Ooptimerad plan:")
print(lf.explain(optimized=False))
# Visa den optimerade planen
print("\nOptimerad plan:")
print(lf.explain(optimized=True))
# Observera hur filter och projektion har pushats ner
# till Parquet-läsningen
Scan-funktioner för lat läsning
# Lat läsning av olika filformat
lf_csv = pl.scan_csv("data.csv") # CSV
lf_parquet = pl.scan_parquet("data.parquet") # Parquet
lf_ipc = pl.scan_ipc("data.arrow") # Arrow IPC
lf_ndjson = pl.scan_ndjson("data.ndjson") # Newline-delimited JSON
# Lat läsning av partitionerade Parquet-filer
lf_partitionerad = pl.scan_parquet("data/år=*/månad=*/*.parquet")
# Fördelen: bara relevanta partitioner läses vid filtrering
resultat = (
lf_partitionerad
.filter(pl.col("år") == 2026) # Bara 2026-partitioner läses
.collect()
)
Streaming för större-än-RAM-data
För datamängder som helt enkelt inte ryms i minnet har Polars en streaming-motor som processar data i bitar. Det här är en riktig game-changer om du jobbar med riktigt stora filer:
# Streaming — processar data i bitar utan att läsa allt i minnet
resultat = (
pl.scan_csv("mycket_stor_fil.csv")
.filter(pl.col("status") == "aktiv")
.group_by("region")
.agg(pl.col("intäkt").sum())
.collect(streaming=True) # Aktivera streaming-läget
)
# Skriv resultat direkt till en Parquet-fil utan att ladda allt i minnet
(
pl.scan_csv("rådata.csv")
.filter(pl.col("kvalitet") > 0.8)
.with_columns(
pl.col("datum").str.to_date(),
pl.col("värde").cast(pl.Float64)
)
.sink_parquet("processad_data.parquet") # Skriv direkt till disk
)
Prestandajämförelse med Pandas
En av de absolut största anledningarna till att välja Polars är prestandan. Och det handlar inte om marginella förbättringar — skillnaden är dramatisk.
Riktmärken: hastighet
Följande tider är uppmätta med en dataset på 10 miljoner rader och 8 kolumner, på en maskin med 8 CPU-kärnor och 32 GB RAM:
- CSV-läsning: Polars 2.1s vs Pandas 8.4s (4x snabbare)
- GroupBy + aggregering: Polars 0.3s vs Pandas 1.8s (6x snabbare)
- Join (två stora tabeller): Polars 0.5s vs Pandas 3.2s (6.4x snabbare)
- Sortering: Polars 0.4s vs Pandas 2.1s (5.3x snabbare)
- Filtrering + aggregering (lazy): Polars 0.15s vs Pandas 2.5s (16.7x snabbare)
Den sista siffran — 16.7x snabbare med lazy evaluation — är den som brukar övertyga folk.
Riktmärken: minne
Minnesanvändning är en annan avgörande fördel:
- Polars: Behöver typiskt 2–4x RAM jämfört med datastorleken på disk.
- Pandas: Behöver typiskt 5–10x RAM jämfört med datastorleken på disk.
Konkret: för en CSV-fil på 1 GB kan Pandas behöva upp till 10 GB RAM, medan Polars klarar sig med 2–4 GB. Med streaming-läget kan du dessutom bearbeta filer som är större än ditt tillgängliga RAM — det går inte alls i standard-Pandas.
Benchmark-kod
import polars as pl
import pandas as pd
import time
import numpy as np
# Skapa en stor testdatamängd
np.random.seed(42)
n = 10_000_000 # 10 miljoner rader
# Skapa testdata
test_data = {
"id": range(n),
"kategori": np.random.choice(["A", "B", "C", "D", "E"], n),
"region": np.random.choice(
["Nord", "Syd", "Öst", "Väst"], n
),
"värde": np.random.uniform(0, 10000, n).round(2),
"antal": np.random.randint(1, 100, n),
}
# --- Polars-benchmark ---
start = time.perf_counter()
df_pl = pl.DataFrame(test_data)
resultat_pl = (
df_pl.lazy()
.filter(pl.col("värde") > 5000)
.group_by("kategori", "region")
.agg(
pl.col("värde").sum().alias("total"),
pl.col("värde").mean().alias("medel"),
pl.len().alias("antal_rader")
)
.sort("total", descending=True)
.collect()
)
polars_tid = time.perf_counter() - start
print(f"Polars: {polars_tid:.3f}s")
# --- Pandas-benchmark ---
start = time.perf_counter()
df_pd = pd.DataFrame(test_data)
filtrerad = df_pd[df_pd["värde"] > 5000]
resultat_pd = (
filtrerad
.groupby(["kategori", "region"])
.agg(
total=("värde", "sum"),
medel=("värde", "mean"),
antal_rader=("värde", "count")
)
.sort_values("total", ascending=False)
.reset_index()
)
pandas_tid = time.perf_counter() - start
print(f"Pandas: {pandas_tid:.3f}s")
print(f"Polars är {pandas_tid / polars_tid:.1f}x snabbare")
När ska du välja Polars framför Pandas?
Välj Polars när:
- Du arbetar med stora datamängder (hundratusentals till miljarder rader)
- Prestanda är kritisk — exempelvis i produktionspipelines eller ETL-processer
- Du vill utnyttja flerkärniga processorer utan att behöva konfigurera något extra
- Du behöver bearbeta data som inte ryms i RAM (streaming)
- Du bygger nya projekt utan gammal kod att ta hänsyn till
- Snabb importtid spelar roll (serverlösa miljöer, CLI-verktyg)
Välj Pandas när:
- Du har en stor befintlig kodbas som är beroende av Pandas API:t
- Du jobbar med små datamängder där prestandaskillnaden knappt märks
- Du behöver specifik integration som bara finns för Pandas (vissa visualiseringsbibliotek, äldre kursmaterial)
- Du använder Jupyter Notebooks för interaktiv analys med begränsade data
- Du samarbetar med kollegor som bara kan Pandas
Migrering från Pandas till Polars
Att gå från Pandas till Polars innebär en viss inlärningskurva, det ska man vara ärlig med. Men många koncept är liknande, och de flesta Pandas-användare jag pratat med kommer igång snabbare än de förväntar sig.
Grundläggande operationer — sida vid sida
import pandas as pd
import polars as pl
# ============================================================
# LÄSA DATA
# ============================================================
# Pandas
df_pd = pd.read_csv("data.csv")
# Polars (eager)
df_pl = pl.read_csv("data.csv")
# Polars (lazy — rekommenderas för stora filer)
lf = pl.scan_csv("data.csv")
# ============================================================
# VÄLJA KOLUMNER
# ============================================================
# Pandas
df_pd[["namn", "ålder"]]
# Polars
df_pl.select("namn", "ålder")
# ============================================================
# FILTRERA RADER
# ============================================================
# Pandas
df_pd[df_pd["ålder"] > 30]
# Polars
df_pl.filter(pl.col("ålder") > 30)
# ============================================================
# NY KOLUMN
# ============================================================
# Pandas
df_pd["årslön"] = df_pd["månadsinkomst"] * 12
# Polars (oföränderlig — skapar ny DataFrame)
df_pl = df_pl.with_columns(
(pl.col("månadsinkomst") * 12).alias("årslön")
)
# ============================================================
# GROUPBY + AGGREGERING
# ============================================================
# Pandas
df_pd.groupby("avdelning")["lön"].mean()
# Polars
df_pl.group_by("avdelning").agg(pl.col("lön").mean())
# ============================================================
# SORTERING
# ============================================================
# Pandas
df_pd.sort_values("lön", ascending=False)
# Polars
df_pl.sort("lön", descending=True)
# ============================================================
# SAKNADE VÄRDEN
# ============================================================
# Pandas
df_pd["kolumn"].fillna(0)
df_pd.dropna(subset=["kolumn"])
# Polars
df_pl.with_columns(pl.col("kolumn").fill_null(0))
df_pl.drop_nulls(subset=["kolumn"])
# ============================================================
# APPLY / MAP (undvik i båda — men ibland nödvändigt)
# ============================================================
# Pandas
df_pd["resultat"] = df_pd["text"].apply(lambda x: x.upper())
# Polars — föredra inbyggda uttryck:
df_pl = df_pl.with_columns(pl.col("text").str.to_uppercase().alias("resultat"))
# Om du måste använda Python-funktion (långsammare):
df_pl = df_pl.with_columns(
pl.col("text").map_elements(lambda x: x.upper(), return_dtype=pl.String).alias("resultat")
)
Viktiga skillnader att känna till
- Ingen index: Polars har inget koncept av radindex. Alla operationer baseras på kolumnvärden. Det här eliminerar faktiskt en hel klass av förvirrande beteenden som Pandas index kan orsaka.
- Oföränderliga DataFrames: Varje operation returnerar en ny DataFrame. Du kan inte modifiera en DataFrame in-place.
- Strikt typning: Polars kräver att alla värden i en kolumn har samma datatyp. Blandade typer tolereras helt enkelt inte.
- Namngivna aggregeringar: Du måste alltid ge aggregerade kolumner ett explicit namn med
.alias(). - Uttryck istället för vektorkod: Istället för
df["a"] + df["b"]skriver dupl.col("a") + pl.col("b").
Praktiskt ETL-exempel: Komplett datapipeline
Nu ska vi bygga något mer realistiskt. Här är en komplett ETL-pipeline (Extract, Transform, Load) som visar hur Polars fungerar i ett verkligt scenario. Vi processar försäljningsdata från flera källor, rensar och transformerar datan, och skapar en analytisk rapport.
import polars as pl
from datetime import date, datetime
# ====================================================
# STEG 1: EXTRACT — Läsa data från olika källor
# ====================================================
# Läs försäljningsdata (lat — för maximal prestanda)
försäljning = pl.scan_csv(
"data/försäljning_2025_*.csv",
try_parse_dates=True
)
# Läs produktkatalog
produkter = pl.scan_parquet("data/produkter.parquet")
# Läs butiksdata
butiker = pl.scan_csv("data/butiker.csv")
# ====================================================
# STEG 2: TRANSFORM — Rensa och bearbeta data
# ====================================================
# Rensa försäljningsdata
försäljning_ren = (
försäljning
# Ta bort rader utan belopp
.filter(pl.col("belopp").is_not_null() & (pl.col("belopp") > 0))
# Standardisera kolumnnamn
.rename({"butik_nr": "butik_id", "prod_kod": "produkt_id"})
# Lägg till tidsbaserade kolumner
.with_columns(
pl.col("datum").dt.year().alias("år"),
pl.col("datum").dt.month().alias("månad"),
pl.col("datum").dt.quarter().alias("kvartal"),
pl.col("datum").dt.weekday().alias("veckodag"),
# Är det helg?
(pl.col("datum").dt.weekday() >= 5).alias("helg"),
)
# Ta bort eventuella dubbletter
.unique(subset=["transaktions_id"])
)
# Berika med produktinformation
berikad = (
försäljning_ren
.join(produkter, on="produkt_id", how="left")
.join(butiker, on="butik_id", how="left")
# Beräkna härledda mått
.with_columns(
(pl.col("belopp") * pl.col("antal")).alias("total_intäkt"),
(pl.col("belopp") * pl.col("antal") * pl.col("marginal"))
.alias("bruttovinst"),
# Klassificera försäljningens storlek
pl.when(pl.col("belopp") * pl.col("antal") > 10000)
.then(pl.lit("Stor"))
.when(pl.col("belopp") * pl.col("antal") > 1000)
.then(pl.lit("Medel"))
.otherwise(pl.lit("Liten"))
.alias("försäljningsklass")
)
)
# ====================================================
# STEG 3: AGGREGERA — Skapa analytiska sammanställningar
# ====================================================
# Månatlig sammanställning per butik och kategori
månatlig_rapport = (
berikad
.group_by("år", "månad", "butik_namn", "produktkategori")
.agg(
# Intäkter
pl.col("total_intäkt").sum().alias("total_intäkt"),
pl.col("bruttovinst").sum().alias("total_bruttovinst"),
# Antal transaktioner
pl.len().alias("antal_transaktioner"),
# Genomsnittligt ordervärde
pl.col("total_intäkt").mean().alias("snitt_ordervärde"),
# Antal unika kunder
pl.col("kund_id").n_unique().alias("unika_kunder"),
# Helgförsäljning vs vardagsförsäljning
pl.col("total_intäkt")
.filter(pl.col("helg"))
.sum()
.alias("helg_intäkt"),
pl.col("total_intäkt")
.filter(~pl.col("helg"))
.sum()
.alias("vardag_intäkt"),
)
.with_columns(
# Bruttomarginal i procent
(pl.col("total_bruttovinst") / pl.col("total_intäkt") * 100)
.round(2)
.alias("bruttomarginal_pct"),
# Helgandel i procent
(pl.col("helg_intäkt") / pl.col("total_intäkt") * 100)
.round(1)
.alias("helgandel_pct")
)
.sort("år", "månad", "total_intäkt", descending=[False, False, True])
)
# ====================================================
# STEG 4: LOAD — Spara resultaten
# ====================================================
# Kör hela pipelinen och spara till Parquet
månatlig_rapport.collect().write_parquet(
"output/månatlig_försäljningsrapport.parquet"
)
# Alternativt: använd sink för att strömma direkt till disk
månatlig_rapport.sink_parquet("output/rapport_streaming.parquet")
# Spara även som CSV för icke-tekniska mottagare
rapport_df = månatlig_rapport.collect()
rapport_df.write_csv("output/månatlig_försäljningsrapport.csv")
# Skriv ut en sammanfattning
print("Pipeline slutförd!")
print(f"Antal rader i rapporten: {rapport_df.height}")
print(f"Antal kolumner: {rapport_df.width}")
print(f"\nTopp 5 butiker per total intäkt:")
print(
rapport_df
.group_by("butik_namn")
.agg(pl.col("total_intäkt").sum())
.sort("total_intäkt", descending=True)
.head(5)
)
Den här pipelinen visar Polars styrkor i praktiken. Lat utvärdering ser till att hela frågeplanen optimeras innan data processas, join-operationer körs parallellt, och aggregeringar utnyttjar alla tillgängliga CPU-kärnor. För en datamängd med 50 miljoner försäljningsrader kan den här pipelinen köras på under 10 sekunder — en bråkdel av vad Pandas skulle ta.
Lägg märke till hur hela pipelinen är deklarativ. Vi beskriver vad vi vill göra, inte hur. Polars optimerare bestämmer själv den mest effektiva exekveringsordningen.
GPU-acceleration
För riktigt extrema arbetsbelastningar erbjuder Polars numera GPU-acceleration via cuDF-backend. Det är enkelt att komma igång:
# Installera GPU-stöd
# pip install 'polars[gpu]'
import polars as pl
# Aktivera GPU-motorn för en specifik fråga
resultat = (
pl.scan_parquet("stor_datamängd.parquet")
.filter(pl.col("värde") > 1000)
.group_by("kategori")
.agg(pl.col("värde").sum())
.collect(engine="gpu") # Kör på GPU istället för CPU
)
print(resultat)
Slutsats och rekommendationer
Polars har under 2025 och 2026 cementerat sin position som det ledande alternativet till Pandas för prestandakritisk databearbetning i Python. Med version 1.38.1 är biblioteket stabilt, funktionsrikt och redo för produktion. Med över 30 000 stjärnor på GitHub och hundratals aktiva bidragsgivare är Polars inte längre ett nischprojekt — det är en central del av Python-dataekosystemet.
Sammanfattning av Polars styrkor
- Hastighet: 3–10x snabbare än Pandas på stora datamängder, tack vare Rust-kärnan och flertrådskörning.
- Minneseffektivitet: 2–4x RAM vs datastorleken, jämfört med Pandas 5–10x. Arrow-kolumnformatet minimerar onödiga kopieringar.
- Lat utvärdering: Automatisk frågeoptimering med predikatpushdown, projektionspushdown och mer.
- Streaming: Bearbeta datamängder som är större än tillgängligt RAM.
- Modernt API: Expression API:t är deklarativt, rent och mindre felbenäget än Pandas indexbaserade modell.
- Ingen GIL-begränsning: Rust-kärnan påverkas inte av Pythons Global Interpreter Lock.
- GPU-stöd: cuDF-backend för GPU-acceleration vid extrema arbetsbelastningar.
Vår rekommendation
För nya projekt 2026 rekommenderar vi starkt att börja med Polars som ditt standard-DataFrame-bibliotek. Inlärningskurvan är hanterbar (särskilt om du redan kan Pandas), och prestandavinsterna är betydande. Även för befintliga Pandas-projekt är det värt att överväga en gradvis migrering — du kan blanda båda biblioteken i samma kodbas och konvertera mellan dem vid behov.
Polars är inte bara snabbare — det uppmuntrar också till bättre programmeringspraxis. Det oföränderliga API:t, strikt typning och den uttrycksbaserade modellen leder till kod som är lättare att testa, felsöka och underhålla. Jag har själv märkt att mina Polars-pipelines tenderar att ha färre buggar jämfört med motsvarande Pandas-kod.
Det är också värt att nämna att Polars integrerar fint med resten av Python-ekosystemet. Du kan konvertera mellan Polars och Pandas DataFrames, använda Polars med scikit-learn, och exportera till Parquet, CSV, JSON och Arrow. Populära visualiseringsbibliotek som Plotly, Altair och Matplotlib har börjat lägga till direkt Polars-stöd, så behovet av att konvertera via Pandas minskar för varje dag.
Börja med att installera Polars idag, kör dina befintliga Pandas-skript genom översättningen i migrationsavsnittet ovan, och upplev skillnaden själv. Vi är ganska övertygade om att du inte kommer vilja gå tillbaka.