Как получить распределение конверсии в A/B-тесте для оценки неопределённости?

Ответ

Чтобы оценить неопределённость в оценке конверсии (например, доля кликнувших) и разницы между группами, мы используем либо аналитические распределения, либо бутстреп. Это позволяет строить доверительные интервалы и визуализировать overlap.

1. Бета-распределение (Аналитический подход) Если в группе было k конверсий из n пользователей, то апостериорное распределение конверсии p (при uniform prior) описывается Бета-распределением: Beta(α = k + 1, β = n - k + 1). Это стандартный байесовский подход.

import numpy as np
from scipy.stats import beta
import matplotlib.pyplot as plt

# Данные теста: группа A
conversions_A, visitors_A = 120, 1000
conversions_B, visitors_B = 150, 1000

# Параметры бета-распределений
alpha_A, beta_A = conversions_A + 1, visitors_A - conversions_A + 1
alpha_B, beta_B = conversions_B + 1, visitors_B - conversions_B + 1

# Генерация выборок из распределений
dist_A = beta(alpha_A, beta_A)
dist_B = beta(alpha_B, beta_B)
samples_A = dist_A.rvs(10000)
samples_B = dist_B.rvs(10000)

# Разница в конверсии
diff_samples = samples_B - samples_A
prob_B_better = (diff_samples > 0).mean()
print(f"Вероятность, что группа B лучше: {prob_B_better:.2%}")

# Визуализация
plt.hist(samples_A, bins=50, alpha=0.5, label='Group A', density=True)
plt.hist(samples_B, bins=50, alpha=0.5, label='Group B', density=True)
plt.legend()
plt.xlabel('Conversion Rate')
plt.show()

2. Бутстреп (Непараметрический подход) Полезен, когда предположения аналитической модели сомнительны, или для более сложных метрик (например, средний чек).

def bootstrap_mean(data, n_bootstrap=10000):
    """data - бинарный массив (0/1)."""
    boot_means = []
    n = len(data)
    for _ in range(n_bootstrap):
        sample = np.random.choice(data, size=n, replace=True)
        boot_means.append(sample.mean())
    return np.array(boot_means)

# Пример с реальными данными (бинарные клики)
clicks_A = np.array([1,0,0,1, ...])  # массив длины visitors_A
boot_means_A = bootstrap_mean(clicks_A)
# 95% доверительный интервал через процентили
ci_lower, ci_upper = np.percentile(boot_means_A, [2.5, 97.5])

Выбор метода:

  • Бета-распределение — быстрее, элегантнее, стандарт для бинарных конверсий.
  • Бутстреп — более универсален, не делает предположений о форме распределения, но требует больше вычислений.

Ответ 18+ 🔞

Давай разберём эту тему, а то я вижу, у многих тут в головах каша, как будто они не статистику изучали, а хуй с горой.

Вот представь: у тебя есть две версии сайта. На одной конверсия 12%, на другой 15%. И ты такой сидишь и думаешь: «Ёпта, это реальный прирост или просто так, случайно, повезло?» Вот чтобы не гадать на кофейной гуще, как последний распиздяй, есть два рабочих подхода. Один — красивый и математический, второй — тупой, как валенок, но безотказный.

1. Бета-распределение (Красиво и по науке) Если в группе А было k конверсий из n юзеров, то наше представление о её истинной конверсии p — это Бета-распределение: Beta(α = k + 1, β = n - k + 1). Это не я придумал, это байесовская статистика, ей-богу. Берём, генерируем из этих распределений кучу возможных значений конверсии и смотрим, насколько они накладываются друг на друга.

import numpy as np
from scipy.stats import beta
import matplotlib.pyplot as plt

# Данные теста
conversions_A, visitors_A = 120, 1000
conversions_B, visitors_B = 150, 1000

# Параметры для наших бет
alpha_A, beta_A = conversions_A + 1, visitors_A - conversions_A + 1
alpha_B, beta_B = conversions_B + 1, visitors_B - conversions_B + 1

# Генерируем возможные сценарии
dist_A = beta(alpha_A, beta_A)
dist_B = beta(alpha_B, beta_B)
samples_A = dist_A.rvs(10000) # 10 тысяч возможных конверсий для А
samples_B = dist_B.rvs(10000) # 10 тысяч возможных конверсий для Б

# Считаем разницу
diff_samples = samples_B - samples_A
prob_B_better = (diff_samples > 0).mean()
print(f"Вероятность, что группа B лучше: {prob_B_better:.2%}")

# Картинку для начальства
plt.hist(samples_A, bins=50, alpha=0.5, label='Group A', density=True)
plt.hist(samples_B, bins=50, alpha=0.5, label='Group B', density=True)
plt.legend()
plt.xlabel('Conversion Rate')
plt.show()

Смотришь на вероятность prob_B_better. Если она под 95% — можно уже расслабиться и начинать бздеть от радости. Если около 50% — это полная хуйня, разницы никакой.

2. Бутстреп (Тупой, но мощный) А что, если метрика у тебя не конверсия, а, например, средний чек? Или там распределение какое-то пиздопроебибное, не похожее ни на что нормальное? Вот тут на сцену выходит бутстреп. Суть проще пареной репы: берём наши сырые данные и начинаем из них с возвращением тырить куски, строить новые выборки и считать на них нашу метрику. Повторяем это овердохуища раз. Получаем эмпирическое распределение — вот тебе и неопределённость.

def bootstrap_mean(data, n_bootstrap=10000):
    """data - бинарный массив (0/1)."""
    boot_means = []
    n = len(data)
    for _ in range(n_bootstrap):
        sample = np.random.choice(data, size=n, replace=True) # Вот он, главный трюк!
        boot_means.append(sample.mean())
    return np.array(boot_means)

# Допустим, у тебя массив кликов
clicks_A = np.array([1,0,0,1, ...])  # 1 - конверсия, 0 - мимо
boot_means_A = bootstrap_mean(clicks_A)
# 95% доверительный интервал через процентили
ci_lower, ci_upper = np.percentile(boot_means_A, [2.5, 97.5])

Так что же выбрать, ёпта?

  • Бета-распределение — это как швейцарские часы. Быстро, элегантно, специально заточено под бинарные исходы (конверсия/не конверсия). Если условия подходят — юзай его и не выёбывайся.
  • Бутстреп — это как кувалда. Универсальная, грубая, но прошибёт любую, даже самую хитрожопую метрику. Вычислительно тяжёлая, зато никаких допущений о нормальности. Когда сомневаешься — бутстреп в руки.

Вот и вся магия. Главное — не делай выводы, глядя только на разницу в процентах. Посмотри на overlap, на вероятность, на интервалы. А то будет тебе хиросима, а не успешный A/B-тест.