Как проверить значимо ли отличается среднее
discount по убыванию. Поле discount может быть NULL (скидки нет). Чтобы товары без скидки всегда оказывались внизу независимо от настроек СУБД, какой вариант сортировки выбрать?Зачем это знать
Сравнение средних — базовая задача аналитика. «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.
Связанные темы
- T-test vs z-test
- T-test vs chi-square
- Mann-Whitney test
- Bootstrap простыми словами
- P-value простыми словами
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 одна сторона.