Using lavaan

Below you can find the code for installing and loading the required package lavaan (Rosseel 2012), as well as for reading in the example data. 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")
                    )

The RI-CLPM

To specify the RI-CLPM we need four parts.

  • A between part, consisting of the random intercepts. It is specified using the =~ command, RIx =~ 1*x1 + 1*x2 ..., where 1* fixes the factor loading to one.
  • A within part, consisting of within-unit fluctuations. It is also specified using the =~ command, wx1 =~ 1*x1; wx2 =~ 1*x2; ....
  • The lagged regressions between the within-unit components, using wx2 + wy2 ~ wx1 + wy1; wx3 + wy3 ~ wx2 + wy2; ....
  • Relevant covariances in both the between and within part. In the within part the components at wave 1, and their residuals at waves 2 and further are correlated within each wave, using 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;.

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
'

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:

  1. fixed auto-regressive and cross-lagged relations over time, wx2 ~ a*wx1 + b*wy1; ...,
  2. time-invariant (residual) (co-)variances in the within-person part wx2 ~~ cov*wy2; ..., wx2 ~~ vx*wx2; ..., and wy2 ~~ vy*wy2; ..., and
  3. constrained grand means over time, x1 + ... ~ 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)

Extension 1: Including time-invariant predictors and outcomes

Use the tabs below to navigate to the model specification of the RI-CLPM with

  • a time-invariant predictor \(z_{1}\) of the observed variables (\(z_{1}\) \(\rightarrow\) observed),
  • a time-invariant predictor \(z_{1}\) of the random intercepts (\(z_{1}\) \(\rightarrow\) RIs),
  • random intercepts predicting a time-invariant outcome \(z_{2}\) (RIs \(\rightarrow\) \(z_{2}\)), or
  • within components predicting a time-invariant outcome \(z_{2}\) (within \(\rightarrow\) \(z_{2}\)).

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)

Extension 2: Multiple group

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)

Extension 3: Multiple indicators

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:

  • the configural model (Step 1),
  • weak factorial invariance (Step 2),
  • strong factorial invariance (Step 3),
  • strong factorial invariance with factor loadings equal to the within-person factor loadings (Extra), and
  • the latent RI-CLPM (Step 4).

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',
  meanstructure = TRUE
)
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)

\(\bar{\chi}^{2}\)-test

The use of the chi-square difference test is wide-spread in the SEM community to test constraints 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

  1. fitting both the RI-CLPM (RICLPM.fit) and CLPM (CLPM.fit);
  2. extracting the covariance matrix of the random intercepts;
  3. extracting the \(\chi^{2}\) and degrees of freedom of both models; and
  4. performing the \(\bar{\chi}^{2}\)-test using the 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

GORICA

Rather than evaluating a null-hypothesis of no effect, researchers may be interested in evaluating a theory-based, informative hypothesis (Altinisik et al. 2021). Informative hypotheses can reflect a priori theories, and often contain orderings of (standardized) parameters. For example, one may hypothesize that one cross-lagged relationship is higher than another one; often referred to as “causal dominance”. Such a causal dominance hypothesis can be evaluated with an AIC-type criterion called the GORICA (Altinisik et al. 2021; Sukpan and Kuiper 2023). Below you find code for evaluating a causal dominance hypothesis using the GORICA, for both the RI-CLPM with time-independent and time-specific parameters. We make use of the example data provided with these online supplementary materials. For more information, we refer to https://github.com/rebeccakuiper/Tutorials.

In the below R code we use the GORICA to test the informative hypothesis that the cross-lagged effects from \(X\) to \(Y\) are stronger than those from \(Y\) to \(X\) (i.e., \(X\) is causally dominant). To specify the informative hypothesis, we use the labels of parameters as used in the model syntax: b2, c2, b3, c3, etc. Analysis shows that the informative hypothesis has 13.137 times more support than its complement (i.e., that there is no causal dominance, or that \(Y\) is causally dominant).

# R package for testing of linear (in)equality restrictions on parameters in linear models
library(restriktor)

# Informative hypothesis about cross-lagged effects (using parameter labels in model)
H1 <- "abs(b2) < abs(c2); abs(b3) < abs(c3); 
       abs(b4) < abs(c4); abs(b5) < abs(c5)" 

# Fit RI-CLPM
RICLPM_H1 <- '
  # 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 ~ a2*wx1 + b2*wy1 
  wy2 ~ c2*wx1 + d2*wy1
  
  wx3 ~ a3*wx2 + b3*wy2
  wy3 ~ c3*wx2 + d3*wy2
  
  wx4 ~ a4*wx3 + b4*wy3
  wy4 ~ c4*wx3 + d4*wy3
  
  wx5 ~ a5*wx4 + b5*wy4
  wy5 ~ c5*wx4 + d5*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
'

fit_H1 <- lavaan(
  model = RICLPM_H1,
  data = dat, 
  missing = "ML", 
  meanstructure = TRUE, 
  int.ov.free = TRUE
)

summary(fit_H1, standardized = TRUE)

# Compute GORICA values and weights (use `standardized = TRUE`)
GORICA_H1 <- goric(
  fit_H1, 
  standardized = TRUE,
  hypotheses = list(H1ws.l = H1), 
  comparison = "complement", # Test informative hypothesis versus its complement
  type = "gorica"
)

GORICA_H1

The GORICA can also be used to test informative hypotheses when parameters are constrained over time. In the below R code, the lagged effects, residuals variances, and intercepts are restricted to be time-invariant. We again use the GORICA to test the informative hypothesis that the cross-lagged effects from \(X\) to \(Y\) are stronger than those from \(Y\) to \(X\) (i.e., \(X\) is causally dominant). Analysis shows that the informative hypothesis has 785.564 times more support than its complement (i.e., that there is no causal dominance, or that \(Y\) is causally dominant).

# Informative hypothesis about time-invariant cross-lagged effects 
H2 <- "abs(b) < abs(c)" 

# Fit RI-CLPM
RICLPM_H2 <- '
  # 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
'

fit_H2 <- lavaan(
  model = RICLPM_H2,
  data = dat, 
  missing = "ML", 
  meanstructure = TRUE, 
  int.ov.free = TRUE
)

summary(fit_H2, standardized = TRUE)

# Compute GORICA values and weights
GORICA_H2 <- goric(
  fit_H2, 
  hypotheses = list(H2), 
  comparison = "complement", # Test informative hypothesis versus its complement
  type = "gorica"
)

GORICA_H2

References

Altinisik, Yasin, Caspar J. Van Lissa, Herbert Hoijtink, Albertine J. Oldehinkel, and Rebecca M. Kuiper. 2021. “Evaluation of Inequality Constrained Hypotheses Using a Generalization of the AIC.” Psychological Methods 26 (5): 599–621. https://doi.org/10.1037/met0000406.
Kuiper, Rebecca M. 2020. ChiBarSq.DiffTest: Chi-bar-square difference test of the RI-CLPM versus the CLPM and more general.
Rosseel, Yves. 2012. lavaan: An R Package for Structural Equation Modeling.” Journal of Statistical Software 48 (2): 1–36. http://www.jstatsoft.org/v48/i02/.
Stoel, Reinoud D., Francisca Galindo Garre, Conor Dolan, and Godfried van den Wittenboer. 2006. On the likelihood ratio test in structural equation modeling when parameters are subject to boundary constraints.” Psychological Methods 11 (4): 439–55. https://doi.org/10.1037/1082-989X.11.4.439.
Sukpan, Chuenjai, and Rebecca M. Kuiper. 2023. “How to Evaluate Causal Dominance Hypotheses in Lagged Effects Models.” Structural Equation Modeling: A Multidisciplinary Journal 0 (0): 1–16. https://doi.org/10.1080/10705511.2023.2265065.