← Назад к вопросам

Как доказать отсутствие изменений?

1.7 Middle🔥 181 комментариев
#A/B-тестирование#Статистика и теория вероятностей#Статистические критерии и тесты

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как доказать отсутствие изменений?

Проблема

Обычный статистический тест показывает значимость (есть ли эффект). Но как доказать что эффекта НЕТ?

Просто сказать «p-value > 0.05» недостаточно! Это может быть из-за недостаточно большой выборки.

Решение: Эквивалентность (Equivalence Testing)

1. Определяем зону эквивалентности

Что считать «практически отсутствием изменений»?

Пример: Конверсия

  • Старая версия: 5%
  • Новая версия: 5.1%
  • Разница: 0.1%

Вопрос: является ли 0.1% практически значимым?

Ответ: Нет, если зона эквивалентности = ±0.5%

baseline = 5.0  # %
margin_of_equivalence = 0.5  # %

lower_bound = baseline - margin_of_equivalence  # 4.5%
upper_bound = baseline + margin_of_equivalence  # 5.5%

# Если новая версия в [4.5%, 5.5%] → эквивалентна

2. Тест эквивалентности (TOST)

Two One-Sided Tests:

  1. Проверяем H1: новое >= нижней границы
  2. Проверяем H2: новое <= верхней границы

Если ОБЕ гипотезы верны → эффект эквивалентен нулю

from scipy import stats
import numpy as np

# Данные
control = np.array([50, 51, 49, 52, 50, 48, 51, 49, 50, 52])  # старая версия
treatment = np.array([50, 51, 50, 51, 49, 50, 51, 50, 49, 51])  # новая версия

# Параметры
margin = 2.0  # зона эквивалентности = ±2
alpha = 0.05

mean_diff = treatment.mean() - control.mean()
se_diff = np.sqrt(control.var()/len(control) + treatment.var()/len(treatment))

print(f"Разница средних: {mean_diff}")
print(f"Стандартная ошибка: {se_diff}")

# Тест 1: mean_diff > -margin
t1 = (mean_diff - (-margin)) / se_diff
p1 = 1 - stats.t.cdf(t1, len(control) + len(treatment) - 2)

# Тест 2: mean_diff < margin
t2 = (margin - mean_diff) / se_diff
p2 = 1 - stats.t.cdf(t2, len(control) + len(treatment) - 2)

print(f"\nТест 1 (выше нижней границы): p = {p1}")
print(f"Тест 2 (ниже верхней границы): p = {p2}")

if p1 < alpha and p2 < alpha:
    print("\n✓ Эффект эквивалентен нулю (нет значимых изменений)")
else:
    print("\n✗ Не можем утверждать отсутствие эффекта")

3. Практический пример: A/B тест

Сценарий:

  • Контроль: 4500 конверсий из 100,000 → 4.5%
  • Лечение: 4520 конверсий из 100,000 → 4.52%
  • Разница: +0.02% (практически ничто)

Вопрос: можем ли доказать что нет эффекта?

from scipy.stats import binom

control_conversions = 4500
control_total = 100000
treatment_conversions = 4520
treatment_total = 100000

control_rate = control_conversions / control_total
treatment_rate = treatment_conversions / treatment_total

print(f"Контроль: {control_rate*100:.2f}%")
print(f"Лечение: {treatment_rate*100:.2f}%")

# Зона эквивалентности: ±0.5% (практически значимый эффект)
margin = 0.005

if abs(treatment_rate - control_rate) < margin:
    print(f"✓ Различие меньше {margin*100}% → эффект отсутствует")
else:
    print(f"✗ Различие {abs(treatment_rate - control_rate)*100:.2f}% >= {margin*100}%")

4. Доверительные интервалы

Классический подход: посмотри CI

from scipy import stats

control = [50, 51, 49, 52, 50, 48, 51, 49, 50, 52]
treatment = [50, 51, 50, 51, 49, 50, 51, 50, 49, 51]

mean_diff = np.mean(treatment) - np.mean(control)
se = np.sqrt(np.var(control)/len(control) + np.var(treatment)/len(treatment))
ci = stats.t.interval(0.95, len(control) + len(treatment) - 2, mean_diff, se)

print(f"95% CI разницы: [{ci[0]:.2f}, {ci[1]:.2f}]")

# Если CI полностью в [-margin, margin] → эффект отсутствует
margin = 2.0
if ci[0] > -margin and ci[1] < margin:
    print("✓ Вся доверительная область эквивалентна нулю")
else:
    print("✗ Не можем утверждать отсутствие эффекта")

5. Effect Size

Даже если p > 0.05, проверь практическую значимость:

import numpy as np
from scipy import stats

control = np.random.normal(100, 15, 1000)
treatment = np.random.normal(100.5, 15, 1000)

# t-test
t_stat, p_value = stats.ttest_ind(control, treatment)
print(f"p-value: {p_value:.4f}")

# Cohen's d
cohens_d = (treatment.mean() - control.mean()) / np.sqrt((control.std()**2 + treatment.std()**2) / 2)
print(f"Cohen's d: {cohens_d:.4f}")

# Интерпретация
if abs(cohens_d) < 0.2:
    print("✓ Практически нулевой эффект (d < 0.2)")
elif abs(cohens_d) < 0.5:
    print("⚠ Малый эффект (0.2 <= d < 0.5)")
else:
    print("✗ Значимый эффект")

6. Размер выборки и мощность

Проблема: малая выборка → неправильное p > 0.05

from scipy.stats import ttest_ind_from_stats
import numpy as np

# Нужна выборка достаточного размера
# Для обнаружения малого эффекта (d=0.2): n >= 320
# Для обнаружения среднего эффекта (d=0.5): n >= 50

required_n = 320  # для малого эффекта
if len(control) < required_n:
    print(f"⚠ Выборка {len(control)} < требуемой {required_n}")
    print("Не можем доказать отсутствие эффекта!")
else:
    print("✓ Выборка достаточна для вывода")

7. Практические шаги

  1. Определи зону эквивалентности

    • Какой эффект считается практически значимым?
    • Обычно: 10-20% от baseline
  2. Проверь размер выборки

    • Хватает ли данных для надёжного вывода?
  3. Используй TOST

    • Проверь обе стороны доверительного интервала
  4. Посмотри Effect Size

    • Даже если есть статистическая значимость
  5. Задокументируй результат

    • «Эффект эквивалентен нулю в пределах ±margin»

8. Типичные ошибки

НЕПРАВИЛЬНО:

  • «p > 0.05, значит нет эффекта!» (недостаточно)

ПРАВИЛЬНО:

  • «95% CI: [-0.5%, +0.4%], что меньше margin ±1% → нет эффекта»
  • «Cohen's d = 0.05 (практически нулевой) и p = 0.45»

Заключение

Для доказательства отсутствия эффекта:

  1. Используй тест эквивалентности (TOST)
  2. Определи зону практической значимости
  3. Проверь размер выборки
  4. Смотри effect size, не только p-value
  5. Используй доверительные интервалы
Как доказать отсутствие изменений? | PrepBro