vlambda博客
学习文章列表

基于R语言实现倾向性评分匹配(PSM)

一、倾向性评分匹配(Propensity Score Matching)

二、数据集介绍

同样选用lalonde数据集,如前一篇文章介绍,该数据集收集了614个观察对象的十个特征:

1. age:年龄,以岁为单位。
2. educ:学习时间,之前一共上过多少年学。
3. black:是否为黑人。
4. hispan:是否为西班牙裔。
5. married:是否已婚。
6. nodegree:是否没有毕业文凭。
7. re74:1974年的收入。
8. re75:1975年的收入。
9. re78:1978年的收入。
10. treat:1978年前是否接受职业培训。

研究假说是接受过职业培训会增加1978年的收入水平。

三、R语言实现

加载包与数据集

#MatchIt包做PSM,table1做表
library(MatchIt)
library(table1)
#载入数据集
data(lalonde)
#查看变量数据类型
#str(lalonde)

数据集处理

#转换变量数据类型
lalonde$black <- factor(lalonde$black)
lalonde$hispan <- factor(lalonde$hispan)
lalonde$married <- factor(lalonde$married)
lalonde$nodegree <- factor(lalonde$nodegree)
lalonde$black <- as.logical(lalonde$black == 1)
lalonde$hispan <- as.logical(lalonde$hispan == 1)
lalonde$married <- as.logical(lalonde$married == 1)
lalonde$nodegree <- as.logical(lalonde$nodegree == 1)
#更改变量标注
label(lalonde$black) <- "黑人"
label(lalonde$educ) <- "上学时间"
label(lalonde$hispan) <- "西班牙裔"
label(lalonde$married) <- "已婚"
label(lalonde$nodegree) <- "无毕业文凭"
label(lalonde$age) <- "年龄"
label(lalonde$re74) <- "1974年收入"
label(lalonde$re75) <- "1975年收入"
label(lalonde$re78) <- "1978年收入"
units(lalonde$age) <- "岁"
units(lalonde$educ) <- "年"

输出基线特征表格

#为了符合习惯用法,把table1函数修改为表示mean±sd和p值,并重新封装
func_table1<-function(df,ycol,xcol,xlabels=c("Control", "Treatment")){
if( any(c(missing(df),missing(xcol),missing(ycol)))){
return("parameters missing! arg1 data.frame, arg2 ycol, arg3 xcol")
}
else if(!all(c(is.data.frame(df),is.character(xcol),is.character(ycol)))==TRUE){
return("parameter type error! arg1 data.frame, arg2,arg3 character vector")
}
df[[ycol]]<-as.numeric(df[[ycol]])
df[[ycol]]<-factor(df[[ycol]],levels=c(0, 1, 2), labels=c(xlabels, "P-value"))

rndr <- function(x, name, ...) {
if (length(x) == 0) {
y <- df[[name]]
s <- rep("", length(render.default(x=y, name=name, ...)))
if (is.numeric(y)) {
p <- t.test(y ~ df[[ycol]])$p.value
} else {
p <- chisq.test(table(y, droplevels(df[[ycol]])))$p.value
}
s[2] <- sub("<", "&lt;", format.pval(p, digits=3, eps=0.001))
s
} else {
render.default(x=x, name=name, ...)

}
}

rndr.strat <- function(label, n, ...) {
ifelse(n==0, label, render.strat.default(label, n, ...))
}

my.render.cont=c(.="Mean &plusmn; SD",.="Median [Min, Max]")

my.render.cat <- function(x) {
c("", sapply(stats.default(x), function(y) with(y,
sprintf("%d (%0.0f %%)", FREQ, PCT))))
}

table1(as.formula(paste("~",paste(xcol,collapse = "+"),"|",ycol)),
data=df, droplevels=F, render=rndr, render.strat=rndr.strat,
render.continuous=my.render.cont,
render.categorical=my.render.cat,
overall=F)
}
#按是否接受职业培训分组,显示组间基本信息
func_table1(lalonde,"treat",names(lalonde)[-1],c("未职业培训组","职业培训组"))

未职业培训组
(N=429)
职业培训组
(N=185)
P-value
年龄 (岁)


Mean ± SD 28.0 ± 10.8 25.8 ± 7.16 0.00291
Median [Min, Max] 25.0 [16.0, 55.0] 25.0 [17.0, 48.0]
上学时间 (年)


Mean ± SD 10.2 ± 2.86 10.3 ± 2.01 0.585
Median [Min, Max] 11.0 [0, 18.0] 11.0 [4.00, 16.0]
黑人


Yes 87 (20 %) 156 (84 %) <0.001
No 342 (80 %) 29 (16 %)
西班牙裔


Yes 61 (14 %) 11 (6 %) 0.00532
No 368 (86 %) 174 (94 %)
已婚


Yes 220 (51 %) 35 (19 %) <0.001
No 209 (49 %) 150 (81 %)
无毕业文凭


Yes 256 (60 %) 131 (71 %) 0.0113
No 173 (40 %) 54 (29 %)
1974年收入


Mean ± SD 5620 ± 6790 2100 ± 4890 <0.001
Median [Min, Max] 2550 [0, 25900] 0 [0, 35000]
1975年收入


Mean ± SD 2470 ± 3290 1530 ± 3220 0.00115
Median [Min, Max] 1090 [0, 18300] 0 [0, 25100]
1978年收入


Mean ± SD 6980 ± 7290 6350 ± 7870 0.349
Median [Min, Max] 4980 [0, 25600] 4230 [0, 60300]

可以看到组间并没有1978年收入没有差异,但是其他混杂因素均有较显著差异,现在就用R来行倾向性分析,匹配后可得到剔除混杂因素影响的样本。

set.seed(50)
#PSM
m.out <- matchit(data = lalonde,
#以treat分组,匹配混杂因素(age,educ,black,hispan,married,nodegree,re74,re75)无差异的样本
formula = treat ~ age + educ + black + hispan + married + nodegree + re74 + re75
,
method = "nearest",
distance = "logit",
replace = FALSE,
caliper = 0.05)
#匹配后样本数据
lalonde_matched<-match.data(m.out)
#更改变量标注
label(lalonde_matched$black) <- "黑人"
label(lalonde_matched$educ) <- "上学时间"
label(lalonde_matched$hispan) <- "西班牙裔"
label(lalonde_matched$married) <- "已婚"
label(lalonde_matched$nodegree) <- "无毕业文凭"
label(lalonde_matched$age) <- "年龄"
label(lalonde_matched$re74) <- "1974年收入"
label(lalonde_matched$re75) <- "1975年收入"
label(lalonde_matched$re78) <- "1978年收入"
units(lalonde_matched$age) <- "岁"
units(lalonde_matched$educ) <-"年"
#显示匹配后数据
func_table1(lalonde_matched,"treat",names(lalonde)[-1],c("未职业培训组","职业培训组"))

未职业培训组
(N=109)
职业培训组
(N=109)
P-value
年龄 (岁)


Mean ± SD 26.2 ± 11.0 25.7 ± 7.35 0.686
Median [Min, Max] 20.0 [16.0, 55.0] 24.0 [17.0, 46.0]
上学时间 (年)


Mean ± SD 10.3 ± 2.68 10.2 ± 2.03 0.955
Median [Min, Max] 11.0 [1.00, 17.0] 11.0 [4.00, 14.0]
黑人


Yes 78 (72 %) 80 (73 %) 0.879
No 31 (28 %) 29 (27 %)
西班牙裔


Yes 11 (10 %) 11 (10 %) 1
No 98 (90 %) 98 (90 %)
已婚


Yes 25 (23 %) 26 (24 %) 1
No 84 (77 %) 83 (76 %)
无毕业文凭


Yes 73 (67 %) 75 (69 %) 0.885
No 36 (33 %) 34 (31 %)
1974年收入


Mean ± SD 2280 ± 4410 2730 ± 5800 0.518
Median [Min, Max] 349 [0, 21900] 0 [0, 35000]
1975年收入


Mean ± SD 1670 ± 2980 2040 ± 3890 0.425
Median [Min, Max] 206 [0, 13800] 0 [0, 25100]
1978年收入


Mean ± SD 4990 ± 6210 7110 ± 8720 0.0401
Median [Min, Max] 2160 [0, 21600] 4940 [0, 60300]

经过倾向性评分匹配获得的数据组间其他混杂因素均无统计学差异,而职业培训组1978年收入较对照组明显升高,提示接受职业培训可以增加收入水平。此处计算结果与上一篇文章SPSS实现PSM的计算结果有所不同,本文R语言中采用了 “nearest”算法计算倾向值,SPSS中算法与此不同,所以大家在选择方法及统计软件的时候需标注清楚,另外本文R语言的计算结果也是值得商榷的,并非采用了R语言得到了与回归分析一致的结果就万事大吉了,采用PSM的方法仍然要谨慎,上一篇文章中提到的PSM适用原则仍然很有必要()。

四、倾向性评分匹配的原理

真正做倾向性评分的代码其实只有一行

m.out <- matchit(data = lalonde,
formula = treat ~ age + educ + black + hispan + married + nodegree + re74 + re75,
method = "nearest",
distance = "logit",
replace = FALSE,
caliper = 0.05)

我们在看看匹配的数据是什么样子

head(lalonde_matched)
## treat age educ black hispan married nodegree re74 re75 re78
## NSW1 1 37 11 TRUE FALSE TRUE TRUE 0 0 9930.0460
## NSW2 1 22 9 FALSE TRUE FALSE TRUE 0 0 3595.8940
## NSW5 1 33 8 TRUE FALSE FALSE TRUE 0 0 289.7899
## NSW6 1 22 9 TRUE FALSE FALSE TRUE 0 0 4056.4940
## NSW7 1 23 12 TRUE FALSE FALSE FALSE 0 0 0.0000
## NSW10 1 33 12 FALSE FALSE TRUE FALSE 0 0 12418.0700
## distance weights
## NSW1 0.63876993 1
## NSW2 0.22463424 1
## NSW5 0.70163874 1
## NSW6 0.69906990 1
## NSW7 0.65368426 1
## NSW10 0.04292461 1

其实倾向性评分就是给通过结局变量和混杂因素formula = treat ~ age + educ + black + hispan + married + nodegree + re74 + re75做了一个回归分析,得到了distance就是该数据的倾向指数,然后依据倾向指数来均衡组间非试验因素的分布。

五、讨论与疑问

我们知道从总体抽取样本时候会出现抽样误差,那么从所有样本里面抽取匹配后的子样本,也同样存在导致误差可能(本质上是一种筛选样本造成的选择性偏倚),如何评估抽取样本是否有问题?常用的做法是通过第二种方法比如敏感度分析再次验证下结果。

另外一个重要指标是stand mean different,计算公式为(实验组均值 - 对照组均值)/ 实验组标准差。各匹配变量的值最好都要<0.1,我们的变量里面只有re75略大于0.1,匹配质量可以接受。

summary(m.out, standardize = T)$sum.matched
## Means Treated Means Control SD Control Std. Mean Diff.
## distance 0.5002592 0.4924892 0.2476051 0.035272557
## age 25.7064220 26.2201835 11.0272036 -0.071804344
## educ 10.2385321 10.2568807 2.6784589 -0.009125716
## blackFALSE 0.2660550 0.2844037 0.4532137 -0.050331164
## blackTRUE 0.7339450 0.7155963 0.4532137 0.050331164
## hispanTRUE 0.1009174 0.1009174 0.3026107 0.000000000
## marriedTRUE 0.2385321 0.2293578 0.4223617 0.023360849
## nodegreeTRUE 0.6880734 0.6697248 0.4724845 0.040249987
## re74 2732.5276193 2280.9101034 4410.6701851 0.092419194
## re75 2042.2571592 1667.3486967 2977.9886750 0.116458293
## eCDF Med eCDF Mean eCDF Max
## distance 0.018348624 0.022913183 0.082568807
## age 0.073394495 0.083355177 0.229357798
## educ 0.027522936 0.028134557 0.073394495
## blackFALSE 0.009174312 0.009174312 0.018348624
## blackTRUE 0.009174312 0.009174312 0.018348624
## hispanTRUE 0.000000000 0.000000000 0.000000000
## marriedTRUE 0.004587156 0.004587156 0.009174312
## nodegreeTRUE 0.009174312 0.009174312 0.018348624
## re74 0.036697248 0.057363592 0.220183486
## re75 0.027522936 0.031647168 0.091743119

参考文献

[1]. 周支瑞, 胡志德. 疯狂统计学. 长沙:中南大学出版社, 2018.

[2]. 周支瑞, 胡志德. 聪明统计学. 长沙:中南大学出版社, 2016.



*扫描下图二维码或点击“阅读原文”查看周老师全部课程