Skip to content

Commit dcb9cfa

Browse files
20250717 - latent basis growth curve model
1 parent f670c8d commit dcb9cfa

File tree

1 file changed

+243
-0
lines changed

1 file changed

+243
-0
lines changed

sem.Rmd

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,249 @@ ggplot(
374374
linewidth = 2)
375375
```
376376

377+
# Latent Basis Growth Curve Model {#lbgcm}
378+
379+
## Model Syntax
380+
381+
### Abbreviated
382+
383+
```{r}
384+
lbgcm1_syntax <- '
385+
# Intercept and slope
386+
intercept =~ 1*t1 + 1*t2 + 1*t3 + 1*t4
387+
slope =~ 0*t1 + a*t2 + b*t3 + 3*t4 # freely estimate the loadings for t2 and t3
388+
389+
# Regression paths
390+
intercept ~ x1 + x2
391+
slope ~ x1 + x2
392+
393+
# Time-varying covariates
394+
t1 ~ c1
395+
t2 ~ c2
396+
t3 ~ c3
397+
t4 ~ c4
398+
'
399+
```
400+
401+
### Full
402+
403+
```{r}
404+
lbgcm2_syntax <- '
405+
# Intercept and slope
406+
intercept =~ 1*t1 + 1*t2 + 1*t3 + 1*t4
407+
slope =~ 0*t1 + a*t2 + b*t3 + 3*t4 # freely estimate the loadings for t2 and t3
408+
409+
# Regression paths
410+
intercept ~ x1 + x2
411+
slope ~ x1 + x2
412+
413+
# Time-varying covariates
414+
t1 ~ c1
415+
t2 ~ c2
416+
t3 ~ c3
417+
t4 ~ c4
418+
419+
# Constrain observed intercepts to zero
420+
t1 ~ 0
421+
t2 ~ 0
422+
t3 ~ 0
423+
t4 ~ 0
424+
425+
# Estimate mean of intercept and slope
426+
intercept ~ 1
427+
slope ~ 1
428+
'
429+
```
430+
431+
## Fit the Model
432+
433+
### Abbreviated
434+
435+
```{r}
436+
lbgcm1_fit <- growth(
437+
lbgcm1_syntax,
438+
data = Demo.growth,
439+
missing = "ML",
440+
estimator = "MLR",
441+
meanstructure = TRUE,
442+
int.ov.free = FALSE,
443+
int.lv.free = TRUE,
444+
fixed.x = FALSE,
445+
em.h1.iter.max = 100000)
446+
```
447+
448+
### Full
449+
450+
```{r}
451+
lbgcm2_fit <- sem(
452+
lbgcm2_syntax,
453+
data = Demo.growth,
454+
missing = "ML",
455+
estimator = "MLR",
456+
meanstructure = TRUE,
457+
fixed.x = FALSE,
458+
em.h1.iter.max = 100000)
459+
```
460+
461+
## Summary Output
462+
463+
### Abbreviated
464+
465+
```{r}
466+
summary(
467+
lbgcm1_fit,
468+
fit.measures = TRUE,
469+
standardized = TRUE,
470+
rsquare = TRUE)
471+
```
472+
473+
### Full
474+
475+
```{r}
476+
summary(
477+
lbgcm2_fit,
478+
fit.measures = TRUE,
479+
standardized = TRUE,
480+
rsquare = TRUE)
481+
```
482+
483+
## Estimates of Model Fit
484+
485+
```{r}
486+
fitMeasures(
487+
lbgcm1_fit,
488+
fit.measures = c(
489+
"chisq", "df", "pvalue",
490+
"chisq.scaled", "df.scaled", "pvalue.scaled",
491+
"chisq.scaling.factor",
492+
"baseline.chisq","baseline.df","baseline.pvalue",
493+
"rmsea", "cfi", "tli", "srmr",
494+
"rmsea.robust", "cfi.robust", "tli.robust"))
495+
```
496+
497+
## Residuals of Observed vs. Model-Implied Correlation Matrix
498+
499+
```{r}
500+
residuals(
501+
lbgcm1_fit,
502+
type = "cor")
503+
```
504+
505+
## Modification Indices
506+
507+
```{r}
508+
modificationindices(
509+
lbgcm1_fit,
510+
sort. = TRUE)
511+
```
512+
513+
## Internal Consistency Reliability
514+
515+
```{r}
516+
compRelSEM(lbgcm1_fit)
517+
```
518+
519+
## Path Diagram
520+
521+
```{r}
522+
semPaths(
523+
lbgcm1_fit,
524+
what = "Std.all",
525+
layout = "tree2",
526+
edge.label.cex = 1.5)
527+
```
528+
529+
## Plot Trajectories
530+
531+
### Protoypical Growth Curve
532+
533+
```{r}
534+
lbgcm1_intercept <- coef(lbgcm1_fit)["intercept~1"]
535+
lbgcm1_slope <- coef(lbgcm1_fit)["slope~1"]
536+
lbgcm1_slopeloadingt2 <- coef(lbgcm1_fit)["a"]
537+
lbgcm1_slopeloadingt3 <- coef(lbgcm1_fit)["b"]
538+
539+
timepoints <- 4
540+
541+
newData <- data.frame(
542+
time = 1:4,
543+
slopeloading = c(0, lbgcm1_slopeloadingt2, lbgcm1_slopeloadingt3, 3)
544+
)
545+
546+
newData$predictedValue <- NA
547+
newData$predictedValue <- lbgcm1_intercept + lbgcm1_slope * newData$slopeloading
548+
549+
ggplot(
550+
data = newData,
551+
mapping = aes(x = time, y = predictedValue)) +
552+
xlab("Timepoint") +
553+
ylab("Score") +
554+
scale_y_continuous(
555+
limits = c(0, 5)) +
556+
geom_line()
557+
```
558+
559+
### Individuals' Growth Curves
560+
561+
```{r}
562+
person_factors <- as.data.frame(predict(lbgcm1_fit))
563+
person_factors$id <- rownames(person_factors)
564+
565+
slope_loadings <- c(0, lbgcm1_slopeloadingt2, lbgcm1_slopeloadingt3, 3)
566+
567+
# Compute model-implied values for each person at each time point
568+
individual_trajectories <- person_factors %>%
569+
rowwise() %>%
570+
mutate(
571+
t1 = intercept + slope * slope_loadings[1],
572+
t2 = intercept + slope * slope_loadings[2],
573+
t3 = intercept + slope * slope_loadings[3],
574+
t4 = intercept + slope * slope_loadings[4]
575+
) %>%
576+
ungroup() %>%
577+
select(id, t1, t2, t3, t4) %>%
578+
pivot_longer(
579+
cols = starts_with("t"),
580+
names_to = "timepoint",
581+
values_to = "value") %>%
582+
mutate(
583+
time = as.integer(substr(timepoint, 2, 2)) # extract number from "t1", "t2", etc.
584+
)
585+
586+
ggplot(
587+
data = individual_trajectories,
588+
mapping = aes(x = time, y = value, group = factor(id))) +
589+
xlab("Timepoint") +
590+
ylab("Score") +
591+
scale_y_continuous(
592+
limits = c(-10, 20)) +
593+
geom_line()
594+
```
595+
596+
### Individuals' Trajectories Overlaid with Prototypical Trajectory
597+
598+
```{r}
599+
#ggplot(
600+
# data = newData) +
601+
# xlab("Timepoint") +
602+
# ylab("Score") +
603+
# scale_x_continuous(
604+
# limits = c(0, 3),
605+
# labels = 1:4) +
606+
# scale_y_continuous(
607+
# limits = c(-10, 20)) +
608+
# geom_abline(
609+
# mapping = aes(
610+
# slope = slope,
611+
# intercept = intercept)) +
612+
# geom_abline(
613+
# mapping = aes(
614+
# slope = lgcm1_slope,
615+
# intercept = lgcm1_intercept),
616+
# color = "blue",
617+
# linewidth = 2)
618+
```
619+
377620
# Latent Change Score Model {#lcsm}
378621

379622
## Model Syntax

0 commit comments

Comments
 (0)