Как посчитать chi-square test в SQL
Содержание:
Зачем chi-square
Chi-square test (χ²) проверяет, связаны ли две категориальные переменные. Самое типичное применение — A/B-тест на бинарную метрику (конвертнул / не конвертнул) для двух или более групп. Если хочется проверить «реально ли вариант B показывает другой CR, или это шум выборки» — это chi-square.
Таблица сопряжённости
Базовая структура — 2×2 (для A/B):
| Конверсия | Без конверсии | |
|---|---|---|
| Группа A | 120 | 880 |
| Группа B | 145 | 855 |
Сначала эту таблицу нужно построить в SQL, потом считать тест.
Формула
χ² = Σ (O_i − E_i)² / E_iO_i— наблюдаемое значение в ячейкеE_i— ожидаемое при независимости = (row_total × col_total) / grand_total
Степени свободы: (rows−1) × (cols−1). Для 2×2 → df=1.
P-value считают по χ²-распределению — в чистом SQL точное значение проблематично, обычно сравнивают с критическим (df=1, α=0.05 → χ² > 3.841).
Chi-square для A/B-теста
WITH cells AS (
SELECT
variant,
SUM(CASE WHEN converted THEN 1 ELSE 0 END) AS conv,
SUM(CASE WHEN NOT converted THEN 1 ELSE 0 END) AS non_conv,
COUNT(*) AS row_total
FROM ab_experiment_users
WHERE experiment_id = 'checkout_v2'
GROUP BY variant
),
totals AS (
SELECT
SUM(conv) AS total_conv,
SUM(non_conv) AS total_non_conv,
SUM(row_total) AS grand_total
FROM cells
),
expected AS (
SELECT
c.variant,
c.conv AS o_conv,
c.non_conv AS o_non_conv,
c.row_total::NUMERIC * t.total_conv / NULLIF(t.grand_total, 0) AS e_conv,
c.row_total::NUMERIC * t.total_non_conv / NULLIF(t.grand_total, 0) AS e_non_conv
FROM cells c
CROSS JOIN totals t
)
SELECT
SUM(POWER(o_conv - e_conv, 2) / NULLIF(e_conv, 0))
+ SUM(POWER(o_non_conv - e_non_conv, 2) / NULLIF(e_non_conv, 0)) AS chi_square,
1 AS df,
CASE
WHEN SUM(POWER(o_conv - e_conv, 2) / NULLIF(e_conv, 0))
+ SUM(POWER(o_non_conv - e_non_conv, 2) / NULLIF(e_non_conv, 0)) > 3.841
THEN 'significant at 0.05'
ELSE 'NOT significant'
END AS verdict
FROM expected;Критические значения для df=1: 3.841 (α=0.05), 6.635 (α=0.01), 10.828 (α=0.001).
Несколько групп
Для k вариантов (A/B/C/...):
WITH cells AS (
SELECT
variant,
SUM(CASE WHEN converted THEN 1 ELSE 0 END) AS conv,
SUM(CASE WHEN NOT converted THEN 1 ELSE 0 END) AS non_conv,
COUNT(*) AS row_total
FROM ab_experiment_users
WHERE experiment_id = 'multivariate_v1'
GROUP BY variant
),
totals AS (
SELECT
COUNT(*) AS k,
SUM(conv) AS total_conv,
SUM(non_conv) AS total_non_conv,
SUM(row_total) AS grand_total
FROM cells
),
chi AS (
SELECT
SUM(POWER(c.conv - c.row_total::NUMERIC * t.total_conv / t.grand_total, 2)
/ NULLIF(c.row_total::NUMERIC * t.total_conv / t.grand_total, 0))
+ SUM(POWER(c.non_conv - c.row_total::NUMERIC * t.total_non_conv / t.grand_total, 2)
/ NULLIF(c.row_total::NUMERIC * t.total_non_conv / t.grand_total, 0)) AS chi_square,
t.k - 1 AS df
FROM cells c
CROSS JOIN totals t
GROUP BY t.k
)
SELECT chi_square, df FROM chi;P-value уже считают вне SQL — Python scipy.stats.chi2.sf(chi_square, df).
Частые ошибки
Ошибка 1. Применять на малых частотах. Если ожидаемое в ячейке < 5 — chi-square даёт неверный p-value. Используйте Fisher exact test (на малых выборках).
Ошибка 2. Integer division.
row_total * total_conv / grand_total в Postgres = 0 для маленьких ячеек без ::NUMERIC. Приводите к NUMERIC до деления.
Ошибка 3. Зависимые наблюдения.
Если один юзер попал в несколько строк (например, posthog event_count), независимость нарушена. Считайте на уровне user_id.
Ошибка 4. Сравнивать с z-test для пропорций без понимания. Для 2×2 chi-square даёт ровно тот же p-value, что two-proportion z-test. Это эквивалентно.
Ошибка 5. Yates correction в больших таблицах. Поправка Йейтса (вычитание 0.5) уместна только для df=1 и маленьких выборок. На больших — занижает chi-square.
Связанные темы
- Как посчитать t-test в SQL
- Как посчитать Mann-Whitney test в SQL
- Что такое chi-square test
- T-test vs chi-square
FAQ
Когда chi-square, а не t-test?
Chi-square — бинарные или категориальные исходы. T-test — непрерывные (revenue, time-on-page).
Какой минимум на ячейку?
Правило большого пальца: ожидаемое в каждой ячейке ≥ 5. Иначе Fisher exact.
Где брать p-value?
В Postgres нет встроенной CDF. Считают вне SQL: scipy, R, или табличный лук-ап для крит-значений.
Yates correction обязательна?
Только для 2×2 и маленьких выборок. На N > 1000 — игнорируйте.
Multiple comparisons?
Сравниваете 3+ группы попарно — применяйте Bonferroni или Holm-Bonferroni к p-value.