Собесов

Karpov Stepik: χ² тест независимости — гипотеза о связи признаков

Статистика и теорверχ² тестыСредняяJunior

Условие

Опрос: проверяем, зависит ли «купил подписку» от «канал привлечения».

Таблица сопряжённости:

Купили Не купили
Google Ads 120 380
Яндекс.Директ 90 410
Органика 200 300

Проверьте H₀: канал и покупка независимы через χ². Какие предположения важны и как поведёт себя тест при N=10 или N=10М?

Решение

Алгоритм

  1. Маргинальные суммы по строкам и столбцам.
  2. Ожидаемые частоты при независимости: E_ij = (row_i · col_j) / N.
  3. Статистика: χ² = Σ (O_ij − E_ij)² / E_ij.
  4. df = (rows − 1) × (cols − 1) = (3-1)(2-1) = 2.
  5. Сравнить с χ²(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.0167

N мал и 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, но эффект не «огромный».

Подводные камни

  1. expected < 5 в ячейке → χ² ненадёжен. Лучше Fisher exact для 2×2 / 2×3 или объединение редких категорий.
  2. independence vs homogeneity: математика та же, интерпретация разная. Independence — обе переменные случайны; homogeneity — фиксированы row totals (опрос фиксированной структуры).
  3. degrees of freedom: для 2×2 это df=1, не df=4.
  4. Yates correction в 2×2 — спорно; chi2_contingency в scipy применяет по умолчанию (correction=True).
  5. χ² → p-value, и всё: не путать с величиной эффекта. На больших N всё значимо. Cramér's V добавляет смысл.
  6. Не использовать χ² для ordinal данных (низкое/среднее/высокое) — теряется порядок. Использовать линейный тренд-тест или ranks.
  7. 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.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти