semTools (version 0.5-1)

measEq.syntax: Syntax for measurement equivalence

Description

Automatically generates lavaan model syntax to specify a confirmatory factor analysis (CFA) model with equality constraints imposed on user-specified measurement (or structural) parameters. Optionally returns the fitted model (if data are provided) representing some chosen level of measurement equivalence/invariance across groups and/or repeated measures.

Usage

measEq.syntax(configural.model, ..., ID.fac = "std.lv",
  ID.cat = "Wu.Estabrook.2016", ID.thr = c(1L, 2L), group = NULL,
  group.equal = "", group.partial = "", longFacNames = list(),
  longIndNames = list(), long.equal = "", long.partial = "",
  auto = "all", warn = TRUE, debug = FALSE, return.fit = FALSE)

Arguments

configural.model

A model with no measurement-invariance constraints (i.e., representing only configural invariance), unless required for model identification. configural.model can be either:

  • lavaan model.syntax or a parameter table (as returned by parTable) specifying the configural model. Using this option, the user can also provide either raw data or summary statistics via sample.cov and (optionally) sample.mean. In order to include thresholds in the syntax, raw data must be provided. See lavaan.

  • a fitted '>lavaan model (e.g., as returned by cfa) estimating the configural model

...

Additional arguments (e.g., data, ordered, or parameterization) passed to the lavaan function. See also lavOptions.

ID.fac

character. The method for identifying common-factor variances and (if meanstructure = TRUE) means. Three methods are available, which go by different names in the literature:

  • Standardize the common factor (mean = 0, SD = 1) by specifying any of: "std.lv", "unit.variance", "UV", "fixed.factor", "fixed-factor"

  • Choose a reference indicator by specifying any of: "auto.fix.first", "unit.loading", "UL", "marker", "ref", "ref.indicator", "reference.indicator", "reference-indicator", "marker.variable", "marker-variable"

  • Apply effects-code constraints to loadings and intercepts by specifying any of: "FX", "EC", "effects", "effects.coding", "effects-coding", "effects.code", "effects-code"

See Little et al. (2006) for details on all three methods.

ID.cat

character. The method for identifying (residual) variances and intercepts of latent item-responses underlying any ordered indicators. Four methods are available:

  • To follow Wu & Estabrook's (2016) guidelines (default), specify any of: "Wu.Estabrook.2016", "Wu.2016", "Wu.Estabrook", "Wu", "Wu2016".

  • To use the default settings of Mplus and lavaan, specify any of: "default", "Mplus", "Muthen". Details provided in Millsap & Tein (2004).

  • To use the constraints recommended by Millsap & Tein (2004; see also Liu et al., 2017) specify any of: "millsap", "millsap.2004", "millsap.tein.2004"

  • To use the default settings of LISREL, specify "LISREL" or "Joreskog". Details provided in Millsap & Tein (2004).

See Details and References for more information.

ID.thr

integer. Only relevant when ID.cat = "Millsap.Tein.2004". Used to indicate which thresholds should be constrained for identification. The first integer indicates the threshold used for all indicators, the second integer indicates the additional threshold constrained for a reference indicator (ignored if binary).

group

optional character indicating the name of a grouping variable. See cfa.

group.equal

optional character vector indicating type(s) of parameter to equate across groups. Ignored if is.null(group). See lavOptions.

group.partial

optional character vector or a parameter table indicating exceptions to group.equal (see lavOptions). Any variables not appearing in the configural.model will be ignored, and any parameter constraints needed for identification (e.g., two thresholds per indicator when ID.cat = "Millsap") will be removed.

longFacNames

optional named list of character vectors, each indicating multiple factors in the model that are actually the same construct measured repeatedly. See Details and Examples.

longIndNames

optional named list of character vectors, each indicating multiple indicators in the model that are actually the same indicator measured repeatedly. See Details and Examples.

long.equal

optional character vector indicating type(s) of parameter to equate across repeated measures. Ignored if no factors are indicated as repeatedly measured in longFacNames.

long.partial

optional character vector or a parameter table indicating exceptions to long.equal. Any longitudinal variable names not appearing in names(longFacNames) or names(longIndNames) will be ignored, and any parameter constraints needed for identification will be removed.

auto

Used to automatically included autocorrelated measurement errors among repeatedly measured indicators in longIndNames. Specify a single integer to set the maximum order (e.g., auto = 1L indicates that an indicator's unique factors should only be correlated between adjacently measured occasions). auto = TRUE or "all" will specify residual covariances among all possible lags per repeatedly measured indicator in longIndNames.

warn, debug

logical. Passed to lavaan and lavParseModelString. See lavOptions.

return.fit

logical indicating whether the generated syntax should be fitted to the provided data (or summary statistics, if provided via sample.cov). If configural.model is a fitted lavaan model, the generated syntax will be fitted using the update method (see '>lavaan), and … will be passed to lavaan. If neither data nor a fitted lavaan model were provided, this must be FALSE. If TRUE, the generated measEq.syntax object will be included in the lavaan object's @external slot, accessible by fit@external$measEq.syntax.

Value

By default, an object of class '>measEq.syntax. If return.fit = TRUE, a fitted lavaan model, with the measEq.syntax object stored in the @external slot, accessible by fit@external$measEq.syntax.

Details

This function is a pedagogical and analytical tool to generate model syntax representing some level of measurement equivalence/invariance across any combination of multiple groups and/or repeated measures. Support is provided for confirmatory factor analysis (CFA) models with simple or complex structure (i.e., cross-loadings and correlated residuals are allowed). For any complexities that exceed the limits of automation, this function is intended to still be useful by providing a means to generate syntax that users can easily edit to accommodate their unique situations.

Limited support is provided for bifactor models and higher-order constructs. Because bifactor models have cross-loadings by definition, the option ID.fac = "effects.code" is unavailable. ID.fac = "UV" is recommended for bifactor models, but ID.fac = "UL" is available on the condition that each factor has a unique first indicator in the configural.model. In order to maintain generality, higher-order factors may include a mix of manifest and latent indicators, but they must therefore require ID.fac = "UL" to avoid complications with differentiating lower-order vs. higher-order (or mixed-level) factors. The keyword "loadings" in group.equal or long.equal constrains factor loadings of all manifest indicators (including loadings on higher-order factors that also have latent indicators), whereas the keyword "regressions" constrains factor loadings of latent indicators. Users can edit the model syntax manually to adjust constraints as necessary, or clever use of the group.partial or long.partial arguments could make it possible for users to still automated their model syntax. The keyword "intercepts" constrains the intercepts of all manifest indicators, and the keyword "means" constrains intercepts and means of all latent common factors, regardless of whether they are latent indicators of higher-order factors. To test equivalence of lower-order and higher-order intercepts/means in separate steps, the user can either manually edit their generated syntax or conscientiously exploit the group.partial or long.partial arguments as necessary.

ID.fac: If the configural.model fixes any (e.g., the first) factor loadings, the generated syntax object will retain those fixed values. This allows the user to retain additional constraints that might be necessary (e.g., if there are only 1 or 2 indicators). Some methods must be used in conjunction with other settings:

  • ID.cat = "Millsap" requires ID.fac = "UL" and parameterization = "theta".

  • ID.cat = "LISREL" requires parameterization = "theta".

  • ID.fac = "effects.code" is unavailable when there are any cross-loadings.

ID.cat: Wu & Estabrook (2016) recommended constraining thresholds to equality first, and doing so should allow releasing any identification constraints no longer needed. For each ordered indicator, constraining one threshold to equality will allow the item's intercepts to be estimated in all but the first group or repeated measure. Constraining a second threshold (if applicable) will allow the item's (residual) variance to be estimated in all but the first group or repeated measure. For binary data, there is no independent test of threshold, intercept, or residual-variance equality. Equivalence of thresholds must also be assumed for three-category indicators. These guidelines provide the least restrictive assumptions and tests, and are therefore the default.

The default setting in Mplus is similar to Wu & Estabrook (2016), except that intercepts are always constrained to zero (so they are assumed to be invariant without testing them). Millsap & Tein (2004) recommended parameterization = "theta" and identified an item's residual variance in all but the first group (or occasion; Liu et al., 2017) by constraining its intercept to zero and one of its thresholds to equality. A second threshold for the reference indicator (so ID.fac = "UL") is used to identify the common-factor means in all but the first group/occasion. The LISREL software fixes the first threshold to zero and (if applicable) the second threshold to 1, and assumes any remaining thresholds to be equal across groups / repeated measures; thus, the intercepts are always identified, and residual variances (parameterization = "theta") are identified except for binary data, when they are all fixed to one.

Repeated Measures: If each repeatedly measured factor is measured by the same indicators (specified in the same order in the configural.model) on each occasion, without any cross-loadings, the user can let longIndNames be automatically generated. Generic names for the repeatedly measured indicators are created using the name of the repeatedly measured factors (i.e., names(longFacNames)) and the number of indicators. So the repeatedly measured first indicator ("ind") of a longitudinal construct called "factor" would be generated as "._factor_ind.1".

The same types of parameter can be specified for long.equal as for group.equal (see lavOptions for a list), except for "residual.covariances" or "lv.covariances". Instead, users can constrain autocovariances using keywords "resid.autocov" or "lv.autocov". Note that group.equal = "lv.covariances" or group.equal = "residual.covariances" will constrain any autocovariances across groups, along with any other covariances the user specified in the configural.model. Note also that autocovariances cannot be specified as exceptions in long.partial, so anything more complex than the auto argument automatically provides should instead be manually specified in the configural.model.

When users set orthogonal=TRUE in the configural.model (e.g., in bifactor models of repeatedly measured constructs), autocovariances of each repeatedly measured factor will still be freely estimated in the generated syntax.

Missing Data: If users wish to utilize the auxiliary function to automatically include auxiliary variables in conjunction with missing = "FIML", they should first generate the hypothesized-model syntax, then submit that syntax as the model to auxiliary(). If users utilized runMI to fit their configural.model to multiply imputed data, that model can also be passed to the configural.model argument, and if return.fit = TRUE, the generated model will be fitted to the multiple imputations.

References

Little, T. D., Slegers, D. W., & Card, N. A. (2006). A non-arbitrary method of identifying and scaling latent variables in SEM and MACS models. Structural Equation Modeling, 13(1), 59--72. doi:10.1207/s15328007sem1301_3

Liu, Y., Millsap, R. E., West, S. G., Tein, J.-Y., Tanaka, R., & Grimm, K. J. (2017). Testing measurement invariance in longitudinal data with ordered-categorical measures. Psychological Methods, 22(3), 486--506. doi:10.1037/met0000075

Millsap, R. E., & Tein, J.-Y. (2004). Assessing factorial invariance in ordered-categorical measures. Multivariate Behavioral Research, 39(3), 479--515. doi:10.1207/S15327906MBR3903_4

Wu, H., & Estabrook, R. (2016). Identification of confirmatory factor analysis models of different levels of invariance for ordered categorical outcomes. Psychometrika, 81(4), 1014--1045. doi:10.1007/s11336-016-9506-0

See Also

compareFit

Examples

Run this code
# NOT RUN {
mod.cat <- ' FU1 =~ u1 + u2 + u3 + u4
             FU2 =~ u5 + u6 + u7 + u8 '
## the 2 factors are actually the same factor (FU) measured twice
longFacNames <- list(FU = c("FU1","FU2"))

## configural model: no constraints across groups or repeated measures
syntax.config <- measEq.syntax(configural.model = mod.cat, data = datCat,
                               ordered = paste0("u", 1:8),
                               parameterization = "theta",
                               ID.fac = "std.lv", ID.cat = "Wu.Estabrook.2016",
                               group = "g", longFacNames = longFacNames)
## print lavaan syntax to the Console
cat(as.character(syntax.config))
## print a summary of model features
summary(syntax.config)

## threshold invariance
syntax.thresh <- measEq.syntax(configural.model = mod.cat, data = datCat,
                               ordered = paste0("u", 1:8),
                               parameterization = "theta",
                               ID.fac = "std.lv", ID.cat = "Wu.Estabrook.2016",
                               group = "g", group.equal = "thresholds",
                               longFacNames = longFacNames,
                               long.equal = "thresholds")
## notice that constraining 4 thresholds allows intercepts and residual
## variances to be freely estimated in all but the first group & occasion
cat(as.character(syntax.thresh))
## print a summary of model features
summary(syntax.thresh)


## Fit a model to the data either in a subsequent step:
mod.config <- as.character(syntax.config)
fit.config <- cfa(mod.config, data = datCat, group = "g",
                  ordered = paste0("u", 1:8), parameterization = "theta")
## or in a single step:
fit.thresh <- measEq.syntax(configural.model = mod.cat, data = datCat,
                            ordered = paste0("u", 1:8),
                            parameterization = "theta",
                            ID.fac = "std.lv", ID.cat = "Wu.Estabrook.2016",
                            group = "g", group.equal = "thresholds",
                            longFacNames = longFacNames,
                            long.equal = "thresholds", return.fit = TRUE)

## compare their fit to test threshold invariance
anova(fit.config, fit.thresh)


# }
# NOT RUN {
## compare several invariance models
test.seq <- c("thresholds","loadings","intercepts","means","residuals")
meq.list <- list()
for (i in 0:length(test.seq)) {
  if (i == 0L) {
    meq.label <- "configural"
    group.equal <- ""
    long.equal <- ""
  } else {
    meq.label <- test.seq[i]
    group.equal <- test.seq[1:i]
    long.equal <- test.seq[1:i]
  }
  meq.list[[meq.label]] <- measEq.syntax(configural.model = mod.cat,
                                         data = datCat,
                                         ordered = paste0("u", 1:8),
                                         parameterization = "theta",
                                         ID.fac = "std.lv",
                                         ID.cat = "Wu.Estabrook.2016",
                                         group = "g",
                                         group.equal = group.equal,
                                         longFacNames = longFacNames,
                                         long.equal = long.equal,
                                         return.fit = TRUE)
}

compareFit(meq.list)

# }

Run the code above in your browser using DataCamp Workspace