资讯动态

trajeR包实战:组轨迹模型识别纵向数据中的潜在发展轨迹

发布时间:2026/9/15 3:10:50 来源:尧图企业网站定制
开头最近帮一个做儿童发展研究的团队处理追踪数据他们的研究问题是不同孩子的语言能力发展轨迹是不是一样的如果用传统方法直接拟合一条“平均发展曲线”结果就是——所有孩子的差异都被平均掉了最后得出一个高不成低不就的折中结论既不能解释“为什么有的孩子进步快”也不能回答“哪些孩子需要早期干预”。这正是组轨迹模型Group-Based Trajectory ModelsGBTM派上用场的地方。简单来说GBTM能识别出总体中是否存在若干个具有不同发展轨迹的潜在亚组然后给每个个体概率性地“分到”最合适的那条轨迹上。而R语言里的trajeR包是我目前用过最顺手的GBTM实现之一。它是从Stata里的traj命令移植过来的语法结构、输出逻辑都保留了Stata版本的原汁原味同时在R环境下跑得更灵活画图也不用再费劲导出数据。这篇文章我会从模型原理、数据准备、实操流程到参数解读完整走一遍trajeR包做组轨迹分析的全过程。不管你是心理学、社会学、经济学还是搞医学随访数据分析的只要你手上有一组纵向重复测量数据想看看样本里到底存在哪几条“隐藏轨迹”这篇文章应该能帮你少走不少弯路。1. 组轨迹模型到底在做什么1.1 为什么“平均轨迹”会误导人先花点时间把GBTM的核心逻辑说清楚。很多人在处理纵向数据时第一个想到的往往是混合效应模型LMM或GLMM但混合效应模型的核心假设是所有个体共享同一个总体均值轨迹个体差异通过随机截距和随机斜率体现出来。换句话说它假设总体中只存在“一条轨迹”每个人围绕这条轨迹上下波动。这在很多场景下并不合理。还是拿儿童语言发展举例有的孩子18个月就开始大量输出词汇有的孩子两岁半才开口说话有的孩子早期慢但后劲足。这几个群体的发展模式根本不一样硬塞进同一模型里随机效应再大也表达不了这种“质的不同”。GBTM换了个思路——它假设总体是由若干个离散的潜在亚组组成的每个亚组有各自独立的轨迹形态个体只是不知道自己是哪个组的但模型可以估算出来。打个比方混合效应模型就像把所有学生的成绩放在一起算一个平均分而GBTM则是先按学习风格把学生分成“突击型”“稳步型”“后发型”几个群体再分别研究每个群体的成绩曲线。哪种更容易发现规律应该很清楚。1.2 GBTM与LCGA、潜类别增长模型的关系这里必须先把概念理清不然你在看文献的时候很容易被绕晕。GBTM、潜类别增长混合模型LCGALatent Class Growth Analysis、潜类别增长混合模型GMMGrowth Mixture Model这三个术语经常混着出现但它们之间的边界并不那么绝对。严格来说LCGA是GMM的一种特殊情况在LCGA中所有类别的方差和协方差都固定为0相当于组内个体没有随机效应只允许轨迹截距和斜率的均值随类别变化。而GMM允许组内存在随机效应组与组之间既可以有不同的均值轨迹也可以有不同的方差结构。trajeR包实现的方法是GBTM本质上就是LCGA这一类。它假设同一组内的个体完全同质所有个体差异都通过“组别归属”来解释。好处是模型简单、收敛稳定、结果容易解释代价是如果你数据里确实存在很大的组内异质性GBTM可能低估组内的变异。但从实用角度看GBTM的稳健性让它成为轨迹分析首选入门工具尤其适合样本量中等、研究目标是“识别亚组”而非“精确拟合个体变异”的场景。1.3 什么样的数据和研究设计适合用trajeR不是所有纵向数据都适合跑GBTM我在实际分析中总结了几条判断标准重复测量次数不要太少至少3次理想是5次以上。测量太少曲线形状没法识别类别之间的差异很容易被噪声覆盖。样本量不能太小。类别数越多每个类别需要的有效个体也越多。我一般建议总体样本量至少200少于100的话跑出来的类别稳定性会很差。测量时间点要尽量一致。trajeR支持个体层面的时间轴不同但这会大大增加模型复杂度前期数据清理和参数设定都比较麻烦建议优先保证随访时间点对齐。研究问题必须是“存在多个潜在群体”的假设。如果你的研究目的是预测个体层面的发展轨迹而不是识别亚组那应该用混合效应模型或潜变量增长模型而不是GBTM。基于这些标准trajeR比较常见的应用场景包括精神疾病症状亚型的纵向识别、犯罪学中的犯罪轨迹分组、消费行为中的购买模式分类、老年医学中的认知衰退轨迹类型划分以及儿童发展研究中的技能习得路径识别。2. 上手trajeR包安装与数据准备2.1 安装细节与依赖环境trajeR包最早发布在CRAN上直接用常规方式就能安装不需要从GitHub拉开发版本这点比很多R生态里的“半成品”包要省心得多。# 从CRAN安装 install.packages(trajeR) # 加载 library(trajeR)如果你想用最新的开发版也可以从GitHub安装# 需要先安装devtools或remotes # install.packages(remotes) remotes::install_github(fdegriise/trajeR)安装过程一般不会遇到什么依赖问题它主要的底层依赖是Rcpp和RcppArmadillo这两个包在Windows和macOS上都有预编译版本安装很顺畅。我印象中唯一一次遇到报错是因为R版本太老升级到4.x之后就再没出过问题。所以如果你安装失败第一步先检查R版本第二步再检查是否缺RtoolsWindows环境下编译源码包需要。2.2 数据格式的核心要求trajeR对数据格式的要求比较特殊和大多数R包“一行一个观测”的长格式不同它期望的是宽格式数据每一行是一个个体每一列是一个时间点的观测值。举个例子假设你有3个时间点的数据T1、T2、T3每个个体一行那么数据框大致长这样## id T1 T2 T3 ## 1 1 12.50 15.30 16.80 ## 2 2 10.20 12.10 13.90 ## 3 3 15.10 18.60 21.20为什么trajeR强制要求宽格式因为GBTM的计算逻辑是基于个体整体轨迹的它对每个个体的整条轨迹同时建模而不是逐观测点堆积数据。理解了这一点你就不会在数据整理阶段纠结是long还是wide了。时间变量的处理也是关键。如果你们的随访时间是等间隔的比如每年测一次可以直接用1、2、3这样的整数作为时间编码。如果时间点之间间隔不等建议用真实时间比如年龄、随访月龄作为时间变量这样拟合出来的曲线形态更贴近实际发展轨迹。缺失值方面trajeR底层用的是全信息极大似然估计理论上允许部分缺失但实际操作中我强烈建议你先把严重缺失的个体剔除掉。每个个体至少要有3个非缺失观测值否则对轨迹的贡献很小还会增加模型估计的负担。2.3 核心参数速查表trajeR的核心函数是trajeR()我先把最常用的一组参数列出来后面实操的时候你对照着查就行参数作用我的建议Y纵向观测数据矩阵每行一个个体直接传数据框或矩阵列名不重要A时间变量矩阵每行一个个体对应各时间点的时间编码和Y的列一一对应deg列表每个组别对应的多项式阶数比如list(2, 2, 2)表示3组都是二次曲线Model数据分布类型连续变量用CNORM计数数据用ZIPMethod积分方法默认REML即可对应Stata中的ind方法ss是否需要标准化一般设为FALSE除非各变量量纲差异极大hessian是否计算Hessian矩阵设为TRUE可以获取标准误方便后续检验3. 实操演示从零跑通一个组轨迹分析3.1 模拟一份纵向追踪数据为了让你完整看明白全流程我先模拟一份数据。假设你在研究某种行为干预的效果测量了300个个体在5个时间点的行为得分得分越高代表行为表现越好。真实情况是这些个体来自3个潜在亚组一组是“低起点慢速提升”一组是“中起点线性增长”一组是“高起点快速跃升”。set.seed(123) n - 300 time - 0:4 # 生成三个类别的真实参数 group1 - data.frame(a0 2, a1 0.8, a2 -0.1) # 低起点缓慢增长略呈曲线 group2 - data.frame(a0 4, a1 1.8, a2 0) # 中起点线性增长 group3 - data.frame(a0 6, a1 2.5, a2 0.3) # 高起点加速增长 # 按比例分配个体 g - sample(1:3, n, replace TRUE, prob c(0.35, 0.45, 0.2)) # 生成观测值加一点噪声 df - data.frame(id 1:n) for (t in 1:5) { df[[paste0(Y, t)]] - NA for (i in 1:n) { if (g[i] 1) { df[[paste0(Y, t)]][i] - group1$a0 group1$a1 * time[t] group1$a2 * time[t]^2 rnorm(1, 0, 0.5) } else if (g[i] 2) { df[[paste0(Y, t)]][i] - group2$a0 group2$a1 * time[t] group2$a2 * time[t]^2 rnorm(1, 0, 0.5) } else { df[[paste0(Y, t)]][i] - group3$a0 group3$a1 * time[t] group3$a2 * time[t]^2 rnorm(1, 0, 0.5) } } }这份模拟数据里真实类别数K3前两组曲线形态是线性和二次项的组合第三组稍微带点加速趋势。噪声的标准差设为0.5在纵向数据里算比较小的方便后续模型识别。3.2 数据形状检查与预处理拿到数据之后先别急着建模。我每次都会做三件事查看数据结构、检查缺失值比例、画出原始轨迹图。# 查看数据结构 str(df) # 缺失值统计 colSums(is.na(df)) # 画原始轨迹图直观看看大概有几波走势 matplot(t(df[, 2:6]), type l, col rgb(0.4, 0.6, 1, 0.3), xlab Time, ylab Score, main Raw Trajectories of 300 Individuals)画原始轨迹图这一步特别重要它能帮你预先判断趋势总体上是上升还是下降是否有明显的弯曲是否存在几类明显不同的走势如果你在图上完全看不出分组结构那即使模型跑出来显著也要慎重解读因为很可能只是统计伪影。在我这份模拟数据上你看到的应该是大量线条从2分到12分左右分散分布隐约能看出三层分层结构这给了我们跑GBTM的信心。3.3 跑一个三类别的GBTM模型trajeR的建模代码非常简洁。核心是把Y矩阵和时间矩阵A传进去然后指定deg列表和Model类型。# 提取观测矩阵和时间矩阵 Ymat - as.matrix(df[, paste0(Y, 1:5)]) Amat - matrix(rep(0:4, nrow(df)), nrow nrow(df), byrow TRUE) # 拟合三组、全二次项的CNORM模型 fit3 - trajeR::trajeR(Y Ymat, A Amat, deg list(2, 2, 2), Model CNORM, Method REML)这里有几个参数需要解释一下。deg list(2, 2, 2)表示三个组都用二次多项式之所以初始设置全二次是因为我并不知道真实曲线形态宁可先用复杂一点的模型去拟合再用BIC去比较是否真的需要二次项。Model CNORM适用于连续型结局变量它假设残差服从截断正态分布并且能自动处理数据边界问题。Method REML用的是数值积分中的自适应高斯-埃尔米特积分精度更高对应Stata traj命令里的ind选项。3.4 看看模型输出了什么跑完后直接打印fit3就能看到结果。我截取比较关键的几段来解释# 直接查看结果 summary(fit3)输出主要包含以下几块内容组别占比Group membership probabilities每个组在总体中的估计比例。模拟数据里真实比例是0.35、0.45、0.20模型估计出来的比例应该非常接近。组别概率Posterior probabilities每个个体分到各组的后验概率后续可以据此计算平均后验概率AvePP来评价分类质量。各组轨迹参数估计每个组别的多项式系数截距、线性项、二次项及其标准误。比如组1的参数显著为线性负或正组2、组3的趋势各不相同这就能解释每条轨迹的具体形态。模型拟合指标BIC、AIC、Log-likelihood等。只看summary还不够更直观的方式是画轨迹图plot(fit3)trajeR的默认绘图函数会画出每个组的期望轨迹曲线同时用不同颜色区分组别还会在图上标出每个组的占比。你可以在图上直观看到“慢速组”“线性组”“加速组”的曲线形态和模拟设计完全一致。我还习惯把后验分类结果导出来便于后续做交叉验证或者亚组特征描述# 获取每个个体最可能的组别归属 posterior - fit3posteriori class_assign - apply(posterior, 1, which.max) df$class - class_assign # 看看各类别里的样本量 table(df$class)这一步在后期写报告时很常用。比如你可以基于class变量继续比较各组在基线特征上的差异用卡方检验或方差分析丰富研究结论。4. 核心参数选型与模型比较4.1 怎么确定类别数K跑一趟三类别模型只是开始真正的工作在模型比较阶段。GBTM里最核心的决策是“到底分成几组比较合理”这没有固定答案我通常的做法是把K1到K6的模型全部跑一遍然后用BIC做初步筛选。# 批量比较不同类别数的模型 result_list - list() for (k in 1:6) { deg_list - rep(list(2), k) fit - trajeR::trajeR(Y Ymat, A Amat, deg deg_list, Model CNORM, Method REML) result_list[[k]] - fit } # 提取BIC/AIC/LogLik model_compare - data.frame( K 1:6, BIC sapply(result_list, function(x) xBIC), AIC sapply(result_list, function(x) xAIC), LogLik sapply(result_list, function(x) xloglik) ) print(model_compare)BIC的判断原则是“越小越好”但不要机械地选最小BIC对应的K。我见过不少人选了一个BIC最小的6组模型结果有两个组的占比只有1%完全不具备临床或实际解释意义。更合理的策略是画出BIC随K变化的折线图找到一个“拐点”——BIC下降幅度明显变缓的位置通常就是合适的类别数。4.2 多项式阶数怎么选deg参数决定了每条轨迹是直线、二次曲线还是三次曲线。选多高的阶数合适核心原则是“够用就好”。阶数太高容易过拟合阶数太低又拟合不了曲线形态。我的实操顺序是先全部用二次项跑一遍如果二次项系数不显著再降为线性如果曲线形态明显呈现U型或倒U型考虑加三次项。不要一开始就全上三次项结果会出现很多拟合得咔咔响但毫无意义的波形轨迹。4.3 Model和Method怎么选trajeR支持的Model类型主要有CNORM连续型数据、ZIP零膨胀计数数据等。如果你的结局变量是评分表总分、量表得分这类近似连续的数据选CNORM就好。如果结局是事件次数、症状个数这类计数数据尤其是大量为0的数据要用ZIP。Method方面REML和ML都可以选。我的习惯是样本量比较小时用REML因为它的估计偏差更小样本量非常大、计算时间敏感时可以用ML速度更快但精度略有损失。4.4 分类质量的硬指标除了整体拟合指标你还需要看分类质量。最核心的指标是平均后验概率AvePP。每个个体都会被模型算出属于每个组的后验概率而每个组的AvePP就是属于该组的个体在该组上的平均后验概率。AvePP大于0.7基本可以接受大于0.8表示分类质量很好。# 计算每个组的平均后验概率 posterior - fit3posteriori avepp - sapply(1:ncol(posterior), function(k) mean(posterior[class_assign k, k])) names(avepp) - paste0(Group, 1:length(avepp)) print(avepp)如果某个组的AvePP低于0.7说明这个组的成员身份模糊很多人在这个组和其他组之间摇摆。这时候要考虑是否合并组别或者增加测量时点来增强区分度。5. 常见报错与问题排查实录5.1 模型不收敛怎么办这是跑trajeR最常碰到的坑。表现是模型迭代到某一步就停止或者输出结果里有一堆NaN值。常见原因和处理方式如下初始值太差trajeR内置了自动初始值猜测但对复杂模型有时猜不准。解决办法是指定fct参数手工传入更合理的初始值比如从已有文献或描述性统计出发设定各组的大致截距和斜率。类别数太多数据只有300个样本硬要分成6组难免有组人数太少导致估计不稳定。解决办法是减少K或者提高该组的占比约束。deg设置过高3次项4次项会让似然面变得很崎岖优化器容易卡住。解决办法是先用低阶项跑通再逐步加阶。5.2 某组占比过小怎么办跑出来的结果里如果有一组占比不到5%我会非常警惕。这种小集群很可能不是稳定的亚组而是极端值或模型人为切出来的边界片段。处理办法是先看这批个体到底长什么样。把他们的原始轨迹单独画出来如果走势没有清晰的共性直接尝试减少类别数重新拟合。如果确实验证出非常有意义的一小撮人比如症状极端且预后特殊那可以在论文里作为exploratory finding报告但不要一开始就把模型结论建立在这么小的组上。5.3 不同种子下结果不一致trajeR本身不涉及随机初始化结果应该是确定性的。但如果你在数据拆分、缺失值填补或模拟步骤中用了随机过程不同随机种子下类别归属可能有差异。别怕这很正常。我的处理方式是固定随机种子跑一遍主分析然后额外跑敏感性分析改变种子和初始值看类别数、轨迹形态和占比是否保持一致。如果差异很大说明数据结构本身对模型选择不敏感结果要谨慎解读。稳健性是轨迹分析里最容易被忽视、却也最重要的一环。5.4 画图时的定制需求默认的plot(fit3)虽然方便但投稿或者写报告时往往需要调整样式。trajeR的plot函数支持基本的参数自定义比如颜色、线型、图例标题但如果你想做更复杂的图形组合比如每个组单独一个面板、加上置信区间建议直接把各组的多项式系数导出来用ggplot2自己画。# 提取各组系数 coefs - fit3coef print(coefs)拿到系数之后你就可以手动生成轨迹线加上置信区间甚至和真实观测均值叠加对比。这一步对你向合作者解释结果特别有帮助毕竟大多数人看到一堆数字是没感觉的但看到“三条曲线分得清清楚楚”就会点头。5.5 结果解读中容易犯的错最后提醒一下GBTM分组的结果是数据驱动的分出来的组不一定有生物学或社会学意义。你要在分析之前就明确这个分组结果是否能在理论上被解释组间差异是否与已有的研究背景吻合如果模型选出来5组但第2组和第4组轨迹几乎重叠理论也无法解释那就应该减少类别数。我自己的经验是GBTM的价值不在“数学上最优”而在“解释上有意义”。模型比较指标只是参考最终的决策往往是“统计指标理论可解释性实际应用价值”三方面权衡的结果。写在最后的一点体会trajeR包整体用下来给我的感觉是“轻、快、稳”。它的语法设计很简洁核心函数就一个不像一些R包要记十几个关联函数。处理几百到几千人的纵向数据跑一个三组模型只要几秒到几十秒完全不需要等得很焦虑。不过它也有明显短板文档不够详尽部分参数说明藏在包源码里需要自己探索默认绘图功能比较基础定制化还得靠ggplot2。但整体来说对于做组轨迹识别的研究者trajeR是目前R生态里性价比很高的选择。如果你正在筹划自己的轨迹分析我的建议是先从模拟数据跑通流程再上手真实数据。先固定类别数K3全用二次项看看结果大概什么样再逐步放松假设。一旦你对这个包的手感熟了后面的分析就会顺畅得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价