esem.from.mods: Runs ESEM based on CFA and EFA model outputs.

View source: R/esem.from.mods.R

esem.from.modsR Documentation

Runs ESEM based on CFA and EFA model outputs.

Description

esem.from.keys runs exploratory structural equation models (ESEM) in lavaan where the exploratory factor analysis (EFA) factors predict confirmatory factor analysis (CFA) factors and/or bifactor factors in separate models for each CFA or bifactor model. The function takes a fitted lavaan object from an EFA and lists of fitted CFA and/or bifactor lavaan model objects as inputs so will typically use outputs from efa.from.keys(), and cfa.from.keys() or bifactor.from.keys().

Usage

esem.from.mods(
  efa_fit,
  cfa_fit = NULL,
  bif_fit = NULL,
  data,
  fit_save = FALSE,
  fit_measures = "all",
  miss = "ML",
  est = "default",
  name = "esem",
  check = FALSE,
  save_out = FALSE
)

Arguments

efa_fit

A fitted lavaan object of an EFA model.

cfa_fit

A named list of fitted lavaan objects of CFA models. Can be NULL if bif_fit is not NULL.

bif_fit

A named list of fitted lavaan objects of bifactor models. Can be NULL if cfa_fit is not NULL.

data

A dataframe or object coercible to a dataframe. Data must include all observed variables used in any of the models.

fit_save

Logical. TRUE indicates model fit measures should be included in the output; FALSE indicates model fit measures should not be included in the output. FALSE may be desirable when fit measures are of little interest and when they may take a long time to estimate. Fit can still be examined for individual models with, ⁠lavaan::fitMeasures(fit$fit$[model name])⁠.

fit_measures

A vector of fit measures to save or 'all' to select all fit measures, as per the fit.measures parameter from lavaan's lavaan::fitMeasures() function. Defaults to 'all'. Irrelevant if fit_save = FALSE.

miss

A string. Sets the missing parameter, as per lavaan (see lavaan::lavOptions()). Defaults to 'ML'.

est

A string. Sets the estimator parameter, as per lavaan (see lavaan::lavOptions()). The default ('default') uses the lavaan default for the model being run.

name

A string indicating a subdirectory where model outputs will be saved when save_out = TRUE and checked against when check = TRUE. Defaults to "esem". Irrelevant if both save_out = FALSE and check = FALSE. The name should be unique for each set of models or outputs from calls with the same name will be overwritten.

check

Logical. TRUE indicates that current inputs should be compared to previous inputs if they exist and that the model should not be rerun if nothing has changed; FALSE indicates that these checks should not be made and the model should be run regardless of the existence of previous inputs.

save_out

Logical. TRUE indicates that model code, a hash of the data, important input parameter values, and output will be saved; FALSE indicates that nothing will be saved. Selecting save_out = TRUE enables the function to not rerun models next time if check = TRUE the next time the code is run and nothing has changed in the meantime.

Details

The function was designed to streamline running exploratory structural equation models (ESEM) where EFA factors predict a series of latent variables in separate models using Burt's (1976) 2-stage procedure to prevent interpretational confounding. The function is designed to run analyses equivalent to that of Bainbridge, Ludeke, and Smillie (2022).

The function requires fitted lavaan objects as inputs in order to properly employ the 2-stage procedure. Using efa.from.keys(), cfa.from.keys(), and/or bifactor.from.keys() should make this relatively straight-forward.

Matching the philosophy of the package, the function is designed to run for multiple models with a similar design. If you are using the function for a single model, transform inputs into lists as appropriate.

The model relies on sem.check() for the back-end of running the models. This enables saving inputs and outputs from model runs (with save_out = TRUE) and checking to see if anything has changed from prior runs before running again (with check = TRUE). The functionality was included for a number of very slow models or a lot of faster models, such that time spent rerunning them would be onerous. For further details on how this works, see the sem.check() function documentation.

Although the function will take any lavaan models that have successfully produced standard lavaan outputs, it will not complain if these have poor fit or other undesirable characteristics (beyond warnings and errors produced by lavaan). If this matters to you, it would be worth checking the outputs of upstream functions prior to running esem.from.mods().

The 2-stage procedure employed mitigates some of the problems of poor fitting measurement models but, depending on your purpose and research question(s), it could nevertheless be important to update models to ensure good fit prior to running this function. There is currently no capability within semFromKeys to add modifications to CFA or bifactor models (beyond removing items by removing them from the keys).

For bifactor models predicted by ESEM factors, a complication is that fixing orthogonal relationships between general and group factors is not possible if any of the factors are regressed on any others. This is because, in SEM, fixing correlations with a factor that is an outcome fixes correlations with the residual, which is, of course, not the requirement of a bifactor model.

There are three solutions to this problem. First, avoid using bifactor models. This may be possible sometimes, but it frequently is not. Second, avoid using regressions in models with bifactor models and compute regressions based on latent variable correlations. (This can be done by, e.g., creating a correlation matrix from the factor correlations and using the psych::setCor() function). This method produces accurate point estimates in the regressions, but standard errors are biased as the method cannot account for uncertainty in the correlations. This is an adequate solution if p-values and confidence intervals are not required. Finally, if the 2-stage procedure is employed, then there is little room for measurement models to change to allow factor correlations to change. Therefore, the parameter can be relaxed and implied correlations between the group and general factors ought to remain close to zero. This is the method employed by esem.from.mods().

Value

Returns a list of length 4 (if fit_save = FALSE) or 5 (if fit_save = TRUE). The elements of the list are: a list of lavaan model output objects; a list of parameter estimates from the models (standardized if std = TRUE); if fit_save = TRUE, a matrix of fit measures for each model; a list of regression beta parameters from each model; and a dataframe of R-squared values from each model.

References

Bainbridge, T. F., Ludeke, S. G., & Smillie, L. D. (2022). Evaluating the Big Five as an organizing framework for commonly used psychological trait scales. Journal of Personality and Social Psychology, 122(4), 749-777. https://doi.org/10.1037/pspp0000395.

Burt, R. S. (1976). Interpretational confounding of unobserved variables in Structural Equation Models. Sociological Methods & Research, 5(1), 3-52. https://doi.org/10.1177/004912417600500101.

Eid, M., Geiser, C., Koch, T., & Heene, M. (2017). Anomalous results in G-factor models: Explanations and alternatives. Psychological Methods, 22(3), 541-562. https://doi.org/10.1037/met0000083.

See Also

sem.check(), which this function uses for all the back-end; cfa.from.keys(), efa.from.keys(), and bifactor.from.keys(), which are useful functions for creating inputs into esem.from.mods(); and lavaan::sem(), which is used to estimate the models.

Examples

# Create CFA keys
keys0 <- c("grit_c", "grit_p", "hope_a", "hope_p")
keys <- sapply(
  keys0, function(x) names(BFIGritHope)[grep(x, names(BFIGritHope))]
)
# Create bifactor keys
keys_g0 <- c("grit", "hope")
keys_g <- sapply(
  keys_g0, function(x) names(BFIGritHope)[grep(x, names(BFIGritHope))]
)
keys_b1 <- sapply(
  keys_g0, function(x) keys0[grep(x, keys0)], simplify = FALSE
)
# Avoid fit problems with an S-1 model (Eid et al., 2017)
keys_b <- keys_b1
keys_b$hope <- keys_b1$hope[-1]
# Create EFA keys
# Using only 3 factors and fewer items to save time for a simple example
# (This results in a less than ideal solution but it doesn't matter for an
# example)
keys_e0 <- paste0("bfi_", c("e", "a", "c"))
keys_e <- sapply(
  keys_e0,
  function(x) {
    names(BFIGritHope)[grep(paste0(x, "\\d_[1-2]"), names(BFIGritHope))]
  },
  simplify = FALSE
)
# Create fitted objects to use as inputs
cfa_fit <- cfa.from.keys(keys, BFIGritHope, fit_save = FALSE)
efa_fit <- efa.from.keys(keys_e, BFIGritHope, fit_save = FALSE)
bif_fit <- bifactor.from.keys(
  keys_g, keys_b, keys, BFIGritHope, fit_save = FALSE
)
# Run models
esem_fit <- esem.from.mods(
  efa_fit$fit$efa, cfa_fit$fit, bif_fit$fit, data = BFIGritHope,
  fit_save = FALSE, check = FALSE
)
# Examine results
summary(esem_fit$fit$grit_c)  # Standard lavaan summary
esem_fit$r2                   # R-squareds
esem_fit$b                    # Betas

semFromKeys documentation built on July 24, 2026, 5:07 p.m.