Propensity Score Matching: оценка эффектов в observational data

Что такое Propensity Score Matching

Propensity Score Matching (PSM) — метод causal inference для observational data. Позволяет оценить причинный эффект treatment, когда не было рандомизации.

Идея: для каждого «обработанного» (treated) юзера найти похожего «необработанного» (control). Сравнение их outcomes — приближение к causal effect.

«Похожесть» определяется через propensity score — вероятность получить treatment на основе observed features.

Метод предложил Paul Rosenbaum и Donald Rubin в 1983. Широко используется в economics, epidemiology, marketing.

Когда применять PSM

Нельзя провести AB-тест. Например:

  • Treatment уже произошло (postfactum analysis).
  • Randomization неэтична (нельзя произвольно давать скидку некоторым).
  • Policy-level treatments (не на уровне индивида).
  • Retrospective analysis.

Доступны богатые данные о юзерах. Нужно много features для хорошей propensity score модели.

Есть достаточно overlap между treated и control. Оба типа юзеров с похожими characteristics существуют.

Propensity Score

Определение: P(Treatment = 1 | Features).

Вероятность получить treatment, учитывая observed characteristics.

Рассчитывается через логистическую регрессию или ML:

from sklearn.linear_model import LogisticRegression

# Features for predicting treatment
X = df[['age', 'income', 'tenure_days', 'purchases']]
T = df['received_treatment']  # 0 or 1

model = LogisticRegression()
model.fit(X, T)

df['propensity_score'] = model.predict_proba(X)[:, 1]

Каждый юзер получает score 0-1 — вероятность получить treatment.

Идея Rosenbaum-Rubin: если двое юзеров имеют одинаковый propensity score, то (при условных допущениях) treatment между ними назначен «как бы случайно».

Шаги PSM

Шаг 1. Выбор переменных.

Включаем characteristics, которые:

  • Влияют на treatment (selection into treatment).
  • Влияют на outcome.

НЕ включаем:

  • Post-treatment variables.
  • Mediators.
  • Consequences of treatment.

Шаг 2. Estimate propensity score.

Логистическая регрессия или ML модель. Включаем все relevant confounders.

Шаг 3. Check overlap.

Строим distribution propensity scores для treated vs control. Должен быть overlap (common support).

import matplotlib.pyplot as plt

treated_ps = df[df['T']==1]['propensity_score']
control_ps = df[df['T']==0]['propensity_score']

plt.hist(treated_ps, alpha=0.5, label='Treated', bins=30)
plt.hist(control_ps, alpha=0.5, label='Control', bins=30)
plt.legend()

Если treated score распределён в 0.7-1.0, а control — 0.0-0.3, overlap плохой → нельзя делать PSM. Юзеры systematically different.

Шаг 4. Matching.

Для каждого treated найти control с близким propensity score. Методы:

  • Nearest neighbor. Ближайший control. Caliper — максимальная дistance.
  • K-nearest neighbors. Множественные matches для каждого treated.
  • Optimal matching. Минимизирует суммарное расстояние pairs.
  • Stratification. Разбить на strata по propensity, compare within.

Шаг 5. Balance diagnostics.

После matching проверяем, что covariates сбалансированы.

Standardized mean difference (SMD):

SMD = (mean_treated - mean_control) / pooled_sd

После matching SMD должна быть < 0.1 для всех features.

Шаг 6. Estimate treatment effect.

Простая разница outcomes между matched pairs:

ate = df_matched[df_matched['T']==1]['outcome'].mean() - \
      df_matched[df_matched['T']==0]['outcome'].mean()

Или regression на matched sample.

Python implementation

import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import NearestNeighbors

# 1. Estimate propensity score
features = ['age', 'income', 'tenure', 'purchases']
X = df[features]
T = df['treatment']

ps_model = LogisticRegression().fit(X, T)
df['ps'] = ps_model.predict_proba(X)[:, 1]

# 2. Split treated and control
treated = df[df['treatment']==1].reset_index(drop=True)
control = df[df['treatment']==0].reset_index(drop=True)

# 3. Nearest neighbor matching
nn = NearestNeighbors(n_neighbors=1).fit(control[['ps']])
distances, indices = nn.kneighbors(treated[['ps']])

# 4. Apply caliper
caliper = 0.01
good_matches = distances.flatten() < caliper

matched_treated = treated[good_matches].reset_index(drop=True)
matched_control = control.iloc[indices.flatten()[good_matches]].reset_index(drop=True)

# 5. ATE
ate = matched_treated['outcome'].mean() - matched_control['outcome'].mean()
print(f'ATE: {ate}')

Библиотеки:

  • causalinference — PSM из коробки.
  • pymatch — matching algorithms.
  • DoWhy — full causal inference framework.

Assumption: Conditional Independence

Главное allegation PSM — conditional independence assumption (CIA):

Y(0), Y(1) ⊥ T | X

Untreated potential outcomes независимы от treatment given features X. То есть treatment «как бы random» после условия на X.

Это требует: включили все важные confounders в propensity model. Unobserved confounders — убийца PSM.

Alternative: Inverse Probability Weighting

IPW — related method. Вместо matching взвешиваем observations inverse propensity:

Weight_treated = 1 / PS
Weight_control = 1 / (1 - PS)

Использует ВСЕХ observations (не отбрасывает unmatched treated или control). Но sensitive к extreme weights при PS near 0 или 1.

Doubly robust estimators (AIPW) комбинируют PSM-like weighting с regression — наиболее robust.

Преимущества PSM

Интуитивная идея. Сравниваем «похожих» юзеров — понятно stakeholders.

Отбрасывает incomparable observations. Treated без подобных controls — отбрасывается. Чистит выборку.

Visualizable. PS distributions, matched pairs, balance — всё можно визуализировать.

Применимо к observational data. Где AB тест невозможен — PSM альтернатива.

Знание advanced causal methods выделяет senior аналитика. В тренажёре Карьерник есть задачи на causal inference, AB-тесты и статистику.

Недостатки

Unobserved confounders. Главная проблема. Matching only on observed features. Если treated и control отличаются по ненаблюдаемым characteristics — bias.

Data hungry. Нужно много observations для good matches. Малые datasets → плохая точность.

Только ATT, не ATE. PSM обычно даёт Average Treatment Effect on Treated. ATE requires другие методы.

Research-intensive. Не automatic. Требует аккуратной работы с propensity model, balance diagnostics, sensitivity.

Sensitivity analysis

Как понять, robust ли оценка к unobserved confounders?

Rosenbaum bounds. Как сильно unobserved confounder должен быть, чтобы изменить вывод? Если slightly — result fragile.

# Пример с rosenbaum library (Python):
from rosenbaum_bounds import sensitivity
# Gamma parameter: strength of confounding
# Interpretation: Gamma=2 means unobserved confounder would double the odds of treatment

E-value. Минимальная сила unobserved confounder для explaining away observed effect. Высокий E-value → robust.

Simulated confounders. Добавляем artificial confounder различной силы, observe effect stability.

Propensity score — не панацея

Частая заблуждение: PSM «решает» selection bias. На самом деле — только observable selection.

Король David Freedman писал статью «Statistical Models and Shoe Leather» о том, что никакая статистика не замещает shoe-leather работу — understanding of institutional setting, domain knowledge, careful reasoning about causality.

PSM — инструмент, не серебряная пуля.

Best practices

1. Rich feature set. Больше relevant confounders = лучше.

2. Multiple methods. Compare PSM с IPW, regression adjustment. Если results совпадают — robust.

3. Robustness checks. Разные matching methods (1:1, 1:3, kernel), разные calipers.

4. Balance diagnostics. SMD, variance ratios, empirical CDF comparison.

5. Sensitivity analysis. Rosenbaum bounds или E-values — обязательно.

6. Transparent reporting. Показать balance tables, overlap plots, sensitivity.

Real-world example

Gym chain хочет оценить effect premium membership на retention.

  • Treated: premium members.
  • Control: basic members.

Features для PSM: age, income, prior_visits_per_month, gender, location.

Результат PSM:

Unadjusted difference in retention: 25%
After PSM matching: 12%

Interpretation: премиум сам по себе даёт +12% retention.
Остальные 13% — это selection effect (более serious about fitness пользователи 
выбирают premium).

PSM показала, что naive comparison переоценивает effect в 2 раза.

Типичные ошибки

Extrapolation без overlap. Если treated и control не overlap, matching создаёт fake similarity. Всегда проверять common support.

Using ML for PS без validation. Сложные ML модели могут overfit. Проверить out-of-sample AUC.

Ignoring matching quality. Плохие matches → плохие выводы. Balance diagnostics обязательны.

Inclusion of post-treatment variables. Features, которые сами меняются от treatment — смещают ответ.

One-shot matching. Не проводить robustness checks — рискованно.

Читайте также

FAQ

PSM или AB-тест?

AB — predпочтительнее, если возможен (clean randomization). PSM — альтернатива для observational data.

Сколько features в propensity score?

Все relevant confounders. Обычно 10-30 для business applications. Слишком много → overfitting.

Кalипер — какой выбрать?

Популярно: 0.2 × SD of PS logit. Жёстче → лучше matches, но меньше observations. Баланс.

PSM работает с малыми выборками?

Плохо. Минимум несколько сотен observations с хорошим overlap. С < 100 обсервациями — unreliable.