Какие знаешь условия использования Т-теста?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
T-тест: условия применения и использование в аналитике
T-тест — это статистический тест для сравнения средних значений между двумя группами. Это один из самых важных инструментов в A/B тестировании. Рассмотрю условия применения и частые ошибки.
1. Когда использовать T-тест
T-тест используется для:
- Сравнение средних значений между двумя группами (тест vs контроль)
- Проверка гипотезы о том, что две группы имеют разные средние
- Определение статистической значимости различий
Пример: изменили цену плана с $99 на $89. Различается ли средний LTV пользователей в обеих группах?
2. Условия применения T-теста (Critical!)
Условие 1: Нормальность распределения (Normality)
Суть: данные в каждой группе должны быть близки к нормальному распределению (bell curve).
Проверка:
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
# Данные: средний время использования в контрольной группе (минуты)
control_data = np.array([2, 3, 5, 4, 6, 5, 7, 4, 3, 8, 5, 4, 6, 7, 5])
# Тест Шапиро-Уилка (Shapiro-Wilk test)
statistic, p_value = stats.shapiro(control_data)
if p_value > 0.05:
print(f"Data is normally distributed (p={p_value:.3f})")
else:
print(f"Data is NOT normally distributed (p={p_value:.3f})")
# Визуализация
plt.hist(control_data, bins=10, edgecolor='black')
plt.axvline(np.mean(control_data), color='red', linestyle='--', label='Mean')
plt.legend()
plt.show()
Что делать, если не нормально?
- Применить логарифмическое преобразование (log transform)
- Использовать Mann-Whitney U тест (non-parametric альтернатива)
- Увеличить размер выборки (при n > 30 центральная теорема лимита спасает нас)
Условие 2: Независимость наблюдений (Independence)
Суть: каждое наблюдение должно быть независимым от остальных.
Примеры нарушения:
- Один пользователь в обеих тест и контроль группах (они зависимы!)
- Один IP адрес в нескольких пользователях (bot трафик)
- Временная корреляция (если вчера было много регистраций, сегодня тоже будет)
Проверка:
# Правильное распределение: каждый пользователь только в одной группе
test_group = df[df['group'] == 'test']['user_id'].unique()
control_group = df[df['group'] == 'control']['user_id'].unique()
intersection = set(test_group) & set(control_group)
if len(intersection) == 0:
print("Groups are independent ✓")
else:
print(f"WARNING: {len(intersection)} users in both groups!")
Условие 3: Однородность дисперсии (Homogeneity of Variance)
Суть: дисперсия (вариативность) в обеих группах должна быть примерно одинаковой.
Пример нарушения:
- Контрольная группа: LTV = 50 ± 5 (мало вариативности)
- Тест группа: LTV = 55 ± 50 (много вариативности) → Различные дисперсии, t-тест неподходящ
Проверка (Левина тест):
from scipy.stats import levene
control_ltv = [45, 48, 52, 50, 49, 51, 48, 52]
test_ltv = [52, 70, 35, 55, 80, 50, 40, 60] # Большая вариативность
statistic, p_value = levene(control_ltv, test_ltv)
if p_value > 0.05:
print(f"Variances are homogeneous (p={p_value:.3f})")
test_type = "T-test (standard)"
else:
print(f"Variances are NOT homogeneous (p={p_value:.3f})")
test_type = "T-test (Welch, не предполагает равные дисперсии)"
print(f"Use: {test_type}")
Что делать, если дисперсии не одинаковые?
- Использовать Welch's t-test (не предполагает равные дисперсии)
- Это вариант обычного t-теста, который более robust
Условие 4: Размер выборки (Sample Size)
Минимальный размер:
- Если данные нормальны: >= 20 наблюдений на группу
- Если данные не совсем нормальны: >= 30 наблюдений на группу
- Золотой стандарт: >= 50 на группу
Пример ошибки:
# НЕПРАВИЛЬНО: слишком мало данных
control = [100, 120, 110] # 3 наблюдения
test = [115, 125, 130] # 3 наблюдения
# t-тест неподходящ! результат может быть случайностью
# ПРАВИЛЬНО: достаточно данных
control = [100, 102, 98, 105, 103, ..., 101] * 30 # 30+ наблюдений
test = [115, 118, 112, 120, 116, ..., 114] * 30 # 30+ наблюдений
3. Виды T-тестов
Вид 1: Independent Samples T-test (Независимые выборки)
Когда: сравнение двух РАЗНЫХ групп пользователей
Пример: контроль (стара цена) vs тест (новая цена)
from scipy.stats import ttest_ind
control_revenue = [100, 110, 95, 105, 98, 102, 108, 99]
test_revenue = [120, 125, 115, 128, 122, 130, 118, 125]
# Обычный t-тест
t_stat, p_value = ttest_ind(control_revenue, test_revenue)
print(f"t-statistic: {t_stat:.3f}")
print(f"p-value: {p_value:.4f}")
if p_value < 0.05:
print("Разница статистически значима (p < 0.05) ✓")
else:
print("Разница НЕ значима, может быть случайностью (p >= 0.05)")
# Welch's t-test (если дисперсии разные)
t_stat_welch, p_value_welch = ttest_ind(control_revenue, test_revenue, equal_var=False)
Вид 2: Paired Samples T-test (Зависимые выборки)
Когда: сравнение ОДНИХ И ТЕХ ЖЕ пользователей ДО и ПОСЛЕ
Пример: LTV пользователя до обновления UI vs после обновления
from scipy.stats import ttest_rel
# Один пользователь, до и после
user_ltv_before = [100, 150, 80, 120, 110, 95, 130]
user_ltv_after = [120, 170, 100, 140, 135, 115, 150]
# Paired t-test
t_stat, p_value = ttest_rel(user_ltv_before, user_ltv_after)
print(f"p-value: {p_value:.4f}")
if p_value < 0.05:
print("LTV увеличился после обновления (статистически значимо)")
Вид 3: One-sample T-test (Одна выборка)
Когда: сравнение группы с известным значением
Пример: средний LTV пользователя = 100. Отличается ли новая когорта пользователей?
from scipy.stats import ttest_1samp
new_cohort_ltv = [105, 115, 95, 110, 108, 120, 100, 112]
known_mean = 100
t_stat, p_value = ttest_1samp(new_cohort_ltv, known_mean)
if p_value < 0.05:
print(f"Новая когорта отличается от ожидаемого LTV={known_mean}")
4. Интерпретация результатов
p-value < 0.05: разница статистически значима p-value >= 0.05: разница может быть случайностью
# Пример результата
t_stat = 2.35
p_value = 0.032
df = 28 # degrees of freedom
print(f"""\nРезультаты t-теста:
t-статистика: {t_stat:.3f}
P-value: {p_value:.4f}
Степени свободы: {df}
Вывод: p-value = {p_value:.4f} < 0.05
→ Есть статистически значимая разница между группами
→ Вероятность, что это случайность: {p_value*100:.2f}%
""")
5. Частые ошибки
Ошибка 1: p-hacking (многократное тестирование)
# НЕПРАВИЛЬНО: смотреть p-value каждый день
for day in range(1, 30):
subset = data[data['day'] <= day]
t_stat, p_value = ttest_ind(subset['control'], subset['test'])
if p_value < 0.05:
print(f"Значимо на день {day}!")
break # ← BIAS! Вероятность false positive растёт
# ПРАВИЛЬНО: запланировать тест заранее
# Рассчитать нужный sample size
# Дождаться когда достигли нужный n
# Потом один раз провести тест
Ошибка 2: использование t-теста на пропорции
# НЕПРАВИЛЬНО: t-тест на конверсию (0 или 1)
test_conversions = [0, 1, 1, 0, 1, 1, 0, ...]
control_conversions = [0, 0, 1, 0, 0, 1, ...]
t_stat, p_value = ttest_ind(test_conversions, control_conversions) # ← ОШИБКА!
# ПРАВИЛЬНО: использовать Chi-square тест или Z-тест
from scipy.stats import chi2_contingency
contingency_table = [
[test_conversions.count(1), test_conversions.count(0)],
[control_conversions.count(1), control_conversions.count(0)]
]
chi2, p_value, dof, expected = chi2_contingency(contingency_table)
Ошибка 3: не проверить предположения
# ПРАВИЛЬНЫЙ ПРОЦЕСС:
# 1. Проверить нормальность
stat, p = shapiro(data)
if p < 0.05:
print("Данные не нормальны, может нужен Mann-Whitney U тест")
# 2. Проверить однородность дисперсии
stat, p = levene(group1, group2)
if p < 0.05:
# Использовать Welch's t-test
t_stat, p_value = ttest_ind(group1, group2, equal_var=False)
else:
# Обычный t-тест
t_stat, p_value = ttest_ind(group1, group2)
# 3. Интерпретировать результат
6. Практический пример: A/B тест цены
import numpy as np
from scipy import stats
# Данные: средний LTV при разных ценах
control_price = 99
control_ltv = np.array([95, 105, 100, 98, 102, 101, 99, 103, 97, 100] * 10)
test_price = 89
test_ltv = np.array([108, 112, 110, 105, 115, 109, 107, 111, 106, 113] * 10)
print(f"Контрольная группа (цена ${control_price}):")
print(f" Среднее LTV: ${control_ltv.mean():.2f}")
print(f" Стд. отклонение: ${control_ltv.std():.2f}")
print(f" n = {len(control_ltv)}")
print(f"\nТестовая группа (цена ${test_price}):")
print(f" Среднее LTV: ${test_ltv.mean():.2f}")
print(f" Стд. отклонение: ${test_ltv.std():.2f}")
print(f" n = {len(test_ltv)}")
print(f"\nРазница в средних: ${(test_ltv.mean() - control_ltv.mean()):.2f}")
# Проверить предположения
print("\n=== Проверка предположений ===")
# Нормальность
stat_ctrl, p_ctrl = stats.shapiro(control_ltv)
stat_test, p_test = stats.shapiro(test_ltv)
print(f"\nНормальность (Shapiro-Wilk):")
print(f" Контроль: p={p_ctrl:.4f} {'✓' if p_ctrl > 0.05 else '✗'}")
print(f" Тест: p={p_test:.4f} {'✓' if p_test > 0.05 else '✗'}")
# Однородность дисперсии
stat_levene, p_levene = stats.levene(control_ltv, test_ltv)
print(f"\nОднородность дисперсии (Levene test):")
print(f" p={p_levene:.4f} {'✓' (equal variance)' if p_levene > 0.05 else '✗ (unequal)'} ")
# T-тест
t_stat, p_value = stats.ttest_ind(control_ltv, test_ltv, equal_var=(p_levene > 0.05))
print(f"\n=== T-тест результаты ===")
print(f"t-статистика: {t_stat:.4f}")
print(f"p-value: {p_value:.6f}")
print(f"\nВыводы:")
if p_value < 0.05:
direction = "выше" if test_ltv.mean() > control_ltv.mean() else "ниже"
lift = abs((test_ltv.mean() - control_ltv.mean()) / control_ltv.mean() * 100)
print(f"✓ LTV при цене ${test_price} статистически значимо {direction}")
print(f" Lift: +{lift:.1f}%")
print(f" Вероятность ошибки (false positive): {p_value*100:.2f}%")
else:
print(f"✗ Разница не статистически значима")
print(f" Нельзя сделать вывод на основе текущих данных")
print(f" Может потребоваться больше пользователей или больше времени")
Заключение
T-тест — мощный инструмент для Product Analyst'а, но его нужно использовать правильно. Ключные моменты:
- Проверить предположения перед использованием
- Выбрать правильный тип (independent, paired, one-sample)
- Быть осторожным с p-hacking (не смотреть результаты каждый день)
- Правильно интерпретировать p-value (это не вероятность, что гипотеза верна)
- Планировать тест заранее (sample size, significance level)
Когда есть сомнения, лучше проконсультироваться со статистиком или использовать Bayesian методы, которые более интуитивны.