Условие
Метрика — медиана LTV. Параметрический CI для медианы — сложно. Использовать bootstrap. Как?
Решение
Идея bootstrap
Семплируем из данных с замещением, считаем статистику много раз, смотрим распределение.
import numpy as np
def bootstrap_ci(data, stat=np.median, n_boot=10_000, alpha=0.05):
boot = np.array([stat(np.random.choice(data, len(data), replace=True))
for _ in range(n_boot)])
lo, hi = np.percentile(boot, [100 * alpha / 2, 100 * (1 - alpha / 2)])
return lo, hi
ci = bootstrap_ci(ltv_data, stat=np.median)Виды bootstrap CI
1. Percentile: квантили эмпирического распределения. Простой, но смещён при не-симметрии.
2. Basic (reverse percentile):
CI = [2·θ_hat - θ_high, 2·θ_hat - θ_low]
3. BCa (bias-corrected and accelerated): учитывает bias и skewness. Лучший, но сложнее.
from scipy.stats import bootstrap
res = bootstrap((data,), np.median, confidence_level=0.95,
method='BCa', n_resamples=10_000)
print(res.confidence_interval)4. Studentized: использует SE bootstrap-репликаций. Требует pivot, обычно избыточно.
Когда bootstrap нужен
- Сложная статистика (медиана, IQR, MAD, p99).
- Нелинейные комбинации (ratio metric, CTR между сессиями).
- Малая выборка с неизвестным распределением.
- Стат. фунция, для которой нет аналитического SE.
Когда не нужен
- Простое среднее на большой выборке — t/Normal CI быстрее.
- Очень малая выборка (n < 10) — bootstrap нестабильный.
- Высокая корреляция / clustered data — нужен block bootstrap.
Bootstrap для A/B
def diff_median(a, b):
return np.median(a) - np.median(b)
def bootstrap_diff(a, b, n_boot=10_000):
diffs = []
for _ in range(n_boot):
a_re = np.random.choice(a, len(a), replace=True)
b_re = np.random.choice(b, len(b), replace=True)
diffs.append(np.median(a_re) - np.median(b_re))
return np.array(diffs)
diffs = bootstrap_diff(group_a, group_b)
lo, hi = np.percentile(diffs, [2.5, 97.5])
p_value = 2 * min((diffs > 0).mean(), (diffs < 0).mean())Block bootstrap для time series
Если данные коррелированы (time series, panel), наивный bootstrap нарушает зависимость. Block bootstrap режет данные на блоки и сэмплирует блоки.
Подводные камни
- n_boot ≥ 10 000 для стабильных CI; 1 000 — недостаточно для 95% CI с разумной точностью.
- Percentile CI смещён в сторону среднего на асимметричных распределениях — BCa лучше.
- Bootstrap не работает на n < 10 — выборки повторяются слишком сильно.
- Не для extreme quantiles (p99 на n=100) — bootstrap не может «изобрести» новые tail точки.
- Clustered/time series данные нарушают iid assumption — block bootstrap.
Эталонный ответ
Resample with replacement, calculate stat, repeat 10k раз, take percentiles. Best для медиан, ratio metrics, сложных статистик. BCa> percentile при асимметрии. Block bootstrap для time series.