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

Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.

Зачем это знать

Сравнение средних — базовая задача аналитика. «CR A/B-теста 10.2% vs 10.5% — это реально разница или случайность?». «Средний чек после релиза 1500 → 1520, значим ли lift?». Без статистического теста ответ гадание.

На собесе спросят: «какой тест для сравнения средних?». Junior скажет t-test. Middle добавит Welch's для неравных variance. Senior обсудит — когда t-test не работает и что делать (Mann-Whitney, bootstrap).

В статье:

  • T-test: Student's vs Welch's
  • Assumptions и когда они нарушаются
  • Mann-Whitney U при non-normal
  • Bootstrap как универсал
  • Python код для всех

Тесты для сравнения средних

Independent samples t-test (Student's)

Сравнение двух independent групп.

Assumptions:

  • Независимость наблюдений
  • Approximately normal distribution (для small N)
  • Equal variance (Student's) или не equal (Welch's)
from scipy.stats import ttest_ind

# Student's (assumes equal variance)
t_stat, p_value = ttest_ind(group_a, group_b, equal_var=True)

# Welch's (не предполагает equal variance) — recommended
t_stat, p_value = ttest_ind(group_a, group_b, equal_var=False)

Paired t-test

Для связанных выборок (до/после, один и тот же user).

from scipy.stats import ttest_rel
t_stat, p_value = ttest_rel(before, after)

Когда t-test не работает

Non-normal distribution

Для heavy-tailed (чек, время) t-test менее точен. Но при N > 100 CLT помогает.

Extreme outliers

Один outlier может сильно повлиять.

Малые выборки non-normal

N < 30 + non-normal → t-test ненадёжен.

Альтернативы

Mann-Whitney U test

Non-parametric. Сравнение медиан (точнее — распределений).

from scipy.stats import mannwhitneyu
stat, p_value = mannwhitneyu(group_a, group_b)

Работает на любых distributions. Теряет power на truly normal.

Bootstrap

Универсальный. Обработка любых метрик.

import numpy as np

def bootstrap_test(a, b, n_iter=10000):
    obs_diff = np.mean(a) - np.mean(b)
    combined = np.concatenate([a, b])
    diffs = []
    for _ in range(n_iter):
        sample = np.random.choice(combined, len(combined), replace=True)
        diff = np.mean(sample[:len(a)]) - np.mean(sample[len(a):])
        diffs.append(diff)
    p_value = np.mean(np.abs(diffs) >= np.abs(obs_diff))
    return p_value

Медленнее, но гибче.

Permutation test

Похож на bootstrap, но перемешивает метки:

def permutation_test(a, b, n_iter=10000):
    obs_diff = np.mean(a) - np.mean(b)
    combined = np.concatenate([a, b])
    count = 0
    for _ in range(n_iter):
        np.random.shuffle(combined)
        diff = np.mean(combined[:len(a)]) - np.mean(combined[len(a):])
        if abs(diff) >= abs(obs_diff):
            count += 1
    return count / n_iter

Как выбрать тест

Normal + equal variance  → Student's t-test
Normal + different variance → Welch's t-test (default recommendation)
Non-normal, large N       → t-test ок (CLT)
Non-normal, small N       → Mann-Whitney U
Сложная metric / custom   → Bootstrap
Paired data               → Paired t-test / Wilcoxon

Практика: A/B-тест

import numpy as np
from scipy.stats import ttest_ind, mannwhitneyu

# control group
control = np.random.lognormal(3, 1, 1000)  # heavy-tailed

# test group (с эффектом)
test = np.random.lognormal(3.05, 1, 1000)

# Welch's
t, p = ttest_ind(control, test, equal_var=False)
print(f't-test p = {p:.4f}')

# Mann-Whitney
u, p = mannwhitneyu(control, test)
print(f'Mann-Whitney p = {p:.4f}')

Проверка assumptions

Нормальность

from scipy.stats import shapiro, normaltest

stat, p = shapiro(data)  # для small samples
stat, p = normaltest(data)  # для large

Если p < 0.05 → не normal. Но на больших N эти тесты почти всегда отвергают.

Визуально

import matplotlib.pyplot as plt
from scipy.stats import probplot

probplot(data, plot=plt)  # Q-Q plot
plt.show()

Prямая линия → нормально.

Equal variance

from scipy.stats import levene
stat, p = levene(group_a, group_b)
# p < 0.05 → разные variance → Welch's

В A/B-тестах

Default рекомендация:

  • Конверсия (proportion): z-test для пропорций или chi-square
  • Continuous с нормальным: Welch's t-test
  • Continuous с heavy tail (чек): Mann-Whitney или bootstrap
  • Составные метрики: bootstrap

На собесе

«Какой тест для сравнения средних?» Welch's t-test (не предполагает equal variance).

«Когда Mann-Whitney?» Non-normal distribution с малой выборкой или heavy tails.

«Bootstrap vs t-test?» Bootstrap универсальнее, но медленнее. T-test — если assumptions выполнены.

«Paired vs independent?» Paired — одни и те же subjects (до/после). Independent — разные.

Частые ошибки

1. Student's вместо Welch's

В A/B variances часто разные. Default — Welch's.

2. t-test на small N с outliers

Ненадёжно. Bootstrap или Mann-Whitney.

3. p < 0.05 → значимо навсегда

Multiple testing! Поправка Bonferroni для множественных сравнений.

4. Игнорировать effect size

Значимость ≠ практическая важность. Смотрите и p-value, и effect size.

Связанные темы

FAQ

p = 0.049 — significant?

Technically да (при α = 0.05). Но очень близко. Проверьте mbet повторить.

Nonparametric always safer?

Не всегда. На truly normal данных t-test power выше.

В Python или R?

Scipy / statsmodels в Python. В R — более широкий набор.

One-sided или two-sided?

Default — two-sided. One-sided только если теоретически excluded одна сторона.


Тренируйте статистику — откройте тренажёр с 1500+ вопросами для собесов.