Skip to contents

The latents R package provides functions for fitting multilevel latent profile analysis, latent class, and latent transition models for continuous, categorical, and mixed-type variables. Models are estimated by expectation-maximization, handle missing data, and provide standard and robust standard errors. Model selection is supported by information criteria, entropy, classification diagnostics, and a parametric bootstrap likelihood ratio test. Covariates can be related to latent class membership using three-step methods with correction for classification error. Latent transition models estimate transitions between latent profiles across ordered occasions and are compatible with the ‘tna’ package for network and sequence analysis. The multilevel mixture formulation follows Vermunt (2003) doi:10.1111/j.0081-1750.2003.t01-1-00131.x.

Multilevel latent profile analysis (MLPA)

Multilevel latent profile analysis (MLPA) is a model-based method for identifying unobserved subgroups in continuous multivariate data when observations are nested within higher-level units. It is applicable to designs such as repeated measurements within individuals, students within schools, and patients within clinics. In these settings, observations from the same unit may be dependent, and the distribution of latent profiles may vary systematically between units.

Ordinary latent profile analysis represents heterogeneity among observations through a finite mixture of distributions. Each component, or latent profile, is characterized by a set of indicator means and variances. The two-level formulation implemented in multilpa() extends this representation by introducing latent classes at the group level. Observation-level profiles describe patterns in the indicators; group-level classes describe differences in the probabilities of those profiles. This allows the analysis to distinguish variation among observations from variation in profile composition across groups.

For example, in repeated measurements of student engagement, an observation-level profile may represent a pattern of relatively high activity across several indicators. A student-level class may then represent students whose observations frequently belong to that profile. Membership in this student class does not require every observation from a student to have the same engagement profile. The distinction is useful whenever within-person variation is itself part of the research question.

The latents package provides estimation, result extraction, visualization, and diagnostic procedures for this two-level mixture. Its extensions include categorical and mixed indicators, membership covariates, missing-indicator likelihoods, staged estimation, and latent transition analysis. This tutorial develops the continuous two-level model first, then examines these extensions in relation to their analytical purposes.

Model formulation and scope

Observation-level profiles and group-level classes

Let 𝐲ij\mathbf{y}_{ij} denote the vector of DD continuous indicators for observation ii in group jj. Let CijC_{ij} denote its latent profile, with KK possible values, and let GjG_j denote the latent class of the group, with HH possible values. In the Gaussian measurement model,

𝐘ij∣Cij=k∼𝒩D(𝛍k,𝚺k). \mathbf{Y}_{ij} \mid C_{ij}=k \sim \mathcal{N}_D(\boldsymbol{\mu}_k,\boldsymbol{\Sigma}_k).

The mean vector 𝛍k\boldsymbol{\mu}_k describes the location of profile kk across indicators, and 𝚺k\boldsymbol{\Sigma}_k describes variation within that profile. At the group level, the parameters

πhk=P(Cij=k∣Gj=h),ηh=P(Gj=h) \pi_{hk}=P(C_{ij}=k\mid G_j=h), \qquad \eta_h=P(G_j=h)

describe the conditional profile probabilities and group-class proportions, respectively. The profile probabilities sum to one within each group class, and the group-class proportions sum to one across classes.

Under the model’s conditional independence assumptions, the contribution of group jj to the likelihood is

Lj=∑h=1Hηh∏i=1nj{∑k=1KπhkϕD(𝐲ij;𝛍k,𝚺k)}. L_j = \sum_{h=1}^{H}\eta_h \prod_{i=1}^{n_j} \left\{\sum_{k=1}^{K}\pi_{hk} \phi_D(\mathbf{y}_{ij};\boldsymbol{\mu}_k,\boldsymbol{\Sigma}_k)\right\}.

This expression makes the two levels explicit. The inner mixture combines the observation-level profiles. The outer mixture combines the group classes, each with its own distribution over those profiles. Observations in a group share the same latent group class; integrating over that class induces dependence among them.

Measurement assumptions

The measurement parameters are shared across group classes. Thus, profile kk has the same indicator means and covariance matrix in every class. This measurement-invariance assumption gives the conditional profile probabilities a common interpretation: differences between group classes concern the prevalence of the same profiles.

By default, the covariance matrices are diagonal. For Gaussian indicators, this implies that indicators are independent conditional on profile membership, an assumption commonly called local independence. A full covariance specification permits residual associations within profiles. The choice affects both the interpretation of the profiles and the number of profiles that may be needed to describe the data.

The basic MLPA model also assumes independence of observations within a group conditional on its latent group class, after marginalizing over the observation profiles. It describes a group’s composition without estimating a direct dependence on the preceding observation. A latent transition model introduces this temporal dependence explicitly.

When the model is appropriate

MLPA is relevant when the research objective concerns both multivariate profiles at the lower level and systematic differences in profile composition at the higher level. The choice of levels follows the study design: an observation might be a measurement occasion, a student, or a clinical encounter, while its group might be a person, a school, or a clinic.

The model implemented here represents higher-level heterogeneity through discrete latent classes. It therefore entails a substantive assumption about how group differences can be represented. The presence of clustering alone does not establish that a particular number of latent group classes is warranted. That specification must be evaluated together with the measurement model and the observed data.

Installation and data preparation

The released version can be installed from CRAN, and the development version from GitHub. These installation commands are provided for reference and are not evaluated when the tutorial is rendered.

install.packages("latents")

# development version
install.packages("remotes")
remotes::install_github("mohsaqr/latents")

Example data and variables

The tutorial uses course_engagement, a simulated dataset containing 1,422 course enrolments from 106 students across 32 courses. Each row represents one student’s enrolment in one course, with up to fifteen enrolments per student. Enrolments form Level 1 and students form Level 2.

library(latents)
vars <- c("browse", "lectures", "forum_read", "forum_post", "attendance")
head(course_engagement)
#>   student course sequence browse lectures forum_read forum_post attendance previous_grade
#> 1       1     23        1   0.73    -0.08       0.30       0.67       0.72           0.54
#> 2       1     19        2   0.68     0.53      -0.02      -0.55       0.70          -0.71
#> 3       1      4        3  -1.51    -0.51      -0.91      -0.80      -0.97           1.98
#> 4       1     18        4   0.64    -1.02      -1.62      -1.20      -1.26          -1.42
#> 5       1     16        5  -0.94    -0.37      -0.71      -0.28      -1.52          -0.67
#> 6       1     29        6  -0.36    -0.46      -0.77      -0.43      -1.34          -0.40
#>   engagement student_type
#> 1    engaged     wavering
#> 2    engaged     wavering
#> 3 disengaged     wavering
#> 4 disengaged     wavering
#> 5 disengaged     wavering
#> 6 disengaged     wavering

Five indicators describe browsing, lecture activity, forum reading, forum posting, and attendance. The indicators were simulated as continuous values, with parameters intended to represent activity on a log-count scale, and then standardized within course and rounded to two decimal places. The simulation does not generate raw event counts or apply a log1p() transformation. An indicator value of zero therefore corresponds to the course average; a value of one corresponds to one standard deviation above that average. The estimated profiles describe engagement relative to other enrolments in the same course.

The student variable identifies the groups used in the likelihood. The sequence variable records the ordering of enrolments within each student and will be used for the longitudinal extension. The variable previous_grade records achievement before the enrolment and will be introduced as an external variable in subsequent analyses.

The generating labels, engagement and student_type, are also available because the dataset is simulated. They are excluded from estimation and used only to examine recovery of the known structure. In empirical applications, the latent classifications generally have no directly observed equivalent.

Data structure and initial inspection

Data should be arranged with one row per Level-1 observation, indicator variables in columns, and an identifier linking observations to their Level-2 units. Indicator selection determines the dimensions along which profiles can differ. The choice should follow the construct being studied, with inspection of distributions, transformations, missingness, and potentially redundant indicators before estimation.

The descriptives() function provides indicator summaries and intraclass correlations when a group identifier is supplied.

descriptives(course_engagement, vars = vars, id = "student")
#>     variable    n n_missing      mean    sd   min  max n_distinct n_groups    icc
#> 1     browse 1422         0 -2.81e-05 0.989 -3.14 2.49        398      106 0.1923
#> 2   lectures 1422         0 -4.22e-05 0.989 -2.91 3.22        399      106 0.0878
#> 3 forum_read 1422         0  7.03e-05 0.989 -2.73 2.81        400      106 0.2240
#> 4 forum_post 1422         0 -2.11e-05 0.989 -2.61 3.10        397      106 0.1503
#> 5 attendance 1422         0  7.74e-05 0.989 -2.45 2.41        401      106 0.2215

The ICC summarizes the proportion of variation in an indicator attributable to between-group differences. The nonzero ICCs in this example indicate variation between students in their indicator levels. This is useful descriptive evidence of clustering, although it does not identify the number or nature of latent student classes. Limited between-group variation in individual indicator means also does not exclude differences in more complex multivariate patterns.

Course enrolments additionally share course membership. The model below uses student as its grouping variable and does not estimate a crossed student-by-course structure. Standardization within course defines the indicator scale but does not in general account for every source of course-related dependence.

Specifying and estimating a two-level model

The initial specification contains two observation-level profiles and two group-level classes. The purpose of this fit is to establish an interpretable reference model before comparing alternative specifications.

fit <- multilpa(
  data = course_engagement,
  vars = vars,
  id = "student",
  n_profiles = 2,
  n_group_classes = 2,
  variance_model = "varying",
  n_starts = 10,
  seed = 1
)
get_results(fit)
#>    profile  indicator   mean variance standard_deviation mean_standard_error
#> 1        1     browse  0.540    0.590              0.768              0.0271
#> 2        1   lectures  0.427    0.845              0.919              0.0325
#> 3        1 forum_read  0.620    0.481              0.694              0.0247
#> 4        1 forum_post  0.530    0.689              0.830              0.0295
#> 5        1 attendance  0.649    0.384              0.620              0.0223
#> 6        2     browse -0.766    0.528              0.727              0.0312
#> 7        2   lectures -0.606    0.538              0.734              0.0309
#> 8        2 forum_read -0.879    0.363              0.603              0.0262
#> 9        2 forum_post -0.753    0.421              0.649              0.0277
#> 10       2 attendance -0.921    0.374              0.611              0.0264
#>    variance_standard_error
#> 1                   0.0295
#> 2                   0.0421
#> 3                   0.0244
#> 4                   0.0348
#> 5                   0.0196
#> 6                   0.0322
#> 7                   0.0321
#> 8                   0.0228
#> 9                   0.0261
#> 10                  0.0228

The arguments n_profiles and n_group_classes specify KK and HH, respectively. Setting n_group_classes = 1 reduces the model to a single-level profile analysis: there is then one common distribution over profiles, and no latent group-class heterogeneity.

With variance_model = "varying", each profile has its own indicator variances. With variance_model = "equal", the variance of a given indicator is constrained to be equal across profiles, while different indicators can retain different variances. In the default diagonal model, both specifications set within-profile covariances to zero.

Estimation uses the expectation-maximization algorithm. Its expectation step evaluates latent membership probabilities under the current parameters, and its maximization step updates parameters using those probabilities. Iteration continues until the convergence criterion is met or the iteration limit is reached.

Because mixture likelihoods can have local maxima, estimation is repeated from multiple starting values. Here, n_starts = 10 specifies ten starts and seed = 1 makes the starting procedure reproducible for the same input and row order. Convergence and replication across starts are inspected below; a converged fit alone does not establish that the best attainable likelihood has been found.

Inspecting and interpreting the estimated structure

Measurement profiles

get_results(fit) returns the measurement parameters, with one row for each profile and indicator. Means describe the location of a profile, while variances describe dispersion within it. The default plot displays the profile means across indicators.

plot(fit)
plot of chunk profile-plot

plot of chunk profile-plot

In this solution, profile 1 has the higher engagement pattern and profile 2 the lower engagement pattern. Because the indicators were standardized within course, the separation concerns relative activity within courses. The interpretation should be based on the configuration of means across all indicators, together with within-profile dispersion, rather than on a single indicator or the numerical profile label.

Profile numbers are arbitrary identifiers. A different initialization or model specification can reverse their numbering without changing the substantive solution. Comparisons between fits therefore require matching the measurement patterns before comparing class-specific parameters.

Conditional profile probabilities

The higher-level structure is inspected through the probabilities of the observation profiles within each group class.

get_results(fit, "profile_probabilities")
#>   group_class profile probability group_class_probability
#> 1           1       1       0.786                   0.675
#> 2           1       2       0.214                   0.675
#> 3           2       1       0.170                   0.325
#> 4           2       2       0.830                   0.325
plot(fit, what = "probabilities")
plot of chunk probabilities

plot of chunk probabilities

The probability column contains the estimates of πhk\pi_{hk}. Student class 1 assigns approximately 79% probability to the more engaged profile, whereas student class 2 assigns approximately 83% probability to the less engaged profile. These classes describe different engagement compositions among students while retaining a shared definition of engagement at the enrolment level.

The group_class_probability column contains the estimates of ηh\eta_h, or the proportion of students in each class. These are group-level proportions. They should be distinguished from enrolment-level profile proportions, particularly when students contribute different numbers of observations.

Posterior membership and classification

The fitted model yields posterior probabilities at both levels. Observation-level probabilities quantify membership in each profile using the fitted multilevel structure; group-level probabilities quantify membership in each student class using the observations belonging to that student. Observation-level probabilities incorporate uncertainty in the group’s class.

head(get_results(fit, "posteriors", format = "wide"))
#>   row group profile posterior_profile_1 posterior_profile_2
#> 1   1     1       1            1.00e+00            0.000444
#> 2   2     1       1            9.85e-01            0.014854
#> 3   3     1       2            2.42e-06            0.999998
#> 4   4     1       2            3.86e-06            0.999996
#> 5   5     1       2            5.75e-06            0.999994
#> 6   6     1       2            2.28e-05            0.999977
head(get_results(fit, "assignments", data = course_engagement))
#>   student course sequence browse lectures forum_read forum_post attendance previous_grade
#> 1       1     23        1   0.73    -0.08       0.30       0.67       0.72           0.54
#> 2       1     19        2   0.68     0.53      -0.02      -0.55       0.70          -0.71
#> 3       1      4        3  -1.51    -0.51      -0.91      -0.80      -0.97           1.98
#> 4       1     18        4   0.64    -1.02      -1.62      -1.20      -1.26          -1.42
#> 5       1     16        5  -0.94    -0.37      -0.71      -0.28      -1.52          -0.67
#> 6       1     29        6  -0.36    -0.46      -0.77      -0.43      -1.34          -0.40
#>   engagement student_type profile group_class uncertainty posterior_profile_1
#> 1    engaged     wavering       1           2    4.44e-04            1.00e+00
#> 2    engaged     wavering       1           2    1.49e-02            9.85e-01
#> 3 disengaged     wavering       2           2    2.42e-06            2.42e-06
#> 4 disengaged     wavering       2           2    3.86e-06            3.86e-06
#> 5 disengaged     wavering       2           2    5.75e-06            5.75e-06
#> 6 disengaged     wavering       2           2    2.28e-05            2.28e-05
#>   posterior_profile_2
#> 1            0.000444
#> 2            0.014854
#> 3            0.999998
#> 4            0.999996
#> 5            0.999994
#> 6            0.999977
get_results(fit, "entropy")
#>         level n_classes n_units entropy_sum relative_entropy
#> 1 individuals         2    1422       60.94            0.938
#> 2      groups         2     106        4.43            0.940

The assignments table records the most probable profile and group class. Such modal assignments simplify presentation but discard part of the posterior information. An observation with profile probabilities 0.51 and 0.49 has substantially more classification uncertainty than one with probabilities 0.99 and 0.01, although both receive a single assigned profile.

Relative entropy summarizes separation at each level. Values approaching one indicate more concentrated posterior membership probabilities. In this example, relative entropy is approximately 0.94 at both levels. Entropy characterizes classification precision under the fitted model; it does not establish the adequacy of the measurement assumptions or select the correct class count.

get_results(fit, "counts")
#>         level class effective_count effective_proportion
#> 1 individuals     1           834.1                0.587
#> 2 individuals     2           587.9                0.413
#> 3      groups     1            71.5                0.675
#> 4      groups     2            34.5                0.325

Effective class counts are obtained by summing posterior membership probabilities. They need not be integers and differ conceptually from counts based on modal assignments. Small effective classes may provide limited information for estimating means, variances, and membership parameters, and should be evaluated for stability and substantive plausibility.

Recovery of the generating structure

For simulated data, classification can additionally be compared with the known generating labels.

get_results(fit, "assignments", data = course_engagement,
            truth = c("engagement", "student_type"))
#>    assignment class        truth      value   n proportion
#> 1     profile     1   engagement disengaged  16     0.0274
#> 2     profile     2   engagement disengaged 569     0.9726
#> 3     profile     1   engagement    engaged 820     0.9797
#> 4     profile     2   engagement    engaged  17     0.0203
#> 5 group_class     1 student_type  committed  61     0.9839
#> 6 group_class     2 student_type  committed   1     0.0161
#> 7 group_class     1 student_type   wavering  10     0.2273
#> 8 group_class     2 student_type   wavering  34     0.7727

The enrolment-level comparison uses engagement, and the student-level comparison uses student_type, counting each student once. Agreement is interpreted after allowing for arbitrary label permutations. This analysis describes recovery under the specific simulation conditions; in empirical data, validation instead relies on the fitted structure, diagnostics, substantive evidence, and, where available, replication.

Evaluating the model

Convergence and stability across initializations

The starts table records the optimization results for the fitted model.

get_results(fit, "starts")
#>    start log_likelihood converged iterations error boundary
#> 1      1          -8440      TRUE          9  <NA>    FALSE
#> 2      2          -8440      TRUE         12  <NA>    FALSE
#> 3      3          -8440      TRUE          9  <NA>    FALSE
#> 4      4          -8440      TRUE         12  <NA>    FALSE
#> 5      5          -8440      TRUE          9  <NA>    FALSE
#> 6      6          -8440      TRUE         12  <NA>    FALSE
#> 7      7          -8440      TRUE          9  <NA>    FALSE
#> 8      8          -8440      TRUE         12  <NA>    FALSE
#> 9      9          -8440      TRUE          9  <NA>    FALSE
#> 10    10          -8440      TRUE          9  <NA>    FALSE

The relevant evidence includes whether the selected solution converged, how often the best log likelihood was attained, and whether competing solutions have materially different likelihoods or classifications. Repeated attainment of the best value supports numerical stability. A solution attained only once may require additional starts or a closer examination of the fitted parameters.

The sensitivity() function extends this inspection by refitting under different seeds and comparing the resulting solutions.

sensitivity(fit, seeds = 1:3)
#>   seed log_likelihood converged iterations optimum best agreement
#> 1    1          -8440      TRUE          9       1 TRUE         1
#> 2    2          -8440      TRUE          9       1 TRUE         1
#> 3    3          -8440      TRUE          9       1 TRUE         1

Replication across seeds strengthens confidence in the numerical solution without proving a global maximum. The number of starts should be increased when the candidate model is difficult to estimate or the best likelihood is poorly replicated.

Local dependence and covariance specification

Under a diagonal measurement model, the latent profiles account for all modeled associations between indicators. Residual associations assess the dependence that remains after fitting the profiles.

get_results(fit, "residuals", by = "overall")
#>    profile indicator_1 indicator_2     kind observed expected residual effective_n statistic
#> 1  overall  forum_read  attendance gaussian  0.21588        0  0.21588        1422    8.2620
#> 2  overall    lectures  attendance gaussian  0.19234        0  0.19234        1422    7.3369
#> 3  overall      browse  attendance gaussian  0.18077        0  0.18077        1422    6.8850
#> 4  overall  forum_post  attendance gaussian  0.17239        0  0.17239        1422    6.5594
#> 5  overall      browse  forum_read gaussian  0.06265        0  0.06265        1422    2.3632
#> 6  overall      browse  forum_post gaussian -0.02511        0 -0.02511        1422   -0.9461
#> 7  overall      browse    lectures gaussian -0.02126        0 -0.02126        1422   -0.8009
#> 8  overall  forum_read  forum_post gaussian  0.01986        0  0.01986        1422    0.7483
#> 9  overall    lectures  forum_post gaussian  0.01710        0  0.01710        1422    0.6440
#> 10 overall    lectures  forum_read gaussian  0.00193        0  0.00193        1422    0.0725
#>    df  p_value p_adjusted
#> 1  NA 1.43e-16   1.43e-16
#> 2  NA 2.19e-13   2.19e-13
#> 3  NA 5.78e-12   5.78e-12
#> 4  NA 5.40e-11   5.40e-11
#> 5  NA 1.81e-02   1.81e-02
#> 6  NA 3.44e-01   3.44e-01
#> 7  NA 4.23e-01   4.23e-01
#> 8  NA 4.54e-01   4.54e-01
#> 9  NA 5.20e-01   5.20e-01
#> 10 NA 9.42e-01   9.42e-01

In this simulation, attendance was constructed from activity measures, making residual association substantively plausible. Residuals should be interpreted by their magnitude and pattern as well as their inferential summaries. Persistent association involving particular indicators can motivate reconsideration of the measurement specification.

A full covariance model estimates within-profile associations explicitly while retaining the same number of profiles and group classes.

dependent <- multilpa(
  course_engagement, vars, "student",
  n_profiles = 2, n_group_classes = 2,
  variance_model = "varying", covariance_model = "full",
  n_starts = 10, seed = 1
)
rbind(
  diagonal = get_results(fit, "information_criteria"),
  full = get_results(dependent, "information_criteria")
)
#>          log_likelihood n_parameters   aic   kic bic_groups bic_individual sabic_groups
#> diagonal          -8440           23 16926 16952      16987          17047        16914
#> full              -8313           43 16712 16758      16827          16938        16691
#>          sabic_individual caic_groups caic_individual awe_groups awe_individual icl_groups
#> diagonal            16974       17010           17070      17172          17404      16996
#> full                16802       16870           16981      17165          17564      16835
#>          icl_individual clc_groups clc_individual
#> diagonal          17168      16889          17002
#> full              17123      16635          16811
get_results(dependent, "residuals", by = "overall")
#>    profile indicator_1 indicator_2     kind observed expected residual effective_n statistic
#> 1  overall    lectures  attendance gaussian  0.20502  0.20502 1.76e-06        1422  6.94e-05
#> 2  overall      browse  attendance gaussian  0.20383  0.20382 1.57e-06        1422  6.16e-05
#> 3  overall      browse    lectures gaussian -0.01381 -0.01381 1.51e-06        1422  5.69e-05
#> 4  overall  forum_post  attendance gaussian  0.18790  0.18790 1.49e-06        1422  5.82e-05
#> 5  overall    lectures  forum_post gaussian  0.02007  0.02007 1.40e-06        1422  5.29e-05
#> 6  overall      browse  forum_post gaussian -0.01867 -0.01867 1.24e-06        1422  4.67e-05
#> 7  overall    lectures  forum_read gaussian  0.00885  0.00885 1.17e-06        1422  4.39e-05
#> 8  overall  forum_read  attendance gaussian  0.24179  0.24179 7.72e-07        1422  3.09e-05
#> 9  overall      browse  forum_read gaussian  0.07442  0.07442 5.60e-07        1422  2.12e-05
#> 10 overall  forum_read  forum_post gaussian  0.02435  0.02435 4.90e-07        1422  1.85e-05
#>    df p_value p_adjusted
#> 1  NA       1          1
#> 2  NA       1          1
#> 3  NA       1          1
#> 4  NA       1          1
#> 5  NA       1          1
#> 6  NA       1          1
#> 7  NA       1          1
#> 8  NA       1          1
#> 9  NA       1          1
#> 10 NA       1          1

The information criteria compare improvement in likelihood with the additional parameters required by the full covariance model. The residual table indicates how much of the observed association the revised specification accounts for. Near-zero residuals are expected when those associations are directly fitted and do not independently establish overall model adequacy.

Covariance specification and class enumeration are related. Additional diagonal profiles can approximate associations that a more flexible covariance structure represents within profiles. Consequently, the number of profiles should be interpreted in the context of the measurement assumptions under which it was selected.

Selecting the numbers of profiles and group classes

Class enumeration evaluates candidate values of both KK and HH. The following analysis fits two to four observation profiles and one to three group classes under the diagonal specification.

candidates <- enumerate_classes(
  course_engagement, vars, "student",
  n_profiles = 2:4, n_group_classes = 1:3, model = "VVI",
  n_starts = 10, seed = 1
)
candidates
#> Class enumeration: 9 candidate models
#>  n_profiles n_group_classes model log_likelihood n_parameters   aic bic_groups bic_individual
#>           2               1   VVI          -8615           21 17272      17328          17383
#>           3               1   VVI          -8537           32 17137      17222          17306
#>           4               1   VVI          -8501           43 17088      17203          17314
#>           2               2   VVI          -8440           23 16926      16987          17047
#>           3               2   VVI          -8365           35 16799      16892          16983
#>           4               2   VVI          -8326           47 16746      16872          16994
#>           2               3   VVI          -8421           25 16892      16959          17024
#>           3               3   VVI          -8348           38 16773      16874          16973
#>           4               3   VVI          -8307           51 16716      16852          16985
#>  icl_individual profile_entropy group_entropy converged boundary n_best_replicated
#>           17537           0.922            NA      TRUE    FALSE                10
#>           18017           0.772            NA      TRUE    FALSE                10
#>           18359           0.735            NA      TRUE    FALSE                 8
#>           17168           0.938         0.940      TRUE    FALSE                10
#>           17640           0.790         0.943      TRUE    FALSE                10
#>           18021           0.739         0.937      TRUE    FALSE                10
#>           17142           0.940         0.816      TRUE    FALSE                10
#>           17622           0.792         0.797      TRUE    FALSE                10
#>           18014           0.739         0.823      TRUE    FALSE                10
#> No candidate is selected automatically. Compare one convention consistently.
get_results(candidates, "criteria")
#>    criterion  convention n_profiles n_group_classes model value
#> 1        aic        <NA>          4               3   VVI 16716
#> 2        kic        <NA>          4               3   VVI 16770
#> 3        bic      groups          4               3   VVI 16852
#> 4        bic individuals          3               3   VVI 16973
#> 5      sabic      groups          4               3   VVI 16691
#> 6      sabic individuals          4               3   VVI 16823
#> 7       caic      groups          4               3   VVI 16903
#> 8       caic individuals          3               3   VVI 17011
#> 9        awe      groups          3               2   VVI 17169
#> 10       awe individuals          2               3   VVI 17398
#> 11       icl      groups          4               2   VVI 16881
#> 12       icl individuals          2               3   VVI 17142
#> 13       clc      groups          4               3   VVI 16656
#> 14       clc individuals          2               3   VVI 16960
plot(candidates)
plot of chunk enumeration

plot of chunk enumeration

Information criteria balance the fitted likelihood against model complexity; smaller values are preferred within a given criterion. For example, BIC takes the form −2ℓ+plog⁡(n)-2\ell + p\log(n), where ℓ\ell is the maximized log likelihood and pp is the number of estimated parameters. In a multilevel mixture, the choice of sample size in the penalty requires attention. The package reports criteria under both group-count and observation-count conventions. For the single-level LPA specification, the individual-count BIC is the relevant version.

Candidate models should be assessed jointly for information criteria, convergence, replication of the likelihood, effective class sizes, residual dependence, and substantive interpretability. A criterion that continues to improve at the boundary of the grid indicates that the examined grid has not located an interior optimum for that criterion. It does not by itself justify adopting the largest fitted model.

An individual candidate can be retrieved for the same inspection applied to the initial fit.

candidate <- candidate_fit(candidates, n_profiles = 2, n_group_classes = 2)
get_results(candidate)
#>    profile  indicator   mean variance standard_deviation mean_standard_error
#> 1        1     browse  0.540    0.590              0.768              0.0271
#> 2        1   lectures  0.427    0.845              0.919              0.0325
#> 3        1 forum_read  0.620    0.481              0.694              0.0247
#> 4        1 forum_post  0.530    0.689              0.830              0.0295
#> 5        1 attendance  0.649    0.384              0.620              0.0223
#> 6        2     browse -0.766    0.528              0.727              0.0312
#> 7        2   lectures -0.606    0.538              0.734              0.0309
#> 8        2 forum_read -0.879    0.363              0.603              0.0262
#> 9        2 forum_post -0.753    0.421              0.649              0.0277
#> 10       2 attendance -0.921    0.374              0.611              0.0264
#>    variance_standard_error
#> 1                   0.0295
#> 2                   0.0421
#> 3                   0.0244
#> 4                   0.0348
#> 5                   0.0196
#> 6                   0.0322
#> 7                   0.0321
#> 8                   0.0228
#> 9                   0.0261
#> 10                  0.0228

For supported nested models differing by one class at one level, bootstrap_lrt() provides a parametric bootstrap likelihood-ratio comparison. It simulates data from the smaller model and estimates both models for each replicate, constructing an empirical reference distribution for the likelihood-ratio statistic. The following optional example is not evaluated during rendering because it requires hundreds of additional fits.

smaller <- multilpa(course_engagement, vars, "student",
                    n_profiles = 2, n_group_classes = 2, seed = 1)
larger <- multilpa(course_engagement, vars, "student",
                   n_profiles = 3, n_group_classes = 2, seed = 1)
bootstrap_lrt(smaller, larger, iter = 199, seed = 42)

Both models must be fitted by maximum likelihood to the same data. FIML fits are supported by preserving the missingness pattern; this bootstrap assumes missingness independent of indicators and latent classes conditional on fixed covariates, and is not generally calibrated under indicator-dependent MAR. The package withholds the p-value if any replicate fails validation. For membership-covariate replicates, likelihood convergence is sufficient even when separated logits continue to drift; this is recorded in logits_settled.

Parameter uncertainty

Parameter uncertainty is assessed after specifying the model. parameter_inference() provides standard errors and Wald intervals using the observed information matrix by default.

inference <- parameter_inference(fit)
head(inference)
#>         level   outcome       term parameter estimate standard_error statistic   p_value
#> 1 measurement profile_1     browse      mean    0.540         0.0271      19.9  3.37e-88
#> 2 measurement profile_1   lectures      mean    0.427         0.0325      13.2  1.56e-39
#> 3 measurement profile_1 forum_read      mean    0.620         0.0247      25.1 1.25e-138
#> 4 measurement profile_1 forum_post      mean    0.530         0.0295      18.0  3.71e-72
#> 5 measurement profile_1 attendance      mean    0.649         0.0223      29.2 8.03e-187
#> 6 measurement profile_2     browse      mean   -0.766         0.0312     -24.5 7.17e-133
#>   p_adjusted conf_low conf_high
#> 1   3.37e-88    0.486     0.593
#> 2   1.56e-39    0.364     0.491
#> 3  1.25e-138    0.571     0.668
#> 4   3.71e-72    0.473     0.588
#> 5  8.03e-187    0.605     0.693
#> 6  7.17e-133   -0.827    -0.704

confint(fit) provides confidence intervals, while parameter_inference(fit, vcov_type = "robust") requests a sandwich covariance estimator clustered by the observed groups. These procedures quantify uncertainty conditional on the fitted class count and measurement specification. They do not incorporate uncertainty from choosing among candidate models.

Inference requires an adequately identified solution. A variance estimate at its lower bound or a non-positive-definite information matrix can prevent the package from reporting intervals. Such results should prompt examination of class sizes, parameter constraints, and solution stability. The latent transition models described below report standard errors for their initial and transition probabilities as well as for the measurement model.

Covariates and external variables

External variables can be incorporated during estimation or related to an established classification afterward. The distinction concerns whether those variables contribute to estimation of the profile definitions.

Joint estimation of membership regressions

Membership covariates enter through multinomial logistic regressions. In this example, previous_grade varies across enrolments and is specified as a predictor of observation-profile membership.

with_predictors <- multilpa(
  course_engagement, vars, "student",
  n_profiles = 2, n_group_classes = 2,
  profile_covariates = "previous_grade",
  n_starts = 10, seed = 1
)
get_results(with_predictors, "coefficients")
#>     level       outcome           term parameter estimate
#> 1 profile     profile_1  group_class_1     logit   -1.538
#> 2 profile     profile_1  group_class_2     logit    1.268
#> 3 profile     profile_1 previous_grade     logit    0.422
#> 4   group group_class_1    (Intercept)     logit   -0.762

A membership coefficient represents the change in log odds of a profile relative to the reference profile for a one-unit increase in the covariate. Its exponentiation gives the corresponding odds ratio. Interpretation requires identifying the reference category and the measurement pattern associated with each profile. Since previous grade is standardized, a one-unit difference represents one standard deviation on that covariate’s scale.

The joint model estimates membership regression and measurement parameters simultaneously, allowing profile definitions to change when covariates are introduced. For covariates measured at the group level and constant within groups, group_covariates specifies the corresponding regression for group-class membership. The temporal ordering of previous grade supports its use as a prior predictor, but these coefficients remain model-based associations.

Classification-error-corrected three-step analysis

Three-step procedures retain an established measurement solution and account for classification error when relating classes to external variables. three_step() applies the BCH approach to estimating class-specific means; r3step() estimates a corrected membership regression.

three_step(fit, data = course_engagement, outcome = "previous_grade")
#> Distal outcome by class (bch, 95% CI)
#> 
#> Class   Mean          95% CI     SE  Effective n
#> -----  -----  --------------  -----  -----------
#>     1   0.22  [ 0.15,  0.29]  0.035       810.27
#>     2  -0.31  [-0.39, -0.24]  0.036       564.36
r3step(fit, data = course_engagement, covariates = "previous_grade")
#>         level   outcome           term estimate standard_error statistic  p_value p_adjusted
#> 1 individuals profile_1    (Intercept)    0.376         0.1303      2.89 3.90e-03         NA
#> 2 individuals profile_1 previous_grade    0.575         0.0593      9.69 3.35e-22   3.35e-22
#>   conf_low conf_high
#> 1    0.121     0.631
#> 2    0.459     0.691

The first call compares previous-grade means between enrolment profiles. The argument name outcome identifies the variable whose means are estimated; it does not imply that engagement precedes or causes previous grade. The second call estimates the association of previous grade with profile membership while preserving the original profile definitions.

Differences from the jointly estimated regression are therefore expected: the two procedures use different constraints on the measurement solution. three_step() clusters its variance estimates by group by default, while r3step() uses the observed information by default and offers clustered sandwich inference through vcov_type = "robust".

The supplied data must preserve the original observation order and include the external variables. The package uses shared fitted columns to check alignment between the data and the stored fit.

Missing indicators

Observed-data likelihood estimation is available through missing = "fiml". The example below removes attendance measurements after each student’s twelfth enrolment and fits a full covariance model to the remaining observed information.

engagement_with_na <- transform(
  course_engagement,
  attendance = ifelse(sequence > 12, NA, attendance)
)
descriptives(engagement_with_na, vars = vars, id = "student")
#>     variable    n n_missing      mean    sd   min  max n_distinct n_groups    icc
#> 1     browse 1422         0 -2.81e-05 0.989 -3.14 2.49        398      106 0.1923
#> 2   lectures 1422         0 -4.22e-05 0.989 -2.91 3.22        399      106 0.0878
#> 3 forum_read 1422         0  7.03e-05 0.989 -2.73 2.81        400      106 0.2240
#> 4 forum_post 1422         0 -2.11e-05 0.989 -2.61 3.10        397      106 0.1503
#> 5 attendance 1261       161  5.17e-03 0.992 -2.45 2.41        395      106 0.2233
missing_fit <- multilpa(
  engagement_with_na, vars, "student",
  n_profiles = 2, n_group_classes = 2,
  covariance_model = "full", missing = "fiml",
  n_starts = 10, seed = 1
)
get_results(missing_fit)
#>    profile  indicator   mean variance standard_deviation mean_standard_error
#> 1        1     browse  0.531    0.600              0.775              0.0281
#> 2        1   lectures  0.419    0.847              0.921              0.0331
#> 3        1 forum_read  0.611    0.493              0.702              0.0260
#> 4        1 forum_post  0.522    0.698              0.836              0.0308
#> 5        1 attendance  0.634    0.406              0.637              0.0248
#> 6        2     browse -0.769    0.524              0.724              0.0328
#> 7        2   lectures -0.607    0.544              0.737              0.0323
#> 8        2 forum_read -0.885    0.357              0.597              0.0278
#> 9        2 forum_post -0.757    0.413              0.643              0.0288
#> 10       2 attendance -0.927    0.398              0.631              0.0307
#>    variance_standard_error
#> 1                   0.0308
#> 2                   0.0426
#> 3                   0.0259
#> 4                   0.0365
#> 5                   0.0227
#> 6                   0.0335
#> 7                   0.0334
#> 8                   0.0237
#> 9                   0.0270
#> 10                  0.0272

Each observation contributes the density of its observed indicators, obtained by marginalizing over its missing components. Estimation therefore uses partially observed indicator vectors without imputing values. The resulting inference assumes that the missingness mechanism is ignorable for the fitted analysis; the likelihood option itself does not establish that assumption.

Missing-data handling should be described together with the pattern and extent of missingness and the information available to explain it. Without missing = "fiml", missing indicators produce an error. Membership-covariate models accept missing = "fiml" for their indicators, but the covariates themselves must be complete or imputed before fitting with pool_imputations(). The FIML bootstrap likelihood-ratio test preserves the observed missingness mask, subject to the calibration assumptions described above.

Ordered observations and latent transitions

Repeated measurements support distinct questions about profile composition and temporal dynamics. MLPA describes differences between groups in the prevalence of profiles. Latent transition analysis additionally estimates dependence between successive latent profiles.

Inspecting profile sequences

Providing time to multilpa() allows ordered profile assignments to be extracted.

over_time <- multilpa(
  course_engagement, vars, "student",
  n_profiles = 2, n_group_classes = 2,
  time = "sequence", n_starts = 10, seed = 1
)
head(get_results(over_time, "sequences", format = "wide"))
#>   group group_class sequence_1 sequence_2 sequence_3 sequence_4 sequence_5 sequence_6
#> 1     1           2          1          1          2          2          2          2
#> 2     2           2          2          2          2          2          2          2
#> 3     3           1          1          1          1          1          1          1
#> 4     4           1          1          1          1          1          1          1
#> 5     5           2          2          2          2          2          2          2
#> 6     6           1          1          2          2          2          1          1
#>   sequence_7 sequence_8 sequence_9 sequence_10 sequence_11 sequence_12 sequence_13 sequence_14
#> 1          2          2          2           2           2           2           2        <NA>
#> 2          2          2          2           2           2           2           2        <NA>
#> 3          1          1          1           1           1           1           1           1
#> 4          1          1          1           1           1           1           1        <NA>
#> 5          2          1          2           2           2           2           2           2
#> 6          1          2          2           1           1           1        <NA>        <NA>
#>   sequence_15
#> 1        <NA>
#> 2        <NA>
#> 3        <NA>
#> 4        <NA>
#> 5        <NA>
#> 6        <NA>

These sequences are descriptive summaries based on modal assignments. They facilitate inspection of individual histories but do not estimate a transition process or preserve the complete posterior uncertainty about each sequence.

Estimating a latent transition model

lta() estimates initial profile probabilities and transition probabilities across ordered observations. With multiple group classes, both sets of probabilities may differ between classes.

moves <- lta(
  course_engagement, vars, "student",
  n_profiles = 2, n_group_classes = 2,
  time = "sequence", seed = 1
)
get_results(moves, "initial")
#>   group_class profile probability prevalence group_class_probability
#> 1           1       1       0.184      0.220                   0.647
#> 2           1       2       0.816      0.780                   0.647
#> 3           2       1       0.724      0.769                   0.353
#> 4           2       2       0.276      0.231                   0.353
get_results(moves, "transitions")
#>   group_class from to probability expected_count stable estimated group_class_probability
#> 1           1    1  1      0.5868          108.2   TRUE      TRUE                   0.647
#> 2           1    1  2      0.4132           76.2  FALSE      TRUE                   0.647
#> 3           1    2  1      0.1222           81.6  FALSE      TRUE                   0.647
#> 4           1    2  2      0.8778          586.3   TRUE      TRUE                   0.647
#> 5           2    1  1      0.9306          330.5   TRUE      TRUE                   0.353
#> 6           2    1  2      0.0694           24.6  FALSE      TRUE                   0.353
#> 7           2    2  1      0.2572           27.9  FALSE      TRUE                   0.353
#> 8           2    2  2      0.7428           80.6   TRUE      TRUE                   0.353
plot(moves, what = "transitions")
plot of chunk transitions

plot of chunk transitions

For group class hh, a transition probability can be expressed as P(Cj,t=l∣Cj,t−1=k,Gj=h)P(C_{j,t}=l\mid C_{j,t-1}=k,G_j=h). Each row of a transition matrix conditions on the preceding profile and sums to one across possible destination profiles. Diagonal entries quantify persistence; off-diagonal entries quantify transitions to other profiles.

The implemented model is first order and time homogeneous: the next profile depends on the preceding profile, with transition probabilities held constant across sequence positions within each group class. Measurement parameters are invariant across occasions, ensuring that a transition refers to movement between profiles with stable definitions.

In this dataset, consecutive observations are successive course enrolments. A transition therefore concerns adjacent observed enrolments, which need not be separated by equal calendar intervals. This distinction is part of the substantive interpretation of the estimated transition probabilities.

The fitted transitions can be exported through get_tna() and get_group_tna() for further analysis with tna. parameter_inference() reports standard errors and intervals for every transition-model parameter; likelihood-ratio testing and class enumeration are not available for these models.

Categorical and mixed measurement models

For categorical indicators, the measurement model estimates category-response probabilities within profiles. Specifying all indicators as categorical yields two-level latent class analysis. Specifying a subset yields a mixture of Gaussian and categorical measurement components.

The student_esm dataset provides repeated reports of leisure activities nested within students. The following example uses prompts from the first week, selected by day <= 6, and fits a two-level latent class model with multilca(), which treats every indicator as categorical.

activities <- c("time_with_friends", "on_social_media", "tv_video_games",
                "listened_music", "sports", "walking", "reading",
                "part_time_job")
first_week <- subset(student_esm, day <= 6)
lca <- multilca(
  first_week, activities, "student",
  n_profiles = 2, n_group_classes = 2,
  n_starts = 10, seed = 1
)
get_results(lca, "responses")
#>    profile         indicator category probability threshold probability_standard_error
#> 1        1 time_with_friends       no      0.9368     2.696                    0.01281
#> 2        2 time_with_friends       no      0.8324     1.603                    0.01525
#> 3        1 time_with_friends      yes      0.0632        NA                    0.01281
#> 4        2 time_with_friends      yes      0.1676        NA                    0.01525
#> 5        1   on_social_media       no      0.3365    -0.679                    0.04591
#> 6        2   on_social_media       no      0.9235     2.491                    0.02663
#> 7        1   on_social_media      yes      0.6635        NA                    0.04591
#> 8        2   on_social_media      yes      0.0765        NA                    0.02663
#> 9        1    tv_video_games       no      0.6313     0.538                    0.02891
#> 10       2    tv_video_games       no      0.8664     1.870                    0.01605
#> 11       1    tv_video_games      yes      0.3687        NA                    0.02891
#> 12       2    tv_video_games      yes      0.1336        NA                    0.01605
#> 13       1    listened_music       no      0.6464     0.603                    0.03167
#> 14       2    listened_music       no      0.9406     2.762                    0.01419
#> 15       1    listened_music      yes      0.3536        NA                    0.03167
#> 16       2    listened_music      yes      0.0594        NA                    0.01419
#> 17       1            sports       no      0.9406     2.762                    0.01208
#> 18       2            sports       no      0.9159     2.388                    0.01053
#> 19       1            sports      yes      0.0594        NA                    0.01208
#> 20       2            sports      yes      0.0841        NA                    0.01053
#> 21       1           walking       no      0.8865     2.056                    0.01657
#> 22       2           walking       no      0.8365     1.633                    0.01423
#> 23       1           walking      yes      0.1135        NA                    0.01657
#> 24       2           walking      yes      0.1635        NA                    0.01423
#> 25       1           reading       no      0.8790     1.983                    0.01685
#> 26       2           reading       no      0.8917     2.108                    0.01190
#> 27       1           reading      yes      0.1210        NA                    0.01685
#> 28       2           reading      yes      0.1083        NA                    0.01190
#> 29       1     part_time_job       no      0.9838     4.103                    0.00625
#> 30       2     part_time_job       no      0.9587     3.144                    0.00730
#> 31       1     part_time_job      yes      0.0162        NA                    0.00625
#> 32       2     part_time_job      yes      0.0413        NA                    0.00730
plot(lca, what = "responses")
plot of chunk categorical

plot of chunk categorical

The response table describes the probability of each answer conditional on profile membership. These probabilities define the activity profiles. As in the continuous model, get_results(lca, "profile_probabilities") describes how the distribution of those profiles varies between student classes.

A mixed model adds affect ratings as continuous indicators while retaining categorical measurement for the activities.

mixed <- multilpa(
  first_week, c("happy", "relaxed", "exhausted", activities), "student",
  n_profiles = 2, n_group_classes = 2,
  categorical = activities, n_starts = 10, seed = 1
)
get_results(mixed)
#>   profile indicator mean variance standard_deviation mean_standard_error
#> 1       1     happy 3.90    1.963              1.401                  NA
#> 2       1   relaxed 3.66    1.946              1.395                  NA
#> 3       1 exhausted 4.10    2.842              1.686                  NA
#> 4       2     happy 5.98    0.659              0.812                  NA
#> 5       2   relaxed 5.77    1.016              1.008                  NA
#> 6       2 exhausted 2.32    1.976              1.406                  NA
#>   variance_standard_error
#> 1                      NA
#> 2                      NA
#> 3                      NA
#> 4                      NA
#> 5                      NA
#> 6                      NA

This specification treats the affect ratings as approximately Gaussian within profiles. The measurement table describes their means and variances, while get_results(mixed, "responses") provides the activity-response probabilities. Including affect changes the dimensions represented by the model and can consequently change the substantive meaning of its profiles.

Categorical measurement uses freely estimated category probabilities. Ordered categories do not impose an ordinal regression structure. The choice between continuous and categorical treatment of a rating scale should therefore reflect both its distribution and the measurement assumptions appropriate to the analysis.

Staged estimation

Joint estimation allows the higher-level structure to contribute to estimation of the observation profiles. Staged estimation instead establishes the measurement profiles first and then estimates group classes with those measurement parameters held fixed.

staged <- fit_staged(
  course_engagement, vars, "student",
  n_profiles = 2, n_group_classes = 2, seed = 1
)
get_results(staged, "stages")
#>         stage group_classes            fixed log_likelihood parameters
#> 1 measurement             1             <NA>          -8615         21
#> 2  membership             2 means, variances          -8440          3
#>   parameters_with_measurement converged
#> 1                          21      TRUE
#> 2                          23      TRUE

This separation is useful when a common observation-level representation is to be retained while examining heterogeneity in its distribution across groups. It also connects to the distinction between state diversity and person heterogeneity discussed in the VaSSTra approach. The fit_staged() procedure specifically implements staged estimation of the package’s multilevel mixture; its output should be interpreted within that model.

Standard errors and information criteria from the staged fit are conditional on the fixed measurement parameters. Uncertainty from estimating those parameters in the first stage is not propagated, which should be stated when reporting inferential results.

Mixture regression

mixture_regression() fits regressions whose coefficients differ across latent classes, for continuous ("gaussian"), binary or binomial ("binomial") and count ("poisson") outcomes. The class can belong to each row, to a whole group (class_level = "group"), or to each row with a second-level group class that shifts how often each regression class occurs within a group (n_group_classes). Covariates can predict class membership (membership, group_membership), and terms can share one coefficient across classes (common).

regressions <- mixture_regression(score ~ hours, data = study_hours, n_classes = 2,
                                  n_starts = 3, seed = 1)
get_results(regressions, "coefficients")
#> Regression coefficients (95% CI)
#> 
#> Class    Term       Estimate          95% CI      p
#> -------  ---------  --------  --------------  -----
#> Class 1  Intercept     34.35  [32.94, 35.76]  <.001
#> Class 1  hours          4.57  [ 4.36,  4.77]  <.001
#> Class 2  Intercept     54.90  [52.75, 57.04]  <.001
#> Class 2  hours          0.81  [ 0.48,  1.14]  <.001

enumerate_regressions() compares numbers of classes, with an optional parametric bootstrap likelihood-ratio test. Standard errors come from analytic scores, with observed-information, sandwich and outer-product estimators. Single-level and group-level models reproduce flexmix’s likelihood at the same parameters; the mixture regression article walks through every nesting.

Retrieving results and reporting an analysis

The package provides a consistent result interface across supported model families. get_results(fit) retrieves the primary measurement table, and the what argument selects other available tables. The names of those tables can be inspected directly.

names(get_results(fit, "all"))
#>  [1] "profiles"              "responses"             "covariances"          
#>  [4] "profile_probabilities" "counts"                "posteriors"           
#>  [7] "group_posteriors"      "assignments"           "classification"       
#> [10] "average_posteriors"    "classification_errors" "bch_weights"          
#> [13] "entropy"               "residuals"             "information_criteria" 
#> [16] "model"                 "stages"                "starts"               
#> [19] "data"

Result tables are returned as base R data frames. get_results(fit, "all") returns them as a named list, summary(fit) provides a printed overview, and report(fit) combines summaries, diagnostics, and plots. These interfaces support both detailed inspection and preparation of tables for reporting.

A methodological report should identify the Level-1 observations and Level-2 units, describe indicator selection and scaling, state the measurement and covariance constraints, and explain treatment of missingness. The estimation account should specify candidate class counts, starting values, convergence criteria, and evidence that the selected likelihood was replicated.

Interpretation should then distinguish the measurement profiles from the higher-level composition classes. Profile means or response probabilities establish the meaning of the observation-level classes; conditional profile probabilities establish the meaning of the group-level classes. Effective class sizes, posterior classification uncertainty, residual diagnostics, and the rationale for selecting the final model provide the evidence needed to assess that interpretation.

The package vignettes cover the main model families.

vignette("lpa", package = "latents")
vignette("lca", package = "latents")
vignette("lta", package = "latents")
citation("latents")

Further guides are on the package website: complete workflows for latent profile and latent class analysis, model evaluation, covariates and staged estimation, mixture regression, latent class growth analysis, growth mixture models and group-class and cross-level models.

Authors and citation

latents is written by Mohammed Saqr and Sonsoles López-Pernas. Mohammed Saqr maintains the package. Questions and bug reports can be submitted through the repository issue tracker.

The source repository contains further worked examples and software comparison studies. The package citation is available through citation("latents").