Skip to contents

Create an extended SEM with OpenMx (Boker et al., 2011) using a lavaan-style (Rosseel, 2012) syntax.

Usage

mxsem(
  model,
  data,
  scale_loadings = TRUE,
  scale_latent_variances = FALSE,
  add_intercepts = TRUE,
  add_variances = TRUE,
  add_exogenous_latent_covariances = TRUE,
  add_exogenous_manifest_covariances = TRUE,
  lbound_variances = TRUE,
  directed = unicode_directed(),
  undirected = unicode_undirected(),
  return_parameter_table = FALSE
)

Arguments

model

model syntax similar to lavaan's syntax

data

raw data used to fit the model. Alternatively, an object created with OpenMx::mxData can be used (e.g., OpenMx::mxData(observed = cov(OpenMx::Bollen), means = colMeans(OpenMx::Bollen), numObs = nrow(OpenMx::Bollen), type = "cov")).

scale_loadings

should the first loading of each latent variable be used for scaling?

scale_latent_variances

should the latent variances be used for scaling?

add_intercepts

should intercepts for manifest variables be added automatically? If set to false, intercepts must be added manually. If no intercepts are added, mxsem will automatically use just the observed covariances and not the observed means.

add_variances

should variances for manifest and latent variables be added automatically?

add_exogenous_latent_covariances

should covariances between exogenous latent variables be added automatically?

add_exogenous_manifest_covariances

should covariances between exogenous manifest variables be added automatically?

lbound_variances

should the lower bound for variances be set to 0.000001?

directed

symbol used to indicate directed effects (regressions and loadings)

undirected

symbol used to indicate undirected effects (variances and covariances)

return_parameter_table

if set to TRUE, the internal parameter table is returend together with the mxModel

Value

mxModel object that can be fitted with mxRun or mxTryHard. If return_parameter_table is TRUE, a list with the mxModel and the parameter table is returned.

Details

Setting up SEM can be tedious. The lavaan (Rosseel, 2012) package provides a great syntax to make the process easier. The objective of mxsem is to provide a similar syntax for OpenMx. OpenMx is a flexible R package for extended SEM. However, note that mxsem only covers a small part of the OpenMx framework by focusing on "standard" SEM. Similar to lavaan's sem()-function, mxsem tries to set up parts of the model automatically (e.g., adding variances automatically or scaling the latent variables automatically). If you want to unlock the full potential of OpenMx, mxsem may not be the best option.

Warning: The syntax and settings of mxsem may differ from lavaan in some cases. See vignette("Syntax", package = "mxsem") for more details on the syntax and the default arguments.

Alternatives

You will find similar functions in the following packages:

  • metaSEM (Cheung, 2015) provides a lavaan2RAM function that can be combined with the create.mxModel function. This combination offers more features than mxsem. For instance, constraints of the form a < b are supported. In mxsem such constraints require algebras (e.g., !diff; a := b - exp(diff)).

  • umx (Bates et al., 2019) provides the umxRAM and umxLav2RAM functions that can parse single lavaan-style statements (e.g., eta =~ y1 + y2 + y3) or an entire lavaan models to OpenMx models.

  • tidySEM (van Lissa, 2023) provides the as_ram function to translate lavaan syntax to OpenMx and also implements a unified syntax to specify both, lavaan and OpenMx models. Additionally, it works well with the tidyverse.

  • ezMx (Bates, et al. 2014) simplifies fitting SEM with OpenMx and also provides a translation of lavaan models to OpenMx with the lavaan.to.OpenMx function.

Because mxsem implements the syntax parser from scratch, it can extend the lavaan syntax to account for specific OpenMx features. This enables implicit transformations with curly braces.

Citation

Cite OpenMx (Boker et al., 2011) for the modeling and lavaan for the syntax (Rosseel, 2012). mxsem itself is just a very small package and lets OpenMx do all the heavy lifting.

Defaults

By default, mxsem scales latent variables by setting the loadings on the first item to 1. This can be changed by setting scale_loadings = FALSE in the function call. Setting scale_latent_variances = TRUE sets latent variances to 1 for scaling.

mxsem will add intercepts for all manifest variables as well as variances for all manifest and latent variables. A lower bound of 1e-6 will be added to all variances. Finally, covariances for all exogenous variables will be added. All of these options can be changed when calling mxsem.

Syntax

The syntax is, for the most part, identical to that of lavaan. The following specifies loadings of a latent variable eta on manifest variables y1-y4:

eta =~ y1 + y2 + y3

Regressions are specified with ~:

xi  =~ x1 + x2 + x3
eta =~ y1 + y2 + y3
# predict eta with xi:
eta ~  xi

Add covariances with ~~

xi  =~ x1 + x2 + x3
eta =~ y1 + y2 + y3
# predict eta with xi:
eta ~  xi
x1 ~~ x2

Intercepts are specified with ~1

xi  =~ x1 + x2 + x3
eta =~ y1 + y2 + y3
# predict eta with xi:
eta ~  xi
x1 ~~ x2

eta ~ 1

Parameter labels and constraints

Add labels to parameters as follows:

xi  =~ l1*x1 + l2*x2 + l3*x3
eta =~ l4*y1 + l5*y2 + l6*y3
# predict eta with xi:
eta ~  b*xi

Fix parameters by using numeric values instead of labels:

xi  =~ 1*x1 + l2*x2 + l3*x3
eta =~ 1*y1 + l5*y2 + l6*y3
# predict eta with xi:
eta ~  b*xi

Bounds

Lower and upper bounds allow for constraints on parameters. For instance, a lower bound can prevent negative variances.

xi  =~ 1*x1 + l2*x2 + l3*x3
eta =~ 1*y1 + l5*y2 + l6*y3
# predict eta with xi:
eta ~  b*xi
# residual variance for x1
x1 ~~ v*x1
# bound:
v > 0

Upper bounds are specified with v < 10. Note that the parameter label must always come first. The following is not allowed: 0 < v or 10 > v.

(Non-)linear constraints

Assume that latent construct eta was observed twice, where eta1 is the first observation and eta2 the second. We want to define the loadings of eta2 on its observations as l_1 + delta_l1. If delta_l1 is zero, we have measurement invariance.

eta1 =~ l1*y1 + l2*y2 + l3*y3
eta2 =~ l4*y4 + l5*y5 + l6*y6
# define new delta-parameter
!delta_1; !delta_2; !delta_3
# redefine l4-l6
l4 := l1 + delta_1
l5 := l2 + delta_2
l6 := l3 + delta_3

Alternatively, implicit transformations can be used as follows:

eta1 =~ l1*y1 + l2*y2 + l3*y3
eta2 =~ {l1 + delta_1} * y4 + {l2 + delta_2} * y5 + {l3 + delta_3} * y6

Specific labels for the transformation results can also be provided:

eta1 =~ l1*y1 + l2*y2 + l3*y3
eta2 =~ {l4 := l1 + delta_1} * y4 + {l5 := l2 + delta_2} * y5 + {l6 := l3 + delta_3} * y6

This is inspired by the approach in metaSEM (Cheung, 2015).

Definition variables

Definition variables allow for person-specific parameter constraints. Use the data.-prefix to specify definition variables.

I =~ 1*y1 + 1*y2 + 1*y3 + 1*y4 + 1*y5
S =~ data.t_1 * y1 + data.t_2 * y2 + data.t_3 * y3 + data.t_4 * y4 + data.t_5 * y5

I ~ int*1
S ~ slp*1

Starting Values

mxsem differs from lavaan in the specification of starting values. Instead of providing starting values in the model syntax, the set_starting_values function is used.

References

  • Bates, T. C., Maes, H., & Neale, M. C. (2019). umx: Twin and Path-Based Structural Equation Modeling in R. Twin Research and Human Genetics, 22(1), 27–41. https://doi.org/10.1017/thg.2019.2

  • Bates, T. C., Prindle, J. J. (2014). ezMx. https://github.com/OpenMx/ezMx

  • Boker, S. M., Neale, M., Maes, H., Wilde, M., Spiegel, M., Brick, T., Spies, J., Estabrook, R., Kenny, S., Bates, T., Mehta, P., & Fox, J. (2011). OpenMx: An Open Source Extended Structural Equation Modeling Framework. Psychometrika, 76(2), 306–317. https://doi.org/10.1007/s11336-010-9200-6

  • Cheung, M. W.-L. (2015). metaSEM: An R package for meta-analysis using structural equation modeling. Frontiers in Psychology, 5. https://doi.org/10.3389/fpsyg.2014.01521

  • Rosseel, Y. (2012). lavaan: An R package for structural equation modeling. Journal of Statistical Software, 48(2), 1–36. https://doi.org/10.18637/jss.v048.i02

  • van Lissa, C. J. (2023). tidySEM: Tidy Structural Equation Modeling. R package version 0.2.4, https://cjvanlissa.github.io/tidySEM/.

Examples

# THE FOLLOWING EXAMPLE IS ADAPTED FROM LAVAAN
library(mxsem)

model <- '
  # latent variable definitions
     ind60 =~ x1 + x2 + x3
     dem60 =~ y1 + a1*y2 + b*y3 + c1*y4
     dem65 =~ y5 + a2*y6 + b*y7 + c2*y8

  # regressions
    dem60 ~ ind60
    dem65 ~ ind60 + dem60

  # residual correlations
    y1 ~~ y5
    y2 ~~ y4 + y6
    y3 ~~ y7
    y4 ~~ y8
    y6 ~~ y8
'

fit <- mxsem(model = model,
            data  = OpenMx::Bollen) |>
  mxTryHard()
#> Running untitled13 with 41 parameters
#> 
#> Beginning initial fit attempt
#> Running untitled13 with 41 parameters
#> 
#>  Lowest minimum so far:  3096.94452072454
#> 
#> Solution found
#> 


#> 
#>  Solution found!  Final fit=3096.9445 (started at 226053.29)  (1 attempt(s): 1 valid, 0 errors)
#>  Start values from best fit:
#> 2.17951987773111,1.81811280701136,1.44903971585263,0.604497109020104,1.2914705087076,1.17388194480817,1.30214833592763,0.898493075089598,1.1324734492235,1.20957839087818,1.91458020207026,7.40452084345933,4.99235939692914,1.32054688622592,3.15118547115015,2.17540111771095,5.0152306057039,0.0813525457778652,0.120528431147638,0.466700862872675,0.590970527832852,2.30230807957473,0.731339590208144,3.52499601408348,0.353182177765634,1.41225190814923,3.32140676951725,0.448633932250771,3.71721598526889,0.164480652182256,5.46466355896305,4.25643995945798,6.56310792761437,4.4525276703987,2.97807303177841,5.05438328541841,4.7921934164438,3.55768869770291,5.13624831577953,6.19625910503793,4.04338865506019
omxGetParameters(fit)
#>    ind60→x2    ind60→x3 ind60→dem60 ind60→dem65          a1           b 
#>  2.17951988  1.81811281  1.44903972  0.60449711  1.29147051  1.17388194 
#>          c1 dem60→dem65          a2          c2       y1↔y1       y2↔y2 
#>  1.30214834  0.89849308  1.13247345  1.20957839  1.91458020  7.40452084 
#>       y3↔y3       y2↔y4       y4↔y4       y2↔y6       y6↔y6       x1↔x1 
#>  4.99235940  1.32054689  3.15118547  2.17540112  5.01523061  0.08135255 
#>       x2↔x2       x3↔x3       y1↔y5       y5↔y5       y3↔y7       y7↔y7 
#>  0.12052843  0.46670086  0.59097053  2.30230808  0.73133959  3.52499601 
#>       y4↔y8       y6↔y8       y8↔y8 ind60↔ind60 dem60↔dem60 dem65↔dem65 
#>  0.35318218  1.41225191  3.32140677  0.44863393  3.71721599  0.16448065 
#>      one→y1      one→y2      one→y3      one→y4      one→y6      one→x1 
#>  5.46466356  4.25643996  6.56310793  4.45252767  2.97807303  5.05438329 
#>      one→x2      one→x3      one→y5      one→y7      one→y8 
#>  4.79219342  3.55768870  5.13624832  6.19625911  4.04338866 


model_transformations <- '
  # latent variable definitions
     ind60 =~ x1 + x2 + x3
     dem60 =~ y1 + a1*y2 + b1*y3 + c1*y4
     dem65 =~ y5 + {a2 := a1 + delta_a}*y6 + {b2 := b1 + delta_b}*y7 + c2*y8

  # regressions
    dem60 ~ ind60
    dem65 ~ ind60 + dem60

  # residual correlations
    y1 ~~ y5
    y2 ~~ y4 + y6
    y3 ~~ y7
    y4 ~~ y8
    y6 ~~ y8
'

fit <- mxsem(model = model_transformations,
            data  = OpenMx::Bollen) |>
  mxTryHard()
#> Running untitled16 with 42 parameters
#> 
#> Beginning initial fit attempt
#> Running untitled16 with 42 parameters
#> 
#>  Lowest minimum so far:  3095.58188681023
#> 
#> Solution found
#> 


#> 
#>  Solution found!  Final fit=3095.5819 (started at 225616.79)  (1 attempt(s): 1 valid, 0 errors)
#>  Start values from best fit:
#> 2.18036692502419,1.8185110842876,1.48299713907554,0.572339861173599,1.25674950145635,1.05771651270941,1.26478738795161,0.837345234806707,1.26594677757215,1.89139933687739,7.37285050090153,5.06747712630144,1.31312234445956,3.14792354607904,2.15286574816779,4.95395470168421,0.0815493292024986,0.119806744784092,0.466702792790979,0.623669558954236,2.35097477433781,0.794960818250051,3.43139043112232,0.34822384350209,1.35615768074408,3.25408176784843,0.448438066317501,3.95602557364003,0.172474415796178,5.46467040485137,4.25645067885819,6.56311263149745,4.45253722882951,2.97808065434922,5.054384557668,4.7921959164682,3.55769087542126,5.13625612535699,6.19626561257264,4.0433958742259,-0.0710524345068419,0.221794335683815
omxGetParameters(fit)
#>    ind60→x2    ind60→x3 ind60→dem60 ind60→dem65          a1          b1 
#>  2.18036693  1.81851108  1.48299714  0.57233986  1.25674950  1.05771651 
#>          c1 dem60→dem65          c2       y1↔y1       y2↔y2       y3↔y3 
#>  1.26478739  0.83734523  1.26594678  1.89139934  7.37285050  5.06747713 
#>       y2↔y4       y4↔y4       y2↔y6       y6↔y6       x1↔x1       x2↔x2 
#>  1.31312234  3.14792355  2.15286575  4.95395470  0.08154933  0.11980674 
#>       x3↔x3       y1↔y5       y5↔y5       y3↔y7       y7↔y7       y4↔y8 
#>  0.46670279  0.62366956  2.35097477  0.79496082  3.43139043  0.34822384 
#>       y6↔y8       y8↔y8 ind60↔ind60 dem60↔dem60 dem65↔dem65      one→y1 
#>  1.35615768  3.25408177  0.44843807  3.95602557  0.17247442  5.46467040 
#>      one→y2      one→y3      one→y4      one→y6      one→x1      one→x2 
#>  4.25645068  6.56311263  4.45253723  2.97808065  5.05438456  4.79219592 
#>      one→x3      one→y5      one→y7      one→y8     delta_a     delta_b 
#>  3.55769088  5.13625613  6.19626561  4.04339587 -0.07105243  0.22179434