Polars 1.x в Python: Пълно ръководство за DataFrame, LazyFrame и Streaming (2026)

Практическо ръководство за Polars 1.x в Python: мързелив оптимизатор, streaming engine, миграция от Pandas и интеграция със scikit-learn с реални примери и бенчмаркове за 2026.

Актуализирано: 20 август 2026

Polars е бърза колонна DataFrame библиотека за Python, написана на Rust и базирана на Apache Arrow, която обработва до 30 пъти по-бързо групиране, филтриране и join операции в сравнение с Pandas. Причината е проста: многоядрено изпълнение и мързелив оптимизатор на заявки. В това ръководство за 2026 ще научите как работи Polars 1.x, как да мигрирате от Pandas, кога да включите streaming engine и как да интегрирате Polars със scikit-learn в реален ML пайплайн.

Честно казано, първия път когато преминах един ETL job от Pandas към Polars, си помислих, че съм счупил измерването. Пет минути се превърнаха в 12 секунди. Оттогава Polars е първата ми опция за нови pipeline-и.

  • Polars 1.39 (март 2026) стабилизира streaming engine с merge join и AsOf join, което позволява обработка на набори от данни, които не се събират в RAM.
  • LazyFrame API прилага predicate pushdown, projection pushdown и operation fusion, което често прави заявките 5–30× по-бързи от Pandas.
  • Всички операции използват всички налични CPU ядра по подразбиране, без нужда от ръчен паралелизъм.
  • Миграцията от Pandas е плавна: pl.from_pandas() и .to_pandas() са zero-copy за числови колони.
  • Pandas 3.0 премина на PyArrow за низове, но Polars запазва предимство при аналитични заявки над 100K реда.
  • Използвайте Polars за нови ETL пайплайни и Pandas при границата с scikit-learn или библиотеки, които не разбират Arrow.

Какво е Polars в Python?

Polars е DataFrame библиотека, написана на Rust, която експонира първокласен Python API за анализ на структурирани данни. Ritchie Vink стартира проекта през 2020 г. с изричната цел да замести Pandas за workloads, където производителността, паметта и предвидимостта на типовете имат значение. Първата стабилна 1.0 версия излезе през 2024 г. До август 2026 г. проектът е достигнал 1.43, с 12 релийза само през първото тримесечие и над 95 активни contributors.

Тъй като библиотеката е базирана на колонния формат Apache Arrow, данните ви живеят в непрекъснати блокове памет, оптимизирани за vectorized операции. За разлика от Pandas, който използва NumPy и Python-центричен модел, Polars може да чете колона от 100 милиона реда, без да сериализира стойностите в Python обекти. Това е основата за автоматичното многоядрено изпълнение (всяка операция се разцепва между всички физически ядра), без вие да пишете нито ред многонишков код.

Polars предлага два режима на изпълнение: eager (изпълнение веднага, като Pandas) и lazy (изгражда логически план на заявката, оптимизира го и го изпълнява при .collect()). Lazy режимът е препоръчителният по подразбиране за production, защото само тогава оптимизаторът може да пренарежда филтри, да отхвърля колони и да слива операции в един проход.

Polars срещу Pandas 2026: сравнителна таблица и бенчмаркове

На тестове с 240 милиона реда Polars показва 10× ускорение при group-by и joins, до 11× при sort и filter, и 4.7× при четене на Parquet. За обикновени 10-милионни таблици типичното време на Pandas е около 5–6 секунди, докато Polars приключва под 250 милисекунди. Разликата е най-голяма при lazy режим и wide таблици, където projection pushdown отсява неизползвани колони още при отваряне на файла.

ХарактеристикаPolars 1.43Pandas 3.0
Език на имплементацияRustPython + C (NumPy)
Формат в паметтаApache Arrow (columnar)NumPy + PyArrow за низове (от 3.0)
ИзпълнениеEager + Lazy + StreamingСамо eager
ThreadingМногоядрено по подразбиранеЕдноядрено
Оптимизатор на заявкиДа (predicate/projection pushdown)Не
Липсващи стойностиСамо nullNaN, NA, None
Обработка извън паметтаДа (streaming engine)Само на части чрез chunking
Бенчмарк 240M реда group-by~3 сек~30 сек

Pandas 3.0 направи PyArrow задължителна зависимост и превключи низовите колони на PyArrow-backed strings, което е най-голямото производителностно подобрение от версия 2.0 насам. Въпреки това, при аналитични заявки на 100K+ реда Polars все още е значително по-бърз, особено при lazy режим. За допълнителни съвети как да ускорите съществуващите Pandas пайплайни, вижте нашето ръководство за инженеринг на признаци с Pandas и scikit-learn.

Инсталиране на Polars и първи DataFrame

Polars 1.x работи на Python 3.9+ и се разпространява като wheel за всички основни платформи. Инсталирайте базовия пакет с pip или uv:

# Основна инсталация
pip install "polars>=1.39"

# С pyarrow за zero-copy връзка с Parquet/Arrow
pip install "polars[pyarrow]"

# Плюс SQL интерфейс, XLSX и Delta Lake
pip install "polars[all]"

За системи с CPU без AVX2 (например по-стари сървъри) използвайте polars-lts-cpu. Ако използвате uv, командата е uv add polars[pyarrow]. За среди с GPU Polars предлага експериментален NVIDIA cuDF backend, който активирате с engine="gpu" при .collect().

Първи DataFrame и няколко базови операции:

import polars as pl

# Създаване от dict
df = pl.DataFrame({
    "продукт": ["хляб", "мляко", "яйца", "сирене"],
    "цена":    [1.20, 2.50, 4.10, 8.90],
    "бройка":  [120, 80, 200, 45],
})

# Селекция и филтър
скъпи = df.filter(pl.col("цена") > 2.0).select(["продукт", "цена"])
print(скъпи)

# Изчислена колона с оборот
df = df.with_columns(
    (pl.col("цена") * pl.col("бройка")).alias("оборот")
)
print(df.sort("оборот", descending=True))

Синтаксисът е сходен с Pandas, но има една ключова разлика. Изразът pl.col("цена") е израз, а не низ, и може да участва във верижни операции без Python overhead. Малка разлика на пръв поглед, но точно оттук идва цялото ускорение.

Expression API: концепция и примери

Изразите (expressions) са гръбнакът на Polars. Всеки израз описва трансформация на колона (или няколко колони) и се изпълнява от Rust ядрото, без да минава през Python интерпретатора за всяка стойност. Това ги прави хиляди пъти по-бързи от еквивалентните DataFrame.apply(lambda ...) в Pandas. Това е и основната причина, поради която Polars може да разцепи една агрегация между всички CPU ядра автоматично.

import polars as pl

df = pl.read_csv("продажби.csv")

result = (
    df
    .filter(pl.col("регион") == "София")
    .group_by("категория")
    .agg([
        pl.col("сума").sum().alias("общо"),
        pl.col("сума").mean().alias("средно"),
        pl.col("клиент").n_unique().alias("уникални_клиенти"),
        (pl.col("сума") > 1000).sum().alias("големи_поръчки"),
    ])
    .sort("общо", descending=True)
)

Обърнете внимание, че всички агрегации се пресмятат за един проход през данните. Polars групира израза, планира какви колони са нужни и разпределя работата между наличните ядра. За условна логика използвайте pl.when(...).then(...).otherwise(...):

df = df.with_columns(
    pl.when(pl.col("възраст") < 18).then(pl.lit("непълнолетен"))
      .when(pl.col("възраст") < 65).then(pl.lit("възрастен"))
      .otherwise(pl.lit("пенсионер"))
      .alias("категория_възраст")
)

Този подход замества чисто Python if/else вериги и запазва работата векторизирана. За допълнителна статистическа обработка Polars разбира и повечето операции, които ще срещнете в нашето ръководство за статистически анализ и тестване на хипотези.

LazyFrame и оптимизаторът на заявки: как работи

LazyFrame е абстракция, която не изпълнява операциите веднага, а гради логически план. Когато извикате .collect(), оптимизаторът анализира целия план и прилага серия от трансформации преди действителното изпълнение. Според официалната документация на Polars, най-важните оптимизации са:

  • Predicate pushdown. Филтрите се преместват колкото се може по-рано, дори до нивото на файловия scanner, така че редовете, които не отговарят, никога не се четат.
  • Projection pushdown. Неизползваните колони се изхвърлят при отваряне на файла, а не в паметта.
  • Operation fusion. Няколко последователни трансформации се обединяват в един проход върху данните.
  • Common subexpression elimination. Повтарящи се подизрази се пресмятат само веднъж.
import polars as pl

# Lazy pipeline — нищо не се изпълнява до .collect()
q = (
    pl.scan_parquet("s3://моят-бъкет/логове-2026/*.parquet")
    .filter(pl.col("код") == 500)
    .filter(pl.col("timestamp") >= "2026-08-01")
    .select(["ендпойнт", "latency_ms", "user_id"])
    .group_by("ендпойнт")
    .agg([
        pl.col("latency_ms").quantile(0.99).alias("p99"),
        pl.col("user_id").n_unique().alias("засегнати"),
    ])
    .sort("p99", descending=True)
)

# Прегледайте оптимизирания план преди изпълнение
print(q.explain(optimized=True))

# Изпълнете
df = q.collect()

Извиквайки .explain(optimized=True), ще видите как Polars е преместил филтрите преди четенето на файловете и е отхвърлил колоните, които не участват в резултата. За S3, GCS и Azure Blob посочете storage_options={"aws_access_key_id": ..., "aws_secret_access_key": ...}. От версия 1.43 Polars стриймва директно от cloud, без първо да сваля целия файл.

Streaming engine в Polars 1.39: обработка на данни, които не се събират в паметта

Streaming engine позволява изпълнение на заявки на batches, така че наборите от данни, които не се събират в RAM, могат да се обработват без crash. Според официалните release notes за Polars 1.39 (12 март 2026), новата streaming engine добавя merge join (в 1.38) и AsOf join (в 1.39). Версия 1.40 добави и групиран AsOf join, а 1.43 разшири streaming за Hive-partitioned източници.

Ето как включвате streaming в заявка:

import polars as pl

q = (
    pl.scan_csv("огромен-файл-50gb.csv")
    .filter(pl.col("state") == "active")
    .group_by("customer_id")
    .agg([
        pl.col("amount").sum(),
        pl.col("order_id").count(),
    ])
)

# Изпълнение чрез streaming engine
df = q.collect(engine="streaming")

# Или глобално, за цялата програма:
pl.Config.set_engine_affinity("streaming")

Streaming engine е не само по-икономичен откъм памет, но често и по-бърз от in-memory engine, защото използва по-модерни векторизирани kernel-и. Полярният екип препоръчва да бъде включен по подразбиране в 2026 г. и вероятно ще стане стандартен engine в следваща major версия.

Ако пишете резултата обратно на диск, използвайте sink_parquet, sink_ipc или sink_csv. Те стриймват записа и не буферират цялата таблица в паметта:

q.sink_parquet("резултат.parquet", compression="zstd")

Group-by и joins в няколко ядра

Един от най-често цитираните ползи на Polars е паралелният group_by. При Pandas, дори с PyArrow backend, group-by работи на едно ядро. Polars разцепва хеш таблицата между всички ядра, което дава почти линейно ускорение. На 16-ядрен CPU агрегация над 100M реда приключва под 2 секунди. Същата операция в Pandas ми отнема над минута на същия хардуер.

result = (
    df.lazy()
      .group_by(["страна", "категория"])
      .agg([
          pl.col("приход").sum(),
          pl.col("клиент").n_unique(),
          pl.col("отстъпка").mean(),
      ])
      .collect(engine="streaming")
)

Joins в Polars също са паралелни. Поддържат се inner, left, outer, semi, anti, cross и as-of join (за времеви серии, където съответствието е "най-близък по-малък ключ"). AsOf join е особено ценен за финансови данни, където съпоставяте tick-ове с котировки:

котировки = pl.scan_parquet("quotes.parquet").sort("time")
търгове   = pl.scan_parquet("trades.parquet").sort("time")

fills = (
    търгове.join_asof(
        котировки,
        on="time",
        by="symbol",
        strategy="backward",  # най-близка предишна котировка
        tolerance="500ms",
    )
    .collect(engine="streaming")
)

Миграция от Pandas: капани и добри практики

Ако мигрирате съществуващ Pandas код, следните разлики обикновено предизвикват най-много объркване:

  1. Без index. Polars няма концепция за row index (редовете са позиционни). Ако вашият Pandas код разчита на df.set_index("date"), преформатирайте го към df.sort("date") плюс филтри по колона.
  2. Строги типове. Polars не позволява имплицитни конверсии, не можете да смесите int и str в една колона. Това предотвратява много от суптилните грешки в Pandas.
  3. Само null. Няма разлика между NaN, NA и None. Проверявайте с .is_null(), а не с .isna().
  4. Без inplace=True. Всяка операция връща нов DataFrame. Присвоете обратно: df = df.with_columns(...).
  5. Изрази вместо колони като списъци. Използвайте pl.col("x") + pl.col("y"), а не df["x"] + df["y"], за да останете във векторизираното ядро.
  6. Method chaining е предпочитан. Дълги chain-ове от операции позволяват на оптимизатора да види целия pipeline и да го препише.

Интеграция със scikit-learn, Matplotlib и Arrow екосистемата

Повечето scikit-learn естиматори очакват NumPy масиви или Pandas DataFrame. Polars предлага .to_pandas() и .to_numpy(), и двата zero-copy за числови колони благодарение на общия Arrow буфер. Препоръчаният pattern е ясен: Polars за предварителна обработка, конверсия на границата, fit с scikit-learn. Този подход комбинира скоростта на Polars за ETL с богатата ML екосистема на Pandas/NumPy. Вписва се естествено и в scikit-learn Pipeline подхода.

import polars as pl
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import train_test_split

# ETL с Polars — бърз и паралелен
raw = pl.scan_parquet("сделки.parquet")
features = (
    raw
    .filter(pl.col("статус") == "затворена")
    .with_columns([
        pl.col("сума").log().alias("log_сума"),
        (pl.col("завършване") - pl.col("създаване")).dt.total_seconds().alias("продължителност"),
    ])
    .select(["log_сума", "продължителност", "тип_клиент", "печалба"])
    .collect(engine="streaming")
)

# Конверсия на границата с scikit-learn
X = features.select(pl.exclude("печалба")).to_pandas()
y = features["печалба"].to_numpy()

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model = GradientBoostingClassifier().fit(X_train, y_train)

За визуализация Polars работи безпроблемно с Matplotlib, Seaborn, Plotly и Altair. Просто извикайте .to_pandas() преди sns.lineplot(), или ползвайте директно plotly.express, който поддържа Polars DataFrame нативно от версия 5.20. Вижте нашето ръководство за визуализация с Matplotlib и Seaborn за конкретни рецепти.

Polars също така интеропира с DuckDB, Delta Lake и Iceberg. Всички те говорят Arrow, така че прехвърлянето между тях е практически безплатно. Това прави Polars идеална "лепилна" библиотека в модерен lakehouse pipeline: Polars за трансформации, DuckDB за ad-hoc SQL и Delta/Iceberg за версионирано съхранение.

Често задавани въпроси

Какво е Polars и с какво се отличава от Pandas?

Polars е колонна DataFrame библиотека, написана на Rust и базирана на Apache Arrow. За разлика от Pandas, тя работи многоядрено по подразбиране, поддържа мързелив оптимизатор на заявки и streaming engine за данни, които не се събират в паметта, което често дава 5–30× ускорение при аналитични заявки.

По-бърз ли е Polars от Pandas 3.0?

Да. Дори след като Pandas 3.0 премина на PyArrow за низови колони, Polars остава значително по-бърз при group-by, joins и агрегации над 100K реда. На бенчмарк с 240M реда Polars дава ~10× ускорение при group-by и до 11× при sort и filter.

Кога да използвам Polars вместо Pandas?

Изберете Polars за нови ETL пайплайни, аналитични заявки над 100K реда, планирани job-ове и данни, които не се събират в паметта. Задръжте Pandas за бърза експлорация в Jupyter и когато интегрирате с библиотеки, които не разбират Arrow (например някои по-стари ML инструменти).

Мога ли да ползвам Polars със scikit-learn?

Да. Използвайте Polars за предварителна обработка и извикайте .to_pandas() или .to_numpy() точно преди fit(). Тези конверсии са zero-copy за числови колони, така че overhead-ът е минимален.

Как да включа streaming engine в Polars?

Извикайте .collect(engine="streaming") върху LazyFrame заявка, или задайте глобално pl.Config.set_engine_affinity("streaming"). От Polars 1.39 streaming поддържа merge join и AsOf join, така че покрива повечето реални pipeline-и.

Поддържа ли Polars GPU?

Да, чрез експерименталния NVIDIA cuDF backend, който активирате с collect(engine="gpu"). Той е най-полезен за големи group-by и joins над числови колони. За IO-heavy pipeline-и с текст CPU streaming engine обикновено е по-бърз.

Editorial Team
За Автора Editorial Team

Our team of expert writers and editors.