倾向得分匹配与广义倾向得分匹配的Stata实操指南

发布时间:2026/10/4 12:02:50
倾向得分匹配与广义倾向得分匹配的Stata实操指南 1. 政策评估的老大难选择偏差到底怎么破做政策评估、项目效应分析的人八成都会碰到一个尴尬局面数据拿到了回归也跑完了结果却被审稿人或者领导一句话怼回来——“你有没有处理选择偏差”这句话背后的逻辑其实很朴素。假设我们要评估一项职业培训对收入的效应最直观的做法是把参加培训的人处理组和没参加培训的人对照组的收入做个比较。但问题在于参加培训的人往往是主动报名的这些人可能本身就更有上进心、学习能力更强、家庭条件更好。换句话说处理组和对照组从一开始就不在同一基准线上收入差异里既有培训的功劳也有这群人本身“更优秀”的贡献。这就是选择偏差。处理选择偏差的经典路径有很多工具变量、双重差分、断点回归、随机对照试验。但每一种方法都有自己的前提条件和使用场景。随机对照试验最干净可惜现实中很难做到让政策落地时还老老实实抽签工具变量难在找到一个既影响处理又只影响结果的工具双重差分要求平行趋势假定成立数据太短时也不好使。相比之下匹配法Matching是一个“条件更弱、更依赖数据”的策略而其中应用最广的就是倾向得分匹配Propensity Score MatchingPSM。PSM的思路一句话可以概括既然处理组和对照组在很多特征上不一样那我就把特征相似的个体配成对再比较他们在结果上的差异这样不就相当于“控制住那些特征”了吗听起来像在数据里玩连连看实际做起来也确实有这个味道。而广义倾向得分匹配Generalized Propensity Score MatchingGPSM则是把匹配法从“参加/不参加”的二元处理场景延伸到“处理强度”是连续变量的情况比如补贴金额、用药剂量、教育年限这类变量。这个扩展在实际研究中特别关键因为政策评估不只是关心“有没有用”更常需要回答“用多少效果最好”。这篇内容前面部分是罗嗦后面全部是实操。我会带你从PSM的基本原理到Stata里的经典命令再到GPSM的完整流程和代码一步步走一遍。用的案例都是平时研究中常见的类型就业培训、补贴强度、政策剂量等。代码可以直接复制去跑参数选择的逻辑我也会写清楚。适合正在写实证论文的硕博生、做项目评估的科研人员以及想在因果推断里找一套可靠方法的朋友收藏备用。1.1 为什么不能直接比较均值的差异先用一个数字例子把问题说明白。假设某市推行一项创业扶持计划研究目标是估算这项计划对参与者年收入的因果效应。现在有两组人参与计划的创业者处理组和未参与计划的创业者对照组。处理组的平均年收入是18万元对照组的平均年收入是12万元。如果直接相减得到6万元的处理效应你会兴高采烈地说“创业扶持计划让创业者收入提高了6万”但冷静想想这个差异可靠吗参与扶持计划的人可能更年轻、学历更高、家庭资产更厚甚至所在行业本身就更赚钱。这些特征同时影响了“是否参与”和“年收入”于是6万元的差异里至少有相当一部分来自这两组人本身的系统差异而不是政策的效果。用计量语言说处理分配不是随机的处理变量D与潜在结果之间存在相关性直接用Y的均值差作为效应估计是有偏的。这种偏差在实证分析里无处不在。政策不是天上掉下来的而是人、组织甚至市场机制共同选择的结果。只要存在选择过程简单均值比较就会失真。PSM要解决的就是这个问题通过某种方法在观测数据中重新构造一个“接近随机分配”的比较场景。不是说PSM能完全消除偏差而是说在可观测特征范围内它能通过重构样本的方式让比较变得公平很多。1.2 匹配法的基本逻辑从高维匹配到“分数”压缩匹配法最朴素的形式是对特征完全相同的个体做配对。如果处理组里有一个30岁、本科、互联网行业的创业者对照组里也有一个30岁、本科、互联网行业的创业者那这两个人放在一起比较就很接近“控制了这些特征”的效果。这个方法叫精确匹配Exact Matching理论上很干净但实际使用时立刻会遇到一个麻烦特征一旦多了匹配对象就找不到。假设你有5个协变量每个变量只分3档那就有3的5次方等于243种组合。要在这个243个格子里面都为处理组的每个人找到对照组的配对者数据量要求会急剧上升。协变量增加到10个、20个组合数量暴增精确匹配基本失效。这就是所谓的“维度灾难”。Rosenbaum和Rubin在1983年提出了一个关键思路与其在多维空间里逐个匹配不如先把所有协变量压缩成一个一维的分数——个体接受处理的概率也就是倾向得分。这个分数的核心性质是在倾向得分相同的条件下处理组和对照组协变量的分布趋向一致就像随机分配一样。于是匹配就从“高维空间找邻居”简化成了“一维线上找邻居”操作难度大大降低匹配质量反而更可控。这个原理也是所有倾向得分类方法的地基。2. 经典倾向得分匹配在Stata里的完整操作PSM的原理不难难的是把原理落地到代码里。Stata里做PSM的命令有不少早期常用pscore、psmatch2Stata 13之后官方命令teffects psmatch也成为了很多人的选择。我个人习惯把psmatch2作为常规操作的默认选项因为它配套命令完善做匹配、算效应、画图、平衡性检验一条龙都能完成。下面按完整流程过一遍。2.1 模型设定与倾向得分估计第一步先要估计倾向得分。倾向得分本质上是条件概率P(D1|X)通常用Logit或Probit模型来估计。选择Logit还是Probit一般差别不大计量教科书里多数例子用Logit你也没有必要在这个细节上纠结太多关键是协变量要选对。协变量的选择直接决定匹配质量。原则是把所有同时影响处理分配和结果变量的可观测变量都放进去。少了变量匹配不彻底偏差残留多了变量也不行会加剧共线性甚至把工具变量类的东西误放进去导致效率损失。实操中常见的做法是参考相关文献把人口统计学特征年龄、性别、教育年限、社会经济特征收入、就业状态、家庭规模、地区特征城市层级、地区GDP水平等都纳入模型。对实验研究来说一个常规的倾向得分估计命令如下logit treat age educ income hukou urban, nolog predict ps, pr跑完后会生成一个预测变量ps这就是每个个体的倾向得分。建议画个图看一下处理组和对照组的得分分布twoway (kdensity ps if treat1, legend(label(1 已处理))) /// (kdensity ps if treat0, legend(label(2 未处理))), /// xtitle(倾向得分)如果两组分布形态差异明显、重叠区域很小说明匹配的前提已经出了问题。这时应该回头看协变量选择和样本定义是否有硬伤先解决数据问题再继续往下走。2.2 匹配方式与代码写法最近邻、卡尺、半径、核匹配得到倾向得分之后就要按分数进行匹配。psmatch2的基本语法如下psmatch2 treat age educ income hukou urban, outcome(wage) neighbor(1) caliper(0.05) common ate命令里的逻辑值得逐一说清楚。outcome指定结果变量neighbor(1)表示一对一最近邻匹配即每个处理组个体只找一个倾向得分最近的对照组个体配对。这种匹配方法最直观但也会存在一个问题如果最邻近的对照组个体与自己得分相差仍较大配对质量就不高。这时可以加上caliper(0.05)也就是卡尺匹配要求配对个体的得分差距不能超过0.05。相当于不但找人还要求“长得像”。如果不想追求一对一配对还可以用半径匹配用caliper(0.05)同时不限定邻居数量所有在半径范围内的对照样本都会被纳入权重或者用核匹配利用所有对照组个体、按倾向得分距离赋予不同权重。这些方法没有绝对优劣之分核心权衡是偏差与方差最近邻匹配偏差小但方差大核匹配方差小但可能引入偏差。稳健起见实证文章一般会同时报告多种匹配方式的结果看结论是否一致。跑完匹配后psmatch2会自动显示处理效应的估计结果。ATE是平均处理效应ATT是处理组平均处理效应。政策评估中更常关注ATT因为它回答的问题是“参加了培训的人他们的收入相较于假如没参加提高了多少”。命令里加ate参数才输出ATE不加的话默认只输出ATT。根据研究问题选择要汇报的指标不要两个都堆上去。2.3 平衡性检验别匹配完就急着汇报匹配完成后最重要的一步是平衡性检验。这里的逻辑是匹配的目标是实现处理组和对照组在协变量上的平衡——两组在年龄、学历、收入等特征上不再有显著差异。如果匹配后还有协变量明显不同那就意味着偏差没有消除干净估计结果仍然不可信。psmatch2配套的pstest命令可以直接完成这个检验pstest age educ income hukou urban, treated(treat)输出结果里会给出匹配前后每个变量的标准化偏差。经验准则是匹配后标准化偏差的绝对值小于10%就认为这个变量达到了较好的平衡同时看t检验的p值是否大于0.05如果匹配后不再显著说明平衡性可以接受。还有一个值得注意的问题是共同支撑域Common Support。psmatch2命令加common选项会让软件只保留倾向得分重叠区间的样本避免那些“找不到对手”的个体被强行配对。匹配过程中处理组里有些个体的得分远超对照组最高分或者对照组里有些个体的得分低于处理组最低分这些极端样本本身就不具备可比性。删掉它们虽然会减少样本量但能显著提升匹配质量。实际操作中我通常会在命令里加上common并在论文附表报告删除样本的数量。为了更直观地展示匹配质量可以用psgraph命令画平衡图。这个图会展示处理组和对照组的倾向得分分布以及共同支撑域的范围审稿人经常喜欢看这个图建议养成画图的习惯。3. 广义倾向得分匹配GPSM从0/1到连续处理变量经典PSM解决的是“做没做”的问题但现实研究里很多事情没有这么二元。政府补贴给企业补贴金额从几万到几百万不等医疗干预中患者的用药剂量各不相同家庭教育投入也是连续变化。这时关心的问题就变成了“多做了多少的效果是多少”。你当然可以把处理变量做离散化处理把补贴金额分成高、中、低三档但这样做会丢失信息而且分组标准带有很强的主观性。Hirano和Imbens在2004年提出了广义倾向得分匹配GPSM专门处理连续处理变量的因果效应问题。3.1 GPSM的核心思想GPSM的逻辑与PSM类似但做了关键拓展。对于二元处理变量倾向得分是条件概率而对于连续处理变量广义倾向得分被定义为处理变量在给定协变量条件下的条件密度值r(T)|X 的条件密度函数值记为R f(T|X)这里的逻辑依然是“控制协变量”。若处理强度与协变量有关那么直接比较高强度组和低强度组的平均结果会混入协变量的影响。通过控制广义倾向得分R相当于把处理变量中可由协变量解释的部分剥离掉剩下的变异近似于随机分配。于是比较同一条“得分线”上的不同处理强度就接近因果效应的估计。GPSM的适用场景要求处理变量T是一个连续变量且满足弱非混淆性假定weak unconfoundedness。这个假定的形式化表达是在控制协变量X之后处理变量T与潜在结果Y(t)条件独立。实际检验中很难直接验证这个假定只能尽量多地控制协变量并在论文里做敏感性分析。值得注意的是GPSM并不要求处理变量服从正态分布但软件在估计条件密度时需要假定某种分布形式。经验做法是如果处理变量是高度右偏的比如补贴金额就先取对数再建模。我做了很多项目后觉得这一步其实非常关键——不取对数你会发现后面的GPS估计和剂量响应函数全都偏得离谱。3.2 GPS的估计方法GPS的估计分为两步第一步对处理变量与协变量做回归得到条件期望和方差第二步根据分布假定计算出每个个体对应的条件密度函数值。当处理变量近似正态分布时可以用普通最小二乘回归估计条件均值再用残差的标准差估计条件标准差最后把观测值代入正态密度函数计算GPS。Stata里有一个专门的命令包叫doseresponse是Bia和Flores等学者为广义倾向得分匹配开发的操作起来非常方便。后续第4部分会完整展示它的用法。4. Stata中GPSM实操doseresponse命令全流程doseresponse命令由一系列子命令组成核心是DoseResponse和DoseResponse_Estimation、DoseResponse_Graph三个。第一次使用前先安装ssc install doseresponse装好之后我以一个实际研究场景为例演示全流程研究政府补贴率对制造业企业研发投入的影响。处理变量是补贴率s_rate结果变量是研发投入强度rd_intensity协变量包括企业规模size、企业年龄age、资产负债率lev、净资产收益率roe、行业竞争度hhi等。数据格式是截面数据处理变量为连续变量。4.1 第一步估计GPS先对处理变量建模。由于补贴率通常存在右偏问题先取对数再估计gen ln_s_rate ln(s_rate 1) DoseResponse ln_s_rate size age lev roe hhi, analysis(regress, treat(ln_s_rate))DoseResponse命令的作用是估计处理变量对协变量的回归同时得到每个个体的广义倾向得分。analysis选项里regress表示用OLS估计treat指定处理变量是哪一项。跑完后它会自动生成几个新变量其中最重要的就是GPS值后续步骤会用到。4.2 第二步估计剂量-响应函数接下来用DoseResponse_Estimation估计处理变量的因果效应。这个步骤的核心是在控制GPS的条件下估计处理变量与结果变量之间的关系。DoseResponse_Estimation, dose(ln_s_rate) gpscore(gpscore) outcome(rd_intensity) /// analysis(regress, treat(ln_s_rate)) minobs(5) power(3) /// nbins(10) predict(quadratic)参数的含义需要仔细说明。dose指定处理变量gpscore指定上一步生成的GPS变量outcome指定结果变量。analysis里的regress表示结果回归模型采用线性回归treat则指定处理变量。minobs(5)要求每个分段内最少包含5个观测避免某个段内样本太少导致估计不稳定。power(3)表示处理变量的多项式阶数为3次给剂量-响应关系更多灵活性不至于因为假设线性而漏掉非线性关系。nbins(10)是把处理变量分成10段用于后续检验平衡性。comparison选项用于输出平衡性检验结果强烈建议加上DoseResponse_Estimation, dose(ln_s_rate) gpscore(gpscore) outcome(rd_intensity) /// analysis(regress, treat(ln_s_rate)) minobs(5) power(3) /// nbins(10) predict(quadratic) comparison4.3 第三步画剂量-响应曲线估计完系数后用DoseResponse_Graph画图直观展示处理变量的剂量-响应关系DoseResponse_Graph这个图会把处理变量ln_s_rate放在横轴、结果变量rd_intensity放在纵轴同时给出置信区间。如果曲线显示补贴率对研发投入的影响是先升后降说明存在一个最优补贴区间这对政策启示非常有用。比如撤回一路高增的“补贴越多创新越强”的假设实际是过了某个峰值后政策效应递减这种非线性结论往往比单一系数更受期刊青睐。4.4 结果解读时容易犯的几个错误第一doseresponse估计出的系数不是直接可以像普通线性回归那样解释的。它给出的是剂量-响应函数上各个点对应的预测值应该看的是曲线形态和边际效应变化而不是某个回归系数。第二GPSM的结果对外观上的选择很敏感。处理变量的对数化、协变量的选择、多项式阶数power的高低、分箱数量nbins的大小都会对曲线形状产生影响。实操中建议做几组敏感性分析换用不同的power值2和4、调整nbins8和12、增减协变量看核心结论是否改变。如果曲线形状稳健结论就更加可靠。第三处理变量的支持域问题。如果某些处理强度下样本量极少GPS方法在这些区域的估计会很不可靠。读取DoseResponse_Graph输出的置信区间时如果发现区间开头的宽容纳了几乎整个纵轴说明那个区域的样本支撑严重不足解读时需要格外谨慎。5. 常见问题与排查技巧实录跑PSM和GPSM时新手容易在几个地方卡住。下面这些问题是我在实际操作中反复看到的也踩过不少次坑整理成速查表。5.1 psmatch2命令报错处理psmatch2在使用时经常会报这类错误last estimates not found。原因通常是你先运行了psmatch2然后又做了其他操作比如gen或summarize导致Stata内部保存的估计结果被覆盖。解决方法是把psmatch2和后续配套命令连在一起写或者重新跑一次psmatch2再执行后续操作。如果报错偏向于变量未定义那大概率是倾向得分变量名写错检查一下predict生成的变量名是否与命令中的一致。5.2 平衡性检验不通过怎么办匹配后标准化偏差大于10%是最常见的问题。处理办法有几个一是检查协变量选择是否漏掉了关键变量比如教育程度、就业状态、地区差异这些强力预测因子二是增加匹配方式——从最近邻匹配换成核匹配或半径匹配让匹配过程利用更多信息三是调整卡尺范围——卡尺太窄会让匹配样本减少、方差变大卡尺太宽又会放大偏差可以在0.02到0.1的区间里试探找到平衡点四是考虑做一些协变量的交互项或平方项让倾向得分模型的函数形式更灵活。有时两组倾向得分的重叠区域太小即使匹配后平衡性通过了样本代表性也值得怀疑。这时要回想研究设计的定义处理组和对照组来自同一个总体吗有没有样本框不一致的问题如果确实没有共同的样本基础就要重新审视识别策略而不是硬着头皮用匹配救场。5.3 GPSM的常见踩坑细节GPSM实操中最容易出问题的环节是doseresponse命令的安装和子命令顺序。有些版本的Stata特别是15以下不支持doseresponse中的新选项建议先把Stata升级到较新版本再检查ssc的最新更新。装好后务必按DoseResponse → DoseResponse_Estimation → DoseResponse_Graph的顺序依次运行。我曾经因为只改了参数就重新跑第二行结果软件还在用旧模型做预测整个结果面目全非。处理变量的分布形态也经常让人掉坑。原始处理变量如果严重右偏比如少数企业拿了巨额补贴、多数企业只有小额补贴这时直接用原始值建模会导致GPS估计失真。正确做法是先取对数变换让处理变量分布更接近正态。就算取对数后的模型仍然不是完美的正态OLS加残差正态近似的方案也能给出比原始变量更好的结果。对于GPSM结果有许多人会问怎么没有输出类似ATT的指标回答“平均处理效应是多少”这是因为GPSM的核心输出是剂量-响应函数关注的是在不同处理水平下结果变量的估计值以及曲线形态而不是一个单一的平均处理效应。如果想报告一个总体效应可以用加权平均的方式对不同处理水平下的效应做汇总但报告剂量响应曲线本身才是最标准、最容易传达信息的方式。5.4 一个完整的PSM参考代码模板最后分享一个我常用的PSM标准流程模板数据集为面板数据时先保留某一期截面然后按下面的顺序操作* 假设处理变量为treat结果变量为y协变量为x1 x2 x3 x4 logit treat x1 x2 x3 x4, nolog predict ps, pr * 查看倾向得分分布 twoway (kdensity ps if treat1, legend(label(1 处理组))) /// (kdensity ps if treat0, legend(label(2 对照组))), /// xtitle(倾向得分) * 最近邻匹配 psmatch2 treat x1 x2 x3 x4, outcome(y) neighbor(1) caliper(0.05) common ate * 核匹配 psmatch2 treat x1 x2 x3 x4, outcome(y) kernel common ate * 平衡性检验 pstest x1 x2 x3 x4, treated(treat) * 匹配前后样本变化 psmatch2 treat x1 x2 x3 x4, outcome(y) neighbor(1) common ate psgraph跑完这套流程把输出整理成表格匹配前处理组对照组的均值、标准化偏差、t值匹配后同样的三列指标再附上ATT值和标准误。这样一份实证表格已经足够应付大多数课程论文和期刊初稿的要求。我个人在实际操作中的体会是无论是PSM还是GPSM重要的从来不是命令怎么写而是你清不清楚自己在做什么。匹配法不是万能药它只能在可观测变量层面消除偏差对隐藏偏差无能为力。所以论文里一定要有意识地做敏感性分析比如尝试不同的匹配方法、调整卡尺和核函数带宽、变更协变量组合用结果的稳健性来弥补识别策略的先天局限。做完这些工作再回头审视你的因果参数结论会踏实得多。