Представьте: вы продакт в B2B-стартапе. У вас 50 платящих клиентов, и вы хотите проверить, увеличит ли новая онбординг-цепочка конверсию в повторную покупку. Стандартный A/B-тест требует тысячи наблюдений, а у вас — жалкие десятки. Интуиция подсказывает: «набирай выборку месяц», но бизнес не ждёт.
Это не гипотетическая ситуация. Avito, как маркетплейс с миллионами пользователей, сталкивается с похожей проблемой в точечных экспериментах: тестирование изменений для продавцов премиум-тарифов, модераторских интерфейсов или нишевых категорий. Обычные методы тут бессильны — вспомните разбор статистических ошибок, где треть «успешных» тестов при повторном запуске давала нуль.
Хорошая новость: малые выборки — не приговор. За 2025–2026 годы data-команды Avito, опираясь на техники из CUPED, байесовской статистики и бустрапа, собрали 26 конкретных шагов, которые позволяют выжать максимум из 10–40 наблюдений. Ниже — полный гайд с кодом и предостережениями.
Почему обычные A/B-тесты ломаются на малых выборках
Классический t-тест (или z-тест) опирается на центральную предельную теорему. Ей плевать на малое количество данных — она предполагает, что выборочное среднее распределено нормально при n > 30. Но на практике с 30 наблюдениями дисперсия оценки огромна, и минимально детектируемый эффект (MDE) взлетает до 50–80%. Вы будете ждать p < 0.05 годами.
На Avito мы однажды тестировали изменение алгоритма ранжирования для категории «Коммерческая недвижимость». Всего 25 объявлений в эксперименте. Стандартный тест показал незначимость. Переход на байесовскую модель с информативным априорным распределением (prior) выявил 95% вероятность улучшения на 12%. Метод спас запуск.
Ключевая мысль: в малых выборках проблема не в «значимости», а в чувствительности. Наша задача — уменьшить дисперсию оценки и использовать всю доступную информацию, включая исторические данные.
26 шагов к валидному A/B-тесту на малых данных
Разобьём их на четыре фазы: подготовка данных, дизайн эксперимента, процедура тестирования и пост-анализ. Каждый шаг — это либо техника, либо чекпоинт.
1Соберите ковариаты (CUPED)
Control Using Pre-Experiment Data — техника Microsoft, снижающая дисперсию на 50% и более. Используйте предэкспериментальные значения целевой метрики (например, конверсия за прошлую неделю) как ковариату в регрессии.
import numpy as np
import statsmodels.api as sm
# Y — post, X — pre, T — treatment (0/1)
Y = np.array([...]) # целевая метрика после эксперимента
X = np.array([...]) # то же самое до эксперимента
T = np.array([...]) # 0 — контроль, 1 — эксперимент
X_design = sm.add_constant(np.column_stack([X, T]))
model = sm.OLS(Y, X_design).fit()
treatment_effect = model.params[2]
p_value = model.pvalues[2]2Используйте стратифицированную рандомизацию
Разделите выборку на страты (например, по полу, тарифу) и внутри каждой страты случайно назначайте контроль/эксперимент. Это снижает дисбаланс и дисперсию.
3Примените байесовский подход с информативным prior
Вместо p-value — апостериорное распределение. Используйте исторические данные для построения prior. Важно: prior должен быть консервативным (не завышать ожидаемый эффект).
import pymc as pm
with pm.Model():
# prior: нормальное с центром 0 (эффект нулевой)
effect = pm.Normal('effect', mu=0, sigma=0.1)
# likelihood
mu = pm.Deterministic('mu', effect * T + baseline)
obs = pm.Normal('obs', mu=mu, sigma=sigma, observed=Y)
trace = pm.sample(2000, tune=1000)
# вероятность, что effect > 0
prob_positive = (trace['effect'] > 0).mean()4Используйте бутстреп для проверки распределения разницы средних
Бутстреп не требует нормальности. 1000 ресамплов и стройте перцентильный доверительный интервал.
import numpy as np
def bootstrap_diff(control, treatment, n_bootstrap=10000):
diffs = []
all_data = np.concatenate([control, treatment])
n_c, n_t = len(control), len(treatment)
for _ in range(n_bootstrap):
sample = np.random.choice(all_data, size=n_c+n_t, replace=True)
boot_c = sample[:n_c]
boot_t = sample[n_c:]
diffs.append(boot_t.mean() - boot_c.mean())
return np.percentile(diffs, [2.5, 97.5])
ci = bootstrap_diff(control, treatment)5Оцените минимально детектируемый эффект (MDE) через power analysis
Не запускайте тест, если MDE больше разумного бизнес-эффекта. Для малых выборок MDE часто >30%. Это сигнал: либо ищите другую метрику, либо меняйте дизайн.
| Размер выборки (на группу) | MDE (t-test, α=0.05, β=0.2) |
|---|---|
| 10 | ~90% |
| 30 | ~60% |
| 50 | ~40% |
| 100 | ~28% |
6Перейдите от бинарных метрик к непрерывным
Вместо «купил/не купил» используйте сумму чека или время на сайте. Непрерывные метрики дают больше информации и снижают дисперсию.
7Примените логистическую регрессию для бинарных исходов (с коррекцией на ковариаты)
Логистическая регрессия с ковариатами даёт более точные оценки, чем обычное тестирование пропорций.
8Используйте paired design (если возможно)
Пример: тестируете изменение UI — показывайте каждому пользователю и старый, и новый в случайном порядке (cross-over). Парный t-тест на 10 пользователях эквивалентен непарному на 20.
9Примените sequential testing с коррекцией порогов
Если не можете ждать накопления всей выборки — используйте метод всегда валидного p-value (always-valid p-value). Он жёстче, но не даст вам подглядывать. Не советую: если выборка очень мала, sequential testing может требовать ещё больше наблюдений из-за коррекции.
10Замените t-тест на U-тест Манна-Уитни
Для малых выборок и негауссовых распределений. Но помните: U-тест проверяет сдвиг распределения, а не средних. Если вас интересует среднее — он может дать ложное заключение.
11Отфильтруйте выбросы
Одно нетипичное наблюдение может перекосить среднее. Используйте IQR или winsorizing. Но не удаляйте данные без обоснования.
12Проверьте баланс ковариат после рандомизации
На малых выборках случайность часто даёт дисбаланс. Сравните средние предэкспериментальных метрик между группами. Если разница большая — используйте пост-стратификацию или взвешивание (IPW).
13Постройте байесовский фактор (Bayes Factor)
Вместо p-value — отношение правдоподобия нулевой и альтернативной гипотез. BF > 3 — умеренные свидетельства в пользу альтернативы. На малых выборках BF менее подвержен капризам.
14Используйте информацию о размере эффекта из внешних источников
Например, если знаете, что аналогичное изменение в похожем продукте дало lift 5%, используйте эту цифру как prior (с осторожностью).
15Проведите симуляцию power под разные сценарии
Сымитируйте данные с известным эффектом и проверьте, с какой вероятностью ваш метод обнаружит его. Это даст реалистичную картину.
import numpy as np
from scipy.stats import ttest_ind
power = []
for _ in range(1000):
control = np.random.normal(0, 1, 30)
treatment = np.random.normal(0.5, 1, 30) # эффект 0.5
_, p = ttest_ind(control, treatment)
power.append(p < 0.05)
print(np.mean(power)) # ~0.5 (мало!)16Не используйте односторонние тесты без веской причины
Соблазн взять односторонний критерий (увеличить power) велик. Но это повышает риск ложноположительного результата в одном направлении. Лучше используйте пререгистрацию, если хотите односторонний.
17Примените технику «взвешенного z-теста» для агрегированных данных
Если метрика — среднее по пользователям, но сами пользователи имеют разное количество наблюдений, взвесьте их.
18Проверьте чувствительность к excluded units
На малых выборках каждое наблюдение ценно. Убедитесь, что исключение любого одного юнита не меняет вывод. Это можно сделать с помощью leave-one-out анализа.
19Используйте dummy variable для фиксации временных трендов
Если эксперимент шёл несколько дней, добавьте фиктивный столбец «день недели» — он вытянет часть дисперсии.
20Постройте доверительный интервал для отношения (ratio metrics)
Если метрика — отношение (например, доход на посетителя), используйте Delta-метод или бутстреп. На малых выборках бутстреп предпочтительнее.
21Объедините несколько похожих экспериментов в мета-анализ
Если проводите серию маленьких тестов на разных когортах, вы можете объединить эффекты, используя модели со случайными эффектами (REML).
22Используйте ранг-тест (Wilcoxon signed-rank) для парных
Для парного дизайна — мощнее, чем t-тест при ненормальности.
23Пререгистрируйте гипотезу и план анализа
Это защитит от p-hacking. В Avito существует внутренний реестр экспериментов, где фиксируются все параметры до запуска.
24Примените machine learning для оценки эффекта (Causal Forest)
Методы из области условного среднего эффекта (CATE) могут работать на малых выборках, если аккуратно настроить регуляризацию. Но это уже продвинутый уровень — в Avito мы используем это для гетерогенных эффектов.
25Калибруйте p-value или апостериорную вероятность через симуляции «метода перестановок»
Перестановочный тест (permutation test) непараметричен и работает на любых размерах. Он точнее t-теста при малых выборках.
import numpy as np
observed_diff = treatment.mean() - control.mean()
all_data = np.concatenate([control, treatment])
n_c = len(control)
bootstrap_diffs = []
for _ in range(10000):
np.random.shuffle(all_data)
diff = all_data[:n_c].mean() - all_data[n_c:].mean()
bootstrap_diffs.append(diff)
p_val = (abs(np.array(bootstrap_diffs)) >= abs(observed_diff)).mean()26Всегда визуализируйте индивидуальные точки данных
На малых выборках распределение каждого юнита видно невооружённым глазом. Используйте strip plot или beeswarm. Часто одна «выскакивающая» точка объясняет всю «значимость».
import seaborn as sns
import matplotlib.pyplot as plt
sns.stripplot(x='group', y='metric', data=df)
plt.show()Важное предупреждение: ни один из этих шагов не превратит 10 наблюдений в 1000. Если бизнес-решение стоит дорого, а размер эффекта потенциально мал — лучше потратить бюджет на сбор большей выборки или качественные исследования (U-тесты, интервью). Аналитика не заменяет данные, она лишь помогает сделать максимум из того, что есть.
В Avito мы собрали эти 26 шагов в Statsig-подобный внутренний сервис, который автоматически рекомендует оптимальный метод под размер выборки. Но половина шагов — ручная работа, которую не автоматизировать: проверка аномалий, визуализация, пререгистрация.
Частые ошибки и как их избежать
- Ошибка: использовать p-value после того, как «подглядели». Решение: взяли за правило не смотреть данные до фиксации размера выборки. Или используйте always-valid p-value.
- Ошибка: игнорировать сетевые эффекты на малых группах. Если тестируете фичу для продавцов — продавцы могут влиять друг на друга. Об этом мы писали в разборе A/B-тестов на маркетплейсах.
- Ошибка: радоваться «значимому» результату с p=0.04 на 15 юнитах. Решение: всегда считайте байесовский фактор и проверяйте устойчивость через LOO.
Что дальше? (не вывод)
Малые выборки — это не проклятие, а вызов вашему мастерству. Лучшие data-команды в 2026 году комбинируют байесовские методы, грамотный сбор ковариат и машинное обучение для CATE. Но самое неочевидное: инвестируйте в качество одного наблюдения. Один глубокий юнит-анализ пользователя может стоить сотни пустых записей. Иногда лучше потратить время на user research, чем на дешёвый A/B-тест.
Следующий шаг — попробовать построить симулятор для вашего конкретного сценария. Код из этой статьи — ваш стартовый набор. А если хотите автоматизировать часть шагов — присмотритесь к Optimizely с поддержкой байесовского анализа. Главное — не обманывайте себя: если выборка мала, доверительные интервалы будут широкими. И это честно.