Difference in latent variable means in multigroup analysis

1,923 views
Skip to first unread message

Sarah Miles

unread,
Feb 13, 2013, 4:07:29 PM2/13/13
to lav...@googlegroups.com
Hello,

I would like to do a multigroup analysis where I show that a model in which my latent variable means are set to be equal across groups fits worse than a model in which latent variable means are allowed to vary. The issue is that I have four latent variables in my model but I only want to constrain the means of 3 of them, and always let the 4th mean (FR) vary. Here is my model:

MyModel<-'
Inhibit <- I1 + I2 + I3 
Update <- U1 + U2 + U3
Switch <- S1 + S2 + S3
FR<- FR1 + FR2 + FR3 + FR4'

I have bee able to free all latent variable means using:

fit <- cfa (MyModel, data=data, missing = "ML", group="FRcorrect4", group.equal=c("loadings", "intercepts"))

But I am having trouble constraining the Inhibit, Update and Switch means to be equal across groups. Is there an easy way to do this?


Thanks,

Sarah

Terrence Jorgensen

unread,
Feb 13, 2013, 4:54:49 PM2/13/13
to lav...@googlegroups.com
 Sarah,

You can use identical labels to constrain parameters.  The "group.equal" argument is helpful when you want to make the same constraint across (e.g.) all all intercepts for all groups.  I'm not familiar with identification rules for formative indicators (did you mean to use traditional measurement with the "=~" operator?), but your syntax might look like this:


MyModel<-'
Inhibit <- I1 + I2 + I3 
Update <- U1 + U2 + U3
Switch <- S1 + S2 + S3
FR<- FR1 + FR2 + FR3 + FR4

## selected mean constraints
Inhibit ~ c(mean1, mean1)*1
Update ~ c(mean2, mean2)*1
Switch ~ c(mean3, mean3)*1
'

fit <- cfa (MyModel, data=data, missing = "ML", group="FRcorrect4", group.equal=c("loadings", "intercepts"))

This is assuming your "FRcorrect4" variable has only 2 levels.  If there are K levels, you would need to repeat the labels in parentheses K times instead of 2 times.  Any labels that are different are unique freely estimated parameters, so you can also allow 1 of 3 groups to be different by (e.g.) "Inhibit ~ c(mean1, mean2, mean2)*1"

Terry

Sarah Miles

unread,
Feb 13, 2013, 8:42:25 PM2/13/13
to lav...@googlegroups.com
Hi Terry,

Thanks for the quick reply. When I run the code as you suggest I get an error message (below). The model converges after 425 iterations but standard errors are not estimated. Could this have to do with the fact that my groups are small and unbalanced (40 and 133 observations)? I get the same error message even if I constrain only one set of means to be equal.

Thanks again for the help,
Sarah


Error in solve.default(E) : 

  system is computationally singular: reciprocal condition number = 8.99305e-19

Warning message:

In estimateVCOV(lavaanModel, samplestats = lavaanSampleStats, options = lavaanOptions,  :

  lavaan WARNING: could not compute standard errors!

yrosseel

unread,
Feb 14, 2013, 9:15:19 AM2/14/13
to lav...@googlegroups.com
On 02/14/2013 02:42 AM, Sarah Miles wrote:
> Hi Terry,
>
> Thanks for the quick reply. When I run the code as you suggest I get an
> error message (below). The model converges after 425 iterations but
> standard errors are not estimated. Could this have to do with the fact
> that my groups are small and unbalanced (40 and 133 observations)? I get
> the same error message even if I constrain only one set of means to be
> equal.

If you must constrain the means to be equal across all groups, you
should fix them to zero. The means of the first group can not be
estimated (without further constraints).

So using this call:

fit <- cfa (MyModel, data=data, missing = "ML", group="FRcorrect4",
group.equal=c("loadings", "intercepts"))

the means in the first group are fixed to zero, the means in the other
groups are estimated.

If you request 'equal means':

fit <- cfa (MyModel, data=data, missing = "ML", group="FRcorrect4",
group.equal=c("loadings", "intercepts","means"))

all means will be zero. But if you specify something like

Inhibit ~ c(m1,m1)~1

you will free up the mean in the first group, while it is not identified
(causing the Error in solve.default(E)...)

If you have more than 2 groups, you could constrain the (estimated)
means of the remaining groups to be equal, as long as the first one
remains zero, eg

Inhibit ~ c(0,m1,m1,m1)*1


Hope this helps,

Yves.


Sarah Miles

unread,
Feb 14, 2013, 10:13:53 AM2/14/13
to lav...@googlegroups.com
Hi Yves,

Now that I've requested 'equal means', is it possible to free one set of latent variable means and still have an identified model? For example, for group 1 FR mean = 0 and for group 2 FR mean free to vary. I've tried to specify this using 

fit <- cfa (MyModel, data=data, missing = "ML", group="FRcorrect4", 
group.equal=c("loadings", "intercepts","means"), groups.partial=c("FR~1")) 

but both group means come out at 0. I apologize if this is a very basic question, this is my first time doing SEM and I am still trying to wrap my head around some of the concepts. 

Thanks again,
Sarah

On Wednesday, February 13, 2013 4:07:29 PM UTC-5, Sarah Miles wrote:

Terrence Jorgensen

unread,
Feb 14, 2013, 10:24:25 AM2/14/13
to lav...@googlegroups.com
Sarah,

In that case, use Yves' final example to set (at least) 1 group's latent mean to zero, then allow others to be free:

Inhibit ~ c(0,m1,m1,m1)*1

The subsequent groups can have any pattern of constraints, for instance, groups 1 and 3 can be equal, and groups 2 and 4 could be equal but have a different (freely estimated) mean than groups 1 and 3:

Inhibit ~ c(0,m1,0,m1)*1

Terry

Sarah Miles

unread,
Feb 14, 2013, 10:30:27 AM2/14/13
to lav...@googlegroups.com
Sorry, I should have mentioned in my last post that I did try that strategy (FR ~ c(0, m1)*1, since I only have two groups) and I get the same error message that the 'system is computationally singular'.


On Wednesday, February 13, 2013 4:07:29 PM UTC-5, Sarah Miles wrote:

Terrence Jorgensen

unread,
Feb 14, 2013, 10:40:28 AM2/14/13
to lav...@googlegroups.com
I wouldn't expect that to be a problem if you are constraining intercepts to equality in the same model, but I can't see exactly what you did.  When something doesn't work, could you post your entire model syntax along with the function call?  e.g.
 
myModel <- "
f1 =~ x1 + x2 + x3
"
fit <- sem(myModel, data = myData)
 
It's possible that (as you mentioned earlier) the small n = 40 of one of your groups is problematic, but it's better to be sure it isn't a code problem before blaming the data.
 
Terry

Sarah Miles

unread,
Feb 14, 2013, 10:49:46 AM2/14/13
to lav...@googlegroups.com
Thanks. Here is my model syntax:

MyModel<-'
Inhibit =~ I1 + I2 + I3 
Update =~ U1 + U2 + U3
Switch =~ S1 + S2 + S3
FR =~ FR1 + FR2 + FR3 + FR4
FR ~ c(0, m1)*1'

fit <- cfa (MyModel, data=data, missing = "ML", group="FRcorrect4", group.equal=c("loadings", "intercepts","means")) 

Terrence Jorgensen

unread,
Feb 14, 2013, 11:03:13 AM2/14/13
to lav...@googlegroups.com
I'd be interested to hear if Yves has a different conclusion, but I think the problem is that you are using the default "std.lv = FALSE" to identify the model, but you are also fixing the latent mean to zero.  In other words, you are identifying the scale both by fixing the first indicator intercept to zero (which is the default) and by fixing the latent mean of FR in group 1 to zero.  This might not be the problem, but I would be interested if the problem disappears if you use the fixed-factor method of identification.  Try adding the std.lv = TRUE argument to your call to cfa():
 
fit <- cfa (MyModel, data=data, missing = "ML", group="FRcorrect4", group.equal=c("loadings", "intercepts","means"), std.lv = TRUE)
 
That might fix the problem, but it might not be the problem at all.  It depends how close the indicator mean is to the true-score mean.  By fixing the first indicator intercept to zero and the latent mean to zero, I think you are fitting a model that says your indicator has the exact same mean as the latent variable, so if the indicator and latent construct have noticeably different means, then this constraint may cause problems. 
 
Terry

Sarah Miles

unread,
Feb 14, 2013, 11:31:02 AM2/14/13
to lav...@googlegroups.com
Hi Terry,

Running :

fit <- cfa (MyModel, data=data, missing = "ML", group="FRcorrect4", group.equal=c("loadings", "intercepts","means"), std.lv = TRUE)

still gives the error message. Another strange (I think) thing is that the m1 label is being applied to the FR intercept in both groups, so that both have the same value, despite specifying FR ~ c(0, m1)*1. This is not unique to using std.lv=TRUE, it happens when std.lv=FALSE too.

Sarah

yrosseel

unread,
Feb 14, 2013, 11:39:39 AM2/14/13
to lav...@googlegroups.com
On 02/14/2013 05:31 PM, Sarah Miles wrote:
> Hi Terry,
>
> Running :
>
> fit <- cfa (MyModel, data=data, missing = "ML",
> group="FRcorrect4", group.equal=c("loadings", "intercepts","means"),
> std.lv <http://std.lv/> = TRUE)
>
> still gives the error message. Another strange (I think) thing is that
> the m1 label is being applied to the FR intercept in both groups, so
> that both have the same value, despite specifying FR ~ c(0, m1)*1. This
> is not unique to using std.lv=TRUE, it happens when std.lv=FALSE too.

Yes, of course, this is my mistake. I wrote in a previous message:

Inhibit ~ c(0,m1,m1,m1)*1

but in fact, this is (currently) illegal syntax. I should know better,
but it looked so natural that I may try to make it legal in a future
version. Anyway, currently (0.5-11/12), you can not mix labels and
values. So, the correct syntax here would be:

Inhibit ~ c(m0,m1,m1,m1)*1 + c(0,NA,NA,NA)*1

where the first modifier sets the labels, and the second modifier sets
the values (and 'NA' means 'free')

But since you only have two groups, there is no need to use labels. We
just need to 'free' the mean of 'FR' in the second group, right? What
about this:

MyModel<-'
Inhibit =~ I1 + I2 + I3
Update =~ U1 + U2 + U3
Switch =~ S1 + S2 + S3
FR =~ FR1 + FR2 + FR3 + FR4
Inhibit ~ c(0,0)*1
Update ~ c(0,0)*1
Switch ~ c(0,0)*1
FR ~ c(0, NA)*1'

fit <- cfa (MyModel, data=data, missing = "ML", group="FRcorrect4",
group.equal=c("loadings", "intercepts"))

Yves.

Terrence Jorgensen

unread,
Feb 14, 2013, 11:46:16 AM2/14/13
to lav...@googlegroups.com

On Thursday, February 14, 2013 10:39:39 AM UTC-6, Yves Rosseel wrote:

But since you only have two groups, there is no need to use labels. We
just need to 'free' the mean of 'FR' in the second group, right? What
about this:

MyModel<-'
Inhibit =~ I1 + I2 + I3
Update =~ U1 + U2 + U3
Switch =~ S1 + S2 + S3
FR =~ FR1 + FR2 + FR3 + FR4
Inhibit ~ c(0,0)*1
Update ~ c(0,0)*1
Switch ~ c(0,0)*1
FR ~ c(0, NA)*1'

fit <- cfa (MyModel, data=data, missing = "ML", group="FRcorrect4",
group.equal=c("loadings", "intercepts"))

Yves.

 
Yves, would she still need to add the "std.lv = TRUE" argument to the cfa() call so that the first intercept is not constrained to 0 along with the latent means?
 
Terry 

Sarah Miles

unread,
Feb 14, 2013, 11:58:35 AM2/14/13
to lav...@googlegroups.com
Yves and Terry,

Thanks to both of you for all the help, the model is working. Both std.lv=TRUE and std.lv=FALSE work.

Cheers,
Sarah


On Wednesday, February 13, 2013 4:07:29 PM UTC-5, Sarah Miles wrote:

Terrence Jorgensen

unread,
Feb 14, 2013, 12:36:08 PM2/14/13
to lav...@googlegroups.com
Good news!  For the different settings of "std.lv", I imagine you get 2 different degrees of freedom (should be lower for std.lv = TRUE if that allows the first intercept in each factor to be free).  Just be careful you are testing the hypotheses you intend to test (i.e., if the latent mean is fixed to 0 AND the first indicator intercept is fixed to zero, you are claiming that the observed mean of that indicator is exactly zero, which I doubt is the case if you aren't using mean-centered data).
 
My advisor wrote an article that illustrates the steps of invariance testing and comparing latent means/variances/correlations with an applied example.  It helped make the rationale behind identification methods (and when to fix/free certain parameters) more concrete when I first started learning SEM.  Here's a link, if you think it would be helpful
 
 
Terry
Message has been deleted

Salvador Ruiz

unread,
Mar 11, 2016, 2:51:46 PM3/11/16
to lavaan
Hi Yves,

Thanks a lot for the contribution. 
I am also working with differences in means of latent variables 
Just a short question, although with some time difference from your contribution :-) 
If I check for the scalar invariance with 

scalarinva <- sem(CFA1, data = mydata, estimator = "MLM", group = "V1", 
                  group.equal=c("loadings", "intercepts"), std.lv = TRUE)  

and then I check for equal means with 

equalmeans <- sem(CFA1, data = mydata, estimator = "MLM", group = "V1", 
                  group.equal=c("loadings", "intercepts", "means"),
                  std.lv=TRUE)  
And I see that the anova test is significant with

anova(scalarinva, equalmeans)

When I want to check  which of the means are different and I follow your advice, I run

CFA1b <- ' 
LV1 =~ P1.1 + P1.2 + P1.3
LV2 =~ P2.1 + P2.2 + P2.3
LV3 =~ P3.1 + P3.2 + P3.3 + P3.4
LV1 ~ c(0,NA)*1 
LV2 ~ c(0,0)*1 
LV3 ~ c(0,0)*1 
'
fitEqualLV1 <- sem(CFA1b, data = mydata, estimator = "MLM", group = "V1", 
                group.equal=c("loadings", "intercepts"),
                std.lv=TRUE)


My question is
Should I compare this result with that of the scalar invariance

anova(scalarinva, fitEqualLV1)

or with the equal means

anova(equalmeans, fitEqualLV1)

?

I think it should be compared with the scalar invariance, but I would like to know your opinion

Thanks a lot 

Salvador

Terrence Jorgensen

unread,
Mar 12, 2016, 8:02:42 AM3/12/16
to lavaan
You can do either or both, depending on which research question you want to answer.  The model equalmeans is nested within fitEqualLV1, which is nested within scalarinva.  So could even put all three in the anova() function, or a pair at a time.  In either case, you are testing the null hypothesis that the constraints in the more-constrainted model (with more df) hold in the population.  So 

anova(scalarinva, fitEqualLV1)

tests the null hypothesis that the second and third means are equal across groups.  By contrast, 

anova(equalmeans, fitEqualLV1)

already assumes those means are equal (because the constraint exists in both models), so you are testing the null hypothesis that the first mean is equal across groups.

Terry


Reply all
Reply to author
Forward
0 new messages