Department of Industrial Psychology

Stellenbosch University

South Africa

Examining and establishing partial measurement invariance with the lavaan package in R

Read the data, choose an estimator, specify the grouping variable

library(lavaan)
library(psych)
library(semTools)
mydata <- read.csv(file.choose())

### Choose an estimator from "ML", "MLM", "MLR"
estimator <- "MLM"

### Type the name of the grouping variable
group <- "Country_habitat"

Specify the measurement model

mymodel <- '
Stress =~ GWS1 + GWS2 + GWS3 + GWS4 + GWS5 + GWS6 + GWS7 + GWS8 + GWS9
'

Activate the measurement invariance function

measurement.invariance <- function(fit.config, fit.weak, fit.strong, fit.strict, fit.means) {
config.fit <- fitmeasures(fit.config, c("cfi.robust", "tli.robust", "rmsea.robust", "srmr", "BIC", "chisq.scaled", "df.scaled", "pvalue.scaled"))
weak.fit <- fitmeasures(fit.weak, c("cfi.robust", "tli.robust", "rmsea.robust", "srmr", "BIC", "chisq.scaled", "df.scaled", "pvalue.scaled"))
strong.fit <- fitmeasures(fit.strong, c("cfi.robust", "tli.robust", "rmsea.robust", "srmr", "BIC", "chisq.scaled", "df.scaled", "pvalue.scaled"))
strict.fit <- fitmeasures(fit.strict, c("cfi.robust", "tli.robust", "rmsea.robust", "srmr", "BIC", "chisq.scaled", "df.scaled", "pvalue.scaled"))
means.fit <- fitmeasures(fit.means, c("cfi.robust", "tli.robust", "rmsea.robust", "srmr", "BIC", "chisq.scaled", "df.scaled", "pvalue.scaled"))
fit.models <- rbind(config.fit, weak.fit, strong.fit, strict.fit, means.fit)
delta.weak <- config.fit - weak.fit
delta.strong <- weak.fit - strong.fit
delta.strict <- strong.fit - strict.fit
delta.means <- strict.fit - means.fit
delta.models <- rbind(delta.weak, delta.strong, delta.strict, delta.means)
fit.models
delta.models
anova.models <- anova(fit.config, fit.weak, fit.strong, fit.strict, fit.means)
compare.models <- list(anova = anova.models, model.fit = fit.models, model.delta = delta.models[ , c(1, 3, 5)])
compare.models
}

Fit the partial measurement invariance models to the observed data

Users indicate the parameter equality constraints that should be lifted with the group.partial argument. These constraints can be identified by means of Lagrange multiplier tests.

How to indicate a parameter

  • Factor loading of item i1 on factor F1: “F1 =~ i1”
  • Intercept of item i1: “i1 ~ 1”
  • Residual variance of item i1: “i1 ~~ i1”

Example 1 - if it is desired that the factor loading of item i1 should be freely estimated across groups, one would add the following argument to the weak invariance model and all subsequent invariance models: group.partial = c("F1 =~ i1")

Example 2 - if it is desired that the intercept of item i1 should be freely estimated across group, one would add the following argument to the strong invariance model and all subsequent models: group.partial = c("i1 ~ 1")

Example 3 - if it is desired that the residual variance of item i1 should be freely estimated across group, one would add the following argument to the strict invariance model and all subsequent models: group.partial = c("i1 ~~ i1")

Example 4: if it is desired that the factor loading of item i1 and the intercept of item i2 and the residual variance of i3 should be freely estimated one would add the following argument for the weak invariance model: group.partial = c("F1 =~ i1"). Then one would add the following argument for the strong invariance model: group.partial = c("F1 =~ i1", "i1 ~ 1"). Finally, one would add the following argument to the strict invariance model: group.partial = c("F1 =~ i1", "i2 ~ 1", "i3 ~~ i3")

##### Configural invariance
fit.config <- cfa(mymodel, data = mydata, group = group, 
                  estimator = estimator)
#summary(fit.config)

####### Weak invariance
fit.weak <- cfa(mymodel, data = mydata, group = group, 
                estimator = estimator, 
                group.equal ="loadings", 
                group.partial = NULL)
#summary(fit.weak)

####### Strong invariance
fit.strong <- cfa(mymodel, data = mydata, group = group, 
                  estimator = estimator, 
                  group.equal = c("loadings", "intercepts"),
                  group.partial = c("GWS7 ~ 1", "GWS8 ~ 1", "GWS6 ~ 1"))
#summary(fit.strong)

####### Strict invariance
fit.strict <- cfa(mymodel, data = mydata, group = group, 
                  estimator = estimator, 
                  group.equal = c("loadings", "intercepts", "residuals"), 
                  group.partial = c("GWS7 ~ 1", "GWS8 ~ 1", "GWS6 ~ 1"))
#summary(fit.strict)

####### Equal factor means
fit.means <- cfa(mymodel, data = mydata, group = group, 
                 estimator = estimator, 
                 group.equal = c("loadings", "intercepts", "residuals", "means"),
                 group.partial = c("GWS7 ~ 1", "GWS8 ~ 1", "GWS6 ~ 1"))
#summary(fit.means)

###################################################################
# Run the measurement invariance function in the MeasInvar.R file #
###################################################################
measurement.invariance(fit.config, fit.weak, fit.strong, fit.strict, fit.means)
## $anova
## 
## Scaled Chi-Squared Difference Test (method = "satorra.bentler.2001")
## 
## lavaan NOTE:
##     The "Chisq" column contains standard test statistics, not the
##     robust test that should be reported per model. A robust difference
##     test is a function of two standard (not robust) statistics.
##  
##            Df   AIC   BIC  Chisq Chisq diff Df diff Pr(>Chisq)    
## fit.config 54 29471 29754 842.56                                  
## fit.weak   62 29464 29704 850.86      8.292       8   0.405518    
## fit.strong 67 29468 29682 865.40     15.473       5   0.008521 ** 
## fit.strict 76 29512 29679 927.14     54.882       9  1.283e-08 ***
## fit.means  77 29558 29720 975.61     70.185       1  < 2.2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## $model.fit
##            cfi.robust tli.robust rmsea.robust       srmr      bic chisq.scaled
## config.fit  0.8807417  0.8409889    0.1435070 0.05366521 29753.55     591.7743
## weak.fit    0.8806962  0.8614536    0.1339543 0.05570739 29704.03     621.4061
## strong.fit  0.8791627  0.8701450    0.1296846 0.05698057 29682.44     647.1674
## strict.fit  0.8711234  0.8779064    0.1257493 0.06289092 29679.13     706.6205
## means.fit   0.8636819  0.8725337    0.1284863 0.08676744 29720.37     748.1621
##            df.scaled pvalue.scaled
## config.fit        54             0
## weak.fit          62             0
## strong.fit        67             0
## strict.fit        76             0
## means.fit         77             0
## 
## $model.delta
##                cfi.robust rmsea.robust        bic
## delta.weak   4.546861e-05  0.009552626  49.520277
## delta.strong 1.533467e-03  0.004269700  21.592692
## delta.strict 8.039326e-03  0.003935299   3.309571
## delta.means  7.441490e-03 -0.002736971 -41.239457

Inspect the factor loadings, intercepts and error variances of a fitted model

Type here the name of the fitted model that you want to inspect. This will typically be the best fitting model that was identified in the previous step.

summary(fit.strong, fit.measures = TRUE, standardized = TRUE)
## lavaan 0.6.15 ended normally after 38 iterations
## 
##   Estimator                                         ML
##   Optimization method                           NLMINB
##   Number of model parameters                        55
##   Number of equality constraints                    14
## 
##   Number of observations per group:                   
##     2                                              793
##     1                                              584
## 
## Model Test User Model:
##                                               Standard      Scaled
##   Test Statistic                               865.405     647.167
##   Degrees of freedom                                67          67
##   P-value (Chi-square)                           0.000       0.000
##   Scaling correction factor                                  1.337
##     Satorra-Bentler correction                                    
##   Test statistic for each group:
##     2                                          431.144     322.418
##     1                                          434.261     324.749
## 
## Model Test Baseline Model:
## 
##   Test statistic                              6523.400    4555.388
##   Degrees of freedom                                72          72
##   P-value                                        0.000       0.000
##   Scaling correction factor                                  1.432
## 
## User Model versus Baseline Model:
## 
##   Comparative Fit Index (CFI)                    0.876       0.871
##   Tucker-Lewis Index (TLI)                       0.867       0.861
##                                                                   
##   Robust Comparative Fit Index (CFI)                         0.879
##   Robust Tucker-Lewis Index (TLI)                            0.870
## 
## Loglikelihood and Information Criteria:
## 
##   Loglikelihood user model (H0)             -14693.053  -14693.053
##   Loglikelihood unrestricted model (H1)     -14260.351  -14260.351
##                                                                   
##   Akaike (AIC)                               29468.107   29468.107
##   Bayesian (BIC)                             29682.441   29682.441
##   Sample-size adjusted Bayesian (SABIC)      29552.200   29552.200
## 
## Root Mean Square Error of Approximation:
## 
##   RMSEA                                          0.132       0.112
##   90 Percent confidence interval - lower         0.124       0.105
##   90 Percent confidence interval - upper         0.139       0.119
##   P-value H_0: RMSEA <= 0.050                    0.000       0.000
##   P-value H_0: RMSEA >= 0.080                    1.000       1.000
##                                                                   
##   Robust RMSEA                                               0.130
##   90 Percent confidence interval - lower                     0.121
##   90 Percent confidence interval - upper                     0.139
##   P-value H_0: Robust RMSEA <= 0.050                         0.000
##   P-value H_0: Robust RMSEA >= 0.080                         1.000
## 
## Standardized Root Mean Square Residual:
## 
##   SRMR                                           0.057       0.057
## 
## Parameter Estimates:
## 
##   Standard errors                           Robust.sem
##   Information                                 Expected
##   Information saturated (h1) model          Structured
## 
## 
## Group 1 [2]:
## 
## Latent Variables:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##   Stress =~                                                             
##     GWS1              1.000                               0.854    0.786
##     GWS2    (.p2.)    1.116    0.025   44.507    0.000    0.953    0.829
##     GWS3    (.p3.)    0.970    0.033   29.744    0.000    0.828    0.746
##     GWS4    (.p4.)    0.902    0.039   23.414    0.000    0.770    0.682
##     GWS5    (.p5.)    0.774    0.031   24.805    0.000    0.661    0.669
##     GWS6    (.p6.)    0.851    0.030   28.231    0.000    0.727    0.772
##     GWS7    (.p7.)    0.874    0.034   25.681    0.000    0.746    0.715
##     GWS8    (.p8.)    0.870    0.033   26.126    0.000    0.743    0.754
##     GWS9    (.p9.)    0.773    0.033   23.252    0.000    0.660    0.645
## 
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##    .GWS1    (.20.)    2.736    0.036   76.917    0.000    2.736    2.517
##    .GWS2    (.21.)    2.295    0.039   58.675    0.000    2.295    1.997
##    .GWS3    (.22.)    2.356    0.036   65.927    0.000    2.356    2.121
##    .GWS4    (.23.)    2.367    0.037   64.595    0.000    2.367    2.099
##    .GWS5    (.24.)    2.137    0.031   68.058    0.000    2.137    2.162
##    .GWS6              2.135    0.034   63.692    0.000    2.135    2.267
##    .GWS7              2.569    0.037   69.041    0.000    2.569    2.462
##    .GWS8              1.963    0.035   55.680    0.000    1.963    1.993
##    .GWS9    (.28.)    2.098    0.033   64.435    0.000    2.098    2.050
##     Stress            0.000                               0.000    0.000
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##    .GWS1              0.452    0.026   17.258    0.000    0.452    0.383
##    .GWS2              0.414    0.029   14.458    0.000    0.414    0.313
##    .GWS3              0.548    0.044   12.355    0.000    0.548    0.444
##    .GWS4              0.680    0.040   17.077    0.000    0.680    0.534
##    .GWS5              0.540    0.031   17.474    0.000    0.540    0.552
##    .GWS6              0.358    0.024   15.036    0.000    0.358    0.404
##    .GWS7              0.532    0.033   16.161    0.000    0.532    0.488
##    .GWS8              0.419    0.030   13.959    0.000    0.419    0.431
##    .GWS9              0.612    0.038   16.035    0.000    0.612    0.584
##     Stress            0.729    0.050   14.706    0.000    1.000    1.000
## 
## 
## Group 2 [1]:
## 
## Latent Variables:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##   Stress =~                                                             
##     GWS1              1.000                               0.694    0.695
##     GWS2    (.p2.)    1.116    0.025   44.507    0.000    0.775    0.764
##     GWS3    (.p3.)    0.970    0.033   29.744    0.000    0.674    0.703
##     GWS4    (.p4.)    0.902    0.039   23.414    0.000    0.626    0.626
##     GWS5    (.p5.)    0.774    0.031   24.805    0.000    0.538    0.657
##     GWS6    (.p6.)    0.851    0.030   28.231    0.000    0.591    0.710
##     GWS7    (.p7.)    0.874    0.034   25.681    0.000    0.607    0.578
##     GWS8    (.p8.)    0.870    0.033   26.126    0.000    0.604    0.696
##     GWS9    (.p9.)    0.773    0.033   23.252    0.000    0.537    0.639
## 
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##    .GWS1    (.20.)    2.736    0.036   76.917    0.000    2.736    2.737
##    .GWS2    (.21.)    2.295    0.039   58.675    0.000    2.295    2.263
##    .GWS3    (.22.)    2.356    0.036   65.927    0.000    2.356    2.458
##    .GWS4    (.23.)    2.367    0.037   64.595    0.000    2.367    2.369
##    .GWS5    (.24.)    2.137    0.031   68.058    0.000    2.137    2.612
##    .GWS6              2.271    0.040   56.742    0.000    2.271    2.730
##    .GWS7              3.077    0.047   65.183    0.000    3.077    2.930
##    .GWS8              2.253    0.042   53.773    0.000    2.253    2.596
##    .GWS9    (.28.)    2.098    0.033   64.435    0.000    2.098    2.496
##     Stress           -0.317    0.045   -7.022    0.000   -0.456   -0.456
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##    .GWS1              0.517    0.034   15.184    0.000    0.517    0.517
##    .GWS2              0.429    0.036   11.825    0.000    0.429    0.417
##    .GWS3              0.465    0.034   13.740    0.000    0.465    0.506
##    .GWS4              0.607    0.039   15.613    0.000    0.607    0.608
##    .GWS5              0.380    0.026   14.846    0.000    0.380    0.568
##    .GWS6              0.343    0.025   13.457    0.000    0.343    0.495
##    .GWS7              0.734    0.045   16.387    0.000    0.734    0.666
##    .GWS8              0.388    0.033   11.874    0.000    0.388    0.515
##    .GWS9              0.418    0.033   12.751    0.000    0.418    0.592
##     Stress            0.482    0.041   11.712    0.000    1.000    1.000

Perform Lagrange multiplier tests to identify parameters that should be freely estimated

Inspect the chi-square and p-values of the Lagrange multiplier tests. These tests are used to identify parameter equality constraints that contribute toward model misfit. Identify the parameter equality constraint with the highest chi-square. Fit a new series of measurement invariance models where this constraint is lifted, which yields a model of partial measurement invariance. Note whether this leads to an improvement of fit. Repeat the process by conducting new Lagrange multiplier tests until satisfactory fit is obtained.

lavTestScore(fit.strong)
## Warning in lavTestScore(fit.strong): lavaan WARNING: se is not `standard'; not
## implemented yet; falling back to ordinary score test
## $test
## 
## total score test:
## 
##    test     X2 df p.value
## 1 score 21.859 14   0.082
## 
## $uni
## 
## univariate score tests:
## 
##      lhs op   rhs    X2 df p.value
## 1   .p2. == .p31. 0.016  1   0.899
## 2   .p3. == .p32. 4.950  1   0.026
## 3   .p4. == .p33. 1.719  1   0.190
## 4   .p5. == .p34. 0.001  1   0.979
## 5   .p6. == .p35. 0.077  1   0.781
## 6   .p7. == .p36. 0.271  1   0.603
## 7   .p8. == .p37. 0.764  1   0.382
## 8   .p9. == .p38. 0.340  1   0.560
## 9  .p20. == .p49. 0.015  1   0.903
## 10 .p21. == .p50. 0.088  1   0.767
## 11 .p22. == .p51. 0.047  1   0.827
## 12 .p23. == .p52. 9.033  1   0.003
## 13 .p24. == .p53. 7.267  1   0.007
## 14 .p28. == .p57. 0.073  1   0.787

References

de Bruin, G. P. & Taylor, N. (2005). Development of the Sources of Work Stress Inventory. South African Journal of Psychology, 35, 748-765.

de Bruin, G. P. (2006). Dimensionality of the General Work Stress Scale. SA Journal of Industrial Psychology, 32(4), 68-75.