crosstab() в Pandas — кросс-таблицы
Коротко
pd.crosstab() строит таблицу частот для двух (или более) категориальных переменных. Строки — значения одной переменной, столбцы — другой, ячейки — количество наблюдений. Это первый инструмент для анализа связи категорий: «как сегмент пользователя связан с платформой?», «как город влияет на конверсию?». Аналог PIVOT в SQL и сводной таблицы в Excel.
Базовое использование
import pandas as pd
df = pd.DataFrame({
'segment': ['premium', 'free', 'premium', 'free', 'trial', 'premium', 'free', 'trial'],
'platform': ['ios', 'android', 'ios', 'ios', 'android', 'android', 'android', 'ios'],
'revenue': [500, 0, 800, 0, 100, 600, 0, 50]
})
ct = pd.crosstab(df['segment'], df['platform'])
print(ct)| platform | android | ios |
|---|---|---|
| free | 2 | 1 |
| premium | 1 | 2 |
| trial | 1 | 1 |
Каждая ячейка — количество пользователей с такой комбинацией сегмент × платформа.
Нормализация
# По строкам (доля внутри каждого сегмента)
pd.crosstab(df['segment'], df['platform'], normalize='index')
# free: android 0.67, ios 0.33
# premium: android 0.33, ios 0.67
# По столбцам (доля внутри каждой платформы)
pd.crosstab(df['segment'], df['platform'], normalize='columns')
# От общего числа
pd.crosstab(df['segment'], df['platform'], normalize='all')normalize='index' — проценты по строкам. Показывает: из premium 67% на iOS, 33% на Android.
Маргиналы (итоги)
pd.crosstab(df['segment'], df['platform'], margins=True, margins_name='Total')| platform | android | ios | Total |
|---|---|---|---|
| free | 2 | 1 | 3 |
| premium | 1 | 2 | 3 |
| trial | 1 | 1 | 2 |
| Total | 4 | 4 | 8 |
margins=True добавляет строку и столбец с итогами.
Агрегация значений
# Средняя выручка по сегмент × платформа
pd.crosstab(
df['segment'],
df['platform'],
values=df['revenue'],
aggfunc='mean'
)| platform | android | ios |
|---|---|---|
| free | 0 | 0 |
| premium | 600 | 650 |
| trial | 100 | 50 |
С values и aggfunc crosstab работает как pivot_table. Можно использовать 'sum', 'mean', 'count', 'median' и любую функцию.
Практические примеры
Конверсия по городу и каналу
ct = pd.crosstab(
df['city'],
df['utm_source'],
values=df['converted'],
aggfunc='mean',
margins=True
).round(3)
# Какой канал лучше конвертит в каждом городе?Распределение оценок по продукту
ct = pd.crosstab(
df['product'],
df['rating'],
normalize='index'
).round(3) * 100
# Процент 5-звёздочных отзывов по каждому продуктуАктивность по дню недели и часу
df['weekday'] = df['event_time'].dt.day_name()
df['hour'] = df['event_time'].dt.hour
ct = pd.crosstab(df['hour'], df['weekday'])
# Визуализация heatmap
import seaborn as sns
sns.heatmap(ct, cmap='YlOrRd', annot=True, fmt='d')crosstab vs pivot_table
# Эквивалентные вызовы:
# crosstab
pd.crosstab(df['segment'], df['platform'])
# pivot_table
df.pivot_table(index='segment', columns='platform', aggfunc='size', fill_value=0)
# groupby
df.groupby(['segment', 'platform']).size().unstack(fill_value=0)| Метод | Когда |
|---|---|
| crosstab | Частоты двух категорий, быстрый EDA |
| pivot_table | Агрегация с несколькими функциями |
| groupby + unstack | Максимальная гибкость |
crosstab — шорткат для частотного анализа. pivot_table — для сложных агрегаций.
Хи-квадрат тест на crosstab
crosstab — входные данные для теста хи-квадрат (проверка независимости категорий):
from scipy.stats import chi2_contingency
ct = pd.crosstab(df['segment'], df['platform'])
chi2, p_value, dof, expected = chi2_contingency(ct)
print(f'χ² = {chi2:.2f}, p-value = {p_value:.4f}')
# Если p-value < 0.05 → связь между segment и platform статистически значимаАналог в SQL
-- Кросс-таблица через CASE WHEN
SELECT
segment,
COUNT(CASE WHEN platform = 'ios' THEN 1 END) AS ios,
COUNT(CASE WHEN platform = 'android' THEN 1 END) AS android,
COUNT(*) AS total
FROM users
GROUP BY segment;Подробнее — в гайде по PIVOT в SQL.
Типичные ошибки
crosstab без нормализации. Абсолютные числа обманчивы: если free-пользователей 10000, а premium — 100, raw counts не показывают реальную разницу. Используйте normalize='index'.
Слишком много категорий. crosstab из 100 городов × 50 каналов — нечитаемая. Группируйте мелкие категории в «Другие».
Забывают fill_value. Если комбинации нет в данных — NaN. Для подсчётов: pd.crosstab(...).fillna(0).
Вопросы с собеседований
-- Как построить таблицу частот для двух категорий?
-- pd.crosstab(df['col1'], df['col2']). С нормализацией: normalize='index' (по строкам), 'columns' (по столбцам), 'all' (от общего).
-- Чем crosstab отличается от pivot_table? -- crosstab принимает Series (столбцы), по умолчанию считает частоты. pivot_table принимает DataFrame, по умолчанию считает mean. Для частотного анализа crosstab удобнее.
-- Как проверить связь двух категориальных переменных?
-- Построить crosstab, затем тест хи-квадрат: chi2_contingency(ct). Если p-value < 0.05 — связь статистически значима.
Потренируйтесь решать задачи — откройте тренажёр с 1500+ вопросами для подготовки к собеседованиям аналитиков.
FAQ
crosstab с тремя переменными?
Передайте список в index или columns: pd.crosstab([df['city'], df['segment']], df['platform']). Создаст MultiIndex.
Как сохранить crosstab в Excel?
ct.to_excel('crosstab.xlsx'). Для красивого форматирования — ct.style.background_gradient().to_excel('styled.xlsx').
Как тренироваться
crosstab — базовый инструмент EDA. Задачи на pandas — в тренажёре Карьерник. Больше вопросов — в разделе с примерами.