Using group.equal argument to constrain parameters with ESEM

239 views
Skip to first unread message

Lex L'Insalata

unread,
Nov 16, 2021, 12:21:44 PM11/16/21
to lavaan
Hi everyone,

I have really been enjoying using lavaan, but have recently run into difficulty regarding the group.equal argument. I would really appreciate any help that others may be able to provide as I am still new to this. I have fit an ESEM model with 4 factors and I’m using them to predict several variables in a sample with N > 600. I want to test the model’s measurement invariance across two groups in order to see if it is possible to compare models across two timepoints. So far, I have attempted this via the code included below.

After reviewing the output I had a few concerns. Particularly, that the group.equal argument may not be working properly when combined with ESEM. It appears to not be truly constraining to equality the loadings. Specifically, it’s only successfully constraining loadings for the Residual model and not for the Metric or Scalar models. Intercepts and residuals did not have this issue and were properly constrained when included in the argument for the appropriate models (indicated in code below). 

I am wondering if I am understanding the output properly and if this concern regarding the group.equal argument is valid? And if it is valid, how I might go about testing invariance in other ways?


esem1_model <- '
efa("efa")*f1 +
efa("efa")*f2 +
efa("efa")*f3 +
efa("efa")*f4 =~ cp_pes2 + cp_pes3 + cp_pes4 + cp_pes5 + cp_pes6 + cp_pes7 + cp_pes8 + cp_pes9 + cp_pes10 + cp_pes11 + cp_pes12 + cp_pes14 + cp_pes15 + cp_pes18 + cp_pes19 + cp_pes20 + cp_pes21 + cp_pes22 + cp_pes23 + cp_pes25 + cp_pes26 + cp_pes27 + cp_pes28 + cp_pes29 + cp_pes30 + cp_pes32 + cp_pes34 + cp_pes36 + cp_pes37 + cp_pes38 + cp_pes39’


m1.fit.configural <- sem(model = esem1_model,
             data = dat,
             rotation = "oblimin",
             estimator = "MLR",
             missing = "ml",
             std.ov = TRUE,
             group = "Timepoint")
summary(m1.fit.configural, fit.measures = TRUE)

For the Metric model group.equal = c("loadings")) is the final line in the above code that otherwise remains unchanged. For the Scalar model group.equal = c("loadings", "intercepts")) is the final line in the above code that also otherwise remains unchanged. And finally, for the Residual model group.equal = c("loadings", "intercepts", "residuals")) is the final line in the above code that otherwise remains unchanged. 


I hope this makes sense and please do let me know if further clarification is needed to help. Thank you so much in advance!

Best,
Lex

Mauricio Garnier-Villarreal

unread,
Nov 16, 2021, 12:29:23 PM11/16/21
to lavaan
Lex

First some clarification. You are not running an ESEM model, this is an Exploratory factor analysis (EFA). For ESEM you would need some added steps in lavaan, there is no "easy" way to do ESEM.
Another posibility would be to do the model in Bayesian SEM with the package blavaan, and use priors to constraint cross-loadings.

Another is that if what you have are timepoints, would be better to evaluate measurement invariance with a longitudinal model instead of multiple groups. With multiple group you are assuming that the subjects are independent between timepoints, instead of being the same subjects over time

Lex L'Insalata

unread,
Nov 22, 2021, 6:21:57 PM11/22/21
to lavaan
Thank you so much for your helpful feedback!! I had some follow-up questions/clarifications if that is alright to make sure I am understanding this all properly. Also, apologies for my lack of clarification as these are independent subjects at each timepoint as it is looking at a group recruited before COVID-19 lockdown and a second group recruited during the lockdown, instead of the same subjects over time. I am not sure if that changes any of your feedback about modeling. Again, sorry I did not make that clear the first time.

The code I initially included I now realize only was for the EFA part of ESEM and was not all of the code so apologize as well if that left out the important steps that differentiate the EFA from ESEM. I have included that below in case you notice that even with the full code that I am still missing some added steps in lavaan for ESEM. That being said, if that is all now the correct steps/code is your suggestion to still use Bayesian SEM? Or is there possibly some part of the code left out that is resulting in the loadings not being constrained when testing for measurement invariance across the hierarchical model with the group.equal argument as stated initially?

#Full code used for esem model - 4 factor with no outcome variables
esem1_model <- '
efa("efa")*f1 +
efa("efa")*f2 +
efa("efa")*f3 +
efa("efa")*f4 =~ cp_pes2 + cp_pes3 + cp_pes4 + cp_pes5 + cp_pes6 + cp_pes7 + cp_pes8 + cp_pes9 + cp_pes10 + cp_pes11 + cp_pes12 + cp_pes14 + cp_pes15 + cp_pes18 + cp_pes19 + cp_pes20 + cp_pes21 + cp_pes22 + cp_pes23 + cp_pes25 + cp_pes26 + cp_pes27 + cp_pes28 + cp_pes29 + cp_pes30 + cp_pes32 + cp_pes34 + cp_pes36 + cp_pes37 + cp_pes38 + cp_pes39’

esem1 <- sem(model = esem1_model,
             data = esem_dat,
             rotation = "oblimin",
             estimator = "MLR",
             missing = "ml",
             std.ov = TRUE)

summary(esem1, fit.measures = TRUE)

#Configural Invariance of multi-group version of esem model 1
m1.fit.configural <- sem(model = esem1_model,
             data = dat,
             rotation = "oblimin",
             estimator = "MLR",
             missing = "ml",
             std.ov = TRUE,
             group = "Timepoint")
summary(m1.fit.configural, fit.measures = TRUE)

For the Metric model group.equal = c("loadings")) is the final line in the above code that otherwise remains unchanged. For the Scalar model group.equal = c("loadings", "intercepts")) is the final line in the above code that also otherwise remains unchanged. And finally, for the Residual model group.equal = c("loadings", "intercepts", "residuals")) is the final line in the above code that otherwise remains unchanged. 

Again appreciate the help!

Best,
Lex

Mauricio Garnier-Villarreal

unread,
Nov 23, 2021, 3:57:32 AM11/23/21
to lavaan
Lex

I see, yes if your "time" variable are when the independent groups are collected but still different subjects then yes multiple group is approproate
Here is the previous example on how to do ESEM in lavaan https://msilvestrin.me/post/esem/#ref-asparouhov_exploratory_2009
For the ESEM structure you are missing the steps to setuo anchor parameters based on the EFA part.
Even though there is that example for lavaan, I havent seen multiple groups examples.
Also, will clarify that I am not an expert on these models, in part becasue I dont like them (also my bias on them), and another part of my bias is that I prefer Bayesian in general
So, my personal recommendation would still be to use Bayesian SEM, in blavaan (http://ecmerkle.github.io/blavaan/) you can have a lot of control of the cross-loadings and across groups.

On the other part, lavaan should equate the parameters across groups as specify. From the last model that doesnt constraint parameters properly for you, would recommend to look at the partable() of the model, to see if all the labels across groups are specify correctly. If some labels are not specify correctly we start looking where the possible bug might be. can you share the output of the model that doesnt work properly?

Yves Rosseel

unread,
Nov 26, 2021, 3:48:38 AM11/26/21
to lav...@googlegroups.com
Hello Lex,

I am afraid the group.equal= argument does not work (yet) in combination
with EFA rotation. Multiple group rotation is not simple, but several
approaches do exist. Unfortunately, none of them have been implemented
in lavaan yet. We plan to do this in the spring of 2022.

At the very least, lavaan should provide a warning message. Could you
please open an issue on github (https://github.com/yrosseel/lavaan) so
that I don't forget?

Thanks in advance,

Yves.
> into difficulty regarding the group.equalargument. I would
> really appreciate any help that others may be able to provide as
> I am still new to this. I have fit an ESEM model with 4 factors
> and I’m using them to predict several variables in a sample with
> N > 600. I want to test the model’s measurement invariance
> across two groups in order to see if it is possible to compare
> models across two timepoints. So far, I have attempted this via
> the code included below.
>
> After reviewing the output I had a few concerns. Particularly,
> that the group.equal argument may not be working properly when
> combined with ESEM. It appears to not be truly constraining to
> equality the loadings. Specifically, it’s only successfully
> constraining loadings for the Residual model and not for the
> Metric or Scalar models. Intercepts and residuals did not have
> this issue and were properly constrained when included in the
> argument for the appropriate models (indicated in code below).
>
> I am wondering if I am understanding the output properly and if
> this concern regarding the group.equalargument is valid? And if
> it is valid, how I might go about testing invariance in other ways?
>
>
> esem1_model <- '
> efa("efa")*f1 +
> efa("efa")*f2 +
> efa("efa")*f3 +
> efa("efa")*f4 =~ cp_pes2 + cp_pes3 + cp_pes4 + cp_pes5 + cp_pes6
> + cp_pes7 + cp_pes8 + cp_pes9 + cp_pes10 + cp_pes11 + cp_pes12 +
> cp_pes14 + cp_pes15 + cp_pes18 + cp_pes19 + cp_pes20 + cp_pes21
> + cp_pes22 + cp_pes23 + cp_pes25 + cp_pes26 + cp_pes27 +
> cp_pes28 + cp_pes29 + cp_pes30 + cp_pes32 + cp_pes34 + cp_pes36
> + cp_pes37 + cp_pes38 + cp_pes39’
>
>
> m1.fit.configural <- sem(model = esem1_model,
>              data = dat,
>              rotation = "oblimin",
>              estimator = "MLR",
>              missing = "ml",
>              std.ov = TRUE,
>              group = "Timepoint")
> summary(m1.fit.configural, fit.measures = TRUE)
>
> For the Metric model group.equal = c("loadings"))is the final
> line in the above code that otherwise remains unchanged. For the
> Scalar model group.equal = c("loadings", "intercepts"))is the
> final line in the above code that also otherwise remains
> unchanged. And finally, for the Residual model group.equal =
> c("loadings", "intercepts", "residuals"))is the final line in
> the above code that otherwise remains unchanged.
>
>
> I hope this makes sense and please do let me know if further
> clarification is needed to help. Thank you so much in advance!
>
> Best,
> Lex
>
> --
> You received this message because you are subscribed to the Google
> Groups "lavaan" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to lavaan+un...@googlegroups.com
> <mailto:lavaan+un...@googlegroups.com>.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/lavaan/f8327409-710a-43a0-94fe-3fa6574e340cn%40googlegroups.com
> <https://groups.google.com/d/msgid/lavaan/f8327409-710a-43a0-94fe-3fa6574e340cn%40googlegroups.com?utm_medium=email&utm_source=footer>.

Leon

unread,
Jan 29, 2022, 4:13:45 AM1/29/22
to lavaan
Hi all,

For those interested in a package that assists with implementing ESEM-within-CFA in lavaan. A beta version of a new package has been released that we hope will be helpful.
Please contribute or report any bugs or strange behaviour that you might encounter to the developers. 

Reply all
Reply to author
Forward
0 new messages