yrosseel/lavaan

`int.ov.free=FALSE` has no effect with `cfa()` but intended effect with `lavaan()`

juliuspfadt opened this issue · 6 comments

First things first, lavaan is great :) Thanks for that.

I am uncertain if it is a bug or not, but in a multi group CFA with estimated intercepts I would like to fix the manifest intercepts to 0, and let the latent intercepts run free. I thought the following code would give me what I want but it does not:

library(lavaan)

HS.model <- ' visual  =~ x1 + x2 + x3
              textual =~ x4 + x5 + x6
              speed   =~ x7 + x8 + x9 '

fit <- cfa(HS.model,
           data = HolzingerSwineford1939, 
           group = "school",
           meanstructure = TRUE,
           int.ov.free = FALSE,
           int.lv.free = TRUE,
           std.lv = FALSE,
           auto.fix.first = TRUE,
           auto.fix.single = TRUE,
           auto.var = TRUE,
           auto.cov.lv.x = TRUE,
           auto.cov.y = TRUE,
           auto.th = TRUE,
           auto.delta = TRUE,
           auto.efa = TRUE)

summary(fit)

What does work is:

library(lavaan)

HS.model <- ' visual  =~ x1 + x2 + x3
              textual =~ x4 + x5 + x6
              speed   =~ x7 + x8 + x9 '

fit <- lavaan(HS.model,
           data = HolzingerSwineford1939, 
           group = "school",
           meanstructure = TRUE,
           int.ov.free = FALSE,
           int.lv.free = TRUE,
           std.lv = FALSE,
           auto.fix.first = TRUE,
           auto.fix.single = TRUE,
           auto.var = TRUE,
           auto.cov.lv.x = TRUE,
           auto.cov.y = TRUE,
           auto.th = TRUE,
           auto.delta = TRUE,
           auto.efa = TRUE)

summary(fit)

That is because cfa() is a wrapper that sets those arguments for you. So your own choices are overwritten. You can print cfa at your R Console to see what happens before cfa() internally calls lavaan(), namely:

    mc$int.ov.free = TRUE
    mc$int.lv.free = FALSE

The std.lv argument is assigned conditional on whether they are already specified in dotdotdot.
Perhaps the remaining assignments below it can also be made conditional.

Thanks for your reply. I get what you are saying. What I wonder now is why the choice was made to overwrite these arguments?

That's what cfa() and sem() are for: to make decisions for you, so that model specification is simpler, using common defaults that can be overwritten by the model syntax itself. But I agree sometimes setting a subset of those arguments to different values would make things easier than adding elements to model syntax, or going all the way to lavaan(), which would require setting more of them (e.g., auto.var=TRUE and auto.cov.lv.x = TRUE) that are already defaults in cfa().

Agreed. Well, if you do not consider this a bug, you may just close this. Either way is fine with me.

I'd leave the issue open to see how Yves feels about it.
Yves, I can work on a pull request if you don't see a problem with this.

I gave this some thought, and decided to change the current behavior. The cfa/sem/growth functions set the defaults, but they cannot be changed. That used to be clear when all the arguments were listed in the man page of cfa/sem/growth/lavaan. But at some point, we moved many arguments to the '...' (dot dot dot), and they are documented in the man page of lavOptions. Because of this, the documentation is now dubious at best (about this specific behavior).

In lavaan 0.6-17, you will be able to pass the arguments int.lv.free= and int.ov.free= (etc) to the cfa/sem/growth function and change their default setting if needed. If they are not specified, they retain their default values.