免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

PSM倾向得分匹配实战:从原理到R代码的因果推断指南

PSM倾向得分匹配实战:从原理到R代码的因果推断指南 开头我先把我常用的一句话撂在这拿观察数据做因果推断PSM 倾向得分匹配Propensity Score Matching是性价比极高的第一站。不少朋友第一次接触 PSM是在论文实证或者项目评估里碰了钉子——想评估培训、补贴、改版、活动这类干预到底有没有用可手头数据是观察性的不是随机分组。直接比处理组和对照组均值结论总是被各种自选择偏差污染。比如参加职业培训的人本来就更有上进心、教育水平更高你看到培训后工资涨了到底该归功于培训还是归功于这群人本身就更可能涨薪PSM 干的事就是在没参加培训的人里逐一找出与参加者在可观测特征上“长得像”的对照构造一个事后对照组尽量逼近随机分组的效果。这篇文章我会用一份完全可复现的模拟数据把 PSM 从原理、变量筛选、Logit 建模、匹配、平衡性检验到效应估计和稳健性检验的完整步骤走一遍代码以 R 为主同时给出 Stata 和 Python 的对照思路。无论你是研究生、数据岗还是做政策评估的从业者照着这篇文章能直接搭出一条可落地的分析流程。1. 在动手跑代码前先搞懂 PSM 到底解决了什么问题1.1 为什么“直接比较均值”会失真假设你手头有一份 1000 人的数据记录了是否参加职业培训treat、培训后的月薪wage_post以及年龄、教育程度、工作经验、培训前工资、所在地区。你想知道培训对工资的因果效应。最直觉的做法是求均值差却忽略了一个关键事实谁参加了培训在观察数据里不是随机决定的。教育程度高、工作意愿强、所在地区经济好的人更可能主动报名培训而这些因素本身又会正向影响工资。于是你看到的均值差是“培训效应 选择效应”的混合体。除非你假设这些差异不存在否则均值差就是有偏的。正是为了处理这种由可观测特征带来的选择偏差PSM 才被设计出来。它的策略不是“假装两组一开始就一样”而是“在事后把两组修成看起来一样”。1.2 PSM 的三个成立前提无视它们等于瞎跑很多人一上手就直接跑matchit()运行结果也漂亮却根本不知道自己依赖了哪些假设。PSM 能成立至少需要三个前提条件独立假设Unconfoundedness给定所有可观测协变量 X处理分配与潜在结果相互独立。翻译成人话就是我们能在数据里找到信息足以解释谁参加培训、谁不参加培训且不存在既影响处理分配、又影响结果变量但没办法观测到的“隐形变量”。共同支撑假设Overlap / Common Support对任意协变量组合参加培训的概率既不是 0 也不是 1。如果某些人参加培训的概率接近 0他们可能在数据里根本找不到合适的对照匹配就会失效。SUTVA 假设个体处理效应稳定假设一个人的处理状态不影响其他人的潜在结果且处理本身是同质的。这三个假设里面条件独立假设是最致命的——因为它根本无法直接从数据里验证。这也是后来我要单独讲稳健性检验和敏感性分析的原因。所以记住PSM 不是万能药它的结论强度完全取决于你收集到的协变量有多全面。1.3 PSM 适合哪些任务不适合哪些任务PSM 适合的场景数据里有大量协变量处理组和对照组在协变量分布上重叠程度较好且结果变量是连续型或二元型。比如评估一项就业帮扶政策对收入的影响、某个 App 改版对留存的影响、某类营销券对复购率的影响都是典型的应用场景。PSM 不适合的场景协变量缺失严重或者连理论上有重要影响的变量都找不到这时候匹配只是“自欺欺人”。处理组和对照组重叠极差比如几乎只有高收入人群才接受该培训匹配后样本量骤减结论不具有外推性。处理分配本身与时间动态相关需要面板数据或事件研究法的场景PSM 显然不是最优解。我见过不少项目明明应该用工具变量或双重差分却硬用 PSM最后被审稿人一句话问住。所以先判断问题类型再谈方法。2. 数据准备和变量筛选这里出问题后面全部白搭2.1 用一份可复现的模拟数据走通全流程为了让你能原样复现我生成了一份模拟数据。真实的行业场景中你会把这份数据替换成自己的业务数据但字段结构是相通的。# 加载依赖包 library(MatchIt) library(cobalt) library(dplyr) library(ggplot2) library(lmtest) library(sandwich) # 固定随机种子保证结果可复现 set.seed(2025) n - 1000 # 协变量 age - rnorm(n, 35, 8) # 年龄 male - rbinom(n, 1, 0.5) # 性别 education - rbinom(n, 1, 0.4) # 1大专及以上 experience - rnorm(n, 10, 5) # 工作年限 pre_wage - rnorm(n, 5000, 1200) # 培训前月薪 region - rbinom(n, 1, 0.3) # 1东部 # 处理分配机制education、region、age、experience、pre_wage 共同影响是否参加培训 logit_treat - -1.2 0.6 * education 0.4 * region 0.02 * (age - 35) - 0.03 * (experience - 10) - 0.0004 * (pre_wage - 5000) treat - rbinom(n, 1, plogis(logit_treat)) # 结果变量真实处理效应设为 500 wage_post - 5000 800 * education 30 * (age - 35) 25 * experience 300 * region 500 * treat rnorm(n, 0, 800) dat - data.frame(id 1:n, treat, age, male, education, experience, pre_wage, region, wage_post)这样设计是有意为之education、experience、pre_wage、region 这些变量同时影响是否参加培训也影响培训后的工资于是普通均值差会把这些变量的贡献也混进处理效应里。真实处理效应被设定为 500这样后面你就能直观看出 PSM 到底有没有把偏差纠回来。2.2 协变量筛选的“结果导向”逻辑PSM 中协变量选择是门学问也是最容易犯错的环节。常见的错误思维是“既然倾向得分是做分类那就把所有变量都丢进 Logit 模型让模型自己挑。”这套思路放在预测建模里没错放在因果推断里就错了。正确的选择逻辑是协变量必须包含所有同时影响处理分配和结果变量的变量中心目标是平衡协变量分布而不是最大化处理分配的分类准确率。为什么这么说因为加入一个影响处理分配、但和结果完全无关的变量不会带来偏倚但会降低匹配效率——它可能会让两个“在因果上足够相似”的个体因为某个无关特征而匹配不上白白削减有效样本。反过来加入一个实际影响结果的变量哪怕它和处理分配的关系只是中等也必须放进模型否则匹配后的两组在这一变量上仍可能存在差异结果就有偏。还有一条重要原则倾向得分模型中不要加入“工具变量式”的变量——即只影响处理分配、不直接影响结果的变量。这样的变量放进去的后果是倾向得分方差被拉大共同支撑区域变窄匹配质量下降。这在因果推断文献里有明确警告。2.3 两类绝对不能往模型里放的变量第一类中介变量。如果某个变量本身就是“处理影响结果”路径上的一环放进协变量就等于切断了因果路径。举例评估培训对工资影响时“培训后的技能考试成绩”就是典型的中介——培训帮助你提高技能技能提高带来工资上涨如果在匹配时把技能成绩放进模型你匹配掉的就是培训的合理组成部分估计出来的效应就不再是总效应而是所谓的“直接效应”。第二类事后变量。一切发生在处理之后的变量只要它可能受处理影响都不能放进倾向得分模型。这一点在新手代码里特别常见因为数据表里变量很多一不小心就把“参加培训后的工作时长变化”这样的变量选进去了。正确做法是先画出变量之间的关系图明确每个变量的因果位置。哪怕只是纸笔画个 DAG有向无环图也比闭着眼睛瞎跑要稳得多。我个人的经验是先做一个理论上的变量清单再通过数据检查重叠和遗漏但因果位置必须由业务逻辑决定不由统计软件决定。3. 倾向得分模型Logit 回归跑出来的数字该怎么理解3.1 为什么是 Logit 而不是线性概率模型倾向得分的定义是给定协变量 X 的条件下个体接受处理的概率 P(T1|X)。估计这个概率业界默认用 Logit逻辑回归理由很直观线性概率模型LPM预测出来的值可能落到 [0,1] 区间之外处理概率不可能超过 1 或低于 0这会给匹配过程中的距离计算带来麻烦。Logit 模型天然把输出压缩在 0~1 之间预测值能直接解读为概率。当倾向得分非常接近 0 或 1 时Logit 的 S 型曲线能更好地刻画这种饱和效应。实际操作中你不一定只能用 Logit。用 Probit、CART、随机森林、GBM 都可以估计倾向得分。尤其是样本量大、协变量之间存在复杂非线性关系时GBM 这类机器学习方法的拟合效果常常更好。但从解释性和工程稳定性角度我日常分析里 80% 的情况都用 Logit因为它成熟、好解释、可复现性高。如果你不是为了卷预测精度Logit 足够用。3.2 倾向得分模型的 R 实现与结果解读# 估计倾向得分 ps_model - glm(treat ~ age education experience pre_wage region, data dat, family binomial(link logit)) # 把倾向得分加到原数据里 dat$ps - predict(ps_model, type response) # 看处理组与对照组的得分分布 summary(dat$ps[dat$treat 1]) summary(dat$ps[dat$treat 0])一个常见的问题变量要不要做交互项或多项式我建议先不要一开始就加太多。倾向得分模型的核心目的是达到协变量平衡而不是追求模型的似然函数最大。你可以从主效应模型开始做完匹配后看平衡性检验如果某个变量的标准化均值差依然超标再考虑加入它的交互项或平方项。这个“先简单后修正”的思路比一开始就堆砌高次项稳健得多。3.3 共同支撑域检查匹配前必做倾向得分算出来后第一步不是立刻匹配而是看两组得分的分布重叠情况。如果处理组的得分集中在 0.6~0.9对照组的得分集中在 0.1~0.4那你几乎找不到处在重叠区域的对照样本强行匹配只会得到大量低质量配对。ggplot(dat, aes(x ps, fill factor(treat))) geom_density(alpha 0.4) labs(x Propensity Score, y Density, fill Treat) theme_minimal()重叠越宽匹配越从容。如果重叠区域很窄通常的处理是限制分析样本到共同支撑范围内也就是只保留倾向得分落在两组得分交集内的个体。在matchit()中可以用discard both或discard control做截断但你要明白这会改变你估计的目标人群——你只能对“原本有可能接受处理”的那部分人做因果推断结论不能随意外推到所有人。4. 匹配怎么配四种方法的取舍和我常用的参数4.1 最近邻匹配1:1 vs 1:n 的代价最经典的匹配方法是最近邻匹配Nearest Neighbor Matching。它在处理组里逐个找倾向得分最接近的对照组个体形成配对。1:1 匹配最直观实现简单但样本利用率低处理组那部分匹配不到配对的人会被丢弃对照组里大量信息也被浪费。1:n 匹配比如一个处理组个体对 2~4 个对照组个体则能提高样本利用率减少估计方差代价是匹配质量稍微下降因为你要接受一个倾向得分没那么近的对照样本。我实际项目里的默认配置是 1:1 先跑一遍看平衡性如果样本量紧张、结果不稳定再换成 1:3 或 1:4。永远不要在不知道数据长什么样的情况下直接把 ratio 拉到 5那不是优化是偷懒。4.2 卡尺匹配卡尺取多大是有讲究的卡尺Caliper的定义很直白只有倾向得分差距小于指定阈值的配对才允许进入最终样本。它的作用和现实中对“长得像”的容忍限度是一样的。卡尺取值有两种主流做法。一种是在倾向得分0~1 区间上直接设一个固定阈值比较常见的是 0.05 或 0.1另一种是文献里常建议的做法——在 Logit 线性预测值尺度上取 0.2 倍标准差因为倾向得分在 0 附近非常敏感直接约束概率间隔偶尔不如约束对数优势比稳定。Austin2011等人研究过在多数场景下0.2 个线性预测标准差的卡尺能在偏差和方差之间取得较好平衡。卡尺太大等于没约束卡尺太小配对数量锐减估计方差爆炸。最稳妥的思路是跑一组卡尺敏感性比如 0.02 / 0.05 / 0.10看结论是否稳定而不是死守一个值。4.3 核匹配与完全匹配低损耗替代方案最近邻匹配的痛点在于“配对”是离散的、有损耗的。于是有了核匹配Kernel Matching和完全匹配Full Matching。核匹配的思路比较像加权一个处理组个体不强制只匹配给一个对照组个体而是以倾向得分距离为权重给一系列对照个体赋权距离越近权重越大。它的优势是样本损耗极小估计方差更小。完全匹配更进一步它把所有处理组和对照组样本都保留下来分成若干个子组在子组内权重可以自动调整。大量模拟研究表明完全匹配经常能取得比最近邻匹配更低的均方误差尤其在共同支撑区间较宽的数据上。这些方法之间不是“谁必然更好”的关系而是一种权衡。匹配后样本量充足时最近邻加卡尺更透明样本量吃紧、倾向得分重叠宽时完全匹配或核匹配更稳。4.4 MatchIt 中匹配参数实现代码# 方案一1:1 最近邻匹配倾向得分尺度卡尺 0.05 m1 - matchit(treat ~ age education experience pre_wage region, data dat, method nearest, distance glm, estimand ATT, ratio 1, caliper 0.05, replace FALSE) # 方案二1:3 最近邻匹配 m3 - matchit(treat ~ age education experience pre_wage region, data dat, method nearest, distance glm, estimand ATT, ratio 3, caliper 0.05, replace FALSE) # 方案三完全匹配 m_full - matchit(treat ~ age education experience pre_wage region, data dat, method full, distance glm, estimand ATT) # 查看基本匹配结果 m1输出里你会看到匹配前Sample处理组和对照组数量、匹配后实际使用数量、是否已检查共同支撑等信息。注意一个原则后续做效应估计时这些匹配对象返回的权重和配对关系都必须原样使用不要手动改样本。这里还需要解释一个概念estimand参数。estimand ATT指的是“受处理者的平均处理效应”也就是“参加了培训的人因为参加培训多获得了多少收益”这正是大多数政策评估关心的参数。如果你关心的是整个总体随机分配后带来的总效应用estimand ATE但它的匹配逻辑和权重计算完全不同对共同支撑要求也更高。5. 平衡性检验匹配成不成功不靠肉眼靠指标5.1 标准化均值差SMD的原理与阈值匹配做完之后第一个问题是处理组和对照组在各协变量上是否已经不显著地不同了很多人会下意识想到 t 检验但我建议你不要用 t 检验作为主要判据。t 检验有个致命问题它对样本量极其敏感。样本量大的时候哪怕两组均值差异非常小也会显著样本量小的时候哪怕差异明显也可能不显著。这跟你想考察的“实际差异”是两回事。行业惯例是看标准化均值差Standardized Mean Difference, SMD公式是SMD (mean_treat - mean_control) / sqrt((sd_treat^2 sd_control^2) / 2)简单说就是把两组均值的差异除以合并标准差得到一个量纲无关的效应量。经验规则是匹配后所有协变量的 |SMD| 0.1就认为平衡质量可接受。0.1 这个值来源于文献虽然有一定随意性但已经是风险管理领域广泛接受的默认线。5.2 用 Love Plot 直观确认匹配质量在 R 里用 cobalt 包可以一次性输出平衡性汇总并画出 Love Plotbal.tab(m1, un TRUE, thresholds c(m 0.1)) love.plot(m1, thresholds c(m 0.1), binary std)Love Plot 会把每个协变量匹配前后的 SMD 画在同一张图上你会清晰地看到匹配前education、pre_wage 这些变量的点可能在 0.1 阈值线之外匹配后所有点都应该回到 0.1 线以内。如果在匹配后某个变量仍然明显越线说明倾向得分模型不够好需要回去调整模型设定。5.3 如果平衡性不过关下一步做什么平衡性不过关时优先尝试顺序如下检查倾向得分模型的设定添加交互项或非线性项。换匹配方法从最近邻换到完全匹配或核匹配。调整卡尺或者匹配比例放宽至 1:3 或 1:4换取更多配对。检查共同支撑考虑用discard剔除极端倾向得分样本。这里有一个我反复强调的点**不要为了提高匹配数量而牺牲平衡性也不要为了追求全部协变量完全平衡而让样本量掉到只剩原数据三分之一。**两者之间要有一个业务可接受、汇报可解释的平衡点。6. 处理效应估计匹配之后不能只做个 t 检验6.1 效应指标先选清楚ATT 还是 ATE很多人匹配完之后直接t.test(wage_post ~ treat, data mdata)这不能说错但很粗糙。先想清楚你要估计哪个因果参数ATE平均处理效应随机抽取一个人无论他是否实际参与培训干预带来的平均增益。ATT受处理者的平均处理效应实际参与培训的人培训带来的平均增益。ATC未受处理者的平均处理效应实际没参与培训但假如参与了会获得的增益。在matchit()中默认是estimand ATT这符合大多数政策评估的语义。完整汇报时最好在文章里写明“本文估计的是 ATT”避免读者混淆。6.2 匹配后回归代码、权重与聚类标准误匹配后的理想境地是协变量完全平衡这时候直接做均值差也行。但现实数据中总会有微小的残留不平衡所以我习惯在匹配之后再加一层回归调整这叫“匹配后回归”。理由很简单回归可以吸收掉匹配后仍然存在的微小协变量差异得到更精确的估计。另一个关键细节是权重的使用。匹配对象中每个样本的权重并不一样的尤其当使用 1:n 匹配或完全匹配时。match.data()返回的weights列必须传给回归函数否则你就是在不用权重的错误样本上跑结果。还有标准误。匹配后样本是配对的处理组和对照组的观测不再独立忽略配对结构直接得到标准误会偏小。所以要用聚类标准误聚类层级就是匹配对subclass。# 提取匹配后数据 mdata - match.data(m1, weights weights) head(mdata) # 匹配后回归加入权重并按匹配对聚类 fit_psm - lm(wage_post ~ treat age education experience pre_wage region, data mdata, weights weights) coeftest(fit_psm, vcov vcovCL, cluster ~subclass)coeftest()函数会输出每个回归系数的估计值、稳健标准误、z 值和 p 值。重点看treat那一行。6.3 结果解读从系数看到因果效应以我们的模拟数据为例真实处理效应被设定为 500。如果直接跑lm(wage_post ~ treat, data dat)treat 系数会明显大于 500因为 education、experience 这些正相关变量带来的偏差被吸收了进来。而跑完 PSM 匹配后回归treat 系数应该明显回落到 500 附近。实操中我见到不少同学匹配做得很漂亮平衡性也达标了结果解释说错了——非要把匹配后的系数说成是“总体层面的平均处理效应”而实际上他们用的是estimand ATT结论只能说“参加了该项目的那些人平均而言因此增加了 XX 单位的 Y”。这两句话的适用范围差别非常大汇报时要注意。7. 稳健性检验审稿人问“结果稳不稳”时的应对7.1 换匹配方法和参数结果方向会不会变因果推断最大的质疑点是“你的结论是不是只对某一种参数设置成立”。所以最基础的稳健性检验就是换方法、换参数看结论的方向和量级是否一致。我一般至少跑四套结果来对比# 方案对比不同匹配比例、不同卡尺、不同方法 m_1to3 - matchit(treat ~ age education experience pre_wage region, data dat, method nearest, ratio 3, caliper 0.10, estimand ATT) m_full - matchit(treat ~ age education experience pre_wage region, data dat, method full, estimand ATT) m_kernel - matchit(treat ~ age education experience pre_wage region, data dat, method nearest, distance glm, ratio 1, caliper 0.02, estimand ATT) models - list(m1 m1, m_1to3 m_1to3, m_full m_full, m_kernel m_kernel) for (nm in names(models)) { d - match.data(models[[nm]], weights weights) fit - lm(wage_post ~ treat age education experience pre_wage region, data d, weights weights) ct - coeftest(fit, vcov vcovCL, cluster ~subclass)[treat, ] cat(nm, :, round(ct[1], 2), SE:, round(ct[2], 2), p:, round(ct[4], 4), \n) }如果这几种方案下 treat 的系数都在一个合理区间摆动且显著性方向一致那结论就比较坚实了。如果换一种方法结论就翻车说明原始数据里处理组和对照组可比性本身就不足需要回头审视变量清单。7.2 安慰剂检验用培训前工资当虚假结局审稿人最喜欢问的一个问题是你的匹配到底把两组修得像不像一个很巧妙的检验是安慰剂检验。核心逻辑是培训前工资pre_wage应该不受培训影响。如果匹配有效用 pre_wage 作为“虚假结局”跑同样的匹配回归treat 系数应该接近 0 且不显著。如果这里显著了说明匹配后的两组在培训前本来就有系统差异你匹配得并不干净。# 用培训前工资作为虚假结局 fit_placebo - lm(pre_wage ~ treat age education experience region, data mdata, weights weights) coeftest(fit_placebo, vcov vcovCL, cluster ~subclass)[treat, ]我在实际项目中很喜欢用这个检验因为它的解释成本极低业务方一听就懂“如果匹配有效那培训前两组的工资就应该没有差异。”7.3 未观测混杂的敏感性分析思路PSM 能被诟病的最大软肋就是它只平衡了可观测变量未观测变量可能仍然同时影响处理分配和结果。前面模拟里我特意用了没有未观测混杂的数据所以 PSM 表现很好但如果加入一个未观测的能力变量情况就完全不同了。# 包含未观测混杂的模拟 set.seed(2026) u - rnorm(n) # 未观测能力 logit_treat2 - -1.2 0.6 * education 0.4 * region 0.02 * (age - 35) - 0.03 * (experience - 10) - 0.0004 * (pre_wage - 5000) 1.2 * u treat2 - rbinom(n, 1, plogis(logit_treat2)) wage_post2 - 5000 800 * education 30 * (age - 35) 25 * experience 300 * region 500 * treat2 300 * u rnorm(n, 0, 800)你再跑同样的 PSM 流程会发现估计结果明显偏离真实效应 500。这不是 PSM 实现错了而是数据本身违反了条件独立假设。这种场景下需要做敏感性分析。R 里可以用rbounds包做 Rosenbaum 敏感性分析它回答的问题是“如果存在某个未观测混杂变量它的影响要达到多大才能推翻你当前的显著结论”实际操作中我建议把敏感性分析当作审稿回复的标配而不是可选项。哪怕只是报告一句“在 Gamma1.3 的区间内结论依然稳健”也比只给一个点估计要可信得多。8. 实操经验与坑位总结重跑十遍后我相信的做法8.1 新手最容易踩的四个坑第一把协变量选择交给算法而不是业务逻辑。这是我见过最多的问题。机器学习的变量筛选逻辑在因果推断里是“投机取巧”你永远需要业务和文献帮你确认因果结构。第二匹配后直接 t 检验而不用权重和聚类标准误。t 检验的结果只能说明均值差的大小但标准误会偏小p 值会偏小结论容易被质疑。第三把倾向得分当作“得分”来直接用。经常会有人把倾向得分本身当成特征丢进结局回归这倒不是完全不行但可能是在毫无必要地给模型加约束。更干净的方式是只把它用在匹配、分层或加权环节而不让它参与结果模型。第四报告结果时漏掉“ATT vs ATE”的区分。审稿人或老板可能不会立刻追问但如果被追问了你竟然回答不清自己估计的是哪个参数那整篇结果的可信度都会打折。8.2 R、Stata、Python 三套方案速查我日常主力是 R但偶尔在团队协作中也要给对方提供 Stata 或 Python 的对照代码工具核心函数/命令适合场景RMatchIt::matchit cobalt::bal.tab / love.plot建模灵活、出图方便、教程资料多Statateffects psmatch官方内置命令结果表格排版好学术论文常见Pythonpsmpy想要嵌入数据流水线或自动化报告时以 Stata 为例早期的psmatch2已经比较老了我更建议用官方命令teffects psmatchteffects psmatch (wage_post) (treat age education experience pre_wage region), atetPython 的 psmpy 用法也很直白本质上就是先Logit计算倾向得分再match()建模最后matched_data输出匹配样本。但如果你只做一次性分析R 依然是最省心的选择。8.3 我最后会怎么向项目组交付 PSM 结果如果这个项目要我签字交付我会固定输出四张表匹配前处理组与对照组的协变量描述统计表包含 SMD。匹配后处理组与对照组的协变量描述统计表同样包含 SMD。主模型结果表treat 系数、聚类稳健标准误、p 值、样本量。稳健性检验汇总表至少覆盖两套不同匹配参数和一个安慰剂检验。这四张表放在一起基本上就能把一个 PSM 故事讲圆了。不需要花哨的话术也不需要堆砌一堆高级图表关键是每一步都有据可查。我这些年跑下来最大的体会是PSM 的代码门槛并不高真正拉开差距的是对变量因果位置的理解和每一步决策的解释。与其在匹配方法里反复横跳不如多花点时间想清楚你的协变量集合到底能不能支撑“条件独立”这个核心假设。
返回列表