@@ -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"]
863863quadraticGCM1_linear <- coef(quadraticGCM1_fit)["linear~1"]
864864quadraticGCM1_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-
879866timepoints <- 4
880867
881868newData <- 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