Как посчитать FDR (Benjamini-Hochberg) в SQL
Содержание:
Зачем FDR
При 20+ метриках в одном A/B-тесте Bonferroni слишком консервативен — теряете реальные эффекты. FDR (False Discovery Rate) контролирует долю ложных открытий среди объявленных значимыми. Стандарт для метрических панелей в большом продуктовом A/B, не для критических медицинских trials.
Процедура Benjamini-Hochberg
- Сортируем p-values по возрастанию:
p_(1) ≤ p_(2) ≤ ... ≤ p_(m). - Для каждого
iсчитаем порогq × i / m, гдеq— желаемый FDR. - Находим максимальный
k, такой чтоp_(k) ≤ q × k / m. - Объявляем значимыми все
p_(1), ..., p_(k).
BH в SQL
WITH raw_p AS (
SELECT * FROM (VALUES
('conversion', 0.001),
('revenue', 0.012),
('retention_d7', 0.030),
('arpu', 0.045),
('time_on_page', 0.120),
('refunds', 0.310),
('unsubscribes', 0.500),
('crashes', 0.890)
) AS t(metric, p_value)
),
ordered AS (
SELECT
metric,
p_value,
ROW_NUMBER() OVER (ORDER BY p_value) AS rnk,
COUNT(*) OVER () AS m
FROM raw_p
),
bh AS (
SELECT
metric,
p_value,
rnk,
m,
0.05 * rnk::NUMERIC / m AS threshold,
p_value <= 0.05 * rnk::NUMERIC / m AS below_threshold
FROM ordered
),
max_rnk AS (
SELECT MAX(rnk) AS k FROM bh WHERE below_threshold
)
SELECT
b.metric,
b.p_value,
b.rnk,
b.threshold,
b.rnk <= COALESCE(m.k, 0) AS significant_bh
FROM bh b
CROSS JOIN max_rnk m
ORDER BY b.rnk;Adjusted p-values
Если хочется именно «BH-adjusted p»:
p_adjusted(i) = min over j >= i of (p_(j) × m / j)WITH ordered AS (
SELECT
metric,
p_value,
ROW_NUMBER() OVER (ORDER BY p_value) AS rnk,
COUNT(*) OVER () AS m
FROM raw_p
),
candidate AS (
SELECT
metric,
p_value,
rnk,
m,
p_value * m / rnk::NUMERIC AS raw_adj
FROM ordered
)
SELECT
metric,
p_value,
LEAST(MIN(raw_adj) OVER (ORDER BY rnk DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), 1.0)
AS p_bh_adjusted
FROM candidate
ORDER BY rnk;Window ORDER BY rnk DESC ROWS UNBOUNDED PRECEDING — running min от хвоста, чтобы adjusted-p были монотонными.
FDR vs FWER
SELECT
metric,
p_value,
LEAST(p_value * m, 1.0) AS p_bonferroni, -- FWER
p_bh_adjusted -- FDR
FROM bh_results;FWER (Bonferroni): «вероятность хотя бы одного ложного открытия ≤ 5%». FDR (BH): «среди объявленных значимыми ≤ 5% ложных».
На 20 метриках Bonferroni α/20 = 0.0025 — реальные эффекты теряются. BH с q=0.05 отлавливает больше.
Частые ошибки
Ошибка 1. Сортировать по m, а не по i / m.
В BH-процедуре сравнивают p_(i) ≤ q × i / m, не p_(i) ≤ q / m. i / m растёт по позиции.
Ошибка 2. Не корректировать adjusted-p на монотонность.
Грубый p × m / i может дать p_adj(2) > p_adj(3). Стандарт — running min от хвоста, как в SQL выше.
Ошибка 3. BH при зависимых тестах. BH формально требует независимость p-values. На сильно коррелированных метриках — Benjamini-Yekutieli (BY) с поправкой.
Ошибка 4. Применять FDR на маленьких m.
При m=3 Bonferroni и BH дают почти одно. Эффект FDR проявляется при m ≥ 10.
Ошибка 5. Использовать в medical/regulatory. FWER здесь обычно строже (FDA, MTM). FDR — для исследовательских и продуктовых задач.
Связанные темы
- Как посчитать Bonferroni correction в SQL
- Как посчитать chi-square test в SQL
- Как посчитать t-test в SQL
- Как посчитать effect size в SQL
FAQ
BH vs Bonferroni?
BH — менее строгий, больше power. Bonferroni — строже, теряете эффекты.
Какой q выбрать?
Обычно 0.05 или 0.10. В research — 0.20 допустимо.
FDR можно для коррелированных метрик?
BY-procedure: p_(i) ≤ q × i / (m × Σ 1/j for j=1..m). Жёстче BH, но валиден при зависимости.
Можно ли BH для попарных сравнений A/B/C?
Да, если у вас m гипотез о попарной разнице.
m фиксируется?
Да, до анализа. Иначе p-hacking.