Introduction to ForceChoice

Haijiang Qin & Lei Guo

2026-07-22

Overview

The ForceChoice package provides a unified framework for fitting, simulating, and evaluating forced-choice and traditional item response theory (IRT) models. It supports eight model families with full Bayesian estimation via Stan (Hamiltonian Monte Carlo), a fast iterative stochastic EM (iStEM) algorithm, and a deterministic EM backend for FCGDINA.

This vignette is intended as a reproducible user guide rather than a complete methodological review. Model formulas and object components are described using the parameterization implemented in ForceChoice. The reference list gives DOI links where available so readers can verify the underlying psychometric sources.

Model Families

Family Description Data Type
MIRT Multidimensional IRT (1PL–4PL) Binary
MGPCM Multidimensional Generalized Partial Credit Polytomous
MGGUM Multidimensional Generalized Graded Unfolding Polytomous
FCMIRT Forced-Choice MIRT with Luce–Plackett ranking Ranking/MOLE/PICK
FCGGUM Forced-Choice GGUM with ranking Ranking/MOLE/PICK
TIRT Thurstonian IRT with pairwise probit Ranking/MOLE/PICK
FCDCM Forced-Choice Diagnostic Classification Paired comparison
FCGDINA Forced-Choice GDINA diagnostic model Ranking/MOLE/PICK

The forced-choice families accept full ranking ("RANK"), most-least ("MOLE"), or best-only ("PICK") data when supported by the corresponding fitting function. FCDCM is the exception: it is a paired-comparison model where every block contains exactly two statements.

Model and Backend Coverage

Family Main response process Backends Primary outputs
MIRT Dominance IRT for binary items Stan, iStEM item parameters, \(\theta\), factor correlations
MGPCM Dominance IRT for ordered categories Stan, iStEM category intercepts, \(\theta\), factor correlations
MGGUM Ideal-point/unfolding IRT Stan, iStEM slopes, locations, thresholds, \(\theta\)
FCMIRT MIRT endorsement plus Luce–Plackett ranking Stan, iStEM item parameters, \(\theta\), block fit
FCGGUM GGUM endorsement plus Luce–Plackett ranking Stan, iStEM unfolding item parameters, \(\theta\), block fit
TIRT Pairwise Thurstonian probit comparisons Stan, iStEM loadings, uniquenesses, \(\theta\)
FCDCM Higher-order DCM for two-statement FC blocks Stan, iStEM attribute profiles, higher-order parameters
FCGDINA GDINA/DINA/DINO/ACDM plus FC ranking Stan, iStEM, EM attribute profiles, CDM item parameters

Estimation Backends

Installation

# Development version from GitHub
remotes::install_github("Naidantu/ForceChoice")

Quick Start: Traditional IRT

Simulating and Fitting a MIRT Model

library(ForceChoice)

# Simulate binary response data
sim <- sim.data.MIRT(N = 20, I = 6, D = 2, model = "2PL")

# Fit via iStEM
fit <- fit.MIRT(sim$data, model = "2PL", D = 2,
                method = "iStEM",
                control.method = list(
                  vis = FALSE, seed = 123,
                  M = 2, B = 2, burnin.maxitr = 2,
                  maxitr = 3, eps1 = 10, eps2 = 10,
                  estimate.se = FALSE))

# Examine results
print(fit)
summary(fit)

# Item parameter estimates (first 6 items)
head(coef(fit))

# Factor correlation matrix
fit$Corr$est

# Trait recovery
diag(cor(fit$theta$est, sim$theta))

Goodness-of-Fit Evaluation

# Compute comprehensive fit indices
gof <- get.fit.index(fit)

# Summary of fit indices
summary(gof)

# Extract specific indices
gof$M2       # Limited-information M2 statistic
gof$RMSEA    # RMSEA with 90% CI
gof$CFI      # Comparative Fit Index
gof$TLI      # Tucker-Lewis Index
gof$SRMSR    # Standardized Root Mean Square Residual
gof$AIC      # Akaike Information Criterion
gof$BIC      # Bayesian Information Criterion

Forced-Choice Modeling

Simulating and Fitting FCMIRT

Forced-choice data uses ranking strings (e.g., "2>1>3" means item 2 is preferred over item 1 over item 3).

# Simulate forced-choice ranking data
sim <- sim.data.FCMIRT(N.person = 20, N.block = 3, I.block = 2,
                       D = 2, model = "2PL", fc.type = "RANK")

# The data contains ranking strings
head(sim$data)

# Fit: block.items and fc.type are auto-detected
fit <- fit.FCMIRT(sim$data, model = "2PL", D = 2,
                  method = "iStEM",
                  control.method = list(
                    vis = FALSE, seed = 123,
                    M = 2, B = 2, burnin.maxitr = 2,
                    maxitr = 3, eps1 = 10, eps2 = 10,
                    estimate.se = FALSE))

# Trait recovery
cor(fit$theta$est, sim$theta)

# Goodness-of-fit (uses nominal binary expansion)
gof <- get.fit.index(fit)
summary(gof)

Thurstonian IRT (TIRT)

sim <- sim.data.TIRT(N.person = 20, N.block = 3, I.block = 2,
                     D = 2, fc.type = "RANK")

fit <- fit.TIRT(sim$data, Q.matrix = sim$Q.matrix,
                block.items = sim$block.items,
                method = "iStEM",
                control.method = list(
                  vis = FALSE, seed = 123,
                  M = 2, B = 2, burnin.maxitr = 2,
                  maxitr = 3, eps1 = 10, eps2 = 10,
                  estimate.se = FALSE))

# Structural parameters: loadings and uniquenesses
head(coef(fit))

# Gamma matrix (pairwise intercepts)
fit$gamma.matrix$est[1:5, 1:5]

Forced-Choice DCM (FCDCM)

sim <- sim.data.FCDCM(N.person = 20, N.block = 3, D = 2,
                      dcm.type = "DINA")

fit <- fit.FCDCM(sim$data, Q.matrix = sim$Q.matrix,
                 block.items = sim$block.items,
                 method = "iStEM",
                 control.method = list(
                   vis = FALSE, seed = 123,
                   M = 2, B = 2, burnin.maxitr = 2,
                   maxitr = 3, eps1 = 10, eps2 = 10,
                   estimate.se = FALSE))

# Posterior attribute mastery probabilities
head(fit$alpha$prob)

# Attribute mastery proportions
colMeans(fit$alpha$prob > 0.5)

# Higher-order IRT parameters
fit$delta$est

Forced-Choice GDINA (FCGDINA)

FCGDINA is the diagnostic-classification counterpart for multi-statement forced-choice blocks. Unlike FCDCM, which is restricted to paired comparisons under a higher-order DCM structure, FCGDINA supports "GDINA", "DINA", "DINO", and "ACDM" statement-level models and can be fitted to full ranking, most-least, or best-only forced-choice data.

sim <- sim.data.FCGDINA(N.person = 20, N.block = 2, I.block = 2,
                        D = 2, model = "GDINA", fc.type = "RANK")

fit <- fit.FCGDINA(sim$data, Q.matrix = sim$Q.matrix,
                   block.items = sim$block.items, model = "GDINA",
                   fc.type = sim$fc.type, method = "EM",
                   control.method = list(vis = FALSE, seed = 123,
                                         maxitr = 2,
                                         estimate.se = FALSE))

# Posterior attribute mastery probabilities
head(fit$alpha$est)

# CDM item-parameter estimates
coef(fit, type = "delta")

Working with Polytomous Data

MGPCM (Dominance)

sim <- sim.data.MGPCM(N = 20, I = 6, D = 2, length.poly = 4)

fit <- fit.MGPCM(sim$data, D = 2, method = "iStEM",
                 control.method = list(
                   vis = FALSE, seed = 123,
                   M = 2, B = 2, burnin.maxitr = 2,
                   maxitr = 3, eps1 = 10, eps2 = 10,
                   estimate.se = FALSE))

# Category threshold parameters
coef(fit)

MGGUM (Ideal-Point / Unfolding)

sim <- sim.data.MGGUM(N = 20, I = 6, D = 2, length.poly = 4)

fit <- fit.MGGUM(sim$data, D = 2, method = "iStEM",
                 control.method = list(
                   vis = FALSE, seed = 123,
                   M = 2, B = 2, burnin.maxitr = 2,
                   maxitr = 3, eps1 = 10, eps2 = 10,
                   estimate.se = FALSE))

coef(fit)

Solution Rotation

For exploratory MIRT analyses, post-hoc rotation helps achieve simple structure:

fit_rot <- rotate.MIRT(fit, rotate = "oblimin")

# Compare original and rotated loadings
head(coef(fit))
head(coef(fit_rot))

Diagnostic Plots

# Item characteristic curves (ICC)
plot(fit, type = "icc", items = 1:8)

# Person parameter distributions
plot(fit, type = "theta")

# iStEM convergence trace
plot(fit, type = "trace")

Estimation Control

Common Parameters (top-level)

fit <- fit.MIRT(data, model = "2PL", D = 2, method = "iStEM",
                control.method = list(
                  vis = FALSE,
                  seed = 123,      # Reproducibility
                  M = 2, B = 2, burnin.maxitr = 2,
                  maxitr = 3, eps1 = 10, eps2 = 10,
                  estimate.se = FALSE))

Stan-Specific Control

# stan code, long time
fit <- fit.MIRT(data, model = "2PL", D = 2, method = "stan",
                control.method = list(chains = 1, iter = 200,
                                      warmup = 100, cores = 1,
                                      seed = 123))

iStEM-Specific Control (advanced)

fit <- fit.MIRT(data, model = "2PL", D = 2, method = "iStEM",
                control.method = list(
                  seed = 123,
                  M = 2,            # Burn-in batches for convergence
                  B = 2,            # Iterations per batch
                  burnin.maxitr = 2,
                  maxitr = 3,
                  eps1 = 1.5,       # Geweke convergence threshold
                  eps2 = 0.4        # MC error tolerance
                ))

References