Regression discontinuity простыми словами

Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.

Зачем это знать

Бонусная программа включается при score ≥ 70. Те, кто 69, не получают, у 71 — получают. Почти identical users, разное treatment. Это natural experiment — regression discontinuity (RDD).

В банковской, страховой, fintech аналитике RDD встречается постоянно: cutoffs для кредитов, тарифов, offers. Знание RDD — мощный инструмент для middle+.

Короткое объяснение

RDD измеряет эффект воздействия, которое применяется к unit на основе их score относительно cutoff.

Сравниваем units «чуть выше» и «чуть ниже» threshold → они почти identical, кроме treatment.

Пример

Кредитный бонус даётся клиентам со score ≥ 700.

  • Score 699: бонуса нет → LTV = $200
  • Score 701: бонус есть → LTV = $280

Разница $80 — эффект бонуса, если верить в RDD.

Sharp vs Fuzzy

Sharp RDD

Treatment применяется всегда и только при score ≥ cutoff.

Все 701+ получают бонус, никто ниже.

Fuzzy RDD

Вероятность treatment растёт на cutoff, но не на 100%.

Например, 80% клиентов со score ≥ 700 выбирают бонус, 20% — нет.

Требует instrumental variable approach.

Assumption

Continuity

Все остальные факторы (кроме treatment) — continuous функции running variable около cutoff.

Другими словами: без cutoff LTV(score) был бы smooth function.

No manipulation

Units не могут precision-контролировать свой score, чтобы попасть в/выше cutoff.

Если клиенты могут «накачать» score до 701 — RDD сломан.

В формуле

Y = α + β × Treatment + f(score) + ε

Где f(score) — smooth function score (polynomial или kernel), а β — эффект treatment.

Визуально

График: X-axis — running variable, Y-axis — outcome.

  • До cutoff: smooth trend
  • На cutoff: jump (discontinuity)
  • После cutoff: smooth trend

Размер jump = effect.

В Python

import statsmodels.api as sm
import numpy as np

# Near cutoff (bandwidth 30)
data_near = data[abs(data['score'] - 700) < 30]
data_near['treatment'] = (data_near['score'] >= 700).astype(int)
data_near['score_centered'] = data_near['score'] - 700

# Linear с interaction
X = sm.add_constant(data_near[['treatment', 'score_centered']])
X['interact'] = X['treatment'] * X['score_centered']

model = sm.OLS(data_near['outcome'], X).fit()
# treatment coefficient = effect

Или специальный пакет:

import rdrobust
rdrobust.rdplot(y=data['y'], x=data['score'], c=700)

Bandwidth

Ключевое решение: как близко к cutoff смотреть.

  • Слишком wide: bias (units слишком разные)
  • Слишком narrow: variance (мало данных)

Оптимум — через cross-validation или optimal bandwidth formulas (Imbens-Kalyanaraman).

Когда использовать

  • Есть sharp cutoff в процессе
  • Running variable continuous
  • Около cutoff достаточно данных

Классические кейсы

  • Кредитные ограничения
  • Минимальный балл (admissions)
  • Возрастные cutoffs (pension, legal drinking)
  • Политические partitions (close elections)

Pitfalls

Manipulation

Если units могут manipulate score — тест McCrary для density около cutoff. Если density jump есть → manipulation.

Bandwidth sensitivity

Результат может сильно меняться от bandwidth. Robustness checks нужны.

Multiple cutoffs

Если есть несколько cutoffs — усложняется.

На собесе

«Что такое RDD?» Метод измерения эффекта от treatment на основе cutoff running variable.

«Sharp vs fuzzy?» Sharp — 100% treatment выше cutoff, fuzzy — probability.

«Assumption?» Continuity и no manipulation.

«Когда использовать?» Когда есть sharp cutoff и нельзя A/B.

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

Игнорировать manipulation

McCrary test обязательно.

Слишком wide bandwidth

Сравниваются units, не похожие друг на друга.

Wrong functional form

Если f(score) на самом деле cubic, а вы fit linear — bias.

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

FAQ

В A/B есть?

Нет. RDD — когда A/B нельзя сделать.

Сложно?

Концепция проста, но детали (bandwidth, polynomial order) требуют care.

External validity?

Эффект на границе cutoff может отличаться от среднего effect в популяции.


Тренируйте causal — откройте тренажёр с 1500+ вопросами для собесов.