Как посчитать propensity score matching в SQL

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

Зачем PSM

В observational data treated и control различаются не только по самому факту treatment, но и по характеристикам (age, plan, country). Это даёт selection bias: «новички в Enterprise купили feature и вырос revenue». Propensity score matching выравнивает группы — каждому treated находим control с похожей вероятностью treatment, и сравниваем пары.

Шаги PSM

  1. Оценить P(treatment | covariates) — обычно logit-регрессия в Python.
  2. Импортировать propensity score в SQL.
  3. Для каждого treated найти nearest neighbor в control по PS.
  4. Сравнить outcome.

В чистом SQL шаг 1 сложен, поэтому обычно considered hybrid: model в Python, matching в SQL.

PSM в SQL

Допустим, propensity score уже посчитан и сохранён:

WITH treated AS (
    SELECT user_id, propensity_score, outcome
    FROM observations
    WHERE is_treated = TRUE
),
control AS (
    SELECT user_id, propensity_score, outcome
    FROM observations
    WHERE is_treated = FALSE
),
nearest AS (
    SELECT
        t.user_id AS treated_id,
        t.propensity_score AS treated_ps,
        t.outcome AS treated_outcome,
        c.user_id AS control_id,
        c.propensity_score AS control_ps,
        c.outcome AS control_outcome,
        ABS(t.propensity_score - c.propensity_score) AS ps_distance,
        ROW_NUMBER() OVER (PARTITION BY t.user_id ORDER BY ABS(t.propensity_score - c.propensity_score)) AS rn
    FROM treated t
    CROSS JOIN control c
)
SELECT *
FROM nearest
WHERE rn = 1;

CROSS JOIN дорогостоящий — на 100k×100k не пойдёт. Для больших данных используют k-d tree (вне SQL).

Баланс ковариат

После matching проверяем, что age/plan/country у пар равны:

WITH matched_pairs AS (
    -- из предыдущего CTE
    SELECT treated_id, control_id FROM nearest WHERE rn = 1
)
SELECT
    'age' AS covariate,
    AVG(t.age) - AVG(c.age) AS mean_diff
FROM matched_pairs mp
JOIN observations t ON t.user_id = mp.treated_id
JOIN observations c ON c.user_id = mp.control_id

UNION ALL

SELECT
    'plan_premium',
    AVG((t.plan = 'premium')::INT::NUMERIC) - AVG((c.plan = 'premium')::INT::NUMERIC)
FROM matched_pairs mp
JOIN observations t ON t.user_id = mp.treated_id
JOIN observations c ON c.user_id = mp.control_id;

Хороший matching: standardized mean differences < 0.1 по всем ковариатам.

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

ATE по matched парам

Average Treatment Effect:

WITH matched_pairs AS (
    SELECT treated_id, control_id, treated_outcome, control_outcome
    FROM nearest WHERE rn = 1
)
SELECT
    AVG(treated_outcome - control_outcome) AS ate,
    COUNT(*) AS n_pairs,
    STDDEV_SAMP(treated_outcome - control_outcome) / SQRT(COUNT(*)) AS se_ate
FROM matched_pairs;

ATE — оценка эффекта treatment, очищенная от observable confounders.

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

Ошибка 1. PSM не выравнивает unobserved confounders. Если в данных нет важной переменной (motivation, region), PSM не поможет.

Ошибка 2. Matching with replacement vs without. With replacement один control попадает многим. Без — каждый control в одной паре. Влияет на bias-variance trade-off.

Ошибка 3. Игнорировать common support. Если у treated PS близок к 1, а у control max 0.6 — для таких treated нет match. Отбраковка обязательна.

Ошибка 4. Использовать matched data для обычного t-test. Pairs зависимы. Используйте paired t-test или conditional inference.

Ошибка 5. Caliper не задан. Без caliper «nearest» может быть с PS 0.9 и 0.1. Стандарт — caliper = 0.2 × σ(PS).

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

FAQ

PSM vs A/B?

A/B — random. PSM — наблюдательные данные с попыткой эмулировать random.

Какой caliper?

0.2 × stddev(PS). Жёстче — лучше match, меньше pairs.

Когда PSM не работает?

При strong unobserved confounding. Sensitivity-анализ обязателен.

Matching k-to-1?

Один treated → k controls. Снижает variance, повышает bias.

Альтернатива?

Inverse probability weighting (IPW), DiD, synthetic control — все используют PS, но по-разному.