Закажи экспресс-аудит своего дела онлайн всего за 199 ₽
и получи рекомендации по улучшению - Жми сюда !

Использование библиотек Polar вместо Pandas: подробный анализ производительности

В этой статье мы рассмотрим три реальные задачи на основе реальных данных, в которых Polars превосходит Pandas по всем показателям.

Полярные птицы против панд

# Введение

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

Однако, как только вы начинаете работать с миллионами строк, начинают проявляться недостатки: операции группировки, занимающие несколько секунд, промежуточные копии, потребляющие оперативную память, и оконные функции, которые выполняются как циклы на уровне Python, а не как векторизованный код на C или Rust .

Polars — это библиотека DataFrame, написанная на Rust на основе Apache Arrow . Она разработана с учетом параллелизма и ленивой оценки как основных функций. Pandas выполняет каждую операцию заранее и последовательно, тогда как Polars может составить план запроса и оптимизировать его перед выполнением, при этом большинство операций автоматически выполняются параллельно на всех доступных ядрах ЦП.

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

Полярные птицы против панд

# Использование функции rank() вместо with_row_count(): Рейтинг активности

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

// Представление данных

В таблице google_gmail_emails хранится одна строка для каждого отправленного электронного письма, содержащая идентификатор отправителя (from_user), идентификатор получателя (to_user) и дату отправки письма. Вот предварительный просмотр таблицы:

идентификатор от_пользователя to_user день
0 6edf0be4b2267df1fa 75d295377a46f83236 10
1 6edf0be4b2267df1fa 32ded68d89443e808 6
2 6edf0be4b2267df1fa 55e60cfcc9dc49c17e 10
3 6edf0be4b2267df1fa e0e0defbb9ec47f6f7 6
4
314 e6088004caf0c8cc51 e6088004caf0c8cc51 5

Grain (что означает одна строка выходных данных): один пользователь, с указанием общего количества его электронных писем и уникального рейтинга активности.

// Распространенная ошибка

В задании требуется присвоить уникальный ранг, даже если у двух пользователей одинаковое количество электронных адресов. Распространенная ошибка — использование метода `rank(method='dense')` в Pandas, который присваивает одинаковый ранг пользователям с одинаковым количеством адресов. Правильный метод — `first`, который разрешает совпадения по положению в отсортированном фрейме. Поскольку перед ранжированием мы сортируем по алфавиту по `user_id`, полученные ранги уникальны и детерминированы.

Оптимальное решение Polars полностью исключает функцию ранжирования. После сортировки по [«total_emails», «user_id»] в порядке убывания и возрастания соответственно, условие .with_row_count(«activity_rank», offset=1) присваивает последовательные целые числа, начиная с 1. Логика разрешения неоднозначностей не требуется, поскольку сортировка уже это обработала.

// Решения

1. Решение с использованием Pandas

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

import pandas as pd import numpy as np google_gmail_emails = google_gmail_emails.rename(columns={«from_user»: «user_id»}) result = google_gmail_emails.groupby( ['user_id']).size().to_frame('total_emails').reset_index() result['activity_rank'] = result['total_emails'].rank(method='first', ascending=False) result = result.sort_values(by=['total_emails', 'user_id'], ascending=[False, True])

2. Решение полярных растворов

Мы используем ленивую цепочку, которая переименовывает, группирует, сортирует и присваивает номера строкам за один проход. Вызов метода `.collect()` в конце материализует результат.

import polars as pl google_gmail_emails = google_gmail_emails.rename({«from_user»: «user_id»}) result = ( google_gmail_emails.lazy() .group_by(«user_id») .agg(total_emails = pl.count()) .sort( by=[«total_emails», «user_id»], descending=[True, False] ) .with_row_count(«activity_rank», offset=1) .select([ pl.col(«user_id»), «total_emails», «activity_rank» ]) .collect() )

// Сравнение производительности

Полярные птицы против панд

Решение Pandas выполняет итерацию по данным дважды после группировки: один раз для вычисления размеров и один раз для присвоения рангов. Внутри функции rank(method='first') выделяется массив рангов, разрешаются совпадающие значения с помощью argsort и записываются обратно — что значительно дороже, чем кажется для одного столбца. Функция group_by в Polar распределяет рабочую нагрузку между всеми доступными ядрами ЦП, что приводит к значительно более быстрой агрегации для больших таблиц. А поскольку оператор .with_row_count() представляет собой один последовательный проход O(n) после сортировки, он заменяет функцию ранжирования самой дешевой возможной операцией. В таблице, содержащей миллионы записей электронной почты, использование параллельной агрегации без функции ранжирования может привести к 5–10-кратному сокращению времени выполнения по сравнению с подходом Pandas.

Вот предварительный просмотр результата выполнения кода:

ID пользователя total_emails activity_rank
32ded68d89443e808 19 1
ef5fe98c6b9f313075 19 2
5b8754928306a18b68 18 3
55e60cfcc9dc49c17e 16 4
91f59516cb9dee1e88 16 5
e6088004caf0c8cc51 6 25

# Использование cumcount() + pivot() вместо over(): поиск покупок пользователей

В этом задании нас просят определить активных пользователей, совершивших повторную покупку, — а именно, тех, кто совершил вторую покупку в течение 1 и 7 дней после первой. Покупки, совершенные в один и тот же день, не должны включаться. Результатом будет просто список соответствующих значений user_id.

// Представление данных

В таблице amazon_transactions содержится одна строка на каждую покупку, включающая user_id, item, created_at date и revenue.

Вот предварительный просмотр таблицы:

идентификатор ID пользователя элемент создано_в доход
1 109 молоко 2020-03-03 123
2 139 печенье 2020-03-18 421
3 120 молоко 2020-03-18 176
100 117 хлеб 2020-03-10 209

Grain (что означает одна строка выходных данных): один идентификатор пользователя, совершившего соответствующую возвратную покупку в течение 7 дней с момента первой.

// Крайний случай

Покупки, совершенные в один день, не учитываются, то есть промежуток между первой и второй покупкой должен превышать 0 дней и составлять не более 7 дней. Клиент, совершивший две покупки в один день, не имеет права на льготу.

// Решения

Оба решения находят самую раннюю дату покупки каждого пользователя, а затем фильтруют последующие покупки в течение 1–7 дней. Следует обратить внимание на один момент: если поле created_at содержит временные метки вместо обычных дат, необходимо обрезать значение до даты перед сравнением. В противном случае две покупки, совершенные в разное время в один и тот же день, будут некорректно проходить проверку на строгое неравенство.

1. Решение с использованием Pandas

В Pandas решение включает в себя выделение уникальных дат покупок для каждого пользователя, ранжирование их с помощью функции cumcount(), преобразование данных для получения первой и второй дат рядом и вычисление разницы в днях.

import pandas as pd amazon_transactions[«purchase_date»] = pd.to_datetime(amazon_transactions[«created_at»]).dt.date daily = amazon_transactions[[«user_id», «purchase_date»]].drop_duplicates() ranked = daily.sort_values([«user_id», «purchase_date»]) ranked[«rn»] = ranked.groupby(«user_id»).cumcount() + 1 first_two = (ranked[ranked[«rn»] = 1) & (first_two[«diff»] pl.col(«first_purchase_date»)) & (pl.col(«created_at»)

Источник: www.kdnuggets.com

✅ Найденные теги: Pandas, Polar, Анализ, Библиотеки, Использование, новости, Производительность

Добавить комментарий

Нет других записей в этой рубрике.

Новости других рубрик

Архив рубрики ~Лента новостей~: Стремительный рост солнечной энергетики и увеличение доли гидроэнергетики вытесняют всё больше угольных электростанций из энергосистемы США. Архив рубрики ~Лента новостей~: Apple заявляет, что иск Epic не должен менять правила App Store для всех разработчиков. Архив рубрики ~Лента новостей~: Водить полезно для здоровья Архив рубрики ~Лента новостей~: Карманный компьютер OneXPlayer X1 Pro обновился до процессора Ryzen AI 9 HX 470 Архив рубрики ~Лента новостей~: Принципы построения колоды для писателей: как создать эффективный инструмент для творчества Архив рубрики ~Лента новостей~: Все в режиме реального времени занимаются вопросами безопасности ИИ — даже Google. Архив рубрики ~Лента новостей~: Структура с дифференциальной приватностью для получения информации об использовании чат-ботов на основе ИИ. Архив рубрики ~Лента новостей~: Компании Cepheid и Oxford Nanopore расширяют партнерство, Revvity запускает новую платформу и многое другое.