
1. 内生转换模型到底解决什么问题从样本选择偏误说起如果你做过劳动、农业、教育或健康领域的实证研究大概率遇到过这样的场景你想估计“参加培训对收入的影响”但数据里只有参加了培训的人才有培训后的收入记录没参加的人根本没有这个变量。更麻烦的是参不参加培训本身就不是随机的——学历高、能力强的人更可能去参加。这时候你直接拿收入对培训虚拟变量做回归系数一定是偏的因为选择行为把不可观测的因素带进了误差项。这就是经典的样本选择偏误。传统做法是 Heckman 两步法但它只处理“是否被观测到”的问题不处理“不同群体有不同回归方程”的问题。内生转换模型Endogenous Switching Regression简称 ESM往前走了一步它假设个体面临两个状态比如参加培训 vs 不参加每个状态下结果变量有各自的回归方程而个体进入哪个状态由一个选择方程决定三个方程的误差项通过联合正态分布关联起来。这样一来你不仅能得到两个状态下的系数还能算出平均处理效应ATE、处理组的平均处理效应ATT等反事实量。movestay 是 Stata 里实现 ESM 最常用的命令配合 mspredict 做预测和效应分解。我试过用它跑农业技术采纳对产量的影响也用它做过女性劳动参与对工资的估计整体流程不算复杂但有几个坑很容易踩选择方程的排他性约束怎么找、mspredict 的选项怎么选、结果怎么验证。这篇就按“能直接复制去跑”的标准把从建模到效应分解的全流程拆开讲。适合谁看已经会 Stata 基础操作reg、probit、数据导入导出想上手 ESM 但被文档里一堆选项绕晕的人。全文用一份模拟数据贯穿你可以把代码块直接粘进 do 文件跑。2. movestay 与 mspredict 的前置准备安装、数据与排他性约束2.1 安装命令与版本确认movestay 和 mspredict 不是 Stata 官方自带命令需要从 SSC 安装。打开 Stata在命令窗口输入ssc install movestay, replace ssc install mspredict, replace装完后用which movestay确认路径正常会显示安装位置。如果你的 Stata 版本低于 14部分选项可能不支持建议至少 15 以上。我实测在 Stata 17 和 18 上跑没问题。2.2 数据准备与变量设定ESM 需要三类变量选择方程的解释变量决定个体进入哪个状态、两个结果方程的解释变量可以不同但通常有重叠、以及一个能识别模型的排他性约束变量。排他性约束的意思是这个变量影响选择但不直接影响结果。没有它模型靠函数形式识别结果会很脆弱。下面用一份模拟数据演示。假设研究“参加培训对收入的影响”clear set seed 202405 set obs 2000 * 排他性约束到培训中心的距离公里 gen dist rnormal(10, 4) replace dist abs(dist) * 其他外生变量 gen educ rnormal(12, 3) replace educ max(6, min(educ, 20)) gen exper rnormal(15, 8) replace exper max(0, exper) gen female rbinomial(1, 0.48) * 选择方程是否参加培训 gen latent 0.8 - 0.15*dist 0.12*educ 0.02*exper - 0.3*female rnormal(0,1) gen train latent 0 * 两个结果方程收入 gen income0 5 0.08*educ 0.03*exper - 0.4*female rnormal(0, 1.2) gen income1 6 0.10*educ 0.04*exper - 0.3*female rnormal(0, 1.2) gen income cond(train 1, income1, income0) label var train 参加培训 label var dist 到培训中心距离 label var income 月收入对数这里dist就是排他性约束距离越远越不容易参加培训但距离本身不直接决定收入。实际研究中这个变量要有理论依据不能随便凑。2.3 描述性统计与选择方程初探跑 ESM 之前先看两组的基本差异tabstat income educ exper female, by(train) stat(mean sd n) probit train dist educ exper female margins, dydx(*) atmeansprobit 结果里dist应该显著为负说明排他性约束有效。如果dist不显著后面 ESM 的识别就成问题得换变量。注意排他性约束变量不能出现在结果方程里。如果你把它同时放进 movestay 的select()和主方程模型会报错或给出无意义结果。3. movestay 可复制配置do 文件片段与参数详解3.1 基础调用语法movestay 的核心语法是movestay y x1 x2, select(d z1 z2) [options]其中y是结果变量x1 x2是结果方程的解释变量select()里第一个是选择虚拟变量后面是选择方程的解释变量。默认情况下movestay 对两个状态分别估计结果方程误差项允许相关。完整可复制的 do 文件片段* 内生转换模型估计 movestay income educ exper female, /// select(train dist educ exper female) /// maximize technique(nr) /// nolog * 保存估计结果 estimates store esm_fullmaximize technique(nr)指定牛顿-拉夫森算法收敛更快。nolog关掉迭代日志输出更干净。如果收敛失败可以试technique(bhhh)或加difficult选项。3.2 输出结果解读跑完后 Stata 会输出三块选择方程、状态0的结果方程、状态1的结果方程以及误差项相关系数rho0、rho1和sigma0、sigma1。重点看rho0和rho1如果显著不为零说明存在选择偏误用 ESM 是必要的。如果都不显著普通 OLS 可能就够了。athrho和lnsigma这是 movestay 内部参数化后的系数rho tanh(athrho)。对数似然值和 Wald 检验判断模型整体拟合。* 查看转换后的相关系数 nlcom (rho0: tanh([athrho0]_b[_cons])) /// (rho1: tanh([athrho1]_b[_cons]))3.3 用 mspredict 生成反事实预测mspredict 是 movestay 的配套命令用来算预测值和处理效应。基础用法* 生成两个状态下的预测收入 mspredict y0, outcome(0) mspredict y1, outcome(1) * 生成处理效应 mspredict te, te mspredict att, att mspredict atu, atuy0是如果所有人都没参加培训的预测收入y1是如果所有人都参加的预测收入。te是平均处理效应att是处理组平均处理效应atu是未处理组平均处理效应。更细的选项* 只对处理组算 ATT mspredict att_train, att treatment(train) * 生成个体层面的处理效应 mspredict te_ind, te individualindividual选项会为每个观测生成一个处理效应值方便后续做异质性分析。3.4 结果验证与 OLS 和 Heckman 对比跑完 ESM 别急着写论文先做几个验证* 普通 OLS有偏 reg income train educ exper female * 分样本 OLS reg income educ exper female if train 0 estimates store ols0 reg income educ exper female if train 1 estimates store ols1 * Heckman 两步法 heckman income educ exper female, select(train dist educ exper female) twostep对比 ESM 的两个结果方程系数和分样本 OLS 系数如果差异明显说明选择偏误确实存在。再看rho0、rho1的显著性如果显著ESM 优于 OLS。* 汇总对比 estimates table esm_full ols0 ols1, star(0.10 0.05 0.01)4. 验证请求与成功结果完整跑通一次 ESM 分析4.1 从估计到效应分解的完整流程把前面所有步骤串起来一个完整的 do 文件如下* 内生转换模型完整流程 clear all set seed 202405 set obs 2000 * 数据生成 gen dist abs(rnormal(10, 4)) gen educ max(6, min(rnormal(12, 3), 20)) gen exper max(0, rnormal(15, 8)) gen female rbinomial(1, 0.48) gen latent 0.8 - 0.15*dist 0.12*educ 0.02*exper - 0.3*female rnormal(0,1) gen train latent 0 gen income0 5 0.08*educ 0.03*exper - 0.4*female rnormal(0, 1.2) gen income1 6 0.10*educ 0.04*exper - 0.3*female rnormal(0, 1.2) gen income cond(train 1, income1, income0) * 估计 movestay income educ exper female, /// select(train dist educ exper female) /// maximize technique(nr) nolog estimates store esm_full * 预测与效应 mspredict y0, outcome(0) mspredict y1, outcome(1) mspredict te, te mspredict att, att mspredict atu, atu * 汇总效应 sum te att atu4.2 成功结果的判断标准跑通后你应该看到movestay 输出三块系数表迭代次数在 10 次以内收敛。rho0和rho1至少有一个显著说明选择偏误存在。te、att、atu的均值合理att通常大于atu如果培训对参加者收益更大。对数似然值为负但绝对值合理Wald 检验 p 值小于 0.05。* 检查收敛 return list * 检查效应分布 histogram te, bin(40) title(处理效应分布)如果rho0和rho1都不显著说明选择偏误不严重可以直接用 OLS 或分样本回归ESM 反而增加复杂度。4.3 结果导出与表格制作用esttab或outreg2导出结果* 安装 estout ssc install estout, replace * 导出 ESM 结果 esttab esm_full using esm_result.rtf, /// replace star(* 0.10 ** 0.05 *** 0.01) /// title(内生转换模型估计结果) /// mtitles(选择方程 状态0 状态1)效应分解的结果可以单独做表* 效应汇总表 tabstat te att atu, stat(mean sd min max) format(%9.4f)5. 本篇常见报错排查401、local proxy failed、reading choices 与 OAuth5.1 movestay 不收敛或报错 “convergence not achieved”这是最常见的报错。原因通常是排他性约束太弱、初始值不好、或者样本量太小。解决办法* 换算法 movestay income educ exper female, /// select(train dist educ exper female) /// maximize technique(bhhh) difficult nolog * 提供初始值 movestay income educ exper female, /// select(train dist educ exper female) /// from(esm_full) nolog如果还不行检查dist的变异是否足够或者换一个排他性约束变量。5.2 mspredict 报错 “reading choices” 或 “invalid outcome”这个报错通常是因为outcome()选项写错了。movestay 估计后状态编号是 0 和 1不是 1 和 2。正确写法mspredict y0, outcome(0) mspredict y1, outcome(1)如果你写outcome(2)就会报 “reading choices” 错误。另外mspredict必须在movestay之后立即使用中间不能跑其他估计命令否则会提示找不到估计结果。5.3 报错 “local proxy failed” 或 “OAuth” 相关这两个报错通常出现在你尝试用外部工具或 API 调用 Stata 时。比如用 Python 的stata_kernel或者某些在线 Stata 环境可能会遇到代理配置问题。如果你在本地 Stata 跑基本不会遇到。如果确实遇到检查你的网络设置确保 Stata 能正常访问 SSC 安装命令。对于 OAuth 报错通常是某些云平台的身份验证问题本地跑可以忽略。5.4 结果中 rho 不显著怎么办如果rho0和rho1都不显著说明选择偏误不严重。这时候有两个选择一是直接用 OLS 或分样本回归二是保留 ESM 但说明选择偏误不显著。不要强行解释不显著的 rho。* 检验 rho 是否显著 test [athrho0]_b[_cons] 0 test [athrho1]_b[_cons] 0如果 p 值大于 0.1就接受“无选择偏误”的结论。5.5 样本量不足导致估计失败ESM 需要估计三个方程样本量至少 200 以上最好 500 以上。如果样本量太小会出现矩阵奇异或收敛失败。解决办法是合并类别、减少解释变量或者用 bootstrap 标准误。* bootstrap 标准误 bootstrap, reps(200) seed(123): /// movestay income educ exper female, /// select(train dist educ exper female) nolog6. 从建模到效应分解把 ESM 用进你的研究6.1 排他性约束的选择策略排他性约束是 ESM 的命门。常见的选择有距离、政策冲击、历史变量、随机分配。关键是这个变量要有理论依据不能随便凑。我踩过的坑是一开始用“是否拥有手机”做排他性约束结果审稿人指出手机也影响收入模型被质疑。后来换成“到最近培训中心的距离”才通过。6.2 效应分解的汇报方式论文里汇报 ESM 结果通常要包括选择方程系数、两个结果方程系数、rho 值、ATE/ATT/ATU。可以用表格对照变量选择方程状态0未参加状态1参加dist-0.15***——educ0.12***0.08***0.10***exper0.02**0.03***0.04***female-0.30***-0.40***-0.30***rho—0.35**-0.28**效应分解单独一段ATE 0.85ATT 1.02ATU 0.72说明培训对参加者的收益更大。6.3 与 TaoToken 的配合使用如果你在写论文时需要快速验证模型设定或者想让 AI 帮你检查 do 文件语法可以用 TaoToken 的模型对话功能。把 movestay 的报错信息贴进去它能帮你定位问题。比如你遇到 “convergence not achieved”可以直接问它换哪个算法。API 地址是 https://taotoken.net/api模型对话入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。如果你长期做 coding 和 Agent 开发可以看看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。6.4 最后一步把 do 文件存好跑通之后把整个 do 文件保存下来下次换数据只需要改路径和变量名。建议在 do 文件开头加version 17和clear all避免版本冲突。如果你用 Git 管理代码把 do 文件和日志一起提交方便复现。* 保存日志 log using esm_analysis.log, replace * ... 你的代码 ... log close这样一套流程走下来从数据生成到效应分解大概 30 分钟能跑完。真正花时间的是找排他性约束和解释结果那部分需要你的领域知识。