Latent class analysis (LCA) and latent profile analysis (LPA) identify unobserved groups using a set of observed indicator variables. Often, researchers also have research questions about variables that were not used to define the classes. These are called auxiliary variables. A distal outcome is an auxiliary variable considered as an outcome; other auxiliary variables may be predictors or covariates.
This vignette introduces three approaches in tidySEM:
BCH, two-step maximum likelihood, and
pseudo-class draws. We illustrate class-specific means,
category probabilities, regressions, hypothesis tests, and predictors of
class membership. For an introduction to estimating the measurement
model, see Introduction
to Latent Class Analysis.
The Naive Approach
An often-heard question is: Why can we not simply assign every participant to their most likely class, and use class membership as a variable in auxiliary analyses?
The reason is that latent class membership is uncertain, and based on posterior class probabilities. A participant with posterior class probabilities is most likely to belong to class 2, with high certainty. However, a participant with posterior class probabilities is most likely to belong to class 1, but there is a lot of uncertainty with this assignment.
If we treat class assignments as an observed variable, this uncertainty propagates into auxiliary analyses and biases the results and their standard errors. The three approaches below address this uncertainty in different ways.
Example: Caregiver profiles
We use the zegwaard_carecompass data included in
tidySEM. As explained in the vignette on Confirmatory
LCA, the measurement model uses four caregiver-experience
indicators. For the present analyses, auxiliary variables concern the
caregiver and the person receiving care. One requirement of all analyses
presented here is that the auxiliary data must have the same number of
rows in exactly the same order as the measurement
data.
library(tidySEM)
library(OpenMx)
#> To take full advantage of multiple cores, use:
#> mxOption(key='Number of Threads', value=parallel::detectCores()) #now
#> Sys.setenv(OMP_NUM_THREADS=parallel::detectCores()) #before library(OpenMx)
df <- zegwaard_carecompass
indicators <- c("burdened", "trapped", "negaffect", "loneliness")
measurement_data <- df[, indicators]For the examples in this vignette, we will just fit a simple two-class model:
set.seed(297)
lpa_model <- mx_profiles(measurement_data, classes = 2)
saveRDS(lpa_model, cache_file("measurement"))
lpa_model <- read_cached("measurement")
table_fit(lpa_model)Classification uncertainty
cp <- class_prob(lpa_model)
head(cp$individual)
cp$mostlikely.classThis table shows the average probability that individuals belong to each true class (rows), and their probability of being assigned to each class (columns). The diagonal of this table thus represents the probability that observations in each class will be correctly classified. These probabilities are high, but not perfect. As also indicated in the fit table by a low Entropy criterion, these classes are not perfectly separated. Therefore, do not treat assigned class as an observed variable; these classification errors would influence downstream auxiliary analyses.
BCH: class differences in distal outcomes
The Bolck–Croon–Hagenaars (BCH) procedure first estimates the
measurement model, then estimates an auxiliary multi-group model,
accounting for classification uncertainty by weighting case
contributions to each class’ group. In tidySEM,
BCH() can compare classes on a single numeric or ordered
outcome, or an entire auxiliary structural equation model.
Continuous distal outcome
Here we compare caregiver distance across classes. The default numeric-outcome specification estimates class-specific means and variances.
bch_mean <- BCH(lpa_model, data = df$distance)
table_results(bch_mean, columns = NULL)The estimates describe class-specific distal means. They do
not by themselves test whether the means differ. For an
omnibus and pairwise comparison, use lr_test(); compare the
means (M) rather than the entire auxiliary model.
lr_test(bch_mean, compare = "M")One crucial limitation of the BCH approach is that its standard errors are not valid, so be careful drawing inferences about parameters in these distal models.
Binary or ordinal distal outcome
For a binary distal outcome, BCH() estimates
class-specific thresholds. The function table_prob()
represents these in the more interpretable response probability
scale:
sex <- ordered(df$sexpatient, levels = c("male", "female"))
bch_sex <- BCH(lpa_model, data = sex)
table_prob(bch_sex)A class-specific regression model
The model argument accepts lavaan-style syntax. This
example investigates whether the classes differ in the effect of
distance on frequency of visits. If we request the "matrix"
column of the output, you can see that the regression coefficients live
in the "A" matrix (asymmetric effects, in other words:
regressions). A likelihood ratio test of the "A" matrix
thus investigates whether the regression coefficient differs
significantly between the two classes:
df_aux <- df[, c("freqvisit", "distance"), drop = FALSE]
df_aux$freqvisit <- as.numeric(df_aux$freqvisit)
bch_reg <- BCH(lpa_model,
model = "freqvisit ~ distance",
data = df_aux)
table_results(bch_reg, columns = c("label", "est_sig", "se", "pval", "confint", "group", "matrix"))
lr_test(bch_reg, compare = "A")Two-step maximum likelihood
A different approach to auxiliary analysis is the
two_step() procedure. It first estimates the latent class
model, then fixes its parameters and adds class-specific auxiliary
models (whose parameters are free). This prevents the auxiliary model
from influencing the class solution. Unlike BCH, the auxiliary outcomes
enter the likelihood directly; posterior class probabilities can
therefore change even though measurement parameters stay fixed.
The interface of this function is similar to BCH():
two_mean <- two_step(lpa_model, data = df$distance)
table_results(two_mean, columns = NULL)Ordinary standard errors for the auxiliary model are also not valid,
but tidySEM replaces them with corrected two-step standard
errors. These are available in the attribute
attr(model, "se_robust"), and table_results()
shows them by default.
attr(two_mean, "se_robust")For a custom auxiliary RAM model, the syntax can include multiple outcomes and regressions:
two_reg <- two_step(
lpa_model,
model = "freqvisit ~ distance",
data = df_aux
)
table_results(two_reg)Pseudo-class draws and pooling
The pseudo-class approach accounts for classification uncertainty by repeatedly drawing one class for each observation according to its posterior probabilities. Thus, a person with posterior probabilities will be assigned to class 2 in most replications, but occasionally to class 1 or 3. Conversely, a participant with posterior class probabilities will be assigned to each of the three classes in many replications.
The auxiliary model is fitted in each completed dataset, and the results are pooled. This propagates classification uncertainty through the draws. Increasing the number of draws reduces Monte Carlo error.
Compare distal means
Here we use lm() with a factor-coded class variable and
no intercept, yielding one coefficient per class. The model
expression is evaluated in each pseudo-class dataset.
set.seed(2027)
pseudo_mean <- pseudo_class(
x = lpa_model,
model = lm(distance ~ I(factor(class)) - 1, data = data),
data = df[, "distance", drop = FALSE],
m = 20
)
pseudo_meanUse RAM syntax
The pseudo_class() approach also accepts auxiliary
models expressed using lavaan syntax. If the model does not
explicitly reference class, the function treats class as a
grouping variable.
set.seed(2028)
pseudo_ram <- pseudo_class(
x = lpa_model,
model = "distance ~ 1",
data = df[, "distance", drop = FALSE],
m = 20
)
pseudo_ramPredicting class membership
So far, we have treated class membership as an explanatory grouping for auxiliary outcomes. Sometimes the research question instead asks which external variables predict class membership. These are not the same estimands: differences in a distal mean do not supply the coefficients of a multinomial regression adjusted for other predictors.
For example, a multinomial model can be fitted across pseudo-class
draws using nnet::multinom():
set.seed(2030)
pseudo_pred <- pseudo_class(
x = lpa_model,
model = function(data) {
nnet::multinom(class ~ distance + freqvisit,
data = data, trace = FALSE)
},
data = df_aux,
m = 20
)
pseudo_predThe coefficients describe conditional associations with class membership relative to a reference class.
Reporting auxiliary analyses
A report should identify the measurement indicators, selected number of classes, classification quality, auxiliary-variable role, estimation method, and treatment of uncertainty. Report class-specific estimates and their units, as well as uncertainty intervals. Mention what kind of standard errors you used, and whether they are valid. For comparisons across classes via likelihood ratio test, state which parameters were constrained.