Статистические тесты: проверяем гипотезы
«Это различие реально или случайно?» — на этот вопрос отвечают статистические тесты. Разберём их логику и инструменты SciPy.
Статистический тест проверяет, согласуются ли данные с нулевой гипотезой (например, «разницы нет»), и выдаёт p-value — вероятность увидеть такие данные, если гипотеза верна.
Зачем нужны тесты
Допустим, новое удобрение дало урожай в среднем на 5% больше. Это эффект удобрения — или просто повезло с погодой на этих грядках? Глаз тут бессилен: нужна процедура, которая отделит сигнал от шума. Статистический тест формализует это: он считает, насколько удивительны данные при предположении «эффекта нет».
Логика проверки гипотез
1. Нулевая гипотеза H0: "эффекта нет" (по умолчанию верна)
2. Считаем статистику теста по данным
3. Считаем p-value = P(увидеть такие или более крайние данные | H0 верна)
4. Если p-value < α (обычно 0.05) -> отвергаем H0 ("эффект значим")
Иначе -> данных недостаточно, чтобы отвергнуть H0
Важно: маленький p-value не «доказывает» эффект и не говорит о его величине — он лишь сообщает, что данные плохо вяжутся с «эффекта нет».
Основные тесты в scipy.stats
| Тест | Функция | Когда |
| t-тест (две группы) | ttest_ind | Сравнить средние двух выборок |
| t-тест (парный) | ttest_rel | До/после на одних объектах |
| Хи-квадрат | chi2_contingency | Связь категориальных признаков |
| Корреляция | pearsonr | Линейная связь двух величин |
| Нормальность | shapiro | Похоже ли на нормальное распределение |
from scipy.stats import ttest_ind
control = [4.1, 3.9, 4.0, 4.2, 3.8]
treatment = [4.5, 4.7, 4.6, 4.4, 4.8]
stat, pvalue = ttest_ind(control, treatment)
print(pvalue) # ~6e-05 -> различие значимо
Корреляцию посчитаем руками
Коэффициент корреляции Пирсона измеряет линейную связь от −1 до +1. Реализуем на stdlib и проверим на «идеальной» прямой:
import statistics
def pearson(x, y):
mx, my = statistics.mean(x), statistics.mean(y)
cov = sum((a - mx) * (b - my) for a, b in zip(x, y))
sx = sum((a - mx) ** 2 for a in x) ** 0.5
sy = sum((b - my) ** 2 for b in y) ** 0.5
return cov / (sx * sy)
x = [1, 2, 3, 4, 5]
y = [2, 4, 6, 8, 10] # ровно y = 2x -> корреляция +1
z = [10, 8, 6, 4, 2] # ровно обратная -> -1
print("corr(x, y) =", round(pearson(x, y), 4))
print("corr(x, z) =", round(pearson(x, z), 4))
Вывод:
corr(x, y) = 1.0 corr(x, z) = -1.0
Как работает под капотом
t-тест считает статистику t = (разница средних) / (оценка разброса): чем больше разница относительно шума, тем больше t. Затем по распределению Стьюдента (которое тоже есть в scipy.stats как t) вычисляется, насколько вероятно получить такое t случайно — это и есть p-value. Распределение Стьюдента, а не нормальное, потому что при малых выборках мы не знаем истинный разброс и оцениваем его по тем же данным — это добавляет неопределённости, которую «толстые хвосты» Стьюдента учитывают.
Частые ошибки
- p-value = вероятность того, что H0 верна. Нет! Это P(данные | H0), а не P(H0 | данные). Распространённейшее заблуждение.
- Не значимо = эффекта нет. Высокий p-value лишь означает «данных мало, чтобы что-то утверждать», а не доказывает отсутствие эффекта.
- Множественные сравнения. Прогнав 20 тестов, по чистой случайности получите один «значимый» при α=0.05 — нужна поправка (Бонферрони и т.п.).
- Путать корреляцию с причинностью. corr=0.9 не значит, что одно вызывает другое.
Итог
- Тест проверяет согласие данных с нулевой гипотезой через p-value.
- p-value — это P(данные | H0), а НЕ вероятность гипотезы.
- В SciPy:
ttest_ind,chi2_contingency,pearsonr,shapiroи др. - Корреляцию легко посчитать руками; тесты лучше доверять SciPy.