Example 8.21: latent class analysis

[This article was first published on SAS and R, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Latent class analysis is a technique used to classify observations based on patterns of categorical responses. Collins and Lanza’s book,”Latent Class and Latent Transition Analysis,” provides a readable introduction, while the UCLA ATS center has an online statistical computing seminar on the topic.

We consider an example analysis from the HELP dataset, where we wish to classify subjects based on their observed (manifest) status on the following variables: 1) on street or in shelter in past 180 days [homeless], 2) CESD score above 20, 3) received substance abuse treatment [satreat], or 4) linked to primary care [linkstatus]. We arbitrarily specify a three class solution.

Support for this method in SAS is available through the proc lca and proc lta add-on routines created and distributed by the Methodology Center at Penn State University. While it’s customary in R to use researcher-written routines, it’s less so for SAS; the machinery which allows independently written procs thus has the potential to mislead users. It bears explicitly stating that third-party procs probably don’t have the same level of robustness or support as those distributed by SAS Institute.

The proc lca code assumes that the data exist in the dataset ds. The current coding of 0’s and 1’s needs to be changed to 1’s and 2’s.

data ds_0; set "c:\book\help.sas7bdat"; run;

data ds; set ds_0;
   homeless = homeless+1;
   cesdcut = (cesd > 20) + 1;
   satreat = satreat+1;
   linkstatus = linkstatus+1;

The call to the LCA procedure specifies the number of classes, the variables to include, the number of categories per variable, and information about the starting values and random starts. It’s highly recommended to run a “large” number of random starts to ensure that the true maximum likelihood estimate is reached (the 20 we used is likely too few for more complex models).

proc lca data=ds;
   title '3 class model';
   nclass 3;
   items homeless cesdcut satreat linkstatus;
   categories 2 2 2 2;
   seed 42;
   nstarts 20;

The output begins with diagnostic information, and indicates that 40% of the seeds were associated with the best fitting model.

Data Summary, Model Information, and Fit Statistics (EM 

Number of subjects in dataset:              431
Number of subjects in analysis:             431

Number of measurement items:             4
Response categories per item:            2 2 2 2
Number of groups in the data:            1
Number of latent classes:                3
Rho starting values were randomly generated (seed = 42).

No parameter restrictions were specified (freely estimated).

Seed selected for best fitted model:    1486228051
Percentage of seeds associated with best fitted model:   40.00%

The model converged in 3241 iterations.

Maximum number of iterations: 5000
Convergence method: maximum absolute deviation (MAD)
Convergence criterion:  0.000001000

A number of fit statistics are provided to help with model comparison (e.g. number of classes, constraints in more complex models).

Fit statistics:
Log-likelihood:     -1032.48
G-squared:              1.22
AIC:                   29.22
BIC:                   86.15
CAIC:                 100.15
Adjusted BIC:          41.72
Entropy R-sqd.:         0.94
Degrees of freedom:        1

The results indicate that 22% of subjects are in class 1, just 8% in class 2, and 70% in class 3.

                            Parameter Estimates
Gamma estimates (class membership probabilities): 
Class:                     1          2          3
                      0.2163     0.0785     0.7052

The next set of output describes the classes. The prevalence for each level of each variable is described for each class. The last response category is redundant (equal to 1 minus the sum of the other probabilities).

Rho estimates (item response probabilities): 
  Response category  1:
Class:                     1          2          3
  homeless    :       0.2703     1.0000     0.5625
  cesdcut     :       0.1154     0.4214     0.1678
  satreat     :       0.0004     0.0000     1.0000
  linkstatus  :       0.6029     1.0000     0.5855

  Response category  2:
Class:                     1          2          3
  homeless    :       0.7297     0.0000     0.4375
  cesdcut     :       0.8846     0.5786     0.8322
  satreat     :       0.9996     1.0000     0.0000
  linkstatus  :       0.3971     0.0000     0.4145

Members of class 1 were primarily homeless subjects with a larger proportion of high scores on the CESD, with substance abuse treatment history, and 40% of whom linked to primary care. Class 2 (the smallest group) was comprised of non-homeless subjects with lower CESD scores, substance abuse treatment, but no linkage. Class 3 was 44% homeless, had high levels of CESD, did not report substance abuse treatment, and 41% linked to primary care.


We begin by reading in the data, Then we use the within() function (section 1.3.1) to generate a dataframe with the variables of interest.

ds = read.csv("http://www.math.smith.edu/r/data/help.csv")
ds = within(ds, (cesdcut = ifelse(cesd>20, 1, 0)))

The poLCA package supports estimation of latent class models in R. The poLCA() function, like proc lca, can incorporate polytomous categorical variables, but also like proc lca requires the variables to be coded starting with positive integers. We specify 10 repetitions (with random starting values).

res2 = poLCA(cbind(homeless=homeless+1, 
   cesdcut=cesdcut+1, satreat=satreat+1, 
   linkstatus=linkstatus+1) ~ 1, 
   maxiter=50000, nclass=3, 
   nrep=10, data=ds)

This generates the following output:

Model 1: llik = -1032.889 ... best llik = -1032.889
Model 2: llik = -1032.889 ... best llik = -1032.889
Model 3: llik = -1032.484 ... best llik = -1032.484
Model 4: llik = -1032.889 ... best llik = -1032.484
Model 5: llik = -1032.889 ... best llik = -1032.484
Model 6: llik = -1032.484 ... best llik = -1032.484
Model 7: llik = -1032.484 ... best llik = -1032.484
Model 8: llik = -1032.889 ... best llik = -1032.484
Model 9: llik = -1032.889 ... best llik = -1032.484
Model 10: llik = -1032.889 ... best llik = -1032.484
Conditional item response (column) probabilities,
 by outcome variable, for each class (row) 
           Pr(1)  Pr(2)
class 1:  0.2703 0.7297
class 2:  1.0000 0.0000
class 3:  0.5625 0.4375

           Pr(1)  Pr(2)
class 1:  0.1154 0.8846
class 2:  0.4213 0.5787
class 3:  0.1678 0.8322

          Pr(1) Pr(2)
class 1:      0     1
class 2:      0     1
class 3:      1     0

           Pr(1)  Pr(2)
class 1:  0.6029 0.3971
class 2:  1.0000 0.0000
class 3:  0.5855 0.4145

Estimated class population shares 
 0.2162 0.0785 0.7053 
Predicted class memberships (by modal posterior prob.) 
 0.181 0.1137 0.7053 
Fit for 3 latent classes: 
number of observations: 431 
number of estimated parameters: 14 
residual degrees of freedom: 1 
maximum log-likelihood: -1032.484 
AIC(3): 2092.967
BIC(3): 2149.893
G^2(3): 1.221830 (Likelihood ratio/deviance statistic) 
X^2(3): 1.233247 (Chi-square goodness of fit) 

The results are consistent with those found in proc lca. We note that, also similar to proc lca the global maximum likelihood estimates were reached 3 times out of 10– this can be discerned by examination of the 10 model results. It’s always a good idea to fit a large number of iterations to ensure that the global maximum likelihood estimates have been reached.

To leave a comment for the author, please follow the link and comment on their blog: SAS and R.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)