Below you can find the code for installing and loading the required
package lavaan
(Rosseel
2012), as well as for reading in the data for the Random
Intercept Cross-Lagged Panel Model (RI-CLPM) and its 3 extensions. You
can specify the path to the data yourself, or through a menu by using
the file.choose()
-function. You can download the simulated
example datasets here.
# If necessary, install the 'Lavaan' package.
# install.packages('lavaan', dependencies = T)
# Load the required packages.
require(lavaan)
# Load in the data.
## Traditional RI-CLPM
dat <- read.table("/Users/jeroenmulder/Git/RICLPM/data/RICLPM.dat",
col.names = c("x1", "x2", "x3", "x4", "x5", "y1", "y2", "y3", "y4", "y5"))
## Extension 1
datZ <- read.table("C:\\Users\\5879167\\Documents\\GitHub\\RI-CLPM\\data\\RICLPM-Z.dat",
col.names = c("x1", "x2", "x3", "x4", "x5", "y1", "y2", "y3", "y4", "y5", "z2", "z1"))
## Extension 2
datMG <- read.table("/Users/jeroenmulder/Git/RICLPM/data/RICLPM-MG.dat",
col.names = c("x1", "x2", "x3", "x4", "x5", "y1", "y2", "y3", "y4", "y5", "G"))
## Extension 3
datMI <- read.table("/Users/jeroenmulder/GitHub/RI-CLPM/data/RICLPM-MI.dat",
col.names = c("x11", "x12", "x13",
"x21", "x22", "x23",
"x31", "x32", "x33",
"x41", "x42", "x43",
"x51", "x52", "x53",
"y11", "y12", "y13",
"y21", "y22", "y23",
"y31", "y32", "y33",
"y41", "y42", "y43",
"y51", "y52", "y53"))
To specify the RI-CLPM we need four parts.
=~
command,
RIx =~ 1*x1 + 1*x2 ...
, where 1*
fixes the
factor loading to one.=~
command,
wx1 =~ 1*x1; wx2 =~ 1*x2; ...
.wx2 + wy2 ~ wx1 + wy1; wx3 + wy3 ~ wx2 + wy2; ...
.wx1 ~~ wy1; wx2 ~~ wy2;...
. We also need to specify their
(residual) variances here using
wx1 ~~ wx1; wx2 ~~ wx2; ...
. For the between part we have
to specify the variances and covariance of the random intercepts using
RIx ~~ RIy;
.Note that the models below are fitted using the lavaan()
function. Other functions for fitting SEM models using
lavaan
include sem()
and cfa()
.
These functions rely on different defaults than lavaan()
and fitting the below models with these functions can result in model
misspecification and subsequently convergence issues.
The code for specifying the basic RI-CLPM is given below.
RICLPM <- '
# Create between components (random intercepts)
RIx =~ 1*x1 + 1*x2 + 1*x3 + 1*x4 + 1*x5
RIy =~ 1*y1 + 1*y2 + 1*y3 + 1*y4 + 1*y5
# Create within-person centered variables
wx1 =~ 1*x1
wx2 =~ 1*x2
wx3 =~ 1*x3
wx4 =~ 1*x4
wx5 =~ 1*x5
wy1 =~ 1*y1
wy2 =~ 1*y2
wy3 =~ 1*y3
wy4 =~ 1*y4
wy5 =~ 1*y5
# Estimate lagged effects between within-person centered variables
wx2 + wy2 ~ wx1 + wy1
wx3 + wy3 ~ wx2 + wy2
wx4 + wy4 ~ wx3 + wy3
wx5 + wy5 ~ wx4 + wy4
# Estimate covariance between within-person centered variables at first wave
wx1 ~~ wy1 # Covariance
# Estimate covariances between residuals of within-person centered variables
# (i.e., innovations)
wx2 ~~ wy2
wx3 ~~ wy3
wx4 ~~ wy4
wx5 ~~ wy5
# Estimate variance and covariance of random intercepts
RIx ~~ RIx
RIy ~~ RIy
RIx ~~ RIy
# Estimate (residual) variance of within-person centered variables
wx1 ~~ wx1 # Variances
wy1 ~~ wy1
wx2 ~~ wx2 # Residual variances
wy2 ~~ wy2
wx3 ~~ wx3
wy3 ~~ wy3
wx4 ~~ wx4
wy4 ~~ wy4
wx5 ~~ wx5
wy5 ~~ wy5
'
RICLPM.fit <- lavaan(RICLPM,
data = dat,
missing = "ML",
meanstructure = T,
int.ov.free = T
)
summary(RICLPM.fit, standardized = T)
Imposing constraints to the model can be achieved through
pre-multiplication. It means that we have to prepend
the number that we want to fix the parameter to, and an asterisk, to the
parameter in the model specification. For example,
F =~ 0*x1
fixes the factor loading of item x1
to factor F
to 0. Using pre-multiplication we can also
constrain parameters to be the same by giving them the same label. Below
we specify an RI-CLPM with the following constraints:
wx2 ~ a*wx1 + b*wy1; ...
,wx2 ~~ cov*wy2; ...
, wx2 ~~ vx*wx2; ...
, and
wy2 ~~ vy*wy2; ...
, andx1 + ... ~ mx*1
and
y1 + ... ~ my*1
.RICLPM5 <- '
# Create between components (random intercepts)
RIx =~ 1*x1 + 1*x2 + 1*x3 + 1*x4 + 1*x5
RIy =~ 1*y1 + 1*y2 + 1*y3 + 1*y4 + 1*y5
# Create within-person centered variables
wx1 =~ 1*x1
wx2 =~ 1*x2
wx3 =~ 1*x3
wx4 =~ 1*x4
wx5 =~ 1*x5
wy1 =~ 1*y1
wy2 =~ 1*y2
wy3 =~ 1*y3
wy4 =~ 1*y4
wy5 =~ 1*y5
# Estimate lagged effects between within-person centered variables
# (constrained)
wx2 ~ a*wx1 + b*wy1
wy2 ~ c*wx1 + d*wy1
wx3 ~ a*wx2 + b*wy2
wy3 ~ c*wx2 + d*wy2
wx4 ~ a*wx3 + b*wy3
wy4 ~ c*wx3 + d*wy3
wx5 ~ a*wx4 + b*wy4
wy5 ~ c*wx4 + d*wy4
# Estimate covariances between residuals of within-person centered variables
# (i.e., innovations, constrained)
wx2 ~~ cov*wy2
wx3 ~~ cov*wy3
wx4 ~~ cov*wy4
wx5 ~~ cov*wy5
# Estimate covariance between within-person centered variables at first wave
wx1 ~~ wy1 # Covariance
# Estimate variance and covariance of random intercepts
RIx ~~ RIx
RIy ~~ RIy
RIx ~~ RIy
# Estimate (residual) variance of within-person centered variables
# (constrained)
wx1 ~~ wx1 # Variance
wy1 ~~ wy1
wx2 ~~ vx*wx2 # Residual variance
wy2 ~~ vy*wy2
wx3 ~~ vx*wx3
wy3 ~~ vy*wy3
wx4 ~~ vx*wx4
wy4 ~~ vy*wy4
wx5 ~~ vx*wx5
wy5 ~~ vy*wy5
# Constrain grand means over time
x1 + x2 + x3 + x4 + x5 ~ mx*1
y1 + y2 + y3 + y4 + y5 ~ my*1
'
RICLPM5.fit <- lavaan(RICLPM5,
data = dat,
missing = 'ML',
meanstructure = T,
int.ov.free = T
)
summary(RICLPM5.fit, standardized = T)
Rather than imposing constraints on the unstandardized
auto-regressive and cross-lagged relations over time, we can impose
constraints on the standardized lagged effects, as explained in the FAQ.
Compared to the previous model (with constraints over time on the
unstandardized lagged effects), there are multiple changes to the
syntax. First, we freely estimate the factor loadings that link the
observed variables to the within-components (by premultiplying with
NA
), rather than fixing them to 1:
'
# Create within-components with freely estimated factor loadings
wx1 =~ NA*x1
wx2 =~ NA*x2
wx3 =~ NA*x3
wx4 =~ NA*x4
wx5 =~ NA*x5
wy1 =~ NA*y1
wy2 =~ NA*y2
wy3 =~ NA*y3
wy4 =~ NA*y4
wy5 =~ NA*y5
'
Second, we set the variances of within-components at first wave to 1, and label the covariance (now also the correlation) between them:
'
# Set variances of within-components at first wave to 1
wx1 ~~ 1*wx1
wy1 ~~ 1*wy1
# Estimate correlation between within-components at first wave
wx1 ~~ cor1*wy1
'
Third, we give the residual variance and covariances between the within-component each a unique label:
'
# Label residual covariances
wx2 ~~ rcov2*wy2
wx3 ~~ rcov3*wy3
wx4 ~~ rcov4*wy4
wx5 ~~ rcov5*wy5
# Label residual variances
wx2 ~~ rvx2*wx2
wy2 ~~ rvy2*wy2
wx3 ~~ rvx3*wx3
wy3 ~~ rvy3*wy3
wx4 ~~ rvx4*wx4
wy4 ~~ rvy4*wy4
wx5 ~~ rvx5*wx5
wy5 ~~ rvy5*wy5
'
Finally, we compute the correlations between the within-components
themselves at each wave, and then constrain the residual
variances to ensure that the total variance of each within-component
equals 1. This is done with the :=
command:
'
# Compute correlations of within-components at each wave
cor2 := a*c + b*d + a*d*cor1 + b*c*cor1 + rcov2
cor3 := a*c + b*d + a*d*cor2 + b*c*cor2 + rcov3
cor4 := a*c + b*d + a*d*cor3 + b*c*cor3 + rcov4
# Contrain residual variances of within-components such that variance of each
# within-component equals 1
rvx2 == 1 - (a*a + b*b + 2*a*b*cor1)
rvy2 == 1 - (c*c + d*d + 2*c*d*cor1)
rvx3 == 1 - (a*a + b*b + 2*a*b*cor2)
rvy3 == 1 - (c*c + d*d + 2*c*d*cor2)
rvx4 == 1 - (a*a + b*b + 2*a*b*cor3)
rvy4 == 1 - (c*c + d*d + 2*c*d*cor3)
rvx5 == 1 - (a*a + b*b + 2*a*b*cor4)
rvy5 == 1 - (c*c + d*d + 2*c*d*cor4)
'
These steps ultimately result in the below syntax. Comparing the constrainted unstandardized and constrained standardized results shows that these are now equivalent.
RICLPM6 <- '
# Create between components (random intercepts)
RIx =~ 1*x1 + 1*x2 + 1*x3 + 1*x4 + 1*x5
RIy =~ 1*y1 + 1*y2 + 1*y3 + 1*y4 + 1*y5
# Create within-components with freely estimated factor loadings
wx1 =~ NA*x1
wx2 =~ NA*x2
wx3 =~ NA*x3
wx4 =~ NA*x4
wx5 =~ NA*x5
wy1 =~ NA*y1
wy2 =~ NA*y2
wy3 =~ NA*y3
wy4 =~ NA*y4
wy5 =~ NA*y5
# Estimate lagged effects between within-person centered variables
# (constrained)
wx2 ~ a*wx1 + b*wy1
wy2 ~ c*wx1 + d*wy1
wx3 ~ a*wx2 + b*wy2
wy3 ~ c*wx2 + d*wy2
wx4 ~ a*wx3 + b*wy3
wy4 ~ c*wx3 + d*wy3
wx5 ~ a*wx4 + b*wy4
wy5 ~ c*wx4 + d*wy4
# Label residual covariances
wx2 ~~ rcov2*wy2
wx3 ~~ rcov3*wy3
wx4 ~~ rcov4*wy4
wx5 ~~ rcov5*wy5
# Estimate correlation between within-components at first wave
wx1 ~~ cor1*wy1
# Estimate variance and covariance of random intercepts
RIx ~~ RIx
RIy ~~ RIy
RIx ~~ RIy
# Set variances of within-components at first wave to 1
wx1 ~~ 1*wx1
wy1 ~~ 1*wy1
# Label residual variances
wx2 ~~ rvx2*wx2
wy2 ~~ rvy2*wy2
wx3 ~~ rvx3*wx3
wy3 ~~ rvy3*wy3
wx4 ~~ rvx4*wx4
wy4 ~~ rvy4*wy4
wx5 ~~ rvx5*wx5
wy5 ~~ rvy5*wy5
# Constrain grand means over time
x1 + x2 + x3 + x4 + x5 ~ mx*1
y1 + y2 + y3 + y4 + y5 ~ my*1
# Compute correlations of within-components at each wave
cor2 := a*c + b*d + a*d*cor1 + b*c*cor1 + rcov2
cor3 := a*c + b*d + a*d*cor2 + b*c*cor2 + rcov3
cor4 := a*c + b*d + a*d*cor3 + b*c*cor3 + rcov4
# Contrain residual variances of within-components such that variance of each
# within-component equals 1
rvx2 == 1 - (a*a + b*b + 2*a*b*cor1)
rvy2 == 1 - (c*c + d*d + 2*c*d*cor1)
rvx3 == 1 - (a*a + b*b + 2*a*b*cor2)
rvy3 == 1 - (c*c + d*d + 2*c*d*cor2)
rvx4 == 1 - (a*a + b*b + 2*a*b*cor3)
rvy4 == 1 - (c*c + d*d + 2*c*d*cor3)
rvx5 == 1 - (a*a + b*b + 2*a*b*cor4)
rvy5 == 1 - (c*c + d*d + 2*c*d*cor4)
'
RICLPM6.fit <- lavaan(RICLPM6,
data = dat,
missing = 'ML',
meanstructure = T,
int.ov.free = T
)
summary(RICLPM6.fit, standardized = F)
summary(RICLPM6.fit, standardized = T)
Use the tabs below to navigate to the model specification of the RI-CLPM with
Below you can find the code for an RI-CLPM with 5 waves and a time-invariant predictor \(z_{1}\) for the observed variables. The effect of \(z_{1}\) on the observed variables is constrained to be the same across waves.
RICLPM.ext1 <- '
# Create between components (random intercepts)
RIx =~ 1*x1 + 1*x2 + 1*x3 + 1*x4 + 1*x5
RIy =~ 1*y1 + 1*y2 + 1*y3 + 1*y4 + 1*y5
# Create within-person centered variables
wx1 =~ 1*x1
wx2 =~ 1*x2
wx3 =~ 1*x3
wx4 =~ 1*x4
wx5 =~ 1*x5
wy1 =~ 1*y1
wy2 =~ 1*y2
wy3 =~ 1*y3
wy4 =~ 1*y4
wy5 =~ 1*y5
# Regression of observed variables on z1 (constrained)
x1 + x2 + x3 + x4 + x5 ~ s1*z1 # Constrained over time.
y1 + y2 + y3 + y4 + y5 ~ s2*z1 # Constrained over time.
# Estimate lagged effects between within-person centered variables
wx2 + wy2 ~ wx1 + wy1
wx3 + wy3 ~ wx2 + wy2
wx4 + wy4 ~ wx3 + wy3
wx5 + wy5 ~ wx4 + wy4
# Estimate covariance between within-person centered variables at first wave
wx1 ~~ wy1 # Covariance
# Estimate covariances between residuals of within-person centered variables
# (i.e., innovations)
wx2 ~~ wy2
wx3 ~~ wy3
wx4 ~~ wy4
wx5 ~~ wy5
# Estimate variance and covariance of random intercepts
RIx ~~ RIx
RIy ~~ RIy
RIx ~~ RIy
# Estimate (residual) variance of within-person centered variables
wx1 ~~ wx1 # Variances
wy1 ~~ wy1
wx2 ~~ wx2 # Residual variances
wy2 ~~ wy2
wx3 ~~ wx3
wy3 ~~ wy3
wx4 ~~ wx4
wy4 ~~ wy4
wx5 ~~ wx5
wy5 ~~ wy5
'
RICLPM.ext1.fit <- lavaan(RICLPM.ext1,
data = datZ,
missing = 'ML',
meanstructure = T,
int.ov.free = T
)
summary(RICLPM.ext1.fit, standardized = T)
Below you can find the code for an RI-CLPM with 5 waves and a time-invariant predictor \(z_{1}\) for the random intercepts.
RICLPM1.ext1 <- '
# Create between components (random intercepts)
RIx =~ 1*x1 + 1*x2 + 1*x3 + 1*x4 + 1*x5
RIy =~ 1*y1 + 1*y2 + 1*y3 + 1*y4 + 1*y5
# Estimate variance and covariance of random intercepts
RIx ~~ RIx
RIy ~~ RIy
RIx ~~ RIy
# Regression of random intercepts on z1
RIx + RIy ~ z1 # Constrained over time.
# Create within-person centered variables
wx1 =~ 1*x1
wx2 =~ 1*x2
wx3 =~ 1*x3
wx4 =~ 1*x4
wx5 =~ 1*x5
wy1 =~ 1*y1
wy2 =~ 1*y2
wy3 =~ 1*y3
wy4 =~ 1*y4
wy5 =~ 1*y5
# Estimate lagged effects between within-person centered variables
wx2 + wy2 ~ wx1 + wy1
wx3 + wy3 ~ wx2 + wy2
wx4 + wy4 ~ wx3 + wy3
wx5 + wy5 ~ wx4 + wy4
# Estimate covariance between within-person centered variables at first wave
wx1 ~~ wy1 # Covariance
# Estimate covariances between residuals of within-person centered variables
# (i.e., innovations)
wx2 ~~ wy2
wx3 ~~ wy3
wx4 ~~ wy4
wx5 ~~ wy5
# Estimate (residual) variance of within-person centered variables
wx1 ~~ wx1 # Variances
wy1 ~~ wy1
wx2 ~~ wx2 # Residual variances
wy2 ~~ wy2
wx3 ~~ wx3
wy3 ~~ wy3
wx4 ~~ wx4
wy4 ~~ wy4
wx5 ~~ wx5
wy5 ~~ wy5
'
RICLPM1.ext1.fit <- lavaan(RICLPM1.ext1,
data = datZ,
missing = 'ML',
meanstructure = T,
int.ov.free = T
)
summary(RICLPM1.ext1.fit, standardized = T)
Below you can find code for an RI-CLPM with the between components (the random intercepts) predicting a time-invariant outcome. For sake of completeness, the time-invariant \(z_{1}\) is also included as a predictor the for observed variables (constrained).
RICLPM3.ext1 <- '
# Create between components (random intercepts)
RIx =~ 1*x1 + 1*x2 + 1*x3 + 1*x4 + 1*x5
RIy =~ 1*y1 + 1*y2 + 1*y3 + 1*y4 + 1*y5
# Estimate variance and covariance of random intercepts
RIx ~~ RIx
RIy ~~ RIy
RIx ~~ RIy
# Regression of time-invariant outcome z2 on random intercepts
z2 ~ RIx + RIy
z2 ~~ z2 # Residual variance z2
# Create within-person centered variables
wx1 =~ 1*x1
wx2 =~ 1*x2
wx3 =~ 1*x3
wx4 =~ 1*x4
wx5 =~ 1*x5
wy1 =~ 1*y1
wy2 =~ 1*y2
wy3 =~ 1*y3
wy4 =~ 1*y4
wy5 =~ 1*y5
# Regression of observed variables on z1 (constrained)
x1 + x2 + x3 + x4 + x5 ~ s1*z1
y1 + y2 + y3 + y4 + y5 ~ s2*z1
# Estimate lagged effects between within-person centered variables
wx2 + wy2 ~ wx1 + wy1
wx3 + wy3 ~ wx2 + wy2
wx4 + wy4 ~ wx3 + wy3
wx5 + wy5 ~ wx4 + wy4
# Estimate covariance between within-person centered variables at first wave
wx1 ~~ wy1
# Estimate covariances between residuals of within-person centered variables
# (i.e., innovations)
wx2 ~~ wy2
wx3 ~~ wy3
wx4 ~~ wy4
wx5 ~~ wy5
# Estimate (residual) variance of within-person centered variables
wx1 ~~ wx1 # Variances
wy1 ~~ wy1
wx2 ~~ wx2 # Residual variances
wy2 ~~ wy2
wx3 ~~ wx3
wy3 ~~ wy3
wx4 ~~ wx4
wy4 ~~ wy4
wx5 ~~ wx5
wy5 ~~ wy5
'
RICLPM3.ext1.fit <- lavaan(RICLPM3.ext1,
data = datZ,
missing = 'ML',
meanstructure = T,
int.ov.free = T
)
summary(RICLPM3.ext1.fit, standardized = T)
Below you can find code for an RI-CLPM with the within components predicting a time-invariant outcome. For sake of completeness, the time-invariant \(z_{1}\) is also included as a predictor the for observed variables (constrained).
RICLPM4.ext1 <- '
# Create between components (random intercepts)
RIx =~ 1*x1 + 1*x2 + 1*x3 + 1*x4 + 1*x5
RIy =~ 1*y1 + 1*y2 + 1*y3 + 1*y4 + 1*y5
# Estimate variance and covariance of random intercepts
RIx ~~ RIx
RIy ~~ RIy
RIx ~~ RIy
# Regression of time-invariant outcome z2 on within components
z2 ~ wx1 + wx2 + wx3 + wx4 + wx5 + wy1 + wy2 + wy3 + wy4 + wy5
z2 ~~ z2 # Residual variance z2
# Create within-person centered variables
wx1 =~ 1*x1
wx2 =~ 1*x2
wx3 =~ 1*x3
wx4 =~ 1*x4
wx5 =~ 1*x5
wy1 =~ 1*y1
wy2 =~ 1*y2
wy3 =~ 1*y3
wy4 =~ 1*y4
wy5 =~ 1*y5
# Regression of observed variables on z1 (constrained)
x1 + x2 + x3 + x4 + x5 ~ s1*z1
y1 + y2 + y3 + y4 + y5 ~ s2*z1
# Estimate lagged effects between within-person centered variables
wx2 + wy2 ~ wx1 + wy1
wx3 + wy3 ~ wx2 + wy2
wx4 + wy4 ~ wx3 + wy3
wx5 + wy5 ~ wx4 + wy4
# Estimate covariance between within-person centered variables at first wave
wx1 ~~ wy1
# Estimate covariances between residuals of within-person centered variables
# (i.e., innovations)
wx2 ~~ wy2
wx3 ~~ wy3
wx4 ~~ wy4
wx5 ~~ wy5
# Estimate (residual) variance of within-person centered variables
wx1 ~~ wx1 # Variances
wy1 ~~ wy1
wx2 ~~ wx2 # Residual variances
wy2 ~~ wy2
wx3 ~~ wx3
wy3 ~~ wy3
wx4 ~~ wx4
wy4 ~~ wy4
wx5 ~~ wx5
wy5 ~~ wy5
'
RICLPM4.ext1.fit <- lavaan(RICLPM4.ext1,
data = datZ,
missing = 'ML',
meanstructure = T,
int.ov.free = T
)
summary(RICLPM4.ext1.fit, standardized = T)
Use the tabs below to navigate to the model specification of the basic multiple-group model, or the model with constrained lagged parameters (and intercepts across groups).
Below you can find the code for a multiple group RI-CLPM with 5 waves.
RICLPM.ext2 <- '
# Create between components (random intercepts)
RIx =~ 1*x1 + 1*x2 + 1*x3 + 1*x4 + 1*x5
RIy =~ 1*y1 + 1*y2 + 1*y3 + 1*y4 + 1*y5
# Create within-person centered variables
wx1 =~ 1*x1
wx2 =~ 1*x2
wx3 =~ 1*x3
wx4 =~ 1*x4
wx5 =~ 1*x5
wy1 =~ 1*y1
wy2 =~ 1*y2
wy3 =~ 1*y3
wy4 =~ 1*y4
wy5 =~ 1*y5
# Estimate lagged effects between within-person centered variables
wx2 + wy2 ~ wx1 + wy1
wx3 + wy3 ~ wx2 + wy2
wx4 + wy4 ~ wx3 + wy3
wx5 + wy5 ~ wx4 + wy4
# Estimate covariance between within-person centered variables at first wave
wx1 ~~ wy1
# Estimate covariances between residuals of within-person centered variables
# (i.e., innovations)
wx2 ~~ wy2
wx3 ~~ wy3
wx4 ~~ wy4
wx5 ~~ wy5
# Estimate variance and covariance of random intercepts
RIx ~~ RIx
RIy ~~ RIy
RIx ~~ RIy
# Estimate (residual) variance of within-person centered variables
wx1 ~~ wx1 # Variances
wy1 ~~ wy1
wx2 ~~ wx2 # Residual variances
wy2 ~~ wy2
wx3 ~~ wx3
wy3 ~~ wy3
wx4 ~~ wx4
wy4 ~~ wy4
wx5 ~~ wx5
wy5 ~~ wy5
'
RICLPM.ext2.fit <- lavaan(RICLPM.ext2,
data = datMG,
missing = 'ML',
group = "G",
meanstructure = T,
int.ov.free = T
)
summary(RICLPM.ext2.fit)
Below you can find the code for a multiple group RI-CLPM with 4 waves. The lagged-parameters are constrained to be equal over time.
RICLPM1.ext2 <- '
# Create between components (random intercepts)
RIx =~ 1*x1 + 1*x2 + 1*x3 + 1*x4 + 1*x5
RIy =~ 1*y1 + 1*y2 + 1*y3 + 1*y4 + 1*y5
# Create within-person centered variables
wx1 =~ 1*x1
wx2 =~ 1*x2
wx3 =~ 1*x3
wx4 =~ 1*x4
wx5 =~ 1*x5
wy1 =~ 1*y1
wy2 =~ 1*y2
wy3 =~ 1*y3
wy4 =~ 1*y4
wy5 =~ 1*y5
# Estimate lagged effects between within-person centered variables (constrain
# autoregressive effects across groups)
wx2 ~ c(a1, a1)*wx1 + c(b1, b1)*wy1
wy2 ~ c(c1, c1)*wx1 + c(d1, d1)*wy1
wx3 ~ c(a2, a2)*wx2 + c(b2, b2)*wy2
wy3 ~ c(c2, c2)*wx2 + c(d2, d2)*wy2
wx4 ~ c(a3, a3)*wx3 + c(b3, b3)*wy3
wy4 ~ c(c3, c3)*wx3 + c(d3, d3)*wy3
wx5 ~ c(a4, a4)*wx4 + c(b4, b4)*wy4
wy5 ~ c(c4, c4)*wx4 + c(d4, d4)*wy4
# Estimate covariance between within-person centered variables at first wave
wx1 ~~ wy1
# Estimate covariances between residuals of within-person centered variables
# (i.e., innovations)
wx2 ~~ wy2
wx3 ~~ wy3
wx4 ~~ wy4
wx5 ~~ wy5
# Estimate variance and covariance of random intercepts
RIx ~~ RIx
RIy ~~ RIy
RIx ~~ RIy
# Estimate (residual) variance of the within-person centered variables
wx1 ~~ wx1 # Variances
wy1 ~~ wy1
wx2 ~~ wx2 # Residual variances
wy2 ~~ wy2
wx3 ~~ wx3
wy3 ~~ wy3
wx4 ~~ wx4
wy4 ~~ wy4
wx5 ~~ wx5
wy5 ~~ wy5
'
RICLPM1.ext2.fit <- lavaan(RICLPM1.ext2,
data = datMG,
missing = 'ML',
group = "G",
meanstructure = T,
int.ov.free = T
)
summary(RICLPM1.ext2.fit)
Use the tabs below to navigate to the model specification of a multiple indicator RI-CLPM, 5 waves and 3 indicators for each variable at each wave. The five steps correspond to:
You can download the simulated example dataset MIRICLPM.dat from https://jeroendmulder.github.io/RI-CLPM/data/MIRICLPM.dat.
When we have three indicators \(X\),
measured at five waves, we specify three random intercepts to capture
the trait-like part of each indicator, that is,
RIX1 =~ 1*x11 1*x21 ...
,
RIX2 =~ 1*x121 1*x22@1 ...
, and
RIX3 =~ 1*x13 1*x23 ...
. In addition, we specify five
within-unit components that capture the state-like part at each wave,
using WFX1 =~ x11 x12 x13; WFX2 =~ x21 x22 x23; ...
.
At the latent within-unit level, we specify the dynamic model in
Mplus using WFX2 ~ WFY1 + WFX1; WFX3 ~ WFY2 + WFX2; ...
. In
addition, we allow the within-person factors at the first wave, and
their residuals at subsequent waves to be correlated within each wave,
WFX1 ~~ WFY1; WFX2 ~~ WFY2; ...
. The six random intercepts
are allowed to be freely correlated with each other, either through
inclusion of RIX1 + RIX2 + ... ~~ RIX1 + RIX2 + ...
in the
model syntax, or by default when using the cfa()
function
as we have done below.
RICLPM1.ext3 <- '
################
# BETWEEN PART #
################
# Create between factors (random intercepts) for each indicator separately
RIX1 =~ 1*x11 + 1*x21 + 1*x31 + 1*x41 + 1*x51
RIX2 =~ 1*x12 + 1*x22 + 1*x32 + 1*x42 + 1*x52
RIX3 =~ 1*x13 + 1*x23 + 1*x33 + 1*x43 + 1*x53
RIY1 =~ 1*y11 + 1*y21 + 1*y31 + 1*y41 + 1*y51
RIY2 =~ 1*y12 + 1*y22 + 1*y32 + 1*y42 + 1*y52
RIY3 =~ 1*y13 + 1*y23 + 1*y33 + 1*y43 + 1*y53
##################################
# WITHIN PART: MEASUREMENT MODEL #
##################################
# Factor models for X at 5 waves
WFX1 =~ x11 + x12 + x13
WFX2 =~ x21 + x22 + x23
WFX3 =~ x31 + x32 + x33
WFX4 =~ x41 + x42 + x43
WFX5 =~ x51 + x52 + x53
# Factor models for Y at 5 waves
WFY1 =~ y11 + y12 + y13
WFY2 =~ y21 + y22 + y23
WFY3 =~ y31 + y32 + y33
WFY4 =~ y41 + y42 + y43
WFY5 =~ y51 + y52 + y53
#########################
# WITHIN PART: DYNAMICS #
#########################
# Specify lagged effects between within-person centered latent variables
WFX2 + WFY2 ~ WFX1 + WFY1
WFX3 + WFY3 ~ WFX2 + WFY2
WFX4 + WFY4 ~ WFX3 + WFY3
WFX5 + WFY5 ~ WFX4 + WFY4
# Estimate correlations within same wave
WFX1 ~~ WFY1
WFX2 ~~ WFY2
WFX3 ~~ WFY3
WFX4 ~~ WFY4
WFX5 ~~ WFY5
##########################
# ADDITIONAL CONSTRAINTS #
##########################
# Constrain covariance of between factors and exogenous within factors to 0
RIX1 + RIX2 + RIX3 + RIY1 + RIY2 + RIY3 ~~ 0*WFY1 + 0*WFX1
'
RICLPM1.ext3.fit <- cfa(RICLPM1.ext3,
data = datMI,
missing = 'ML'
)
summary(RICLPM1.ext3.fit, standardized = T)
In second step, we constrain the factor loadings to be invariant over
time using the labels a*
, b*
, etc.
RICLPM2.ext3 <- '
################
# BETWEEN PART #
################
# Create between factors (random intercepts) for each indicator separately
RIX1 =~ 1*x11 + 1*x21 + 1*x31 + 1*x41 + 1*x51
RIX2 =~ 1*x12 + 1*x22 + 1*x32 + 1*x42 + 1*x52
RIX3 =~ 1*x13 + 1*x23 + 1*x33 + 1*x43 + 1*x53
RIY1 =~ 1*y11 + 1*y21 + 1*y31 + 1*y41 + 1*y51
RIY2 =~ 1*y12 + 1*y22 + 1*y32 + 1*y42 + 1*y52
RIY3 =~ 1*y13 + 1*y23 + 1*y33 + 1*y43 + 1*y53
##################################
# WITHIN PART: MEASUREMENT MODEL #
##################################
# Factor models for X at 5 waves (constrained)
WFX1 =~ a*x11 + b*x12 + c*x13
WFX2 =~ a*x21 + b*x22 + c*x23
WFX3 =~ a*x31 + b*x32 + c*x33
WFX4 =~ a*x41 + b*x42 + c*x43
WFX5 =~ a*x51 + b*x52 + c*x53
# Factor models for Y at 5 waves (constrained)
WFY1 =~ d*y11 + e*y12 + f*y13
WFY2 =~ d*y21 + e*y22 + f*y23
WFY3 =~ d*y31 + e*y32 + f*y33
WFY4 =~ d*y41 + e*y42 + f*y43
WFY5 =~ d*y51 + e*y52 + f*y53
#########################
# WITHIN PART: DYNAMICS #
#########################
# Specify lagged effects between within-person centered latent variables
WFX2 + WFY2 ~ WFX1 + WFY1
WFX3 + WFY3 ~ WFX2 + WFY2
WFX4 + WFY4 ~ WFX3 + WFY3
WFX5 + WFY5 ~ WFX4 + WFY4
# Estimate correlations within same wave
WFX1 ~~ WFY1
WFX2 ~~ WFY2
WFX3 ~~ WFY3
WFX4 ~~ WFY4
WFX5 ~~ WFY5
##########################
# ADDITIONAL CONSTRAINTS #
##########################
# Constrain covariance of between factors and exogenous within factors to 0
RIX1 + RIX2 + RIX3 + RIY1 + RIY2 + RIY3 ~~ 0*WFY1 + 0*WFX1
'
RICLPM2.ext3.fit <- cfa(RICLPM2.ext3,
data = datMI,
missing = 'ML'
)
summary(RICLPM2.ext3.fit, standardized = T)
Multiple indicator RI-CLPM 4 waves with 3 indicators for each variable at each wave (24 observed variables). Fitting a model with constraints to ensure strong factorial invariance, with a random intercept for each indicator separately.
RICLPM3.ext3 <- '
################
# BETWEEN PART #
################
# Create between factors (random intercepts) for each indicator separately
RIX1 =~ 1*x11 + 1*x21 + 1*x31 + 1*x41 + 1*x51
RIX2 =~ 1*x12 + 1*x22 + 1*x32 + 1*x42 + 1*x52
RIX3 =~ 1*x13 + 1*x23 + 1*x33 + 1*x43 + 1*x53
RIY1 =~ 1*y11 + 1*y21 + 1*y31 + 1*y41 + 1*y51
RIY2 =~ 1*y12 + 1*y22 + 1*y32 + 1*y42 + 1*y52
RIY3 =~ 1*y13 + 1*y23 + 1*y33 + 1*y43 + 1*y53
##################################
# WITHIN PART: MEASUREMENT MODEL #
##################################
# Factor models for X at 5 waves (constrained)
WFX1 =~ a*x11 + b*x12 + c*x13
WFX2 =~ a*x21 + b*x22 + c*x23
WFX3 =~ a*x31 + b*x32 + c*x33
WFX4 =~ a*x41 + b*x42 + c*x43
WFX5 =~ a*x51 + b*x52 + c*x53
# Factor models for Y at 5 waves (constrained)
WFY1 =~ d*y11 + e*y12 + f*y13
WFY2 =~ d*y21 + e*y22 + f*y23
WFY3 =~ d*y31 + e*y32 + f*y33
WFY4 =~ d*y41 + e*y42 + f*y43
WFY5 =~ d*y51 + e*y52 + f*y53
# Constrained intercepts over time (necessary for strong factorial
# invariance; without these contraints we have week factorial invariance)
x11 + x21 + x31 + x41 + x51 ~ g*1
x12 + x22 + x32 + x42 + x52 ~ h*1
x13 + x23 + x33 + x43 + x53 ~ i*1
y11 + y21 + y31 + y41 + y51 ~ j*1
y12 + y22 + y32 + y42 + y52 ~ k*1
y13 + y23 + y33 + y43 + y53 ~ l*1
# Free latent means from t = 2 onward (only do this in combination with
# constraints on intercepts; without these, this would not be specified)
WFX2 + WFX3 + WFX4 + WFX5 + WFY2 + WFY3 + WFY4 + WFY5 ~ 1
#########################
# WITHIN PART: DYNAMICS #
#########################
# Specify lagged effects between within-person centered latent variables
WFX2 + WFY2 ~ WFX1 + WFY1
WFX3 + WFY3 ~ WFX2 + WFY2
WFX4 + WFY4 ~ WFX3 + WFY3
WFX5 + WFY5 ~ WFX4 + WFY4
# Estimate correlations within same wave
WFX1 ~~ WFY1
WFX2 ~~ WFY2
WFX3 ~~ WFY3
WFX4 ~~ WFY4
WFX5 ~~ WFY5
##########################
# ADDITIONAL CONSTRAINTS #
##########################
# Constrain covariance of between factors and exogenous within factors to 0
RIX1 + RIX2 + RIX3 + RIY1 + RIY2 + RIY3 ~~ 0*WFY1 + 0*WFX1
'
RICLPM3.ext3.fit <- cfa(RICLPM3.ext3,
data = datMI,
missing = 'ML'
)
summary(RICLPM3.ext3.fit, standardized = T)
Multiple indicator RI-CLPM, 5 waves with 3 indicators for each variable at each wave (30 observed variables). Fitting a model With constraints to ensure strong factorial invariance, with a random intercept for each indicator separately, for which a factor model is specified, with factor loadings equal to the within- person factor loadings.
RICLPM4.ext3 <- '
################
# BETWEEN PART #
################
# Create between factors (random intercepts) for each indicator separately
RIX1 =~ 1*x11 + 1*x21 + 1*x31 + 1*x41 + 1*x51
RIX2 =~ 1*x12 + 1*x22 + 1*x32 + 1*x42 + 1*x52
RIX3 =~ 1*x13 + 1*x23 + 1*x33 + 1*x43 + 1*x53
RIY1 =~ 1*y11 + 1*y21 + 1*y31 + 1*y41 + 1*y51
RIY2 =~ 1*y12 + 1*y22 + 1*y32 + 1*y42 + 1*y52
RIY3 =~ 1*y13 + 1*y23 + 1*y33 + 1*y43 + 1*y53
# Create a single random intercept for all X variables, and another for all
# Y variables and constrain the factor loadings to be identical to
# within-person factor loadings
RIX =~ a*RIX1 + b*RIX2 + c*RIX3
RIY =~ d*RIY1 + e*RIY2 + f*RIY3
# Add (co)variance between two higher-order random intercepts
RIX ~~ RIY
RIX ~~ RIX
RIY ~~ RIY
# Constrain measurement error variances of the second order factor model to 0
RIX1 ~~ 0*RIX1
RIX2 ~~ 0*RIX2
RIX3 ~~ 0*RIX3
RIY1 ~~ 0*RIY1
RIY2 ~~ 0*RIY2
RIY3 ~~ 0*RIY3
##################################
# WITHIN PART: MEASUREMENT MODEL #
##################################
# Factor models for X at 5 waves (constrained)
WFX1 =~ a*x11 + b*x12 + c*x13
WFX2 =~ a*x21 + b*x22 + c*x23
WFX3 =~ a*x31 + b*x32 + c*x33
WFX4 =~ a*x41 + b*x42 + c*x43
WFX5 =~ a*x51 + b*x52 + c*x53
# Factor models for Y at 5 waves (constrained)
WFY1 =~ d*y11 + e*y12 + f*y13
WFY2 =~ d*y21 + e*y22 + f*y23
WFY3 =~ d*y31 + e*y32 + f*y33
WFY4 =~ d*y41 + e*y42 + f*y43
WFY5 =~ d*y51 + e*y52 + f*y53
# Constrained intercepts over time (necessary for strong factorial invariance;
# without these contraints we have week factorial invariance)
x11 + x21 + x31 + x41 + x51 ~ g*1
x12 + x22 + x32 + x42 + x52 ~ h*1
x13 + x23 + x33 + x43 + x53 ~ i*1
y11 + y21 + y31 + y41 + y51 ~ j*1
y12 + y22 + y32 + y42 + y52 ~ k*1
y13 + y23 + y33 + y43 + y53 ~ l*1
# Free latent means from t = 2 onward (only do this in combination with
# constraints on intercepts; without these, this would not be identified)
WFX2 + WFX3 + WFX4 + WFX5 + WFY2 + WFY3 + WFY4 + WFY5 ~ 1
#########################
# WITHIN PART: DYNAMICS #
#########################
# Specify lagged effects between within-person centered latent variables
WFX2 + WFY2 ~ WFX1 + WFY1
WFX3 + WFY3 ~ WFX2 + WFY2
WFX4 + WFY4 ~ WFX3 + WFY3
WFX5 + WFY5 ~ WFX4 + WFY4
# Estimate correlations within same wave
WFX1 ~~ WFY1
WFX2 ~~ WFY2
WFX3 ~~ WFY3
WFX4 ~~ WFY4
WFX5 ~~ WFY5
##########################
# ADDITIONAL CONSTRAINTS #
##########################
# Constrain covariance of between factors and exogenous within factors to 0
RIX + RIY + RIX1 + RIX2 + RIX3 + RIY1 + RIY2 + RIY3 ~~ 0*WFY1 + 0*WFX1
'
RICLPM4.ext3.fit <- cfa(RICLPM4.ext3,
data = datMI,
missing = 'ML'
)
summary(RICLPM4.ext3.fit, standardized = T)
Multiple indicator RI-CLPM, 5 waves with 3 indicators for each variable at each wave (30 observed variables). Fitting a model with constraints to ensure strong factorial invariance, with the RI-CLPM at the latent level.
RICLPM5.ext3 <- '
#####################
# MEASUREMENT MODEL #
#####################
# Factor models for X at 5 waves (constrained)
FX1 =~ a*x11 + b*x12 + c*x13
FX2 =~ a*x21 + b*x22 + c*x23
FX3 =~ a*x31 + b*x32 + c*x33
FX4 =~ a*x41 + b*x42 + c*x43
FX5 =~ a*x51 + b*x52 + c*x53
# Factor models for Y at 5 waves (constrained)
FY1 =~ d*y11 + e*y12 + f*y13
FY2 =~ d*y21 + e*y22 + f*y23
FY3 =~ d*y31 + e*y32 + f*y33
FY4 =~ d*y41 + e*y42 + f*y43
FY5 =~ d*y51 + e*y52 + f*y53
# Constrained intercepts over time (this is necessary for strong factorial
# invariance; without these contraints we have week factorial invariance)
x11 + x21 + x31 + x41 + x51 ~ g*1
x12 + x22 + x32 + x42 + x52 ~ h*1
x13 + x23 + x33 + x43 + x53 ~ i*1
y11 + y21 + y31 + y41 + y51 ~ j*1
y12 + y22 + y32 + y42 + y52 ~ k*1
y13 + y23 + y33 + y43 + y53 ~ l*1
# Free latent means from t = 2 onward (only do this in combination with
# constraints on intercepts; without these, this would not be identified)
FX2 + FX3 + FX4 + FX5 + FY2 + FY3 + FY4 + FY5 ~ 1
################
# BETWEEN PART #
################
# Create between factors (random intercepts)
RIX =~ 1*FX1 + 1*FX2 + 1*FX3 + 1*FX4 + 1*FX5
RIY =~ 1*FY1 + 1*FY2 + 1*FY3 + 1*FY4 + 1*FY5
# Set residual variances of all FX and FY variables to 0
FX1 ~~ 0*FX1
FX2 ~~ 0*FX2
FX3 ~~ 0*FX3
FX4 ~~ 0*FX4
FX5 ~~ 0*FX5
FY1 ~~ 0*FY1
FY2 ~~ 0*FY2
FY3 ~~ 0*FY3
FY4 ~~ 0*FY4
FY5 ~~ 0*FY5
###############
# WITHIN PART #
###############
# Create within-components
WFX1 =~ 1*FX1
WFX2 =~ 1*FX2
WFX3 =~ 1*FX3
WFX4 =~ 1*FX4
WFX5 =~ 1*FX5
WFY1 =~ 1*FY1
WFY2 =~ 1*FY2
WFY3 =~ 1*FY3
WFY4 =~ 1*FY4
WFY5 =~ 1*FY5
# Specify lagged effects between within-person centered latent variables
WFX2 + WFY2 ~ WFX1 + WFY1
WFX3 + WFY3 ~ WFX2 + WFY2
WFX4 + WFY4 ~ WFX3 + WFY3
WFX5 + WFY5 ~ WFX4 + WFY4
# Estimate correlations within same wave
WFX2 ~~ WFY2
WFX3 ~~ WFY3
WFX4 ~~ WFY4
WFX5 ~~ WFY5
##########################
# ADDITIONAL CONSTRAINTS #
##########################
# Set correlations between between-factors (random intercepts) and within-
# factors at wave 1 at 0
RIX + RIY ~~ 0*WFX1 + 0*WFY1
'
RICLPM5.ext3.fit <- cfa(RICLPM5.ext3,
data = datMI,
missing = 'ML'
)
summary(RICLPM5.ext3.fit, standardized = T)
The use of the chi-square difference test is wide-spread in the SEM community to test constaints on parameters. However, when constraints are placed on the bound of the parameter space, we should use the chi-bar-square test (\(\bar{\chi}^{2}\)-test) (Stoel et al. 2006). For example, if we constrain the variances of all random intercepts (and their covariance) in the RI-CLPM to zero, we obtain a model that is nested under the RI-CLPM, and that is statistically equivalent to the traditional cross-lagged panel model (CLPM). Below you can find R code for performing the chi-bar-square test (code by Rebecca M. Kuiper) for comparing these two models. It involves
RICLPM.fit
) and CLPM
(CLPM.fit
);ChiBarSq.DiffTest
package (Kuiper
2020).# Install and load required packages
## library(devtools)
## install_github("rebeccakuiper/ChiBarSq.DiffTest")
library(ChiBarSq.DiffTest)
# Step 1: Fit RI-CLPM (RICLPM.fit) and CLPM (CLPM.fit)
# Step 2: Find indices needed for covariance matrix of random intercepts
vcov(RICLPM.fit) # Full covariance matrix
indices <- c(22, 23) # The 22nd and 23rd indices regard random intercepts
q <- length(indices) # Number of random intercepts
S <- vcov(RICLPM.fit)[indices, indices] # Extract covariance matrix of random intercepts
# Step 3: Extract Chi-square and degrees of freedom
Chi2_clpm <- summary(CLPM.fit, fit.measures = TRUE)[1]$FIT[c("chisq")]
Chi2_riclpm <- summary(RICLPM.fit, fit.measures = TRUE)[1]$FIT[c("chisq")]
df_clpm <- summary(CLPM.fit, fit.measures = TRUE)[1]$FIT[c("df")]
df_riclpm <- summary(RICLPM.fit, fit.measures = TRUE)[1]$FIT[c("df")]
# Step 4: Perform Chi-bar-square test (and obtain Chi-bar-square weights)
ChiBar2DiffTest <- ChiBarSq.DiffTest(q, S, Chi2_clpm, Chi2_riclpm, df_clpm, df_riclpm)
ChiBar2DiffTest
ChiBar2DiffTest$p_value