Привет, Хабр! Это Леша Жиряков, техлид 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)
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 у вас написано про фильтрацию.
В целом вы могли бы и лучше, но явно халтурите.
fiksii
27.12.2024 10:06Я не знаю как вам, но, если техлид делает бенчмаркинг и сравнивает результаты по 1 запуску, то для меня это показатель полного непрофессионализма.
Andrey_Solomatin
У Polars есть один большой плюс, важный в больщих системах. У неё НЕТ ЗАВИСИМОСТЕЙ!
Сложных системах, где используется много разных инструментов для датасаенса, начинается цирк с коням, так как очень многие зависять от numpy определённых версий.
Другими словами, если вы хотите использовать новыю версию библиотеки, может понадобиться апгрейдить практически все зависимости в системе.