FIML models and information conventions

Scope

semTests supports continuous FIML models with one or several groups. Observed exogenous predictors can be included when they are modeled jointly by setting fixed.x = FALSE and conditional.x = FALSE. Fixed or conditional predictors need a different reference distribution and are refused for now. These models use the standard likelihood-ratio statistic and the usual biased gamma estimate. The complete-data RLS and Du–Bentler UG choices do not apply.

library("semTests")
library("lavaan")

model <- "
  visual  =~ x1 + x2 + x3
  textual =~ x4 + x5 + x6
  speed   =~ x7 + x8 + x9
"

A reproducible MAR-like example

To make the information choice visible, we let missingness in x1 and x5 depend on x9, which stays observed. This gives the example a reproducible MAR-like mechanism. It says nothing about the missingness process in the original data.

fiml_data <- HolzingerSwineford1939
set.seed(20260717)
driver <- as.numeric(scale(fiml_data$x9))

missing_x1 <- runif(nrow(fiml_data)) <
  plogis(qlogis(.20) + .8 * driver)
missing_x5 <- runif(nrow(fiml_data)) <
  plogis(qlogis(.20) - .8 * driver)

fiml_data$x1[missing_x1] <- NA
fiml_data$x5[missing_x5] <- NA
colMeans(is.na(fiml_data[paste0("x", 1:9)]))
#>        x1        x2        x3        x4        x5        x6        x7        x8 
#> 0.2624585 0.0000000 0.0000000 0.0000000 0.2059801 0.0000000 0.0000000 0.0000000 
#>        x9 
#> 0.0000000

Fit the model using FIML:

fit_observed <- cfa(
  model, fiml_data,
  missing = "fiml",
  estimator = "MLR"
)

pvalues(fit_observed, c("SB", "SS", "PEBA4"))
#>        sb_ml        ss_ml     peba4_ml 
#> 2.442470e-08 1.018704e-07 6.558237e-08 
#> estimator: ML (MLR) (FIML) | data: continuous | information: observed | df: 24 | FIML convention: observed

An observed predictor works too when its distribution belongs to the joint model:

model_x <- "
  visual =~ x1 + x2 + x3
  visual ~ ageyr
"

random_x_data <- fiml_data
random_x_data$ageyr[seq(7, nrow(random_x_data), by = 23)] <- NA

fit_random_x <- cfa(
  model_x, random_x_data,
  missing = "fiml", estimator = "MLR",
  fixed.x = FALSE, conditional.x = FALSE
)

pvalues(fit_random_x, c("SB", "SS", "PEBA2"))
#>     sb_ml     ss_ml  peba2_ml 
#> 0.6776279 0.6772337 0.6775772 
#> estimator: ML (MLR) (FIML) | data: continuous | information: observed | df: 2 | FIML convention: observed

Because ageyr belongs to the joint model, its missing values are handled by FIML too. Calling a predictor random is still a modeling decision. If ageyr should instead be treated as a fixed design variable, this example is not a substitute for a conditional analysis.

Keep the two information choices separate

Two similarly named controls do different jobs:

  1. lavaan’s information fit option selects the model information stored and used for lavaan’s own inference.
  2. fiml.convention selects the information matrices that semTests uses for its reference distribution.

The semTests default, "observed", uses observed saturated and observed model information throughout. semTests requests these matrices explicitly. A lavaan fit created with expected information therefore keeps the same observed-convention result.

Choose "lavaan" when the goal is compatibility with lavaan’s own robust construction. For one model, it reproduces lavInspect(fit, "UGamma"). For a nested pair, it reproduces lavaan’s public Satorra-2000 construction. In that compatibility path, information = "expected" controls the model-information part of the calculation.

Why observed information is the default

Expected information can be inconsistent under general MAR (Kenward and Molenberghs, 1998). Observed information reflects the realized missingness patterns and sample curvature. Savalei (2010) and Savalei and Rosseel (2022) work through the consequences for SEM. The choice can matter in finite samples, so we checked it with a large simulation.

The study crossed:

We followed the interval coverage of one free loading and the factor covariance. Six standard-error recipes combined three information choices with either a model-based covariance or an empirical-score sandwich covariance (Yuan & Bentler, 2000).

Mean 95% coverage across sample sizes, missingness mechanisms, and focal parameters. Misspecified cells use centered coverage.
Method Correct, normal Correct, t5 Misspecified, normal Misspecified, t5
Expected, model-based 94.48 79.41 92.10 75.21
Expected + sandwich 94.13 92.45 91.53 89.28
Observed H1, model-based 94.67 79.98 92.72 76.38
Observed H1 + sandwich 94.39 92.52 92.46 89.98
Full observed Hessian, model-based 94.73 80.14 93.46 77.51
Full observed Hessian + sandwich 94.72 93.64 94.53 93.00

The full observed Hessian with a sandwich covariance came closest to 95% overall, with its advantage largest under heavy tails and misspecification, so semTests uses observed information by default. The study targets parameter interval coverage, not the null calibration of the goodness-of-fit p-values themselves, which have their own deterministic and seeded checks under tools/. The "lavaan" convention remains available when the goal is to reproduce lavaan’s spectrum.

Comparing observed and expected model information

Fit the same model again, changing only lavaan’s model-information option:

fit_expected <- cfa(
  model, fiml_data,
  missing = "fiml",
  estimator = "MLR",
  information = "expected"
)

max(abs(coef(fit_observed) - coef(fit_expected)))
#> [1] 0

The information option leaves the fitted parameters unchanged here. It can still change the reference distribution:

tests <- c("SB", "SS", "PEBA4")

comparison <- cbind(
  observed_fit__observed_convention = as.numeric(
    pvalues(fit_observed, tests, fiml.convention = "observed")
  ),
  observed_fit__lavaan_convention = as.numeric(
    pvalues(fit_observed, tests, fiml.convention = "lavaan")
  ),
  expected_fit__observed_convention = as.numeric(
    pvalues(fit_expected, tests, fiml.convention = "observed")
  ),
  expected_fit__lavaan_convention = as.numeric(
    pvalues(fit_expected, tests, fiml.convention = "lavaan")
  )
)
rownames(comparison) <- tests
format(comparison, scientific = TRUE, digits = 4)
#>       observed_fit__observed_convention observed_fit__lavaan_convention
#> SB    "2.442e-08"                       "1.699e-08"                    
#> SS    "1.019e-07"                       "6.115e-07"                    
#> PEBA4 "6.558e-08"                       "1.977e-07"                    
#>       expected_fit__observed_convention expected_fit__lavaan_convention
#> SB    "2.442e-08"                       "6.648e-08"                    
#> SS    "1.019e-07"                       "5.204e-07"                    
#> PEBA4 "6.558e-08"                       "2.728e-07"

The two observed-convention columns agree because semTests explicitly requests observed information. The lavaan-convention columns can differ because that path respects the model-information choice stored in the fit.

Observed information reflects the realized missingness patterns and sample curvature. Expected information averages curvature under the fitted model. They can be close in well-behaved settings. Missingness and non-normality can pull them apart. semTests keeps the convention visible in both the function call and the result footer.

Nested FIML comparison

For nested models, both fits must use the same raw observations and missingness mask:

constrained <- "
  visual  =~ x1 + a*x2 + a*x3
  textual =~ x4 + b*x5 + b*x6
  speed   =~ x7 + x8 + x9
"

m1 <- cfa(
  model, fiml_data,
  missing = "fiml", estimator = "MLR"
)
m0 <- cfa(
  constrained, fiml_data,
  missing = "fiml", estimator = "MLR"
)

The default delta map works with the local model derivatives and tolerates a wider range of equivalent parameterizations. The exact map uses the literal parameter restrictions and requires both fits to share the same underlying set of free parameters:

nested_tests <- c("SB", "SS", "PEBA2")
nested <- cbind(
  observed_delta = as.numeric(pvalues_nested(
    m0, m1,
    tests = nested_tests,
    A.method = "delta", fiml.convention = "observed"
  )),
  observed_exact = as.numeric(pvalues_nested(
    m0, m1,
    tests = nested_tests,
    A.method = "exact", fiml.convention = "observed"
  )),
  lavaan_delta = as.numeric(pvalues_nested(
    m0, m1,
    tests = nested_tests,
    A.method = "delta", fiml.convention = "lavaan"
  ))
)
rownames(nested) <- nested_tests
format(nested, scientific = TRUE, digits = 4)
#>       observed_delta observed_exact lavaan_delta
#> SB    "3.392e-02"    "4.335e-02"    "1.530e-02" 
#> SS    "3.551e-02"    "4.566e-02"    "1.752e-02" 
#> PEBA2 "3.431e-02"    "4.384e-02"    "1.609e-02"

The delta default is the more flexible choice when equivalent models have different parameter tables. Use exact when the hypothesis is naturally written as a literal restriction on one shared set of free parameters.

FIML with several groups

Grouped models use the same interface. Here we compare a configural model with one that holds the factor loadings equal across schools:

mg1 <- cfa(
  model, fiml_data,
  group = "school",
  missing = "fiml", estimator = "MLR"
)
mg0 <- cfa(
  model, fiml_data,
  group = "school", group.equal = "loadings",
  missing = "fiml", estimator = "MLR"
)

pvalues(mg1, c("SB", "SS", "PEBA4"))
#>        sb_ml        ss_ml     peba4_ml 
#> 5.066665e-07 4.715279e-06 1.618893e-06 
#> estimator: ML (MLR) (FIML) | data: continuous | information: observed | df: 48 | FIML convention: observed
pvalues_nested(mg0, mg1, tests = c("SB", "SS", "PEBA4"))
#>     sb_ml     ss_ml  peba4_ml 
#> 0.2253208 0.2320262 0.2254550 
#> estimator: ML (MLR) (FIML) | data: continuous | information: observed | df: 6 | FIML convention: observed | nested (method 2000, A.method delta)

The groups may have different sample sizes and missingness patterns. The two fits in a nested comparison still need the same observations and missingness mask within each group.

What to report

A reproducible FIML analysis should report:

The result footer and attr(result, "semtests") retain these semTests choices with the returned p-values.

References

Kenward, M. G., & Molenberghs, G. (1998). Likelihood based frequentist inference when data are missing at random. Statistical Science, 13(3), 236–247. https://doi.org/10.1214/ss/1028905886

Savalei, V. (2010). Expected versus observed information in SEM with incomplete normal and nonnormal data. Psychological Methods, 15(4), 352–367. https://doi.org/10.1037/a0020143

Savalei, V., & Rosseel, Y. (2022). Computational options for standard errors and test statistics with incomplete normal and nonnormal data in SEM. Structural Equation Modeling, 29(2), 163–181. https://doi.org/10.1080/10705511.2021.1877548

Satorra, A. (2000). Scaled and adjusted restricted tests in multi-sample analysis of moment structures. In R. D. H. Heijmans, D. S. G. Pollock, & A. Satorra (Eds.), Innovations in Multivariate Statistical Analysis (pp. 233–247). Kluwer Academic. https://doi.org/10.1007/978-1-4615-4603-0_17

Yuan, K.-H., & Bentler, P. M. (2000). Three likelihood-based methods for mean and covariance structure analysis with nonnormal missing data. Sociological Methodology, 30, 165–200. https://doi.org/10.1111/0081-1750.00078