Wprowadzenie — czym jest Polars i dlaczego warto go poznać?
Jeśli pracujesz z danymi w Pythonie, na pewno znasz pandasa. Przez ponad dekadę był absolutnym standardem — i nadal jest świetnym narzędziem, nie zrozum mnie źle. Ale jest rok 2026 i krajobraz przetwarzania danych zmienił się nie do poznania. Zbiory danych rosną, laptopy mają coraz więcej rdzeni, a my nadal przetwarzamy dane na jednym wątku? To trochę jak jazda Ferrari na pierwszym biegu.
I tutaj wchodzi Polars.
Polars to błyskawicznie szybka biblioteka DataFrame napisana w Rust, z natywnym API dla Pythona. W wersji 1.38 (luty 2026) oferuje wielowątkowe przetwarzanie, leniwą ewaluację (lazy evaluation), optymalizator zapytań i pamięciooszczędne kolumnowe przechowywanie danych oparte na Apache Arrow. A co najlepsze? Składnia jest intuicyjna i — szczerze mówiąc — często czystsza niż w pandasie. Przynajmniej według mnie.
W tym przewodniku pokażę Ci Polars od A do Z: od instalacji i tworzenia DataFrame, przez filtrowanie, grupowanie i łączenie danych, aż po zaawansowane techniki lazy evaluation i integrację z DuckDB. Każdy przykład kodu możesz skopiować i uruchomić od razu — testowałem je na Polars 1.38.
Instalacja i pierwsze kroki
Instalacja Polars
Instalacja jest banalnie prosta — wystarczy jedno polecenie:
pip install polars
Jeśli planujesz korzystać z integracji z Apache Arrow (np. do pracy z DuckDB albo formatem Parquet), warto zainstalować wersję z dodatkami:
pip install 'polars[pyarrow,xlsx2csv,fsspec]'
Szybka weryfikacja, czy wszystko gra:
import polars as pl
print(f"Polars wersja: {pl.__version__}")
# Polars wersja: 1.38.1
Konfiguracja wyświetlania
Polars domyślnie ładnie formatuje wyniki w terminalu, ale ja zazwyczaj na starcie zmieniam kilka ustawień — żeby widzieć więcej danych bez scrollowania:
import polars as pl
# Pokaż więcej wierszy i kolumn
pl.Config.set_tbl_rows(15)
pl.Config.set_tbl_cols(10)
pl.Config.set_fmt_str_lengths(80)
Tworzenie DataFrame — podstawy
W Polars DataFrame tworzymy najczęściej ze słownika. Klucze to nazwy kolumn, wartości to listy z danymi — nic skomplikowanego:
import polars as pl
df = pl.DataFrame({
"imie": ["Anna", "Bartek", "Celina", "Dawid", "Ewa"],
"wiek": [28, 35, 42, 31, 26],
"miasto": ["Warszawa", "Kraków", "Gdańsk", "Wrocław", "Poznań"],
"wynagrodzenie": [8500.0, 12000.0, 9800.0, 11500.0, 7200.0],
})
print(df)
# shape: (5, 4)
# ┌────────┬──────┬──────────┬───────────────┐
# │ imie ┆ wiek ┆ miasto ┆ wynagrodzenie │
# │ --- ┆ --- ┆ --- ┆ --- │
# │ str ┆ i64 ┆ str ┆ f64 │
# ╞════════╪══════╪══════════╪═══════════════╡
# │ Anna ┆ 28 ┆ Warszawa ┆ 8500.0 │
# │ Bartek ┆ 35 ┆ Kraków ┆ 12000.0 │
# │ Celina ┆ 42 ┆ Gdańsk ┆ 9800.0 │
# │ Dawid ┆ 31 ┆ Wrocław ┆ 11500.0 │
# │ Ewa ┆ 26 ┆ Poznań ┆ 7200.0 │
# └────────┴──────┴──────────┴───────────────┘
Zwróć uwagę na jedną istotną rzecz: Polars nie ma indeksu. Wiersz identyfikowany jest wyłącznie przez swoją pozycję. To eliminuje cały zestaw problemów związanych z .loc, .iloc i niesławnym SettingWithCopyWarning. Szczerze? To jedna z moich ulubionych różnic w porównaniu z pandasem.
Typy danych w Polars
Polars ma ścisły system typów — każda kolumna ma z góry określony typ danych. To zapobiega subtelnym błędom, które w pandasie potrafią się ukrywać godzinami (kto nie szukał kiedyś buga wynikającego z mieszania int i float?):
print(df.dtypes)
# [String, Int64, String, Float64]
print(df.schema)
# Schema({'imie': String, 'wiek': Int64, 'miasto': String, 'wynagrodzenie': Float64})
Najczęściej spotykane typy danych w Polars:
Int8,Int16,Int32,Int64— liczby całkowiteFloat32,Float64— liczby zmiennoprzecinkoweString(dawniejUtf8) — tekstBoolean— wartości logiczneDate,Datetime,Duration— daty i czasList,Struct— typy zagnieżdżone
Wczytywanie danych z plików
CSV
Wczytywanie pliku CSV wygląda bardzo podobnie jak w pandasie:
import polars as pl
# Tryb eager — wczytuje cały plik od razu
df = pl.read_csv("dane_sprzedazy.csv")
# Tryb lazy — buduje plan zapytania bez wczytywania danych
lf = pl.scan_csv("dane_sprzedazy.csv")
Ważna zasada, którą warto zapamiętać: Jeśli planujesz filtrować, grupować lub w jakikolwiek sposób przetwarzać dane, zawsze używaj scan_csv() zamiast read_csv(). Dlaczego? Bo scan_csv() pozwala Polars na optymalizację — np. wczyta tylko te kolumny i wiersze, których faktycznie potrzebujesz.
Uwaga na antywzorzec: wywołanie read_csv().lazy() wygląda na sprytne, ale najpierw wczytuje cały plik do pamięci, a dopiero potem przechodzi w tryb lazy. To mija się z celem.
Parquet
Polars świetnie radzi sobie z formatem Parquet — kolumnowym formatem binarnym, który jest znacznie szybszy i bardziej kompaktowy niż CSV:
# Wczytaj Parquet (eager)
df = pl.read_parquet("dane.parquet")
# Skanuj Parquet (lazy — zalecane)
lf = pl.scan_parquet("dane.parquet")
# Zapisz DataFrame do Parquet
df.write_parquet("wynik.parquet")
Mała rada praktyczna: jeśli pracujesz z dużymi plikami CSV, konwertuj je na Parquet raz, a potem analizuj dane z Parquet. Przyspieszenie wczytywania potrafi być nawet 10-krotne. Serio, to robi ogromną różnicę.
Excel, JSON, bazy danych
# Excel
df = pl.read_excel("raport.xlsx", sheet_name="Arkusz1")
# JSON Lines (NDJSON)
df = pl.read_ndjson("zdarzenia.jsonl")
# Zwykły JSON
import json
with open("dane.json") as f:
data = json.load(f)
df = pl.DataFrame(data)
Selekcja i filtrowanie danych
Wybieranie kolumn — select()
Dobra, przejdźmy do mięsa. W Polars do wybierania kolumn używamy metody select() w połączeniu z wyrażeniami pl.col():
import polars as pl
df = pl.DataFrame({
"imie": ["Anna", "Bartek", "Celina", "Dawid"],
"wiek": [28, 35, 42, 31],
"miasto": ["Warszawa", "Kraków", "Gdańsk", "Wrocław"],
"wynagrodzenie": [8500.0, 12000.0, 9800.0, 11500.0],
})
# Wybierz konkretne kolumny
wynik = df.select("imie", "wynagrodzenie")
print(wynik)
# Użyj wyrażeń do transformacji
wynik = df.select(
pl.col("imie"),
pl.col("wynagrodzenie").alias("pensja"),
(pl.col("wynagrodzenie") * 12).alias("roczne_wynagrodzenie"),
)
print(wynik)
Dodawanie kolumn — with_columns()
Metoda with_columns() dodaje nowe kolumny (lub nadpisuje istniejące) bez usuwania pozostałych. To coś, czego mi brakowało w pandasie — tam dodawanie kolumny z transformacją bywało dość karkołomne:
df = df.with_columns(
(pl.col("wynagrodzenie") * 12).alias("roczne"),
(pl.col("wiek") >= 30).alias("powyzej_30"),
)
print(df)
Filtrowanie wierszy — filter()
Filtrowanie w Polars jest czytelne i wydajne:
# Pojedynczy warunek
osoby_30_plus = df.filter(pl.col("wiek") > 30)
# Wiele warunków — operator & (AND) i | (OR)
dobrze_zarabiajacy_w_duzym_miescie = df.filter(
(pl.col("wynagrodzenie") > 10000) &
(pl.col("miasto").is_in(["Warszawa", "Kraków", "Wrocław"]))
)
# Filtrowanie po tekście
osoby_na_a = df.filter(pl.col("imie").str.starts_with("A"))
Ważna uwaga: w Polars nie używamy nawiasów kwadratowych do filtrowania jak w pandasie (df[df["wiek"] > 30]). Zamiast tego zawsze .filter() — jest bardziej czytelne i łatwiejsze do łączenia w łańcuchy operacji. Na początku może to wydawać się nieintuicyjne, ale po kilku dniach już nie chcesz wracać do starej składni.
Grupowanie i agregacja
Grupowanie danych to chleb powszedni analizy danych, więc to jest sekcja, na którą warto zwrócić szczególną uwagę. W Polars metoda group_by() jest niezwykle elastyczna:
import polars as pl
sprzedaz = pl.DataFrame({
"produkt": ["Laptop", "Telefon", "Laptop", "Tablet", "Telefon", "Laptop"],
"region": ["Północ", "Południe", "Południe", "Północ", "Północ", "Północ"],
"kwota": [4500, 2800, 5200, 1900, 3100, 4800],
"ilosc": [1, 2, 1, 3, 2, 1],
})
# Podstawowe grupowanie z wieloma agregacjami
wynik = sprzedaz.group_by("produkt").agg(
pl.col("kwota").sum().alias("suma_sprzedazy"),
pl.col("kwota").mean().alias("srednia_sprzedaz"),
pl.col("ilosc").sum().alias("laczna_ilosc"),
pl.len().alias("liczba_transakcji"),
)
print(wynik)
Zauważ, jak czytelnie wygląda składnia — każda agregacja to osobna linijka, z jasnym aliasem. W pandasie osiągnięcie tego samego wymaga albo słownika z lambda funkcjami, albo metody .agg() z dość zagmatwaną składnią.
Grupowanie po wielu kolumnach
# Grupowanie po produkcie i regionie
wynik = sprzedaz.group_by("produkt", "region").agg(
pl.col("kwota").sum().alias("suma"),
pl.col("kwota").max().alias("maks"),
)
print(wynik)
Sortowanie wyników
# Posortuj wynik malejąco po sumie sprzedaży
wynik = (
sprzedaz
.group_by("produkt")
.agg(pl.col("kwota").sum().alias("suma"))
.sort("suma", descending=True)
)
print(wynik)
Łączenie DataFrame — join()
Polars obsługuje wszystkie standardowe typy złączeń znane z SQL. Jeśli masz doświadczenie z SQL-em, poczujesz się tu jak w domu:
import polars as pl
pracownicy = pl.DataFrame({
"id": [1, 2, 3, 4],
"imie": ["Anna", "Bartek", "Celina", "Dawid"],
"dzial_id": [10, 20, 10, 30],
})
dzialy = pl.DataFrame({
"dzial_id": [10, 20, 40],
"nazwa_dzialu": ["IT", "Marketing", "Finanse"],
})
# LEFT JOIN — wszyscy pracownicy, nawet bez dopasowanego działu
wynik = pracownicy.join(dzialy, on="dzial_id", how="left")
print(wynik)
# shape: (4, 4)
# ┌─────┬────────┬──────────┬──────────────┐
# │ id ┆ imie ┆ dzial_id ┆ nazwa_dzialu │
# │ --- ┆ --- ┆ --- ┆ --- │
# │ i64 ┆ str ┆ i64 ┆ str │
# ╞═════╪════════╪══════════╪══════════════╡
# │ 1 ┆ Anna ┆ 10 ┆ IT │
# │ 2 ┆ Bartek ┆ 20 ┆ Marketing │
# │ 3 ┆ Celina ┆ 10 ┆ IT │
# │ 4 ┆ Dawid ┆ 30 ┆ null │
# └─────┴────────┴──────────┴──────────────┘
# INNER JOIN — tylko pasujące rekordy
wynik_inner = pracownicy.join(dzialy, on="dzial_id", how="inner")
print(wynik_inner)
Dostępne typy złączeń: inner, left, right, full, cross, semi i anti.
A teraz coś, co rzadko widać w tutorialach, ale co jest naprawdę przydatne w praktyce — złączenia semi i anti:
# SEMI JOIN — pracownicy, którzy mają pasujący dział (bez kolumn z prawej tabeli)
w_istniejacym_dziale = pracownicy.join(dzialy, on="dzial_id", how="semi")
# ANTI JOIN — pracownicy BEZ pasującego działu
bez_dzialu = pracownicy.join(dzialy, on="dzial_id", how="anti")
print(bez_dzialu)
# shape: (1, 3)
# Dawid z dzial_id=30, którego nie ma w tabeli działów
anti join to moja tajna broń przy walidacji danych — szybko znajduję rekordy, które „nie pasują" do tabeli referencyjnej.
Lazy evaluation — potęga leniwych obliczeń
Okej, to jest funkcja, która naprawdę wyróżnia Polars na tle pandasa. I szczerze powiedziawszy, to był główny powód, dla którego sam przerzuciłem się na Polars w produkcyjnych potokach danych.
Lazy evaluation (leniwa ewaluacja) pozwala na budowanie całego potoku przetwarzania danych jako planu zapytania, który Polars optymalizuje przed wykonaniem. Brzmi abstrakcyjnie? Zaraz pokażę na przykładzie.
Czym jest LazyFrame?
LazyFrame to odpowiednik DataFrame, ale z jedną kluczową różnicą: nie przechowuje żadnych danych. Zamiast tego przechowuje zestaw instrukcji — plan zapytania — opisujący, co zrobić z danymi. Dopiero wywołanie .collect() uruchamia faktyczne obliczenia.
import polars as pl
# Tworzenie LazyFrame ze skanowania pliku
lf = pl.scan_csv("sprzedaz_2026.csv")
# Budowanie potoku — nic nie jest jeszcze wykonywane!
wynik = (
lf
.filter(pl.col("kwota") > 1000)
.select("produkt", "region", "kwota", "data")
.group_by("produkt", "region")
.agg(
pl.col("kwota").sum().alias("suma_sprzedazy"),
pl.col("kwota").mean().alias("srednia"),
pl.len().alias("liczba"),
)
.sort("suma_sprzedazy", descending=True)
)
# Podgląd planu zapytania
print(wynik.explain())
# Dopiero teraz dane są przetwarzane
df_wynik = wynik.collect()
print(df_wynik)
Jakie optymalizacje stosuje Polars?
Optymalizator zapytań w Polars automatycznie przepisuje Twój plan, żeby był jak najszybszy. To dzieje się w tle — nie musisz nic robić. Oto najważniejsze techniki, które stosuje:
- Projection pushdown — jeśli w całym potoku używasz tylko 3 z 20 kolumn, Polars wczyta z pliku tylko te 3 kolumny. W pandasie wczytałbyś wszystkie 20 i dopiero potem wybrał trzy.
- Predicate pushdown — filtry są „przesuwane" jak najgłębiej w planie. Jeśli filtrujesz po dacie, Polars przy odczycie Parquet pominie całe grupy wierszy (row groups) niespełniające warunku.
- Common subexpression elimination — jeśli to samo wyrażenie pojawia się wielokrotnie, Polars oblicza je tylko raz.
- Slice pushdown — operacje
.head()lub.limit()są przesuwane do źródła danych.
# Porównanie planów — przed i po optymalizacji
lf = pl.scan_parquet("duzy_plik.parquet")
plan = (
lf
.filter(pl.col("rok") == 2026)
.select("produkt", "kwota")
.group_by("produkt")
.agg(pl.col("kwota").sum())
)
# Plan nieoptymalizowany
print(plan.explain(optimized=False))
# Plan zoptymalizowany — zobacz, jak Polars przesuwa filtry i projekcje
print(plan.explain(optimized=True))
Warto poświęcić chwilę i porównać oba plany. Zobaczysz, jak Polars reorganizuje operacje — to naprawdę imponujące.
Debugowanie LazyFrame
Kilka przydatnych metod, które oszczędzą Ci czas przy debugowaniu leniwych obliczeń:
# Pobierz kilka wierszy bez wykonywania pełnego planu
probka = lf.fetch(5)
print(probka)
# Wyświetl schemat bez wykonywania obliczeń
print(lf.collect_schema())
# Podgląd zoptymalizowanego planu
print(lf.explain(optimized=True))
Streaming — przetwarzanie danych większych niż RAM
To jest coś, co mnie osobiście urzekło w Polars. Tryb streaming pozwala na przetwarzanie zbiorów danych, które są większe niż dostępna pamięć RAM. Na zwykłym laptopie!
import polars as pl
# Przetwarzanie ogromnego pliku w trybie streaming
wynik = (
pl.scan_csv("ogromny_plik_100gb.csv")
.filter(pl.col("status") == "aktywny")
.group_by("kategoria")
.agg(
pl.col("wartosc").sum().alias("suma"),
pl.len().alias("liczba"),
)
.sort("suma", descending=True)
.collect(streaming=True) # Kluczowy parametr!
)
print(wynik)
Cała magia kryje się w streaming=True. Polars przetwarza dane w partiach (chunks) zamiast wczytywać cały zbiór do pamięci. W praktyce oznacza to, że na laptopie z 16 GB RAM możesz przetworzyć plik o rozmiarze 100 GB i więcej. Próbowałem tego na prawdziwych danych i działa zaskakująco sprawnie.
Polars vs pandas — praktyczne porównanie
Wiem, że wielu z Was przychodzi tu z pandasa, więc oto zestawienie najczęstszych operacji w obu bibliotekach — żebyś od razu widział, co się zmienia:
# ============ PANDAS ============
import pandas as pd
pdf = pd.read_csv("dane.csv")
# Filtrowanie
pdf_filtered = pdf[pdf["wiek"] > 30]
# Nowa kolumna
pdf["roczne"] = pdf["wynagrodzenie"] * 12
# Grupowanie
pdf_grouped = pdf.groupby("miasto")["wynagrodzenie"].mean()
# Sortowanie
pdf_sorted = pdf.sort_values("wynagrodzenie", ascending=False)
# ============ POLARS ============
import polars as pl
pldf = pl.read_csv("dane.csv")
# Filtrowanie
pldf_filtered = pldf.filter(pl.col("wiek") > 30)
# Nowa kolumna
pldf = pldf.with_columns(
(pl.col("wynagrodzenie") * 12).alias("roczne")
)
# Grupowanie
pldf_grouped = pldf.group_by("miasto").agg(
pl.col("wynagrodzenie").mean()
)
# Sortowanie
pldf_sorted = pldf.sort("wynagrodzenie", descending=True)
Jak widzisz, składnia Polars jest trochę bardziej „gadatliwa", ale za to bardziej jednoznaczna. Nie ma zgadywania, czy df["col"] zwróci kopię czy widok — w Polars po prostu nie ma tego problemu.
Kiedy wybrać Polars, a kiedy pandas?
Nie chodzi o to, że Polars jest „lepszy" od pandasa w każdej sytuacji. Oto jak ja o tym myślę:
- Polars — duże zbiory danych (powyżej kilkuset MB), potoki ETL, projekty wymagające powtarzalności i wydajności, nowe projekty bez zależności od ekosystemu pandasa.
- pandas — szybka analiza eksploracyjna, małe zbiory danych, projekty głęboko zintegrowane ze scikit-learn lub innymi bibliotekami ML, sytuacje gdy cały zespół zna pandasa (i nie ma czasu na naukę nowego narzędzia).
- Oba razem — i to jest podejście, które sam stosuję najczęściej. Przetwarzaj dane w Polars, a potem konwertuj do pandasa tam, gdzie wymaga tego ekosystem:
df_pandas = df_polars.to_pandas().
Integracja z DuckDB
Polars i DuckDB to jedno z najciekawszych połączeń w ekosystemie danych w 2026 roku. DuckDB to analityczna baza danych SQL działająca w procesie (in-process), a dzięki wspólnemu formatowi Apache Arrow, wymiana danych między nimi jest praktycznie natychmiastowa — bez kopiowania pamięci.
import polars as pl
import duckdb
# Utwórz DataFrame w Polars
df = pl.DataFrame({
"produkt": ["Laptop", "Telefon", "Tablet", "Laptop", "Telefon"],
"kwota": [4500, 2800, 1900, 5200, 3100],
"region": ["Północ", "Południe", "Północ", "Południe", "Północ"],
})
# Odpytaj DataFrame Polars za pomocą SQL w DuckDB
wynik = duckdb.sql("""
SELECT
produkt,
SUM(kwota) AS suma_sprzedazy,
AVG(kwota) AS srednia_sprzedaz,
COUNT(*) AS liczba_transakcji
FROM df
GROUP BY produkt
ORDER BY suma_sprzedazy DESC
""").pl() # .pl() zwraca wynik jako Polars DataFrame
print(wynik)
Metoda .pl() na wyniku DuckDB automatycznie konwertuje go na Polars DataFrame. Fajne, prawda? Jeśli wolisz lazy, możesz też użyć .pl(lazy=True), żeby otrzymać LazyFrame.
Wyrażenia — serce Polars
System wyrażeń (expressions) to prawdziwa siła napędowa Polars. Wyrażenia są wektoryzowane — operują na całych kolumnach naraz, zamiast iterować po wierszach. To dlatego Polars jest tak szybki.
import polars as pl
df = pl.DataFrame({
"imie": ["Anna Kowalska", "bartek nowak", "CELINA WIŚNIEWSKA"],
"email": ["[email protected]", "[email protected]", "[email protected]"],
"data_urodzenia": ["1995-03-15", "1988-11-22", "1982-07-08"],
"wynagrodzenie": [8500, 12000, 9800],
})
wynik = df.with_columns(
# Operacje na tekście
pl.col("imie").str.to_titlecase().alias("imie_sformatowane"),
pl.col("email").str.split("@").list.last().alias("domena"),
# Operacje na datach
pl.col("data_urodzenia").str.to_date("%Y-%m-%d").alias("data_ur"),
# Operacje warunkowe
pl.when(pl.col("wynagrodzenie") > 10000)
.then(pl.lit("wysoka"))
.when(pl.col("wynagrodzenie") > 8000)
.then(pl.lit("srednia"))
.otherwise(pl.lit("podstawowa"))
.alias("kategoria_pensji"),
)
print(wynik)
Funkcje okna (window functions)
Jeśli lubisz window functions z SQL-a, pokochasz je w Polars. Pozwalają na obliczenia w ramach grup bez utraty wierszy (czyli bez agregacji):
import polars as pl
sprzedaz = pl.DataFrame({
"sprzedawca": ["Ala", "Ala", "Ala", "Bob", "Bob", "Bob"],
"miesiac": [1, 2, 3, 1, 2, 3],
"kwota": [5000, 6200, 5800, 4500, 7100, 6300],
})
wynik = sprzedaz.with_columns(
# Średnia per sprzedawca (bez utraty wierszy)
pl.col("kwota").mean().over("sprzedawca").alias("srednia_sprzedawcy"),
# Ranking w obrębie sprzedawcy
pl.col("kwota").rank(descending=True).over("sprzedawca").alias("ranking"),
# Narastająca suma per sprzedawca
pl.col("kwota").cum_sum().over("sprzedawca").alias("narastajaco"),
)
print(wynik)
Obsługa brakujących wartości
Polars używa null zamiast pandasowego NaN. To subtelna, ale ważna różnica — null jest obsługiwany na poziomie Arrow i nie psuje operacji arytmetycznych tak jak NaN w NumPy:
import polars as pl
df = pl.DataFrame({
"nazwa": ["A", "B", None, "D", "E"],
"wartosc": [10.0, None, 30.0, None, 50.0],
})
# Diagnostyka
print(df.null_count()) # Liczba nulli per kolumna
# Uzupełnianie braków
df_clean = df.with_columns(
pl.col("nazwa").fill_null("Nieznany"),
pl.col("wartosc").fill_null(pl.col("wartosc").mean()),
)
print(df_clean)
# Usuwanie wierszy z brakami
df_bez_nulli = df.drop_nulls()
# Usuwanie braków tylko w wybranych kolumnach
df_bez_nulli_nazwa = df.drop_nulls(subset=["nazwa"])
Najlepsze praktyki pracy z Polars w 2026
Na koniec kilka zasad, które wypracowałem podczas codziennej pracy z Polars. Nie są to absolutne prawdy, ale pomagają unikać typowych pułapek:
- Zawsze preferuj
scan_*()nadread_*()— dzięki lazy evaluation Polars może optymalizować wczytywanie danych. To naprawdę robi różnicę przy większych zbiorach. - Konwertuj CSV na Parquet — zrób to raz, a następnie analizuj dane wielokrotnie szybciej. Kiedyś skonwertowałem plik CSV 8 GB na Parquet 1.2 GB — i wczytywanie z kilkunastu sekund spadło do poniżej sekundy.
- Unikaj pętli Pythona — zamiast
for row in df.iter_rows(), używaj wyrażeń Polars, które są wektoryzowane i wielowątkowe. - Filtruj wcześnie — przesuwaj filtry na początek potoku, przed operacjami
joinigroup_by. Mniej danych = szybsze przetwarzanie. - Używaj
.explain()do debugowania — sprawdzaj plan zapytania, żeby upewnić się, że optymalizacje działają tak, jak tego oczekujesz. - Korzystaj z
.fetch(n)— zamiast.collect(), użyj.fetch(5)do szybkiego podglądu wyników bez przetwarzania całego zbioru.
Najczęściej zadawane pytania (FAQ)
Czy Polars zastąpi pandasa?
Raczej nie w najbliższym czasie. Pandas ma ogromny ekosystem i jest głęboko zintegrowany z bibliotekami ML takimi jak scikit-learn. Polars to raczej uzupełnienie — doskonały wybór do przetwarzania danych, ETL i pracy z dużymi zbiorami, podczas gdy pandas nadal świetnie sprawdza się w szybkiej analizie eksploracyjnej. Wiele zespołów w 2026 roku stosuje po prostu oba narzędzia równolegle i jest z tego zadowolona.
Jak szybki jest Polars w porównaniu z pandasem?
W benchmarkach Polars jest typowo 5–30 razy szybszy od pandasa, w zależności od operacji. Największe różnice widać przy grupowaniu, łączeniu danych (join) i przetwarzaniu dużych plików. Wynika to z wielowątkowego przetwarzania, optymalizatora zapytań i architektury opartej na Apache Arrow. Na mniejszych zbiorach (poniżej 10 MB) różnica jest często pomijalna.
Czy mogę używać Polars w Jupyter Notebook?
Tak, i to działa świetnie. Polars renderuje eleganckie tabele HTML w Jupyter Notebook, Jupyter Lab i Google Colab. Wystarczy pip install polars i import polars as pl — DataFrame'y wyświetlają się automatycznie z kolorowaniem typów danych.
Jak przenieść istniejący kod z pandasa na Polars?
Polars ma oficjalny przewodnik migracji z pandasa. Kluczowe różnice, które warto zapamiętać: brak indeksu, użycie pl.col() zamiast nawiasów kwadratowych, .filter() zamiast maskowania boolowskiego oraz group_by().agg() zamiast groupby().agg(). Konwersja między formatami jest prosta: df_pandas = df_polars.to_pandas() i df_polars = pl.from_pandas(df_pandas).
Czy Polars obsługuje GPU?
Polars rozwija wsparcie dla akceleracji GPU przez NVIDIA RAPIDS cuDF. W 2026 roku ta funkcja jest w fazie aktywnego rozwoju, ale samo CPU-owe wielowątkowe przetwarzanie Polars jest już wystarczająco szybkie dla większości zastosowań. W wielu przypadkach na pojedynczym komputerze potrafi przewyższyć nawet rozwiązania oparte na Spark — co brzmi absurdalnie, ale jest prawdą.