Polars в Python: практическое руководство для пользователей pandas

Осваиваем Polars для Python на практике: сравнение API с pandas, ленивые вычисления через LazyFrame, чтение CSV и Parquet, интеграция с scikit-learn, бенчмарки 2026 года и шпаргалка по миграции.

Что такое Polars и зачем он нужен

Если вы работаете с данными в Python, то скорее всего давно подружились с pandas — де-факто стандартом для табличных данных. Но вот в чём дело: как только объёмы начинают расти, pandas начинает задыхаться. Однопоточная обработка, прожорливость по памяти, медленное чтение больших файлов — знакомо?

Именно для решения этих болячек и появился Polars — высокопроизводительная библиотека DataFrame, написанная на Rust. Да, именно на Rust — и это не просто маркетинговый ход.

Polars использует формат Apache Arrow для хранения данных в памяти, умеет распараллеливать запросы на все доступные ядра и поддерживает ленивые вычисления с автоматической оптимизацией. По бенчмаркам 2026 года Polars обрабатывает данные в 5–30 раз быстрее pandas — и при этом ест меньше оперативки.

Актуальная стабильная версия на момент написания — Polars 1.39 (март 2026). Нужен Python 3.10+. Обновления выходят практически каждую неделю, так что библиотека развивается очень активно.

Установка и первые шаги

Тут всё максимально просто. Ставим через pip:

pip install polars

Если нужна работа с дополнительными форматами и облачными хранилищами, берите расширенную версию:

pip install 'polars[numpy,pandas,pyarrow,fsspec]'

По соглашению импортируем с алиасом pl (аналогично тому, как pandas — это pd):

import polars as pl

# Создание DataFrame из словаря
df = pl.DataFrame({
    "имя": ["Алиса", "Борис", "Виктория", "Дмитрий"],
    "возраст": [28, 35, 42, 31],
    "зарплата": [85000, 120000, 95000, 110000],
    "отдел": ["аналитика", "разработка", "аналитика", "разработка"],
})
print(df)

На выходе получаем аккуратную таблицу с типами данных — Polars строго типизирован и выводит типы автоматически при создании:

shape: (4, 4)
┌──────────┬─────────┬──────────┬────────────┐
│ имя      ┆ возраст ┆ зарплата ┆ отдел      │
│ ---      ┆ ---     ┆ ---      ┆ ---        │
│ str      ┆ i64     ┆ i64      ┆ str        │
╞══════════╪═════════╪══════════╪════════════╡
│ Алиса    ┆ 28      ┆ 85000    ┆ аналитика  │
│ Борис    ┆ 35      ┆ 120000   ┆ разработка │
│ Виктория ┆ 42      ┆ 95000    ┆ аналитика  │
│ Дмитрий  ┆ 31      ┆ 110000   ┆ разработка │
└──────────┴─────────┴──────────┴────────────┘

Кстати, обратите внимание на разделитель вместо привычной вертикальной черты — к этому быстро привыкаешь.

Ключевые отличия Polars от pandas

Перед тем как нырять в код, давайте разберёмся в принципиальных различиях. Это реально сэкономит время и нервы при переходе.

Нет индекса

В pandas у каждого DataFrame есть индекс — и с ним целый зоопарк проблем: .loc vs .iloc, SettingWithCopyWarning, сбросы индекса после группировки... В Polars индекса просто нет. Строки адресуются по позиции. И знаете что? Жить стало проще.

Apache Arrow вместо NumPy

Pandas хранит данные в массивах NumPy, а Polars — в формате Apache Arrow. Это современный колоночный формат, который обеспечивает лучшую кэш-локальность, zero-copy обмен данными между библиотеками и куда более эффективную работу со строками.

Многопоточность из коробки

Pandas работает в одном потоке. Точка. В Polars параллелизм встроен на уровне архитектуры — операции автоматически раскидываются по всем ядрам без единой строчки дополнительного кода с вашей стороны.

Ленивые вычисления (Lazy Evaluation)

Pandas выполняет каждую операцию сразу (eager mode). Polars поддерживает оба режима. В ленивом режиме библиотека сначала строит план выполнения, оптимизирует его (pushdown предикатов, pushdown проекций, объединение операций) и только потом считает. На сложных цепочках преобразований это даёт кратное ускорение.

Выражения вместо императивного кода

В pandas вы пишете пошаговые инструкции — сделай то, потом это. В Polars вы описываете что хотите получить через систему выражений, а библиотека сама решает как это сделать быстрее всего. Честно говоря, к этому подходу привыкаешь за пару дней — и потом не хочется возвращаться.

Основные операции: сравнение с pandas

Итак, давайте посмотрим на типичные операции бок о бок. Это, пожалуй, самая полезная часть для тех, кто уже знает pandas.

Выборка столбцов (select)

# pandas
df_pd[["имя", "зарплата"]]

# Polars
df.select("имя", "зарплата")
# или с выражениями:
df.select(pl.col("имя"), pl.col("зарплата"))

Фильтрация строк (filter)

# pandas
df_pd[df_pd["возраст"] > 30]

# Polars
df.filter(pl.col("возраст") > 30)

Добавление и изменение столбцов (with_columns)

# pandas
df_pd["бонус"] = df_pd["зарплата"] * 0.1

# Polars — DataFrame неизменяемый, создаётся новый
df = df.with_columns(
    (pl.col("зарплата") * 0.1).alias("бонус")
)

Важный момент: в Polars нельзя менять DataFrame на месте через df["col"] = .... Только with_columns, который возвращает новый объект. Поначалу это раздражает, но потом начинаешь ценить — меньше побочных эффектов, меньше багов.

Группировка и агрегация (group_by)

# pandas
df_pd.groupby("отдел").agg({"зарплата": ["mean", "max"], "возраст": "mean"})

# Polars
df.group_by("отдел").agg(
    pl.col("зарплата").mean().alias("средняя_зарплата"),
    pl.col("зарплата").max().alias("макс_зарплата"),
    pl.col("возраст").mean().alias("средний_возраст"),
)

Синтаксис Polars тут чуть длиннее, зато явно видно, что происходит. Никаких словарей со вложенными списками — всё читаемо.

Сортировка (sort)

# pandas
df_pd.sort_values("зарплата", ascending=False)

# Polars
df.sort("зарплата", descending=True)

Соединение таблиц (join)

# Создаём таблицу отделов
отделы = pl.DataFrame({
    "отдел": ["аналитика", "разработка"],
    "бюджет": [500000, 800000],
})

# pandas
pd.merge(df_pd, отделы_pd, on="отдел", how="left")

# Polars
df.join(отделы, on="отдел", how="left")

Ленивые вычисления: LazyFrame на практике

Вот тут начинается самое интересное. Ленивые вычисления — это, пожалуй, главная киллер-фича Polars. Вместо того чтобы считать каждый шаг сразу, вы описываете весь конвейер, а Polars сам разбирается, как выполнить его оптимально.

Базовый пример LazyFrame

import polars as pl

# Создаём LazyFrame
lazy_df = pl.LazyFrame({
    "product": ["A", "B", "A", "C", "B", "A"],
    "revenue": [100, 200, 150, 300, 250, 180],
    "region": ["Москва", "СПб", "Москва", "СПб", "Москва", "СПб"],
})

# Строим план запроса (ничего не выполняется!)
result = (
    lazy_df
    .filter(pl.col("revenue") > 120)
    .group_by("product")
    .agg(
        pl.col("revenue").sum().alias("total_revenue"),
        pl.col("revenue").count().alias("num_sales"),
    )
    .sort("total_revenue", descending=True)
)

# А вот теперь выполняем
print(result.collect())

Вызов collect() запускает оптимизатор и прогоняет весь план за один проход. Polars автоматически «проталкивает» фильтры поближе к источнику данных (predicate pushdown) и загружает только нужные столбцы (projection pushdown).

Чтение больших файлов с scan_parquet

Настоящая мощь LazyFrame раскрывается при работе с файлами. Функция scan_parquet не грузит весь файл в память — она создаёт LazyFrame с отложенным чтением:

# Ленивое чтение Parquet-файла
lazy_df = pl.scan_parquet("sales_data_2026.parquet")

# Polars прочитает только нужные столбцы и строки
result = (
    lazy_df
    .filter(pl.col("date") >= pl.lit("2026-01-01"))
    .select("product_id", "revenue", "quantity")
    .group_by("product_id")
    .agg(
        pl.col("revenue").sum().alias("total_revenue"),
        pl.col("quantity").sum().alias("total_qty"),
    )
    .sort("total_revenue", descending=True)
    .head(10)
    .collect()
)

print(result)

Parquet-файлы хранят метаданные о диапазонах значений в каждом row group. Polars использует это для пропуска целых блоков, не подходящих под фильтр. На практике это может ускорить чтение в десятки раз по сравнению с pd.read_parquet().

Потоковая обработка: данные больше RAM

А что делать, если датасет банально не влезает в память? Для этого есть streaming-режим:

# Данные не помещаются в RAM — используем streaming
(
    pl.scan_parquet("huge_dataset_100gb.parquet")
    .filter(pl.col("status") == "active")
    .group_by("category")
    .agg(pl.col("amount").sum())
    .collect(engine="streaming")
)

Параметр engine="streaming" заставляет Polars обрабатывать данные блоками, не пытаясь загрузить всё сразу. Я лично гонял 50-гигабайтный датасет на ноутбуке с 16 ГБ RAM — и оно работало. С pandas такой фокус не прошёл бы.

Работа с файлами: CSV, Parquet, JSON

Polars умеет читать и писать все основные форматы. Вот краткий обзор.

CSV

# Чтение CSV (eager)
df = pl.read_csv("data.csv")

# Ленивое чтение CSV
lazy_df = pl.scan_csv("data.csv")

# Запись в CSV
df.write_csv("output.csv")

Polars параллелит чтение CSV по нескольким ядрам, что даёт 2–5-кратное ускорение по сравнению с pd.read_csv(). Если вы регулярно грузите большие CSV-файлы, разницу почувствуете сразу.

Parquet

# Чтение Parquet
df = pl.read_parquet("data.parquet")

# Ленивое чтение — рекомендуемый способ
lazy_df = pl.scan_parquet("data.parquet")

# Запись в Parquet
df.write_parquet("output.parquet")

Parquet — лучший друг Polars. Колоночное хранение, встроенное сжатие, метаданные для оптимизации запросов. Если есть возможность, конвертируйте свои CSV в Parquet — не пожалеете.

JSON

# Чтение JSON
df = pl.read_json("data.json")

# Чтение JSON Lines (по строке на запись)
df = pl.read_ndjson("data.jsonl")

# Запись
df.write_json("output.json")
df.write_ndjson("output.jsonl")

Интеграция с pandas и другими библиотеками

Переход на Polars — это не ультиматум. Совсем не обязательно выбрасывать весь свой pandas-код. Обе библиотеки отлично уживаются вместе благодаря общему фундаменту Apache Arrow.

Конвертация между Polars и pandas

import pandas as pd
import polars as pl

# pandas → Polars
pd_df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
pl_df = pl.from_pandas(pd_df)

# Polars → pandas
pd_df_back = pl_df.to_pandas()

Конвертация через Arrow идёт без копирования данных (zero-copy), если типы совместимы. То есть накладных расходов — почти ноль.

Использование с scikit-learn

Scikit-learn пока ожидает на входе массивы NumPy или pandas DataFrame (хотя, возможно, скоро это изменится). Пока что — просто конвертируем:

from sklearn.linear_model import LinearRegression
import polars as pl

df = pl.DataFrame({
    "feature_1": [1.0, 2.0, 3.0, 4.0, 5.0],
    "feature_2": [2.1, 3.9, 6.2, 7.8, 10.1],
    "target": [3.0, 6.1, 9.0, 12.2, 15.0],
})

X = df.select("feature_1", "feature_2").to_numpy()
y = df["target"].to_numpy()

model = LinearRegression()
model.fit(X, y)
print(f"Коэффициенты: {model.coef_}")

Использование с matplotlib

import matplotlib.pyplot as plt
import polars as pl

df = pl.DataFrame({
    "месяц": ["Янв", "Фев", "Мар", "Апр", "Май", "Июн"],
    "продажи": [120, 135, 148, 162, 155, 178],
})

plt.figure(figsize=(8, 5))
plt.bar(df["месяц"].to_list(), df["продажи"].to_list())
plt.title("Продажи по месяцам")
plt.ylabel("Продажи (тыс. руб.)")
plt.show()

Да, нужен лишний шаг с .to_list() или .to_numpy(), но это мелочь.

Производительность: реальные бенчмарки 2026 года

Ну а теперь к цифрам — ради чего всё это затевалось. Бенчмарки на датасете 10 млн строк (19 столбцов, Parquet ~255 МБ), машина с 4 ядрами / 8 потоков и 16 ГБ RAM:

ОперацияpandasPolarsУскорение
Чтение CSV12.4 с2.8 с4.4×
Фильтрация9.4 с1.9 с5.0×
Сортировка37.0 с3.0 с12.3×
Group by + agg8.2 с2.6 с3.2×
Join (left)9.5 с2.6 с3.6×

Сортировка — вообще разгром: 12× быстрее. А по памяти ситуация такая: pandas съедает примерно 5–10× от размера данных на диске, Polars — около 2–4×. Для нашего датасета в 255 МБ это ~1.5 ГБ у pandas против ~600 МБ у Polars.

Когда выбрать Polars, а когда оставить pandas

Давайте начистоту: Polars — не серебряная пуля. Вот мой чек-лист для выбора.

Берите Polars, когда:

  • Большие наборы данных — от 1 ГБ и выше, где pandas начинает тормозить
  • ETL-пайплайны — регулярная обработка данных, где каждая секунда на счету
  • Мало памяти — streaming позволяет ворочать данные, которые не влезают в RAM
  • Новый проект с нуля — нет legacy-кода, можно сразу начать с Polars
  • Работа с Parquet — здесь Polars раскрывается на полную

Оставайтесь с pandas, когда:

  • Маленькие датасеты — до 1 ГБ разница в скорости почти незаметна
  • ML-экосистема — scikit-learn, statsmodels и компания пока дружат только с pandas
  • Интерактивный анализ в Jupyter — для быстрого EDA pandas по-прежнему удобнее
  • Куча существующего кода — переписывать рабочий пайплайн ради скорости не всегда имеет смысл
  • Сложная работа со строками — тут pandas пока быстрее (хотя Polars догоняет)

Мой совет на 2026 год: не выбирайте «или–или». Используйте Polars для тяжёлых вычислений и ETL, а pandas — для интерактивного анализа и интеграции с ML-библиотеками. Конвертация между ними через Arrow занимает миллисекунды, так что никакого оверхеда.

Шпаргалка: pandas → Polars

Эту таблицу стоит держать под рукой, пока привыкаете к новому синтаксису:

ОперацияpandasPolars
Импортimport pandas as pdimport polars as pl
Создание DataFramepd.DataFrame(data)pl.DataFrame(data)
Чтение CSVpd.read_csv()pl.read_csv() / pl.scan_csv()
Выборка столбцовdf[["a", "b"]]df.select("a", "b")
Фильтрацияdf[df["a"] > 5]df.filter(pl.col("a") > 5)
Новый столбецdf["c"] = df["a"] + 1df.with_columns((pl.col("a") + 1).alias("c"))
Группировкаdf.groupby("a").agg(...)df.group_by("a").agg(...)
Сортировкаdf.sort_values("a")df.sort("a")
Joinpd.merge(df1, df2)df1.join(df2)
Размерdf.shapedf.shape
Описаниеdf.describe()df.describe()

Часто задаваемые вопросы (FAQ)

Polars полностью заменяет pandas?

Нет, и не стоит так ставить вопрос. Polars — это альтернатива для определённых задач. У pandas огромная экосистема: сотни библиотек ожидают pandas DataFrame на входе. Polars быстрее для тяжёлой обработки, но pandas остаётся лучшим выбором для интерактивного анализа и ML-пайплайнов. Лучшая стратегия — использовать обе библиотеки вместе, каждую для своих задач.

Насколько Polars быстрее pandas на практике?

Зависит от задачи и объёма данных. На датасетах от 1 ГБ — в 5–30 раз быстрее для типичных операций. На маленьких (до 100 МБ) разница может быть незаметной. Самый большой выигрыш — на сортировке (до 12×) и чтении файлов (до 5×).

Сложно ли перейти с pandas на Polars?

Синтаксис другой, но концепции те же самые. Главное — перестроить мышление с императивного стиля на декларативный (выражения). По моему опыту, большинство разработчиков осваивают базу за 1–2 дня. А ещё у Polars есть отличный гайд по миграции с pandas в официальной документации.

Можно ли использовать Polars с Jupyter Notebook?

Конечно. Polars полностью совместим с Jupyter Notebook и JupyterLab — DataFrame рендерятся в красивые HTML-таблицы. Для визуализации подключайте matplotlib, seaborn или plotly, предварительно конвертировав данные через .to_pandas() или .to_numpy().

Поддерживает ли Polars работу с базами данных?

Да. Есть встроенные функции pl.read_database() и pl.scan_database() для PostgreSQL, MySQL и SQLite. Также можно подключиться к любой БД через SQLAlchemy и передать результат в Polars через pandas или Arrow.

Об авторе Editorial Team

Our team of expert writers and editors.