Chapter 5 Testing for measurement invariance with lavaan in R

5.1 How to Run a MGCFA in R

Why R? Because it is free.

Alternatives:

5.2 Download R and RStudio

5.3 Install lavaan and semTools

5.4 Data cleaning

  • Do whatever data wrangling necessary (missing values, etc)

  • Remember that you need a variable that identifies the group

5.5 Use lavaan language to formalize the model

The lavaan tutorial explains well how to formalize a theoretical model. It even includes a measurement invariance testing example.

In a nutshell:

Factor loadings in R are indicated by =∼ and covariances (between factors or error terms for items) are indicated by ∼∼. The model is specified similar to writing regression equations.

5.5.1 Example: Testing the Political Trust for measurement invariance

We want to know if we can compare the political trust concept in Portugal and Spain.

5.5.2 Load lavaan and semTools

library(lavaan)
library(semTools)

5.5.3 Model Building

Let´s build our model using lavaan model syntax

baseline <- 'political_trust =~ trstprl + trstlgl + trstplt + trstprt
'


#CFA function
fit_baseline<-cfa(baseline, data = mea_inv, group="cntry")


#Summary shows the estimation results
summary(fit_baseline, fit.measures=T, standardized = T)
## Length  Class   Mode 
##      1 lavaan     S4

5.5.4 Testing

Not a good model.

Commonly reported fit indices and recommended cut-offs

Measure Name Cut-off
CFI Comparative Fit Index CFI>=.90
RMSEA Root Mean Square Root of Approximation RMSEA<0.08
SRMR Standardized Root Mean Square Residual SRMR<0.08

Please take this as generally indicative. Read more about testing on Further Readings

5.5.5 Changing the original model

baseline1 <- 'political_trust =~ trstprl + trstlgl + trstplt + trstprt
trstplt ~~ trstprt
'

fit_configural<-cfa(baseline1, data = mea_inv, group="cntry")

summary(fit_configural, fit.measures=T, standardized = T)
## Length  Class   Mode 
##      1 lavaan     S4

Much better model. Configural Invariance set. Let’s go for metric.

5.5.6 Metric Invariance

metric <- 'political_trust =~ trstprl + trstlgl + trstplt + trstprt
trstplt ~~ trstprt
'

fit_metric<-cfa(baseline1, data = mea_inv, group="cntry", group.equal = c("loadings"))

summary(fit_metric, fit.measures=T, standardized = T)
## Length  Class   Mode 
##      1 lavaan     S4

Also a good model. Let’s go ahead for scalar.

5.5.7 Scalar Invariance

scalar <- 'political_trust =~ trstprl + trstlgl + trstplt + trstprt
trstplt ~~ trstprt
'

fit_scalar<-cfa(baseline1, data = mea_inv, group="cntry", group.equal = c("loadings", "intercepts"))

summary(fit_scalar, fit.measures=T, standardized = T)
## Length  Class   Mode 
##      1 lavaan     S4

Scalar Invariance holds!

5.5.8 Comparing several models using compareFit

compareFit(fit_configural, fit_metric, fit_scalar)
## ################### Nested Model Comparison #########################
## Chi-Squared Difference Test
## 
##                Df   AIC   BIC   Chisq Chisq diff Df diff Pr(>Chisq)  
## fit_configural  2 25032 25174  6.9645                                
## fit_metric      5 25031 25157 11.6200     4.6555       3    0.19883  
## fit_scalar      8 25031 25141 18.0521     6.4321       3    0.09238 .
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## ####################### Model Fit Indices ###########################
##                  chisq df pvalue    cfi    tli        aic        bic rmsea  srmr
## fit_configural  6.965†  2   .031 0.998†  .991  25032.320  25174.353  .053  .006†
## fit_metric     11.620   5   .040 0.998   .995  25030.976† 25156.620  .039  .018 
## fit_scalar     18.052   8   .021 0.997  0.995† 25031.408  25140.664† .038† .020 
## 
## ################## Differences in Fit Indices #######################
##                             df    cfi   tli    aic     bic  rmsea  srmr
## fit_metric - fit_configural  3 -0.001 0.004 -1.345 -17.733 -0.014 0.012
## fit_scalar - fit_metric      3 -0.001 0.000  0.432 -15.956 -0.001 0.002

5.6 Omnibus invariance testing

measurementInvariance (model = baseline1, data = mea_inv, group = "cntry")
## 
## Measurement invariance models:
## 
## Model 1 : fit.configural
## Model 2 : fit.loadings
## Model 3 : fit.intercepts
## Model 4 : fit.means
## 
## Chi-Squared Difference Test
## 
##                Df   AIC   BIC   Chisq Chisq diff Df diff Pr(>Chisq)  
## fit.configural  2 25032 25174  6.9645                                
## fit.loadings    5 25031 25157 11.6200     4.6555       3    0.19883  
## fit.intercepts  8 25031 25141 18.0521     6.4321       3    0.09238 .
## fit.means       9 25034 25138 22.3435     4.2914       1    0.03831 *
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## 
## Fit measures:
## 
##                  cfi rmsea cfi.delta rmsea.delta
## fit.configural 0.998 0.053        NA          NA
## fit.loadings   0.998 0.039     0.001       0.014
## fit.intercepts 0.997 0.038     0.001       0.001
## fit.means      0.996 0.041     0.001       0.003

Terry Jorgensen measurementInvariance function, within the semTools package is very handy

5.7 Advanced Tutorial: Partial Invariance (individually constraining parameters)

Source: Designed by starline / Freepik

Figure 5.1: Source: Designed by starline / Freepik