ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于R语言的抑郁症状网络分析:从偏相关到中心性实战

基于R语言的抑郁症状网络分析:从偏相关到中心性实战 两年前我处理一份大样本流调数据时遇到了一个很别扭的现象某抑郁量表的九个条目内部一致性高得漂亮Cronbachs α接近0.9可一旦放进传统的因子分析模型拟合指标却始终差口气。当时一位同事建议我试试症状网络分析我这才意识到问题出在假设上——我一直默认一个潜在的抑郁因子导致所有症状出现可真实世界里的抑郁症状更像是一群互相影响、彼此强化的个体而不只是某个共同因子的被动表现。后来我用R语言把这套分析完整跑下来发现它不仅解释了我数据里的矛盾还给了我一个完全不同视角的临床解读工具。这篇就聊聊如何用R完成抑郁症状网络分析从数据准备到估计、稳定性检验、可视化、论文报告再到我踩过的那些坑。1. 症状网络到底在分析什么从一个因子到一张网1.1 我为什么会转向网络分析先解释一下背景。传统心理测量学里量表总分几乎是万能的得分越高抑郁越严重。这个逻辑背后有一个潜在变量模型假设所有条目共享一个抑郁因子条目之间之所以相关是因为它们都从同一个源头获得信息。就像一盏灯照亮的房间灯是抑郁家具是症状所以我们能看到彼此的影子。但网络理论提出了一个相反的思路失眠会让人白天疲劳疲劳导致注意力下降注意力下降又加重情绪低落情绪低落进一步恶化睡眠……症状之间的因果链条完全可以在没有共享源头的情况下自发形成。一个量表分数背后可能根本不存在那个单一的抑郁实体而是九条互相缠绕的症状关系网。网络分析要做的就是把这张网量化出来谁是节点症状谁和谁直接相连症状间的独特关联哪条边最粗关联最强哪个节点最核心。这种视角的价值在于实践层面。如果某个症状真的是网络里的枢纽撬动它就可能影响整个网络的走向这为心理干预提供了一个比总分降两分更具体的靶点。1.2 每条边为何是偏相关而非普通相关这是新手最容易混淆的地方我必须先说清楚。网络图里的边不是两两症状的皮尔逊相关系数而是偏相关——即控制了网络里其他所有症状之后两个症状之间剩下的独特关联。举个例子睡眠问题和情绪低落之间的普通相关可能很高但这个相关可能是疲劳这个中间变量造成的假象。失眠的第二天人当然容易疲劳疲劳的人更容易情绪差这时候你观察到的失眠-情绪低落相关其实有一部分是绕了远路的间接效应。偏相关要做的是把这条远路堵死把疲劳、食欲、注意力等所有其他症状对这两个变量的影响全部回归掉再看它们之间是否还残留直接对话。只有这部分才是独属于这两个症状的关联才有资格在网络里画成一条边。普通相关会高估关联强度甚至画出很多冗余边。所以现代症状网络分析几乎都以偏相关网络为基础配合正则化技术让网络变得稀疏、可读、可解释。1.3 一篇网络分析论文只讲三件事大多数心理学期刊里一篇症状网络分析研究报告的内容可以被压缩成三件事网络图长什么样、谁是最核心的症状、这张图有多稳。对应到R语言就是qgraph画图、中心性指标计算、bootnet稳定性检验这三个环节。理解了这三个输出物整条分析流水线心里就有数了先准备干净的数据再用正则化方法估计偏相关网络接着计算中心性和桥接指标最后用自助法检验结果稳不稳稳才算数。2. 数据准备阶段最容易翻车的三件事2.1 问卷条目选择与编码检查症状网络分析的初衷是研究症状层面symptom level的关系所以输入数据通常是单一条目的得分而不是量表总分。以最常见的抑郁自评量表为例九个条目分别对应兴趣减退、情绪低落、睡眠障碍、疲劳、食欲改变、自我否定、注意力困难、精神运动性迟缓或激越、自伤意念每个条目按严重程度计0到3分。我拿到的原始问卷多半是别人录入好的最容易被忽略的是变量类型问题。qgraph和bootnet内部做相关矩阵运算要求所有输入列必须是数值型。我接过一份数据九个条目全部以字符型变量存进来r读入后全是0123的字符串直接喂给estimateNetwork会报错或者静默转成逻辑索引。所以第一步永远是# 检查结构 str(items) # 如果发现是字符型先转换 items - as.data.frame(lapply(items, as.numeric))另外要检查反向计分条目是否做了翻转。网络分析对方向极其敏感一个没翻过来的反向条目会直接制造一条负相关假边整张图都会变形。用psych::describe()扫一遍每个条目的均值、标准差、最小值最大值基本能发现问题如果某个条目的均值明显偏离其他条目且方向可疑回到原始问卷核对计分规则。2.2 缺失值处理删还是补问卷数据几乎不可能完全没有缺失。处理缺失值的方式会直接影响网络估计缺失比例在5%以内、且完全随机缺失时整条删除listwise deletion还能勉强接受缺失达到10%以上我强烈建议做多重插补再估计。原因很简单网络估计需要的是完整相关矩阵。如果不同变量对采用不同的有效样本量算出来的相关矩阵内部不一致后续的矩阵求逆和正则化很可能直接报矩阵非正定错误。这就像画地图时用了两套比例尺压根拼不到一起。我用的是mice包的默认多重插补插补次数通常设20次然后在每次插补后的数据上分别估计网络最后汇总边的权重。不过实际发表时多数研究会选择在原始完整数据上估计一次网络再报告缺失比例和插补流程操作简单且审稿人买账。2.3 样本量门槛与条目数量网络分析对样本量的胃口比传统回归大得多。9个节点的网络至少需要150到200人才能估计出能看但未必稳的图300人以上才谈得上稳定的中心性结果。经验法则可以粗略记每个节点至少对应5到10个观测节点数越多门槛越高。样本量不足时网络图会显得边特别多、置信区间巨大、中心性排序一碰就碎这些在后面的稳定性检验环节会原形毕露。3. 用qgraphbootnet跑通EBICglasso网络完整实操3.1 安装包与环境准备整个分析链路依赖的R包不超过六个按我的使用习惯一次装上install.packages(c(qgraph, bootnet, igraph, networktools, psych, mice))qgraph负责网络估计与绘图bootnet负责稳定性检验和自助法networktools提供桥接症状分析psych用来做描述统计。值得一提的是bootnet里的estimateNetwork()函数整合了多种估计方法日常执行网络估计我甚至不需要单独加载其他包。3.2 estimateNetwork核心参数解读数据格式确认无误后核心估计就一句话library(bootnet) items - dep_data[, c(phq1, phq2, phq3, phq4, phq5, phq6, phq7, phq8, phq9)] net - estimateNetwork( data items, default EBICglasso, tuning 0.5, corMethod cor, verbose TRUE ) # 查看邻接矩阵 net$graph这里default EBICglasso是当前症状网络分析的事实标准别的先不用管。它做的事情分两步先算出所有症状两两之间的偏相关再用图形套索graphical lasso算法对偏相关矩阵做正则化惩罚把那些非常接近零的边直接压成精确的0。这样得到的网络是稀疏的只有真正站得住的关联会被保留为边视觉上更干净统计上更保守。tuning 0.5是EBIC准则里的惩罚系数取值范围通常在0到0.5之间。取值越大网络越稀疏删掉的弱边越多取0则几乎不惩罚网络会密得没法看。默认0.5在绝大多数心理数据上表现稳定没必要随便改。这一步的为什么值得多说两句9个症状两两之间的偏相关有36条如果全画出来图里塞满边任何结论都无从谈起。正则化的本质是帮我们做减法——宁愿漏掉一些真边也不要把大量假边当成结论端出来。临床数据噪声大稀疏网络反而是更诚实的表达。3.3 第一次绘图布局、颜色与粗细估计完毕画图library(qgraph) labels - c(兴趣, 情绪, 睡眠, 疲劳, 食欲, 自我评价, 注意, 动作, 自伤) qgraph(net$graph, labels labels, layout spring, cut 0.1, minimum 0.05, posCol #2E8B57, # 正相关用绿色 negCol #C0392B, # 负相关用红色 vsize 7, esize 4, groups list( 认知情感症状 c(1, 2, 6, 7), 躯体症状 c(3, 4, 5, 8, 9) ))几个参数的含义我得展开讲。layout spring让布局基于力导向算法自动排列关联紧密的节点被拉近关联弱的被推开节点位置本身不含统计意义只是方便看图。cut 0.1意味着只显示绝对值大于0.1的边minimum 0.05则是更低的显示阈值两者配合能过滤掉那些细到可以忽略的边但注意这两个只是视觉过滤不影响底层网络矩阵。边的粗细与权重绝对值成正比颜色表示方向。这里有一个常被忽视的细节图中边的相对粗细可以比较但跨两个图的边的绝对粗细没有可比性因为qgraph会根据当前图的最大权重自动缩放。所以如果要并行比较多个网络比如男性和女性两组一定要加上maximum参数把缩放标准化否则两张图看起来粗细不同其实是自动缩放的假象。4. 中心性指标与桥接症状图好看不等于结论可靠4.1 三类中心性指标到底选哪个网络图呈现的是整体结构但研究者更关心哪个症状最核心。这就轮到中心性指标登场。教科书上常见的三类指标是强度Strength、中介中心性Betweenness和接近中心性Closeness。强度最简单也最实用一个节点所有边的权重绝对值之和。它衡量的是这个症状在直接关联中积累的总影响力可以理解为和多少症状保持着多强的直接联系。接近中心性是某个节点到其他所有节点的平均最短距离的倒数代表信息的可达速度。中介中心性则是该节点出现在多少条其他节点间最短路径上衡量的是桥梁作用。我的个人建议是报告强度为主中介和接近中心性为辅甚至干脆只报强度。原因在于后面的稳定性检验里会看到中介中心性和接近中心性在心理网络数据上极不稳定样本稍微波动排名就翻天覆地。许多模拟研究也反复论证过这一点。只报强度审稿人挑不出毛病。计算和可视化的代码centralityPlot(net, include c(Strength, Betweenness, Closeness)) centralityTable(net) # 输出具体的数值表4.2 bridge()识别跨集群桥接症状除了单个症状的核心程度网络分析还有一个非常出彩的功能识别桥接症状。它回答的问题是——如果我把网络划分成认知情感症状群和躯体症状群是谁在跨越两个群落传递影响用networktools包的bridge函数library(networktools) bridge_result - bridge(net, communities list( 认知情感 c(兴趣, 情绪, 自我评价, 注意), 躯体 c(睡眠, 疲劳, 食欲, 动作, 自伤) )) bridge_result输出里的bridge strength指标计算的是一个节点与对侧群落所有节点的边权重绝对值之和。桥接强度最高的节点往往就是两个症状群互相激活的关键枢纽。举例来说如果疲劳的桥接强度最高意味着躯体疲劳是把认知情绪症状和躯体症状连在一起的核心通路。这在临床上的潜在含义是打断这条通路可能比分别处理两个群落内的症状更高效。4.3 中心性解读的一个完整范例拿到中心性结果后怎么写进论文以我一次实际分析为例假想结果是疲劳和情绪低落的强度并列前两名而食欲最弱。一段合格的表述应该是这样疲劳和情绪低落节点在网络中表现出最高的强度提示二者在抑郁症状网络中占据核心位置食欲节点强度最低与整体网络的关联相对有限。上述排序的稳定性需结合自助法结果综合判断。注意措辞里的分寸只说关联范围广占据核心位置不要直接说是抑郁的病因。横断面网络没有方向更没有因果。这个分寸我在第七部分还会专门说。5. bootnet稳定性检验审稿人必然会问的三个问题5.1 边的稳定性自助置信区间中心性指标只有在一个前提下才有价值网络本身是稳定的。这个前提靠bootnet包的自助法来验证。我的固定操作是set.seed(2024) boot_res - bootnet(net, nBoots 1000, nCores 4, type nonparametric) # 画边的置信区间 plot(boot_res, labels c(edge))这里做的事情是对原始样本进行有放回重抽样每次抽样都重新估计一整张网络重复1000次最终得到每条边权重的95%置信区间。区间越窄说明这条边受样本波动影响越小。解读要点边的置信区间如果跨越0即包含正负两端说明这条边可能只是噪声。更要紧的是两条边的置信区间如果大范围重叠就不能断言边A比边B强哪怕图上A看起来更粗。5.2 CS系数中心性排序到底可不可信第二个关键输出是CS系数correlation stability coefficient它专门回答中心性排序稳不稳corStability(boot_res)CS系数的计算逻辑很巧妙从全样本开始逐步随机舍弃一定比例的样本每次舍弃后重新计算中心性并和全样本的中心性求相关。这个相关掉到0.7以下时所对应的最大样本丢弃比例就是CS系数。可以通俗地理解成这张网络的中心性结论禁得起丢掉多少样本的折腾。业界公认的及格线是CS系数大于等于0.25才能勉强解读中心性层面的排序大于等于0.5则比较可靠。如果强度指标的CS系数不到0.25请你克制一点不要在结果里对中心性排序做出任何实质性解读报告边权和网络图就够了——这是硬规矩很多审稿人都盯着这个数字。5.3 差异检验真的可以说这个节点最重要吗即使CS系数达标还有一个容易踩的雷两个节点的强度数值差一点能说一个比另一个更强吗统计学上不能只看点估计要做两两差异检验。bootnet可视化里提供了这个功能plot(boot_res, statistics strength, plot difference)输出的矩阵图会告诉你哪些节点的强度排序差异具有统计显著性。那些差异不显著的对子在论文里绝不能写成一个比另一个核心。我见过太多手稿只报了强度排序表没有做差异检验结果被审稿人一句these differences may not be significant打回来。多跑这一行代码能省去一轮修回的痛苦。6. 可视化升级与论文报告要点6.1 qgraph参数微调达到投稿级别qgraph默认出图能看但离投稿还有点距离。我会做三处调整把边色设为色盲友好的配色、调整节点大小和字体、输出PDF矢量图。pdf(depression_network.pdf, width 8, height 7) qgraph(net$graph, labels labels, layout spring, posCol #4DAF4A, negCol #E41A1C, vsize 9, label.cex 1.2, edge.width 1.5, groups list(认知情感 c(1, 2, 6, 7), 躯体 c(3, 4, 5, 8, 9))) dev.off()edge.width 1.5不是固定值它会和esize共同作用我一般反复试几次选出粗细协调的参数。另外记得在绘图前固定set.seed因为spring布局有随机初始化seed不同布局位置就不同这会影响图的重复性。我自己的经验是多个网络放同一张图对比时用固定布局参数layout参数传入坐标矩阵能让节点位置完全一致对比效果瞬间提升几个档次。6.2 用ggplot2重绘中心性图qgraph自带的centralityPlot画得快但样式自由度低。我习惯把centralityTable导出来用ggplot2重新画library(ggplot2) library(dplyr) cent - centralityTable(net) %% filter(measure Strength) %% arrange(value) ggplot(cent, aes(x reorder(node, value), y value)) geom_point(size 3.5, color #2166AC) coord_flip() labs(x NULL, y 强度strength) theme_minimal(base_size 14)这种图放在正文或补充材料里都合适。审稿人喜欢能直接看到排序和数值差距的图而不是一张只有网络拓扑的图。6.3 论文方法部分的最小报告清单网络分析最容易被质疑的是可复现性。方法部分我建议至少交代样本来源与筛选流程、纳入分析的条目清单、缺失比例及处理方法、网络估计方法EBICglasso及tuning值、稳定性检验结果CS系数、边权置信区间、以及软件与版本号。现在很多期刊要求分析代码开源我建议把清洗数据和网络分析的R脚本连同匿名化后的数据一起放在公开仓库这既是自保也是给领域的可复现性做贡献。方法部分的一个合格表述样例是采用EBICglasso方法估计偏相关网络EBIC惩罚参数设为0.5网络稳定性通过1000次非参数自助法检验强度中心性CS系数为0.56满足0.5的良好标准。短短两句话信息量比一大段含糊的描述大得多。7. 踩坑记录我在这套流程里吃过的亏7.1 小样本网络看起来很美实则全是噪音我第一次做网络分析时手里只有80来人的预实验数据。图跑出来非常漂亮——spring布局、粗细分明的边、清晰的集群结构我当时差点直接写进报告。后来顺手跑了一下bootnetCS系数只有0.13远远低于0.25的及格线。这意味着如果把样本砍掉13%中心性排序就和原先对不上了之前那些核心症状的解读全部建立在流沙上。那次之后我养成了习惯画完图第一件事不是欣赏而是跑稳定性。小样本数据的网络图务必只报告边权和网络结构不要碰中心性排序。7.2 把横断面网络当成因果路径这是我见过最普遍的过度解读自己也踩过。看到失眠-疲劳的边权重高达0.4差点写出一句失眠导致疲劳从而维持抑郁。审稿人一眼就看出来问题横断面数据里边是无方向的偏相关它只能说明在控制其他症状后失眠与疲劳存在正向关联。你既不能说A导致B也不能说B导致A方向问题必须交给纵向数据回答。如果想讨论真正的动态关系应该用的是纵向网络模型如多层向量自回归网络那是另一套分析思路。7.3 复现性底线种子、版本和开源最后一条经验是流程层面。网络分析的自助法、spring布局、插补过程都有随机性如果不固定随机种子换一台机器跑出来的图可能不太一样。我建议在每个分析脚本开头设好set.seed()在脚本结尾用sessionInfo()记录完整软件环境并把这两项一起归档。数据公开前记得做匿名化去掉所有能定位到个体的字段。防备的不仅是指标被攻击更是给自己留一条任何人都能复现我这个结果的底气。如果你现在正准备用R跑第一张抑郁症状网络图我的建议是别急着追求复杂的模型先把手头一批干净问卷数据跑通上面的完整流程估计、画图、中心性、稳定性每一步都问自己一遍这个数在回答什么问题。把这套底子打好之后再去看组间比较、纵向网络、多层网络这些进阶方向就不会迷路。网络分析说到底不是生成一张漂亮图的工具而是逼着你在症状层面重新思考疾病结构的一种思维方式——这个转变远比代码本身更值钱。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进