Dacă lucrați cu date în Python în 2026, sunt șanse mari că ați auzit deja de Polars. Poate l-ați văzut menționat pe Twitter, pe Reddit, în prezentări de la conferințe de data science — sau poate un coleg v-a spus, cu un entuziasm aproape contagios, că „Polars e de 10 ori mai rapid decât pandas". Și, surprinzător, omul nu exagerează cu mult.
Polars este o bibliotecă de DataFrame-uri scrisă în Rust, construită pe fundația Apache Arrow, și proiectată de la zero pentru performanță pe hardware modern. Nu e un wrapper peste NumPy ca pandas. Nu e un port dintr-un alt limbaj. E, sincer, o reimaginare completă a ceea ce poate fi o bibliotecă de procesare a datelor. Iar acum, la versiunea 1.38, a ajuns la un nivel de maturitate care te face să vrei s-o folosești în producție fără nicio ezitare.
În acest ghid vom acoperi cam tot ce trebuie să știți pentru a începe cu Polars: de la instalare și concepte fundamentale, la expresii, evaluare lazy, operații avansate și comparații concrete cu pandas. Dacă ați citit deja ghidul nostru despre Pandas 3.0, articolul de față e complementul natural — vă arată alternativa modernă, cea care câștigă tot mai mult teren.
De Ce Polars? Motivația și Filosofia de Design
Pandas a fost creat în 2008. Gândiți-vă puțin la asta — într-o lume cu procesoare single-core și seturi de date care încăpeau confortabil în RAM. Era un instrument revoluționar pentru epoca respectivă, fără discuție.
Dar lumea s-a schimbat destul de radical de atunci. Procesoarele moderne au 8, 16 sau chiar 64 de nuclee, datele ajung la gigabytes sau terabytes, și ne așteptăm la rezultate în secunde, nu minute.
Polars a fost construit cu aceste realități în minte. Iată principiile fundamentale care stau la baza designului:
- Paralelism automat — Polars utilizează toate nucleele disponibile fără nicio configurare din partea voastră. Nu trebuie să vă bateți capul cu threading sau multiprocessing.
- Evaluare lazy cu optimizare automată — Polars construiește un plan de execuție pe care îl optimizează înainte de a rula efectiv operațiile, cam cum face un motor SQL.
- Format columnar Apache Arrow — Datele sunt stocate în format columnar nativ, ceea ce oferă eficiență maximă atât pentru memorie, cât și pentru operațiile analitice.
- Procesare streaming (out-of-core) — Polars poate procesa seturi de date mai mari decât memoria RAM disponibilă, procesându-le în fragmente. Da, ați citit bine.
- Zero-copy interoperability — Datorită Apache Arrow, datele pot fi partajate între Polars, pandas, PyArrow și alte biblioteci fără copiere.
Instalare și Primii Pași
Instalarea e simplă, direct din PyPI — nimic complicat:
pip install polars
# Sau cu extensii opționale
pip install "polars[all]" # Toate extensiile
pip install "polars[numpy]" # Interop cu NumPy
pip install "polars[pandas]" # Interop cu pandas
pip install "polars[pyarrow]" # Suport PyArrow extins
Hai să verificăm instalarea și să creăm primul DataFrame:
import polars as pl
print(f"Versiune Polars: {pl.__version__}")
# Crearea unui DataFrame
df = pl.DataFrame({
"nume": ["Ana", "Bogdan", "Cristina", "Dan", "Elena"],
"departament": ["IT", "Vânzări", "IT", "HR", "Vânzări"],
"salariu": [7500, 6200, 8100, 5800, 6700],
"experienta_ani": [5, 3, 8, 2, 4]
})
print(df)
Rezultatul arată imediat diferit față de pandas — Polars afișează tipurile de date sub fiecare coloană, ceea ce personal mi se pare un detaliu foarte util:
shape: (5, 4)
┌──────────┬─────────────┬─────────┬─────────────────┐
│ nume ┆ departament ┆ salariu ┆ experienta_ani │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 ┆ i64 │
╞══════════╪═════════════╪═════════╪═════════════════╡
│ Ana ┆ IT ┆ 7500 ┆ 5 │
│ Bogdan ┆ Vânzări ┆ 6200 ┆ 3 │
│ Cristina ┆ IT ┆ 8100 ┆ 8 │
│ Dan ┆ HR ┆ 5800 ┆ 2 │
│ Elena ┆ Vânzări ┆ 6700 ┆ 4 │
└──────────┴─────────────┴─────────┴─────────────────┘
Conceptele Fundamentale: Expresii și Contexte
Asta e partea care la început m-a cam dat peste cap, dar odată ce o înțelegeți, totul face sens. Dacă pandas se bazează pe indexare și metode aplicate pe Series, Polars folosește un concept complet diferit: expresii (expressions) evaluate în contexte (contexts). E practic un DSL (Domain Specific Language) propriu, proiectat să fie simultan expresiv pentru programator și optimizabil pentru motor.
Ce sunt expresiile?
O expresie în Polars e o reprezentare lazy a unei transformări de date. Sunt modulare — le combinați, le înlănțuiți și le compuneți ca pe niște blocuri de construcție:
import polars as pl
# Expresii simple
pl.col("salariu") # Referință la o coloană
pl.col("salariu") * 12 # Salariul anual
pl.col("salariu").mean() # Media salariilor
pl.col("nume").str.to_uppercase() # Nume cu majuscule
# Expresii compuse
(pl.col("salariu") - pl.col("salariu").mean()) / pl.col("salariu").std()
# Z-score al salariului
Ce sunt contextele?
Un context definește unde și cum se evaluează expresiile. Sunt patru contexte principale și merită să le cunoașteți pe fiecare:
# 1. select() — selectează și transformă coloane
df.select(
pl.col("nume"),
pl.col("salariu").alias("salariu_lunar"),
(pl.col("salariu") * 12).alias("salariu_anual")
)
# 2. with_columns() — adaugă sau modifică coloane, păstrând restul
df.with_columns(
(pl.col("salariu") * 12).alias("salariu_anual"),
(pl.col("experienta_ani") >= 5).alias("senior")
)
# 3. filter() — filtrează rânduri
df.filter(
(pl.col("departament") == "IT") &
(pl.col("salariu") > 7000)
)
# 4. group_by().agg() — grupare și agregare
df.group_by("departament").agg(
pl.col("salariu").mean().alias("salariu_mediu"),
pl.col("nume").count().alias("numar_angajati"),
pl.col("experienta_ani").max().alias("experienta_maxima")
)
Frumusețea acestui design (și nu folosesc cuvântul ăsta des când vorbesc despre API-uri) e că motorul Polars poate paraleliza și optimiza expresiile independent. Trei expresii într-un select()? Polars le rulează simultan pe nuclee diferite, fără nicio intervenție din partea voastră.
Evaluare Lazy: Puterea Reală a Polars
Acesta e conceptul care, sincer, diferențiază Polars de orice altă bibliotecă de DataFrame-uri din Python. Evaluarea lazy (leneșă) înseamnă că Polars nu execută operațiile imediat — în schimb, construiește un plan de execuție (query plan) pe care îl optimizează înainte de a rula.
Sună abstract? Hai să vedem concret.
Eager vs. Lazy
# EAGER — execuție imediată (similar cu pandas)
df = pl.read_csv("date_vanzari.csv")
rezultat = df.filter(pl.col("valoare") > 1000).select("produs", "valoare")
# LAZY — construiește un plan, apoi optimizează și execută
lf = pl.scan_csv("date_vanzari.csv")
rezultat = (
lf.filter(pl.col("valoare") > 1000)
.select("produs", "valoare")
.collect() # Aici se execută totul
)
Diferența critică: în modul lazy, Polars aplică optimizări automate asupra planului de execuție. Și nu-s optimizări superficiale:
- Predicate pushdown — Filtrul
valoare > 1000este „împins" la citirea fișierului. Polars nu citește întregul CSV în memorie și apoi filtrează — ci filtrează pe măsură ce citește. Diferența e enormă. - Projection pushdown — Dacă selectați doar 2 din 50 de coloane, Polars citește doar acele 2 coloane din fișier. Restul? Ignorate complet.
- Constant folding — Expresiile constante sunt pre-calculate.
- Join reordering — Ordinea join-urilor e reorganizată pentru eficiență maximă.
Vizualizarea planului de execuție
Un lucru pe care-l apreciez mult: puteți inspecta planul de execuție pentru a înțelege exact ce face Polars „sub capotă":
lf = pl.scan_csv("date_vanzari.csv")
plan = (
lf.filter(pl.col("valoare") > 1000)
.group_by("categorie")
.agg(pl.col("valoare").sum())
.sort("valoare", descending=True)
)
# Planul neoptimizat
print(plan.explain())
# Planul optimizat (ce se execută de fapt)
print(plan.explain(optimized=True))
scan_csv vs. read_csv
Asta e una dintre cele mai importante distincții practice, și totuși o văd neglijată surprinzător de des:
# read_csv() — citește TOTUL în memorie (eager)
df = pl.read_csv("fisier_mare.csv") # 10GB în RAM? Probabil nu e o idee bună.
# scan_csv() — creează un LazyFrame, nu citește nimic încă
lf = pl.scan_csv("fisier_mare.csv") # Instant, 0 bytes utilizați
# Polars citește doar ce e necesar la .collect()
rezultat = (
lf.filter(pl.col("status") == "activ")
.select("id", "nume", "valoare")
.collect() # Citește doar 3 coloane, doar rândurile active
)
Suport similar există pentru Parquet (scan_parquet()), IPC/Feather (scan_ipc()) și NDJSON (scan_ndjson()).
Operații Practice: De la Bază la Avansat
Citirea și scrierea datelor
Polars suportă o gamă largă de formate. Iată cele mai utilizate:
# CSV
df = pl.read_csv("date.csv")
df.write_csv("rezultat.csv")
# Parquet (recomandat pentru performanță)
df = pl.read_parquet("date.parquet")
df.write_parquet("rezultat.parquet")
# Din pandas
import pandas as pd
df_pandas = pd.read_excel("date.xlsx")
df_polars = pl.from_pandas(df_pandas)
# Către pandas
df_pandas = df_polars.to_pandas()
# Citire directă din URL
df = pl.read_csv("https://example.com/date.csv")
Manipularea coloanelor
df = pl.DataFrame({
"produs": ["Laptop", "Mouse", "Tastatură", "Monitor", "Căști"],
"pret": [4500, 120, 350, 1800, 250],
"cantitate": [10, 150, 80, 25, 60],
"categorie": ["Electronice", "Periferice", "Periferice", "Electronice", "Periferice"]
})
# Adăugarea de coloane noi
df = df.with_columns(
(pl.col("pret") * pl.col("cantitate")).alias("valoare_totala"),
(pl.col("pret") * 1.19).round(2).alias("pret_cu_tva"),
pl.col("produs").str.to_uppercase().alias("produs_upper")
)
# Redenumirea coloanelor
df = df.rename({"pret": "pret_unitar", "cantitate": "stoc"})
# Ștergerea coloanelor
df = df.drop("produs_upper")
# Selectarea coloanelor cu regex
df.select(pl.col("^pret.*$")) # Toate coloanele care încep cu "pret"
# Selectarea coloanelor după tip
df.select(pl.col(pl.Int64)) # Doar coloanele de tip Int64
df.select(pl.col(pl.Utf8)) # Doar coloanele de tip string
Filtrare avansată
Filtrarea e intuitivă și, odată ce vă obișnuiți cu sintaxa, o să vă întrebați de ce nu ați trecut la Polars mai devreme:
# Condiții multiple
df.filter(
(pl.col("pret_unitar") > 200) &
(pl.col("categorie") == "Periferice")
)
# Filtrare cu is_in (echivalentul isin din pandas)
df.filter(pl.col("produs").is_in(["Laptop", "Monitor"]))
# Filtrare cu condiții pe string
df.filter(pl.col("produs").str.contains("(?i)^[LM]"))
# Filtrare cu between
df.filter(pl.col("pret_unitar").is_between(100, 500))
Sortare
# Sortare simplă
df.sort("pret_unitar", descending=True)
# Sortare pe mai multe coloane
df.sort(["categorie", "pret_unitar"], descending=[False, True])
# Sortare cu valori nule la final
df.sort("pret_unitar", nulls_last=True)
Group By și Agregări: Unde Polars Chiar Strălucește
Operațiile de grupare și agregare sunt, fără exagerare, unul dintre punctele forte ale Polars. Nu doar că sunt mai rapide decât în pandas (de 2-6 ori, în funcție de setul de date), dar și sintaxa e mai clară. M-am obișnuit atât de repede cu ea încât acum codul pandas mi se pare verbos.
vanzari = pl.DataFrame({
"data": ["2026-01-15", "2026-01-15", "2026-01-16", "2026-01-16", "2026-01-17"],
"produs": ["A", "B", "A", "C", "B"],
"regiune": ["Nord", "Sud", "Nord", "Nord", "Sud"],
"cantitate": [100, 50, 75, 200, 120],
"pret_unitar": [25.0, 45.0, 25.0, 15.0, 45.0]
}).with_columns(pl.col("data").str.to_date())
# Agregare simplă
vanzari.group_by("produs").agg(
pl.col("cantitate").sum().alias("total_cantitate"),
pl.col("cantitate").mean().alias("medie_cantitate"),
pl.len().alias("numar_tranzactii")
)
# Agregare pe mai multe coloane
vanzari.group_by(["regiune", "produs"]).agg(
(pl.col("cantitate") * pl.col("pret_unitar")).sum().alias("venit_total"),
pl.col("cantitate").sum().alias("unitati_vandute")
)
# Agregări multiple pe aceeași coloană
vanzari.group_by("regiune").agg(
pl.col("cantitate").min().alias("min_cantitate"),
pl.col("cantitate").max().alias("max_cantitate"),
pl.col("cantitate").median().alias("mediana_cantitate"),
pl.col("cantitate").std().alias("deviatia_standard"),
pl.col("cantitate").quantile(0.9).alias("percentila_90")
)
Window Functions: Agregări fără Pierderea Rândurilor
Funcțiile de fereastră (window functions) sunt, din experiența mea, una dintre cele mai puternice și subutilizate caracteristici ale Polars. Permit efectuarea de agregări peste grupuri, dar fără a reduce numărul de rânduri — rezultatul agregat e atribuit fiecărui rând din grup.
# Adăugarea unei coloane cu media salariului per departament
df.with_columns(
pl.col("salariu").mean().over("departament").alias("medie_dept"),
pl.col("salariu").rank().over("departament").alias("rang_in_dept"),
(pl.col("salariu") / pl.col("salariu").sum().over("departament") * 100)
.round(1)
.alias("procent_din_dept")
)
Funcția .over() e echivalentul transform() din pandas, dar mult mai intuitivă și mai ușor de compus cu alte expresii:
# Diferența față de media grupului
df.with_columns(
(pl.col("salariu") - pl.col("salariu").mean().over("departament"))
.alias("diferenta_fata_de_medie")
)
# Running total (sumă cumulativă) per grup
vanzari.sort("data").with_columns(
pl.col("cantitate").cum_sum().over("produs").alias("total_cumulativ")
)
Join-uri: Combinarea Seturilor de Date
Polars suportă toate tipurile standard de join-uri, plus câteva variante specializate pe care le veți aprecia enorm dacă lucrați cu date temporale:
angajati = pl.DataFrame({
"id_angajat": [1, 2, 3, 4],
"nume": ["Ana", "Bogdan", "Cristina", "Dan"],
"id_departament": [10, 20, 10, 30]
})
departamente = pl.DataFrame({
"id_dept": [10, 20, 40],
"nume_dept": ["IT", "Vânzări", "Marketing"]
})
# Inner join
angajati.join(
departamente,
left_on="id_departament",
right_on="id_dept",
how="inner"
)
# Left join (păstrează toți angajații)
angajati.join(
departamente,
left_on="id_departament",
right_on="id_dept",
how="left"
)
# Cross join (produs cartezian)
angajati.select("nume").join(
departamente.select("nume_dept"),
how="cross"
)
Join-uri temporale (asof join)
Acesta e un tip de join extrem de util pentru date cu timestamp-uri. Nu căutați o potrivire exactă, ci cea mai apropiată valoare anterioară — lucru frecvent întâlnit în domeniul financiar, de exemplu:
tranzactii = pl.DataFrame({
"timestamp": ["2026-01-15 10:30:00", "2026-01-15 14:15:00", "2026-01-16 09:00:00"],
"produs": ["AAPL", "GOOG", "AAPL"],
"cantitate": [100, 50, 75]
}).with_columns(pl.col("timestamp").str.to_datetime())
preturi = pl.DataFrame({
"timestamp": ["2026-01-15 10:00:00", "2026-01-15 11:00:00", "2026-01-16 10:00:00"],
"produs": ["AAPL", "AAPL", "AAPL"],
"pret": [185.50, 186.20, 187.00]
}).with_columns(pl.col("timestamp").str.to_datetime())
# Asof join — găsește cel mai recent preț disponibil
tranzactii.join_asof(
preturi,
on="timestamp",
by="produs",
strategy="backward" # Cel mai recent preț anterior tranzacției
)
Procesarea Stringurilor și a Datelor Temporale
Operații pe stringuri
Polars vine cu un namespace .str bogat, care acoperă cam tot ce ați putea avea nevoie:
df = pl.DataFrame({
"email": ["[email protected]", "[email protected]", "[email protected]"],
"descriere": [" Manager IT ", "Analist date junior", "Senior Developer Python"]
})
df.with_columns(
# Extragere domeniu din email
pl.col("email").str.extract(r"@(.+)$", 1).alias("domeniu"),
# Curățare whitespace
pl.col("descriere").str.strip_chars().alias("descriere_curata"),
# Verificare pattern
pl.col("email").str.contains(r"\.ro$").alias("email_romania"),
# Înlocuire
pl.col("descriere").str.replace(r"(?i)senior", "Sr.").alias("descriere_scurta"),
# Split și acces la element
pl.col("email").str.split("@").list.first().alias("username")
)
Operații temporale
df_temporal = pl.DataFrame({
"data_comanda": ["2026-01-15", "2026-02-20", "2025-12-01", "2026-01-30"],
"valoare": [1500, 2300, 890, 4100]
}).with_columns(pl.col("data_comanda").str.to_date())
df_temporal.with_columns(
pl.col("data_comanda").dt.month().alias("luna"),
pl.col("data_comanda").dt.weekday().alias("zi_saptamana"),
pl.col("data_comanda").dt.quarter().alias("trimestru"),
(pl.col("data_comanda") + pl.duration(days=30)).alias("data_livrare_estimata"),
(pl.date(2026, 12, 31) - pl.col("data_comanda")).dt.total_days().alias("zile_pana_sf_an")
)
# Resampling temporal
vanzari_zilnice = (
vanzari.sort("data")
.group_by_dynamic("data", every="1w")
.agg(
pl.col("cantitate").sum().alias("total_saptamanal"),
pl.col("pret_unitar").mean().alias("pret_mediu")
)
)
Polars vs. Pandas: Comparație Practică
Bun, hai să comparăm direct aceleași operații în cele două biblioteci. Cred că diferențele de sintaxă și abordare vorbesc de la sine:
Citirea și filtrarea datelor
# === PANDAS ===
import pandas as pd
df_pd = pd.read_csv("vanzari.csv")
rezultat_pd = df_pd[
(df_pd["valoare"] > 1000) &
(df_pd["regiune"] == "Nord")
][["produs", "valoare", "data"]]
# === POLARS (eager) ===
import polars as pl
df_pl = pl.read_csv("vanzari.csv")
rezultat_pl = df_pl.filter(
(pl.col("valoare") > 1000) &
(pl.col("regiune") == "Nord")
).select("produs", "valoare", "data")
# === POLARS (lazy — recomandat) ===
rezultat_lazy = (
pl.scan_csv("vanzari.csv")
.filter(
(pl.col("valoare") > 1000) &
(pl.col("regiune") == "Nord")
)
.select("produs", "valoare", "data")
.collect()
)
Grupare și agregare
# === PANDAS ===
rezultat_pd = (
df_pd.groupby("categorie")
.agg(
total_vanzari=("valoare", "sum"),
numar_tranzactii=("valoare", "count"),
valoare_medie=("valoare", "mean")
)
.sort_values("total_vanzari", ascending=False)
)
# === POLARS ===
rezultat_pl = (
df_pl.group_by("categorie")
.agg(
pl.col("valoare").sum().alias("total_vanzari"),
pl.len().alias("numar_tranzactii"),
pl.col("valoare").mean().alias("valoare_medie")
)
.sort("total_vanzari", descending=True)
)
Pivotare și reshape
# === PANDAS ===
pivot_pd = df_pd.pivot_table(
values="valoare",
index="regiune",
columns="categorie",
aggfunc="sum",
fill_value=0
)
# === POLARS ===
pivot_pl = df_pl.pivot(
on="categorie",
index="regiune",
values="valoare",
aggregate_function="sum"
).fill_null(0)
Benchmark-uri Reale: Cât de Rapid Este Polars de Fapt?
Știu, știu — toată lumea vorbește despre performanță. Dar cifrele chiar sunt impresionante. Iată rezultatele tipice din benchmark-uri independente din 2025-2026:
| Operație | Pandas (timp) | Polars (timp) | Speedup |
|---|---|---|---|
| Citire CSV (1GB) | 12.3s | 2.1s | ~5.9x |
| Filter pe 1GB | 2.8s | 0.61s | ~4.6x |
| GroupBy + Agregare | 606ms | 107ms | ~5.7x |
| Join (10K rânduri) | 1.97s | 0.14s | ~13.8x |
| Sort (100M rânduri) | 18.5s | 1.89s | ~9.8x |
Mai mult, Polars consumă de aproximativ 8 ori mai puțină energie decât pandas pentru seturi de date mari, conform benchmark-urilor oficiale. Asta contează nu doar pentru facturile de curent, ci și pentru sustenabilitate — un aspect pe care nu-l mai putem ignora.
Câteva observații importante despre aceste numere:
- Speedup-ul crește odată cu dimensiunea datelor — la seturi mici (sub 10.000 de rânduri), diferența e minimă și aproape neglijabilă.
- Modul lazy oferă cele mai mari câștiguri, datorită optimizărilor automate.
- Join-urile sunt operația unde Polars excelează cel mai mult — algoritmul de hashing din Rust e extrem de eficient.
Suport GPU: Accelerare cu NVIDIA RAPIDS
Începând din 2024, Polars oferă suport experimental (beta deschis) pentru accelerare pe GPU prin integrarea cu NVIDIA RAPIDS cuDF. Practic, puteți rula operații pe placa video pentru speedup-uri și mai mari pe seturi de date masive.
# Necesită GPU NVIDIA și instalarea cuDF
# pip install polars[gpu]
lf = pl.scan_parquet("date_masive.parquet")
rezultat = (
lf.filter(pl.col("status") == "activ")
.group_by("categorie")
.agg(pl.col("valoare").sum())
.sort("valoare", descending=True)
.collect(engine="gpu") # Execuție pe GPU!
)
Ce-mi place la această integrare e că optimizatorul Polars reduce volumul de lucru înainte de a trimite operațiile pe GPU, deci nu risipim memoria VRAM pe date inutile. E un detaliu de design inteligent.
Integrarea cu Ecosistemul Python
O întrebare pe care o aud mereu: „Pot folosi Polars cu scikit-learn, matplotlib sau alte biblioteci?" Răspunsul scurt: da. Răspunsul lung: da, dar cu câteva mici considerații.
Cu matplotlib și seaborn
import matplotlib.pyplot as plt
import polars as pl
df = pl.DataFrame({
"luna": ["Ian", "Feb", "Mar", "Apr", "Mai"],
"vanzari": [15000, 18000, 22000, 19500, 25000]
})
# Polars -> matplotlib direct (prin conversie la Python lists)
plt.figure(figsize=(10, 6))
plt.bar(df["luna"].to_list(), df["vanzari"].to_list())
plt.title("Vânzări Lunare")
plt.ylabel("Valoare (RON)")
plt.show()
# Sau convertiți la pandas pentru seaborn
import seaborn as sns
sns.barplot(data=df.to_pandas(), x="luna", y="vanzari")
Cu scikit-learn
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
import polars as pl
import numpy as np
# Pregătirea datelor cu Polars (rapid)
df = pl.scan_parquet("dataset_mare.parquet")
df_pregatit = (
df.filter(pl.col("target").is_not_null())
.with_columns(
pl.col("categorie").cast(pl.Categorical).to_physical().alias("categorie_cod")
)
.select("feature_1", "feature_2", "categorie_cod", "target")
.collect()
)
# Conversie la NumPy pentru scikit-learn
X = df_pregatit.select("feature_1", "feature_2", "categorie_cod").to_numpy()
y = df_pregatit["target"].to_numpy()
# Antrenare model
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
model = RandomForestClassifier(n_estimators=100)
model.fit(X_train, y_train)
print(f"Acuratețe: {model.score(X_test, y_test):.4f}")
Când Să Folosiți Polars vs. Pandas
Nu e vorba de „Polars sau pandas" — ci de „Polars și pandas". Fiecare bibliotecă are locul ei, și sincer, cel mai bun răspuns e de obicei „depinde".
Alegeți Polars când:
- Lucrați cu seturi de date mari (peste 1GB sau milioane de rânduri)
- Performanța e critică (pipeline-uri de producție, ETL)
- Aveți nevoie de procesare out-of-core (date mai mari decât RAM)
- Operațiile sunt predominant transformări, agregări și join-uri
- Porniți un proiect nou și nu aveți cod pandas existent
Rămâneți cu pandas când:
- Aveți cod existent cu pandas care funcționează bine — dacă nu-i stricat, nu-l repara
- Folosiți intensiv analiză exploratorie în Jupyter (pandas e mai matur aici, deocamdată)
- Dependeți de biblioteci care necesită pandas (unele biblioteci ML/viz acceptă doar pandas)
- Seturile de date sunt mici (sub 100K rânduri) și timpul de execuție nu e o problemă
- Echipa are experiență solidă cu pandas și deadline-uri strânse
Abordarea hibridă (recomandată)
Sincer, asta e abordarea pe care o folosesc cel mai des în practică:
import polars as pl
import pandas as pd
# Pasul 1: Procesare grea cu Polars (rapid)
df_procesat = (
pl.scan_parquet("date_brute/*.parquet")
.filter(pl.col("anul") >= 2024)
.group_by(["regiune", "produs"])
.agg(
pl.col("vanzari").sum().alias("total"),
pl.col("vanzari").mean().alias("medie")
)
.sort("total", descending=True)
.collect()
)
# Pasul 2: Vizualizare și analiză finală cu pandas (ecosistem bogat)
df_pandas = df_procesat.to_pandas()
df_pandas.plot(kind="bar", x="regiune", y="total", figsize=(12, 6))
Sfaturi și Bune Practici pentru Polars
După câteva luni bune de utilizare intensivă, iată ce am învățat (uneori pe pielea mea):
- Folosiți modul lazy ori de câte ori e posibil. Serios, aproape orice pipeline beneficiază de optimizările automate. Începeți cu
scan_*și terminați cu.collect(). - Preferați Parquet în loc de CSV. Parquet e un format columnar, nativ pentru Polars. Citirea e de 5-10x mai rapidă, fișierele sunt de 3-5x mai mici, și tipurile de date sunt păstrate automat. Nu mai pierdeți timp cu parsarea CSV-urilor.
- Evitați iterarea pe rânduri. Dacă vă trece prin minte
for row in df.iter_rows(), opriți-vă. Aproape sigur există o expresie Polars care face același lucru, de 100x mai rapid. - Folosiți
.alias()pentru denumirea rezultatelor. Fiecare expresie într-unselect()sauwith_columns()ar trebui să aibă un alias clar — codul vostru din viitor vă va mulțumi. - Profitați de selecția avansată a coloanelor.
pl.col("^prefix_.*$"),pl.col(pl.Float64),cs.numeric()(cuimport polars.selectors as cs) — aceste facilități reduc enorm codul repetitiv. - Convertiți la pandas doar la final. Faceți toată procesarea grea în Polars, apoi convertiți doar setul de date final (de obicei mic) pentru vizualizare sau ML.
Migrarea de la Pandas: Ghid Rapid de Referință
Pentru cei care vin din pandas (adică probabil toată lumea), iată o referință rapidă cu echivalențele cele mai comune:
| Pandas | Polars |
|---|---|
df["col"] | df.select("col") sau df["col"] |
df[df["col"] > 5] | df.filter(pl.col("col") > 5) |
df["new"] = expr | df.with_columns(expr.alias("new")) |
df.groupby().agg() | df.group_by().agg() |
df.merge() | df.join() |
df.apply(func) | df.select(pl.col("col").map_elements(func)) |
df.fillna(val) | df.fill_null(val) |
df.dropna() | df.drop_nulls() |
df.sort_values() | df.sort() |
df.reset_index() | Nu e necesar — Polars nu are index |
Un aspect important de menționat: Polars nu are concept de index. Dacă veniți din pandas, asta e probabil cea mai mare schimbare mentală. În Polars, rândurile sunt identificate doar prin poziție, nu prin etichete. Și sincer? Pentru majoritatea cazurilor de utilizare, e mai simplu așa. Am petrecut ore întregi debuggind probleme cauzate de indexul pandas — cu Polars, pur și simplu nu mai există acea categorie de bug-uri.
Concluzie
Polars nu e doar „un pandas mai rapid" — e o regândire fundamentală a modului în care procesăm date tabulare în Python. Evaluarea lazy cu optimizare automată, paralelismul nativ, formatul columnar Arrow și sintaxa bazată pe expresii formează un pachet care, sincer, nu are echivalent în ecosistemul Python la momentul actual.
În 2026, Polars a ajuns la un nivel de maturitate care îl face o alegere serioasă pentru producție. Cu versiunea 1.38, API-ul e stabil, documentația e excelentă, și comunitatea crește vizibil de la o lună la alta. Suportul experimental pentru GPU deschide perspective și mai interesante pentru viitor.
Recomandarea mea? Începeți să învățați Polars acum. Nu trebuie să abandonați pandas — cele două biblioteci coexistă foarte bine. Dar pentru pipeline-uri noi, pentru procesarea seturilor mari de date, și pentru orice situație unde performanța contează cu adevărat, Polars e instrumentul potrivit. Viitorul procesării datelor în Python e paralel, lazy și columnar — iar Polars e în fruntea acestei revoluții.