Как посчитать synthetic control в SQL
Содержание:
Зачем synthetic control
Когда у вас 1 treated unit (страна, регион, бренд) и много controls, обычный DiD не работает — нет «параллельной группы». Synthetic control строит взвешенную комбинацию controls, которая до intervention максимально похожа на treated. Разница после intervention — оценка эффекта.
Применение: оценка эффекта рекламной кампании в одной стране, лонч feature в одном городе, влияние policy change на регион.
Идея метода
- Treated unit i*, набор control units i ∈ {1, …, J}.
- Найти веса w_i ≥ 0, Σw_i = 1, чтобы Σ w_i × y_i(t) ≈ y_{i*}(t) в pre-period.
- Predicted y* в post = Σ w_i × y_i(t_post).
- Effect = actual y_{i*}(t_post) − predicted.
Веса обычно подбирают оптимизацией (Abadie). В SQL только грубая аппроксимация.
Synthetic control в SQL
В SQL минимально работающий вариант — равные веса по K «самым похожим» controls:
WITH series AS (
SELECT
region_id,
week_date,
metric_value,
AVG(metric_value) FILTER (WHERE week_date < '2026-04-01') OVER (PARTITION BY region_id) AS pre_mean
FROM weekly_metrics
WHERE week_date BETWEEN '2025-10-01' AND '2026-05-01'
),
treated_pre AS (
SELECT AVG(metric_value) AS treated_pre_avg
FROM series
WHERE region_id = 'treated_region'
AND week_date < '2026-04-01'
),
candidates AS (
SELECT
region_id,
ABS(MAX(pre_mean) - (SELECT treated_pre_avg FROM treated_pre)) AS distance
FROM series
WHERE region_id <> 'treated_region'
GROUP BY region_id
ORDER BY distance ASC
LIMIT 5
)
SELECT
s.week_date,
AVG(s.metric_value) FILTER (WHERE s.region_id IN (SELECT region_id FROM candidates)) AS synthetic,
MAX(s.metric_value) FILTER (WHERE s.region_id = 'treated_region') AS actual
FROM series s
GROUP BY s.week_date
ORDER BY s.week_date;Это примитивный «top-5 closest» — не оптимальные веса, но даёт baseline.
Эффект treatment
После построения synthetic — разница post-period:
WITH compared AS (
SELECT
week_date,
actual,
synthetic,
actual - synthetic AS gap
FROM weekly_compare
)
SELECT
week_date,
actual,
synthetic,
gap
FROM compared
WHERE week_date >= '2026-04-01'
ORDER BY week_date;Cumulative effect:
SELECT SUM(gap) AS cumulative_effect
FROM weekly_compare
WHERE week_date >= '2026-04-01';Placebo-тест
Применить тот же метод к каждому control — посмотреть, насколько часто effect такой большой случайно:
-- Псевдо-код: для каждого региона запускаем тот же расчёт, как будто он "treated"
-- Если treated даёт effect = +20%, а у placebo regions диапазон -3% до +5% —
-- эффект статистически отделён от шума.В SQL реализуется через цикл по регионам (PL/pgSQL), в Python проще через pandas + scipy.
Если у treated эффект сильнее, чем у 95% placebo-regions — значимо. Иначе — это шум.
Частые ошибки
Ошибка 1. Bad pre-fit. Если synthetic плохо повторяет treated в pre-period, верить пост-разнице нельзя.
Ошибка 2. Использовать controls, которые сами «обработаны». Если в control regions была своя кампания, synthetic тоже выросло бы. Тщательно отбирайте donor pool.
Ошибка 3. Слишком мало donors. Минимум 10-20 controls для устойчивых весов.
Ошибка 4. Игнорировать covariates. Помимо pre-period outcome, в матчинг включают age structure, GDP, plan distribution и т.д.
Ошибка 5. Не делать placebo. Без placebo-теста — это просто наблюдение, не статистическая значимость.
Связанные темы
- Как посчитать DiD в SQL
- Как посчитать propensity score matching в SQL
- Synthetic control простыми словами
- Как посчитать changepoint detection в SQL
FAQ
Когда synthetic control?
Один treated unit, много controls, есть длинный pre-period.
Минимум pre-period?
Не меньше 10–20 точек. Лучше 30+.
Donors можно weight больше 1?
Стандартный SC — weights в [0, 1] и сумма = 1. Bias-variance trade-off через регуляризацию.
CI для эффекта?
Placebo-тест даёт permutation-based p-value.
Реализация в SQL — это норма?
Для оценки направления — да. Для production-grade — Python pysyncon / R synth.