Неопределённость: доверительные интервалы и размер выборки
Есть один вопрос, который отделяет аналитика от человека, умеющего писать GROUP BY: насколько ваша цифра могла бы оказаться другой, если бы вы собрали данные ещё раз?
Пока на него нет ответа, число «конверсия 4,3 процента» — это не измерение, а мнение, оформленное десятичной дробью. Мнение можно оспорить, но нельзя проверить. Измерение выглядит иначе: «конверсия 4,3 процента, интервал от 3,0 до 5,6» — и сразу видно, что разговор про рост с 4,0 до 4,3 закрыт, потому что интервал в четыре раза шире обсуждаемой разницы.
Предыдущие главы разбирали, что данные искажены до того, как вы их увидели: сбор, качество, форма распределения. Эта глава — про другое: даже если сбор идеален и данные чисты, ваша цифра всё равно не равна истине, потому что вы наблюдали конечное число случаев. Задача — научиться количественно говорить, насколько не равна, и, что важнее, сколько данных нужно, чтобы вопрос вообще имел ответ. Аппарат вероятностей, на который это опирается, разобран в математике; здесь — прикладная часть: какие формулы применять, когда они ломаются и что писать в отчёте.
Два источника расхождения, и интервал ловит только один
Наблюдённое число отличается от истинного по двум принципиально разным причинам, и путать их — самая дорогая ошибка в профессии.
Шум — случайность выборки. Вы наблюдали 1000 сессий из потенциально бесконечного потока; в другую тысячу конверсий было бы чуть больше или чуть меньше. Шум симметричен, не сдвигает оценку систематически и лечится объёмом: чем больше n, тем меньше разброс. Смещение — систематический сдвиг из-за способа сбора: трекер не срабатывает у части пользователей, поздние события не долетели, в выгрузку попал не тот сегмент. Смещение не лечится объёмом вообще: миллион наблюдений с блокировщиками рекламы даст тот же неверный ответ, что и тысяча, только с более узким интервалом вокруг него.
Держите в голове ключевое: доверительный интервал измеряет только шум. Он ничего не знает о том, что ваш трекер теряет 30 процентов событий. Красивый узкий интервал вокруг смещённой оценки — самая убедительная форма ошибки, которая существует; про это подробно в главе Откуда берутся данные.
Практическое следствие: прежде чем считать интервал, оцените порядок смещения. Если вы подозреваете потерю событий на уровне 5 процентов, а интервал даёт ±0,3 процента — интервал бесполезен, он описывает не ту неопределённость, которая доминирует. Об этом отдельно в конце главы.
«У нас же все данные» — почему интервал всё равно нужен
Регулярное возражение: «Мы посчитали конверсию по всем пользователям за июнь, это не выборка, а генеральная совокупность. Какая ещё неопределённость?» Возражение звучит логично и почти всегда неверно.
Вы не хотите знать, какой была конверсия в июне. Вы хотите знать, какова конверсия вашего продукта — свойство процесса, а не конкретных 30 дней. Июньские пользователи — одна реализация процесса, который в июле выдаст другую. Формально это суперпопуляция: наблюдённые данные — выборка из гипотетического множества всех исходов, которые процесс мог бы породить. Аналогия: вы подбросили монету 1000 раз и получили 517 орлов; «все данные» по этим броскам у вас есть, но вопрос был про монету, а про неё вы знаете только то, что её смещение лежит примерно между 0,486 и 0,548.
Проверочный тест: если ответ будет использован для решения о будущем — это выборка, и интервал нужен. Если вопрос строго ретроспективный («сколько денег мы выставили в счетах за июнь») — это учёт, а не аналитика.
Исключение, где поправка реальна: выборка из конечного фиксированного списка — например, разобрали руками 500 обращений в поддержку из 2000 закрытых за квартал. Тут работает конечная поправка $\mathrm{SE}_{\text{fpc}} = \mathrm{SE}\sqrt{(N-n)/(N-1)}$: при $N = 2000$, $n = 500$ множитель равен $\sqrt{1500/1999} \approx 0{,}866$, интервал на 13 процентов уже. При $n = N$ множитель обращается в ноль — разобрали всё, неопределённости про этот квартал нет. Но про следующий — есть.
Стандартная ошибка: одна формула, из которой растёт всё
Стандартная ошибка (SE) — стандартное отклонение самой оценки, то есть мера того, как сильно оценка «дрожит» от выборки к выборке.
$$\mathrm{SE}(\bar{x}) = \frac{s}{\sqrt{n}}, \qquad \mathrm{SE}(\hat p) = \sqrt{\frac{\hat p\,(1-\hat p)}{n}}$$
Здесь $s$ — выборочное стандартное отклонение, $\hat p$ — наблюдённая доля, $n$ — число независимых наблюдений (слово «независимых» ниже окажется главным). Обратите внимание на знаменатель: $\sqrt{n}$, а не $n$ — чтобы сузить интервал вдвое, нужно вчетверо больше данных; втрое — в девять раз; на порядок — в сто раз.
Отсюда два вывода, которые экономят недели. Первый: если текущий интервал шире нужного в пять раз, «подождать ещё недельку» не поможет — ждать нужно в 25 раз дольше, то есть полгода, и вопрос надо переформулировать. Второй: если у вас уже 10 миллионов наблюдений, ещё два миллиона не изменят ничего, и усилия стоит потратить на борьбу со смещением, а не на объём.
Числовой ориентир для конверсии около 4 процентов:
| n | SE | 95 % интервал (Wald) | Полуширина |
|---|---|---|---|
| 1 000 | 0,62 п.п. | 2,79 % … 5,21 % | ±1,21 п.п. |
| 10 000 | 0,20 п.п. | 3,62 % … 4,38 % | ±0,38 п.п. |
| 100 000 | 0,06 п.п. | 3,88 % … 4,12 % | ±0,12 п.п. |
Тысяча сессий не отличает 4 процента от 5. Это не пессимизм, это арифметика.
Что на самом деле означает «95 %»
Доверительный интервал строится процедурой: взять оценку, взять её стандартную ошибку, отложить в обе стороны $z$ или $t$ стандартных ошибок. Гарантия относится к процедуре, а не к конкретному интервалу: если повторять весь эксперимент много раз, 95 процентов построенных таким способом интервалов накроют истинное значение.
На картинке — двадцать независимых замеров конверсии при истинном значении 4,0 процента и $n = 1000$. Девятнадцать интервалов накрыли истину, один (замер #4, где случайно выпало 28 конверсий вместо ожидаемых 40) — нет. Это не сбой метода, а ровно то, что метод обещал.
Здесь и прячется тонкость, из-за которой ломаются формулировки в отчётах. Сказать можно так: «процедура, которой построен этот интервал, накрывает истину в 95 процентах случаев», «значения внутри интервала совместимы с наблюдёнными данными, снаружи — плохо совместимы», «данные не позволяют отличить 3,9 процента от 4,3 процента». А вот чего сказать нельзя:
- «Истинное значение лежит внутри с вероятностью 0,95». Это байесовское утверждение: в частотной постановке истинное значение — константа, оно либо внутри, либо нет, вероятности 0,5 или 0,95 тут не при чём. Чтобы получить право на такую фразу, нужен байесовский доверительный интервал (credible interval) и явно заданное априорное распределение.
- «95 процентов будущих наблюдений попадут в этот интервал». Это интервал предсказания, он гораздо шире: $\bar{x} \pm t \cdot s\sqrt{1 + 1/n}$. При большом $n$ доверительный интервал стягивается в точку, а интервал предсказания — нет, он упирается в разброс самих данных.
- «Раз интервал не содержит нуля, эффект есть». Точнее: «данные плохо совместимы с гипотезой об отсутствии эффекта при принятых допущениях» — а допущений там много. Разбор в следующей главе.
Полезная практика последних лет — называть его интервалом совместимости (compatibility interval): термин честнее описывает смысл и не провоцирует на «вероятность 95 процентов». Аргументацию см. в Greenland et al., «Statistical tests, P values, confidence intervals, and power: a guide to misinterpretations» и в комментарии Amrhein, Greenland, McShane в Nature (2019).
Интервал для доли: Wald ломается там, где он интереснее всего
Формула $\hat p \pm z\sqrt{\hat p (1-\hat p)/n}$ (интервал Вальда) — та, которую все помнят, и она плоха ровно в тех случаях, где интервал по-настоящему нужен: редкие события и малые выборки.
| k / n | Wald | Wilson | Что не так с Wald |
|---|---|---|---|
| 2 / 100 | −0,0074 … 0,0474 | 0,0055 … 0,0700 | отрицательная нижняя граница |
| 0 / 500 | 0,0000 … 0,0000 | 0,0000 … 0,0076 | нулевая ширина: «мы точно знаем, что это невозможно» |
| 40 / 1000 | 0,0279 … 0,0521 | 0,0295 … 0,0540 | разница уже небольшая |
| 95 / 100 | 0,9073 … 0,9927 | 0,8882 … 0,9785 | верхняя граница почти упёрлась в 1 |
Рабочая замена — интервал Уилсона: он всегда лежит внутри отрезка от 0 до 1, не схлопывается при нулевых счётчиках и имеет заметно лучшее реальное покрытие при малых $n$.
$$\tilde p = \frac{\hat p + \dfrac{z^2}{2n}}{1 + \dfrac{z^2}{n}}, \qquad w = \frac{z}{1 + \dfrac{z^2}{n}}\sqrt{\frac{\hat p\,(1-\hat p)}{n} + \frac{z^2}{4n^2}}$$
Интервал — это $\tilde p \pm w$. Сравнительный анализ методов: Brown, Cai, DasGupta, «Interval Estimation for a Binomial Proportion», Statistical Science 2001.
from math import sqrt
from statistics import NormalDist
Z = NormalDist().inv_cdf(0.975) # 1.959964 — для 95 %
def wilson(k: int, n: int, z: float = Z) -> tuple[float, float]:
"""95-процентный интервал Уилсона для доли k из n."""
if n == 0:
return (0.0, 1.0) # ничего не наблюдали — ничего не знаем
p = k / n
denom = 1 + z * z / n
center = (p + z * z / (2 * n)) / denom
half = z / denom * sqrt(p * (1 - p) / n + z * z / (4 * n * n))
return (max(0.0, center - half), min(1.0, center + half))
wilson(2, 100) # (0.0055, 0.0700) — против (-0.0074, 0.0474) у Вальда
wilson(0, 500) # (0.0000, 0.0076) — против нулевой ширины у Вальда
wilson(40, 1000) # (0.0295, 0.0540)
wilson(95, 100) # (0.8882, 0.9785)
В продакшне не пишите это руками: statsmodels.stats.proportion.proportion_confint(count, nobs, method="wilson") даёт то же самое и ещё пять методов, документация.
Правило трёх: что делать, когда событий ноль
Отдельный практический случай: за 500 запусков ни одного отказа. Отказов не бывает? Из $(1-p)^n \le 0{,}05$ следует $p \lesssim -\ln(0{,}05)/n \approx 3/n$. Это правило трёх: при нуле событий из $n$ верхняя 95-процентная граница равна примерно $3/n$ — 0 из 100 даёт «не больше 3 процентов», 0 из 500 — «не больше 0,6 процента», 0 из 10 000 — «не больше 0,03 процента».
Формулировка «падений не зафиксировано; при 500 наблюдениях это означает частоту не выше 0,6 процента» полезна и проверяема, а «падений нет» — нет. Подробнее: Wikipedia, Rule of three (statistics).
Интервал для среднего: где ЦПТ действительно не спасает
Для среднего интервал строится через $t$-распределение:
$$\bar{x} \pm t_{1-\alpha/2,\,n-1} \cdot \frac{s}{\sqrt{n}}$$
При $n \ge 100$ разница между $t$ и $z$ уже в третьем знаке, и на практике все берут 1,96. Опасность не в этом.
Опасность в правиле «при $n > 30$ центральная предельная теорема всё сгладит». Оно верно для симметричных распределений и катастрофически неверно для скошенных — то есть для выручки, длительности сессии, времени отклика, размера чека, всего, чем реально занимается аналитик. Вот измеренное покрытие номинально 95-процентного интервала на логнормальных данных (по 4000–6000 симуляций):
| Скошенность распределения | n = 30 | n = 100 | n = 1000 | n = 10 000 |
|---|---|---|---|---|
| $\sigma = 0{,}5$ (перекос 1,8) | 91,5 % | 94,3 % | 95,5 % | 95,3 % |
| $\sigma = 1{,}0$ (перекос 6,2) | 86,7 % | 91,1 % | 93,8 % | 94,8 % |
| $\sigma = 1{,}5$ (перекос 33) | 76,2 % | 85,4 % | 92,0 % | 94,7 % |
| $\sigma = 2{,}0$ (перекос 414) | 65,1 % | 74,2 % | 85,8 % | 91,5 % |
Читается так: при сильно скошенной выручке и сотне заказов ваш «95-процентный» интервал на самом деле накрывает истину в 74 процентах случаев. Каждый четвёртый вывод неверен, а выглядит одинаково уверенно во всех случаях.
Практический ориентир из статьи Boos & Hughes-Oliver, «How Large Does n Have to Be for Z and t Intervals?», The American Statistician 2000: нужно примерно $n \ge 25\,\gamma_1^2$, где $\gamma_1$ — коэффициент асимметрии. Для перекоса 6 это уже 900 наблюдений, для перекоса 33 — десятки тысяч.
Что делать: считать асимметрию до того, как строить интервал (одна строка в запросе, а решает, доверять ли результату); оценивать медиану или урезанное среднее, если бизнес-вопрос это допускает — выбор центра разбирают глава 05 и глава 06; логарифмировать — интервал для среднего логарифма ведёт себя прилично, но обратно преобразуется в интервал для геометрического среднего, а не арифметического, и подменять одно другим нельзя; бутстрапировать — с оговоркой, что он тоже не всесилен.
Бутстрап: интервал там, где формулы нет
Для медианы, 95-го перцентиля, отношения двух метрик, доли пользователей, сделавших три и более заказа, формулы стандартной ошибки либо нет, либо она громоздкая. Бутстрап решает это грубой силой: если выборка репрезентирует популяцию, то пересэмплирование выборки репрезентирует пересэмплирование популяции.
n наблюдений] --> B[Реплика 1
n элементов с возвращением] A --> C[Реплика 2 ... Реплика B] B --> F[Статистика на каждой реплике] C --> F F --> G[Распределение B значений] G --> H[Перцентили 2,5 и 97,5
= границы интервала] G --> I[Стандартное отклонение
= оценка SE]
import numpy as np
def bootstrap_ci(values, stat=np.mean, n_boot=10_000, alpha=0.05, seed=42, chunk=500):
"""Перцентильный бутстрап-интервал.
Время O(n_boot * n), память O(n + chunk * n): считаем блоками, чтобы не съесть RAM."""
rng = np.random.default_rng(seed)
values = np.asarray(values, dtype=float)
n, reps, done = len(values), np.empty(n_boot), 0
while done < n_boot:
b = min(chunk, n_boot - done)
idx = rng.integers(0, n, size=(b, n)) # индексы с возвращением
reps[done:done + b] = stat(values[idx], axis=1)
done += b
return tuple(np.quantile(reps, [alpha / 2, 1 - alpha / 2]))
x = np.random.default_rng(1).lognormal(6.0, 1.2, size=3000) # похоже на чек
p95 = lambda a, axis: np.quantile(a, 0.95, axis=axis)
bootstrap_ci(x) # среднее 823.5 -> (770.6, 879.4)
bootstrap_ci(x, np.median) # медиана 402.9 -> (382.3, 421.9)
bootstrap_ci(x, p95) # p95 2915.7 -> (2624.2, 3106.0)
Сложность: $O(B \cdot n)$ по времени, $O(n)$ по памяти при блочном счёте. $B = 1000$ хватает для оценки SE, для перцентильных границ берите $B \ge 5000$ — иначе дрожат сами границы.
Чего бутстрап не делает. Он не создаёт информацию, которой нет в выборке. Измеренное покрытие бутстрап-интервала на тех же логнормальных данных ($\sigma = 1{,}5$):
| n | $t$-интервал | Бутстрап |
|---|---|---|
| 20 | 76,8 % | 76,7 % |
| 50 | 81,9 % | 80,9 % |
| 200 | 87,4 % | 89,5 % |
| 1000 | 91,8 % | 92,0 % |
Разница в пределах пары процентных пунктов: на малых выборках со скошенными данными оба метода врут примерно одинаково. Бутстрап полезен там, где формулы просто нет, а не как способ починить нехватку данных. Для скошенных случаев есть улучшенные версии — BCa (bias-corrected and accelerated) и studentized bootstrap; каноническое изложение — Efron & Tibshirani, «An Introduction to the Bootstrap» (1993).
Где бутстрап ломается совсем: на экстремальных квантилях (бутстрап p99 при $n = 200$ пересэмплирует одни и те же две точки из хвоста, интервал выйдет оптимистично узким); на максимуме, минимуме и размахе — статистики, зависящие от границы носителя распределения, он оценивает несостоятельно; и на зависимых наблюдениях — про это следующий раздел, и это самая частая производственная ошибка.
Перцентиль по 40 наблюдениям — это шум, и вот насколько
Обещание из главы про SQL. Логнормальные данные с истинным p95 = 1504, разброс выборочной оценки p95:
| n | Медианная оценка | 95 % разброс оценки | Ширина относительно истины |
|---|---|---|---|
| 40 | 1369 | 881 … 2283 | 93 % |
| 200 | 1467 | 1187 … 1860 | 45 % |
| 1000 | 1497 | 1357 … 1662 | 20 % |
| 10 000 | 1504 | 1453 … 1555 | 7 % |
По сорока наблюдениям p95 определён «плюс-минус в полтора раза». Ещё нагляднее: вероятность того, что все 40 значений окажутся ниже истинного p95, равна $0{,}95^{40} \approx 12{,}9$ процента — примерно в каждом восьмом случае вся выборка целиком лежит ниже величины, которую вы оцениваете, и верхней границы у вас нет в принципе.
Распределённо-свободный интервал для квантиля строится по порядковым статистикам: границами берут элементы с рангами примерно $nq \pm z\sqrt{nq(1-q)}$. Для $n = 40$, $q = 0{,}95$ это ранги с 35-го по 40-й, то есть «p95 где-то между 35-м и самым большим из сорока»; для $n = 1000$ — ранги 936…965, перцентили от 93,6 до 96,5. Отсюда правило: не показывайте перцентиль без числа наблюдений рядом — колонка n в отчёте это единственный способ понять, измерение перед вами или дрожание.
Независимость: главное место, где n оказывается не тем n
Все формулы выше содержат $n$ — число независимых наблюдений. В аналитике продукта его почти всегда завышают, потому что считают события, а события одного пользователя друг от друга не независимы.
Демонстрация на смоделированных данных: 5000 пользователей, 14 931 заказ, средний чек 1413 рубля. У пользователей разный «уровень трат» — богатые покупают дорого стабильно.
| Способ | SE | 95 % интервал |
|---|---|---|
| Наивный, по 14 931 заказу | 9,38 | 1394,7 … 1431,5 |
| Правильный, по 5000 пользователей (дельта-метод) | 15,21 | 1383,3 … 1442,9 |
| Правильный, бутстрап по пользователям | 15,41 | — |
Наивный интервал в 1,6 раза уже правильного. Поскольку дисперсия — квадрат SE, это означает, что расчёт приписывает себе в $1{,}62^2 \approx 2{,}6$ раза больше информации, чем в данных есть. В A/B-тесте это ровно тот механизм, из-за которого «значимые» результаты не воспроизводятся.
Величина эффекта описывается коэффициентом внутрикластерной корреляции $\rho$ и средним размером кластера $\bar m$:
$$\mathrm{DEFF} = 1 + (\bar m - 1)\,\rho, \qquad n_{\text{eff}} = \frac{n}{\mathrm{DEFF}}, \qquad \mathrm{SE}{\text{верно}} = \mathrm{SE}{\text{наивно}} \cdot \sqrt{\mathrm{DEFF}}$$
| $\bar m$ (событий на пользователя) | $\rho$ | DEFF | Во сколько раз шире интервал |
|---|---|---|---|
| 2 | 0,2 | 1,2 | 1,10 |
| 3 | 0,5 | 2,0 | 1,41 |
| 5 | 0,3 | 2,2 | 1,48 |
| 10 | 0,1 | 1,9 | 1,38 |
В примере выше $\bar m = 3{,}0$, $\rho \approx 0{,}64$, DEFF $\approx 2{,}3$, $\sqrt{\mathrm{DEFF}} \approx 1{,}5$ — почти в точности наблюдённые 1,6. Отсюда практическое правило: сначала сверните данные до уровня рандомизации, потом считайте. Если эксперимент рандомизирует пользователей — единица наблюдения пользователь, даже если метрика формулируется в кликах.
Отношения метрик и дельта-метод
Отдельная ловушка — метрики вида «отношение сумм»: средний чек = выручка / число заказов, CTR = клики / показы, конверсия из корзины в оплату по событиям. Это не среднее по пользователям, поэтому обычная SE не годится. Работает дельта-метод: пусть $x_i$ и $y_i$ — числитель и знаменатель, агрегированные по пользователю $i$, и $R = \bar x / \bar y$. Тогда
$$\widehat{\mathrm{Var}}(R) \approx \frac{1}{n\,\bar y^{\,2}}\left( s_x^2 - 2R\,s_{xy} + R^2 s_y^2 \right)$$
где $s_x^2$, $s_y^2$ — выборочные дисперсии, $s_{xy}$ — ковариация, $n$ — число пользователей. Ковариационный член обязателен: у активных пользователей и числитель, и знаменатель больше, и игнорирование этой связи снова даёт слишком узкий интервал. Промышленное изложение с деталями для A/B: Deng, Knoblich, Lu, «Applying the Delta Method in Metric Analytics», KDD 2018.
Интервалы прямо в SQL
Не заводите отдельный ноутбук ради интервала: доверительные границы считаются в том же запросе, что и метрика, и это единственный способ добиться, чтобы их видели.
Интервал Уилсона для конверсии по сегментам
-- PostgreSQL. Конверсия по каналам с 95-процентным интервалом Уилсона.
WITH agg AS (
SELECT channel,
COUNT(*) AS n,
COUNT(*) FILTER (WHERE converted) AS k
FROM sessions
WHERE session_date >= DATE '2026-06-01'
AND session_date < DATE '2026-07-01'
GROUP BY channel
),
base AS (
SELECT channel, n, k,
k::double precision / n AS p,
1.959964 AS z -- 95 %; для 99 % возьмите 2.575829
FROM agg WHERE n > 0
)
SELECT channel, n, k,
ROUND(p::numeric, 4) AS conversion,
ROUND(((p + z*z/(2*n)) / (1 + z*z/n)
- z/(1 + z*z/n) * sqrt(p*(1-p)/n + z*z/(4*n*n)))::numeric, 4) AS ci_low,
ROUND(((p + z*z/(2*n)) / (1 + z*z/n)
+ z/(1 + z*z/n) * sqrt(p*(1-p)/n + z*z/(4*n*n)))::numeric, 4) AS ci_high
FROM base
ORDER BY n DESC;
Такая таблица моментально закрывает разговоры вида «в канале referral конверсия выше на 0,3 п.п., перекладываем бюджет»: если интервалы каналов перекрываются почти целиком, перекладывать нечего.
Интервал для среднего с проверкой на скошенность
-- Средний чек по платформам + асимметрия, чтобы понимать, можно ли верить интервалу.
-- Асимметрия через сырые моменты, один проход:
-- g1 = (E[x^3] - 3*mu*E[x^2] + 2*mu^3) / (E[x^2] - mu^2)^1.5
SELECT platform,
COUNT(*) AS n,
ROUND(AVG(amount)::numeric, 1) AS mean_amount,
ROUND((AVG(amount) - 1.959964 * STDDEV_SAMP(amount)
/ sqrt(COUNT(*)))::numeric, 1) AS ci_low,
ROUND((AVG(amount) + 1.959964 * STDDEV_SAMP(amount)
/ sqrt(COUNT(*)))::numeric, 1) AS ci_high,
ROUND(((AVG(POWER(amount, 3)) - 3 * AVG(amount) * AVG(POWER(amount, 2))
+ 2 * POWER(AVG(amount), 3))
/ POWER(AVG(POWER(amount, 2)) - POWER(AVG(amount), 2), 1.5))::numeric, 2)
AS skewness
FROM orders
WHERE created_at >= TIMESTAMP '2026-06-01 00:00:00'
AND created_at < TIMESTAMP '2026-07-01 00:00:00'
GROUP BY platform
HAVING COUNT(*) >= 100;
В ClickHouse и DuckDB есть готовая skewness(), в PostgreSQL её нет — отсюда явные моменты; на больших n версии совпадают до второго знака. Правило чтения: skewness больше 2 при n меньше 25 * skewness^2 — интервал декоративный, переходите к медиане или бутстрапу.
Дельта-метод для отношения метрик
-- Средний чек как отношение сумм: единица наблюдения — пользователь, а не заказ.
WITH per_user AS (
SELECT user_id,
SUM(amount) AS x, -- числитель: выручка пользователя
COUNT(*) AS y -- знаменатель: число его заказов
FROM orders
WHERE created_at >= TIMESTAMP '2026-06-01 00:00:00'
AND created_at < TIMESTAMP '2026-07-01 00:00:00'
GROUP BY user_id
),
stats AS (
SELECT COUNT(*)::double precision AS n,
AVG(x::double precision) AS mx,
AVG(y::double precision) AS my,
VAR_SAMP(x::double precision) AS vx,
VAR_SAMP(y::double precision) AS vy,
COVAR_SAMP(x::double precision, y::double precision) AS cxy
FROM per_user
),
ratio AS (
SELECT n, mx / my AS r,
sqrt((vx - 2*(mx/my)*cxy + (mx/my)*(mx/my)*vy) / (n * my * my)) AS se
FROM stats
)
SELECT n AS users,
ROUND(r::numeric, 2) AS avg_order_value,
ROUND(se::numeric, 2) AS se_delta,
ROUND((r - 1.959964 * se)::numeric, 2) AS ci_low,
ROUND((r + 1.959964 * se)::numeric, 2) AS ci_high
FROM ratio;
Бутстрап, не выходя из хранилища
Пересэмплирование с возвращением на миллионах строк в SQL неудобно. Промышленная замена — пуассоновский бутстрап: вместо выбора $n$ элементов с возвращением каждому наблюдению независимо присваивается вес из распределения Пуассона с параметром 1. При больших $n$ распределение реплик то же, но считается всё одним проходом и параллелится.
-- 500 реплик ARPU. Вес ~ Poisson(1) задан через обратную функцию распределения.
WITH per_user AS (
SELECT user_id, SUM(amount) AS revenue FROM orders GROUP BY user_id
),
draws AS ( -- одна равномерная величина на пару «реплика × пользователь»
SELECT r.b, u.revenue, random() AS u
FROM generate_series(1, 500) AS r(b)
CROSS JOIN per_user u
),
weighted AS (
SELECT b, revenue,
CASE WHEN u < 0.367879 THEN 0 -- P(0) = e^-1
WHEN u < 0.735759 THEN 1 -- P(0)+P(1)
WHEN u < 0.919699 THEN 2
WHEN u < 0.981012 THEN 3
WHEN u < 0.996340 THEN 4
WHEN u < 0.999406 THEN 5
ELSE 6 END AS w
FROM draws
),
arpu AS (
SELECT b, SUM(revenue * w) / NULLIF(SUM(w), 0) AS arpu_b
FROM weighted GROUP BY b
)
SELECT ROUND(percentile_cont(0.025) WITHIN GROUP (ORDER BY arpu_b)::numeric, 2) AS ci_low,
ROUND(percentile_cont(0.500) WITHIN GROUP (ORDER BY arpu_b)::numeric, 2) AS arpu_median,
ROUND(percentile_cont(0.975) WITHIN GROUP (ORDER BY arpu_b)::numeric, 2) AS ci_high
FROM arpu;
Проверка на смоделированных данных: пуассоновский бутстрап дал 3823,5 … 3889,1, аналитический нормальный интервал — 3820,2 … 3889,2. Цена: запрос читает n × B строк, при 500 репликах и миллионе пользователей это полмиллиарда строк — на колоночном движке (ClickHouse, BigQuery, DuckDB) терпимо, на строчной OLTP-базе нет; про разницу см. колоночные хранилища. Уменьшайте $B$ до 200, если нужна только оценка SE, а не хвостовые перцентили.
Обратная задача: сколько данных нужно
До сих пор мы отвечали «насколько точна цифра, которая уже есть». Планирование задаёт обратный вопрос: какой объём нужен, чтобы различить эффект интересующего размера. Формула для сравнения двух долей (на одну группу, двусторонний критерий):
$$n = \frac{\left(z_{1-\alpha/2} + z_{1-\beta}\right)^2 \left( p_1(1-p_1) + p_2(1-p_2) \right)}{(p_2 - p_1)^2}$$
При стандартных $\alpha = 0{,}05$ и мощности $1-\beta = 0{,}8$ множитель $(1{,}960 + 0{,}842)^2 \approx 7{,}85$. Для среднего аналогично: $n \approx 15{,}7\,\sigma^2 / \Delta^2$ на группу.
from math import sqrt
from statistics import NormalDist
def sample_size_proportion(p_base, mde_rel, alpha=0.05, power=0.80):
"""Размер ОДНОЙ группы для двустороннего теста двух долей.
mde_rel — относительный эффект: 0.10 значит «хотим ловить рост на 10 %»."""
z_a, z_b = NormalDist().inv_cdf(1 - alpha / 2), NormalDist().inv_cdf(power)
p1, p2 = p_base, p_base * (1 + mde_rel)
return (z_a + z_b) ** 2 * (p1 * (1 - p1) + p2 * (1 - p2)) / (p2 - p1) ** 2
def detectable_effect(p_base, n_per_group, alpha=0.05, power=0.80):
"""Обратная задача: какой минимальный АБСОЛЮТНЫЙ эффект различим при данном n."""
z_a, z_b = NormalDist().inv_cdf(1 - alpha / 2), NormalDist().inv_cdf(power)
return (z_a + z_b) * sqrt(2 * p_base * (1 - p_base) / n_per_group)
round(sample_size_proportion(0.04, 0.10)) # 39472 на группу
round(detectable_effect(0.04, 10_000) * 100, 2) # 0.78 п.п.
Базовая конверсия 4 процента, $\alpha = 0{,}05$, мощность 0,8:
| Хотим поймать рост | Абсолютный эффект | n на группу | Дней при 20 000 сессий/день |
|---|---|---|---|
| +50 % (до 6,0 %) | 2,00 п.п. | 1 860 | 0,2 |
| +20 % (до 4,8 %) | 0,80 п.п. | 10 313 | 1,0 |
| +10 % (до 4,4 %) | 0,40 п.п. | 39 472 | 3,9 |
| +5 % (до 4,2 %) | 0,20 п.п. | 154 301 | 15,4 |
| +2 % (до 4,08 %) | 0,08 п.п. | 950 884 | 95 |
| +1 % (до 4,04 %) | 0,04 п.п. | 3 785 507 | 379 |
Квадратичная зависимость видна невооружённым глазом: переход от «ловим +10 процентов» к «ловим +5 процентов» стоит вчетверо больше данных. Именно поэтому «а давайте измерим эффект точнее» почти всегда означает «давайте потратим на это квартал».
которое от него зависит] --> B[Какая разница меняет решение?
Это и есть MDE] B --> C{Считаем n
по формуле мощности} C --> D{Хватит ли трафика
за разумный срок?} D -->|Да| E[Планируем длительность,
фиксируем правило остановки] D -->|Нет| F{Можно ли уменьшить дисперсию?} F -->|Да| G[CUPED, стратификация,
урезание выбросов,
более чувствительная метрика] G --> C F -->|Нет| H{Можно ли повысить MDE?
Готовы ли ловить только крупный эффект} H -->|Да| C H -->|Нет| I[Эксперимент не проводить.
Решать качественно —
интервью, экспертиза, обратимость] E --> J[Считаем интервал ОДИН раз
в конце запланированного срока]
Ветка I — не поражение, а самый ценный результат планирования. Тест, который заведомо не может обнаружить обсуждаемый эффект, потребляет трафик, время и внимание, а на выходе даёт «различий не обнаружено» — фразу, которую все прочитают как «эффекта нет». Про эту подмену и про мощность подробно — в главе про A/B; про решение без эксперимента — в главе про причинность и в MVP и экспериментах.
Что ваш объём вообще способен различить
Более полезная в реальной жизни постановка: трафик фиксирован, вопрос — что вы вообще можете узнать. При базовой конверсии $p$:
$$\Delta_{\min} = \left(z_{1-\alpha/2} + z_{1-\beta}\right)\sqrt{\frac{2\,p(1-p)}{n}}$$
| n на группу | Минимально различимый эффект при $p = 4\,%$ | В относительных величинах |
|---|---|---|
| 2 000 | 1,74 п.п. | +43 % |
| 10 000 | 0,78 п.п. | +19 % |
| 50 000 | 0,35 п.п. | +9 % |
| 100 000 | 0,25 п.п. | +6 % |
| 500 000 | 0,11 п.п. | +3 % |
Читать так: при 10 000 пользователей на группу вы способны надёжно поймать только рост конверсии почти на пятую часть. Изменение цвета кнопки таких эффектов не даёт — значит, этот эксперимент бессмысленен до того, как он запущен, и это можно сказать заранее за две минуты. Быстрая прикидка — калькулятор Эвана Миллера, в коде — statsmodels power.
И один пункт, который относится сюда, хотя разбирается в главе про A/B: интервал честен ровно один раз — в момент, для которого он планировался. Если пересчитывать его каждый день и остановиться, когда он перестал накрывать ноль, реальная частота ложных срабатываний уходит с 5 процентов к 20–30. Правильность формулы это не компенсирует: сломано не вычисление, а процедура, к которой относится гарантия. Механику подглядывания и корректные последовательные критерии разбирает глава 09.
Что делать с интервалом: решение, а не украшение
Интервал приносит пользу тогда, когда его сопоставляют с порогом, меняющим решение, — той самой величиной, ради которой задавался вопрос. Четыре исхода:
Разница между квадрантами 3 и 4 — та, которую путают чаще всего. «Интервал накрывает ноль» означает разное в зависимости от ширины: интервал от −0,2 до +0,25 п.п. при базе 4 процента — это эффекта практически нет, любой заметный эффект данными исключён, содержательный и ценный ответ. Интервал от −4 до +5 п.п. — это мы ничего не узнали: и удвоение конверсии, и обвал совместимы с данными, и писать здесь «влияния не обнаружено» значит дезинформировать.
Формулировки, которые стоит перенести в свои отчёты дословно:
- «Рост конверсии 0,40 п.п. (95 %: 0,01 … 0,79), то есть от +0,2 до +19,8 процента относительно базы. Эффект скорее положительный, но его величина не определена: нижняя граница не окупает разработку, верхняя окупает вдесятеро».
- «Различие −0,05 п.п. (95 %: −0,25 … +0,15). Эффект больше 0,25 п.п. в любую сторону данными исключён; порог окупаемости был 0,5 п.п., поэтому решение — не внедрять».
- «Интервал −4 … +5 п.п. Замер не даёт информации о вопросе; при текущем трафике нужно 12 недель. Предлагаю решать иначе».
Разница между первой и третьей формулировкой — это разница между аналитиком и генератором графиков. Как встроить такое в регулярную отчётность, разбирает глава про дашборды.
Как показывать неопределённость и как её показывать нельзя
Три правила, дальнейшее — в главе про визуализацию.
1. Столбик без усов — утверждение о точности, которого у вас нет. Столбчатая диаграмма из двух столбиков 4,0 и 4,3 читается как «выросло». Те же данные с интервалами читаются как «неизвестно». Если данных на интервал не хватает, это факт про данные, а не повод его скрыть.
2. Перекрывающиеся усы НЕ означают отсутствия различий. Классическая ошибка чтения. Пусть у двух групп одинаковая SE. Их 95-процентные интервалы перекрываются, пока разность меньше $2 z \cdot \mathrm{SE} = 3{,}92\,\mathrm{SE}$. А различие значимо на уровне 0,05, когда разность больше $z\sqrt{2} \cdot \mathrm{SE} = 2{,}77\,\mathrm{SE}$. В зазоре между 2,77 и 3,92 усы перекрываются, а различие значимо. Обратное неверно: непересекающиеся 95-процентные интервалы почти всегда означают значимое различие. Разбор — Cumming & Finch, «Inference by Eye» (American Psychologist, 2005).
Отсюда вывод, экономящий много споров: стройте интервал для разности, а не два интервала для групп — вопрос был про разницу, интервал должен быть про разницу.
$$\mathrm{SE}(\hat p_2 - \hat p_1) = \sqrt{\frac{\hat p_1 (1-\hat p_1)}{n_1} + \frac{\hat p_2 (1-\hat p_2)}{n_2}}$$
Пример: 4,00 против 4,40 процента, по 20 000 в группе. $\mathrm{SE} = 0{,}00201$, разность 0,40 п.п., интервал 0,007 … 0,793 п.п. В относительных величинах — рост от 0,2 до 19,8 процента. Заголовок «конверсия выросла на 10 процентов» формально не соврал и при этом не сообщил почти ничего.
3. Не подписывайте интервал как «разброс данных». Интервал среднего сужается с ростом $n$, разброс самих наблюдений — нет. Нужен разброс — показывайте перцентили или ящик с усами; это разные картинки с разным смыслом.
Типичные ошибки и когда интервал бесполезен
| Ошибка или ситуация | Как выглядит | Что делать |
|---|---|---|
| Цифра без интервала | «Конверсия 4,3 %» | Всегда рядом n и границы |
| Wald при малых счётчиках | Нижняя граница отрицательна | Уилсон или Клоппер — Пирсон |
| «Событий нет, значит не бывает» | 0 из 500 → «0 %» | Правило трёх: не выше 3/n |
| n = число событий | 100 000 кликов от 8000 юзеров | Свернуть до уровня рандомизации |
| Отношение метрик как обычное среднее | CTR через AVG по строкам показов | Дельта-метод или бутстрап по пользователям |
| Среднее по скошенным данным при малом n | AVG(revenue), n = 80 |
Медиана, бутстрап, честная оговорка |
| Ежедневный пересчёт интервала | «Уже почти значимо, подождём» | Правило остановки заранее, глава 09 |
| Перекрывающиеся усы как «разницы нет» | Два столбика с усами | Интервал для разности |
| «Вероятность 95 %, что истина внутри» | В тексте отчёта | «Совместимо с данными» |
| Смещение сбора больше ширины интервала | Узко и уверенно мимо | Оценить смещение численно, глава 02 |
| Гипотеза выбрана после просмотра данных | «Смотрите, в этом сегменте!» | Назвать находку поисковой, перепроверить на новых данных |
| Сравниваются 20 сегментов сразу | Один «значимый» ожидаем и без эффекта | Поправка на множественность |
| Величина учётная, а не выборочная | Сумма счетов за июнь | Просто число, интервал не нужен |
Отдельно: интервал не защищает от того, что вы измерили не ту величину. Узкий интервал вокруг метрики, не связанной с решением, — точный ответ на неправильный вопрос; про то, как метрика меняет поведение системы, едва став целью, — глава 14 и локальная оптимизация.
Чеклист перед публикацией цифры
- Что здесь единица наблюдения и совпадает ли она с уровнем рандомизации или отбора?
- Чему равно $n$ независимых единиц, а не строк в выгрузке?
- Каков масштаб известного смещения сбора и больше ли он ширины интервала?
- Доля или среднее? Для доли — Уилсон; для среднего — какова асимметрия?
- Метрика — отношение сумм? Тогда дельта-метод или бутстрап по кластерам.
- Интервал относится к разности, если вопрос был про разность?
- Гипотеза сформулирована до того, как вы посмотрели на данные, и сколько метрик вы проверили заодно?
- Какой порог меняет решение, как интервал расположен относительно него — и если интервал широкий, написано ли явно «мы не узнали ответ», а не «различий нет»?
Мини-итог
Доверительный интервал — не украшение отчёта, а способ признать конечность данных в проверяемой форме. Он описывает только случайный шум и молчит про смещение сбора, поэтому узкий интервал вокруг смещённой оценки — самая опасная конфигурация из возможных. Точность растёт как $\sqrt{n}$, из чего следует, что вдвое более точный ответ стоит вчетверо дороже, а «уточнить на порядок» обычно означает «отказаться от вопроса». Для долей работает Уилсон, для скошенных средних — бутстрап или отказ от среднего, для отношений — дельта-метод, а $n$ почти всегда меньше, чем кажется, потому что события одного пользователя не независимы. И самое практичное: расчёт минимально различимого эффекта делается до эксперимента и часто его отменяет — это лучший из возможных результатов, потому что стоит две минуты вместо трёх недель.
Источники
- Greenland S. et al. «Statistical tests, P values, confidence intervals, and power: a guide to misinterpretations». European Journal of Epidemiology, 2016. https://link.springer.com/article/10.1007/s10654-016-0149-3
- Amrhein V., Greenland S., McShane B. «Scientists rise up against statistical significance». Nature, 2019. https://www.nature.com/articles/d41586-019-00857-9
- Brown L.D., Cai T.T., DasGupta A. «Interval Estimation for a Binomial Proportion». Statistical Science, 2001. https://projecteuclid.org/journals/statistical-science/volume-16/issue-2/Interval-Estimation-for-a-Binomial-Proportion/10.1214/ss/1009213286.full
- Boos D.D., Hughes-Oliver J.M. «How Large Does n Have to Be for Z and t Intervals?». The American Statistician, 2000. https://www.tandfonline.com/doi/abs/10.1080/00031305.2000.10474524
- Efron B., Tibshirani R. «An Introduction to the Bootstrap». Chapman & Hall, 1993.
- Deng A., Knoblich U., Lu J. «Applying the Delta Method in Metric Analytics». KDD, 2018. https://arxiv.org/abs/1803.06336
- Kohavi R., Tang D., Xu Y. «Trustworthy Online Controlled Experiments». Cambridge University Press, 2020. https://experimentguide.com/
- Cumming G., Finch S. «Inference by Eye: Confidence Intervals and How to Read Pictures of Data». American Psychologist, 2005. https://doi.org/10.1037/0003-066X.60.2.170
- Документация statsmodels: интервалы для долей и расчёт мощности. https://www.statsmodels.org/stable/stats.html
Что дальше
Мы научились говорить, насколько цифра могла бы быть другой. Следующий шаг — научиться отвечать на вопрос «а могло ли наблюдённое различие возникнуть само», и разобраться, что при этом означает и, главное, чего не означает знаменитая величина, которую все считают и почти никто не формулирует верно.