This example comes from Chapter 4 of Beaujean (2014), Latent Variable Models with Multiple Groups. The example examines if the structure of the WISC-III scale is the same in children with and without manic symptoms. Source

Load the package we need.

library(lavaan)
## This is lavaan 0.6-19
## lavaan is FREE software! Please report any bugs.

Read in data

The data is manually entered as covariance matrices. This is not normally how you would enter data in R. Notice we enter two separate covariance matrices: one for children with manic symptoms and those without.

# variable names
wisc3.names <- c("Info", "Sim", "Vocab","Comp", 
                 "PicComp", "PicArr", "BlkDsgn", "ObjAsmb")

# covariance for group with manic symptoms
manic.cov <- c(9.364, 7.777, 12.461, 6.422, 8.756, 10.112, 
               5.669, 7.445, 6.797, 8.123, 3.048, 4.922, 
               4.513, 4.116, 6.200, 3.505, 4.880, 4.899, 
               5.178, 5.114, 15.603, 3.690, 5.440, 5.220, 
               3.151, 3.587, 6.219, 11.223, 3.640, 4.641, 
               4.877, 3.568, 3.819, 5.811, 6.501, 9.797)

# lavaan function to create a covariance matrix
manic.cov <- lav_matrix_lower2full(manic.cov)

# means of the eight variables
manic.means <- c(10.09, 12.07, 10.25, 9.96, 10.90, 11.24, 10.30, 10.44)

# label the covariances and means
colnames(manic.cov) <- rownames(manic.cov)  <- wisc3.names
names(manic.means) <- wisc3.names

# preview the covariance matrix
manic.cov
##          Info    Sim  Vocab  Comp PicComp PicArr BlkDsgn ObjAsmb
## Info    9.364  7.777  6.422 5.669   3.048  3.505   3.690   3.640
## Sim     7.777 12.461  8.756 7.445   4.922  4.880   5.440   4.641
## Vocab   6.422  8.756 10.112 6.797   4.513  4.899   5.220   4.877
## Comp    5.669  7.445  6.797 8.123   4.116  5.178   3.151   3.568
## PicComp 3.048  4.922  4.513 4.116   6.200  5.114   3.587   3.819
## PicArr  3.505  4.880  4.899 5.178   5.114 15.603   6.219   5.811
## BlkDsgn 3.690  5.440  5.220 3.151   3.587  6.219  11.223   6.501
## ObjAsmb 3.640  4.641  4.877 3.568   3.819  5.811   6.501   9.797

Now do the same for the children without manic symptoms.

# covariance for group without manic symptoms
norming.cov <- c(9.610, 5.844, 8.410, 6.324, 6.264, 9.000, 
                 4.405, 4.457, 5.046, 8.410, 4.464,  4.547, 
                 4.512, 3.712, 10.240, 3.478, 2.967, 2.970, 
                 2.871, 3.802,  10.890, 5.270, 4.930, 4.080, 
                 3.254, 5.222, 3.590, 11.560, 4.297, 4.594, 
                 4.356, 3.158, 4.963, 3.594, 6.620, 10.890)
norming.cov <- lav_matrix_lower2full(norming.cov) 

# means
norming.means <- c(10.10, 10.30, 9.80, 10.10, 10.10, 10.10, 9.90, 10.20)

# label the covariances and means
colnames(norming.cov) <- rownames(norming.cov)  <- wisc3.names
names(norming.means) <- wisc3.names

# preview the covariance matrix
norming.cov
##          Info   Sim Vocab  Comp PicComp PicArr BlkDsgn ObjAsmb
## Info    9.610 5.844 6.324 4.405   4.464  3.478   5.270   4.297
## Sim     5.844 8.410 6.264 4.457   4.547  2.967   4.930   4.594
## Vocab   6.324 6.264 9.000 5.046   4.512  2.970   4.080   4.356
## Comp    4.405 4.457 5.046 8.410   3.712  2.871   3.254   3.158
## PicComp 4.464 4.547 4.512 3.712  10.240  3.802   5.222   4.963
## PicArr  3.478 2.967 2.970 2.871   3.802 10.890   3.590   3.594
## BlkDsgn 5.270 4.930 4.080 3.254   5.222  3.590  11.560   6.620
## ObjAsmb 4.297 4.594 4.356 3.158   4.963  3.594   6.620  10.890

Finally, combine the covariance matrices, sample sizes, and means into single list objects.

combined.cov <- list(manic = manic.cov, norming = norming.cov)
combined.n <- list(manic = 81, norming = 200)
combined.means <- list(manic = manic.means, norming = norming.means)

Now ready to specify the CFA model.

CFA Model

The model below says there are two factors: Verbal-Comprehension (VC) and Visual-Spatial (VS). The model hypothesizes these two factors are influencing four variables each. The model also says we want to estimate the covariance between the two factors (last line).

wisc3.model <-'
VC =~ Info + Sim + Vocab + Comp 
VS =~ PicComp + PicArr + BlkDsgn + ObjAsmb
VC ~~ VS
'

We’ll also define a vector of fit indices so we can easily request them after fitting a model.

# specify fit indices of interest
fit.indices <- c("chisq", "df", "cfi", "rmsea", "srmr", "mfi")

Configural Invariance

This fits the same model to both groups and allows all parameters to be freely estimated. In other words, we’re doing a CFA with each group. Notice in the summary output each group’s parameters are different.

configural.fit <- cfa(wisc3.model, 
                      sample.cov = combined.cov,
                      sample.nobs = combined.n, 
                      sample.mean = combined.means) 
summary(configural.fit)
## lavaan 0.6-19 ended normally after 93 iterations
## 
##   Estimator                                         ML
##   Optimization method                           NLMINB
##   Number of model parameters                        50
## 
##   Number of observations per group:                   
##     manic                                           81
##     norming                                        200
## 
## Model Test User Model:
##                                                       
##   Test statistic                                53.380
##   Degrees of freedom                                38
##   P-value (Chi-square)                           0.050
##   Test statistic for each group:
##     manic                                       29.169
##     norming                                     24.211
## 
## Parameter Estimates:
## 
##   Standard errors                             Standard
##   Information                                 Expected
##   Information saturated (h1) model          Structured
## 
## 
## Group 1 [manic]:
## 
## Latent Variables:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC =~                                               
##     Info              1.000                           
##     Sim               1.330    0.153    8.687    0.000
##     Vocab             1.189    0.138    8.613    0.000
##     Comp              1.015    0.125    8.129    0.000
##   VS =~                                               
##     PicComp           1.000                           
##     PicArr            1.437    0.274    5.246    0.000
##     BlkDsgn           1.322    0.234    5.641    0.000
##     ObjAsmb           1.285    0.220    5.830    0.000
## 
## Covariances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC ~~                                               
##     VS                3.086    0.772    3.997    0.000
## 
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info             10.090    0.338   29.861    0.000
##    .Sim              12.070    0.390   30.965    0.000
##    .Vocab            10.250    0.351   29.191    0.000
##    .Comp              9.960    0.315   31.648    0.000
##    .PicComp          10.900    0.275   39.643    0.000
##    .PicArr           11.240    0.436   25.769    0.000
##    .BlkDsgn          10.300    0.370   27.843    0.000
##    .ObjAsmb          10.440    0.346   30.206    0.000
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info              3.742    0.673    5.564    0.000
##    .Sim               2.564    0.605    4.237    0.000
##    .Vocab             2.200    0.502    4.379    0.000
##    .Comp              2.348    0.467    5.027    0.000
##    .PicComp           2.926    0.592    4.945    0.000
##    .PicArr            8.806    1.631    5.398    0.000
##    .BlkDsgn           5.497    1.089    5.046    0.000
##    .ObjAsmb           4.399    0.916    4.804    0.000
##     VC                5.507    1.369    4.024    0.000
##     VS                3.198    0.924    3.462    0.001
## 
## 
## Group 2 [norming]:
## 
## Latent Variables:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC =~                                               
##     Info              1.000                           
##     Sim               0.997    0.079   12.607    0.000
##     Vocab             1.045    0.082   12.778    0.000
##     Comp              0.768    0.083    9.301    0.000
##   VS =~                                               
##     PicComp           1.000                           
##     PicArr            0.715    0.122    5.854    0.000
##     BlkDsgn           1.149    0.135    8.542    0.000
##     ObjAsmb           1.100    0.130    8.464    0.000
## 
## Covariances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC ~~                                               
##     VS                4.103    0.661    6.204    0.000
## 
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info             10.100    0.219   46.192    0.000
##    .Sim              10.300    0.205   50.355    0.000
##    .Vocab             9.800    0.212   46.314    0.000
##    .Comp             10.100    0.205   49.377    0.000
##    .PicComp          10.100    0.226   44.748    0.000
##    .PicArr           10.100    0.233   43.392    0.000
##    .BlkDsgn           9.900    0.240   41.282    0.000
##    .ObjAsmb          10.200    0.233   43.822    0.000
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info              3.609    0.455    7.928    0.000
##    .Sim               2.450    0.354    6.925    0.000
##    .Vocab             2.453    0.370    6.635    0.000
##    .Comp              4.857    0.533    9.114    0.000
##    .PicComp           5.426    0.675    8.034    0.000
##    .PicArr            8.398    0.897    9.364    0.000
##    .BlkDsgn           5.215    0.716    7.279    0.000
##    .ObjAsmb           5.069    0.682    7.434    0.000
##     VC                5.953    0.928    6.416    0.000
##     VS                4.763    0.952    5.006    0.000

Fit indices look good.

fitMeasures(configural.fit, fit.indices)
##  chisq     df    cfi  rmsea   srmr    mfi 
## 53.380 38.000  0.985  0.054  0.034  0.973

And the residuals are small. Notice everything is smaller than 2. That’s good.

residuals(configural.fit, type = "normalized")
## $manic
## $manic$type
## [1] "normalized"
## 
## $manic$cov
##           Info    Sim  Vocab   Comp PicCmp PicArr BlkDsg ObjAsm
## Info     0.000                                                 
## Sim      0.244  0.000                                          
## Vocab   -0.161 -0.040  0.000                                   
## Comp     0.008 -0.060  0.053  0.000                            
## PicComp -0.084  0.685  0.788  1.036  0.000                     
## PicArr  -0.704 -0.666 -0.294  0.450  0.374  0.000              
## BlkDsgn -0.363 -0.037  0.234 -0.932 -0.687  0.042  0.000       
## ObjAsmb -0.328 -0.524  0.085 -0.474 -0.353 -0.110  0.732  0.000
## 
## $manic$mean
##    Info     Sim   Vocab    Comp PicComp  PicArr BlkDsgn ObjAsmb 
##       0       0       0       0       0       0       0       0 
## 
## 
## $norming
## $norming$type
## [1] "normalized"
## 
## $norming$cov
##           Info    Sim  Vocab   Comp PicCmp PicArr BlkDsg ObjAsm
## Info     0.000                                                 
## Sim     -0.160  0.000                                          
## Vocab    0.090  0.039  0.000                                   
## Comp    -0.268 -0.184  0.343  0.000                            
## PicComp  0.442  0.596  0.270  0.771  0.000                     
## PicArr   0.691  0.037 -0.154  0.857  0.476  0.000              
## BlkDsgn  0.639  0.265 -1.121 -0.523 -0.326 -0.413  0.000       
## ObjAsmb -0.306  0.093 -0.504 -0.458 -0.369 -0.214  0.617  0.000
## 
## $norming$mean
##    Info     Sim   Vocab    Comp PicComp  PicArr BlkDsgn ObjAsmb 
##       0       0       0       0       0       0       0       0

This is a good fitting model for each group.

Weak Invariance

Now constrain loadings to be equal between groups. Same code as above with one additional argument: group.equal = "loadings". Notice in the summary output that all the loadings (listed under Latent Variables) are equal in both groups.

weak.fit <- cfa(wisc3.model, 
                sample.cov = combined.cov, 
                sample.nobs = combined.n, 
                sample.mean = combined.means, 
                group.equal = "loadings") 
summary(weak.fit)
## lavaan 0.6-19 ended normally after 94 iterations
## 
##   Estimator                                         ML
##   Optimization method                           NLMINB
##   Number of model parameters                        50
##   Number of equality constraints                     6
## 
##   Number of observations per group:                   
##     manic                                           81
##     norming                                        200
## 
## Model Test User Model:
##                                                       
##   Test statistic                                65.992
##   Degrees of freedom                                44
##   P-value (Chi-square)                           0.018
##   Test statistic for each group:
##     manic                                       37.832
##     norming                                     28.160
## 
## Parameter Estimates:
## 
##   Standard errors                             Standard
##   Information                                 Expected
##   Information saturated (h1) model          Structured
## 
## 
## Group 1 [manic]:
## 
## Latent Variables:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC =~                                               
##     Info              1.000                           
##     Sim     (.p2.)    1.105    0.073   15.193    0.000
##     Vocab   (.p3.)    1.094    0.071   15.298    0.000
##     Comp    (.p4.)    0.864    0.068   12.720    0.000
##   VS =~                                               
##     PicComp           1.000                           
##     PicArr  (.p6.)    0.888    0.118    7.554    0.000
##     BlkDsgn (.p7.)    1.217    0.121   10.089    0.000
##     ObjAsmb (.p8.)    1.174    0.116   10.148    0.000
## 
## Covariances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC ~~                                               
##     VS                3.865    0.845    4.571    0.000
## 
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info             10.090    0.363   27.815    0.000
##    .Sim              12.070    0.373   32.397    0.000
##    .Vocab            10.250    0.358   28.639    0.000
##    .Comp              9.960    0.306   32.548    0.000
##    .PicComp          10.900    0.287   37.996    0.000
##    .PicArr           11.240    0.403   27.915    0.000
##    .BlkDsgn          10.300    0.373   27.646    0.000
##    .ObjAsmb          10.440    0.346   30.170    0.000
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info              3.739    0.694    5.386    0.000
##    .Sim               2.787    0.589    4.733    0.000
##    .Vocab             2.097    0.494    4.242    0.000
##    .Comp              2.419    0.464    5.217    0.000
##    .PicComp           2.831    0.599    4.725    0.000
##    .PicArr           10.109    1.678    6.024    0.000
##    .BlkDsgn           5.564    1.079    5.158    0.000
##    .ObjAsmb           4.417    0.895    4.936    0.000
##     VC                6.920    1.362    5.081    0.000
##     VS                3.835    0.877    4.371    0.000
## 
## 
## Group 2 [norming]:
## 
## Latent Variables:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC =~                                               
##     Info              1.000                           
##     Sim     (.p2.)    1.105    0.073   15.193    0.000
##     Vocab   (.p3.)    1.094    0.071   15.298    0.000
##     Comp    (.p4.)    0.864    0.068   12.720    0.000
##   VS =~                                               
##     PicComp           1.000                           
##     PicArr  (.p6.)    0.888    0.118    7.554    0.000
##     BlkDsgn (.p7.)    1.217    0.121   10.089    0.000
##     ObjAsmb (.p8.)    1.174    0.116   10.148    0.000
## 
## Covariances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC ~~                                               
##     VS                3.607    0.558    6.461    0.000
## 
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info             10.100    0.212   47.646    0.000
##    .Sim              10.300    0.209   49.298    0.000
##    .Vocab             9.800    0.209   46.810    0.000
##    .Comp             10.100    0.209   48.367    0.000
##    .PicComp          10.100    0.221   45.728    0.000
##    .PicArr           10.100    0.241   41.952    0.000
##    .BlkDsgn           9.900    0.239   41.391    0.000
##    .ObjAsmb          10.200    0.233   43.843    0.000
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info              3.762    0.452    8.329    0.000
##    .Sim               2.346    0.352    6.660    0.000
##    .Vocab             2.516    0.362    6.953    0.000
##    .Comp              4.821    0.532    9.056    0.000
##    .PicComp           5.568    0.662    8.415    0.000
##    .PicArr            8.290    0.905    9.155    0.000
##    .BlkDsgn           5.238    0.705    7.432    0.000
##    .ObjAsmb           5.055    0.671    7.535    0.000
##     VC                5.225    0.770    6.783    0.000
##     VS                4.189    0.777    5.389    0.000

According to fit measures this is also a good model.

fitMeasures(weak.fit, fit.indices)
##  chisq     df    cfi  rmsea   srmr    mfi 
## 65.992 44.000  0.979  0.060  0.055  0.962

And the residuals look good as well, though they are getting bigger.

residuals(weak.fit, type = "normalized")
## $manic
## $manic$type
## [1] "normalized"
## 
## $manic$cov
##           Info    Sim  Vocab   Comp PicCmp PicArr BlkDsg ObjAsm
## Info    -0.971                                                 
## Sim      0.021  0.550                                          
## Vocab   -0.958  0.180 -0.248                                   
## Comp    -0.333  0.541  0.140  0.347                            
## PicComp -0.949  0.533  0.230  0.807 -0.564                     
## PicArr   0.022  0.633  0.734  1.581  1.353  0.941              
## BlkDsgn -0.885  0.122  0.009 -0.862 -1.128  1.245 -0.091       
## ObjAsmb -0.836 -0.327 -0.118 -0.374 -0.765  1.163  0.697 -0.015
## 
## $manic$mean
##    Info     Sim   Vocab    Comp PicComp  PicArr BlkDsgn ObjAsmb 
##       0       0       0       0       0       0       0       0 
## 
## 
## $norming
## $norming$type
## [1] "normalized"
## 
## $norming$cov
##           Info    Sim  Vocab   Comp PicCmp PicArr BlkDsg ObjAsm
## Info     0.601                                                 
## Sim      0.052 -0.433                                          
## Vocab    0.730 -0.112  0.211                                   
## Comp    -0.186 -0.829  0.118 -0.422                            
## PicComp  1.090  0.738  0.729  0.820  0.424                     
## PicArr   0.339 -0.835 -0.754  0.127  0.081 -0.698              
## BlkDsgn  1.030  0.068 -0.960 -0.760  0.116 -1.151  0.053       
## ObjAsmb  0.054 -0.146 -0.389 -0.727  0.027 -0.978  0.659  0.010
## 
## $norming$mean
##    Info     Sim   Vocab    Comp PicComp  PicArr BlkDsgn ObjAsmb 
##       0       0       0       0       0       0       0       0

Finally, we could formally compare the models using a Chi-squared difference test (aka, Log-likelihood ratio test). We can do this with the lavTestLRT() function. The null hypothesis is no difference between the models. A small p-value provides evidence against this hypothesis and suggests a preference for the more complex model (ie, the model with fewer degrees of freedom). Below there is some evidence against the weak invariance model (p = 0.049), however AIC and BIC metrics suggest otherwise. Lower AIC/BIC values are better and I don’t see any reason to reject the weak invariance model.

lavTestLRT(configural.fit, weak.fit)
## 
## Chi-Squared Difference Test
## 
##                Df   AIC   BIC  Chisq Chisq diff    RMSEA Df diff Pr(>Chisq)  
## configural.fit 38 10583 10765 53.380                                         
## weak.fit       44 10584 10744 65.992     12.613 0.088568       6    0.04961 *
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

This is also a good model. It appears the manifest (observed) variables for each group are influenced in the same way by the two factors.

Strong Invariance

Now constrain both loadings and intercepts to be equal. Notice the group.equal argument now has a vector: c("loadings", "intercepts"). Notice in the summary output that all the loadings (Latent Variables) and Intercepts are equal in both groups.

strong.fit <- cfa(wisc3.model, 
                  sample.cov = combined.cov, 
                  sample.nobs = combined.n, 
                  sample.mean = combined.means, 
                  group.equal = c("loadings", "intercepts")) 
summary(strong.fit)
## lavaan 0.6-19 ended normally after 105 iterations
## 
##   Estimator                                         ML
##   Optimization method                           NLMINB
##   Number of model parameters                        52
##   Number of equality constraints                    14
## 
##   Number of observations per group:                   
##     manic                                           81
##     norming                                        200
## 
## Model Test User Model:
##                                                       
##   Test statistic                               109.088
##   Degrees of freedom                                50
##   P-value (Chi-square)                           0.000
##   Test statistic for each group:
##     manic                                       70.405
##     norming                                     38.684
## 
## Parameter Estimates:
## 
##   Standard errors                             Standard
##   Information                                 Expected
##   Information saturated (h1) model          Structured
## 
## 
## Group 1 [manic]:
## 
## Latent Variables:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC =~                                               
##     Info              1.000                           
##     Sim     (.p2.)    1.118    0.077   14.588    0.000
##     Vocab   (.p3.)    1.110    0.073   15.103    0.000
##     Comp    (.p4.)    0.858    0.070   12.321    0.000
##   VS =~                                               
##     PicComp           1.000                           
##     PicArr  (.p6.)    0.892    0.116    7.722    0.000
##     BlkDsgn (.p7.)    1.188    0.117   10.188    0.000
##     ObjAsmb (.p8.)    1.140    0.112   10.222    0.000
## 
## Covariances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC ~~                                               
##     VS                3.894    0.847    4.595    0.000
## 
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info    (.20.)   10.477    0.321   32.647    0.000
##    .Sim     (.21.)   11.115    0.351   31.644    0.000
##    .Vocab   (.22.)   10.338    0.341   30.287    0.000
##    .Comp    (.23.)   10.295    0.279   36.925    0.000
##    .PicComp (.24.)   10.749    0.265   40.565    0.000
##    .PicArr  (.25.)   10.736    0.283   37.941    0.000
##    .BlkDsgn (.26.)   10.466    0.321   32.646    0.000
##    .ObjAsmb (.27.)   10.690    0.305   35.032    0.000
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info              4.007    0.743    5.391    0.000
##    .Sim               4.146    0.800    5.184    0.000
##    .Vocab             1.936    0.501    3.861    0.000
##    .Comp              2.595    0.496    5.237    0.000
##    .PicComp           2.823    0.605    4.667    0.000
##    .PicArr           10.327    1.716    6.017    0.000
##    .BlkDsgn           5.677    1.091    5.204    0.000
##    .ObjAsmb           4.570    0.909    5.025    0.000
##     VC                6.682    1.333    5.011    0.000
##     VS                3.923    0.893    4.391    0.000
## 
## 
## Group 2 [norming]:
## 
## Latent Variables:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC =~                                               
##     Info              1.000                           
##     Sim     (.p2.)    1.118    0.077   14.588    0.000
##     Vocab   (.p3.)    1.110    0.073   15.103    0.000
##     Comp    (.p4.)    0.858    0.070   12.321    0.000
##   VS =~                                               
##     PicComp           1.000                           
##     PicArr  (.p6.)    0.892    0.116    7.722    0.000
##     BlkDsgn (.p7.)    1.188    0.117   10.188    0.000
##     ObjAsmb (.p8.)    1.140    0.112   10.222    0.000
## 
## Covariances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC ~~                                               
##     VS                3.639    0.562    6.478    0.000
## 
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info    (.20.)   10.477    0.321   32.647    0.000
##    .Sim     (.21.)   11.115    0.351   31.644    0.000
##    .Vocab   (.22.)   10.338    0.341   30.287    0.000
##    .Comp    (.23.)   10.295    0.279   36.925    0.000
##    .PicComp (.24.)   10.749    0.265   40.565    0.000
##    .PicArr  (.25.)   10.736    0.283   37.941    0.000
##    .BlkDsgn (.26.)   10.466    0.321   32.646    0.000
##    .ObjAsmb (.27.)   10.690    0.305   35.032    0.000
##     VC               -0.525    0.348   -1.510    0.131
##     VS               -0.529    0.301   -1.759    0.079
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info              3.799    0.455    8.352    0.000
##    .Sim               2.440    0.364    6.704    0.000
##    .Vocab             2.489    0.364    6.836    0.000
##    .Comp              4.896    0.539    9.087    0.000
##    .PicComp           5.534    0.663    8.346    0.000
##    .PicArr            8.301    0.911    9.117    0.000
##    .BlkDsgn           5.293    0.706    7.495    0.000
##    .ObjAsmb           5.128    0.672    7.628    0.000
##     VC                5.133    0.766    6.698    0.000
##     VS                4.321    0.791    5.459    0.000

The fit measures for this model are not so good.

fitMeasures(strong.fit, fit.indices)
##   chisq      df     cfi   rmsea    srmr     mfi 
## 109.088  50.000   0.942   0.092   0.064   0.900

But the residuals are mostly OK. The only point of strain is the residual for the “Sim” mean (2.45) in the manic group.

residuals(strong.fit, type = "normalized")
## $manic
## $manic$type
## [1] "normalized"
## 
## $manic$cov
##           Info    Sim  Vocab   Comp PicCmp PicArr BlkDsg ObjAsm
## Info    -0.991                                                 
## Sim      0.143 -0.100                                          
## Vocab   -0.840  0.227 -0.116                                   
## Comp    -0.118  0.685  0.280  0.402                            
## PicComp -0.981  0.459  0.135  0.804 -0.646                     
## PicArr  -0.008  0.578  0.665  1.569  1.276  0.810              
## BlkDsgn -0.820  0.141  0.017 -0.776 -1.121  1.238 -0.073       
## ObjAsmb -0.750 -0.289 -0.091 -0.270 -0.735  1.168  0.819  0.005
## 
## $manic$mean
##    Info     Sim   Vocab    Comp PicComp  PicArr BlkDsgn ObjAsmb 
##  -1.145   2.450  -0.250  -1.064   0.549   1.155  -0.449  -0.723 
## 
## 
## $norming
## $norming$type
## [1] "normalized"
## 
## $norming$cov
##           Info    Sim  Vocab   Comp PicCmp PicArr BlkDsg ObjAsm
## Info     0.658                                                 
## Sim      0.099 -0.586                                          
## Vocab    0.751 -0.184  0.158                                   
## Comp    -0.031 -0.733  0.186 -0.368                            
## PicComp  1.049  0.626  0.603  0.812  0.328                     
## PicArr   0.283 -0.961 -0.891  0.102 -0.090 -0.834              
## BlkDsgn  1.111  0.093 -0.956 -0.645  0.075 -1.214  0.098       
## ObjAsmb  0.163 -0.091 -0.356 -0.589  0.015 -1.013  0.803  0.085
## 
## $norming$mean
##    Info     Sim   Vocab    Comp PicComp  PicArr BlkDsgn ObjAsmb 
##   0.679  -1.113   0.216   1.251  -0.531  -0.705   0.261   0.488

The Chi-squared difference test suggests we reject the strong invariance model for the weak invariance model based on the small p-value.

lavTestLRT(weak.fit, strong.fit)
## 
## Chi-Squared Difference Test
## 
##            Df   AIC   BIC   Chisq Chisq diff   RMSEA Df diff Pr(>Chisq)    
## weak.fit   44 10584 10744  65.992                                          
## strong.fit 50 10615 10753 109.088     43.096 0.20977       6  1.117e-07 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

For sake of completeness, let’s move to the next model.

Strict Invariance

Now constrain loadings, intercepts and variances to be equal. Notice the group.equal argument has the vector: c("loadings", "intercepts", "residuals"). Notice in the summary output that all the loadings (Latent Variables), intercepts and variances are equal in both groups.

strict.fit <- cfa(wisc3.model, 
                  sample.cov = combined.cov, 
                  sample.nobs = combined.n, 
                  sample.mean = combined.means, 
                  group.equal = c("loadings", "intercepts", "residuals"))
summary(strict.fit)
## lavaan 0.6-19 ended normally after 71 iterations
## 
##   Estimator                                         ML
##   Optimization method                           NLMINB
##   Number of model parameters                        52
##   Number of equality constraints                    22
## 
##   Number of observations per group:                   
##     manic                                           81
##     norming                                        200
## 
## Model Test User Model:
##                                                       
##   Test statistic                               129.979
##   Degrees of freedom                                58
##   P-value (Chi-square)                           0.000
##   Test statistic for each group:
##     manic                                       82.922
##     norming                                     47.057
## 
## Parameter Estimates:
## 
##   Standard errors                             Standard
##   Information                                 Expected
##   Information saturated (h1) model          Structured
## 
## 
## Group 1 [manic]:
## 
## Latent Variables:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC =~                                               
##     Info              1.000                           
##     Sim     (.p2.)    1.138    0.077   14.745    0.000
##     Vocab   (.p3.)    1.110    0.074   15.020    0.000
##     Comp    (.p4.)    0.845    0.071   11.825    0.000
##   VS =~                                               
##     PicComp           1.000                           
##     PicArr  (.p6.)    0.893    0.115    7.789    0.000
##     BlkDsgn (.p7.)    1.178    0.115   10.236    0.000
##     ObjAsmb (.p8.)    1.128    0.110   10.218    0.000
## 
## Covariances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC ~~                                               
##     VS                3.884    0.874    4.446    0.000
## 
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info    (.20.)   10.547    0.320   32.961    0.000
##    .Sim     (.21.)   11.322    0.353   32.048    0.000
##    .Vocab   (.22.)   10.429    0.343   30.439    0.000
##    .Comp    (.23.)   10.440    0.281   37.214    0.000
##    .PicComp (.24.)   10.701    0.281   38.123    0.000
##    .PicArr  (.25.)   10.759    0.286   37.626    0.000
##    .BlkDsgn (.26.)   10.451    0.323   32.319    0.000
##    .ObjAsmb (.27.)   10.687    0.310   34.483    0.000
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info    (.10.)    3.842    0.392    9.807    0.000
##    .Sim     (.11.)    2.850    0.348    8.196    0.000
##    .Vocab   (.12.)    2.355    0.307    7.666    0.000
##    .Comp    (.13.)    4.252    0.403   10.551    0.000
##    .PicComp (.14.)    4.740    0.502    9.433    0.000
##    .PicArr  (.15.)    8.905    0.821   10.844    0.000
##    .BlkDsgn (.16.)    5.300    0.600    8.836    0.000
##    .ObjAsmb (.17.)    4.908    0.553    8.867    0.000
##     VC                6.642    1.322    5.026    0.000
##     VS                4.106    0.981    4.184    0.000
## 
## 
## Group 2 [norming]:
## 
## Latent Variables:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC =~                                               
##     Info              1.000                           
##     Sim     (.p2.)    1.138    0.077   14.745    0.000
##     Vocab   (.p3.)    1.110    0.074   15.020    0.000
##     Comp    (.p4.)    0.845    0.071   11.825    0.000
##   VS =~                                               
##     PicComp           1.000                           
##     PicArr  (.p6.)    0.893    0.115    7.789    0.000
##     BlkDsgn (.p7.)    1.178    0.115   10.236    0.000
##     ObjAsmb (.p8.)    1.128    0.110   10.218    0.000
## 
## Covariances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC ~~                                               
##     VS                3.659    0.561    6.518    0.000
## 
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info    (.20.)   10.547    0.320   32.961    0.000
##    .Sim     (.21.)   11.322    0.353   32.048    0.000
##    .Vocab   (.22.)   10.429    0.343   30.439    0.000
##    .Comp    (.23.)   10.440    0.281   37.214    0.000
##    .PicComp (.24.)   10.701    0.281   38.123    0.000
##    .PicArr  (.25.)   10.759    0.286   37.626    0.000
##    .BlkDsgn (.26.)   10.451    0.323   32.319    0.000
##    .ObjAsmb (.27.)   10.687    0.310   34.483    0.000
##     VC               -0.632    0.348   -1.818    0.069
##     VS               -0.520    0.308   -1.686    0.092
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info    (.10.)    3.842    0.392    9.807    0.000
##    .Sim     (.11.)    2.850    0.348    8.196    0.000
##    .Vocab   (.12.)    2.355    0.307    7.666    0.000
##    .Comp    (.13.)    4.252    0.403   10.551    0.000
##    .PicComp (.14.)    4.740    0.502    9.433    0.000
##    .PicArr  (.15.)    8.905    0.821   10.844    0.000
##    .BlkDsgn (.16.)    5.300    0.600    8.836    0.000
##    .ObjAsmb (.17.)    4.908    0.553    8.867    0.000
##     VC                5.105    0.762    6.695    0.000
##     VS                4.453    0.794    5.607    0.000

Fit measures are not too good. The CFI is too low and RMSEA is too high.

fitmeasures(strict.fit, fit.indices)
##   chisq      df     cfi   rmsea    srmr     mfi 
## 129.979  58.000   0.930   0.094   0.077   0.880

However, the residuals look good for the most part. The only point of strain is the residual for the “PicCmp” variance (-2.829) in the manic group.

residuals(strict.fit, type = "normalized")
## $manic
## $manic$type
## [1] "normalized"
## 
## $manic$cov
##           Info    Sim  Vocab   Comp PicCmp PicArr BlkDsg ObjAsm
## Info    -0.850                                                 
## Sim      0.082  0.439                                          
## Vocab   -0.806  0.163 -0.353                                   
## Comp    -0.012  0.702  0.388 -0.771                            
## PicComp -0.970  0.398  0.145  0.870 -2.829                     
## PicArr  -0.005  0.537  0.667  1.605  1.138  1.334              
## BlkDsgn -0.778  0.116  0.059 -0.683 -1.298  1.136  0.051       
## ObjAsmb -0.698 -0.306 -0.038 -0.168 -0.901  1.070  0.715 -0.298
## 
## $manic$mean
##    Info     Sim   Vocab    Comp PicComp  PicArr BlkDsgn ObjAsmb 
##  -1.352   1.918  -0.510  -1.524   0.725   1.102  -0.409  -0.713 
## 
## 
## $norming
## $norming$type
## [1] "normalized"
## 
## $norming$cov
##           Info    Sim  Vocab   Comp PicCmp PicArr BlkDsg ObjAsm
## Info     0.643                                                 
## Sim      0.004 -1.312                                          
## Vocab    0.790 -0.291  0.344                                   
## Comp     0.098 -0.711  0.327  0.562                            
## PicComp  1.022  0.493  0.572  0.855  0.978                     
## PicArr   0.253 -1.090 -0.926  0.135 -0.245 -1.496              
## BlkDsgn  1.126 -0.001 -0.938 -0.553 -0.057 -1.341  0.023       
## ObjAsmb  0.191 -0.169 -0.325 -0.486 -0.101 -1.125  0.735  0.245
## 
## $norming$mean
##    Info     Sim   Vocab    Comp PicComp  PicArr BlkDsgn ObjAsmb 
##   0.846  -1.481   0.343   0.950  -0.358  -0.837   0.255   0.429

The Chi-squared test favors the strong invariance model based on AIC and p-value, but the BIC actually points to the strict fit.

lavTestLRT(strong.fit, strict.fit)
## 
## Chi-Squared Difference Test
## 
##            Df   AIC   BIC  Chisq Chisq diff   RMSEA Df diff Pr(>Chisq)   
## strong.fit 50 10615 10753 109.09                                         
## strict.fit 58 10620 10729 129.98      20.89 0.10709       8   0.007444 **
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Partial Strict Invariance

If we want, we can remove constraints for certain parameters using the group.partial argument. For example, to allow the variance for PicCmp to be estimated separately between the groups, add the line group.partial = "PicComp ~~ PicComp". In the summary, notice under Variances that “PicComp” gets a separate estimate in each group.

strict.fit2 <- cfa(wisc3.model, 
                   sample.cov = combined.cov, 
                   sample.nobs = combined.n,
                   sample.mean = combined.means, 
                   group.equal = c("loadings", "intercepts", "residuals"),
                   group.partial = "PicComp ~~ PicComp")
summary(strict.fit2)
## lavaan 0.6-19 ended normally after 76 iterations
## 
##   Estimator                                         ML
##   Optimization method                           NLMINB
##   Number of model parameters                        52
##   Number of equality constraints                    21
## 
##   Number of observations per group:                   
##     manic                                           81
##     norming                                        200
## 
## Model Test User Model:
##                                                       
##   Test statistic                               122.221
##   Degrees of freedom                                57
##   P-value (Chi-square)                           0.000
##   Test statistic for each group:
##     manic                                       76.147
##     norming                                     46.074
## 
## Parameter Estimates:
## 
##   Standard errors                             Standard
##   Information                                 Expected
##   Information saturated (h1) model          Structured
## 
## 
## Group 1 [manic]:
## 
## Latent Variables:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC =~                                               
##     Info              1.000                           
##     Sim     (.p2.)    1.139    0.077   14.736    0.000
##     Vocab   (.p3.)    1.111    0.074   15.008    0.000
##     Comp    (.p4.)    0.846    0.072   11.828    0.000
##   VS =~                                               
##     PicComp           1.000                           
##     PicArr  (.p6.)    0.916    0.117    7.840    0.000
##     BlkDsgn (.p7.)    1.191    0.117   10.192    0.000
##     ObjAsmb (.p8.)    1.146    0.112   10.212    0.000
## 
## Covariances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC ~~                                               
##     VS                3.865    0.844    4.577    0.000
## 
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info    (.20.)   10.547    0.320   32.974    0.000
##    .Sim     (.21.)   11.323    0.353   32.044    0.000
##    .Vocab   (.22.)   10.429    0.343   30.438    0.000
##    .Comp    (.23.)   10.440    0.281   37.199    0.000
##    .PicComp (.24.)   10.753    0.266   40.499    0.000
##    .PicArr  (.25.)   10.779    0.286   37.675    0.000
##    .BlkDsgn (.26.)   10.470    0.321   32.652    0.000
##    .ObjAsmb (.27.)   10.707    0.308   34.737    0.000
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info    (.10.)    3.850    0.392    9.818    0.000
##    .Sim     (.11.)    2.848    0.347    8.198    0.000
##    .Vocab   (.12.)    2.355    0.307    7.672    0.000
##    .Comp    (.13.)    4.247    0.403   10.549    0.000
##    .PicComp           2.829    0.604    4.681    0.000
##    .PicArr  (.15.)    8.863    0.819   10.828    0.000
##    .BlkDsgn (.16.)    5.400    0.604    8.946    0.000
##    .ObjAsmb (.17.)    4.944    0.555    8.912    0.000
##     VC                6.633    1.320    5.023    0.000
##     VS                3.949    0.896    4.405    0.000
## 
## 
## Group 2 [norming]:
## 
## Latent Variables:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC =~                                               
##     Info              1.000                           
##     Sim     (.p2.)    1.139    0.077   14.736    0.000
##     Vocab   (.p3.)    1.111    0.074   15.008    0.000
##     Comp    (.p4.)    0.846    0.072   11.828    0.000
##   VS =~                                               
##     PicComp           1.000                           
##     PicArr  (.p6.)    0.916    0.117    7.840    0.000
##     BlkDsgn (.p7.)    1.191    0.117   10.192    0.000
##     ObjAsmb (.p8.)    1.146    0.112   10.212    0.000
## 
## Covariances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   VC ~~                                               
##     VS                3.594    0.557    6.457    0.000
## 
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info    (.20.)   10.547    0.320   32.974    0.000
##    .Sim     (.21.)   11.323    0.353   32.044    0.000
##    .Vocab   (.22.)   10.429    0.343   30.438    0.000
##    .Comp    (.23.)   10.440    0.281   37.199    0.000
##    .PicComp (.24.)   10.753    0.266   40.499    0.000
##    .PicArr  (.25.)   10.779    0.286   37.675    0.000
##    .BlkDsgn (.26.)   10.470    0.321   32.652    0.000
##    .ObjAsmb (.27.)   10.707    0.308   34.737    0.000
##     VC               -0.632    0.347   -1.819    0.069
##     VS               -0.537    0.301   -1.786    0.074
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .Info    (.10.)    3.850    0.392    9.818    0.000
##    .Sim     (.11.)    2.848    0.347    8.198    0.000
##    .Vocab   (.12.)    2.355    0.307    7.672    0.000
##    .Comp    (.13.)    4.247    0.403   10.549    0.000
##    .PicComp           5.545    0.663    8.358    0.000
##    .PicArr  (.15.)    8.863    0.819   10.828    0.000
##    .BlkDsgn (.16.)    5.400    0.604    8.946    0.000
##    .ObjAsmb (.17.)    4.944    0.555    8.912    0.000
##     VC                5.097    0.762    6.690    0.000
##     VS                4.280    0.783    5.466    0.000

The fit measures are not great…

fitmeasures(strict.fit2, fit.indices)
##   chisq      df     cfi   rmsea    srmr     mfi 
## 122.221  57.000   0.936   0.090   0.071   0.890

But the residuals look pretty good.

residuals(strict.fit2, type = "normalized")
## $manic
## $manic$type
## [1] "normalized"
## 
## $manic$cov
##           Info    Sim  Vocab   Comp PicCmp PicArr BlkDsg ObjAsm
## Info    -0.849                                                 
## Sim      0.085  0.439                                          
## Vocab   -0.803  0.161 -0.354                                   
## Comp    -0.012  0.698  0.384 -0.772                            
## PicComp -0.949  0.414  0.163  0.883 -0.680                     
## PicArr  -0.056  0.486  0.613  1.559  1.180  1.337              
## BlkDsgn -0.800  0.091  0.033 -0.708 -1.162  1.145  0.050       
## ObjAsmb -0.741 -0.351 -0.085 -0.211 -0.789  1.065  0.765 -0.296
## 
## $manic$mean
##    Info     Sim   Vocab    Comp PicComp  PicArr BlkDsgn ObjAsmb 
##  -1.352   1.917  -0.511  -1.526   0.533   1.058  -0.461  -0.773 
## 
## 
## $norming
## $norming$type
## [1] "normalized"
## 
## $norming$cov
##           Info    Sim  Vocab   Comp PicCmp PicArr BlkDsg ObjAsm
## Info     0.643                                                 
## Sim      0.010 -1.311                                          
## Vocab    0.795 -0.291  0.345                                   
## Comp     0.099 -0.716  0.323  0.563                            
## PicComp  1.107  0.590  0.664  0.927  0.357                     
## PicArr   0.223 -1.132 -0.965  0.102 -0.172 -1.491              
## BlkDsgn  1.163  0.038 -0.899 -0.525  0.118 -1.319  0.031       
## ObjAsmb  0.202 -0.162 -0.316 -0.483  0.043 -1.132  0.818  0.254
## 
## $norming$mean
##    Info     Sim   Vocab    Comp PicComp  PicArr BlkDsgn ObjAsmb 
##   0.846  -1.480   0.343   0.951  -0.515  -0.803   0.288   0.465

Obviously one has to wonder if we would arrive at the same conclusions using a new sample of data. We don’t want to build a model that’s too specific to our sample. That’s would be overfitting.

References