Условие
Распределение revenue per user сильно скошено: медиана 200₽, среднее 800₽, max 50 000₽. Какое распределение и что это означает для A/B-теста?
Решение
Log-normal
Если log(X) ~ Normal(μ, σ²), то X ~ LogNormal(μ, σ²). Типично для:
- Доход / Revenue.
- Время сессии.
- LTV.
- Размер компаний / городов.
Параметры
E[X] = exp(μ + σ²/2)
median = exp(μ)
mode = exp(μ - σ²)
Var = (exp(σ²) - 1) × exp(2μ + σ²)
Среднее > медиана (правый хвост).
Следствия для A/B
- t-test на raw X — теоретически нарушает нормальность. На больших n CLT спасает, но на маленьких — нет.
- Высокая дисперсия → нужны огромные выборки для детектирования lift.
- Whales эффект: один юзер с purchase 50k₽ может сдвинуть среднее группы.
Что делать
- Логарифмировать:
t-test на log(X). Сравниваем медианы (геом. среднее), а не средние.
from scipy import stats
t, p = stats.ttest_ind(np.log(a + 1), np.log(b + 1))Минус: бизнес-метрика "среднее revenue", не "геом. среднее".
- Winsorize/clip: обрезать верхние 1% выбросов.
upper = np.percentile(data, 99)
data_clip = np.clip(data, 0, upper)-
Bootstrap для CI и теста.
-
Mann-Whitney: проверяет «P(A>B) ≠ 0.5», устойчив к хвостам.
-
Stratification по сегменту: разделить на «whales» и «mass», тестировать отдельно.
-
CUPED: использовать pre-period revenue как ковариату.
Sample size для long-tailed
При CV = σ/mean = 2 (типично для revenue):
n ≈ 16 × CV² / lift² = 16 × 4 / 0.05² = ~25 000 на группу
Чтобы детектить 5% lift с 80% power.
Подводные камни
- Medians в A/B-тесте — устойчивее, но «средняя выручка» — это бизнес-метрика. Не подменять.
- Whales в одной группе случайно — могут «выиграть» тест шумом. Stratify или clip.
log(0)undefined.log(x+1)илиlog1p(x). Это меняет интерпретацию.- На log-scale 5% lift в среднем revenue ≠ 5% lift в log-mean.
- CUPED уменьшает variance в 2-5× за счёт корреляции с pre-period.
Эталонный ответ
Revenue обычно log-normal. Для A/B: log-transform, winsorize, bootstrap, CUPED или stratify по whales. Big variance → need huge samples; one whale может сдвинуть среднее.