【LCMM】纵向轨迹模型,组轨迹模型
latent_class_mixed_models
基础知识
增长混合模型(GMM)和潜在类别增长模型(LCGA)的核心区别确实主要在于是否允许类别内存在随机效应,但两者的差异还涉及模型灵活性、假设和应用场景等方面。以下是详细对比:
- 随机效应的处理
GMM(增长混合模型)
允许类别内随机效应:每个潜在类别中,个体的增长参数(如截距、斜率)可以围绕类别均值存在随机变异。例如,同一类别的个体可能有不同的截距和斜率,服从某种分布(如正态分布)。
更灵活:适用于数据中存在“类别内异质性”的情况,即同一类别内个体的轨迹不完全一致。
LCGA(潜在类别增长分析)
无随机效应:每个潜在类别内的所有个体具有完全相同的增长参数(固定效应),即类别内的轨迹是严格同质的(无个体变异)。
更严格:假设同一类别的个体轨迹完全相同,可能忽略实际存在的个体差异。
- 模型复杂度与假设
GMM
参数更多:需要估计随机效应的方差-协方差矩阵,模型更复杂。
样本量需求更大:由于参数更多,通常需要更大的样本量以保证估计稳定性。
适用性:适合探索“类别内异质性”的复杂场景,例如研究不同亚群体的发展轨迹时,同时考虑群体间和群体内的差异。
LCGA
参数更少:仅需估计类别均值和残差方差,模型更简单。
收敛更容易:计算复杂度低,适合小样本或初步探索性分析。
适用性:适用于假设类别内个体高度同质的情况,例如明确划分的干预组或自然分组。
- 实际应用中的选择
模型选择依据:
通过信息准则(如BIC、AIC)或似然比检验(需注意模型嵌套关系)比较。
若GMM中随机效应的方差接近零,则可简化为LCGA。
权衡:
GMM灵活性高,但可能过拟合;LCGA更简洁,但可能忽略重要变异。
实践建议:先尝试LCGA,若模型拟合不佳(如残差变异大),再考虑GMM。
- 其他差异
协变量纳入:两者均可纳入协变量预测类别成员资格(通过多项Logit模型),但GMM还可分析协变量对类别内随机效应的影响。
轨迹形状:LCGA通常假设线性或多项式增长,而GMM可结合更复杂的非线性随机效应。
示例1-GROW
TH MIXTURE MODEL
This repository holds code associated with the publication titled: “Identifying typical trajectories in longitudinal data: modelling strategies and interpretations” (DOI: https://doi.org/10.1007/s10654-020-00615-6 ).
# Load the necessary packages for latent class mixed models and spline functions
library(lcmm)
library(splines)# input dataset df:
# data frame with columns pseudonymised patient episode identifier,
# time from blood culture collection to clinical parameter measurements,
# and C-reactive protein values (Box-Cox transformed).# Define the natural spline basis for 'time'
# using the 25th, 50th, and 75th percentiles as knots and
# 1st and 99th percentiles as boundary knots
timeSplineKnots <- quantile(df$time, c(0.25, 0.5, 0.75))
timeSplineBoundary <- quantile(df$time, c(0.01, 0.99))
timeSpline <- ns(df$time, knots = timeSplineKnots, Boundary.knots = timeSplineBoundary)# Renaming columns for interpretability
timeSplineColNames <- paste0("T", 1:ncol(timeSpline))
colnames(timeSpline) <- timeSplineColNames# Binding the natural spline terms to the original dataframe
df <- cbind(df, timeSpline)# Fitting a series of latent class mixed models with varying number of classes (ng)
# Beginning with a standard linear mixed model (LCMM_CRP_1)
LCMM_CRP_1 <- lcmm(CRP_trans ~ T1 + T2 + T3 + T4,random = ~ T1 + T2 + T3 + T4,subject = "EpisodeID",nproc = 20, # Number of cores to use for parallel processingdata = df
)m2d <- gridsearch(hlme(normMMSE ~ age65+I(age65^2)+CEP,random =~ age65+I(age65^2), # 随机效应部分subject = 'ID',data=paquid, ng = 2, mixture=~age65+I(age65^2)),rep=100, maxiter=30, minit=m1)# Fitting latent class mixed models with multiple classes
# using LCMM_CRP_1 as the starting values (B = LCMM_CRP_1)
LCMM_CRP_2 <- lcmm(CRP_trans ~ T1 + T2 + T3 + T4,mixture = ~ T1 + T2 + T3 + T4,random = ~ T1 + T2 + T3 + T4, maxiter = 10000,subject = "EpisodeID", ng = 2, B = LCMM_CRP_1,nproc = 20, # Number of cores to use for parallel processingdata = df
)# The process continues, increasing ng for each model to test for better fitting class numbers
# The `update` function is used to change the number of classes while keeping the rest of the model the same.
LCMM_CRP_3 <- update(LCMM_CRP_2, ng = 3)
LCMM_CRP_4 <- update(LCMM_CRP_3, ng = 4)
LCMM_CRP_5 <- update(LCMM_CRP_4, ng = 5)
LCMM_CRP_6 <- update(LCMM_CRP_5, ng = 6)postprob(d4)conv<-ifelse(d4$conv==1, "TRUE", "FALSE")k<-paste(d4$AIC, d4$BIC, exp(d4$loglik), conv)k<-gsub(" ", ",", k)pp<-d4$pprobpp<-cbind(pp[,3:(ncol(pp))], d4$pprob$class)postprob(mod4)
示例2
## Package lcmm
##
## Cecile Proust-Lima, Viviane Philipps, Amadou Diakite & Benoit Liquet: lcmm: Extended Mixed Models Using Latent Classes and Latent Processes.
## R package version 1.81. https://CRAN.R-project.org/package=lcmm
# Note: Data needs to be in long format library(lcmm)#load data
data <- read.table("Data.csv",sep=",", header= TRUE, na.strings = NA)#recenter and scale age
data$age12 <- (data$age - 12)/10### Estimation of mixed-effect models and latent class mixed-effect models for continuous Gaussian outcomes ### #Estimate the model with only one class (ng = 1)
m1 <- hlme(bmi ~ age12+I(age12^2),random =~ age12+I(age12^2),subject = 'ID', data = data) #estimate the model with two classes (ng = 2):
m2 <- hlme(bmi ~ age12+I(age12^2),random =~ age12+I(age12^2), mixture=~age12+I(age12^2), subject = 'ID', data = data, ng = 2, B=m1) # ng=2
postprob(m2)# Estimate the model with two classes using gridsearch:
m2_gridsearch <- gridsearch(hlme(bmi ~ age12+I(age12^2),random =~ age12+I(age12^2), subject = 'ID', data = data, ng = 2, mixture=~age12+I(age12^2)),rep=100, maxiter=50, minit=m1) #ng=2
postprob(m2_gridsearch)#get summary of the models
summarytable(m1, m2, m2_gridsearch, which = c("G", "loglik", "conv", "npm", "AIC", "BIC", "SABIC", "entropy", "%class"))### Estimation of mixed-effect models and latent class mixed-effect models
### for continuous non-Gaussian or ordinal outcomes ###
m1_lcmm <- lcmm(fussy_eating ~ age12+I(age12^2),random =~ age12+I(age12^2), subject = 'ID', data = data, link='thresholds')
summary(m1_lcmm) ### Estimation of mixed-effect models and latent class mixed-effect models
### for multiple longitudinal markers measuring the same underlying latent process ###
m1_mult_lin <- multlcmm(BMI + fussy_eating ~ age12+I(age12^2) + time + I(time^2/10), random =~ time +I(time^2 / 10), subject= 'ID', data = data, randomY = TRUE, cor = BM(time), link = c('linear','thresholds'))
summary(m1_mult_lin)### Estimation of mixed-effect models and latent class mixed-effect models
### for longitudinal markers and time to event (possibly with competing risks) ###
m1_jointlcmm <- Jointlcmm(BMI~ age12+I(age12^2),random=~age12+I(age12^2),subject='ID', data=data, ng=1, survival = Surv(Tevent,Event)~ time+event, hazard="3-quant-splines", hazardtype="PH") #ng=1
summary(m1_jointlcmm)
示例3
############################################################################################################
############# Growth modeling for ECHO data
############################################################################################################library(tidyverse)
library(config)
library(here)
library(lcmm)
Sys.setenv(R_CONFIG_ACTIVE = "mike") # 'default')#
config <- config::get()# gm_df <- read.csv(here(config$ECHO_rolling_window_8_LIWC_path,config$ECHO_rolling_window_8_LIWC_name))############# rLSM.D modelsgm_df <- ECHO_smoothed_rLSM %>% # This df comes from runing the rLSM script trhough line 124 (stops before aggregating to file level)mutate(rLSM = rowMeans(select(.,contains('.rLSM')),na.rm = TRUE)) %>%drop_na(text_agg) %>%filter(Speaker == 'D') %>%select(File, rLSM) %>%drop_na() %>%# mutate(# rLSM = scale(rLSM,center = TRUE)# ) %>%group_by(File) %>%mutate(id = row_number(),# t = case_when(id < (max(id)/5) ~ 0,# id < (max(id/5))*2 ~ 1,# id < (max(id/5))*3 ~ 2,# id < (max(id/5))*4 ~ 3,# TRUE ~ 4)t = case_when(id < (max(id)/10) ~ 0,id < (max(id/10))*2 ~ 1,id < (max(id/10))*3 ~ 2,id < (max(id/10))*4 ~ 3,id < (max(id/10))*5 ~ 4,id < (max(id/10))*6 ~ 5,id < (max(id/10))*7 ~ 6,id < (max(id/10))*8 ~ 7,id < (max(id/10))*9 ~ 8,TRUE ~ 9)) %>%ungroup() %>%select(-id) %>%group_by(File,t) %>%summarize(rLSM = mean(rLSM)) %>%ungroup() %>% mutate(group_id = group_indices(., File))m1.rLSM.D.v2 <- lcmm::hlme(rLSM ~ t,# random = ~ t,subject = 'group_id',data = gm_df
)m2.rLSM.D.v2 <- lcmm::hlme(rLSM ~ t,# random = ~ t,mixture = ~ t,ng = 2,subject = 'group_id',data = gm_df,B = random(m1.rLSM.D.v2))# m2g.rLSM.D.v2 <- lcmm::gridsearch(
# rep = 100, maxiter = 30, minit = m1.rLSM.D.v2,
# hlme(rLSM ~ t,
# # random = ~ 1 + t,
# mixture = ~ t,
# ng = 2,
# subject = 'group_id',
# data = gm_df))m3.rLSM.D.v2 <- lcmm::hlme(rLSM ~ t,# random = ~ 1 + t,mixture = ~ t,ng = 3,subject = 'group_id',data = gm_df,B = random(m1.rLSM.D.v2)
)# m3g.rLSM.D.v2 <- lcmm::gridsearch(
# rep = 100, maxiter = 30, minit = m1.rLSM.D.v2,
# hlme(rLSM ~ t,
# random = ~ 1 + t,
# mixture = ~ t,
# ng = 3,
# subject = 'group_id',
# data = gm_df))m4.rLSM.D.v2 <- lcmm::hlme(rLSM ~ t,# random = ~ 1 + t,mixture = ~ t,ng = 4,subject = 'group_id',data = gm_df,B = random(m1.rLSM.D.v2)
)# m4g.rLSM.D.v2 <- lcmm::gridsearch(
# rep = 100, maxiter = 30, minit = m1.rLSM.D.v2,
# hlme(rLSM ~ t,
# random = ~ 1 + t,
# mixture = ~ t,
# ng = 4,
# subject = 'group_id',
# data = gm_df))m5.rLSM.D.v2 <- lcmm::hlme(rLSM ~ t,# random = ~ 1 + t,mixture = ~ t,ng = 5,subject = 'group_id',data = gm_df,B = random(m1.rLSM.D.v2)
)
#
# m5g.rLSM.D.v2 <- lcmm::gridsearch(
# rep = 100, maxiter = 30, minit = m1.rLSM.D.v2,
# hlme(rLSM ~ t,
# random = ~ 1 + t,
# mixture = ~ 1 + t,
# ng = 5,
# subject = 'group_id',
# data = gm_df))summarytable(m1.rLSM.D.v2,m2.rLSM.D.v2,m3.rLSM.D.v2, m4.rLSM.D.v2,m5.rLSM.D.v2,# m4, m4g,#m5,m5g,which = c("G", "loglik", "conv", "npm", "AIC", "BIC", "SABIC", "entropy","ICL", "%class"))
# summaryplot(m1.rLSM.D,m2.rLSM.D,m2g.rLSM.D,m3.rLSM.D,m3g.rLSM.D,
# # m4, m4g,#m5,m5g,
# which = c("BIC", "entropy","ICL"))summary(m2.rLSM.D.v2)data_pred0 <- data.frame(t = seq(0,9,length.out = 50), CEP = 0)
data_pred1 <- data.frame(t = seq(0,9,length.out = 50), CEP = 1)
pred0 <- predictY(m2.rLSM.D, data_pred0, var.time = "t")
pred1 <- predictY(m2.rLSM.D, data_pred1, var.time = "t")plot(pred0, col=c("red","navy"), lty=1,lwd=5,ylab="normMMSE",legend=NULL, main="Predicted trajectories for normMMSE ",ylim=c(.6,.8))
plot(pred1, col=c("red","navy"), lty=2,lwd=3,legend=NULL,add=TRUE)
legend(x="topright",legend=c("class1 :","CEP-","CEP+","class2:","CEP-","CEP+"), col=c(rep("red",3),rep("navy",3)), lwd=2, lty=c(0,1,2,0,1,2), ncol=2, bty="n", cex = 0.7)rLSM.D.2Class <- m2.rLSM.D.v2$pprob %>%full_join(gm_df, by = 'group_id') %>%group_by(class,t) %>%mutate(t = t+1) %>%summarize(sd = sd(rLSM,na.rm = TRUE),rLSM = mean(rLSM)) %>%ungroup() %>%mutate(class = as.factor(class)) %>%ggplot(aes(x = as.factor(t), y = rLSM, color = class, group = class)) + geom_point() + geom_line() + ggthemes::theme_tufte() +# geom_errorbar(aes(ymin=rLSM-sd, ymax=rLSM+sd), width=.2,# position=position_dodge(.9)# ) +scale_fill_brewer(palette = "Set2") +labs(title = 'Mean rw.rLSM.D over time within encounters by growth class',x = 'Decile of turns within encounter',y = 'Mean rw.LSM.D')############# rLSM.P modelsgm_df_p <- ECHO_smoothed_rLSM %>% # This df comes from runing the rLSM script trhough line 124 (stops before aggregating to file level)mutate(rLSM = rowMeans(select(.,contains('.rLSM')),na.rm = TRUE)) %>%drop_na(text_agg) %>%filter(Speaker == 'P') %>%select(File, rLSM) %>%drop_na() %>%group_by(File) %>%mutate(id = row_number(),# t = case_when(id < (max(id)/5) ~ 0,# id < (max(id/5))*2 ~ 1,# id < (max(id/5))*3 ~ 2,# id < (max(id/5))*4 ~ 3,# TRUE ~ 4)t = case_when(id < (max(id)/10) ~ 0,id < (max(id/10))*2 ~ 1,id < (max(id/10))*3 ~ 2,id < (max(id/10))*4 ~ 3,id < (max(id/10))*5 ~ 4,id < (max(id/10))*6 ~ 5,id < (max(id/10))*7 ~ 6,id < (max(id/10))*8 ~ 7,id < (max(id/10))*9 ~ 8,TRUE ~ 9)) %>%ungroup() %>%select(-id) %>%group_by(File,t) %>%summarize(rLSM = mean(rLSM)) %>%ungroup() %>% mutate(group_id = group_indices(., File))m1.rLSM.P.v2 <- lcmm::hlme(rLSM ~ t,random = ~ 1 + t,subject = 'group_id',data = gm_df_p
)m2.rLSM.P.v2 <- lcmm::hlme(rLSM ~ t,random = ~ 1 + t,mixture = ~ t,ng = 2,subject = 'group_id',data = gm_df_p,B = random(m1.rLSM.P.v2)
)# m2g.rLSM.P.v2 <- lcmm::gridsearch(
# rep = 100, maxiter = 30, minit = m1.rLSM.P.v2,
# hlme(rLSM ~ t,
# random = ~ 1 + t,
# mixture = ~ t,
# ng = 2,
# subject = 'group_id',
# data = gm_df_p))m3.rLSM.P.v2 <- lcmm::hlme(rLSM ~ t,random = ~ 1 + t,mixture = ~ t,ng = 3,subject = 'group_id',data = gm_df_p,B = random(m1.rLSM.P.v2)
)# m3g.rLSM.P.v2 <- lcmm::gridsearch(
# rep = 100, maxiter = 30, minit = m1.rLSM.P.v2,
# hlme(rLSM ~ t,
# random = ~ 1 + t,
# mixture = ~ t,
# ng = 3,
# subject = 'group_id',
# data = gm_df_p))m4.rLSM.P.v2 <- lcmm::hlme(rLSM ~ t,random = ~ 1 + t,mixture = ~ t,ng = 4,subject = 'group_id',data = gm_df_p,B = random(m1.rLSM.P.v2)
)# m4g.rLSM.P.v2 <- lcmm::gridsearch(
# rep = 100, maxiter = 30, minit = m1.rLSM.P.v2,
# hlme(rLSM ~ t,
# random = ~ 1 + t,
# mixture = ~ t,
# ng = 4,
# subject = 'group_id',
# data = gm_df_p))m5.rLSM.P.v2 <- lcmm::hlme(rLSM ~ t,random = ~ 1 + t,mixture = ~ t,ng = 5,subject = 'group_id',data = gm_df_p,B = random(m1.rLSM.P.v2)
)# m5g.rLSM.P.v2 <- lcmm::gridsearch(
# rep = 100, maxiter = 30, minit = m1.rLSM.P.v2,
# hlme(rLSM ~ t,
# # random = ~ 1 + t,
# mixture = ~ 1 + t,
# ng = 5,
# subject = 'group_id',
# data = gm_df))summarytable(m1.rLSM.P.v2,m2.rLSM.P.v2,m3.rLSM.P.v2,m4.rLSM.P.v2,m5.rLSM.P.v2,which = c("G", "loglik", "conv", "npm", "AIC", "BIC", "SABIC", "entropy","ICL", "%class"))
# summaryplot(m1.rLSM.P,m2.rLSM.P,m2g.rLSM.P,m3.rLSM.P,m3g.rLSM.P,
# # m4, m4g,#m5,m5g,
# which = c("BIC", "entropy","ICL"))summary(m2.rLSM.P)data_pred0 <- data.frame(t = seq(0,9,length.out = 50), CEP = 0)
data_pred1 <- data.frame(t = seq(0,9,length.out = 50), CEP = 1)
pred0 <- predictY(m2.rLSM.P, data_pred0, var.time = "t")
pred1 <- predictY(m2.rLSM.P, data_pred1, var.time = "t")plot(pred0, col=c("red","navy"), lty=1,lwd=5,ylab="normMMSE",legend=NULL, main="Predicted trajectories for normMMSE ",ylim=c(0,1))
plot(pred1, col=c("red","navy"), lty=2,lwd=3,legend=NULL,add=TRUE)
legend(x="topright",legend=c("class1 :","CEP-","CEP+","class2:","CEP-","CEP+"), col=c(rep("red",3),rep("navy",3)), lwd=2, lty=c(0,1,2,0,1,2), ncol=2, bty="n", cex = 0.7)rLSM.P.2Class <- m2.rLSM.P.v2$pprob %>%full_join(gm_df, by = 'group_id') %>%mutate(t = t+1) %>%group_by(class,t) %>%summarize(sd = sd(rLSM,na.rm = TRUE),rLSM = mean(rLSM)) %>%# ungroup() %>%mutate(class = as.factor(class)) %>%ggplot(aes(x = as.factor(t), y = rLSM, color = class, group = class)) + geom_point() + geom_line() + ggthemes::theme_tufte() +scale_fill_brewer(palette = "Set2") +labs(title = 'Mean rw.rLSM.P over time within encounters by growth class',x = 'Decile of turns within encounter',y = 'Mean rw.LSM.P')############# classes to outcomesclass_df <- m2.rLSM.P$pprob %>%full_join(gm_df, by = 'group_id') %>%rename(class.P = class) %>%select(group_id, class.P, File) %>%full_join(m2.rLSM.D$pprob, by = 'group_id') %>%rename(class.D = class) %>%# select(-c(prob1,prob2,group_id)) %>%distinct()
table(class_df$class.D,class_df$class.P)#open survey data
ECHO_survey_data <- haven::read_dta(here(config$ECHO_survey_data_path, config$ECHO_survey_data_name))#Open ID key file
ECHO_ID_key <- read_csv('ECHO_ID_key.csv')#merge GROWTH_MODELING_FILE and ECHO_ID_key by "File"
class_df <- left_join(class_df, ECHO_ID_key, by = "File" ) #merge GROWTH_MODELING_FILE and ECHO_survey_data by "tapeid" to include survey data
class_df <- left_join(class_df, ECHO_survey_data, by = "tapeid")# Transforming cdspeak from continuous to dichotomous variable "cdspeakhigh"
class_df <- class_df %>%rowwise() %>%mutate(cdspeakhigh = ifelse(cdspeak >= 3, 1, 0))#Making the provider id variable
class_df <- class_df %>%mutate(provider_id = str_sub(File, 1, 4))#Making the site_name variable: only three sites in data set: JC, OC, SC
class_df <- class_df %>%mutate(site_name = str_sub(provider_id, 1, 2))#creating site_id variable by converting JC==0, OC==1, SC==2
class_df <- class_df %>%mutate(site_id = ifelse(site_name == "JC", 0,ifelse(site_name == "OC", 1,2)))#changing site_id to factor
class_df <- class_df %>%mutate(site_id = factor(site_id))class_df <- class_df %>%mutate(provider_id = factor(provider_id))table(class_df$class.D,class_df$cdspeakhigh)
chisq.test(class_df$class.D,class_df$cdspeakhigh, correct = FALSE)# gginference::ggchisqtest(
# chisq.test(class_df$class.D,class_df$cdspeakhigh, correct = FALSE)
# )
table(class_df$class.P,class_df$cdspeakhigh)
chisq.test(class_df$class.P,class_df$cdspeakhigh, correct = FALSE)class_df <- class_df %>% mutate(x = paste0(class.D,'D_',class.P,'P'))
table(class_df$x,class_df$cdspeakhigh)
chisq.test(class_df$x,class_df$cdspeakhigh, correct = FALSE)library(gtsummary)
class_df %>%select(cdspeakhigh, x) %>%mutate(cdspeakhigh = as.factor(cdspeakhigh),x = as.factor(x)) %>%drop_na() %>%gtsummary::tbl_summary(by = x) %>%gtsummary::add_p(pvalue_fun = ~style_pvalue(.x, digits = 2))### Pulling pprob
pprob_df <- m2.rLSM.P$pprob %>%full_join(gm_df, by = 'group_id') %>%rename(class.P = class, P.prob1 = prob1, P.prob2 = prob2) %>%select(-t,-rLSM) %>%full_join(m2.rLSM.D$pprob, by = 'group_id') %>%rename(class.D = class, D.prob1 = prob1, D.prob2 = prob2) %>%select(-c(group_id)) %>%distinct()write_csv(pprob_df, 'ECHO_pprob.csv')
lcmm::gridsearch
第一段代码是单次拟合,计算速度较快,但可能陷入局部最优。
第二段代码通过网格搜索(rep = 100)和更宽松的迭代限制(maxiter = 30)提高找到全局最优解的可能性,但计算时间显著增加。
第一段代码可能是为了快速测试模型结构。
第二段代码更适用于正式分析,通过网格搜索确保参数估计的稳定性。
测试
以下是基于R语言的组轨迹模型(GBTM)或去掉随机效应变为LCMM,分析步骤,适用于纵向血压、体重数据,并纳入年龄、性别作为协变量,分析心血管疾病发病风险的示例代码:
# 1. 准备数据与加载包
# 加载所需包
library(lcmm)
library(tidyverse)
library(traj)# 模拟数据集(假设数据结构)
set.seed(123)
n <- 500 # 样本量
data <- data.frame(id = rep(1:n, each=4),time = rep(0:3, n), # 0-3表示4个时间点age = rep(rnorm(n, 50, 5), each=4),sex = rep(sample(c("Male", "Female"), n, replace=TRUE), each=4),sbp = rnorm(n*4, 120 + 0.5*time, 10), # 收缩压随时间轻微上升weight = rnorm(n*4, 70 - 0.3*time, 5), # 体重随时间轻微下降cvd = rep(rbinom(n, 1, 0.2), each=4) # 结局:是否发生心血管疾病
)
# 2. 拟合血压的组轨迹模型
# 使用lcmm包拟合潜类别混合模型
# 模型设定:时间固定效应,随机截距+斜率,按类别分组
bp_model <- hlme(fixed = sbp ~ time + age + sex, # 固定效应:时间、年龄、性别mixture = ~ time, # 类别间时间效应不同random = ~ time, # 随机效应:截距和斜率ng = 3, # 假设3个类别subject = "id", # 个体IDdata = data,B = random # 随机初始值
)# 查看模型结果
summary(bp_model)
plot(bp_model, which = "trajectory") # 绘制轨迹图
# 3. 选择最佳类别数(K)
# 比较不同K值的BIC
bic_values <- sapply(2:4, function(k) {model <- hlme(sbp ~ time + age + sex, mixture = ~time, random = ~time, ng = k, data = data)return(model$BIC)
})
plot(2:4, bic_values, type="b", xlab="Number of Classes", ylab="BIC")
# 选择BIC最小的K值
# 4. 分配个体到轨迹组
# 提取类别概率并分配最可能类别
data$bp_class <- as.factor(bp_model$pprob$class)# 查看各类别占比
table(data$bp_class) / n
# 5. 分析轨迹组与心血管疾病的关系
# 逻辑回归:轨迹组预测CVD发病(需每条个体保留唯一结局)
data_unique <- data %>% group_by(id) %>% slice(1) %>% ungroup()
glm_model <- glm(cvd ~ bp_class + age + sex, family = binomial, data = data_unique
)
summary(glm_model)
# 6. 对体重进行类似分析
# 重复步骤2-5,将因变量替换为weight。
结果比较
后续分析
相关文章:
【LCMM】纵向轨迹模型,组轨迹模型
latent_class_mixed_models 基础知识 增长混合模型(GMM)和潜在类别增长模型(LCGA)的核心区别确实主要在于是否允许类别内存在随机效应,但两者的差异还涉及模型灵活性、假设和应用场景等方面。以下是详细对比…...
Flask + ajax上传文件(三)--图片上传与OCR识别
本教程将详细介绍如何使用Flask框架构建一个图片上传与文字识别(OCR)的Web应用。我们将使用EasyOCR作为OCR引擎,实现一个支持中文和英文识别的完整应用。 环境准备 首先,确保你已经安装了Python 3.7+环境,然后安装必要的依赖库: pip install flask easyocr pillow werkz…...
观察者模式 (Observer Pattern)
观察者模式(Observer Pattern)是一种行为型设计模式。它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象的状态发生变化时,会自动通知所有观察者对象,使它们能够自动更新自己的状态。 一、基础 1. 意图 核心目的:定义对象间的一种一对…...
【Leetcode 每日一题】2444. 统计定界子数组的数目
问题背景 给你一个整数数组 n u m s nums nums 和两个整数 m i n K minK minK 以及 m a x K maxK maxK。 n u m s nums nums的定界子数组是满足下述条件的一个子数组: 子数组中的 最小值 等于 m i n K minK minK。子数组中的 最大值 等于 m a x K maxK maxK…...
LeetCode热题100——70. 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 示例 1: 输入:n 2 输出:2 解释:有两种方法可以爬到楼顶。 1 阶 1 阶2 阶 示例 2: …...
黑马Java基础笔记-4
方法 什么是方法 方法是程序中最小的执行单元。 形参和实参 调用 直接调用 getSum(10,20,30);赋值调用 int sum getSum(10,20,30);输出调用 System.out.println(getSum(10,20,30));方法的重载 在同一个类中,定义了多个同名的方法,这些同名的方法…...
【Python】Python中的浅拷贝和深拷贝
在Python中,浅拷贝(shallow copy)和深拷贝(deep copy)是两种不同的对象复制方式,它们在复制对象时的行为有所不同: 浅拷贝(Shallow Copy) 浅拷贝是创建一个新对象&…...
使用 LangGraph 和 Elasticsearch 构建强大的 RAG 工作流
作者:来自 Elastic Neha Saini 在这篇博客中,我们将向你展示如何配置和自定义 LangGraph Retrieval Agent 模板与 Elasticsearch,以构建一个强大的 RAG 工作流,实现高效的数据检索和由 AI 驱动的响应。 Elasticsearch 原生集成了…...
云原生--核心组件-容器篇-2-认识下Docker(三大核心之镜像,容器,仓库)
1、Docker基本概念 (1)、定义 Docker是一种开源的应用容器引擎,是基于操作系统级虚拟化技术。允许开发者将应用程序及其依赖项打包到一个可移植的容器中,然后发布到任何支持Docker的环境中运行。Docker容器是轻量级、独立且可执…...
智慧园区IOT项目与AI时代下的机遇 - Java架构师面试实战
在互联网大厂的Java求职者面试中,面试官通常会针对实际业务场景提出一系列问题。以下是关于智慧园区IOT项目及AI时代下的机遇的面试模拟对话。 第一轮提问 面试官:马架构,请简要介绍下智慧园区IOT项目的整体架构设计。 马架构:…...
Unity中文件上传以及下载,获取下载文件大小的解决方案
首先现在Unity插件那么的广泛的情况下,很多东西都不需要自己实现,直接使用第三方插件就可以了,但为什么这里需要自己写,接下来说明原因。 在Unity商城中有很多关于关于网络接口调用的插件,其中有一款叫BestHTTP这款使用比较广泛的插件,不知道朋友们是不是都知道,是不是…...
Word/WPS 删除最后一页空白页,且保持前面布局样式不变
如题,试了多种方法,都不行。主要是可能的原因太多了,没有通解,这只是适用于我的情况。 解决方案: 首先光标放在倒数第二页(即想保留的最后一页),点击页面右下角这个小箭头ÿ…...
MySQL长事务的隐患:深入剖析与解决方案
MySQL长事务的隐患:深入剖析与解决方案 一、什么是长事务? 在数据库系统中,长事务(Long Transaction)通常指执行时间超过预期或系统设定阈值的事务。对于MySQL而言,虽然没有严格的时间定义,但一般认为执行时间超过数…...
【Tauri】桌面程序exe开发 - Tauri+Vue开发Windows应用 - 比Electron更轻量!8MB!
效果图 Tauri的二进制文件体积显著小于Electron,安装包通常缩小80%以上。应用启动更快,内存占用更低,尤其在老旧设备上体验更流畅。 写在前面 Tauri官网 https://tauri.app/zh-cn/支持语言:js、ts、rust、.net编译出来的exe文件&…...
2025春季NC:3.1TheTrapeziumRule
3.1TheTrapeziumRule 📐 The Idea Instead of finding the exact area under a curve y = f ( x ) y = f(x) y=...
【摩尔定律】
一、摩尔定律的核心定义 原始表述(1965年) “集成电路上可容纳的晶体管数量,每隔约 18-24个月 便会增加一倍,同时性能提升一倍,而成本下降一半。” 简化理解 芯片的 晶体管密度 和…...
Maven 依赖冲突调解与版本控制
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编…...
《Python Web部署应知应会》Flask网站隐藏或改变浏览器URL:从Nginx反向代理到URL重写技术
Flask网站隐藏或改变浏览器显示URL地址的实现方案:从Nginx反向代理到URL重写技术 引言 在Web应用开发中,URL路径的安全性往往被忽视,这可能导致网站结构和后端逻辑被攻击者轻易推断。对于Flask框架开发的网站,如何隐藏或改变浏览…...
6.2 内容生成与营销:个性化内容创作与营销策略优化
随着消费者对个性化体验的需求日益增长,传统的内容创作与营销方式已难以满足市场竞争的需要。基于大语言模型(LLM)与智能代理(Agent)的技术为企业提供了全新的解决方案,能够实现高效、精准、规模化的内容生…...
平面连杆机构(上)
1、平面四杆机构的类型与演化 1)平面四杆机构的类型 a、铰链四杆机构:曲柄摇杆机构、双曲柄机构、双摇杆机构 b、其他四杆机构:曲柄滑块机构、导杆机构、滑块机构、双滑块机构、偏心轮四杆机构...... 2)平面四杆机构的演化 a、…...
【数据结构刷题】顺序表与ArrayList
【数据结构刷题】顺序表与ArrayList 1. 杨辉三角2. 合并两个有序数组 1. 杨辉三角 LC链接:杨辉三角 //杨辉三角import java.util.ArrayList; import java.util.List;public class Demo1 {public List<List<Integer>> generate(int numRows) {List<…...
顶点着色器和片元着色器染色+表面体着色器染色
顶点/片元着色器染色 创建材质球及Shader同名文件VFColor //Update NOTE:replaced mul(UNITY_MATRIX_MVP,*) with UnityObjectToClipPos(*) Shader "CreateTest/VFColor" {Properties{_Color("颜色",Color)(1,1,1,1)}SubShader{Pass{//顶点片…...
240426 leetcode exercises
240426 leetcode exercises jarringslee 文章目录 240426 leetcode exercises[1669. 合并两个链表](https://leetcode.cn/problems/merge-in-between-linked-lists/?envTypeproblem-list-v2&envIdlinked-list)🔁基础版 保存断点,先拼再补…...
代码随想录算法训练营Day35
卡码网46.携带研究材料 力扣494.目标和【meidum】 力扣416.分割等和子集【medium】 一、卡码网46.携带研究材料 题目链接:卡码网46.携带研究材料 视频链接:代码随想录 题解链接:代码随想录 1、思路 dp[i][j] 表示从下标为 [0-i] 的物品里任意…...
C++17 折叠表达式
C17 引入的折叠表达式(Fold Expressions) 是处理可变参数模板(Variadic Templates)的革命性特性。它通过简洁的语法,使得对参数包(Parameter Pack)的操作更加直观和高效,避免了传统的…...
Ubuntu编译opencv源码
准备 Ubuntu版本:22.04opencv版本:4.9.0没下载Ubuntu镜像的可以在清华镜像下载 本文以4.9.0版本演示,可根据自身情况选择 安装JDK和依赖项 本次编译主要为了获取java在linux环境下的动态库,所以需要在虚拟机上下载jdk # 安装…...
一种滑窗像素自差值的深度学习损失函数
公司项目,已申请专利。 深度学习作为新兴技术在图像领域蓬勃发展,因其自主学习图像数据特征避免了人工设计算法的繁琐,精准的检测性能、高效的检测效率以及对各种不同类型的图像任务都有比较好的泛化性能,使得深度学习技术在图像领…...
【Typecho】给Joe主题后台添加custom自定义功能!
大家好,今天来添加一下自定义功能! 😂 温馨提示:站长已经通过本地环境测试custom自定义功能,功能正常可以使用,按照我的操作来一定成功! 大纲 创建custom.php粘贴代码到custom.php文件引入cus…...
一些常见的资源池管理、分布式管理和负载均衡的监控工具
资源池管理监控工具 Prometheus 是一款开源的系统监控和警报工具。它可以通过收集各种指标数据,如CPU使用率、内存使用量、磁盘I/O等,来监控资源池中的服务器、容器等资源。Prometheus具有强大的查询语言和可视化功能,能够帮助管理员快速了解资源的使用情况,并及时发现潜在…...
WPF程序使用Sugar操作数据库
WPF 程序使用 Sugar ORM 操作数据库 一、引言 在 WPF(Windows Presentation Foundation)应用程序中,数据库操作是不可或缺的一部分。Sugar ORM(对象关系映射)是一种轻量级的 ORM 框架,它简化了数据库操作,使得开发者能够以面向对象的方式与数据库进行交互。本文将详细…...
【Castle-X机器人】四、智能机械臂安装与调试
持续更新。。。。。。。。。。。。。。。 【Castle-X机器人】智能机械臂安装与调试 四、智能机械臂安装与调试2.1 安装2.2 调试2.2.1 2D摄像头测试 四、智能机械臂安装与调试 2.1 安装 使用相应工具将机械臂固定在Castle-X机器人底盘 2.2 调试 2.2.1 2D摄像头测试 内容地址 链…...
goweb-signup注册功能实现
注册功能 route.go package routerimport ("bluebell/controller""github.com/gin-gonic/gin" )func SetupRouter(mode string) *gin.Engine {r : gin.Default()r.POST("/signup", controller.SignupHandler)return r }UserController.go pac…...
Linux: 如何在VMware上安装Ubuntu操作系统
在VMware上安装Ubuntu操作系统是一个相对简单的过程,以下是详细的步骤: 一、准备工作 安装VMware软件 确保你已经在电脑上安装了VMware Workstation(适用于Windows)或VMware Fusion(适用于Mac)。如果没有安…...
详解 Network.framework:iOS 网络开发的新基石
详解 Network.framework:iOS 网络开发的新基石 引言 自 iOS 12 和 macOS 10.14 起,Apple 推出了一个新的网络开发框架 —— Network.framework。它被定位为下一代网络连接的基础设施,让开发者可以以更安全、更高效的方式,管理 T…...
Java—— 五道算法水题
第一题 需求: 包装类:键盘录入一些1~100之间的整数,并添加到集合中。直到集合中所有数据和超过200为止 代码实现: import java.util.ArrayList; import java.util.Scanner;public class Test1 {public static void main(String[]…...
将服务器接到路由器上访问
应用场景: 实验室网卡更换了,新网卡没有报备到校园网,暂时无法通过外部链接连到服务器. 除了跳板机之外,可以使用以下方法将服务器接入到路由器访问. 将服务器接到交换机上,将交换机接到路由器上本地电脑 连接路由器wifi登录http://192.168.0.1/,访问路…...
MyBatis缓存配置的完整示例,包含一级缓存、二级缓存、自定义缓存策略等核心场景,并附详细注释和总结表格
以下是MyBatis缓存配置的完整示例,包含一级缓存、二级缓存、自定义缓存策略等核心场景,并附详细注释和总结表格: 1. 一级缓存(默认开启) // 使用同一SqlSession执行两次查询,自动命中一级缓存 try (SqlSe…...
我爱学算法之—— 二分查找(上)
了解二分算法 二分查找,想必多多少少有一点了解了,我们了解的二分查找算法: 当一个数组有序的时候,我们可以使用二分算法来查找一个值; 直接比较mid((left right)/2)和我们要查找的值target;如果nums[mid]…...
Tauri快速入门1 - 搭设开发环境
前言 Tauri框架结合了 Web 技术的优势,开发者能用熟悉的 HTML、CSS 和 JavaScript 进行开发,像开发网页应用一样便捷高效。 其次,该框架有着出色的性能表现,相比一些传统框架,其资源占用相对较低。在安全性方面&#x…...
tigase源码学习杂记-IO处理的线程模型
前言 tigase是一个高性能的服务器,其实个人认为作为即时通讯的服务器,高性能主要体现在他对IO复用,和多线程的使用上,今天来学习一下他的IO的线程处理模型的源码,并记录一下他优秀的设计。 概述 tigase是使用的NIO作…...
电商秒杀系统技术栈与难点解析 - Java架构师面试实战
电商秒杀系统技术栈与难点解析 - Java架构师面试实战 第一轮提问 面试官:马架构,欢迎参加我们公司的面试。首先,请您简单介绍一下自己。 马架构:您好,我叫马架构,拥有十年的Java研发经验和架构设计经验&…...
ASP.NET MVC 入门指南三
16. 安全性 16.1 身份验证和授权 身份验证:确认用户的身份。ASP.NET MVC 支持多种身份验证方式,如表单身份验证、Windows 身份验证和 OAuth 等。 表单身份验证:用户通过输入用户名和密码登录,服务器验证后颁发一个身份验证票证&…...
导览项目KD-Tree最近地点搜索优化
背景描述 我在做一个校园导览的小程序的时候,涉及到最近地点搜索的业务功能,根据当前位置搜索最近的校园地点,比如教学楼,图书馆,自习室,办事地点等等。 我最初想到的办法就是获取用户当前位置的经纬度后&…...
【Pandas】pandas DataFrame rmul
Pandas2.2 DataFrame Binary operator functions 方法描述DataFrame.add(other)用于执行 DataFrame 与另一个对象(如 DataFrame、Series 或标量)的逐元素加法操作DataFrame.add(other[, axis, level, fill_value])用于执行 DataFrame 与另一个对象&…...
苹果(IOS)手机怎么开启开发者模式(简单明了版)
苹果手机怎么开启开发者模式(简单明了版) iOS 16 以后,苹果新增了「开发者模式」。如果你要在 iPhone 上运行自己开发的 App,比如通过 Xcode 或其它工具安装测试包,必须先开启这个模式。 下面是开启方法👇…...
Agent2Agent
rag系列文章目录 文章目录 rag系列文章目录前言一、协议设计原则与技术基础二、通信机制与消息格式三、身份验证与安全设计四、能力发现与任务协作总结 前言 谷歌于2025年4月推出了A2A(Agent2Agent)协议,旨在解决当前AI智能体生态中的互操作…...
【MCP】了解远程MCP调用背后使用的SSE协议
本文介绍了远程MCP使用的SSE协议,通过wireshark抓包的方式了解MCP客户端和服务端之间通过SSE协议交互涉及到的请求与响应。 1. 什么是SSE协议? 参考:https://zhuanlan.zhihu.com/p/1894024642395619635和https://blog.csdn.net/aerror/artic…...
Log4j Properties 配置项详细说明
Log4j Properties 配置项详细说明 1. 核心配置项说明 根日志记录器:定义全局日志级别和输出目标 log4j.rootLogger [级别], appender1, appender2,...Appender 定义:指定日志输出目标(控制台、文件等) log4j.appender.[名称].[属…...
哪些物联网框架支持多协议接入?选型指南与核心能力解析
在物联网(IoT)领域,设备通信协议的多样性(如MQTT、CoAP、Modbus、Zigbee等)是开发者面临的核心挑战之一。选择支持多协议接入的物联网框架,可以显著降低异构设备连接的复杂度,提升系统的兼容性和…...
第三方测试机构如何保障软件质量并节省企业成本?
在软件行业,第三方测试机构扮演着极其重要的角色。他们提供独立且专业的测试服务,目的是为了保障软件的质量以及提升用户的使用体验。 专业独立 测试机构拥有经验丰富的测试员和严谨的测试流程。他们会对软件各项功能进行细致检验,力求不放…...