---
title: "Latent Class and Rank Analysis"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Latent Class and Rank Analysis}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 5,
  out.width = "100%"
)
```

> **Note**: Some computationally intensive examples below are shown with `eval=FALSE` to keep CRAN build times short. For full rendered output, see the [pkgdown site](https://kosugitti.github.io/exametrika/articles/latent-class-rank.html).

```{r library, message=FALSE, warning=FALSE}
library(exametrika)
```

## Latent Class Analysis (LCA)

LCA classifies examinees into unordered latent classes. Specify the dataset and the number of classes.

```{r model-lca, message=FALSE, warning=FALSE}
LCA(J15S500, ncls = 5)
```

The Class Membership Matrix indicates which latent class each examinee belongs to:

```{r results-lca-membership, message=FALSE, warning=FALSE}
result.LCA <- LCA(J15S500, ncls = 5)
head(result.LCA$Students)
```

### LCA Plot Types

- **IRP**: Item Reference Profile
- **CMP**: Class Membership Profile
- **TRP**: Test Reference Profile
- **LCD**: Latent Class Distribution

```{r plot-lca, fig.width=7, fig.height=5, message=FALSE, warning=FALSE}
plot(result.LCA, type = "IRP", items = 1:6, nc = 2, nr = 3)
plot(result.LCA, type = "CMP", students = 1:9, nc = 3, nr = 3)
plot(result.LCA, type = "TRP")
plot(result.LCA, type = "LCD")
```

## Latent Rank Analysis (LRA)

LRA is similar to LCA but assumes an ordering among the latent classes (ranks). Specify the dataset and the number of ranks.

```{r model-lra, message=FALSE, warning=FALSE}
LRA(J15S500, nrank = 6)
```

Rank membership probabilities and rank-up/rank-down odds are calculated:

```{r results-lra-membership, message=FALSE, warning=FALSE}
result.LRA <- LRA(J15S500, nrank = 6)
head(result.LRA$Students)
```

```{r plot-lra, fig.width=7, fig.height=5, message=FALSE, warning=FALSE}
plot(result.LRA, type = "IRP", items = 1:6, nc = 2, nr = 3)
plot(result.LRA, type = "RMP", students = 1:9, nc = 3, nr = 3)
plot(result.LRA, type = "TRP")
plot(result.LRA, type = "LRD")
```

## Classifying New Respondents with `predict()`

Once a model is fitted, `predict()` assigns new respondents to its classes or
ranks. The estimated parameters are held fixed and one E-step is run on the
new responses, so the new respondents do not change the model. This is useful
when the model is built on one group and applied to another, for example a
later cohort that took the same test.

Here the model is fitted to the first 400 examinees and then used to classify
the remaining 100.

```{r predict-split, message=FALSE, warning=FALSE}
X <- J15S500$U
X[J15S500$Z == 0] <- NA
fit <- LRA(dataFormat(X[1:400, ]), nrank = 6)
pred <- predict(fit, X[401:500, ])
pred
head(round(pred$Membership, 3))
head(pred$Estimate)
```

`newdata` can be a matrix or data frame with the items in columns, or an object
made by `dataFormat()`. Items are matched by name, so the columns may come in
any order and extra columns are ignored; a missing item is an error. Missing
responses (`NA`, or the codes given in `na`) are simply left out of the
likelihood, so a respondent who answered only some items still gets a
membership. Polytomous responses must use the coding of the fitted data; a
category that the fitted data never used for an item cannot be scored and is
treated as missing, with a warning.

`pred$log_lik` holds each respondent's log-likelihood under the model. Summed
over held-out respondents, it gives a predictive criterion for comparing models
fitted to the same training data:

```{r predict-compare, message=FALSE, warning=FALSE}
fit4 <- LRA(dataFormat(X[1:400, ]), nrank = 4)
c(
  nrank4 = sum(predict(fit4, X[401:500, ])$log_lik),
  nrank6 = sum(pred$log_lik)
)
```

On the data a model was fitted to, `predict()` reproduces the memberships stored
in the fit. It works the same way for `LCA()` and for `Biclustering()` /
Ranklustering, with binary, nominal, ordinal and rated data; for biclustering
the field memberships of the items are held fixed as well.

## LRA for Ordinal Data

LRA can also handle ordinal scale data. The `mic` option enforces monotonic increasing constraints.

```{r model-lra-ordinal, eval=FALSE}
result.LRAord <- LRA(J15S3810, nrank = 3, mic = TRUE)
```

Score-rank relationship visualizations:

```{r plot-lra-ordinal-score, eval=FALSE}
plot(result.LRAord, type = "ScoreFreq")
plot(result.LRAord, type = "ScoreRank")
```

Item-rank relationship plots:

- **ICBR**: Item Category Boundary Reference -- cumulative probability curves for each category threshold
- **ICRP**: Item Category Response Profile -- probability of each response category across ranks

```{r plot-lra-ordinal-icbr, eval=FALSE}
plot(result.LRAord, type = "ICBR", items = 1:4, nc = 2, nr = 2)
plot(result.LRAord, type = "ICRP", items = 1:4, nc = 2, nr = 2)
```

Rank membership profiles for individual examinees:

```{r plot-lra-ordinal-rmp, eval=FALSE}
plot(result.LRAord, type = "RMP", students = 1:9, nc = 3, nr = 3)
```

## LRA for Rated/Nominal Data

For multiple-choice tests (nominal scale), LRA can analyze response patterns including distractor choices.

```{r lra-rated, eval=FALSE}
result.LRArated <- LRA(J35S5000, nrank = 10, mic = TRUE)
```

```{r plot-lra-rated-score, eval=FALSE}
plot(result.LRArated, type = "ScoreFreq")
plot(result.LRArated, type = "ScoreRank")
```

```{r plot-lra-rated-icrp, eval=FALSE}
plot(result.LRArated, type = "ICRP", items = 1:4, nc = 2, nr = 2)
```

## Reference

Shojima, K. (2022). *Test Data Engineering*. Springer.
