Skip to content

Instantly share code, notes, and snippets.

@mihaiconstantin
Last active January 10, 2024 14:52
Show Gist options
  • Save mihaiconstantin/475e1d79978e4103dbbffbece5b46721 to your computer and use it in GitHub Desktop.
Save mihaiconstantin/475e1d79978e4103dbbffbece5b46721 to your computer and use it in GitHub Desktop.
Adding phantom latent variables in SEM in `lavaan`.
# Phantom latent variables.
# Load the packages.
library(lavaan)
library(semPlot)
# Load data.
data(HolzingerSwineford1939)
# Store the data.
data <- HolzingerSwineford1939[, c(7:12)]
# Specify the model.
model_1 <- "
# Define latent variables with observed indicators.
eta1 =~ x1 + x2 + x3
eta2 =~ x4 + x5 + x6
# Define latent covariance.
eta1 ~~ eta2
"
# Fit the model.
fit_1 <- sem(model_1, data = data)
# Display the results.
summary(fit_1)
# Plot the model.
semPlot::semPaths(fit_1, what = "path", whatLabels = "est", style = "ram", layout = "spring")
# Specify the model.
model_2 <- "
# Define latent variables with observed indicators.
eta1 =~ x1 + x2 + x3
eta2 =~ x4 + x5 + x6
# Define latent covariance.
eta1 ~~ eta2
# Define a phantom latent variable.
# See: https://search.r-project.org/CRAN/refmans/lavaan/html/model.syntax.html
phantom =~ 0
# Fix the variance and any covariances of the phantom latent variable.
phantom ~~ 0 * phantom
phantom ~~ 0 * eta2
# Define a structural regression path involving the phantom latent variable.
eta1 ~ 0.5 * phantom
"
# Fit the model.
fit_2 <- sem(model_2, data = data)
# Display the results.
summary(fit_2)
# Plot the model.
semPlot::semPaths(fit_2, what = "path", whatLabels = "est", style = "ram", layout = "spring")
# Add fake variables to the data, with random values.
data$fake_x7 <- rnorm(nrow(data))
data$fake_x8 <- rnorm(nrow(data))
data$fake_x9 <- rnorm(nrow(data))
# Specify the model.
model_3 <- "
# Define latent variables with observed indicators.
eta1 =~ x1 + x2 + x3
eta2 =~ x4 + x5 + x6
# Define latent covariance.
eta1 ~~ eta2
# Define a phantom latent variable based on fake observed variables.
phantom =~ 0 * fake_x7 + 0 * fake_x8 + 0 * fake_x9
# Fix the variance and any covariances of the phantom latent variable.
phantom ~~ 0 * phantom
phantom ~~ 0 * eta2
# Fix the residuals of the fake observed variables.
fake_x7 ~~ 1 * fake_x7
fake_x8 ~~ 1 * fake_x8
fake_x9 ~~ 1 * fake_x9
# Define a structural regression path.
eta1 ~ 0.5 * phantom
"
# Fit the model.
fit_3 <- sem(model_3, data = data)
# Display the results.
summary(fit_3)
# Plot the model.
semPlot::semPaths(fit_3, what = "path", whatLabels = "est", style = "ram", layout = "spring")
# Note. The fake observed variables are not per se used as "known" pieces of
# information. We added them just to define the phantom latent variable with a
# full-fledged measurement model. You can see that the number of free model
# parameters is the same as in `model_2`. However, the degrees of freedom are
# different because `lavaan` does not know that the fake variables are just
# specification trickery. Nevertheless, you will see that the estimated values
# for the model parameters are identical.
# Display all model results.
summary(fit_1)
summary(fit_2)
summary(fit_3)
# Plot all models.
# Set the layout.
layout(matrix(c(1, 2, 3), nrow = 1, ncol = 3))
# Plot model 1.
semPaths(fit_1, what = "path", whatLabels = "est", style = "ram", layout = "tree")
title("Model 1")
# Plot model 2.
semPaths(fit_2, what = "path", whatLabels = "est", style = "ram", layout = "tree")
title("Model 2")
# Plot model 3.
semPaths(fit_3, what = "path", whatLabels = "est", style = "ram", layout = "tree")
title("Model 3")
# Reset the layout.
layout(1:1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment