Привет, Хабр! Меня зовут Панчин Денис, это мой первый пост и я хочу Вам рассказать о работе с сводными таблицами в Pandas. Сводные таблицы хорошо известны всем аналитикам по Excel. Это прекрасный инструмент, который помогает быстро получить различную информацию по массиву данных. Рассмотрим реализацию и тонкости сводных таблиц в Pandas.
Для эксперимента возьмём датасет "Крупные города России: объединенные данные по основным социально-экономическим показателям за 1985-2019 гг.".
Блокнот можно открыть здесь.
Использовать будем только столбцы 'region' (субъект РФ), 'municipality' (муниципальное образование), 'year' (год), 'birth' (число родившихся на 1000 человек населения), 'wage' (cреднемесячная номинальная начисленная заработная плата, руб.). Сразу оговорюсь, что Москва и Санкт-Петербург являются отдельными субъектами Российской Федерации и в этом датафрейме отсутствуют.
import pandas as pd
import numpy as np
df = pd.read_csv('Krupnie_goroda-RF_1985-2019_187_09.12.21/data.csv', sep=';')
df = df[['region', 'municipality', 'year', 'birth', 'wage']]
df.sample(7)
Минимальную статистику можно получить использовав метод describe(include = 'all'). Мы видим что у нас 4109 строки, по 81 региону и 202 городам. Средняя рождаемость на 1000 человек 11,39, минимальная — 3,4, максимальная - 36,1.
df.describe(include = 'all')
Агрегирование
Если объяснять простыми словами, то агрегирование — это процесс приведения некого большого массива значений к некому сжатому по определенному параметру массиву значений. Например, среднее, медиана, количество, сумма.
df[['birth', 'wage']].agg(['mean', 'median', 'min', 'max'])
Groupby
Но средняя температура по больнице нам не интересна, мы хотим знать победителей в лицо. Допустим нам нужно посмотреть средние значения с группировкой по городам и субъектам РФ. Для этого закономерно используем метод groupby(['region', 'municipality']).agg('mean').
df_groupby = df.groupby(['region', 'municipality']).agg('mean')
df_groupby.head(7)
Обратите внимание, что колонки region и municipality стали индексами.
На этом мы не успокаиваемся и пытаемся выжать больше стат.данных: среднее, медиану, минимум, максимум.
agg_func_math = {
'birth': ['mean', 'median', 'min', 'max'],
'wage': ['mean', 'median', 'min', 'max']
}
df.groupby(['region', 'municipality']).agg(agg_func_math).head(7)
Посмотрим топ городов по зарплатам.
df.groupby(['region', 'municipality']).agg('mean').sort_values(by='wage', ascending=False).head(7)
А что если посмотреть данные в разрезе по годам...?
Pivot table
Ответим на этот вопрос чуть позже, а пока создадим сводную таблицу аналогичную той, что мы уже делали ранее.
df_pivot_table = df.pivot_table(index=['region', 'municipality'])
df_pivot_table.head(7)
Уже на этом этапе видно, что сводная таблица достаточно умная и сама агрегировала данные и посчитала средние значения.
Но если мы захотим расширить таблицу новыми значениями — медианой, то увидим, что конечный результат по структуре будет отличаться от того, что мы делали при использовании метода groupby.
df_pivot_table = df.pivot_table(index=['region', 'municipality'],
values=['birth', 'wage'],
aggfunc=[np.mean, np.median])
df_pivot_table.head(7)
Так мы плавно подошли к тем преимуществам, которые делает сводные таблицы швейцарским ножом для аналитиков. Если используя groupby мы «укрупняли» строки по городам, то сейчас мы можем «развернуть» столбец, например, 'year' (год) и посмотреть данные в разрезе по годам.
df_pivot_table = df.pivot_table(index=['region', 'municipality'],
values='birth',
columns='year')
df_pivot_table.head(7)
И, конечно, данные в сводной таблице можно фильтровать. Создадим сводную таблицу и оставим в ней данные по городам, в которых рождаемость на 1000 человек превышает 12, зарплата выше 40.000 и отсортируем всё по убыванию рождаемости.
df2 = df.pivot_table(index=['region', 'municipality'])
df2.query("`birth`>12 and `wage`>40000").sort_values(by='birth', ascending=False)
Pivot
Всё было замечательно, но в Pandas кроме pivot_table есть ещё просто pivot. Посмотрим что это за зверь и чем они отличаются.
Создадим pivot: рождаемость в разрезе по регионам и годам.
df_pivot = df.pivot(index='region',
values='birth',
columns='year')
df_pivot.head(7)
Мы получили ошибку «Index contains duplicate entries, cannot reshape«. Что-то не так с индексами, попробуем создать pivot с индексами по городам, а не регионам.
df_pivot = df.pivot(index='municipality',
values='birth',
columns='year')
df_pivot.head(7)
Всё получилось. Как мы уже заметили всё дело в индексах. На самом деле всё в повторяющихся индексах. Как мы видели ранее в датафрейме есть позиции с несколькими городами в рамках одного субъекта, так получаются дублированные данные индексов с которыми не умеет работать pivot.
Вывод
Groupby, pivot и pivot_table удобные и привычные инструменты для работы с данными. Groupby позволяет кодом в одну строку получить агрегированные и сортированные данные, а pivot и pivot_table работать в более глубоком разрезе. Pivot_table предпочтителен, т.к. не ограничивает вас в уникальности значений в столбце индекса. И, конечно, все эти данные можно фильтровать под ваши запросы.