Chapter 21. Latent Growth Curve Models

Principles and Practice of Structural Equation Modeling (5e) by Rex B. Kline

Author

Sungkyun Cho

Published

September 22, 2024

Load libraries
library(tidyverse)
library(lavaan)
library(semTools)

참고 문헌

  • Grimm, K. J., Ram, N., & Estabrook, R. (2017). Growth modeling: Structural equation and multilevel modeling approaches. Guilford Press.
  • Little, T. D. (2024). Longitudinal structural equation modeling (2e). Guilford Press.
  • Newsom, J. (2024). Longitudinal structural equation modeling: A comprehensive introduction (2e). Routledge.
# read in summary statistics
kimspoonLower.cor <- '
1.00
 .35 1.00
 .28  .35 1.00
 .29  .40  .45 1.00
 .00  .07  .14  .04 1.00
 .00 -.09 -.12 -.10  .26 1.00 '
 
# name the variables and convert to full correlation matrix
kimspoon.cor <- lavaan::getCov(kimspoonLower.cor, names = c("R1", "R2",
 "R3", "R4", "abuse", "neglect"))
 
# display the correlations
kimspoon.cor |> print()
          R1    R2    R3    R4 abuse neglect
R1      1.00  0.35  0.28  0.29  0.00    0.00
R2      0.35  1.00  0.35  0.40  0.07   -0.09
R3      0.28  0.35  1.00  0.45  0.14   -0.12
R4      0.29  0.40  0.45  1.00  0.04   -0.10
abuse   0.00  0.07  0.14  0.04  1.00    0.26
neglect 0.00 -0.09 -0.12 -0.10  0.26    1.00
# add the standard deviations and convert to covariances
kimspoon.cov <- lavaan::cor2cov(kimspoon.cor, sds = c(.05,.77,.76,1.15,
 7.75,4.09))

# create mean vector
kimspoon.mean = c(.04,.61,.57,.83,7.17,3.03)
 
# display the covariances and means
kimspoon.cov |> print()
kimspoon.mean |> print()
              R1        R2        R3        R4     abuse   neglect
R1      0.002500  0.013475  0.010640  0.016675  0.000000  0.000000
R2      0.013475  0.592900  0.204820  0.354200  0.417725 -0.283437
R3      0.010640  0.204820  0.577600  0.393300  0.824600 -0.373008
R4      0.016675  0.354200  0.393300  1.322500  0.356500 -0.470350
abuse   0.000000  0.417725  0.824600  0.356500 60.062500  8.241350
neglect 0.000000 -0.283437 -0.373008 -0.470350  8.241350 16.728100
[1] 0.04 0.61 0.57 0.83 7.17 3.03
# for all models, error variance for R1
# is fixed to equal zero

# lavaan function growth() automatically fixes
# intercepts of indicators to zero but specifies 
# latent growth factor means as free parameters 

# the direct tracing of the constant (delta-1) on
# the latent growth factors are means but are labeled
# as "intercepts" in lavaan output

# specify all models

# model 1
# no growth (intercept only)

noGrowth.model <- '
  # specify intercept
  # fix all loadings to 1.0
  Intercept =~ 1*R1 + 1*R2 + 1*R3 + 1*R4
  # fix error variance for r1 to zero
  R1 ~~ 0*R1
'
# model 2
# latent basis growth model
# (curve fitting, level and shape)
# this model is retained

# maccallum-rmsea for model 2
# exact fit test
# power at N = 150
semTools::findRMSEApower(0, .05, 4, 150, .05, 1) |> print()

# minimum N for power at least .90
semTools::findRMSEAsamplesize(0, .05, 4, .90, .05, 1) |> print()

basis.model <- '
  Intercept =~ 1*R1 + 1*R2 + 1*R3 + 1*R4
  # specify shape, first and last loadings fixed
  Shape =~ 0*R1 + R2 + R3 + 1*R4
  R1 ~~ 0*R1
'
[1] 0.136742
[1] 1542
# model 3
# linear growth model

linear.model <- '
  Intercept =~ 1*R1 + 1*R2 + 1*R3 + 1*R4 
  # all loadings fixed to constants
  Linear =~ 0*R1 + 1*R2 + 2*R3 + 3*R4
  R1 ~~ 0*R1
'
# fit model 1 to data
noGrowth <- lavaan::growth(noGrowth.model, sample.cov = kimspoon.cov,
 sample.mean = kimspoon.mean, sample.nobs = 150)

# fit model 2 to data
basis <- lavaan::growth(basis.model, sample.cov = kimspoon.cov,
 sample.mean = kimspoon.mean, sample.nobs = 150)

# fit model 3 to data
linear <- lavaan::growth(linear.model, sample.cov = kimspoon.cov,
 sample.mean = kimspoon.mean, sample.nobs = 150)
# model chi-squares and chi-square difference tests
anova(noGrowth, basis) |> print()

Chi-Squared Difference Test

         Df    AIC    BIC    Chisq Chisq diff   RMSEA Df diff Pr(>Chisq)    
basis     4 610.60 640.71   2.8109                                          
noGrowth  9 865.42 880.47 267.6294     264.82 0.58858       5  < 2.2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
anova(basis, linear) |> print()

Chi-Squared Difference Test

       Df   AIC    BIC   Chisq Chisq diff   RMSEA Df diff Pr(>Chisq)    
basis   4 610.6 640.71  2.8109                                          
linear  6 638.6 662.69 34.8116     32.001 0.31623       2  1.125e-07 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

# model 1 parameter estimates, global fit statistics,
# residuals
# very poor fit
lavaan::summary(noGrowth, fit.measures = TRUE, estimates = FALSE)
lavaan::fitted(noGrowth)
lavaan::residuals(noGrowth, type = "standardized")
lavaan::residuals(noGrowth, type = "cor.bollen")

# model 2 parameter estimates, global fit statistics,
# residuals
# retained model
lavaan::summary(basis, fit.measures = TRUE, rsquare = TRUE)
lavaan::standardizedSolution(basis)

# variance and standard error for Intercept are close to zero,
# so estimates are printed at 5-decimal accuracy, not 3 (default)
print(lavaan::parameterEstimates(basis), nd = 5)
# implied covariances and means for observed variables
lavaan::fitted(basis)
# implied means for latent growth factors & observed variables
lavaan::lavInspect(basis, "mean.lv")
lavaan::lavInspect(basis, "mean.ov")
# residuals
lavaan::residuals(basis, type = "raw")
lavaan::residuals(basis, type = "standardized")
lavaan::residuals(basis, type = "cor.bollen")
# model 3 parameter estimates, global fit statistics,
# residuals
lavaan::summary(linear, fit.measures = TRUE)
lavaan::fitted(linear) 
lavaan::residuals(linear, type = "raw")
lavaan::residuals(linear, type = "standardized")
lavaan::residuals(linear, type = "cor.bollen")