TGViewer
Продуктовый взгляд | Аналитика данных Продуктовый взгляд | Аналитика данных @prodanalysis · 2.3K subscribers
Post #84 3.3K
Ratio - метрики

Ratio-метрики вроде CTR, ARPU или среднего чека часто ломают привычный подход к A/B-тестам, потому что в них числитель и знаменатель зависимы: у пользователя с большими "views" обычно больше и "clicks", у крупного покупателя выше и "revenue", и "orders". Если применить стандартный t-test как к обычной метрике, дисперсия получается оценена неверно и итоговое p-value начинает врать. Delta-method как раз помогает аккуратно оценить дисперсию метрики вида num / denom с учётом ковариации между числителем и знаменателем и дальше уже работать с этой метрикой как с обычной: считать размер выборки, uplift, доверительные интервалы и значимость. Удобная реализация для данных на уровне пользователя:


def est_ratio_var(num, denom):
mean_num, mean_denom = np.mean(num), np.mean(denom)
var_num, var_denom = np.var(num), np.var(denom)

cov = np.cov(num, denom)[0, 1]

ratio_var = (
(var_num / mean_denom ** 2)
- (2 * (mean_num / mean_denom ** 3) * cov)
+ ((mean_num ** 2 / mean_denom ** 4) * var_denom)
)

return ratio_var


Дальше посмотрим на CTR, где по каждому "user_id" есть "clicks" и "views". Для расчёта размера выборки можно сначала оценить дисперсию CTR в текущих данных, задать относительный MDE и перевести его в "cohen_d", а затем воспользоваться стандартной функцией мощности, например так:


rel_mde = 0.1

var = est_ratio_var(df["clicks"], df["views"])
control_std = var**0.5

control_mean = df["clicks"].sum() / df["views"].sum()
test_mean = control_mean * (1 + rel_mde)

cohen_d = (test_mean - control_mean) / control_std

n1 = tt_ind_solve_power(
effect_size=cohen_d,
alpha=0.05,
power=0.8,
ratio=9,
alternative="two-sided"
)

print(
f"Размер выборки delta-method: "
f"n1 = {round(n1)}, "
f"n2 = {round(9 * n1)}"
)


Когда эксперимент уже прошёл, по тем же группам можно оценить статистическую значимость uplift CTR. Для этого считаем итоговые "clicks", "views" по группам, через delta-method получаем дисперсии и дальше строим нормальную статистику и p-value:


def delta_method(group_A, group_B):
ratio_A = group_A["clicks"].sum() / group_A["views"].sum()
ratio_B = group_B["clicks"].sum() / group_B["views"].sum()

var_A = est_ratio_var(group_A["clicks"], group_A["views"])
var_B = est_ratio_var(group_B["clicks"], group_B["views"])

uplift = ratio_B - ratio_A
se = np.sqrt(var_B / len(group_B) + var_A / len(group_A))

t = uplift / se
p_val = (1 - stats.norm.cdf(abs(t))) * 2

return p_val


Delta-method особенно удобен тем, что оставляет привычную для бизнеса картинку: "uplift", стандартная ошибка, доверительный интервал и p-value, но при этом корректно учитывает зависимость в ratio-метриках. Для надёжности в реальных задачах полезно иногда сверяться с бутстрепом или линеаризацией, но как базовый рабочий инструмент delta-method уже даёт аккуратные и довольно устойчивые оценки. Если формат такого кода и пояснений заходит, можно отдельно разобрать "линеаризацию" или "бутстреп" для тех же CTR и ARPU.

@ProdAnalysis
  • ❤ 26
  • 😁 3
More from @prodanalysis
  1. Sep 22, 2026Яндекс изменил отбор на аналитиков Раньше процесс состоял из технической секции, алгособес…
  2. Sep 18, 2026Хороший маркетинг обязан нравиться всем? Кейс Сидни Суини говорит, что нет Последние пару…
  3. Sep 16, 2026Если собираетесь на собеседование — держите полезную подборку Хотим поделиться каналом «Ги…
  4. Sep 14, 2026Что происходит с ML после того, как модель обучена Рекомендуем канал Data New Gold. Владим…
  5. Sep 14, 2026Товарищи, Поступашкам нужны контент мейкеры в основной канал по аналитике и другим дисципл…
  6. Sep 13, 2026Можно хорошо считать и всё равно не понимать, зачем нужна твоя работа В рамках открытой не…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →