pivot_table в Pandas: шпаргалка

Зачем аналитику pivot_table

pivot_table — pandas-аналог Excel сводной таблицы. Превращает длинные (long) данные в широкие (wide) для анализа и визуализации.

Базовый синтаксис

df.pivot_table(
    index='строка',
    columns='столбец',
    values='значение',
    aggfunc='mean',     # или 'sum', 'count', ...
    fill_value=0        # чем заполнить NaN
)

Пример

import pandas as pd

df = pd.DataFrame({
    'city': ['M', 'SPb', 'M', 'SPb', 'M'],
    'month': ['Apr', 'Apr', 'May', 'May', 'Apr'],
    'revenue': [100, 50, 200, 80, 150]
})

df.pivot_table(index='city', columns='month', values='revenue', aggfunc='sum')
#        Apr  May
# city          
# M      250  200
# SPb     50   80

pivot vs pivot_table

Метод Агрегация Дубликаты ключей
pivot ❌ нет ошибка
pivot_table ✅ есть (aggfunc) агрегирует

pivot_table — универсальный выбор.

# pivot — только reshape, без агрегации
df.pivot(index='city', columns='month', values='revenue')

# pivot_table — универсальный
df.pivot_table(index='city', columns='month', values='revenue', aggfunc='sum')

Попробовать силы на подобных вопросах проще всего в тренажёре Карьерник — прямо в Telegram, без регистрации через сайт.

aggfunc — разные агрегаты

# Несколько агрегатов на одном поле
df.pivot_table(index='city', columns='month', values='revenue',
               aggfunc=['sum', 'mean', 'count'])

# Разные агрегаты для разных полей
df.pivot_table(index='city', columns='month',
               values=['revenue', 'users'],
               aggfunc={'revenue': 'sum', 'users': 'nunique'})

Multi-index

В index

df.pivot_table(index=['city', 'device'], columns='month', values='revenue')

Результат имеет двойной индекс (city, device).

В columns

df.pivot_table(index='city', columns=['month', 'device'], values='revenue')

Двухуровневые столбцы.

fill_value и margins

df.pivot_table(index='city', columns='month', values='revenue',
               aggfunc='sum',
               fill_value=0,       # NaN → 0
               margins=True,        # добавить итоги
               margins_name='Total')

Итоги по строкам и столбцам — как в Excel.

crosstab — частный случай

Если не хотите агрегировать произвольные значения, а нужны просто counts:

pd.crosstab(df['city'], df['month'])
# Количество записей в каждой комбинации

# С процентами
pd.crosstab(df['city'], df['month'], normalize='index') * 100

crosstab проще, чем pivot_table с aggfunc='count'.

Melt — обратный pivot

Превратить wide обратно в long:

wide = df.pivot_table(index='city', columns='month', values='revenue')

long = wide.reset_index().melt(id_vars='city', value_name='revenue')
# Возвращает к исходной структуре

Пройти 30–50 задач по теме за вечер можно в Telegram-тренажёре. Это то, что отличает «знаю» от «уверенно отвечу на собесе».

Типичные задачи

1. Heatmap по месяцам и категориям

pivot = df.pivot_table(index='category', columns='month',
                        values='revenue', aggfunc='sum', fill_value=0)

import seaborn as sns
sns.heatmap(pivot, annot=True, fmt='.0f')

2. Когортная таблица retention

df['cohort'] = df.groupby('user_id')['date'].transform('min').dt.to_period('M')
df['period'] = df['date'].dt.to_period('M')
df['period_num'] = (df['period'] - df['cohort']).apply(lambda x: x.n)

retention = df.pivot_table(
    index='cohort', columns='period_num',
    values='user_id', aggfunc='nunique', fill_value=0
)
retention = retention.divide(retention[0], axis=0)

3. Сравнение по сегментам

df.pivot_table(
    index='channel', columns='segment', values='conversion', aggfunc='mean'
)

4. Топ-5 категорий по продажам в каждом месяце

pivot = df.pivot_table(index='category', columns='month',
                       values='revenue', aggfunc='sum')

for month in pivot.columns:
    print(pivot[month].sort_values(ascending=False).head(5))

5. Подсчёт уникальных пользователей по дням недели и гео

pd.crosstab(df['day_of_week'], df['country'],
            values=df['user_id'], aggfunc='nunique')

6. Среднее и медиана одновременно

df.pivot_table(index='city', values='amount',
               aggfunc=['mean', 'median', 'std'])

7. Доля каждой категории

# Через crosstab с normalize
pd.crosstab(df['city'], df['category'], normalize='index') * 100

8. Сохранить NaN вместо fill

df.pivot_table(index='city', columns='month', values='revenue')
# Оставит NaN там, где нет данных — полезно для визуализации

9. Несколько values и aggregate разные

df.pivot_table(
    index='city',
    values=['revenue', 'orders', 'users'],
    aggfunc={'revenue': 'sum', 'orders': 'count', 'users': 'nunique'}
)

10. Reset index для export в BI

pivot.reset_index().to_csv('pivot.csv', index=False)

10 вопросов с собесов

1. Чем pivot отличается от pivot_table?

pivot — только reshape, без агрегации (ошибка при дубликатах). pivot_table — с aggfunc, работает всегда.

2. Как заменить NaN в pivot на 0?

fill_value=0 в аргументах.

3. Что такое margins?

Добавляет строку/столбец с итогами (sum/mean/... в зависимости от aggfunc).

4. crosstab vs pivot_table?

crosstab — удобный wrapper над pivot_table для счётчиков и нормализации.

5. Как сделать пивот с несколькими агрегатами?

aggfunc=['sum', 'mean', 'count'] — список функций.

6. Какой aggfunc по умолчанию?

mean.

7. Multi-index в pivot?

Да — index=['a', 'b'] или columns=['c', 'd'].

8. Как превратить wide обратно в long?

Через melt().

9. pivot_table на 10M строк тормозит?

Нормально. Для таких объёмов лучше SQL-агрегация + pivot на результате.

10. Как визуализировать пивот?

heatmap из seaborn / plotly — стандарт для матричных данных.


Как тренироваться

pivot_table — must-have для аналитика. На Kaggle-датасетах постройте 3-4 пивот-таблицы разных типов.

Совет: на собесе, когда просят «построить когортную таблицу» — сразу думайте pivot_table + unstack + divide. Это стандартный паттерн.

Читайте также

FAQ

pivot_table или groupby + unstack?

Функционально эквивалентно. pivot_table читабельнее для простых случаев. groupby+unstack гибче для сложных.

Что быстрее?

На больших данных — groupby обычно. Но разница незаметна на <100k строк.

Можно ли в pivot использовать функцию?

Да: aggfunc=lambda x: x.quantile(0.9) или любая функция, возвращающая скаляр.

Multi-index тяжело читается — что делать?

reset_index() или .unstack(level=) чтобы развернуть нужный уровень в столбцы.