A/B-тестытестовое заданиеаналитик данныхстатистикаz-тестсобеседованиеPython

Разбор тестового на A/B-тест: как решать

2026-07-05 12 мин

Коротко (BLUF). Тестовое на A/B почти всегда сводится к одному: даны два варианта с числом конверсий и числом показов — реши, катить ли изменение в прод. Правильный ответ — не «p-value < 0.05, релизим», а последовательность: посчитать конверсии, применить z-тест для двух пропорций, построить доверительный интервал для разницы, отдельно проверить практическую значимость (окупает ли эффект затраты) и явно написать вывод с оговорками. Ниже — полное решение реального по формату кейса с кодом, разбор того, как это подать на собеседовании, и типичные ошибки, на которых валятся кандидаты.

Формат задания я взял из открытых сборников тестовых для аналитиков (например, nodatanogrowth.com/test-tasks — там 189+ реальных заданий Авито, Сбера, DatsTeam и других). Числа в разборе — приближённый к реальным кейсам пример, где важна не сама история, а метод: он одинаков для баннера, кнопки, письма и лендинга.

Задание (как оно выглядит в реальности)

Формулировка из тестового обычно короткая:

Продуктовая команда протестировала новый баннер на главной. Группа A видела старый баннер, группа B — новый. Цель баннера — клик по кнопке «Оформить подписку». Данные за 2 недели теста:
ГруппаПоказы (пользователи)Клики по CTAКонверсия
A (контроль)10 000430?
B (новый баннер)10 000510?
Вопрос: какой баннер лучше и стоит ли раскатывать новый на всех пользователей? Приложите расчёты и обоснуйте вывод.

Ровно такой формат — «вариант А: X кликов из N, вариант Б: Y из M, выбери лучший» — встречается в тестовых Авито, Ozon, банков. Иногда вместо баннера письмо или экран онбординга, иногда дают csv на 200 тысяч строк, но математика внутри одна.

Шаг 1. Считаем конверсии и формулируем гипотезы

Конверсия — доля кликнувших от увидевших:

$$p_A = \frac{430}{10000} = 0.0430 = 4.30\%$$

$$p_B = \frac{510}{10000} = 0.0510 = 5.10\%$$

Абсолютная разница — $5.10\% - 4.30\% = 0.80$ процентных пункта. Относительный прирост (lift) — $\frac{0.0510 - 0.0430}{0.0430} = 18.6\%$. Это цифра, которую любит бизнес: «новый баннер поднимает конверсию в клик на 18–19%».

Но 18.6% сами по себе не значат ничего. Разница могла возникнуть случайно — просто в группу B попали пользователи чуть активнее. Задача статистики — отделить сигнал от шума. Формулируем гипотезы:

Тест двусторонний: мы допускаем, что новый баннер может оказаться и хуже. Уровень значимости берём стандартный $\alpha = 0.05$.

Шаг 2. Z-тест для двух пропорций — формула

Метрика бинарная (кликнул / не кликнул), выборки большие — значит z-тест для пропорций. Сначала считаем объединённую (pooled) конверсию — как если бы обе группы были одной:

$$\hat{p} = \frac{x_A + x_B}{n_A + n_B} = \frac{430 + 510}{10000 + 10000} = \frac{940}{20000} = 0.0470$$

Стандартная ошибка разницы под нулевой гипотезой:

$$SE = \sqrt{\hat{p}(1-\hat{p})\left(\frac{1}{n_A} + \frac{1}{n_B}\right)} = \sqrt{0.047 \cdot 0.953 \cdot \left(\frac{1}{10000} + \frac{1}{10000}\right)} = 0.002993$$

Z-статистика — насколько наблюдаемая разница далека от нуля в единицах стандартной ошибки:

$$z = \frac{p_B - p_A}{SE} = \frac{0.0510 - 0.0430}{0.002993} = 2.67$$

Для двустороннего теста при $\alpha = 0.05$ критическое значение — $z_{крит} = 1.96$. Наше $z = 2.67 > 1.96$, значит p-value < 0.05.

Точное p-value для $z = 2.67$ — примерно 0.0075. Это вероятность увидеть такую (или большую) разницу, если на самом деле баннеры одинаковы. 0.75% — маловероятно, отвергаем $H_0$.

Шаг 3. Тот же расчёт кодом

На собеседовании ценят, что вы не считаете руками, а пишете воспроизводимый код. Вот полное решение:

import numpy as np
from statsmodels.stats.proportion import proportions_ztest, proportion_confint

# Данные теста
clicks = np.array([430, 510])       # A, B
views  = np.array([10000, 10000])

p_a, p_b = clicks / views
print(f"Конверсия A: {p_a:.4f} ({p_a*100:.2f}%)")
print(f"Конверсия B: {p_b:.4f} ({p_b*100:.2f}%)")
print(f"Относительный lift: {(p_b - p_a) / p_a * 100:.1f}%")

# Z-тест для двух пропорций (two-sided, pooled)
z_stat, p_value = proportions_ztest(count=clicks, nobs=views)
print(f"z = {z_stat:.4f}, p-value = {p_value:.4f}")

Вывод:

Конверсия A: 0.0430 (4.30%)
Конверсия B: 0.0510 (5.10%)
Относительный lift: 18.6%
z = 2.6729, p-value = 0.0075
statsmodels по умолчанию использует pooled-оценку — ровно ту формулу, что мы вывели руками. Если под рукой только scipy, можно собрать z-тест вручную или взять scipy.stats.chi2_contingency на таблице 2×2 — для двусторонней проверки двух пропорций хи-квадрат даёт эквивалентный результат ($\chi^2 = z^2$). Потренироваться на подобных задачах с автопроверкой можно в /python-sandbox — там 538 задач, включая A/B и scipy.

Шаг 4. Доверительный интервал — вот что действительно убеждает

P-value отвечает «различие есть?», но не отвечает «насколько большое?». Для этого — доверительный интервал разницы конверсий. Здесь считаем стандартную ошибку без объединения (по каждой группе своя):

$$SE_{ДИ} = \sqrt{\frac{p_A(1-p_A)}{n_A} + \frac{p_B(1-p_B)}{n_B}}$$

95%-й интервал для разницы $p_B - p_A$:

$$(p_B - p_A) \pm 1.96 \cdot SE_{ДИ} = 0.008 \pm 1.96 \cdot 0.003$$

Получаем [0.21%, 1.39%] в абсолютных процентных пунктах.

# ДИ для разницы пропорций
se_diff = np.sqrt(p_a*(1-p_a)/views[0] + p_b*(1-p_b)/views[1])
diff = p_b - p_a
lo, hi = diff - 1.96*se_diff, diff + 1.96*se_diff
print(f"95% ДИ разницы: [{lo*100:.2f}%, {hi*100:.2f}%]")
# 95% ДИ разницы: [0.21%, 1.39%]

Ключевая проверка: интервал не пересекает ноль. Оба конца положительные — значит с 95% уверенностью новый баннер лучше минимум на 0.21 п.п. Это тот же вывод, что и p-value < 0.05, но в форме, которую понимает продакт: «прирост от +0.2 до +1.4 процентных пункта конверсии».

Именно доверительный интервал стоит выносить в вывод. Если написать «p = 0.0075», продакт спросит «и что?». Если написать «конверсия вырастет на 0.2–1.4 п.п., базовый сценарий +0.8 п.п.» — разговор сразу переходит к деньгам.

Шаг 5. Практическая значимость — обязательный второй фильтр

Статистическая значимость говорит «эффект реален». Практическая — «эффект стоит того, чтобы его катить». Это разные вещи, и путать их — классический провал.

Прикинем деньги. Пусть на баннер в месяц приходит 200 000 показов, а клик по CTA в среднем даёт 900 ₽ выручки (через воронку до подписки). Прирост 0.8 п.п. — это дополнительно $200000 \cdot 0.008 = 1600$ кликов в месяц, или ~1.44 млн ₽. Даже по нижней границе интервала (+0.21 п.п.) — ~378 тысяч ₽/мес. Разработка и поддержка нового баннера явно дешевле. Практическая значимость есть.

Здесь же полезно вспомнить про MDE — минимальный эффект, который тест вообще способен поймать. Размер выборки для z-теста считают так:

$$n = \frac{2(z_{\alpha/2} + z_{\beta})^2 \cdot p(1-p)}{MDE^2}$$

При базовой конверсии 4.3%, $\alpha = 0.05$ ($z_{\alpha/2} = 1.96$) и мощности 80% ($z_\beta = 0.84$), чтобы надёжно ловить эффект в 0.8 п.п., нужно ~10 100 пользователей на группу. У нас 10 000 — на грани, но эффект оказался достаточно крупным, чтобы «пробить» его. Для эффекта 0.5 п.п. понадобилось бы уже ~25 800 на группу. Это хорошо упомянуть: показывает, что вы думаете о дизайне теста, а не только о постобработке.

Шаг 6. Вывод — «релизить или нет»

Собираем всё в короткий, честный вывод — именно такой ждут в тестовом:

Новый баннер (B) повышает конверсию в клик с 4.30% до 5.10% — относительный прирост 18.6%. Различие статистически значимо: z = 2.67, p = 0.0075 < 0.05, нулевая гипотеза о равенстве конверсий отвергается. 95% доверительный интервал разницы — [0.21%, 1.39%] п.п., ноль не включён. Практическая значимость положительная: при текущем трафике прирост даёт ориентировочно 1.4 млн ₽/мес дополнительной выручки даже с учётом нижней границы интервала.

>

Рекомендация: раскатывать новый баннер на 100% с последующим мониторингом. Оговорки: тест шёл 2 недели — стоит убедиться, что не попали на аномальную неделю (акция, праздник); проверить эффект в разрезе платформ (web/mobile) и новых/старых пользователей на случай гетерогенности; следить за метриками ниже по воронке — рост кликов не должен «сломать» итоговую конверсию в оплату.

Эти оговорки — не вода. Они показывают зрелость: вы знаете, что «стат-значимый рост клика» может маскировать проблему дальше по воронке.

Контрпример: когда «красивый lift» не значим

Чтобы почувствовать разницу, замените числа: A = 340/8000 (4.25%), B = 391/8000 (4.89%). Относительный прирост 15% — на вид тоже победа. Но:

z_stat, p_value = proportions_ztest([340, 391], [8000, 8000])
print(f"z = {z_stat:.3f}, p = {p_value:.4f}")
# z = 1.931, p = 0.0535

$z = 1.93$, $p = 0.0535 > 0.05$. Доверительный интервал разницы — [−0.01%, 1.29%], он захватывает ноль. Вывод противоположный: несмотря на симпатичный lift 15%, различие статистически не подтверждено. Здесь правильный ответ — «не релизить по текущим данным, продлить тест до нужного размера выборки». Кандидаты, которые смотрят только на процент разницы и не считают тест, на таком кейсе релизят пустышку.

Как подать это на собеседовании

Даже с идеальными числами можно провалиться на защите. Что помогает:

Проговаривайте гипотезы вслух. Начните не с кода, а с «нулевая — конверсии равны, альтернативная — различаются, беру двусторонний тест и alpha 0.05». Это сразу отделяет вас от тех, кто заучил proportions_ztest.

Объясните выбор критерия. «Метрика бинарная, выборки большие — z-тест для пропорций. Если бы считали среднюю выручку на пользователя, взял бы t-тест или бутстрап, потому что распределение денег скошенное». Такая фраза показывает, что вы понимаете, а не подставляете.

Ведите к деньгам, а не к p-value. Финальный слайд/абзац — про бизнес: сколько кликов и рублей приносит эффект, окупается ли. P-value — техническая деталь по пути.

Назовите риски сами. Peeking (подглядывание в результаты до конца теста), сезонность, эффект новизны, несбалансированные группы, множественные сравнения. Достаточно перечислить 2–3 релевантных — интервьюер поймёт, что вы работали с реальными тестами.

Не бойтесь сказать «данных мало». В контрпримере выше честный ответ «продлить тест» ценнее уверенного «релизим». Гейм — не в том, чтобы всегда быть за релиз.

Типичные ошибки кандидатов

Разобрать больше форматов тестовых заданий и потренировать SQL-часть (в тестовых A/B почти всегда идёт в паре с выгрузкой данных) можно в /tasks и /sql-sandbox — 545 задач с автопроверкой. Продуктовую логику экспериментов — в разделе /cases.

Частые вопросы

Какой критерий выбрать для A/B-теста на конверсию?

Для бинарной метрики (кликнул / не кликнул, купил / не купил) и больших выборок — z-тест для двух пропорций. Он эквивалентен хи-квадрату на таблице 2×2 ($\chi^2 = z^2$). Для непрерывных метрик со скошенным распределением (выручка на пользователя, время на сайте) z-тест не подходит — там t-тест, а лучше бутстрап или тест Манна–Уитни. Главное на собеседовании — не просто назвать критерий, а объяснить, почему он подходит к типу метрики.

Что важнее — p-value или доверительный интервал?

Доверительный интервал информативнее. P-value отвечает только «различие есть или нет» одним числом, а интервал показывает и факт значимости (не пересекает ли ноль), и величину эффекта, и его неопределённость. В выводе для бизнеса всегда приводите интервал: «прирост конверсии от +0.2 до +1.4 п.п.» звучит убедительнее, чем «p = 0.0075». Опытные аналитики докладывают результат именно интервалом, а p-value оставляют как техническую сноску.

Чем статистическая значимость отличается от практической?

Статистическая значимость (p < 0.05, интервал не задевает ноль) говорит, что эффект реален и не случаен. Практическая значимость — что эффект достаточно велик, чтобы окупить внедрение. На больших выборках можно поймать статистически значимый прирост в 0.01 п.п., который не стоит ни рубля разработки. И наоборот: крупный эффект на маленькой выборке может быть незначим. В тестовом нужно проверить обе значимости и перевести эффект в деньги или целевую метрику.

Тест дал p = 0.06 — это провал эксперимента?

Нет, это «недостаточно данных для вывода», а не «эффекта нет». Правильная реакция — посмотреть доверительный интервал (насколько он широк и где расположен) и оценить, добрали ли вы нужный размер выборки под MDE. Часто ответ — продлить тест до запланированного объёма. Категорически нельзя останавливаться ровно в момент, когда p впервые опустился ниже 0.05: это peeking, он раздувает долю ложноположительных результатов. Срок и размер выборки фиксируются до старта.


Метод из этого разбора — конверсии → z-тест → доверительный интервал → практическая значимость → вывод с оговорками — работает для любого A/B-тестового: баннер, письмо, кнопка, лендинг, экран онбординга. Отработайте его на 2–3 примерах с разными числами (включая заведомо незначимый), и на реальном собеседовании останется только подставить данные. Больше разборов вопросов с собеседований — в /interviews и на странице /sobesedovanie-analitika-dannyh.

Разбирай реальные кейсы
618 продуктовых кейсов и 545 SQL-задач с автопроверкой. Первые — без регистрации.
Открыть кейсы →