CLCM with Greater than 2 Latent Classes

  1. Data generation with K=2 factors/attributes, 2^K = 4 latent classes
  2. Describe K, alpha, Q-matrix, eta
  3. Correctly specified model fit, misspecified model fit - compared via AIC/BIC

This vignette illustrates slightly different data generation. specifically, we extend the data to two attributes/factors. This introduces additional complexity that should be addressed. This extension is the main focus. Second, we use only ordinal items, which allows us to demonstrate the use of the C2 fit statistic.

Specify Details of Data Generation

First load the CLCM package. Next, specify the data generation details. Here we generate 6 ordinal items, each with 4 categories (0, 1, 2, 3).

library(CLCM)
N <- 200
number.timepoints <- 1
item.type <- rep('Ordinal', 6) 
categories.j <- rep(4, 6)
J <- length(item.type)
item.names <- paste0('Item_', 1:J)

The Q-matrix, alpha, eta and Condensation Rules - The Nitty Gritty

Next, K is set to 2 to indicate 2 factors/attributes. The object alpha is the relationship between the latent classes and the attributes/factors.

Examine alpha:

#>                Factor_1 Factor_2
#> Latent_Class_1        0        0
#> Latent_Class_2        1        0
#> Latent_Class_3        0        1
#> Latent_Class_4        1        1

The Q-matrix, which can be thought of as a factor-loading matrix, is a crucial part of the confirmatory latent class model. This pre-specified loading structure is what makes this a confirmatory approach. This Q-matrix contains 6 rows, 1 for each item, and 2 columns, 1 for each factor/attribute. Each items loads on only 1 factor. This is done for simplicity and to ensure the model is identified.

Examine the Q-matrix:

#>        Factor_1 Factor_2
#> Item_1        1        0
#> Item_2        1        0
#> Item_3        1        0
#> Item_4        0        1
#> Item_5        0        1
#> Item_6        0        1

The eta object specifies the relationship between the latent classes, alpha and the Q-matrix, Q. This is referred to the literature as the Condensation Rule. Here the conjunctive item condensation rule is implemented. See the literature for more details.

Examine eta:

#>                Item_1 Item_2 Item_3 Item_4 Item_5 Item_6
#> Latent_Class_1      0      0      0      0      0      0
#> Latent_Class_2      1      1      1      0      0      0
#> Latent_Class_3      0      0      0      1      1      1
#> Latent_Class_4      1      1      1      1      1      1

Broadly, the combination of the Q-matrix (and, when K>1, the condensation rules), dramatically reduces the number of parameters required to be estimated. This is of crucial importance, and should be highlighted: a substantial decrease in the number of parameters that are estimated is accomplished by the following:

  1. Prespecifying which items load on which factors/attributes

  2. Implementing the conjunctive condensation rule, where subjects are grouped into either Latent Class 0 or Latent Class 1 for each item.

  3. eta specifies this grouping - for example, looking at item 1, we can see that Latent classes 2 and 4 are set to 1, where as latent classes 1 and 3 are set to 0:

#>                Item_1
#> Latent_Class_1      0
#> Latent_Class_2      1
#> Latent_Class_3      0
#> Latent_Class_4      1

This means that, when estimating item j, the CLCM reduces the 2^K = 4 latent classes into 2.

Why latent classes 2 and 4? Well, refer back to the Q-matrix (Q) and alpha.

#>        Factor_1 Factor_2
#> Item_1        1        0
#>                Factor_1 Factor_2
#> Latent_Class_1        0        0
#> Latent_Class_2        1        0
#> Latent_Class_3        0        1
#> Latent_Class_4        1        1

We can see that the first row of the Q-matrix indicates that the first item evaluates attribute/factor 1. Referring to the alpha matrix, we can see that latent classes 2 and 4 are a 1 on attribute/factor 1. Thus, latent classes 2 and 4 are grouped together for any item evaluating attribute/factor 1 by itself.

Latent Class Proportions

The final part to specify are the latent class proportions lc.prop at the first (and only) timepoint. This is a uniform distribution, where the subjects are distributed equally throughout the latent classes.

lc.prop <- list('Time_1' = rep(1/2^K, 2^K)) 
lc.prop
#> $Time_1
#> [1] 0.25 0.25 0.25 0.25

Simulate Item Responses

The next step is to simulate data and briefly examine the item responses and posterior distributions.


set.seed(03062021)
X <- simulate_clcm(N = N, Q = Q, number.timepoints = number.timepoints, 
                   item.type = item.type, 
                   categories.j = categories.j, 
                   lc.prop = lc.prop)
apply(X$item.responses, 2, table)                
#>   Item_1 Item_2 Item_3 Item_4 Item_5 Item_6
#> 0     21     36     32     35     39     38
#> 1     53     46     47     43     45     35
#> 2     67     53     66     68     58     60
#> 3     59     65     55     54     58     67
apply(X$item.responses, 2, function(x) prop.table(table(x)))                
#>   Item_1 Item_2 Item_3 Item_4 Item_5 Item_6
#> 0  0.105  0.180  0.160  0.175  0.195  0.190
#> 1  0.265  0.230  0.235  0.215  0.225  0.175
#> 2  0.335  0.265  0.330  0.340  0.290  0.300
#> 3  0.295  0.325  0.275  0.270  0.290  0.335
prop.table(table(apply(X$post, 1, which.max)))
#> 
#>     1     2     3     4 
#> 0.235 0.285 0.210 0.270

Alright, we are all set to fit the CLCM to the data. We pass the dataframe containing the item responses, the item types, and the item names. Importantly, we also pass the Q-matrix, which specifies the relationship between the items and the attributes/factors. This implicitly also specifies the number of factors!

Estimate CLCM

Next step is to estimate the model. Note the option verbose = FALSE can be used to stop the function from printing estimation progress.

mod1 <- clcm(dat = X$dat, max.diff = 0.001,
             item.type = X$item.type, 
             item.names = X$item.names, 
             Q = X$Q) 
#> iteration: 1  max diff in item parameter estimates: 1.697956
#> iteration: 2  max diff in item parameter estimates: 0.231256
#> iteration: 3  max diff in item parameter estimates: 0.158072
#> iteration: 4  max diff in item parameter estimates: 0.106419
#> iteration: 5  max diff in item parameter estimates: 0.077773
#> iteration: 6  max diff in item parameter estimates: 0.062811
#> iteration: 7  max diff in item parameter estimates: 0.052859
#> iteration: 8  max diff in item parameter estimates: 0.045653
#> iteration: 9  max diff in item parameter estimates: 0.039968
#> iteration: 10  max diff in item parameter estimates: 0.035197
#> iteration: 11  max diff in item parameter estimates: 0.031045
#> iteration: 12  max diff in item parameter estimates: 0.027366
#> iteration: 13  max diff in item parameter estimates: 0.024085
#> iteration: 14  max diff in item parameter estimates: 0.021156
#> iteration: 15  max diff in item parameter estimates: 0.018548
#> iteration: 16  max diff in item parameter estimates: 0.016233
#> iteration: 17  max diff in item parameter estimates: 0.014185
#> iteration: 18  max diff in item parameter estimates: 0.012468
#> iteration: 19  max diff in item parameter estimates: 0.01117
#> iteration: 20  max diff in item parameter estimates: 0.009996
#> iteration: 21  max diff in item parameter estimates: 0.008938
#> iteration: 22  max diff in item parameter estimates: 0.007988
#> iteration: 23  max diff in item parameter estimates: 0.007137
#> iteration: 24  max diff in item parameter estimates: 0.006375
#> iteration: 25  max diff in item parameter estimates: 0.005694
#> iteration: 26  max diff in item parameter estimates: 0.005087
#> iteration: 27  max diff in item parameter estimates: 0.005076
#> iteration: 28  max diff in item parameter estimates: 0.00451
#> iteration: 29  max diff in item parameter estimates: 0.004062
#> iteration: 30  max diff in item parameter estimates: 0.003242
#> iteration: 31  max diff in item parameter estimates: 0.002847
#> iteration: 32  max diff in item parameter estimates: 0.003364
#> iteration: 33  max diff in item parameter estimates: 0.002656
#> iteration: 34  max diff in item parameter estimates: 0.00283
#> iteration: 35  max diff in item parameter estimates: 0.001887
#> iteration: 36  max diff in item parameter estimates: 0.002293
#> iteration: 37  max diff in item parameter estimates: 0.002202
#> iteration: 38  max diff in item parameter estimates: 0.001651
#> iteration: 39  max diff in item parameter estimates: 0.001949
#> iteration: 40  max diff in item parameter estimates: 0.002198
#> iteration: 41  max diff in item parameter estimates: 0.001291
#> iteration: 42  max diff in item parameter estimates: 0.002152
#> iteration: 43  max diff in item parameter estimates: 0.000693

For comparison, let’s also fit a misspecified model with only 2 latent classes instead of 4. First we specify the Q-matrix, which is just a single column of ones, and then we fit the model.

Q_2 <- matrix(1, nrow = J, ncol = 1)
mod2 <- clcm(dat = X$dat, max.diff = 0.001,
             item.type = X$item.type, 
             item.names = X$item.names, 
             Q = Q_2) 
#> iteration: 1  max diff in item parameter estimates: 1.808699
#> iteration: 2  max diff in item parameter estimates: 0.213192
#> iteration: 3  max diff in item parameter estimates: 0.13506
#> iteration: 4  max diff in item parameter estimates: 0.127485
#> iteration: 5  max diff in item parameter estimates: 0.140938
#> iteration: 6  max diff in item parameter estimates: 0.139188
#> iteration: 7  max diff in item parameter estimates: 0.133497
#> iteration: 8  max diff in item parameter estimates: 0.118784
#> iteration: 9  max diff in item parameter estimates: 0.100016
#> iteration: 10  max diff in item parameter estimates: 0.081611
#> iteration: 11  max diff in item parameter estimates: 0.065912
#> iteration: 12  max diff in item parameter estimates: 0.053485
#> iteration: 13  max diff in item parameter estimates: 0.043981
#> iteration: 14  max diff in item parameter estimates: 0.036764
#> iteration: 15  max diff in item parameter estimates: 0.031894
#> iteration: 16  max diff in item parameter estimates: 0.030385
#> iteration: 17  max diff in item parameter estimates: 0.028981
#> iteration: 18  max diff in item parameter estimates: 0.027584
#> iteration: 19  max diff in item parameter estimates: 0.026161
#> iteration: 20  max diff in item parameter estimates: 0.024728
#> iteration: 21  max diff in item parameter estimates: 0.023303
#> iteration: 22  max diff in item parameter estimates: 0.021836
#> iteration: 23  max diff in item parameter estimates: 0.020443
#> iteration: 24  max diff in item parameter estimates: 0.019166
#> iteration: 25  max diff in item parameter estimates: 0.018129
#> iteration: 26  max diff in item parameter estimates: 0.017103
#> iteration: 27  max diff in item parameter estimates: 0.016097
#> iteration: 28  max diff in item parameter estimates: 0.015041
#> iteration: 29  max diff in item parameter estimates: 0.014166
#> iteration: 30  max diff in item parameter estimates: 0.013194
#> iteration: 31  max diff in item parameter estimates: 0.012391
#> iteration: 32  max diff in item parameter estimates: 0.011476
#> iteration: 33  max diff in item parameter estimates: 0.010816
#> iteration: 34  max diff in item parameter estimates: 0.009972
#> iteration: 35  max diff in item parameter estimates: 0.010168
#> iteration: 36  max diff in item parameter estimates: 0.008686
#> iteration: 37  max diff in item parameter estimates: 0.008045
#> iteration: 38  max diff in item parameter estimates: 0.007397
#> iteration: 39  max diff in item parameter estimates: 0.006784
#> iteration: 40  max diff in item parameter estimates: 0.006232
#> iteration: 41  max diff in item parameter estimates: 0.005856
#> iteration: 42  max diff in item parameter estimates: 0.005482
#> iteration: 43  max diff in item parameter estimates: 0.005055
#> iteration: 44  max diff in item parameter estimates: 0.004787
#> iteration: 45  max diff in item parameter estimates: 0.004225
#> iteration: 46  max diff in item parameter estimates: 0.002742
#> iteration: 47  max diff in item parameter estimates: 0.004882
#> iteration: 48  max diff in item parameter estimates: 0.003592
#> iteration: 49  max diff in item parameter estimates: 0.003284
#> iteration: 50  max diff in item parameter estimates: 0.002952
#> iteration: 51  max diff in item parameter estimates: 0.002782
#> iteration: 52  max diff in item parameter estimates: 0.001804
#> iteration: 53  max diff in item parameter estimates: 0.003024
#> iteration: 54  max diff in item parameter estimates: 0.002244
#> iteration: 55  max diff in item parameter estimates: 0.001642
#> iteration: 56  max diff in item parameter estimates: 0.002523
#> iteration: 57  max diff in item parameter estimates: 0.00199
#> iteration: 58  max diff in item parameter estimates: 0.002158
#> iteration: 59  max diff in item parameter estimates: 0.001739
#> iteration: 60  max diff in item parameter estimates: 0.002093
#> iteration: 61  max diff in item parameter estimates: 0.002012
#> iteration: 62  max diff in item parameter estimates: 0.00155
#> iteration: 63  max diff in item parameter estimates: 0.000674

Model Fit

Absolute Fit Statistic

First, compute the absolute fit statistic, C2. Note that this is slow - not run in this vignette.

mod.fit1 <- C2_clcm(mod1) 
mod.fit2 <- C2_clcm(mod2) 

The C2 statistic is expected to fail to reject the first model (correctly specified) and reject the second model (incorrectly specified).

Relative Fit Statistics

Second, compute the relative fit statistics, AIC & BIC:

    mod.fit.rel.1 <- aic_bic_clcm(mod1)
    mod.fit.rel.2 <- aic_bic_clcm(mod2)
    unlist(mod.fit.rel.1)
#>  neg_2LL     npar      AIC      BIC 
#> 3175.487   27.000 3229.487 3318.542
    unlist(mod.fit.rel.2)
#>  neg_2LL     npar      AIC      BIC 
#> 3204.255   25.000 3254.255 3336.713

As we would expect, the incorrectly specified model ( mod2) has the larger AIC and BIC values.

Classification Accuracy

Finally, use the correctly specified model (mod1) and compare the true classifications with estimates:

      lca.hat <- mod1$dat$lca 
      lca.true <- mod1$dat$true_lca      
      table(lca.true == lca.hat)
#> 
#> FALSE  TRUE 
#>    75   125
      prop.table(table(lca.true == lca.hat))
#> 
#> FALSE  TRUE 
#> 0.375 0.625
      xtabs( ~ lca.true + lca.hat)
#>         lca.hat
#> lca.true  1  2  3  4
#>        1 17  8 17  5
#>        2  0 38  1 18
#>        3  1  0 30 11
#>        4  0  4 10 40

We can see that the classification accuracy tends to decrease as additional latent classes are added. This makes sense, given that the probability of randomly assigning a subject to the correct latent class decreases as the number of classes increases.

mirror server hosted at Truenetwork, Russian Federation.