Калькулятор статистической значимости A/B тестов
Проверьте, является ли ваш результат A/B теста статистически значимым. Введите количество посетителей и конверсий, чтобы получить p-значение, z-оценку, доверительный интервал и относительный прирост.
Калькулятор статистической значимости A/B тестов
Введите количество посетителей и конверсий для каждого варианта, чтобы проверить, является ли разница реальной или просто шумом.
Результаты
Статистически значимо
Разница вряд ли произойдёт просто так при выбранном вами уровне уверенности.
Chance this gap is just luck — lower is stronger.
How far apart the two rates are, in standard errors.
The likely range for the true difference.
Документация
A/B тестирование за одну минуту
Вы изменили кнопку, заголовок или процесс оформления. Версия A — это старая, версия B — новая. B выглядит лучше — она конвертировала 15% посетителей против 10% для A. Но может ли этот разрыв быть просто везением, как если бы подброшенная монета десять раз выпала семь орлов?
Этот калькулятор ответит на этот вопрос. Вы вводите, сколько человек видели каждую версию и сколько конвертировало, и он сообщает вам вероятность того, что измеренная вами разница реальна, а не просто случайный шум. Если эта вероятность достаточно высока, у вас есть победитель. Если нет, вы продолжаете тест.
Одно число, на которое нужно обратить внимание — это p-значение: вероятность увидеть разрыв по крайней мере такого размера если бы две версии были действительно идентичными. Маленькое p-значение (например, ниже 0.05) означает "это редко происходит просто так, поэтому разница, вероятно, реальна".
Как это использовать
- Посетители — сколько уникальных людей видели каждую версию.
- Конверсии — сколько из них выполнили интересующее вас действие (купили, зарегистрировались, нажали).
- Уровень уверенности — насколько вы должны быть уверены перед тем, как объявить победителя. 95% — это стандарт отрасли.
- Гипотеза — двусторонняя спрашивает "отличается ли B от A?" (безопаснее, по умолчанию); односторонняя спрашивает только "лучше ли B, чем A?" (более чувствительно, но вы должны решить направление перед запуском теста).
Результат обновляется в реальном времени, а входные данные сохраняются в URL страницы, так что вы можете добавить результат в закладки или поделиться им. Сброс очищает форму; Загрузить примеры данных заполняет рабочий пример.
Рабочий пример
| Посетители | Конверсии | Процент | |
|---|---|---|---|
| A (контрольная) | 1,000 | 100 | 10.0% |
| B (вариация) | 1,000 | 150 | 15.0% |
Это абсолютный прирост на 5 процентных пункта и +50% относительный прирост. Калькулятор возвращает p-значение около 0.0007 — ниже 0.05 — поэтому с уверенностью 95% результат статистически значим. Грубо говоря, если бы две версии были действительно идентичными, вы видели бы разрыв такого размера менее одного раза из тысячи тестов.
Математика (двухпропорциональный z-тест)
Под капотом это стандартный двухпропорциональный z-тест, тот же тест, который преподается во вводных курсах статистики и используется основными инструментами A/B тестирования.
-
Процент конверсии каждой группы:
где это конверсии и это посетители.
-
Объединённый процент, предполагая (для нулевой гипотезы), что обе группы имеют один истинный процент:
-
Стандартная ошибка разницы:
-
Z-оценка — насколько стандартных ошибок разрывают два процента:
-
P-значение получается из стандартного нормального распределения . Двусторонний: . Односторонний: .
-
Значимо когда , где (то есть 0.05 при 95%).
Калькулятор также сообщает доверительный интервал для разницы, вычисленный с непулированной стандартной ошибкой : вероятный диапазон для истинного разрыва. Если этот интервал исключает 0, результат значим.
При уверенности 95%, z за пределами ±1.96 (заштрихованные хвосты, 5% площади) считается значимым.
Честное прочтение результата
- Статистическая значимость не равна деловой значимости. Прирост на 0.1% может быть статистически реальным, но не стоит развёртывания.
- Не подглядывайте и не останавливайтесь рано. Проверка p-значения каждый день и остановка в момент, когда оно упадёт ниже 0.05, завышает вашу частоту ложных срабатываний. Решите размер выборки заранее и позвольте тесту завершиться.
- Соблюдайте эмпирическое правило размера выборки. Нормальная аппроксимация надежна, когда каждая группа имеет по крайней мере ~10 конверсий и ~10 неконверсий. С крошечными числами, рассматривайте результат только как направленный.
- Уверенность ≠ вероятность того, что B лучше. Уровень уверенности 95% означает, что процедура неверна не более чем в 5% случаев в долгосрочной перспективе — это частотное утверждение, а не "95% вероятность того, что B выиграет".
- Один тест, одна метрика. Тестирование многих метрик или вариантов одновременно увеличивает вероятность случайного результата; исправьте это (например, Bonferroni) или предварительно зарегистрируйте единственную метрику, которая решает тест.
Вычислите сами
Каждый фрагмент вычисляет двусторонний p-значение для рабочего примера выше.
1=2*(1-NORM.S.DIST(
2 (B2/A2 - B1/A1) /
3 SQRT(((B1+B2)/(A1+A2))*(1-(B1+B2)/(A1+A2))*(1/A1+1/A2)),
4 TRUE))
5' A1,B1 = control visitors, conversions ; A2,B2 = variation visitors, conversions
61from math import sqrt, erf
2
3def ab_pvalue(n1, x1, n2, x2, two_sided=True):
4 p1, p2 = x1 / n1, x2 / n2
5 p = (x1 + x2) / (n1 + n2)
6 se = sqrt(p * (1 - p) * (1 / n1 + 1 / n2))
7 z = (p2 - p1) / se
8 phi = lambda v: 0.5 * (1 + erf(v / sqrt(2))) # standard normal CDF
9 return 2 * (1 - phi(abs(z))) if two_sided else 1 - phi(z)
10
11print(ab_pvalue(1000, 100, 1000, 150)) # ~0.00072
121// erf via Abramowitz & Stegun 7.1.26
2function erf(x) {
3 const s = x < 0 ? -1 : 1;
4 x = Math.abs(x);
5 const t = 1 / (1 + 0.3275911 * x);
6 const y = 1 - ((((1.061405429 * t - 1.453152027) * t + 1.421413741) * t
7 - 0.284496736) * t + 0.254829592) * t * Math.exp(-x * x);
8 return s * y;
9}
10const phi = z => 0.5 * (1 + erf(z / Math.SQRT2));
11
12function abPValue(n1, x1, n2, x2, twoSided = true) {
13 const p1 = x1 / n1, p2 = x2 / n2, p = (x1 + x2) / (n1 + n2);
14 const se = Math.sqrt(p * (1 - p) * (1 / n1 + 1 / n2));
15 const z = (p2 - p1) / se;
16 return twoSided ? 2 * (1 - phi(Math.abs(z))) : 1 - phi(z);
17}
18console.log(abPValue(1000, 100, 1000, 150)); // ~0.00072
191public static double abPValue(int n1, int x1, int n2, int x2) {
2 double p1 = (double) x1 / n1, p2 = (double) x2 / n2;
3 double p = (double) (x1 + x2) / (n1 + n2);
4 double se = Math.sqrt(p * (1 - p) * (1.0 / n1 + 1.0 / n2));
5 double z = (p2 - p1) / se;
6 // Phi via the error function; erf approximated (A&S 7.1.26) separately.
7 double phi = 0.5 * (1 + erf(Math.abs(z) / Math.sqrt(2)));
8 return 2 * (1 - phi);
9}
101static double AbPValue(int n1, int x1, int n2, int x2) {
2 double p1 = (double)x1 / n1, p2 = (double)x2 / n2;
3 double p = (double)(x1 + x2) / (n1 + n2);
4 double se = Math.Sqrt(p * (1 - p) * (1.0 / n1 + 1.0 / n2));
5 double z = (p2 - p1) / se;
6 double phi = 0.5 * (1 + Erf(Math.Abs(z) / Math.Sqrt(2)));
7 return 2 * (1 - phi);
8}
91#include <cmath>
2
3double ab_pvalue(int n1, int x1, int n2, int x2) {
4 double p1 = (double)x1 / n1, p2 = (double)x2 / n2;
5 double p = (double)(x1 + x2) / (n1 + n2);
6 double se = std::sqrt(p * (1 - p) * (1.0 / n1 + 1.0 / n2));
7 double z = (p2 - p1) / se;
8 double phi = 0.5 * (1 + std::erf(std::fabs(z) / std::sqrt(2.0)));
9 return 2 * (1 - phi);
10}
111import "math"
2
3func abPValue(n1, x1, n2, x2 float64) float64 {
4 p1, p2 := x1/n1, x2/n2
5 p := (x1 + x2) / (n1 + n2)
6 se := math.Sqrt(p * (1 - p) * (1/n1 + 1/n2))
7 z := (p2 - p1) / se
8 phi := 0.5 * (1 + math.Erf(math.Abs(z)/math.Sqrt2))
9 return 2 * (1 - phi)
10}
111// erf from the `libm` crate; std has no erf.
2fn ab_pvalue(n1: f64, x1: f64, n2: f64, x2: f64) -> f64 {
3 let (p1, p2) = (x1 / n1, x2 / n2);
4 let p = (x1 + x2) / (n1 + n2);
5 let se = (p * (1.0 - p) * (1.0 / n1 + 1.0 / n2)).sqrt();
6 let z = (p2 - p1) / se;
7 let phi = 0.5 * (1.0 + libm::erf(z.abs() / 2f64.sqrt()));
8 2.0 * (1.0 - phi)
9}
101function ab_pvalue($n1, $x1, $n2, $x2) {
2 $p1 = $x1 / $n1; $p2 = $x2 / $n2;
3 $p = ($x1 + $x2) / ($n1 + $n2);
4 $se = sqrt($p * (1 - $p) * (1 / $n1 + 1 / $n2));
5 $z = ($p2 - $p1) / $se;
6 $phi = 0.5 * (1 + erf(abs($z) / sqrt(2)));
7 return 2 * (1 - $phi);
8}
91def ab_pvalue(n1, x1, n2, x2)
2 p1, p2 = x1.to_f / n1, x2.to_f / n2
3 p = (x1 + x2).to_f / (n1 + n2)
4 se = Math.sqrt(p * (1 - p) * (1.0 / n1 + 1.0 / n2))
5 z = (p2 - p1) / se
6 phi = 0.5 * (1 + Math.erf(z.abs / Math.sqrt(2)))
7 2 * (1 - phi)
8end
91# R has this built in: prop.test does the whole thing.
2prop.test(c(100, 150), c(1000, 1000), correct = FALSE)$p.value # ~0.00072
3FAQ
Какой размер выборки мне нужен? Достаточно, чтобы существенный прирост можно было обнаружить. В качестве минимума старайтесь иметь по крайней мере несколько сотен конверсий на каждый вариант; используйте специализированный калькулятор размера выборки с вашим базовым процентом и наименьшим приростом, который стоит обнаруживать.
Двусторонний или односторонний? Используйте двусторонний, если у вас нет чёткой, предварительно принятой причины заботиться только об улучшении. Односторонний удваивает вашу чувствительность, но запрещает реагировать на то, что B хуже.
Почему калькулятор показывает доверительный интервал? Он показывает вероятный диапазон истинной разницы, не просто ответ "да/нет". Широкий интервал, пересекающий 0, означает "недостаточно данных пока".
Это байесовский? Нет — это частотный z-тест, наиболее широко преподаваемый и используемый метод. Байесовские инструменты A/B тестирования сообщают "вероятность того, что B превосходит A" вместо p-значения; оба действительны, они отвечают немного на разные вопросы.