Как посчитать regression discontinuity в SQL

Закрепи формулу regression discontinuity в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать regression discontinuity в Telegram

Зачем RDD

Regression Discontinuity Design (RDD) применяется когда treatment назначается по пороговому значению переменной. Юзеры с score > 80 получают premium feature, остальные — нет. RDD оценивает скачок outcome ровно на cutoff: сравнивает почти-treated (score 79) и почти-control (score 81). Эти группы похожи во всём, кроме самого порога.

Sharp vs fuzzy

  • Sharp RDD: treatment назначается строго по cutoff. Все score > 80 → treated.
  • Fuzzy RDD: на cutoff вероятность treatment скачком растёт, но не до 1. Например, 30% → 70%.

Fuzzy решается через IV-эстиматор: cutoff_indicator как instrument для treatment.

RDD в SQL

Простой sharp RDD через сравнение средних в окне ±h вокруг cutoff:

WITH window_data AS (
    SELECT
        score,
        outcome,
        CASE WHEN score >= 80 THEN 1 ELSE 0 END AS treated,
        score - 80 AS centered_score
    FROM observations
    WHERE score BETWEEN 75 AND 85  -- bandwidth h = 5
)
SELECT
    treated,
    COUNT(*) AS n,
    AVG(outcome) AS mean_outcome
FROM window_data
GROUP BY treated;

Разница mean_outcome(treated) − mean_outcome(control) — оценка эффекта (LATE на cutoff).

Местная линейная регрессия — точнее:

WITH window_data AS (
    SELECT
        outcome,
        score - 80 AS x,
        CASE WHEN score >= 80 THEN 1 ELSE 0 END AS d,
        (score - 80) * (CASE WHEN score >= 80 THEN 1 ELSE 0 END) AS x_times_d
    FROM observations
    WHERE score BETWEEN 75 AND 85
)
SELECT
    -- y = a + b*x + c*d + e*(x*d). c — RDD effect.
    AVG(outcome) FILTER (WHERE d = 1) - AVG(outcome) FILTER (WHERE d = 0) AS simple_diff,
    COUNT(*) AS n
FROM window_data;

Для real RDD используют local linear regression — в чистом SQL громоздко, лучше Python rdrobust.

Bandwidth

Маленький bandwidth — точнее, но меньше N. Большой — biased если outcome не линейный. Стандарт — оптимальный bandwidth (Imbens-Kalyanaraman):

-- Грубо: bandwidth = 3 × σ(score | score near cutoff)
WITH near_cutoff AS (
    SELECT score FROM observations WHERE ABS(score - 80) < 10
)
SELECT 3 * STDDEV_SAMP(score) AS suggested_h FROM near_cutoff;

Чувствительность к выбору h проверяется отдельно: запускают RDD на h, h/2, 2h.

Закрепи формулу regression discontinuity в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать regression discontinuity в Telegram

Manipulation check

Если юзеры могут manipulate score (например, попасть в premium искусственно), RDD biased. Density-test на cutoff:

SELECT
    FLOOR(score) AS score_bucket,
    COUNT(*) AS n
FROM observations
WHERE score BETWEEN 75 AND 85
GROUP BY FLOOR(score)
ORDER BY score_bucket;

«Bump» на 80 — sign of manipulation. McCrary test делает это формально (вне SQL).

Частые ошибки

Ошибка 1. Bandwidth слишком большой. Если bandwidth = вся выборка, RDD = OLS с control variables. Bias возрастает.

Ошибка 2. Манипуляция score. Юзеры подгоняют score под cutoff — selection bias возвращается.

Ошибка 3. Discontinuity в running variable. Если distribution score внезапно скачет на cutoff (но не из-за manipulation, а из measurement), RDD не работает.

Ошибка 4. Игнорировать LATE. RDD даёт effect для юзеров с score ≈ cutoff, не для всей populace.

Ошибка 5. Линейный fit там, где нужен polynomial. Если outcome нелинейный, простая разница средних в окне даёт bias.

Связанные темы

FAQ

RDD vs DiD?

DiD — temporal, RDD — cross-sectional (по score). Разные источники variation.

Sharp vs fuzzy — что чаще?

Sharp — когда правило строгое (grade, age cutoff). Fuzzy — soft cutoffs (вероятность treatment).

Какой bandwidth?

Оптимальный — Imbens-Kalyanaraman или Calonico-Cattaneo-Titiunik. Стандартизировано в rdrobust.

Можно ли RDD на бинарном outcome?

Да, через logit или просто linear probability model в окне.

Сколько данных?

В окне ±h хотя бы 100 юзеров с каждой стороны. На малых N RDD шумит.