Условие
Опрос: проверяем, зависит ли «купил подписку» от «канал привлечения».
Таблица сопряжённости:
| Купили | Не купили | |
|---|---|---|
| Google Ads | 120 | 380 |
| Яндекс.Директ | 90 | 410 |
| Органика | 200 | 300 |
Проверьте H₀: канал и покупка независимы через χ². Какие предположения важны и как поведёт себя тест при N=10 или N=10М?
Решение
Алгоритм
- Маргинальные суммы по строкам и столбцам.
- Ожидаемые частоты при независимости:
E_ij = (row_i · col_j) / N. - Статистика:
χ² = Σ (O_ij − E_ij)² / E_ij. - df =
(rows − 1) × (cols − 1)=(3-1)(2-1) = 2. - Сравнить с
χ²(0.95, df=2) = 5.99. Если статистика выше — отвергаем H₀.
Числа
Row totals: 500, 500, 500 N = 1500
Col totals: 410, 1090
E_buy_Google = 500 · 410 / 1500 = 136.67
E_buy_Yandex = 136.67
E_buy_Org = 136.67
E_nbuy_Google = 500 · 1090 / 1500 = 363.33
... (по симметрии)
χ² = (120 − 136.67)² / 136.67 + (90 − 136.67)² / 136.67 + (200 − 136.67)² / 136.67
+ (380 − 363.33)² / 363.33 + (410 − 363.33)² / 363.33 + (300 − 363.33)² / 363.33
= 2.03 + 15.94 + 29.36 + 0.76 + 5.99 + 11.04
≈ 65.1
p < 0.001 ✓ H₀ отвергаем
import numpy as np
from scipy.stats import chi2_contingency
table = np.array([[120, 380], [90, 410], [200, 300]])
chi2, p, dof, expected = chi2_contingency(table)
print(chi2, p, dof)
# 65.12, 7e-15, 2Интерпретация
Статистически связь между каналом и покупкой есть. Дальше — стандартизованные остатки:
(O − E) / √E
Положительные большие — «больше, чем ожидалось» (Органика → купили, +5.4 σ). Отрицательные — «меньше» (Google → купили, −1.4 σ).
Постхок: какие пары различаются
# попарные χ² между каналами:
from scipy.stats import chi2_contingency
pairs = [('Google','Yandex'), ('Google','Organic'), ('Yandex','Organic')]
for i, j in [(0,1), (0,2), (1,2)]:
sub = table[[i,j], :]
chi2, p, _, _ = chi2_contingency(sub)
print(f"{pairs[[(0,1),(0,2),(1,2)].index((i,j))]}: p={p:.4g}")
# Bonferroni: α/3 = 0.0167N мал и N огромен
- N=10: ожидаемые частоты могут быть < 5 → χ² ненадёжен. Использовать точный тест Фишера (
scipy.stats.fisher_exact). - N=10М: χ² «значим» при любом микро-отклонении — отдельно смотреть effect size (
Cramér's V):
V = √(χ² / (N · min(rows-1, cols-1)))
Шкалы: V<0.1 — слабая, 0.1-0.3 — средняя, >0.3 — сильная связь.
Cramér's V для нашего случая
V = √(65.1 / (1500 · 1)) ≈ √0.0434 ≈ 0.208
→ средняя связь (V ≈ 0.21). p-value << 0.05, но эффект не «огромный».
Подводные камни
expected < 5в ячейке → χ² ненадёжен. Лучше Fisher exact для 2×2 / 2×3 или объединение редких категорий.independencevshomogeneity: математика та же, интерпретация разная. Independence — обе переменные случайны; homogeneity — фиксированы row totals (опрос фиксированной структуры).degrees of freedom: для 2×2 этоdf=1, неdf=4.- Yates correction в 2×2 — спорно; chi2_contingency в scipy применяет по умолчанию (
correction=True). - χ² → p-value, и всё: не путать с величиной эффекта. На больших N всё значимо. Cramér's V добавляет смысл.
- Не использовать χ² для ordinal данных (низкое/среднее/высокое) — теряется порядок. Использовать линейный тренд-тест или ranks.
- Multiple comparisons: 6 каналов → 15 попарных тестов. Bonferroni / FDR.
Эталонный ответ
χ² = 65.1, df = 2, p < 0.001 → H₀ отвергаем.
Cramér's V ≈ 0.21 → средняя связь.
Канал и покупка связаны. Органика «перевыполняет», Google/Яндекс — недотягивают. Дальше — попарные тесты с Bonferroni для нахождения, какие именно каналы различаются.
Предположения: expected ≥ 5 в каждой ячейке (иначе Fisher exact). На больших N считать Cramér's V для смысла, не только p-value.