Привет, Хабр! Это Леша Жиряков, техлид backend-команды витрины онлайн-кинотеатра KION. В прошлом посте я рассказывал про альтернативы Pandas, а сегодня будем сравнивать две библиотеки — Polars и Pandas. Обсудим, какие преимущества есть у Polars и за счет чего она выигрывает в производительности. В посте — мой взгляд, но мнения по этому поводу, конечно, разные. Пишите, что думаете, в комментариях — будем обсуждать!

Что за библиотеки такие

Сначала вспомним историю появления и предназначение двух этих инструментов.

Pandas

Pandas создана в 2008 году Уэсом МакКинни в компании AQR Capital Management. Основная специализация библиотеки — обработка и анализ табличных данных, включая их преобразование, фильтрацию, агрегацию и визуализацию. Написана она на Python, с критическими компонентами, оптимизированными на Cython.

Pandas — один из самых популярных инструментов для работы с данными, о чем свидетельствуют 40+ тысяч звезд на GitHub.

Что по характеристикам:

  • Возможности: позволяет разработчикам выбирать между различными типами массивов для представления данных датафрейма. Традиционно большинство датафреймов поддерживается массивами NumPy. Начиная с версии 2.0 можно использовать PyArrow в качестве формата хранения данных.

  • Количество звезд на GitHub: 44,1 тыс.

  • Открытых Issues: ~3,6 тысяч — актуально на декабрь 2024 года.

  • Версия: 2.2.3. 

  • Merge Requests (MRs): обрабатываются индивидуальными разработчиками и широким комьюнити.

Подробнее тут.

Начиная с версии 2.0 в Pandas была введена экспериментальная функция Copy-on-Write (CoW), изменяющая подход к копированию: появились улучшения этого режима. При ее включении копирование данных откладывается до момента их изменения, а это позволяет эффективнее использовать память. DuckDB/Dask всегда используют out-of-core.

Ключевые технологии библиотеки:

  • DataFrame и Series. Основные структуры данных Pandas, обеспечивающие удобную работу с табличными (DataFrame) и одномерными данными (Series). Позволяют легко обрабатывать, фильтровать и манипулировать информацией.

  • NumPy. Pandas построена на базе NumPy —  это обеспечивает высокую производительность и эффективные операции с массивами данных.

  • Чтение и запись данных. Поддержка множества форматов — CSV, Excel, SQL, JSON — делает Pandas универсальным инструментом для загрузки и экспорта информации.

  • Гибкая индексация. Возможности работы с метками и числовыми индексами и поддержка многомерной индексации (MultiIndex) упрощают работу с данными любой сложности.

Polars

Библиотека для обработки данных Polars появилась сравнительно недавно — в 2020 году. Разработана она с упором на производительность. Создатель — Рейнарс Вегис. Polars специализируется на быстром анализе больших объемов информации и написана на Rust. На GitHub у нее 4,5 тысячи звезд.

Что по характеристикам:

  • Возможности: использует колоночный формат Apache Arrow для хранения данных.

  • Количество звезд на GitHub: 31 тыс.

  • Открытых Issues: 2,1 тысяч — актуально на декабрь 2024 года.

  • Версия: 1.17.1.

  • Merge Requests (MRs): комьюнити.

Подробнее тут.

Polars предоставляет два API: отложенные вычисления (lazy evaluation) и немедленное выполнение (eager evaluation). Она эффективно обрабатывает большие объемы данных и превосходит по скорости многие другие библиотеки при выполнении сложных операций — например, сортировке и группировке. Еще библиотека умеет помещать данные в память и работать в out-of-core-режиме, обращаясь к файлам на диске и обрабатывая их небольшими чанками.

Хотя синтаксис Pandas может быть более привычным, Polars обеспечивает лучшую производительность при работе с крупными датафреймами, а это важный фактор для дата-инженеров. При подготовке данных для анализа и машинного обучения, когда требуется очистка, нормализация и другие преобразования, Polars справляется с этими задачами быстрее, чем даже Pandas версии 2.0.

Сравнение производительности — бенчмарки

Агрегация

Операции агрегации в Polars показали значительное превосходство по скорости выполнения по сравнению с Pandas. Для проведения тестов использовался Covertype из библиотеки scikit-learn, в котором было 581 012 строк и 54 столбца. Во время тестирования выполнялась операция вычисления среднего значения для всех числовых столбцов.

Тестирование
# Pandas
start_time = time.time()
mean_pandas = df_pandas.drop('target', axis=1).mean()
pandas_time = time.time() - start_time

# Polars
start_time = time.time()
mean_polars = df_polars.select(pl.all().exclude('target')).mean()
polars_time = time.time() - start_time

print(f"Pandas aggregation time: {pandas_time:.4f} seconds")
print(f"Polars aggregation time: {polars_time:.4f} seconds")
print(f"Polars is {pandas_time/polars_time:.2f}x faster than Pandas")

Результаты показали, что Polars выполнил операцию агрегации за 0,0083 сек., тогда как Pandas потребовалось 0,1863 сек. Значит, Polars оказался больше чем в 22 раза быстрее Pandas.

Такие показатели объясняются использованием Polars языка программирования Rust, который оптимизирует работу с памятью и применяет многопоточность. Это делает библиотеку привлекательной для задач, связанных с обработкой и анализом больших объемов данных.

Слияние, или join

Тот же набор данных использовался и для бенчмарка по операциям фильтрации, ключевым в анализе данных. В рамках тестирования выполнялась операция, где значение в столбце 'feature_0' превышало среднее.

Тестирование
# Pandas
start_time = time.time()
filtered_pandas = df_pandas[df_pandas['feature_0'] > df_pandas['feature_0'].mean()]
pandas_time = time.time() - start_time

# Polars
start_time = time.time()
filtered_polars = df_polars.filter(pl.col('feature_0') > pl.col('feature_0').mean())
polars_time = time.time() - start_time

print(f"Pandas filtering time: {pandas_time:.4f} seconds")
print(f"Polars filtering time: {polars_time:.4f} seconds")
print(f"Polars is {pandas_time/polars_time:.2f}x faster than Pandas")

Результаты показали, что Polars выполнил операцию фильтрации за 0,0183 сек., тогда как Pandas потребовалось 0,0741 сек. То есть Polars оказался примерно в четыре раза быстрее Pandas.

Группировка данных, или GroupBy Operation

В рамках тестирования выполнялась операция группировки по столбцу 'target' с последующим вычислением среднего значения для каждой группы.

Тестирование
# Pandas
start_time = time.time()
grouped_pandas = df_pandas.groupby('target').mean()
pandas_time = time.time() - start_time

# Polars
start_time = time.time()
grouped_polars = df_polars.group_by('target').agg(pl.all().exclude('target').mean())
polars_time = time.time() - start_time

print(f"Pandas groupby time: {pandas_time:.4f} seconds")
print(f"Polars groupby time: {polars_time:.4f} seconds")
print(f"Polars is {pandas_time/polars_time:.2f}x faster than Pandas")

Результаты показали, что Polars выполнил операцию группировки за 0,201 сек., тогда как Pandas потребовалось 1,100 сек. Polars оказался примерно в 5,5 раз быстрее Pandas.

Сортировка, или sorting

В рамках тестирования выполнялась операция сортировки по столбцу 'Elevation'.

Тестирование
# Pandas
start_time = time.time()
sorted_pandas = df_pandas.sort_values('feature_0', ascending=False)
pandas_time = time.time() - start_time

# Polars
start_time = time.time()
sorted_polars = df_polars.sort('feature_0', descending=True)
polars_time = time.time() - start_time

print(f"Pandas sorting time: {pandas_time:.4f} seconds")
print(f"Polars sorting time: {polars_time:.4f} seconds")
print(f"Polars is {pandas_time/polars_time:.2f}x faster than Pandas")

Результаты показали, что Polars выполнил операцию сортировки за 0,0656 сек., тогда как Pandas потребовалось 0,2027 сек. Polars оказался примерно в три раза быстрее Pandas.

Инжиниринг признаков, или feature engineering

Задача состояла в вычислении Z-оценки для каждого значения в столбце 'feature_0'. Это включает вычитание среднего значения столбца и деление на стандартное отклонение.

Тестирование
# Pandas
start_time = time.time()
pandas_zscore = df_pandas.copy()
for column in pandas_zscore.columns[:-1]:  # Exclude the target column
    pandas_zscore[f'{column}_zscore'] = (pandas_zscore[column] - pandas_zscore[column].mean()) / pandas_zscore[column].std()
pandas_time = time.time() - start_time

# Polars
start_time = time.time()
feature_cols = [f'feature_{i}' for i in range(54)]
polars_zscore = df_polars.with_columns([
    ((pl.col(col) - pl.col(col).mean()) / pl.col(col).std()).alias(f'{col}_zscore')
    for col in feature_cols
])
polars_time = time.time() - start_time

print(f"Pandas z-score calculation time: {pandas_time:.4f} seconds")
print(f"Polars z-score calculation time: {polars_time:.4f} seconds")
print(f"Polars is {pandas_time/polars_time:.2f}x faster than Pandas")

Результаты показали, что Polars выполнил вычисление Z-оценки для нормализации данных за 0,0919 сек., тогда как Pandas потребовалось 0,5154 сек. Вывод — Polars оказался примерно в 5,6 раза быстрее Pandas.

Почему Polars быстрее

У Pandas ограниченная поддержка работы с данными, превышающими объем оперативной памяти. Архитектура базируется на NumPy — это ограничивает обработку больших массивов данных, помещающихся в память одного потока. Для взаимодействия с большими наборами данных часто приходится использовать сторонние инструменты, такие как Dask.

Polars же написан на Rust — языке программирования, который славится своей безопасностью и высокой производительностью. Rust позволяет эффективно управлять памятью и гарантирует отсутствие состояния гонки. К тому же Polars использует несколько ключевых технологий:

  • Arrow Columnar Format — формат хранения информации, который оптимизирован для быстрого доступа. Данные хранятся в колонках, так что чтение и обработка становятся более эффективными.

  • Lazy Execution — отложенный запуск, при котором операции реализуются только тогда, когда требуется результат. Это позволяет оптимизировать план выполнения запросов и исключить лишние вычисления.

  • Многопоточность — Polars использует все ядра процессора для реализации задач.

  • Memory Mapping — позволяет работать с данными напрямую с диска, не загружая весь объем в оперативную память.

Что касается API, то у Polars свой собственный DataFrame API (иммутабельный, в отличие от Pandas), а еще есть поддержка SQL.

В целом, Pandas и Polars — мощные инструменты для анализа данных, но библиотека Polars выигрывает в производительности и масштабируемости. Она идеально подходит для работы с большими наборами данных, во многом как раз из-за использования современных технологий и языка Rust. Но Pandas списывать тоже нельзя, она была и остается удобным выбором для решения не слишком объемных задач, плюс не стоит забывать и про экосистему библиотеки. К тому же у Pandas одно из самых больших и активных сообществ среди инструментов для анализа данных в Python.

И напоследок. Polars превосходит Pandas и по логотипу. Белый медведь, в отличие от панды, — самый крупный сухопутный хищник на Земле: длина тела может достигать 3 метров. Он может учуять добычу на расстоянии около километра под метровой толщиной снега, проплыть без остановки 687 км (официально зарегистрированный рекорд) и много чего еще. Ну и как тут с ним бороться?

Теперь вам слово — пишите в комментариях, что предпочитаете.

Что еще почитать:

Комментарии (5)


  1. Andrey_Solomatin
    27.12.2024 10:06

    У Polars есть один большой плюс, важный в больщих системах. У неё НЕТ ЗАВИСИМОСТЕЙ!

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

    Другими словами, если вы хотите использовать новыю версию библиотеки, может понадобиться апгрейдить практически все зависимости в системе.


  1. zzzzzzerg
    27.12.2024 10:06

    Алексей, расскажите, пожалуйста, что вы имеете в виду, когда пишите "Архитектура базируется на NumPy — это ограничивает обработку больших массивов данных, помещающихся в память одного потока."

    Rust позволяет эффективно управлять памятью и гарантирует отсутствие состояния гонки. - это просто общие слова, которые не имеют отношения к делу.

    Если вы противопостовляете pandas + Dask и polars, надо говорить не про "Polars же написан на Rust — языке программирования, который славится своей безопасностью и высокой производительностью." - а про наличие streaming API из коробки (которое появилось не само по себе).

    А вот чтобы было более честное сравнение - "Данные хранятся в колонках, так что чтение и обработка становятся более эффективными." - вы не пробовали те же тесты запустить в pandas если выбрано PyArrow в качестве сравнения? Сюда же и про mmap - оно ведь тоже не на равном месте появляется. Попробуйте так же работать с CSV или XLSX форматами :)

    Что касается API, то у Polars свой собственный DataFrame API (иммутабельный, в отличие от Pandas), а еще есть поддержка SQL. У DuckDB — только SQL. А DuckDB то каким образом к pandas относится?

    А в абзаце про слияние и join у вас написано про фильтрацию.

    В целом вы могли бы и лучше, но явно халтурите.


  1. Lor_Nick
    27.12.2024 10:06

    Получается что polars луче сравнивать со spark, а не pandas?


  1. SharomePokatee
    27.12.2024 10:06

    Удалено


  1. fiksii
    27.12.2024 10:06

    Я не знаю как вам, но, если техлид делает бенчмаркинг и сравнивает результаты по 1 запуску, то для меня это показатель полного непрофессионализма.