Одни и те же данные можно хранить «длинными» и «широкими». Умение быстро переходить между формами экономит половину времени на live-задаче.
Длинный и широкий формат
Длинный (long, tidy): одна строка на одно наблюдение.
| date | city | revenue |
|---|---|---|
| 01.10 | Москва | 100 |
| 01.10 | Казань | 40 |
| 02.10 | Москва | 120 |
Широкий (wide): значения одной переменной разложены по колонкам.
| date | Москва | Казань |
|---|---|---|
| 01.10 | 100 | 40 |
| 02.10 | 120 | NaN |
Длинный удобен для groupby, merge и seaborn. Широкий удобен для отчёта, корреляций между колонками и тепловой карты.
pivot: длинный в широкий без агрегации
wide = df.pivot(index='date', columns='city', values='revenue')
pivot только переставляет значения. Если пара (date, city) встречается дважды, он выбросит ошибку ValueError: Index contains duplicate entries: непонятно, какое из двух значений поставить в ячейку.
pivot_table: со сводкой
pd.pivot_table(df, index='date', columns='city', values='revenue',
aggfunc='sum', fill_value=0, margins=True)
aggfuncпо умолчанию'mean'. Частая ошибка: хотели сумму выручки, получили среднее.fill_valueзаменяет пустые ячейки.margins=Trueдобавляет строку и колонкуAllс итогами.
Дубликаты для pivot_table не проблема, он их агрегирует.
melt: широкий в длинный
long = wide.reset_index().melt(id_vars='date', var_name='city', value_name='revenue')
id_vars остаются как есть, все остальные колонки «расплавляются» в пары (var_name, value_name). Типичный случай: выгрузка из Excel с колонками-месяцами, которую нужно превратить в строки для groupby.
stack и unstack
Работают с индексом:
unstack()переносит последний уровень индекса строк в колонки;stack()делает обратное.
s = df.groupby(['date', 'city'])['revenue'].sum() # Series с MultiIndex
s.unstack('city', fill_value=0) # то же, что pivot_table
Связка groupby + unstack встречается в реальном коде чаще, чем pivot_table.
crosstab: таблица сопряжённости
pd.crosstab(df['segment'], df['converted'], normalize='index')
Считает частоты по двум признакам. normalize='index' превращает их в доли по строкам: например, конверсию каждого сегмента. Это же вход для теста хи-квадрат.
Когортная таблица
Классика собеса: retention по когортам. Это длинная таблица (cohort_month, months_since, users), развёрнутая через pivot, а потом поделённая на первый столбец:
cohorts = counts.pivot(index='cohort', columns='age', values='users')
retention = cohorts.div(cohorts[0], axis=0).round(3)
Частые ошибки
pivotна данных с дублями ключа: нуженpivot_tableили предварительныйgroupby.- Забыли
aggfunc='sum'вpivot_tableи получили среднее. - После
pivotколонки стали индексом с именемcity, и дальше путаница: помогаетreset_index()иcolumns.name = None. - После
pivot_tableс несколькимиvaluesколонки становятся MultiIndex. - Пустые ячейки NaN ломают суммы по строкам в отчёте, если ожидали 0.
Что спросят на собесе
- Чем
pivotотличается отpivot_table? - Как превратить колонки-месяцы в строки?
- Что делают
stackиunstack? - Как построить когортную таблицу retention?
- Какой
aggfuncуpivot_tableпо умолчанию?