Search code examples
rr-lavaan

Lavaan implausible matrices reported using inspect()


For didactic purposes, I am simulating data consistent with a fixed effects model and then running the model in SEM using lavaan. I don't understand what is going on with the underlying matrices lavaan is giving me using the inspect() function.

Here is my covariance matrix for reproducibility purposes:

obs <- matrix( c( 2.1531960, 0.9219589, 1.1247727, 2.1399405, 1.6009631, 1.7877342,
                  0.9219589, 2.0183384, 1.1263892, 1.6586093, 2.0530063, 1.7664361,
                  1.1247727, 1.1263892, 1.9152931, 1.8183510, 1.7904083, 1.9676634,
                  2.1399405, 1.6586093, 1.8183510, 4.2225644, 3.0380404, 3.1018654,
                  1.6009631, 2.0530063, 1.7904083, 3.0380404, 4.0023862, 2.9819462,
                  1.7877342, 1.7664361, 1.9676634, 3.1018654, 2.9819462, 3.8469132), 
               nrow = 6, ncol = 6)

and here is my lavaan code:

fe_sem <- '
# Define individual effects variable 
n =~ 1*y1 + 1*y2 + 1*y3 
# Regressions, constrain coefficient to be equal over time
y1 ~ b*x1
y2 ~ b*x2 
y3 ~ b*x3
# Allow unrestricted correlation between eta and covariates
n ~~ x1 + x2 + x3 
x1 ~~ x2 + x3
x2 ~~ x3 
# Constrain residual variances to be equal over time
y1 ~~ e*y1
y2 ~~ e*y2
y3 ~~ e*y3
'
fe_sem.fit <- sem( model = fe_sem, sample.cov = obs, sample.nobs = 300)

Everything works fine. The estimated coefficient is correct given how I simulated the data. What I don't understand is how lavaan is getting the answer. When I use

inspect( fe_sem.fit)

the lambda matrix is a matrix of zeros:

## $lambda
##    n y1 y2 y3 x1 x2 x3
## y1 0  0  0  0  0  0  0
## y2 0  0  0  0  0  0  0
## y3 0  0  0  0  0  0  0
## x1 0  0  0  0  0  0  0
## x2 0  0  0  0  0  0  0
## x3 0  0  0  0  0  0  0

But I define the latent individual effects variable as n =~ 1*y1 + 1*y2 + 1*y3, so as far as I know, the lambda matrix should look like:

## $lambda
##    n y1 y2 y3 x1 x2 x3
## y1 1  1  0  0  0  0  0
## y2 1  0  1  0  0  0  0
## y3 1  0  0  1  0  0  0
## x1 0  0  0  0  1  0  0
## x2 0  0  0  0  0  1  0
## x3 0  0  0  0  0  0  1

I have double-checked this by hand. Having lambda as a matrix of zeros messes everything up. Does anyone know what is going on here? Is there something I don't understand about the matrix notation used by lavaan?


Solution

  • Note that inspect is an older function and now used as a shortcut to lavInspect. Further note the dimnames for the matrix.

    library(lavaan)
    
    obs <- matrix( c( 2.1531960, 0.9219589, 1.1247727, 2.1399405, 1.6009631, 1.7877342,
                      0.9219589, 2.0183384, 1.1263892, 1.6586093, 2.0530063, 1.7664361,
                      1.1247727, 1.1263892, 1.9152931, 1.8183510, 1.7904083, 1.9676634,
                      2.1399405, 1.6586093, 1.8183510, 4.2225644, 3.0380404, 3.1018654,
                      1.6009631, 2.0530063, 1.7904083, 3.0380404, 4.0023862, 2.9819462,
                      1.7877342, 1.7664361, 1.9676634, 3.1018654, 2.9819462, 3.8469132), 
                   nrow = 6, ncol = 6, dimnames = list(NULL, c(paste0(rep(c("x", "y"), each = 3), 1:3)))
                   )
    

    The output of lavInspect can be regulated with the argument what which defaults to "free". That is, by default, lavInspect gives you the free parameters -- since the loadings are constrained to 1, there are no free parameters. Using the most basic version of your MWE you'd get

    fe_sem <- "
    # Define individual effects variable 
    n =~ 1*y1 + 1*y2 + 1*y3 
    "
    
    fe_sem.fit <- sem( model = fe_sem, sample.cov = obs, sample.nobs = 300)
    lavInspect(fe_sem.fit)$lambda
    

    resulting in

    ##    n
    ## y1 0
    ## y2 0
    ## y3 0
    

    You can access the estimated parameters using what = "est".

    lavInspect(fe_sem.fit, what = "est")$lambda
    
    ##    n
    ## y1 1
    ## y2 1
    ## y3 1
    

    You can cross check that behaviour using n =~ y1 + y2 + y3. Finally, with the introduction of the regression statements the y variables became endogenous rather than measurement loadings. That is, for "loadings" on n you'd need to look at the beta matrix.

    fe_sem <- "
    # Define individual effects variable 
    n =~ 1*y1 + 1*y2 + 1*y3 
    # Regressions, constrain coefficient to be equal over time
    y1 ~ b*x1
    y2 ~ b*x2 
    y3 ~ b*x3
    # Allow unrestricted correlation between eta and covariates
    n ~~ x1 + x2 + x3 
    x1 ~~ x2 + x3
    x2 ~~ x3 
    # Constrain residual variances to be equal over time
    y1 ~~ e*y1
    y2 ~~ e*y2
    y3 ~~ e*y3
    "
    fe_sem.fit <- sem( model = fe_sem, sample.cov = obs, sample.nobs = 300)
    lavInspect(fe_sem.fit, what = "est")$lambda
    

    giving you

    ##    n y1 y2 y3 x1 x2 x3
    ## y1 0  1  0  0  0  0  0
    ## y2 0  0  1  0  0  0  0
    ## y3 0  0  0  1  0  0  0
    ## x1 0  0  0  0  1  0  0
    ## x2 0  0  0  0  0  1  0
    ## x3 0  0  0  0  0  0  1
    

    and

    lavInspect(fe_sem.fit, what = "est")$beta
    

    giving you

    ##    n y1 y2 y3    x1    x2    x3
    ## n  0  0  0  0 0.000 0.000 0.000
    ## y1 1  0  0  0 0.326 0.000 0.000
    ## y2 1  0  0  0 0.000 0.326 0.000
    ## y3 1  0  0  0 0.000 0.000 0.326
    ## x1 0  0  0  0 0.000 0.000 0.000
    ## x2 0  0  0  0 0.000 0.000 0.000
    ## x3 0  0  0  0 0.000 0.000 0.000