2025, Nov 01 09:46

pandas без циклов: считаем доли издателей в продажах видеоигр

Разбираем, как в pandas без циклов посчитать доли издателей в мировых продажах видеоигр: groupby, assign, mask; объединение «Другое» и круговая диаграмма.

Преобразование исследовательской работы с данными в понятный, воспроизводимый код обычно начинается с отказа от лишних промежуточных объектов и циклов. Обычный кейс в pandas: агрегировать метрики, посчитать проценты, объединить мелких вкладчиков в категорию «Другое» и построить диаграмму. Ниже — сжатое объяснение, как заменить подход на циклах на связанный в цепочку векторизованный конвейер, сохранив точное поведение.

Описание задачи

Задача: по датасету Kaggle videogamesales вычислить долю каждого издателя в мировых продажах видеоигр, объединить издателей с вкладом меньше 2% в группу «Другое» и вывести результат на круговой диаграмме. Исходная реализация работает, но порождает промежуточные объекты, меняет состояние между шагами и вручную перебирает строки циклом, чтобы переименовать элементы ниже порога.

Базовый код, требующий упрощения

Ниже показан исходный подход с явными промежуточными переменными и циклом for. Логика сохранена, лишь имена немного уточнены для наглядности.

pub_sales_tbl = games_df.groupby('Publisher')[['Global_Sales']].sum().sort_values('Global_Sales', ascending=False)
all_sales = pub_sales_tbl['Global_Sales'].sum()
pub_sales_tbl['Share'] = pub_sales_tbl['Global_Sales'] / all_sales
pos = 0
cutoff = 0.02
publisher_labels = list(pub_sales_tbl.index)
for ratio in pub_sales_tbl['Share']:
    if ratio < cutoff:
        publisher_labels[pos] = 'Other'
    pos = pos + 1
pub_sales_tbl.index = publisher_labels

pub_sales_tbl = pub_sales_tbl.groupby(pub_sales_tbl.index).sum().sort_values('Global_Sales', ascending=False)
plt.title("most profitable publisehr")
plt.ylabel("")
pie_ax = pub_sales_tbl['Global_Sales'].plot(kind='pie', figsize=(10, 5), legend=False)
pie_ax.set_ylabel("")
plt.show()

Что происходит и почему это не оптимально

Этот процесс корректно считает суммы, доли и проверяет условие, но затем проходит по позициям индекса с ручным счётчиком, чтобы переименовать записи ниже порога. Такой приём и рискован, и избыточен для pandas: библиотека уже предоставляет векторизованные операции, позволяющие преобразовать столбцы и переназначить метки за один проход. Даже небольшое улучшение вроде использования enumerate для индекса не решает главной проблемы: построчный цикл здесь попросту не нужен. Результат останется тем же, но его можно выразить декларативно и собрать по шагам, не создавая и не изменяя множество промежуточных переменных в общем пространстве имён.

Связанное в цепочку, векторизованное решение

Следующий конвейер с цепочкой методов получает тот же результат из того же набора данных. Он строит круговую диаграмму и обходится без явных циклов и лишних переменных. Результатом является фигура matplotlib; при желании последний шаг можно адаптировать, чтобы вернуть агрегированные данные для построения. Набор данных сравнительно небольшой, поэтому здесь сделана ставка на понятность, а не на микрооптимизации.

import pandas as pd

records = pd.read_csv('vgsales.csv')

(
    records
    .groupby('Publisher', as_index=False)['Global_Sales'].sum()
    .rename(columns={'Global_Sales': 'alltime_sales'})
    .assign(
        alltime_sales_percent=lambda frame: frame['alltime_sales'].div(frame['alltime_sales'].sum()).mul(100)
    )
    .assign(
        major_publisher_name=lambda frame: frame['Publisher'].mask(frame['alltime_sales_percent'] < 2, 'Other')
    )
    .pipe(lambda frame: display(frame) or frame)
    .groupby('major_publisher_name')['alltime_sales_percent'].sum()
    .sort_values(ascending=False)
    .plot.pie(
        title='All-time sales share of major publishers',
        ylabel='', figsize=(5, 5), cmap='tab20b'
    )
);

Почему это важно

Цепочка шагов удерживает весь ход преобразований в одном читаемом пайплайне: агрегировать, перевести в проценты, переименовать по условию, свернуть, отсортировать и построить график. Это снижает когнитивную нагрузку, не засоряет пространство имён и делает каждый шаг проверяемым. Если понадобится изменить порог или посмотреть на промежуточное преобразование, достаточно одной строки, а не рефакторинга нескольких переменных. Подход соответствует идиоматике pandas и концептуально масштабируется лучше, чем ручные циклы для построчных правок.

Практические выводы

Когда нужно объединить мелкие категории в «Другое» и показать их долю, выбирайте векторизованные преобразования — например, assign и mask в связке с groupby. Если всё же остаётесь на цикле, замена ручного счётчика индекса на enumerate немного улучшит качество кода, но в этом случае проще и надёжнее вообще обойтись без цикла. Возвращаемую фигуру цепочного вызова можно использовать как есть или изменить финальный шаг, чтобы вернуть агрегированный ряд для дальнейшего использования.

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

Статья основана на вопросе на StackOverflow от just a tw highschooler и ответе MuhammedYunus SaveGaza.