Chapter 17 – Multivariate Latent Change Score Models

1 Overview

This tutorial walks through the fitting of a bivariate latent change score model in the structural equation modeling framework in R using using the lavaan package.

The example follows Chapter 17 of Grimm, Ram, and Estabrook (2017). Please refer to the chapter for further interpretations and insights about the analyses.

1.0.1 Prelim - Loading libraries used in this script.

library(psych)
library(ggplot2)
library(corrplot) #plotting correlation matrices
library(lavaan)  #for fitting structural equation models
library(semPlot)  #for automatically making diagrams 

1.0.2 Prelim - Reading in Repeated Measures Data

We use data from the NLSY-CYA (Center for Human Resource Research, 2009) that includes repeated measures of children’s math ability (math) and reading comprehension ability (rec) from the second through eighth grade.

Reading in the data

#set filepath
filepath <- "https://raw.githubusercontent.com/LRI-2/Data/main/GrowthModeling/nlsy_math_hyp_wide_R.dat"
#read in the text data file using the url() function
nlsy_data <- read.table(file=url(filepath),na.strings = ".")

#adding names for the columns of the data set
names(nlsy_data) <- c('id', 'female', 'lb_wght', 'anti_k1', 'math2', 'math3', 'math4', 'math5', 'math6', 'math7', 'math8', 'comp2', 'comp3', 'comp4', 'comp5', 'comp6', 'comp7', 'comp8', 'rec2', 'rec3', 'rec4', 'rec5', 'rec6', 'rec7', 'rec8', 'bpi2', 'bpi3', 'bpi4', 'bpi5', 'bpi6', 'bpi7', 'bpi8', 'asl2', 'asl3', 'asl4', 'asl5', 'asl6', 'asl7', 'asl8', 'ax2', 'ax3', 'ax4', 'ax5', 'ax6', 'ax7', 'ax8', 'hds2', 'hds3', 'hds4', 'hds5', 'hds6', 'hds7', 'hds8', 'hyp2', 'hyp3', 'hyp4', 'hyp5', 'hyp6', 'hyp7', 'hyp8', 'dpn2', 'dpn3', 'dpn4', 'dpn5', 'dpn6', 'dpn7', 'dpn8', 'wdn2', 'wdn3', 'wdn4', 'wdn5', 'wdn6', 'wdn7', 'wdn8', 'age2', 'age3', 'age4', 'age5', 'age6', 'age7', 'age8', 'men2', 'men3', 'men4', 'men5', 'men6', 'men7', 'men8', 'spring2', 'spring3', 'spring4', 'spring5', 'spring6', 'spring7', 'spring8', 'anti2', 'anti3', 'anti4', 'anti5', 'anti6', 'anti7', 'anti8')

#reduce data down to the id variable and the math and reading variables of interest
nlsy_data <- nlsy_data[ ,c('id', 'math2', 'math3', 'math4', 'math5', 'math6', 'math7', 'math8',
                           'rec2', 'rec3', 'rec4', 'rec5', 'rec6', 'rec7', 'rec8')]

describe(nlsy_data)
vars n mean sd median trimmed mad min max range skew kurtosis se
id 1 933 532334.89711 3.280208e+05 506602.0 520130.77108 391999.4400 201 1256601 1256400 0.2841659 -0.9078872 1.073892e+04
math2 2 335 32.60896 1.028600e+01 32.0 32.27509 10.3782 12 60 48 0.2686681 -0.4600130 5.619840e-01
math3 3 431 39.88399 1.029949e+01 41.0 39.88406 10.3782 13 67 54 -0.0520929 -0.3259168 4.961091e-01
math4 4 378 46.16931 1.016510e+01 46.0 46.22039 8.8956 18 70 52 -0.0595783 -0.0759665 5.228366e-01
math5 5 372 49.77419 9.471909e+00 48.0 49.76510 8.8956 23 71 48 0.0425474 -0.3381126 4.910956e-01
math6 6 390 52.72308 9.915594e+00 50.5 52.38462 9.6369 24 78 54 0.2510417 -0.3808615 5.020956e-01
math7 7 173 55.35260 1.062727e+01 53.0 55.08633 11.8608 31 81 50 0.2148479 -0.9709622 8.079765e-01
math8 8 142 57.83099 1.153101e+01 56.0 57.42982 12.6021 26 81 55 0.1590545 -0.5222967 9.676607e-01
rec2 9 333 34.68168 1.036303e+01 34.0 33.89888 10.3782 15 79 64 0.8084775 1.0583143 5.678904e-01
rec3 10 431 41.29002 1.146468e+01 40.0 40.80290 11.8608 19 81 62 0.4295972 0.0529361 5.522343e-01
rec4 11 376 47.55585 1.233346e+01 47.0 47.16556 11.8608 21 83 62 0.3227598 -0.0742909 6.360496e-01
rec5 12 370 52.91351 1.303500e+01 52.0 52.86149 13.3434 21 84 63 0.0372454 -0.4837760 6.776573e-01
rec6 13 389 55.99486 1.261831e+01 56.0 55.92971 13.3434 21 82 61 -0.0293076 -0.3703399 6.397735e-01
rec7 14 173 60.56069 1.360801e+01 62.0 61.15827 14.8260 23 84 61 -0.3919243 -0.5323470 1.034598e+00
rec8 15 142 64.37324 1.215246e+01 66.0 65.10526 13.3434 32 84 52 -0.5032979 -0.5582369 1.019812e+00

1.0.3 Prelim - Plotting the Repeated Measures Data

#reshaping wide to wide
data_long <- reshape(data=nlsy_data,
                    varying = c('math2', 'math3', 'math4', 'math5', 'math6', 'math7', 'math8',
                           'rec2', 'rec3', 'rec4', 'rec5', 'rec6', 'rec7', 'rec8'),
                    timevar=c("grade"), 
                    idvar=c("id"),
                    direction="long", sep="")

#sorting for easy viewing
#reorder by id and day
data_long <- data_long[order(data_long$id,data_long$grade), ]

#looking at the long data
head(data_long, 8)
id grade math rec
201.2 201 2 NA NA
201.3 201 3 38 35
201.4 201 4 NA NA
201.5 201 5 55 52
201.6 201 6 NA NA
201.7 201 7 NA NA
201.8 201 8 NA NA
303.2 303 2 26 26
#Plotting intraindividual change MATH
ggplot(data = data_long, aes(x = grade, y = math, group = id)) +
  geom_point(color="blue") + 
  geom_line(color="blue") +
  xlab("Grade") + 
  ylab("PIAT Mathematics") + 
  scale_x_continuous(limits=c(2,8), breaks=seq(2,8,by=1)) +
  scale_y_continuous(limits=c(0,90), breaks=seq(0,90,by=10))
## Warning: Removed 4310 rows containing missing values (geom_point).
## Warning: Removed 2787 row(s) containing missing values (geom_path).