Example lavaan models

Rémi Thériault

August 24, 2022

This article attempts to reproduce several different possible lavaan models. We start with the source itself: The lavaan project at https://lavaan.ugent.be. Let’s start by loading both packages.

library(lavaan)
library(lavaanExtra)

Example 1 (Model syntax 1):

Source: https://lavaan.ugent.be/tutorial/syntax1.html

lavaan:

myModel <- " # regressions
             y1 + y2 ~ f1 + f2 + x1 + x2
                  f1 ~ f2 + f3
                  f2 ~ f3 + x1 + x2

             # latent variable definitions
               f1 =~ y1 + y2 + y3
               f2 =~ y4 + y5 + y6
               f3 =~ y7 + y8 + y9 + y10

             # variances and covariances
               y1 ~~ y1
               y1 ~~ y2
               f1 ~~ f2

             # intercepts
               y1 ~ 1
               f1 ~ 1
           "

lavaanExtra:

reg <- list(
  y1 = c("f1", "f2", "x1", "x2"),
  y2 = c("f1", "f2", "x1", "x2"),
  f1 = c("f2", "f3"),
  f2 = c("f3", "x1", "x2")
)
lat <- list(
  f1 = paste0("y", 1:3),
  f2 = paste0("y", 4:6),
  f3 = paste0("y", 7:10)
)
cov <- list(
  y1 = "y1",
  y1 = "y2",
  f1 = "f2"
)
int <- c("y1", "f1")
myModel <- write_lavaan(
  regression = reg,
  latent = lat,
  covariance = cov,
  intercept = int
)
cat(myModel)
## ##################################################
## # [-----Latent variables (measurement model)-----]
## 
## f1 =~ y1 + y2 + y3
## f2 =~ y4 + y5 + y6
## f3 =~ y7 + y8 + y9 + y10
## 
## ##################################################
## # [---------Regressions (Direct effects)---------]
## 
## y1 ~ f1 + f2 + x1 + x2
## y2 ~ f1 + f2 + x1 + x2
## f1 ~ f2 + f3
## f2 ~ f3 + x1 + x2
## 
## ##################################################
## # [------------------Covariances-----------------]
## 
## y1 ~~ y1
## y1 ~~ y2
## f1 ~~ f2
## 
## ##################################################
## # [------------------Intercepts------------------]
## 
## y1 ~ 1
## f1 ~ 1

Example 2 (A CFA example):

Source: https://lavaan.ugent.be/tutorial/cfa.html

lavaan:

HS.model <- " visual  =~ x1 + x2 + x3
              textual =~ x4 + x5 + x6
              speed   =~ x7 + x8 + x9 "

lavaanExtra:

lat <- list(
  visual = paste0("x", 1:3),
  textual = paste0("x", 4:6),
  speed = paste0("x", 7:9)
)
myModel <- write_lavaan(latent = lat)
cat(myModel)
## ##################################################
## # [-----Latent variables (measurement model)-----]
## 
## visual =~ x1 + x2 + x3
## textual =~ x4 + x5 + x6
## speed =~ x7 + x8 + x9

Example 3 (A SEM example):

Source: https://lavaan.ugent.be/tutorial/sem.html

lavaan:

model <- "
  # measurement model
    ind60 =~ x1 + x2 + x3
    dem60 =~ y1 + y2 + y3 + y4
    dem65 =~ y5 + y6 + y7 + y8
  # regressions
    dem60 ~ ind60
    dem65 ~ ind60 + dem60
  # residual correlations
    y1 ~~ y5
    y2 ~~ y4 + y6
    y3 ~~ y7
    y4 ~~ y8
    y6 ~~ y8
"

lavaanExtra:

lat <- list(
  ind60 = paste0("x", 1:3),
  dem60 = paste0("y", 1:4),
  dem65 = paste0("y", 5:8)
)
reg <- list(
  dem60 = "ind60",
  dem65 = c("ind60", "dem60")
)
cov <- list(
  y1 = "y5",
  y2 = c("y4", "y6"),
  y3 = "y7",
  y4 = "y8",
  y6 = "y8"
)
model <- write_lavaan(
  latent = lat,
  regression = reg,
  covariance = cov
)
cat(model)
## ##################################################
## # [-----Latent variables (measurement model)-----]
## 
## ind60 =~ x1 + x2 + x3
## dem60 =~ y1 + y2 + y3 + y4
## dem65 =~ y5 + y6 + y7 + y8
## 
## ##################################################
## # [---------Regressions (Direct effects)---------]
## 
## dem60 ~ ind60
## dem65 ~ ind60 + dem60
## 
## ##################################################
## # [------------------Covariances-----------------]
## 
## y1 ~~ y5
## y2 ~~ y4 + y6
## y3 ~~ y7
## y4 ~~ y8
## y6 ~~ y8

Example 4 (Model syntax 2):

Source: https://lavaan.ugent.be/tutorial/syntax2.html

Example 4.1

lavaan:

model <- "
# three-factor model
  visual =~ x1 + x2 + x3
  textual =~ x4 + x5 + x6
  speed   =~ NA*x7 + x8 + x9
# orthogonal factors
  visual ~~ 0*speed
  textual ~~ 0*speed
# fix variance of speed factor
  speed ~~ 1*speed
"

lavaanExtra:

lat <- list(
  visual = paste0("x", 1:3),
  textual = paste0("x", 4:6),
  speed = c("NA*x7", "x8", "x9")
)
cov <- list(
  visual = "0*speed",
  textual = "0*speed",
  speed = "1*speed"
)
model <- write_lavaan(latent = lat, covariance = cov)
cat(model)
## ##################################################
## # [-----Latent variables (measurement model)-----]
## 
## visual =~ x1 + x2 + x3
## textual =~ x4 + x5 + x6
## speed =~ NA*x7 + x8 + x9
## 
## ##################################################
## # [------------------Covariances-----------------]
## 
## visual ~~ 0*speed
## textual ~~ 0*speed
## speed ~~ 1*speed

Example 4.2

lavaan:

model <- "
visual  =~ x1 + start(0.8)*x2 + start(1.2)*x3
textual =~ x4 + start(0.5)*x5 + start(1.0)*x6
speed   =~ x7 + start(0.7)*x8 + start(1.8)*x9
"

lavaanExtra:

lat <- list(
  visual = c("x1", "start(0.8)*x2", "start(1.2)*x3"),
  textual = c("x4", "start(0.5)*x5", "start(1.0)*x6"),
  speed = c("x7", "start(0.7)*x8", "start(1.8)*x9")
)
model <- write_lavaan(latent = lat)
cat(model)
## ##################################################
## # [-----Latent variables (measurement model)-----]
## 
## visual =~ x1 + start(0.8)*x2 + start(1.2)*x3
## textual =~ x4 + start(0.5)*x5 + start(1.0)*x6
## speed =~ x7 + start(0.7)*x8 + start(1.8)*x9

Example 4.3

lavaan:

model <- "
f =~ y1 + y2 + myLabel*y3 + start(0.5)*y3 + y4
"

lavaanExtra:

lat <- list(f = c("y1", "y2", "myLabel*y3", "start(0.5)*y3", "y4"))
model <- write_lavaan(latent = lat)
cat(model)
## ##################################################
## # [-----Latent variables (measurement model)-----]
## 
## f =~ y1 + y2 + myLabel*y3 + start(0.5)*y3 + y4

Example 4.4

lavaan:

model <- "
visual  =~ x1 + v2*x2 + v2*x3
textual =~ x4 + x5 + x6
speed   =~ x7 + x8 + x9
"

lavaanExtra:

lat <- list(
  visual = c("x1", "v2*x2", "v2*x3"),
  textual = paste0("x", 4:6),
  speed = paste0("x", 7:9)
)
model <- write_lavaan(latent = lat)
cat(model)
## ##################################################
## # [-----Latent variables (measurement model)-----]
## 
## visual =~ x1 + v2*x2 + v2*x3
## textual =~ x4 + x5 + x6
## speed =~ x7 + x8 + x9

Example 4.5

lavaan:

model <- '
visual  =~ x1 + x2 + equal("visual=~x2")*x3
textual =~ x4 + x5 + x6
speed   =~ x7 + x8 + x9
'
sem(model, data = HolzingerSwineford1939)
## lavaan 0.6-18 ended normally after 36 iterations
## 
##   Estimator                                         ML
##   Optimization method                           NLMINB
##   Number of model parameters                        21
##   Number of equality constraints                     1
## 
##   Number of observations                           301
## 
## Model Test User Model:
##                                                       
##   Test statistic                                87.971
##   Degrees of freedom                                25
##   P-value (Chi-square)                           0.000

lavaanExtra:

lat <- list(
  visual = c("x1", "x2", "equal('visual=~x2')*x3"),
  textual = paste0("x", 4:6),
  speed = paste0("x", 7:9)
)
model <- write_lavaan(latent = lat)
cat(model)
## ##################################################
## # [-----Latent variables (measurement model)-----]
## 
## visual =~ x1 + x2 + equal('visual=~x2')*x3
## textual =~ x4 + x5 + x6
## speed =~ x7 + x8 + x9
sem(model, data = HolzingerSwineford1939)
## lavaan 0.6-18 ended normally after 36 iterations
## 
##   Estimator                                         ML
##   Optimization method                           NLMINB
##   Number of model parameters                        21
##   Number of equality constraints                     1
## 
##   Number of observations                           301
## 
## Model Test User Model:
##                                                       
##   Test statistic                                87.971
##   Degrees of freedom                                25
##   P-value (Chi-square)                           0.000

Example 4.6

lavaan:

model.constr <- " # model with labeled parameters
                    y ~ b1*x1 + b2*x2 + b3*x3
                  # constraints
                    b1 == (b2 + b3)^2
                    b1 > exp(b2 + b3) "

lavaanExtra:

reg <- list(y = c("b1*x1", "b2*x2", "b3*x3"))
cstr1 <- list(b1 = "(b2 + b3)^2")
cstr2 <- list(b1 = "exp(b2 + b3)")
model <- write_lavaan(
  regression = reg, constraint.equal = cstr1,
  constraint.larger = cstr2
)
cat(model)
## ##################################################
## # [---------Regressions (Direct effects)---------]
## 
## y ~ b1*x1 + b2*x2 + b3*x3
## 
## ##################################################
## # [-----------------Constraints------------------]
## 
## b1 == (b2 + b3)^2
## b1 > exp(b2 + b3)

Example 5 (Mediation)

Source: https://lavaan.ugent.be/tutorial/mediation.html

lavaan:

model <- " # direct effect
             Y ~ c*X
           # mediator
             M ~ a*X
             Y ~ b*M
           # indirect effect (a*b)
             ab := a*b
           # total effect
             total := c + (a*b)
         "

lavaanExtra:

mediation <- list(
  Y = "c*X",
  M = "a*X",
  Y = "b*M"
)
indirect <- list(
  ab = "a*b",
  total = "c + (a*b)"
)
model <- write_lavaan(mediation = mediation, indirect = indirect)
cat(model)
## ##################################################
## # [-----------Mediations (named paths)-----------]
## 
## Y ~ c*X
## M ~ a*X
## Y ~ b*M
## 
## ##################################################
## # [--------Mediations (indirect effects)---------]
## 
## ab := a*b
## total := c + (a*b)

Example 6 (Multilevel SEM)

Source: https://lavaan.ugent.be/tutorial/multilevel.html

lavaan:

model <- "
    level: 1
        fw =~ y1 + y2 + y3
        fw ~ x1 + x2 + x3
    level: 2
        fb =~ y1 + y2 + y3
        fb ~ w1 + w2
"

lavaanExtra:

cus <-
  "level: 1
    fw =~ y1 + y2 + y3
    fw ~ x1 + x2 + x3
level: 2
    fb =~ y1 + y2 + y3
    fb ~ w1 + w2
"
model <- write_lavaan(custom = cus)
cat(model)
## ##################################################
## # [------------Custom Specifications-------------]
## 
## level: 1
##     fw =~ y1 + y2 + y3
##     fw ~ x1 + x2 + x3
## level: 2
##     fb =~ y1 + y2 + y3
##     fb ~ w1 + w2

Example 7 (total effects)

Source: https://methodenlehre.github.io/SGSCLM-R-course/cfa-and-sem-with-lavaan.html#structural-equation-modelling-sem

lavaan:

model_mediation <- "
# Measurement model
SUP_Parents =~ sup_parents_p1 + sup_parents_p2 + sup_parents_p3
SUP_Friends =~ sup_friends_p1 + sup_friends_p2 + sup_friends_p3
SE_Academic =~ se_acad_p1 + se_acad_p2 + se_acad_p3
SE_Social =~ se_social_p1 + se_social_p2 + se_social_p3
LS  =~ ls_p1 + ls_p2 + ls_p3

# Structural model
# Regressions
SE_Academic ~ b1*SUP_Parents + b3*SUP_Friends
SE_Social ~ b2*SUP_Parents + b4*SUP_Friends
LS ~ b5*SUP_Parents + b6*SUP_Friends + b7*SE_Academic + b8*SE_Social

# Residual covariances
SE_Academic ~~ SE_Social

# Indirect effects
b1b7 := b1*b7
b2b8 := b2*b8
totalind_eltern := b1*b7 + b2*b8
b3b7 := b3*b7
b4b8 := b4*b8
totalind_freunde := b3*b7 + b4*b8

# Total effects
total_eltern := b1*b7 + b2*b8 + b5
total_freunde := b3*b7 + b4*b8 + b6
"

lavaanExtra:

x <- c("sup_parents", "sup_friends", "se_acad", "se_social", "ls")
y <- lapply(x, paste0, "_p", 1:3)
y <- setNames(y, x)
lat <- list(
  SUP_Parents = y$sup_parents,
  SUP_Friends = y$sup_friends,
  SE_Academic = y$se_acad,
  SE_Social = y$se_social,
  LS = y$ls
)

b <- paste0("b", 1:8)
d <- c(
  rep(c("SUP_Parents", "SUP_Friends"), each = 2),
  "SUP_Parents", "SUP_Friends", "SE_Academic", "SE_Social"
)
e <- paste0(b, "*", d)

reg <- list(
  SE_Academic = e[c(1, 3)],
  SE_Social = e[c(2, 4)],
  LS = e[c(5:8)]
)

cov <- list(SE_Academic = "SE_Social")

ind <- list(
  b1b7 = "b1*b7",
  b2b8 = "b2*b8",
  totalind_eltern = "b1*b7 + b2*b8",
  b3b7 = "b3*b7",
  b4b8 = "b4*b8",
  totalind_freunde = "b3*b7 + b4*b8",
  total_eltern = "b1*b7 + b2*b8 + b5",
  total_freunde = "b3*b7 + b4*b8 + b6"
)

model <- write_lavaan(
  regression = reg, covariance = cov,
  indirect = ind, latent = lat
)
cat(model)
## ##################################################
## # [-----Latent variables (measurement model)-----]
## 
## SUP_Parents =~ sup_parents_p1 + sup_parents_p2 + sup_parents_p3
## SUP_Friends =~ sup_friends_p1 + sup_friends_p2 + sup_friends_p3
## SE_Academic =~ se_acad_p1 + se_acad_p2 + se_acad_p3
## SE_Social =~ se_social_p1 + se_social_p2 + se_social_p3
## LS =~ ls_p1 + ls_p2 + ls_p3
## 
## ##################################################
## # [---------Regressions (Direct effects)---------]
## 
## SE_Academic ~ b1*SUP_Parents + b3*SUP_Friends
## SE_Social ~ b2*SUP_Parents + b4*SUP_Friends
## LS ~ b5*SUP_Parents + b6*SUP_Friends + b7*SE_Academic + b8*SE_Social
## 
## ##################################################
## # [------------------Covariances-----------------]
## 
## SE_Academic ~~ SE_Social
## 
## ##################################################
## # [--------Mediations (indirect effects)---------]
## 
## b1b7 := b1*b7
## b2b8 := b2*b8
## totalind_eltern := b1*b7 + b2*b8
## b3b7 := b3*b7
## b4b8 := b4*b8
## totalind_freunde := b3*b7 + b4*b8
## total_eltern := b1*b7 + b2*b8 + b5
## total_freunde := b3*b7 + b4*b8 + b6

Example 8 (intercepts)

Source: https://lavaan.ugent.be/tutorial/means.html

lavaan:

HS.model <- "
# three-factor model
  visual  =~ x1 + x2 + x3
  textual =~ x4 + x5 + x6
  speed   =~ x7 + x8 + x9
# intercepts with fixed values
  x1 + x2 + x3 + x4 ~ 0.5*1
"

lavaanExtra:

lat <- list(
  visual = paste0("x", 1:3),
  textual = paste0("x", 4:6),
  speed = paste0("x", 7:9)
)

cus <- "x1 + x2 + x3 + x4 ~ 0.5*1"

HS.model <- write_lavaan(
  latent = lat, custom = cus
)
cat(HS.model)
## ##################################################
## # [-----Latent variables (measurement model)-----]
## 
## visual =~ x1 + x2 + x3
## textual =~ x4 + x5 + x6
## speed =~ x7 + x8 + x9
## 
## ##################################################
## # [------------Custom Specifications-------------]
## 
## x1 + x2 + x3 + x4 ~ 0.5*1

Example 9 (thresholds)

Source: https://tdjorgensen.github.io/TDJorgensen/CatMeasEq/Jorgensen.CatMeasEq.pdf

lavaan:

mod2 <- "
## LIR means
  y2w1 ~ mean1*1
  y2w2 ~ mean2*1
## LIR (co)variances
  y2w1 ~~ var1*y2w1 + y2w2
  y2w2 ~~ var2*y2w2
## thresholds link LIRs to observed items
  y2w1 | thr1*t1
  y2w2 | thr2*t1
"

lavaanExtra:

reg <- list(
  y2w1 = "mean1*1",
  y2w2 = "mean2*1"
)

cov <- list(
  y2w1 = c("var1*y2w1", "y2w2"),
  y2w2 = "var2*y2w2"
)

thres <- list(
  y2w1 = "thr1*t1",
  y2w2 = "thr2*t1"
)

HS.model <- write_lavaan(
  regression = reg,
  covariance = cov,
  threshold = thres
)
cat(HS.model)
## ##################################################
## # [---------Regressions (Direct effects)---------]
## 
## y2w1 ~ mean1*1
## y2w2 ~ mean2*1
## 
## ##################################################
## # [------------------Covariances-----------------]
## 
## y2w1 ~~ var1*y2w1 + y2w2
## y2w2 ~~ var2*y2w2
## 
## ##################################################
## # [------------------Thresholds------------------]
## 
## y2w1 | thr1*t1
## y2w2 | thr2*t1

Example 10 (longitudinal invariance)

Source: https://thechangelab.stanford.edu/tutorials/growth-modeling/modeling-change-latent-vars-ordinal-indicators-lavaan/

lavaan:

configural_invar <- " #opening quote
#factor loadings
  eta1 =~ 1*t1_sc + #for identification
          t1_intp +
          t1_ext
  eta2 =~ 1*t2_sc + #for identification
          t2_intp +
          t2_ext
  eta3 =~ 1*t4_sc + #for identification
          t4_intp +
          t4_ext

#latent variable variances
   eta1~~eta1
   eta2~~eta2
   eta3~~eta3

#latent variable covariances
   eta1~~eta2
   eta1~~eta3
   eta2~~eta3

#latent variable means
   eta1~0*1 #for scaling
   eta2~0*1 #for scaling
   eta3~0*1 #for scaling

#propensity variances
   t1_sc  ~~ 1*t1_sc
   t1_intp~~ 1*t1_intp
   t1_ext ~~ 1*t1_ext
   t2_sc  ~~ 1*t2_sc
   t2_intp~~ 1*t2_intp
   t2_ext ~~ 1*t2_ext
   t4_sc  ~~ 1*t4_sc
   t4_intp~~ 1*t4_intp
   t4_ext ~~ 1*t4_ext

#unique covariances


#observed variable intercepts/thresholds (4 categories = 3 thresholds)
   t1_sc   |t1 + t2 + t3
   t1_intp |t1 + t2 + t3
   t1_ext  |t1 + t2 + t3
   t2_sc   |t1 + t2 + t3
   t2_intp |t1 + t2 + t3
   t2_ext  |t1 + t2 + t3
   t4_sc   |t1 + t2 + t3
   t4_intp |t1 + t2 + t3
   t4_ext  |t1 + t2 + t3
" # closing quote

lavaanExtra:

eta <- paste0("eta", 1:3)
t <- paste0("t", 1:4)
term <- c("sc", "intp", "ext")
tnames <- paste0(rep(t[c(1:2, 4)], each = 3), "_", term)
tnames2 <- paste0("1*", tnames)

lat <- list(
  eta1 = c(tnames2[1], tnames[2:3]),
  eta2 = c(tnames2[4], tnames[5:6]),
  eta3 = c(tnames2[7], tnames[8:9])
)

cov <- as.list(c(eta, eta[2:3], eta[3], tnames2))
names(cov) <- c(eta, eta[1], eta[1:2], tnames)

thres <- rep(list(t[1:3]), 9)
names(thres) <- tnames

reg <- as.list(setNames(rep("0*1", 3), eta))

HS.model <- write_lavaan(
  regression = reg,
  latent = lat,
  covariance = cov,
  threshold = thres
)
cat(HS.model)
## ##################################################
## # [-----Latent variables (measurement model)-----]
## 
## eta1 =~ 1*t1_sc + t1_intp + t1_ext
## eta2 =~ 1*t2_sc + t2_intp + t2_ext
## eta3 =~ 1*t4_sc + t4_intp + t4_ext
## 
## ##################################################
## # [---------Regressions (Direct effects)---------]
## 
## eta1 ~ 0*1
## eta2 ~ 0*1
## eta3 ~ 0*1
## 
## ##################################################
## # [------------------Covariances-----------------]
## 
## eta1 ~~ eta1
## eta2 ~~ eta2
## eta3 ~~ eta3
## eta1 ~~ eta2
## eta1 ~~ eta3
## eta2 ~~ eta3
## t1_sc ~~ 1*t1_sc
## t1_intp ~~ 1*t1_intp
## t1_ext ~~ 1*t1_ext
## t2_sc ~~ 1*t2_sc
## t2_intp ~~ 1*t2_intp
## t2_ext ~~ 1*t2_ext
## t4_sc ~~ 1*t4_sc
## t4_intp ~~ 1*t4_intp
## t4_ext ~~ 1*t4_ext
## 
## ##################################################
## # [------------------Thresholds------------------]
## 
## t1_sc | t1 + t2 + t3
## t1_intp | t1 + t2 + t3
## t1_ext | t1 + t2 + t3
## t2_sc | t1 + t2 + t3
## t2_intp | t1 + t2 + t3
## t2_ext | t1 + t2 + t3
## t4_sc | t1 + t2 + t3
## t4_intp | t1 + t2 + t3
## t4_ext | t1 + t2 + t3