ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PSM倾向得分匹配实操指南:从原理到代码与稳健性检验

PSM倾向得分匹配实操指南:从原理到代码与稳健性检验 做实证研究、写论文的朋友应该都有过这种经历想评估某项政策、某个培训或者某类项目对结果的影响但手头数据不是随机实验得来的。比如你想看“参加职业培训能不能提高月收入”直接比较参加过和没参加过培训的两拨人会发现参训者的收入明显更高。但这就说明培训有效吗不一定。这些人可能本来就更有上进心、学历更高、家里条件更好收入高是这些因素造成的培训只是“背锅”的。这就是选择偏误Selection Bias。PSMPropensity Score Matching倾向得分匹配就是处理这类问题的经典工具。它的核心思路是虽然我们不能在同一时间看到同一个人“参训”和“未参训”的两种状态但我们可以在未参训的人群里找到一群在各方面特征上和参训者高度相似的人用他们当作参训者的“替身”——也就是反事实。这篇内容我会从原理、详细步骤、代码实现、质量检查到常见坑完整梳理一遍PSM实操流程适合刚接触因果推断的硕博生、需要用PSM完成实证作业的本科论文选手以及工作中涉及政策评估、运营效果分析的从业者。1. 先搞清楚PSM到底在解决什么问题1.1 因果推断的“反事实”困局要理解PSM先得接受一个事实因果推断的本质是反事实问题。你想知道处理A对结果Y的因果效应最理想的办法是同一个人身上同时出现“接受A”和“不接受A”两个平行世界的结果两者相减就是因果效应。但现实世界中每个人只能观测到其中一个状态——要么接受了处理要么没有。这个老大难问题在学术上的名字叫“缺失数据问题”每个个体都“缺了”对照状态下的那一半结果。随机实验RCT能解决这个问题因为随机分组保证了两组人在任何特征上平均而言都相同所以对照组的平均结果可以用来近似处理组“如果不接受处理”时的平均结果。但随机实验成本高、周期长很多场景下根本做不了。观察研究里谁进处理组、谁进对照组是“自选择”的比如更上进的人更可能报名培训而他们也更容易获得高收入这就带来了选择偏误。PSM的思路是给定一系列可观测的协变量X个体i进入处理组的概率就是倾向得分 e(X)P(D1|X)。如果两组人在倾向得分上足够接近那他们的可观测特征分布就趋于一致此时对照组的结果Y0就可以近似作为处理组“反事实”的结果。本质上PSM是用“降维”的方式把一个高维匹配问题转换成一个一维距离问题——这是它最大的价值点也是为什么在计量经济学、医学、社会学中被广泛使用的原因。1.2 适用场景的边界有些场合不该用PSM不是万能的它有严格的前提假设。最重要的一条叫“条件独立假设”Conditional Independence Assumption, CIA也叫“可忽略性假设”意思是给定协变量X之后处理分配D和潜在结果(Y0, Y1)相互独立。说得直白点就是你得把所有同时影响“是否接受处理”和“结果Y”的变量都观测到、都放进模型里。如果有一个关键混淆变量你没测到比如一个人的“内在驱动力”那PSM的匹配结果仍然是有偏的。正因为有这个前提下面几类场景要格外慎重第一数据里缺少关键变量如能力、动机、态度等心理变量时不建议单独使用PSM可以考虑结合双重差分DID做PSM-DID来缓解部分问题第二处理组样本量太小比如只有20个人受了处理这时候很难找到好的对照组匹配质量会很差第三协变量在两组之间的重叠区域太小即“共同支撑域”太窄说明两组人本质上就不是一类人强行匹配没有意义。PSM的价值也恰恰在这里它逼着你认真思考数据里有哪些变量、哪些变量可能造成混淆、协变量之间怎么平衡。就我的经验很多学生第一次跑PSM时把能想到的变量全塞进去结果倾向得分预测得“太好了”两组得分完全不重叠这就是典型的“为了预测而预测”反而违背了匹配的初衷。2. 核心步骤拆解一步步教你从原始数据到匹配样本2.1 第一步把处理变量和结果变量定义清楚这一步听起来简单但最容易出错。处理变量D必须是一个二值变量0/1比如“是否参加培训”“是否为某政策的试点城市”。如果处理变量是连续变量比如“政府补贴金额”那就不能直接用PSM需要先做二值化处理或者改用广义倾向得分Generalized Propensity Score。定义D时有个细节处理状态的“时点”和结果变量的“时点”必须严格错开。比如研究疫情补贴对企业营收的影响补贴是2020年发放的那结果变量营收应该用2020年甚至2021年的数据协变量则用2019年及之前的数据。如果协变量是补贴“之后”观测到的那就存在“后处理变量”Post-treatment Variable问题等于把结果的一部分也算进了匹配变量里会严重削弱因果解释力。结果变量Y没有太多硬性要求连续变量、二值变量都可以。需要注意的是一般用处理后的“变化量”而非“水平值”会更稳比如“培训前后的收入增长率”比“培训后的绝对收入”更能排除初始收入差异的干扰。2.2 第二步协变量选择宁缺毋滥还是多多益善协变量X的选择是PSM里最核心的建模环节直接影响倾向得分的质量。一个经验法则是选择那些理论上同时影响处理分配和结果变量的变量。它们让你有理由相信在控制了这些变量后处理分配近似于是随机的。实操中常见的错误是“把所有能用变量都丢进去”。这样做的坏处有两方面一是会导致倾向得分过度拟合两组得分的共同支撑域变得非常窄样本被大量丢弃二是把一些“工具变量”也选了进来比如某个变量只影响处理分配、不影响结果把它放进去反而会增加偏误而非减少偏误。真实的做法是参考已有文献列出你的“混淆变量集合”再逐步检验。另外要注意变量的类型处理连续变量可以直接进入logit模型但类别变量需要先做虚拟变量处理one-hot编码。交互项要不要加我的建议是加少量理论上重要的交互项即可比如“学历×年龄”这类有实际含义的交互纯粹为了提升模型拟合而加各路交互项会适得其反。2.3 第三步估计倾向得分Logit还是Probit到手的数据长这样一行一个个体每行包含D、Y以及X1到Xk的一串特征。估计倾向得分最常用的是Logit模型也有用Probit模型的。两者的差别主要在于假设误差项的分布不同Logit假设逻辑斯蒂分布Probit假设正态分布在PSM实操中二者的结果差异通常很小选哪个都行。拟合出参数之后对每个样本都计算一个预测概率 p_hat这就是倾向得分。在Stata里命令是logit D X1 X2 ...然后predict pscore在Python里就是statsmodels.api.Logit(D, X).fit().predict()。一个关键点倾向得分不要保留太多位小数用5~6位就够了。因为后面要做近邻匹配得分过于精细反而容易让最近邻“不够近”。有些人还会先把p_hat做一次logit变换转换成线性预测值linear predictor在某些匹配方法里用线性预测值会比直接用概率值更稳。2.4 第四步选择匹配方法没有银弹但有常用组合匹配方法常见的有四种最近邻匹配Nearest Neighbor Matching、卡尺匹配Caliper Matching、卡尺内最近邻匹配、核匹配Kernel Matching。最近邻匹配是最直观的对每个处理组样本找倾向得分最接近的那个对照组样本作为match。优点是简单缺点是可能匹配到得分距离依然很大的“远邻”因此有人加上了卡尺限制比如要求得分差异不超过0.05这叫做卡尺内最近邻匹配。核匹配则不是一对一而是用所有对照组样本的加权平均作为反事实权重取决于得分距离的核函数样本利用效率更高有效样本量大但解释起来稍复杂。我个人的经验是主结果建议用“1:4卡尺内最近邻匹配”卡尺取0.05或0.02再汇报相应的ATT值。为什么是1:4因为多配几个对照样本能降低方差但配太多会增加偏误1:4是一个普遍接受的折中。需要注意的是无论用哪种方法最终都要报告匹配后样本的平衡性检验结果否则审稿人会质疑你的匹配质量。3. 程序实现Stata和Python两套可直接跑的代码3.1 Stata实操psmatch2命令的完整用法如果你用的是Stata最常用的命令是psmatch2外部命令。第一次用时先安装ssc install psmatch2。核心语法如下* 假设数据含处理变量 train、结果变量 wage、协变量 age edu exper * 第一步查看数据基本分布 tab train * 第二步估计倾向得分并进行匹配1:4最近邻卡尺0.05 psmatch2 train age edu exper, outcome(wage) n(4) caliper(0.05) common ate * 第三步查看匹配后的平衡性检验 pstest age edu exper, both * 第四步查看共同支撑域情况 psgraph把上面的命令拆开看outcome(wage)是告诉命令结果变量是谁n(4)是1:4匹配caliper(0.05)限制得分距离在0.05以内common要求只在共同支撑域内匹配ate表示同时汇报ATE、ATT和ATU。如果不写ate默认只汇报ATT。运行完后psmatch2会在变量列表中生成几个新变量_pscore是倾向得分_weight是匹配权重_treated是处理组标记_support是是否在共同支撑域内。还有_id和_n1、_nn这类记录匹配结构的变量通常不用管但如果你后续要用匹配样本做回归分析可以用这些变量筛出匹配成功的样本。3.2 Python实操statsmodels加pandas手写PSMPython没有官方统一的PSM包不过用statsmodels加pandas手写一个也很直接。下面是我常用的模板已经带上了详细的注释import pandas as pd import numpy as np import statsmodels.api as sm from sklearn.linear_model import LogisticRegression from sklearn.neighbors import NearestNeighbors # 读入数据假设列包括 train, wage, age, edu, exper df pd.read_csv(data.csv) # 第一步估计倾向得分用statsmodels的Logit X df[[age, edu, exper]] X sm.add_constant(X) y df[train] model sm.Logit(y, X).fit() df[pscore] model.predict(X) # 第二步分离处理组和对照组 treated df[df[train] 1].copy() control df[df[train] 0].copy() # 第三步1:4最近邻匹配默认是往外找4个最近邻 # 注意这里直接对pscore做匹配 nn NearestNeighbors(n_neighbors4, metriceuclidean) nn.fit(control[[pscore]]) distances, indices nn.kneighbors(treated[[pscore]]) # 整理匹配结果每个处理组样本对应4个对照组样本 matched_control_ids [] for i in range(len(treated)): for j in range(4): matched_control_ids.append(control.iloc[indices[i][j]].name) matched pd.concat([treated, df.loc[matched_control_ids]]) # 第四步计算ATT平均处理效应 att matched[matched[train] 1][wage].mean() - matched[matched[train] 0][wage].mean() print(ATT:, att)这段代码的“手动感”很强好处是每一步都清楚发生了什么方便改造成自定义的匹配规则。如果不想手写也可以装psmpy这个库简单几行调用就好了但很多默认参数不够透明尤其适合快速出结果、验证思路的场景严谨汇报还是建议手写。3.3 代码运行后的重要输出解读无论Stata还是Python跑完PSM后第一件该看的事不是ATT而是共同支撑域和平衡性检验。Stata里psgraph画出来的图如果两组倾向得分的分布重叠面积很大说明匹配的基础很好如果处理组的高分区域对照组完全没有样本那这部分处理样本会被丢弃。pstest的表格里最需要关注的是匹配后的标准化偏差standardized bias经验准则是绝对值小于10%最好小于5%。如果某个变量匹配后偏差仍然大于10%说明匹配没有让该变量在两组间足够平衡需要回到协变量选择重新考虑。Python代码里对应的检查就是比较匹配前后处理组和对照组的协变量均值差异# 匹配前均值差异 print(匹配前:) print(df.groupby(train)[[age, edu, exper]].mean()) # 匹配后均值差异matched样本 print(匹配后:) print(matched.groupby(train)[[age, edu, exper]].mean()) # 算标准化偏差 for col in [age, edu, exper]: diff treated[col].mean() - matched[matched[train]0][col].mean() pooled_sd np.sqrt((treated[col].var() matched[matched[train]0][col].var()) / 2) print(f{col}: {abs(diff / pooled_sd):.4f})标准化偏差小于10%基本可以接受。如果达不到考虑调整卡尺、改用核匹配或者修改logit模型中的协变量组合比如加入平方项或交互项。4. 匹配质量的全面体检平衡性与稳健性4.1 平衡性检验不只是看P值很多人以为平衡性检验就是看匹配后处理组和对照组的协变量均值有没有显著差异于是只看t检验的P值。这是个常见的误区。P值受样本量影响很大样本量大时小差异也显著样本量小时大差异也不显著。所以现在规范做法是看标准化偏差standardized difference和方差比。标准化偏差的计算公式是两组均值差除以两组方差的平均平方根。判断标准是绝对值10%视为平衡良好5%更佳。方差比的合理区间一般是0.5到2接近1最好它在判断匹配后两组协变量的分布形态是否相似上尤其有效。我在实际项目中还习惯看一个东西匹配后协变量的“方差缩减比例”。如果匹配后某个连续变量的方差明显下降说明匹配把他拉得“太紧了”容易产生过度匹配的问题反而掩盖了真实变异。这时候换用核匹配或者用更大的卡尺通常能缓解。4.2 稳健性检验这几板斧必须上任何一篇用PSM的实证文章做完主回归只是第一步后面还得应付审稿人的“你是凑巧做出来的吧”这种质疑。稳健性检验的主要目的就是证明结论不是依赖于某个特定的匹配方式或卡尺选择。第一板斧换匹配方法。主结果用1:4卡尺最近邻那稳健性就换1:1最近邻、换半径匹配卡尺0.02、换核匹配甚至换马氏距离匹配看ATT的符号和显著性是不是基本一致。如果换了方法结果就崩了说明你匹配的基础不够牢靠。第二板斧换倾向得分模型。主结果用Logit那就把协变量加平方项、加交互项换个设定再跑一遍。记住PSM的理论要求不是“预测得准”而是“平衡得好”所以你甚至可以在模型里加一些只影响结果不影响处理的变量看看结果稳不稳。第三板斧敏感性分析。常用的是Rosenbaum边界敏感性分析Rosenbaum Bounds在Stata里用rbounds命令在R里用rbounds包。它的逻辑是假设存在某个未观测到的混淆变量它的影响强度要达到多大才会翻转你的结论。如果临界值Gamma在1.5以上结论依然稳那就算不错的结果。第四板斧安慰剂检验。比如假设处理发生在真实处理前的某个时点重新做一遍PSM如果这时ATT不再显著说明原结果不是某种时间趋势带来的假象。这个做法在政策评估类研究中尤其常见。5. 实操中最容易踩的坑整理成速查表5.1 问题排查清单我见过太多人在PSM上翻车整理一个速查表照着排查你的数据和处理流程。症状可能原因解决方法倾向得分两组完全分离协变量中混入了“后处理变量”或工具变量删除后处理变量筛查协变量定义时点匹配后样本量骤减卡尺设得太小或共同支撑域太窄调大卡尺换核匹配检查协变量是否过度超标平衡性检验不过协变量选择不当或漏了重要交互项加入平方项/交互项重选协变量ATT符号反复横跳匹配方法敏感样本量太小检查处理组样本量考虑1:4或多匹配优先看签到首尾结果结果显著但审稿人不认没做敏感性分析补Rosenbaum边界分析、安慰剂检验报告了个ATE但没说明意义混淆了ATT/ATE/ATU明确研究问题一般发表多用ATT这些坑我基本都踩过一遍。最典型的是一开始我习惯把“是否已婚”“是否有孩子”这类后续状态变量也当作协变量结果倾向得分预测得过于精准处理组和对照组完全不重叠折腾了半天才发现是变量时点定义错了。5.2 三个容易被忽略的实操细节细节一匹配过程使用的变量顺序会影响结果吗在平衡性检验里变量的顺序不会影响匹配本身但会影响你观察的便捷度。我习惯把最重要的混淆变量放在最前面检验方便快速判断匹配效果。细节二样本权重怎么处理如果原始数据自带抽样权重直接忽视它跑PSM会带来偏误。正规做法是把权重纳入倾向得分的估计或者进行加权匹配。用Python做的话要手动把样本权重喂进Logit的sm.WLS用Stata则在psmatch2前通过pweight提前设定。细节三匹配完之后的“标准误问题”。匹配后的样本会形成“配对结构”直接做t检验或回归时把样本当成独立样本处理标准误会偏小。现在规范的做法是用Abadie-Imbens稳健标准误或者直接在匹配样本上做带cluster的回归聚类到配对ID。这个是很多论文初稿里都会被批的点要格外上心。6. PSM的表现形式可视化与论文汇报6.1 用图讲清匹配效果审稿人看PSM结果普遍希望看到直观的匹配质量图。最基础的是倾向得分分布的密度图——处理组和对照组各画一条KDE曲线匹配前两条曲线差异越大匹配后重叠度越高越理想。另一个常用的是“Jitter图”或“点图”把每个样本按倾向得分排开分别用不同颜色标出匹配前被丢弃的点、匹配成功点、共同支撑域外的点一眼就能看清样本损失情况。在Python里画图很简单import matplotlib.pyplot as plt import seaborn as sns # 匹配后的倾向得分分布 sns.kdeplot(datatreated, xpscore, labelTreated, fillTrue) sns.kdeplot(datacontrol, xpscore, labelControl, fillTrue) plt.axvline(df[pscore].min(), linestyle--, colorgray) plt.axvline(df[pscore].max(), linestyle--, colorgray) plt.legend() plt.xlabel(Propensity Score) plt.title(PS Distribution after Matching) plt.show()Stata里psgraph命令则是现成的跑完psmatch2之后直接执行就会出一张漂亮的分组柱状图直接在论文中能用。6.2 论文里的标准汇报格式规范的实证论文里PSM结果部分至少要包含四块内容样本量与匹配设定、倾向得分模型的变量列表、平衡性检验表、ATT及标准误。还有不少期刊要求你额外报告匹配前后协变量标准化偏差的“Love plot”这个图把所有协变量的标准化偏差以散点形式画出来左边是匹配前右边是匹配后清晰直观。再补充一个常见误解ATT是“处理组中的平均处理效应”不是说全体样本的效应。当你研究的政策只覆盖了部分群体时汇报ATT通常更有政策含义。比如培训项目面对的是弱势群体ATT反映的是“参与培训的人相比他们自己不参加培训时多获得了多少收入”这才是政策制定者最需要知道的参数。7. 写在后面几顿毒打换来的实操心得PSM这套方法粗看流程简单——估计得分、匹配、算效应、做检验好像一天就能跑完。但真正用到自己的数据上你会发现大部分时间都花在了“确认自己有没有用错”上面。变量时点对不对、协变量遗漏不遗漏、匹配方法适不适合、标准误要不要聚类每一步都是一道选择题选错了整个结果都不作数。我个人在完成过几个项目之后的体会是PSM说到底是一个“用可观测变量模拟随机化”的工具它不能替代随机实验也不能解决所有遗漏变量问题。在报告结果时保持这种清醒尤为重要别把“匹配后两组均衡了”等同于“这就是因果”。论文里老老实实写清假设和局限反而比强撑着说“我证明了因果关系”更让审稿人信服。最后再分享一个小技巧处理缺失值的时候别急着把有缺失的样本整行删除。试试多重插补Multiple Imputation之后再跑PSM有时候能救回不少样本量匹配结果也会更稳定。这个方法在很多计量软件里都有现成实现Python里可以用statsmodels的MI模块或者sklearn的IterativeImputer操作起来并不复杂值得一试。
返回列表