Как использовать p-value при проверке гипотезы?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как использовать p-value при проверке гипотезы?
p-value — это вероятность получить наблюдаемый результат (или более экстремальный) в предположении, что нулевая гипотеза верна. Это главный инструмент статистического тестирования.
Концепция p-value
Пример: A/B тест
Предположим, мы тестируем новый дизайн кнопки:
- Контрольная группа (старый дизайн): 100 кликов из 1000 = 10%
- Тестовая группа (новый дизайн): 120 кликов из 1000 = 12%
Вопрос: Это реальное улучшение или просто случайность?
Нулевая гипотеза (H₀): Обе кнопки дают одинаковый CTR (Click-Through Rate)
Альтернативная гипотеза (H₁): Новая кнопка лучше
p-value отвечает: "Какова вероятность увидеть такую разницу, если на самом деле кнопки одинаковые?"
Интерпретация p-value
p-value = 0.05 → 5% вероятность, что результат случаен
p-value = 0.01 → 1% вероятность, что результат случаен
p-value = 0.5 → 50% вероятность, что результат случаен (очень слабо!)
Стандартный уровень значимости (alpha = 0.05):
Если p-value < 0.05 → Отклоняем H₀ (результат значим)
Если p-value ≥ 0.05 → НЕ отклоняем H₀ (результат не значим)
Практический пример: A/B тест
import scipy.stats as stats
import numpy as np
# Данные A/B теста
control_clicks = 100
control_total = 1000
treatment_clicks = 120
treatment_total = 1000
# Двухвыборочный тест пропорций (Chi-square test)
from scipy.stats import chi2_contingency
# Таблица сопряжённости
contingency_table = np.array([
[control_clicks, control_total - control_clicks], # контроль
[treatment_clicks, treatment_total - treatment_clicks] # лечение
])
chi2, p_value, dof, expected = chi2_contingency(contingency_table)
print(f"Chi-square статистика: {chi2:.4f}")
print(f"p-value: {p_value:.4f}")
print(f"Степени свободы: {dof}")
# Интерпретация
alpha = 0.05
if p_value < alpha:
print(f"Результат значим (p < {alpha}). Отклоняем нулевую гипотезу.")
print("Новый дизайн статистически значимо лучше!")
else:
print(f"Результат НЕ значим (p ≥ {alpha}). НЕ отклоняем нулевую гипотезу.")
print("Нет достаточных доказательств, что новый дизайн лучше.")
t-тест (для непрерывных данных)
from scipy.stats import ttest_ind
# Данные: время загрузки страницы (мс)
old_page = np.array([1200, 1300, 1100, 1400, 1250])
new_page = np.array([1000, 950, 1050, 980, 1100])
# Двухвыборочный t-тест
t_statistic, p_value = ttest_ind(old_page, new_page)
print(f"t-статистика: {t_statistic:.4f}")
print(f"p-value: {p_value:.4f}")
alpha = 0.05
if p_value < alpha:
print(f"Результат значим (p = {p_value:.4f} < {alpha})")
print(f"Новая страница загружается значимо быстрее!")
else:
print(f"Результат НЕ значим (p = {p_value:.4f} ≥ {alpha})")
print("Разница может быть случайной")
Ошибки при интерпретации p-value
Ошибка 1: Неправильная интерпретация
❌ НЕПРАВИЛЬНО: p-value = 0.05 означает, что H₀ верна с 95% вероятностью
✅ ПРАВИЛЬНО: p-value = 0.05 означает, что если H₀ верна, то вероятность
получить такой результат = 5%
Ошибка 2: p-hacking (манипуляция результатами)
# ❌ ПЛОХО: делаем много тестов, выбираем те с малым p-value
for i in range(100):
result = test_hypothesis()
if result.p_value < 0.05:
print(f"Нашли значимый результат!")
break
# Если сделаешь 100 тестов, примерно 5 будут с p < 0.05 просто по случайности!
# Это False Positive (Type I error)
# ✅ ПРАВИЛЬНО: заранее определи тест и alpha
alpha = 0.05
p_value = test_hypothesis()
if p_value < alpha:
print("Результат значим")
Ошибка 3: Множественные сравнения
# Если делаешь несколько тестов, нужно применить коррекцию
from scipy.stats import ttest_ind
# Тестируем 10 гипотез
p_values = []
for i in range(10):
_, p_val = ttest_ind(group1[i], group2[i])
p_values.append(p_val)
# Коррекция Bonferroni
alpha = 0.05
num_tests = len(p_values)
adjusted_alpha = alpha / num_tests # 0.05 / 10 = 0.005
for i, p_val in enumerate(p_values):
if p_val < adjusted_alpha:
print(f"Тест {i} значим с коррекцией")
else:
print(f"Тест {i} НЕ значим с коррекцией")
# Альтернатива: метод Benjamini-Hochberg (FDR)
from scipy.stats import rankdata
ranks = rankdata(p_values)
fdr_threshold = 0.05
m = len(p_values)
for rank, p_val in sorted(zip(ranks, p_values), reverse=True):
threshold = fdr_threshold * rank / m
if p_val <= threshold:
print(f"Тест значим (FDR контролируется на уровне {fdr_threshold})")
break
Реальный пример: Email маркетинг
import numpy as np
from scipy.stats import chi2_contingency
# А/В тест: две версии письма
# Вариант А (контроль)
group_a_sent = 10000
group_a_opened = 2500 # 25% open rate
# Вариант B (новая тема письма)
group_b_sent = 10000
group_b_opened = 2700 # 27% open rate
print(f"Вариант A: {group_a_opened / group_a_sent:.1%} open rate")
print(f"Вариант B: {group_b_opened / group_b_sent:.1%} open rate")
print(f"Разница: {(group_b_opened / group_b_sent) - (group_a_opened / group_a_sent):.1%}")
# Тест
contingency = np.array([
[group_a_opened, group_a_sent - group_a_opened],
[group_b_opened, group_b_sent - group_b_opened]
])
chi2, p_value, dof, expected = chi2_contingency(contingency)
print(f"\nChi-square: {chi2:.4f}")
print(f"p-value: {p_value:.6f}")
alpha = 0.05
if p_value < alpha:
print(f"\nРезультат ЗНАЧИМ (p = {p_value:.6f} < {alpha})")
print("Новая тема письма значимо улучшает open rate.")
print("Рекомендация: использовать вариант B для всех писем.")
else:
print(f"\nРезультат НЕ ЗНАЧИМ (p = {p_value:.6f} ≥ {alpha})")
print("Разница может быть случайной.")
print("Рекомендация: нужен больший размер выборки.")
Доверительные интервалы вместо p-value
from scipy import stats
# Вычисляем 95% доверительный интервал
group_a_mean = 50
group_b_mean = 55
group_a_std = 10
group_b_std = 10
n = 100
# Стандартная ошибка разницы средних
se_diff = np.sqrt((group_a_std**2 + group_b_std**2) / n)
# 95% CI для разницы
z_critical = 1.96 # для 95%
ci_lower = (group_b_mean - group_a_mean) - z_critical * se_diff
ci_upper = (group_b_mean - group_a_mean) + z_critical * se_diff
print(f"Разница средних: {group_b_mean - group_a_mean}")
print(f"95% доверительный интервал: [{ci_lower:.2f}, {ci_upper:.2f}]")
if ci_lower > 0:
print("Интервал НЕ включает ноль → результат значим на уровне 0.05")
else:
print("Интервал включает ноль → результат НЕ значим на уровне 0.05")
Размер выборки и p-value
# С большой выборкой даже маленькие различия становятся значимыми
from scipy.stats import ttest_ind
# Маленькая выборка
small_a = np.random.normal(100, 10, n=30)
small_b = np.random.normal(101, 10, n=30)
t, p = ttest_ind(small_a, small_b)
print(f"n=30: p-value = {p:.4f}") # часто НЕ значим
# Большая выборка
large_a = np.random.normal(100, 10, n=10000)
large_b = np.random.normal(101, 10, n=10000)
t, p = ttest_ind(large_a, large_b)
print(f"n=10000: p-value = {p:.6f}") # часто значим
# Вывод: даже маленькие эффекты значимы с большой выборкой!
# Поэтому смотри на практическую значимость, не только на p-value
Практические рекомендации
1. Установи alpha заранее
alpha = 0.05 # стандартный уровень
alpha = 0.01 # более строгий (для критичных решений)
2. Выбери тест заранее
# t-тест для непрерывных данных
# Chi-square для категориальных
# Mann-Whitney U для ненормальных распределений
3. Вычисли размер выборки
# Используй power analysis, чтобы определить нужный n
from statsmodels.stats.power import tt_ind_solve_power
# Нам нужна выборка, чтобы обнаружить эффект с power = 0.8
required_n = tt_ind_solve_power(
effect_size=0.5, # небольшой эффект
alpha=0.05,
power=0.8,
alternative='two-sided'
)
print(f"Требуется n = {required_n:.0f} человек в каждой группе")
4. Контролируй Type I (False Positive) и Type II (False Negative) ошибки
5. Смотри на доверительные интервалы, не только p-value
Вывод
p-value — это инструмент, а не ответ. Используй его правильно:
- p-value < alpha → результат статистически значим
- Но это НЕ означает, что эффект практически значим
- Смотри на величину эффекта (effect size)
- Контролируй множественные сравнения
- Определяй размер выборки заранее
- Используй доверительные интервалы дополнительно