Bootstrap Confidence Interval for Standardized Solution in lavaan

What standardizedSolution_boot_ci() Does

In lavaan, if bootstrapping is requested, the standard errors and confidence intervals in the standardized solutions are computed by delta method using the variance-covariance matrix of the bootstrap estimates. The intervals are symmetric about the point estimates and are not the usual bootstrap percentile confidence intervals users expect when bootstrapping is conducted.

standardizedSolution_boot_ci() accepts a lavaan::lavaan-class object fitted with se = "bootstrap" (or se = "boot") and forms the percentile confidence intervals based on the bootstrap estimates stored in the object.

Data and Model

A mediation model example adapted from the official lavaan website is used (https://lavaan.ugent.be/tutorial/mediation.html).

library(lavaan)
set.seed(1234)
n <- 100
X <- runif(n) - .5
M <- 0.20*X + rnorm(n)
Y <- 0.17*M + rnorm(n)
Data <- data.frame(X = X, Y = Y, M = M)
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)
         '

This model is fitted with se = "bootstrap":

fit <- sem(model,
           data = Data,
           se = "bootstrap",
           bootstrap = 5000,
           parallel ="snow", ncpus = 8)

This is the standardized solution with delta-method confidence intervals (results may be different when the code is ran again because seed is not set).

standardizedSolution(fit)
#>     lhs op     rhs label est.std    se       z pvalue ci.lower ci.upper
#> 1     Y  ~       X     c   0.018 0.097   0.185  0.853   -0.172    0.207
#> 2     M  ~       X     a   0.046 0.100   0.456  0.648   -0.150    0.241
#> 3     Y  ~       M     b   0.102 0.102   1.001  0.317   -0.098    0.302
#> 4     Y ~~       Y         0.989 0.021  48.205  0.000    0.949    1.029
#> 5     M ~~       M         0.998 0.009 109.591  0.000    0.980    1.016
#> 6     X ~~       X         1.000 0.000      NA     NA    1.000    1.000
#> 7    ab :=     a*b    ab   0.005 0.010   0.448  0.654   -0.016    0.025
#> 8 total := c+(a*b) total   0.023 0.096   0.234  0.815   -0.167    0.212

Form Bootstrap Percentile CIs for Standardized Solution

To form bootstrap percentile confidence intervals for the standardized solution, simply use standardizedSolution_boot_ci() instead of lavaan::standardizedSolution():

library(semhelpinghands)
ci_boot <- standardizedSolution_boot_ci(fit)
ci_boot
#>     lhs op     rhs label est.std    se       z pvalue ci.lower ci.upper boot.ci.lower boot.ci.upper
#> 1     Y  ~       X     c   0.018 0.097   0.185  0.853   -0.172    0.207        -0.168         0.208
#> 2     M  ~       X     a   0.046 0.100   0.456  0.648   -0.150    0.241        -0.150         0.243
#> 3     Y  ~       M     b   0.102 0.102   1.001  0.317   -0.098    0.302        -0.095         0.297
#> 4     Y ~~       Y         0.989 0.021  48.205  0.000    0.949    1.029         0.898         0.999
#> 5     M ~~       M         0.998 0.009 109.591  0.000    0.980    1.016         0.941         1.000
#> 6     X ~~       X         1.000 0.000      NA     NA    1.000    1.000            NA            NA
#> 7    ab :=     a*b    ab   0.005 0.010   0.448  0.654   -0.016    0.025        -0.029         0.036
#> 8 total := c+(a*b) total   0.023 0.096   0.234  0.815   -0.167    0.212        -0.165         0.210

The bootstrap percentile confidence intervals are appended to the right of the original output of lavaan::standardizedSolution().

Note

The function standardizedSolution_boot_ci() takes some time to run because it retrieves the estimates of the unstandardized solution in each bootstrap sample and computes the estimates in the standardized solution. Therefore, if 5,000 bootstrap samples are requested, this process is repeated 5,000 times. Nevertheless, it is still much faster than fitting the model 5,000 times again.

Background

This function was originally proposed in an issue at GitHub, inspired by a discussion at the Google group for lavaan. It is not a versatile function and used some “tricks” to do the work. A more reliable way is to use function like lavaan::bootstrapLavaan(). Nevertheless, this simple function is good enough for the cases I encountered in my work.