Средний чек в A/B-тесте: как сделать bootstrap по пользователям
В эксперименте пользователей случайно разделили на контроль и тест, а в выгрузке оказались отдельные заказы. Один человек сделал покупку один раз, другой — десять. Если при оценке неопределённости выбирать заказы независимо, связь покупок одного человека исчезнет. Тысяча заказов не превращается автоматически в тысячу независимых участников.
Сначала соберём для каждого назначенного пользователя пару: сумма покупок и число заказов за одинаковое окно наблюдения. При повторной выборке будем брать целиком эту пару. Затем каждый раз заново вычислим средний чек группы. Такой способ сохраняет покупки одного человека вместе.
Определите, какой средний чек сравниваете
Средний чек группы равен общей сумме покупок, делённой на общее число заказов. Это отношение сумм, или ratio-метрика. Среднее индивидуальных чеков пользователей — другой показатель: в нём человек с одним заказом и человек с десятью имеют одинаковый вес.
В таблицу включаем всех назначенных участников, в том числе без заказов: у них сумма и количество равны нулю. Не делим их личную выручку на личное число заказов. Нулевые значения просто входят в групповые суммы. Общий знаменатель группы должен быть положительным, иначе средний чек не определён.
Заказы внутри пары должны иметь согласованные статусы и денежные правила. Если возвраты уменьшили выручку, проверьте, какие заказы остаются в знаменателе. Для сравнения вариантов одинаково задайте момент начала и длительность наблюдения участника.
Повторная выборка сохраняет пользователя
Bootstrap строит много новых выборок из наблюдаемого набора с возвращением: выбранный участник может встретиться несколько раз, другой — ни разу. В каждом повторе берём столько же пользователей, сколько было в группе. Для контроля и теста выборки строятся отдельно.
Если пользователь попался дважды, дважды учитываются и его сумма, и число заказов. Нельзя независимо выбирать массив денег и массив количества: тогда в одной паре окажутся показатели разных людей. Из каждого повтора получаем разницу средних чеков «тест минус контроль». По распределению этих разниц можно оценить неопределённость.
Ниже вычисляется процентильный интервал: от значения, ниже которого лежат 2.5% повторов, до значения, ниже которого лежат 97.5%. Это один конкретный вариант bootstrap-интервала; на маленькой выборке он служит демонстрацией механики. В рабочем анализе проверьте достаточность числа независимых участников и устойчивость оценки к редким крупным покупателям.
Запустите пример
Выполните файл стандартным Python. Восемь строк каждой группы соответствуют восьми пользователям. Деньги в примере указаны в рублях, а фиксированный seed позволяет повторить вычисление.
import random
from statistics import mean
# Each pair contains one assigned user's revenue and order count.
control = [(100,1),(100,1),(600,3),(80,1),(120,2),(200,2),(90,1),(0,0)]
treatment = [(110,1),(120,1),(620,3),(100,1),(150,2),(220,2),(100,1),(0,0)]
def aov(users):
orders = sum(row[1] for row in users)
if orders == 0:
raise ValueError("No orders: AOV is undefined")
return sum(row[0] for row in users) / orders
def quantile(sorted_values, p):
position = (len(sorted_values)-1)*p
lo = int(position)
hi = min(lo+1,len(sorted_values)-1)
return sorted_values[lo] + (position-lo)*(sorted_values[hi]-sorted_values[lo])
rng = random.Random(20261023)
effects = []
for _ in range(10000):
sample_a = rng.choices(control,k=len(control))
sample_b = rng.choices(treatment,k=len(treatment))
effects.append(aov(sample_b)-aov(sample_a))
effects.sort()
low,high = quantile(effects,0.025),quantile(effects,0.975)
effect = aov(treatment)-aov(control)
assert len(effects)==10000 and low < effect < high
assert abs(aov(control)-1290/11) < 1e-10
per_buyer_mean = mean(revenue/orders for revenue,orders in control if orders)
assert abs(per_buyer_mean-aov(control)) > 1
assert aov(control*2)==aov(control)
print("AOV control / treatment:",round(aov(control),2),round(aov(treatment),2))
print("difference:",round(effect,2))
print("95% percentile interval:",round(low,2),round(high,2))
print("mean user AOV:",round(per_buyer_mean,2))
Контрольный средний чек равен 1290/11, примерно 117.27 рубля. Среднее пользовательских чеков будет около 104.29: код отдельно проверяет, что это разные величины. Значения для теста, разницу и границы интервала он вычисляет из массива пользователей.
Функция останавливается, если повторная выборка случайно не содержит ни одного заказа. Для редких покупок такие повторы могут встречаться часто; молча удалять их и публиковать оставшийся интервал нельзя. Здесь оба набора достаточно плотные для показанного запуска, но для разреженного продукта сначала нужно пересмотреть объём наблюдений и способ оценки.
Как использовать интервал в решении
Если интервал включает ноль, наблюдаемые данные совместимы с эффектами разных направлений. Если весь интервал положителен, всё равно сопоставьте размер изменения с заранее выбранным бизнес-порогом и другими метриками. Точечный рост чека не отвечает на вопрос о выручке на назначенного пользователя: чек может расти одновременно с падением числа покупок.
В итоговом отчёте сохраните число участников, сумму покупок, количество заказов, формулу чека и единицу повторной выборки. Тогда расчёт можно восстановить и проверить при обновлении выгрузки. До статистической оценки проверьте, что распределение участников по вариантам соответствует плану эксперимента.
Платный курс «A/B тесты и анализ данных на Python» включает выбор продуктовых метрик, bootstrap, доверительные интервалы и интерпретацию эффекта. Вы сможете связать метод с дизайном эксперимента и подготовить вывод для продуктовой команды. Откройте программу курса, чтобы отработать полный путь от гипотезы до анализа результата.
