结构方程模型实战:lavaan包从建模语法到拟合修正全流程解析 1. 为什么选择lavaan做结构方程模型我的真实使用经历如果你跟我一样平时主要跟问卷数据、心理量表或社科调查数据打交道那么结构方程模型SEM这个名字你肯定不陌生。过去很长一段时间里大家做SEM的第一反应是打开AMOS或者Mplus点鼠标、套模板、导图表。我最早接触SEM也是这样过来的——AMOS的图形界面确实容易上手但用久了问题就来了操作流程难复现、批量处理麻烦、换台电脑重装配置折腾半天而且遇到稍微复杂一点的多组比较或中介效应界面操作那套流程会把人绕晕。后来经人推荐开始用R语言的lavaan包老实说刚开始有点抵触总觉得写代码做SEM要比点鼠标多走一步弯路。但真正用了两三个项目之后我就回不去了。lavaan本质上是一个专门做结构方程建模的R包它把测量模型、结构模型、路径分析、潜变量交互、多组比较这些功能全部统一到一套简洁的模型语法里。你只需要把模型用几行文本表达出来剩下的拟合、估计、检验、修正指数生成它全都帮你算好。而且因为整个过程是代码驱动你做的每一个决策都是留痕的——这对学术报告、论文复现、团队协作来说价值太大了。这篇文章我会从一个实际使用者的角度把lavaan做SEM的完整流程拆开来讲从模型语法怎么组织、数据要满足什么条件到拟合指标怎么解读、出问题怎么排查再到中介效应、多组比较这些进阶玩法。内容尽量做到拿来就能用每一步都有可复现的代码和操作思路。适合刚接触SEM、想从商业软件转到R语言的人也适合已经有R基础但是没系统用过lavaan的读者。另外说一句lavaan的语法设计其实是从Mplus那套文本语法演化来的如果你之前用过Mplus上手lavaan会非常顺。如果完全没接触过SEM也没关系下面每个概念我都会用尽量朴素的话解释清楚。2. 建模前的关键认知测量模型与结构模型的底层逻辑2.1 先分清两个部件测量模型和结构模型结构方程模型这个名字听起来吓人但拆开看就是两块测量模型和结构模型。测量模型描述的是潜变量怎么被观测指标测量结构模型描述的是潜变量之间怎么互相影响。你可以把潜变量理解成一个不能直接测量、只能通过几个题目或指标间接反映的概念——比如学习动机工作满意度品牌信任这类东西。你问被试5个问题理论上这5个问题背后共享一个共同的潜在原因这就是一个潜变量。用lavaan的语法来说测量模型用~符号表达读作由...测量。比如motivation ~ m1 m2 m3 m4 m5意思是学习动机这个潜变量由m1到m5这5个观测指标共同反映。结构模型用~符号表达表示回归关系或路径关系。比如performance ~ motivation意思是动机影响绩效。一个完整的SEM模型就是把这两部分组合在一起先定义好每个潜变量的测量指标再定义潜变量之间的因果路径。很多人容易忽略的一个点如果只有测量模型没有结构模型那其实就是在做验证性因子分析CFA。CFA是SEM的基础构件。我实际建议你在跑完整SEM之前先单独跑一遍CFA确认每个潜变量的测量质量过关了再加路径。这一步看起来多此一举但在实际项目中能帮你省掉大量排查时间——因为如果CFA都拟合不好加了路径之后拟合更不可能变好到时候你根本分不清问题出在测量上还是结构上。2.2 模型识别三个指标是底线不是理想线SEM里有一个很多人第一次接触时完全没概念的问题——模型识别。所谓识别简单说就是你模型里的待估参数够不够资格被数据估计出来。如果模型不可识别lavaan会直接报错或者输出一些非正常的估计结果。用一句话记住底线规则每个潜变量至少要有3个测量指标。这就是所谓的三指标法则。为什么是3个因为只有3个指标时潜变量的方差和指标载荷才有可能被唯一确定。如果只有2个指标模型往往处于恰好识别甚至欠识别的状态除非你给这两个指标的载荷设置相等约束之类否则模型跑不出稳定结果。我见过很多人在量表设计阶段只给某个维度设计了2道题结果到分析阶段发现模型没法识别只能重新设计问卷或者用其他方法补救。所以如果你还在问卷设计阶段看到这里请记住一个维度最少3道题4到6道题更稳妥。指标太少不仅影响模型识别还会压缩潜变量的信度空间导致因子载荷普遍偏低。除了每个潜变量的指标数模型识别还跟潜变量之间的路径设定有关。比如你把所有潜变量之间的相关都设成自由估计模型反而是饱和的、恰好识别的。但如果你设定了一个不切实际的结构比如让两个潜变量互为因果模型照样识别不了。这类问题通常要靠累积经验才能一眼看穿但底线是当你发现lavaan报出model is underidentified之类的错误时先不要急着改语法回头数一数每个潜变量有几个指标。2.3 数据基础不能是随便什么数据都能跑SEMSEM对数据有要求这一点我希望所有新手都能早点知道lavaan默认的估计方法是极大似然估计ML它假设数据是连续变量且近似多元正态分布。如果你的数据是分类的比如二点计分的对错题用默认的ML估计其实是不够严谨的容易产生偏大的卡方值和偏小的标准误。那怎么办分两种情况处理。如果数据是5点、7点李克特量表大多数学者默认把它当作连续变量处理ML估计可用但建议用稳健校正比如estimator MLR它对非正态有一定的容忍度。如果数据是真正的有序分类变量比如0/1计分、三分类选项则建议使用estimator WLSMV这是专门为有序分类数据设计的对角加权最小二乘估计。样本量方面我用过的项目里比较常见的说法是结构方程模型每个自由参数至少需要5个样本10个以上更理想整体样本量最低不要低于200。这只是一个粗糙经验值实际需要结合模型复杂度判断。简单模型、指标质量好、载荷高150个样本也能跑出稳定结果复杂模型比如有交互项、二阶因子500个样本都可能捉襟见肘。这里我给一个省心的操作习惯建模之前先对数据做一次基础体检——变量缺失比例、每个变量的方差、指标之间的相关性到底是个什么水平。尤其是看相关矩阵如果两个指标相关性只有0.1左右那它们要共同测量一个潜变量就很勉强。相关性过高比如超过0.85也需要注意可能存在严重的多重共线性会在估计时引发奇怪的警告。3. lavaan模型语法实例入门从零跑通第一个SEM3.1 数据准备用现成经典数据做示范讲语法之前先说数据。lavaan包里自带一个非常经典的数据集叫HolzingerSwineford1939是1939年一个关于儿童智力测试的研究数据包含301名学生的9个测试得分。这个数据集几乎成了SEM入门的hello world很多教程都用它做演示。加载并查看数据library(lavaan) data(HolzingerSwineford1939) str(HolzingerSwineford1939) View(HolzingerSwineford1939)这个数据集有三个潜变量的经典设定视觉能力由x1、x2、x3测、文本能力由x4、x5、x6测、处理速度由x7、x8、x9测。非常完美地满足三指标法则适合用来讲清楚语法结构。3.2 三个模型语法从CFA到完整SEM我们用这个数据分三步演示。第一步先跑一个只有测量模型的CFAcfa_model - visual ~ x1 x2 x3 textual ~ x4 x5 x6 speed ~ x7 x8 x9 fit_cfa - cfa(cfa_model, data HolzingerSwineford1939) summary(fit_cfa, fit.measures TRUE, standardized TRUE)第二步加入结构路径变成完整的SEM。比如我们假设视觉能力受文本能力和处理速度的共同影响sem_model - # 测量模型部分 visual ~ x1 x2 x3 textual ~ x4 x5 x6 speed ~ x7 x8 x9 # 结构模型部分 visual ~ textual speed fit_sem - sem(sem_model, data HolzingerSwineford1939) summary(fit_sem, fit.measures TRUE, standardized TRUE)第三步如果你想做更复杂的设定比如让visual成为中介变量mediation_model - visual ~ x1 x2 x3 textual ~ x4 x5 x6 speed ~ x7 x8 x9 # 中介路径 visual ~ textual speed ~ visual textual 注意顺序问题。lavaan的语法是声明式的代码从上到下读下来模型里定义的路径并没有严格的先后执行顺序但你自己要清楚每条路径的含义。比如上面的代码里textual同时影响visual和speed而visual影响speed——这就是一个以visual为中介的模型。3.3 核心符号表与输出解读第一次接触lavaan语法的人一定要先把四个符号记牢符号含义示例~潜变量测量关系factor ~ item1 item2 item3~回归/结构路径y ~ x1 x2~~方差或协方差f1 ~~ f2潜变量相关:定义新参数indirect : a * b中介效应第三类符号~~里藏着一个新手最容易忽略的默认行为lavaan默认会估计所有潜变量之间的协方差。也就是说即使你不在模型里写visual ~~ textual只要这些潜变量之间没有被设定因果路径lavaan就会默认让它们自由相关。这在大多数CFA场景下是合理的但在SEM里如果你想让某个潜变量之间完全不相关需要显式约束不能靠不写就自动没有。看summary输出时我的习惯是分几个区块看Latent Variables区块每个潜变量下面列出各指标的载荷估计重点看Estimate和Std.all标准化载荷。载荷一般建议不低于0.5理想状态0.6甚至0.7以上。Regressions区块结构路径的回归系数这是你研究假设的核心检验部分。Variances区块会显示潜变量和观测变量的方差估计。如果出现负数就触发了后面要讲的Heywood case。Model Test区卡方值和自由度以及p值。卡方p值不显著是好的但这只在样本量适中时可靠——样本一大卡方几乎必显著所以别过度依赖它。输出里还有一个Standardizing相关的内容你可以在summary里加standardized TRUE直接看标准化结果。为什么强调看标准化因为原始量表单位不同时非标准化系数之间没法直接比较。标准化后的载荷和路径系数能让你一眼看出各条路径的相对强度。另外提一句lavaan支持把模型对象直接导出成表格写论文时很方便library(lavaan) # 提取拟合指标 fitMeasures(fit_sem) # 提取参数估计表 parameterEstimates(fit_sem, standardized TRUE)拿到parameterEstimates的data.frame之后你可以用任何数据整理的方式筛选中介效应、间接效应那一行直接粘贴进论文表格。4. 拟合评估与模型修正指标不是越多越好关键看组合逻辑4.1 常用拟合指标到底看哪些模型跑完了一堆拟合指标铺在眼前什么CFI、TLI、RMSEA、SRMR、AIC、BIC到底怎么看我的经验是不要背一堆阈值而是要理解每个指标在回答什么问题。先看绝对拟合指数它们衡量的是你的模型协方差矩阵和观测协方差矩阵差多远。最常用的是RMSEA和SRMRRMSEA近似误差的均方根。小于0.05算优秀小于0.08算可接受。它有个特性是会对模型复杂度进行惩罚——所以它比卡方更能反映这个模型是不是靠堆参数堆出来的拟合。SRMR标准化的残差均方根。小于0.08可接受越小越好。它直接反映的是残差的平均水平对样本量没那么敏感。再看相对拟合指数它衡量的是你的模型比最差的基线模型好多少。常用的是CFI和TLICFI大于0.90可接受大于0.95算优良。它是增量拟合指标实际就是比较你的模型和一个什么都无关的零模型之间的改进比例。TLI类似CFI但会对模型复杂度做惩罚。所以在模型比较场景下TLI比CFI更能区分复杂模型的优劣。最后还有信息准则指标AIC/BIC它们主要用于模型之间的比较不是用来判断单一模型是否可接受的。两个模型都有道理时AIC/BIC越小越好。我个人的习惯是报告时主要看CFI、TLI、RMSEA、SRMR这四个指标打包看。不要出现那种CFI到0.97但RMSEA却0.12的情况——这种互相矛盾的信号说明模型在某些局部存在严重失配单纯的对某个指标调参修修补补通常治标不治本。4.2 模型修正的正确姿势从修正指数到理论检查模型拟合不好怎么办lavaan给你提供了一个工具——修正指数modification indices。它表示如果你在模型里加一条之前没加的自由路径卡方值大概能下降多少。mod_indices - modificationIndices(fit_sem) subset(mod_indices, mi 10)注意我在这类输出里经常看到新手直接挑最大的MI往模型里塞路径结果模型拟合漂亮是漂亮了但理论上完全说不通。这是SEM使用里最危险的操作之一。修正指数只是统计层面的建议它不会告诉你我该不该在数据里找第三条路径来补偿理论上的不足。我的操作原则是这样的MI大于10的路径列出来先看有没有理论上说得通的。比如你是研究学习动机的原来只设了一个整体动机潜变量MI提示某个题目同时在课堂参与度上有高载荷——那这可能说明该题目本身带有双重属性理论上有解释空间这时候加一条载荷路径或者调整题目归属是合理的。但如果MI提示你在性别和潜变量误差项之间建立相关那基本就是为了凑指标而凑指标的坏操作不能采纳。另外模型修正一次只能做一步加了一条路径之后要重新跑一遍再看新的MI。千万不要一次加三四条路径那样你根本不知道每条路径到底带来了什么改变。修正后的模型已经属于探索性范畴报告论文时必须诚实说明哪些路径是理论预设的、哪些是数据驱动后加的。4.3 嵌套模型比较什么时候用anova()如果你有两个候选模型而且一个模型是另一个模型的简化版即嵌套关系比如一个模型里有一条路径另一个模型把它固定为0那它们就是嵌套模型。这时用似然比检验来比较就很合适fit_full - sem(full_model, data df) fit_restricted - sem(restricted_model, data df) anova(fit_restricted, fit_full)anova()输出里的卡方差异如果显著说明去掉的路径让模型显著变差了保留这条路径更合理如果不显著说明去掉它没什么损失模型简洁性更重要。这里要特别注意anova()默认只适用于使用ML估计法的模型。如果你用的是MLR稳健估计普通anova不适用需要用特殊的缩放卡方差异检验SCF。AIC/BIC视角下模型比较并不要求嵌套关系任何两个用同一数据拟合的模型都能比较信息准则大小。但信息准则没有显著性检验的概念纯粹是相对优劣的排序。5. 排错实战模型不收敛、负方差与Heywood case的完整排查链路5.1 认识那些让人头皮发麻的警告lavaan跑着跑着突然抛出一连串红色警告最绝望。常见警告有这几种lavaan WARNING: some observed variances are larger than the estimated variances——通常意味着某个观测变量的残差方差估计成了负数。The variance of some latent variable could not be estimated——潜变量方差估计出问题多半与模型识别有关。The model is underidentified——模型参数太多而信息不足无法唯一估计。这些情况里最典型也最容易遇到的是Heywood case也就是方差估计为负或潜变量方差出现负值。它本质上说明某个估计值跑到参数空间的非法区域去了大多是模型误设、样本量过小或指标异常引起的。5.2 我的排查顺序按这个链路走90%的问题能定位遇到警告我一般按下面这个顺序排查从最省事的开始第一步查数据。先看涉及异常的那几个变量是否存在严重缺失、极端异常值、零方差变量。零方差变量会让估计完全炸掉。缺失数据的处理也要确认lavaan默认用完整观测行listwise deletion如果你的原始数据有几个变量缺失较多样本量可能悄悄缩水一大截。可以用missing ml启用全信息极大似然估计来处理缺失前提是你的数据缺失机制满足随机缺失假设。第二步回头看模型设定。数每个潜变量的指标——是不是有潜变量只有2个指标是不是某个题目同时被两个潜变量包含是不是某两条路径与测量模型冲突在排错阶段把复杂模型退回CFA是最快的定位法。第三步检查估计过程。有时候收敛不了是因为迭代次数不够或优化算法问题可以在sem()里增加iter.max 1000或换用不同的优化方法optim.method BFGSlavaan默认是Newton法。对复杂模型来说更换优化器经常有意想不到的效果。第四步简化模型做压力测试。如果你设定了一个包含大量潜变量、大量交叉载荷、甚至二阶因子的宏大模型先只留核心变量跑一遍确认核心部分稳定了再一层层加回其他部分。这样能让问题显形——是某一块测量模型有问题还是整体结构导致的不收敛。5.3 处理Heywood case的几种手段如果确认真的是Heywood case负方差常见处理办法按优先级排列检查是否有极端离群值。有时就一两个异常个体就能把方差估计挤成负数。画出指标变量的箱线图找到并评估是否删除。增加样本量。这在实际中做不了但你要知道小样本多参数本来就是负方差的高发场景。给方差加下限约束。lavaan里可以对参数做简单约束比如把负方差对应的残差设成一个小正数。注意这是不得已的办法属于让模型说得过去而非解决问题。确认数据分布问题。极端非正态可能让ML估计跑偏。换用MLR或其他稳健估计负方差情况常常随之消失。还需要提一个常见误区有人一看到负方差就想给观测变量设~~ 0.01之类的约束。我建议不要一上来就这样做因为这会掩盖数据或模型中原本存在的问题。先通过数据检查、样本检查、模型简化来定位实在无解再考虑参数约束。6. 进阶玩法中介效应、多组SEM与非正态数据下的稳健估计6.1 中介效应与bootstrap置信区间别只用Sobel检验做SEM的人十个里有八个要做中介效应检验。lavaan里定义中介效应非常干净。假设自变量是textual因变量是speed中介变量是visualmediation - visual ~ x1 x2 x3 textual ~ x4 x5 x6 speed ~ x7 x8 x9 visual ~ a * textual speed ~ b * visual c * textual indirect : a * b total : indirect c fit_med - sem(mediation, data HolzingerSwineford1939, se bootstrap, bootstrap 1000) summary(fit_med, fit.measures TRUE, standardized TRUE) parameterEstimates(fit_med, boot.ci.type perc)这里的关键词是se bootstrap。为什么要用bootstrap因为中介效应的抽样分布往往不是正态的传统的Sobel检验假设正态分布做Z检验容易出偏差。bootstrap通过反复重抽样直接估计间接效应置信区间比Sobel要稳健得多。boot.ci.type perc表示用百分位法构造置信区间。如果你的间接效应置信区间不包含0那么中介效应显著。里面还有一个细节给路径a、b、c取名字这种写法是lavaan的标记功能可以让你在:里引用它们做任何组合运算。这是lavaan比Mplus更灵活的地方之一。6.2 多组比较先检验测量不变性再比路径系数有时候你关心的不是总体模型而是这个模型在不同群体之间是否一致。比如性别差异、年级差异等。lavaan的多组分析用group参数实现fit_group - sem(mediation, data HolzingerSwineford1939, group school) summary(fit_group, fit.measures TRUE)但要注意直接跑多组比较之前必须先检验测量不变性measurement invariance。否则跨组比较是没有意义的——你想比较两组学习动机对成绩的影响是否不同但如果学习动机这个量表在两组中的测量含义都不一样潜变量之间根本没可比性。测量不变性通常从松到严分三档形态等值因子结构相同、弱等值因子载荷相同、强等值载荷和截距相同。对应到lavaan里是通过group.equal参数控制fit_weak - sem(mediation, data HolzingerSwineford1939, group school, group.equal c(loadings)) fit_strong - sem(mediation, data HolzingerSwineford1939, group school, group.equal c(loadings, intercepts)) anova(fit_weak, fit_strong)你的研究问题决定了你需要达到哪一档。如果只是比较潜变量之间的路径系数弱等值基本够用如果想比较潜变量的均值那需要强等值甚至严格等值。6.3 遇到非正态数据估计方法选型表调研数据很少是严格正态的。lavaan提供了多种估计方法这里我按真实使用场景整理一个选择参考数据类型推荐估计方法说明近似连续的连续变量ML默认如果有轻微非正态建议用MLR连续但有明显偏态/峰度MLR或MLM稳健标准误与校正卡方有序分类4点以上MLR或DWLS类别较多时可近似连续处理有序分类3点及以下WLSMV自动使用polychoric相关矩阵二元计分数据WLSMV推荐但需足够样本量极度非正态Bootstrap ML结合bootstrap可给出稳健置信区间使用MLR时输出的summary里仍然显示卡方值但要明白它已经经过校正不能直接用普通anova()做模型比较。MLR的模型比较需要用到lavTestLRT()lavTestLRT(fit_mlr_restricted, fit_mlr_full)这个方法内部会做卡方差异校正比直接anova()更可靠。我在实际项目中见过不少人在数据明明是偏态的情况下硬是直接用默认ML估计最后标准误偏小、显著性判断偏乐观。等文章被审稿人问到你有没有检查正态性时才补做校正。与其事后补救不如一开始就根据数据情况选定估计方法。这也是lavaan相比很多商业软件的优势——它给了你完整的控制权。最后再分享一个操作习惯我会把lavaan的代码和结果渲染成R Markdown报告每个模型都记录数据版本、模型语法、拟合指标、参数表。项目结束复盘时哪一步做了什么决策一行一列都能追溯。因为lavaan的语法本身就是文本你甚至可以把它嵌入到自动化分析流程里批量处理几十个变量组合的模型筛选。这种可复现性带来的安全感是图形界面软件永远给不了的。不管你是准备跑人生第一个SEM还是已经在模型修正里挣扎了很久希望这篇分享能帮你把一些底层概念理清楚跑模型时少走几步弯路。