Skip to content

Commit f8e5aa7

Browse files
20250726 - spline GCM
1 parent 205fdbb commit f8e5aa7

1 file changed

Lines changed: 285 additions & 16 deletions

File tree

sem.Rmd

Lines changed: 285 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ individual_trajectories <- person_factors %>%
624624
ungroup() %>%
625625
select(id, t1, t2, t3, t4) %>%
626626
pivot_longer(
627-
cols = starts_with("t"),
627+
cols = t1:t4,
628628
names_to = "timepoint",
629629
values_to = "value") %>%
630630
mutate(
@@ -863,19 +863,6 @@ quadraticGCM1_intercept <- coef(quadraticGCM1_fit)["intercept~1"]
863863
quadraticGCM1_linear <- coef(quadraticGCM1_fit)["linear~1"]
864864
quadraticGCM1_quadratic <- coef(quadraticGCM1_fit)["quadratic~1"]
865865
866-
#ggplot() +
867-
# xlab("Timepoint") +
868-
# ylab("Score") +
869-
# scale_x_continuous(
870-
# limits = c(0, 3),
871-
# labels = 1:4) +
872-
# scale_y_continuous(
873-
# limits = c(0, 5)) +
874-
# geom_abline(
875-
# mapping = aes(
876-
# slope = lgcm1_slope,
877-
# intercept = lgcm1_intercept))
878-
879866
timepoints <- 4
880867
881868
newData <- data.frame(
@@ -920,7 +907,7 @@ individual_trajectories <- person_factors %>%
920907
ungroup() %>%
921908
select(id, t1, t2, t3, t4) %>%
922909
pivot_longer(
923-
cols = starts_with("t"),
910+
cols = t1:t4,
924911
names_to = "timepoint",
925912
values_to = "value") %>%
926913
mutate(
@@ -961,7 +948,289 @@ ggplot() +
961948
)
962949
```
963950

964-
## Spline Growth Curve Model {#splineLGCM}
951+
## Spline (Piecewise) Growth Curve Model {#splineLGCM}
952+
953+
### Model Syntax
954+
955+
#### Abbreviated
956+
957+
```{r}
958+
splineGCM1_syntax <- '
959+
# Intercept and slope
960+
intercept =~ 1*t1 + 1*t2 + 1*t3 + 1*t4
961+
slope =~ 0*t1 + 1*t2 + 2*t3 + 3*t4
962+
knot =~ 0*t1 + 0*t2 + 1*t3 + 1*t4
963+
964+
# Regression paths
965+
intercept ~ x1 + x2
966+
slope ~ x1 + x2
967+
knot ~ x1 + x2
968+
969+
# Spline has no variance
970+
knot ~~ 0*knot
971+
972+
# Spline does not covary with intercept and slope
973+
knot ~~ 0*intercept
974+
knot ~~ 0*slope
975+
976+
# Time-varying covariates
977+
t1 ~ c1
978+
t2 ~ c2
979+
t3 ~ c3
980+
t4 ~ c4
981+
'
982+
```
983+
984+
#### Full
985+
986+
```{r}
987+
splineGCM2_syntax <- '
988+
# Intercept and slope
989+
intercept =~ 1*t1 + 1*t2 + 1*t3 + 1*t4
990+
slope =~ 0*t1 + 1*t2 + 2*t3 + 3*t4
991+
knot =~ 0*t1 + 0*t2 + 1*t3 + 1*t4
992+
993+
# Regression paths
994+
intercept ~ x1 + x2
995+
slope ~ x1 + x2
996+
knot ~ x1 + x2
997+
998+
# Spline has no variance
999+
knot ~~ 0*knot
1000+
1001+
# Spline does not covary with intercept and slope
1002+
knot ~~ 0*intercept
1003+
knot ~~ 0*slope
1004+
1005+
# Time-varying covariates
1006+
t1 ~ c1
1007+
t2 ~ c2
1008+
t3 ~ c3
1009+
t4 ~ c4
1010+
1011+
# Constrain observed intercepts to zero
1012+
t1 ~ 0
1013+
t2 ~ 0
1014+
t3 ~ 0
1015+
t4 ~ 0
1016+
1017+
# Estimate mean of intercept and slope
1018+
intercept ~ 1
1019+
slope ~ 1
1020+
knot ~ 1
1021+
'
1022+
```
1023+
1024+
### Fit the Model
1025+
1026+
#### Abbreviated
1027+
1028+
```{r}
1029+
splineGCM1_fit <- growth(
1030+
splineGCM1_syntax,
1031+
data = Demo.growth,
1032+
missing = "ML",
1033+
estimator = "MLR",
1034+
meanstructure = TRUE,
1035+
int.ov.free = FALSE,
1036+
int.lv.free = TRUE,
1037+
fixed.x = FALSE,
1038+
em.h1.iter.max = 100000)
1039+
```
1040+
1041+
#### Full
1042+
1043+
```{r}
1044+
splineGCM2_fit <- sem(
1045+
splineGCM2_syntax,
1046+
data = Demo.growth,
1047+
missing = "ML",
1048+
estimator = "MLR",
1049+
meanstructure = TRUE,
1050+
fixed.x = FALSE,
1051+
em.h1.iter.max = 100000)
1052+
```
1053+
1054+
### Summary Output
1055+
1056+
#### Abbreviated
1057+
1058+
```{r}
1059+
summary(
1060+
splineGCM1_fit,
1061+
fit.measures = TRUE,
1062+
standardized = TRUE,
1063+
rsquare = TRUE)
1064+
```
1065+
1066+
#### Full
1067+
1068+
```{r}
1069+
summary(
1070+
splineGCM2_fit,
1071+
fit.measures = TRUE,
1072+
standardized = TRUE,
1073+
rsquare = TRUE)
1074+
```
1075+
1076+
### Estimates of Model Fit
1077+
1078+
```{r}
1079+
fitMeasures(
1080+
splineGCM1_fit,
1081+
fit.measures = c(
1082+
"chisq", "df", "pvalue",
1083+
"chisq.scaled", "df.scaled", "pvalue.scaled",
1084+
"chisq.scaling.factor",
1085+
"baseline.chisq","baseline.df","baseline.pvalue",
1086+
"rmsea", "cfi", "tli", "srmr",
1087+
"rmsea.robust", "cfi.robust", "tli.robust"))
1088+
```
1089+
1090+
### Residuals of Observed vs. Model-Implied Correlation Matrix
1091+
1092+
```{r}
1093+
residuals(
1094+
splineGCM1_fit,
1095+
type = "cor")
1096+
```
1097+
1098+
### Modification Indices
1099+
1100+
```{r}
1101+
modificationindices(
1102+
splineGCM1_fit,
1103+
sort. = TRUE)
1104+
```
1105+
1106+
### Internal Consistency Reliability
1107+
1108+
```{r}
1109+
compRelSEM(splineGCM1_fit)
1110+
```
1111+
1112+
### Path Diagram
1113+
1114+
```{r}
1115+
semPlot::semPaths(
1116+
splineGCM1_fit,
1117+
what = "Std.all",
1118+
layout = "tree2",
1119+
edge.label.cex = 1.5)
1120+
1121+
lavaanPlot::lavaanPlot(
1122+
splineGCM1_fit,
1123+
coefs = TRUE,
1124+
#covs = TRUE,
1125+
stand = TRUE)
1126+
1127+
lavaanPlot::lavaanPlot2(
1128+
splineGCM1_fit,
1129+
#stand = TRUE, # currently throws error; uncomment out when fixed: https://github.com/alishinski/lavaanPlot/issues/52
1130+
coef_labels = TRUE)
1131+
```
1132+
1133+
To generate an interactive/modifiable path diagram, you can use the following syntax:
1134+
1135+
```{r, eval = FALSE}
1136+
lavaangui::plot_lavaan(splineGCM1_fit)
1137+
```
1138+
1139+
### Plot Trajectories
1140+
1141+
#### Protoypical Growth Curve
1142+
1143+
Calculated from intercept and slope parameters:
1144+
1145+
```{r}
1146+
splineGCM1_intercept <- coef(splineGCM1_fit)["intercept~1"]
1147+
splineGCM1_slope <- coef(splineGCM1_fit)["slope~1"]
1148+
splineGCM1_knot <- coef(splineGCM1_fit)["knot~1"]
1149+
1150+
timepoints <- 4
1151+
1152+
newData <- data.frame(
1153+
time = 1:4,
1154+
linearLoading = c(0, 1, 2, 3),
1155+
knotLoading = c(0, 0, 1, 1)
1156+
)
1157+
1158+
newData$predictedValue <- NA
1159+
newData$predictedValue <- splineGCM1_intercept + (splineGCM1_slope * newData$linearLoading) + (splineGCM1_knot * newData$knotLoading)
1160+
1161+
ggplot(
1162+
data = newData,
1163+
mapping = aes(
1164+
x = time,
1165+
y = predictedValue)) +
1166+
xlab("Timepoint") +
1167+
ylab("Score") +
1168+
scale_y_continuous(
1169+
limits = c(0, 5)) +
1170+
geom_line()
1171+
```
1172+
1173+
#### Individuals' Growth Curves
1174+
1175+
```{r}
1176+
person_factors <- as.data.frame(predict(splineGCM1_fit))
1177+
person_factors$id <- rownames(person_factors)
1178+
1179+
slope_loadings <- c(0, 1, 2, 3)
1180+
knot_loadings <- c(0, 0, 1, 1)
1181+
1182+
# Compute model-implied values for each person at each time point
1183+
individual_trajectories <- person_factors %>%
1184+
rowwise() %>%
1185+
mutate(
1186+
t1 = intercept + (slope * slope_loadings[1]) + (knot * knot_loadings[1]),
1187+
t2 = intercept + (slope * slope_loadings[2]) + (knot * knot_loadings[2]),
1188+
t3 = intercept + (slope * slope_loadings[3]) + (knot * knot_loadings[3]),
1189+
t4 = intercept + (slope * slope_loadings[4]) + (knot * knot_loadings[4])
1190+
) %>%
1191+
ungroup() %>%
1192+
select(id, t1, t2, t3, t4) %>%
1193+
pivot_longer(
1194+
cols = t1:t4,
1195+
names_to = "timepoint",
1196+
values_to = "value") %>%
1197+
mutate(
1198+
time = as.integer(substr(timepoint, 2, 2)) # extract number from "t1", "t2", etc.
1199+
)
1200+
1201+
ggplot(
1202+
data = individual_trajectories,
1203+
mapping = aes(
1204+
x = time,
1205+
y = value,
1206+
group = factor(id))) +
1207+
xlab("Timepoint") +
1208+
ylab("Score") +
1209+
scale_y_continuous(
1210+
limits = c(-10, 20)) +
1211+
geom_line()
1212+
```
1213+
1214+
#### Individuals' Trajectories Overlaid with Prototypical Trajectory
1215+
1216+
```{r}
1217+
ggplot() +
1218+
geom_line( # individuals' model-implied trajectories
1219+
data = individual_trajectories,
1220+
aes(
1221+
x = time,
1222+
y = value,
1223+
group = id),
1224+
) +
1225+
geom_line( # prototypical trajectory
1226+
data = newData,
1227+
aes(
1228+
x = time,
1229+
y = predictedValue),
1230+
color = "blue",
1231+
linewidth = 2
1232+
)
1233+
```
9651234

9661235
## Saturated Growth Curve Model {#saturatedGCM}
9671236

0 commit comments

Comments
 (0)