倾向性评分匹配PSM
一句话概述:倾向性评分匹配(Propensity Score Matching, PSM)通过计算每个受试者接受"处理"的概率(倾向性评分),然后按评分匹配处理组和对照组,消除混杂因素的影响,在观察性研究中模拟随机对照试验的效果。
核心知识点速查表
| 概念 | 说明 |
|---|
| PSM | 倾向性评分匹配(白话:找"双胞胎"配对来消除偏差) |
| 倾向性评分(PS) | 受试者被分到处理组的概率(0-1) |
| 混杂因素 | 同时影响处理和结局的变量(如年龄、分期) |
| 最近邻匹配 | 最常用的匹配方法(找PS最接近的配对) |
| 卡钳(Caliper) | 匹配允许的PS最大差距(通常0.2×SD) |
| SMD | 标准化均值差,评估匹配后平衡性(<0.1为好) |
一、R语言实操
# === 倾向性评分匹配 ===
library(MatchIt)
# 场景:比较手术A和手术B的生存结果
# 问题:两组患者的年龄、分期等不均衡(混杂)
# 第1步:计算倾向性评分并匹配
matched <- matchit(
treatment ~ age + sex + stage + bmi + comorbidity, # 处理 ~ 混杂因素
data = patient_data,
method = "nearest", # 最近邻匹配
distance = "logit", # 用logistic回归计算PS
caliper = 0.2, # 卡钳宽度(0.2个PS标准差)
ratio = 1 # 1:1匹配
)
# 查看匹配结果
summary(matched) # 匹配统计
plot(matched, type="jitter") # 匹配前后分布图
plot(summary(matched)) # SMD图(★所有变量应<0.1)
# 第2步:提取匹配后的数据
matched_data <- match.data(matched) # 获取匹配后的数据集
cat("匹配前:", nrow(patient_data), "人\n")
cat("匹配后:", nrow(matched_data), "人\n")
# 第3步:在匹配后的数据上做分析
# 生存分析
library(survival); library(survminer)
fit <- survfit(Surv(time, status) ~ treatment, data=matched_data)
ggsurvplot(fit, pval=TRUE, risk.table=TRUE)
# Cox回归
cox_matched <- coxph(
Surv(time, status) ~ treatment, # 匹配后只需要处理变量
data = matched_data
)
summary(cox_matched)
# === 评估匹配质量(非常重要!) ===
library(cobalt)
# 平衡性检查
bal <- bal.tab(matched, thresholds=c(m=0.1)) # SMD阈值0.1
print(bal)
# Love图(可视化平衡性)
love.plot(matched,
threshold=0.1, # SMD阈值
abs=TRUE, # 取绝对值
var.order="unadjusted") # 排序方式
# 匹配前后对比
# SMD (Standardized Mean Difference):
# < 0.1: 良好平衡 ✓
# 0.1-0.2: 可接受
# > 0.2: 仍有不平衡 ✗
二、面试高频考点
Q1: 为什么需要PSM?
- 观察性研究中,处理组和对照组的基线特征不均衡
- 直接比较会有混杂偏差(如手术A组更年轻→生存更好,但可能不是手术A好)
- PSM通过匹配使两组基线特征均衡,消除混杂
- 白话:想比较清华和普通大学哪个好,但清华生本来成绩就高。PSM就是找成绩相似的学生来比
Q2: PSM的局限性?
- 只能控制已知的混杂因素(未测量的混杂无法消除)
- 匹配后样本量减少(丢弃不匹配的样本)
- 选择不同匹配参数结果可能不同
- 只适用于二分类处理(有/无处理)
Q3: 除了PSM还有什么方法控制混杂?
| 方法 | 优点 | 缺点 |
|---|
| PSM | 直观,广泛接受 | 丢弃样本 |
| IPTW | 不丢弃样本,利用所有数据 | 极端权重可能不稳定 |
| 多变量回归 | 简单 | 模型假设可能不满足 |
| 分层分析 | 每层内混杂较小 | 层数太多样本不够 |
速查表
# === PSM速查 ===
library(MatchIt)
# 匹配
m <- matchit(treatment ~ age+sex+stage, data=df, method="nearest", caliper=0.2)
# 检查平衡
summary(m); plot(summary(m)) # SMD<0.1=好
library(cobalt); love.plot(m)
# 提取匹配数据
matched_df <- match.data(m)
# 匹配后分析
coxph(Surv(time,status) ~ treatment, data=matched_df)
# 匹配方法: nearest(最近邻) | optimal(最优) | full(全匹配) | exact(精确)