статистикахи-квадраткатегориальные данные

Тест хи-квадрат: когда и как применять в аналитике

2026-07-11 12 мин

Тест хи-квадрат отвечает на один прикладной вопрос: связаны ли между собой две категориальные переменные, или различия в данных — просто шум выборки. Если у тебя есть таблица с подсчётами (сколько людей из региона A купили, сколько не купили, и то же для региона B) и ты хочешь понять, влияет ли регион на факт покупки — это работа для хи-квадрата, а не для t-теста. Ключевое отличие держи в голове с самого начала: t-тест сравнивает средние, хи-квадрат сравнивает частоты по категориям. Дальше разберу, как он устроен внутри, когда его брать, где легко ошибиться и как посчитать всё за пять минут на scipy.

Что такое тест хи-квадрат простыми словами?

Представь, что у тебя есть две колонки, и обе — категориальные. Например, «канал привлечения» (органика / реклама / реферал) и «оформил подписку» (да / нет). Ты подозреваешь, что канал влияет на конверсию, но глазами по таблице этого не видно — числа разные, но мало ли, случайность.

Хи-квадрат формализует эту интуицию. Он берёт то, что ты реально наблюдал, и сравнивает с тем, что было бы, если бы переменные были полностью независимы — то есть если бы канал вообще не влиял на подписку. Чем сильнее наблюдаемая картина расходится с этой «нулевой» независимой картиной, тем больше значение статистики и тем меньше p-value.

Сам критерий считается тупо и прозрачно: по каждой ячейке таблицы берём (наблюдаемое - ожидаемое) в квадрате, делённое на ожидаемое, и суммируем по всем ячейкам. Возведение в квадрат убирает знак и сильнее штрафует крупные отклонения, а деление на ожидаемое нормирует: расхождение в 10 человек критично, когда ждали 12, и почти незаметно, когда ждали 2000.

Как построить таблицу сопряжённости?

Таблица сопряжённости (contingency table, ещё говорят «кросс-таб») — это основа всего. По строкам одна переменная, по столбцам другая, в ячейках — количество наблюдений, попавших в это сочетание. Для нашего примера с каналами и подпиской:

КаналПодписалсяНе подписался
Органика90910
Реклама140860
Реферал70430

В аналитике такую таблицу почти всегда собираешь прямо из сырых данных запросом. В Postgres удобнее всего через filter:

select
    channel,
    count(*) filter (where subscribed) as subscribed,
    count(*) filter (where not subscribed) as not_subscribed
from users
group by channel;

Если хочешь потренировать такие агрегаты на живой базе — загляни в SQL-тренажёр, там как раз задачи на group by и условную агрегацию, а базовую механику разбираем в курсе по SQL. Числа из этого запроса — ровно то, что дальше уйдёт в тест. Никакой магии: аналитик 80% времени тратит на то, чтобы правильно собрать таблицу, и только 20% — на сам критерий.

Откуда берутся ожидаемые частоты?

Это самое важное место, и его чаще всего пропускают. Ожидаемая частота — это сколько наблюдений попало бы в ячейку, если бы строки и столбцы были независимы. Формула на словах простая: сумма по строке умножить на сумму по столбцу и поделить на общее число наблюдений.

Посчитаем для органики. В нашей таблице всего 3300 пользователей, из них подписались 300, а на органику приходится 1000. Значит, при независимости ожидаем 1000 * 300 / 3300, это примерно 90.9 подписки от органики. Наблюдали мы 90 — почти совпало, органика ведёт себя «средне». А вот у рекламы ожидаемое тоже около 90.9, а наблюдали 140 — вот тут расхождение, которое и раскачивает статистику вверх.

Логика такая: если общая конверсия по всей выборке 300/3300 ≈ 9%, то при отсутствии эффекта канала каждый канал должен давать примерно свои 9%. Ожидаемые частоты — это буквально «размазанная равномерно» конверсия. Тест меряет, насколько реальность от этого отклонилась.

Хорошая новость: руками это считать не надо, scipy вернёт матрицу ожидаемых частот сам. Но понимать, откуда они, обязательно — иначе не поймёшь, почему тест иногда нельзя применять (об этом ниже).

Тест независимости и тест согласия — в чём разница?

Под названием «хи-квадрат» прячутся два разных сценария, и их путают.

Тест независимости — тот, что мы уже разбираем. У тебя две категориальные переменные и таблица сопряжённости, ты проверяешь гипотезу «эти переменные не связаны». Число степеней свободы тут считается как (число строк минус 1) умножить на (число столбцов минус 1). Для таблицы 3×2 это 2 * 1 = 2.

Тест согласия (goodness of fit) — другой зверь. Тут переменная одна, и ты сравниваешь её распределение с каким-то эталонным, которое задаёшь сам. Классика: проверить, равномерно ли распределены заходы на сайт по дням недели, или соответствует ли распределение заказов по категориям тому, что было в прошлом квартале. Степеней свободы здесь число категорий минус 1.

Простой критерий, чтобы не запутаться: если у тебя двумерная таблица и вопрос «связаны ли A и B» — это независимость. Если одномерный вектор частот и вопрос «похоже ли это на ожидаемое распределение» — это согласие. В scipy под них даже разные функции: chi2_contingency и chisquare.

Когда применять хи-квадрат вместо t-теста?

Развилка простая и держится на одном вопросе: что у тебя за метрика — среднее или доля/частота.

Бери t-тест (или его непараметрические аналоги), когда сравниваешь средние непрерывной величины: средний чек, время на сайте, количество сессий на пользователя. Там осмысленно говорить «в группе A в среднем 4.2 сессии, в группе B — 4.8».

Бери хи-квадрат, когда данные — это категории и подсчёты. «Конвертировался / нет», «выбрал тариф Basic / Pro / Enterprise», «ушёл в отток / остался». Тут среднее считать бессмысленно, у тебя частоты по корзинам, и вопрос стоит про связь категорий, а не про разницу средних.

Частая ошибка новичка — закодировать «купил» как 1, «не купил» как 0 и погнать t-тест по этому столбцу. Формально посчитается, но ты теряешь корректную модель: для доли есть свои критерии, и хи-квадрат (или z-тест для двух пропорций) тут честнее. Если хочешь набить руку в различении этих ситуаций, на собеседованиях их спрашивают постоянно — подборка есть в разделе вопросов с интервью, а прикладные разборы на реальных данных — в кейсах.

Как хи-квадрат связан с A/B-тестами на конверсиях?

Напрямую. A/B-тест на конверсию — это и есть таблица сопряжённости 2×2: по строкам две группы (контроль и вариант), по столбцам два исхода (сконвертировался / нет). Проверить, что конверсия в варианте отличается от контроля не случайно, — ровно задача теста независимости.

Здесь есть красивый факт: для таблицы 2×2 тест хи-квадрат на независимость математически эквивалентен z-тесту для сравнения двух долей. Значение статистики хи-квадрат равно квадрату z-статистики, и p-value совпадают. Так что если тебя спросят «чем ты проверял значимость аплифта конверсии», ответ «хи-квадратом» и «z-тестом для пропорций» — про одно и то же, если групп две и исходов два.

Из этого же следует ограничение. Хи-квадрат скажет тебе только «связь есть / нет», но не размер эффекта и не доверительный интервал на разницу конверсий. Для продуктовых решений тебе нужен именно интервал: аплифт +0.3 процентного пункта может быть статзначимым на огромной выборке и при этом коммерчески бесполезным. Поэтому в отчёте по A/B я всегда рядом с p-value кладу разницу долей и её интервал. Про то, как метрики вроде конверсии и DAU складываются в продуктовую картину, стоит думать до того, как гонишь тест, — иначе получишь значимость, которая никому не нужна.

Какие условия применимости надо проверить?

Хи-квадрат — приближённый критерий, и приближение ломается на малых частотах. Базовые правила, которые я проверяю всегда:

Ожидаемые частоты должны быть достаточно большими. Классическое правило большого пальца: ожидаемая частота в каждой ячейке не меньше 5. Иногда допускают, что до 20% ячеек могут иметь ожидаемое от 1 до 5, но если у тебя ячейки с ожидаемым меньше 1 — тест врёт. Обрати внимание: правило про ожидаемые частоты, а не про наблюдаемые. Именно поэтому важно понимать, откуда они берутся.

Наблюдения должны быть независимы. Каждая строка данных — отдельный объект. Если один и тот же пользователь попал в таблицу пять раз (пять сессий), независимость нарушена, и p-value занижено — тест покажет значимость там, где её нет. Перед тестом почти всегда нужно агрегировать до уровня пользователя.

Данные — это счётчики, а не проценты. Хи-квадрат работает с абсолютными частотами. Нельзя скормить ему готовые доли или нормированные на 100 значения — статистика зависит от размера выборки, и на процентах она сломается.

Что делать, если частоты маленькие. Для таблицы 2×2 при небольших числах берут точный тест Фишера (scipy.stats.fisher_exact) — он не приближённый и корректен на любых частотах. Ещё вариант — укрупнить категории: объединить редкие тарифы в «прочее», чтобы поднять ожидаемые частоты выше порога.

Пример на scipy: считаем за пять минут

Тест независимости на нашей таблице каналов. Функция сама посчитает ожидаемые частоты и степени свободы:

import numpy as np
from scipy.stats import chi2_contingency

# строки — каналы, столбцы — подписался / не подписался
observed = np.array([
    [90,  910],   # органика
    [140, 860],   # реклама
    [70,  430],   # реферал
])

chi2, p, dof, expected = chi2_contingency(observed)

print(f"chi2 = {chi2:.3f}")
print(f"p-value = {p:.5f}")
print(f"степеней свободы = {dof}")
print("ожидаемые частоты:")
print(expected.round(1))

if p < 0.05:
    print("Связь есть: канал влияет на подписку")
else:
    print("Оснований считать переменные связанными нет")

Первым делом смотрю не на p-value, а на матрицу expected — все ли ячейки выше 5. Если да, читаю p-value. Меньше выбранного порога (обычно 0.05) — отвергаем гипотезу независимости, связь между каналом и подпиской статистически значима.

Отдельно про таблицы 2×2, то есть про твои A/B: chi2_contingency по умолчанию применяет поправку Йейтса на непрерывность (correction=True). Она делает тест чуть консервативнее, и на пограничных случаях p-value может уехать с 0.049 на 0.051. Это не баг, а особенность — просто знай про неё и не удивляйся, если ручной расчёт без поправки даёт другое число.

Тест согласия делается другой функцией. Проверим, равномерно ли заходят на сайт по дням недели:

from scipy.stats import chisquare

# заходы по дням недели (пн..вс)
observed = [210, 190, 205, 198, 260, 380, 350]

# ждём равномерность: total делим на 7
expected = [sum(observed) / 7] * 7

chi2, p = chisquare(observed, f_exp=expected)
print(f"chi2 = {chi2:.2f}, p = {p:.6f}")

Тут важная тонкость: сумма ожидаемых частот должна равняться сумме наблюдаемых, иначе chisquare ругнётся. Поэтому я не задаю ожидаемое «по 250 в день с потолка», а размазываю реальный суммарный трафик поровну. Если p-value мало — распределение по дням не равномерное (что на трафике почти всегда так: выходные выбиваются).

Хочешь прогнать эти сниппеты вживую — Python-тренажёр запускает scipy прямо в браузере, а синтаксис функций всегда можно подсмотреть в справочнике по Python. Закрепить навык на прикладных задачах помогают тестовые задания — там встречаются и категориальные метрики, и A/B на конверсиях.

Как читать результат и не облажаться

Соберу воедино чек-лист, по которому я реально прохожусь перед тем, как написать в отчёте «значимо».

Сначала проверяю тип данных: категории и счётчики — да, хи-квадрат уместен; средние — нет, это к t-тесту. Потом смотрю ожидаемые частоты: все ≥5, иначе беру Фишера или укрупняю категории. Убеждаюсь, что наблюдения независимы и я не считаю одного пользователя несколько раз. Только после этого читаю p-value и сравниваю с заранее выбранным порогом — заранее, а не подгоняя под желаемый вывод.

И главное, что часто забывают: p-value говорит только о наличии связи, но ничего не говорит о её силе и практической пользе. На выборке в миллион строк значимым станет почти любое микроотличие. Поэтому рядом с результатом теста я всегда кладу сам размер эффекта — разницу конверсий, отношение шансов или меру вроде Крамера V. Значимость без величины эффекта — это половина ответа, и на защите решения вторую половину обязательно спросят.

Если хочешь довести этот навык до автомата — на статистику, A/B и SQL-агрегаты у нас собран полный тренажёр с разбором каждого шага, и Pro-доступ открывает все задачи и кейсы разом, без дневных лимитов. Но и на бесплатном тарифе примеров выше хватит, чтобы завтра посчитать хи-квадрат на своих данных без гугления.

Готовься к собесу аналитика
Вопросы по статистике и A/B с разбором — попробуй бесплатно.
Банк вопросов →