Анализ экспериментов с Ratio-метриками

Проводя эксперименты, обычно, мы используем базовые методы: t-test, z-test, bootstrap (если данных не так много). Это для одних метрик, но, предположим, мы работаем с метриками-отношения (представляют из себя деление суммы случайной величины X на сумму случайной величины Y).

В этом случае в группе A и группе B у нас нет дисперсии. В самом деле, рассмотрим средний чек - метрика отношения (сумма GMV / количество заказов), CTR (сумма кликов / количество показов). Получаем некоторую метрику Za и Zb для которой мы знаем значение, но дисперсии метрики не знаем.

Отсюда выход: а) провести бутстрап, чтобы найти распределение этой метрики и узнать распределение статистики б) бакетировать (меньше по ресурсам, чем бутстрап). в) применять другие методы (например, линеаризацию или дельта-метод). г*) что-то другое...

БутстрапДля оценки распределения метрики можем использовать выборочные значения в группе A и группе B и найти разницу, таким образом, мы получим «разницу средних». Из минусов: MDE не рассчитать (при дизайне эксперимента), очень долгий расчет при больших выборках, ресурсов может не хватить.

Расчет для конкретной выборки, можно разницу средних потом посчитать `def bootstrap_ratio(data, nominator, denominator, group_column, group_value, n_iter=10000):

group_data = data[data[group_column] == group_value]

boot_ratios = [] for _ in range(n_iter): sample = group_data.sample(len(group_data), replace=True) ratio = sample[nominator].sum() / sample[denominator].sum() boot_ratios.append(ratio)

return np.array(boot_ratios)

Бакетизация

Делим также пользователей по бакетам (с их GMV и количеством заказов), распределение метрики будет нормальным, главное, чтобы в бакетах было достаточное количество наблюдений. Мы делаем n-подвыборок (где n - это количество бакетов). Из минусов: сложность работать с маленькими выборками, зависимость от количества бакетов (тонкая настройка).

`def bucketize(data, nominator, denominator, n_buckets=50, random_state=42):

data = data.sample(frac=1, random_state=random_state).reset_index(drop=True) buckets = np.array_split(data, n_buckets) bucket_ratios = [bucket[nominator].sum() / bucket[denominator].sum() for bucket in buckets]

return bucket_ratios

Пару слов про дельта-метод и линеаризацию.

В общем-то это об одном и том же. Мы хотим найти дисперсию метрики для того, чтобы применить классические методы (например, t-test). В дельта-методе мы корректируем дисперсию на корреляцию двух случайных величин (числителя и знаменателя). Только есть разница: дельта-метод вычисляет дисперсию на уровне выборки сразу, а линеаризация позволяет Ratio-метрику превратить в поюзерную. Результаты сонаправлены. Дельта-метод `def calculate_ratio_variance(values_numerator, values_denominator):

mean_num = np.mean(values_numerator) mean_denom = np.mean(values_denominator) variance_num = np.var(values_numerator, ddof=1) variance_denom = np.var(values_denominator, ddof=1)

covariance_num_denom = np.cov(values_numerator, values_denominator)[0, 1]

ratio_variance = ( (variance_num / mean_denom ** 2)

  • (2 * (mean_num / mean_denom ** 3) * covariance_num_denom)
  • ((mean_num ** 2 / mean_denom ** 4) * variance_denom) )

return ratio_variance

Линеаризация `# ratio_control - ratio-метрика в контрольной группе (для теста также рассчитывается)

def calculate_ratio_control(numerator_control, denominator_control): return sum(numerator_control) / sum(denominator_control)

ratio_control = calculate_ratio_control(numerator_control, denominator_control)

def linearization(numerator, denominator, ratio_control): return numerator - ratio_control * denominator

Дополнительные материалы для ознакомления: первый, второй, третий, четвертый, пятый Понравился пост? Давайте наберем 150 🐳🐳🐳, если хотите продолжение, пишите в комментариях, часто ли используете подобные методы?````