yrosseel / lavaan

an R package for structural equation modeling and more
http://lavaan.org
429 stars 98 forks source link

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

Closed juliuspfadt closed 9 months ago

juliuspfadt commented 11 months ago

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)
TDJorgensen commented 11 months ago

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.

juliuspfadt commented 11 months ago

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

TDJorgensen commented 11 months ago

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().

juliuspfadt commented 11 months ago

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

TDJorgensen commented 11 months ago

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.

yrosseel commented 9 months ago

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.