--- title: "Adaptive comparative judgment: estimation, reliability, and evaluation" output: rmarkdown::html_vignette: toc: true toc_depth: 2 vignette: > %\VignetteIndexEntry{Adaptive comparative judgment: estimation, reliability, and evaluation} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} library(pairwiseLLM) has_withr <- requireNamespace("withr", quietly = TRUE) knitr::opts_chunk$set(collapse = TRUE, comment = "#>", eval = has_withr) ``` ## Five related questions Comparative judgment (CJ) uses decisions about pairs of items to estimate their relative strengths. An adaptive run chooses later pairs using earlier results. Good pair selection, precise estimates, and accurate predictions are related, but each needs its own assessment: **Pair selection → parameter estimation → internal reliability → empirical score recovery → held-out predictive performance.** | Step | Question | What to inspect | | --- | --- | --- | | Pair selection | Which comparison should be collected next? | Strategy, allowed pairs, presentation order, and selection logs. | | Parameter estimation | What strengths explain the collected outcomes? | Estimator, likelihood, adjustments, estimates, uncertainty, and convergence. | | Internal reliability | How large is estimated item spread relative to estimated uncertainty? | SSR or Bayesian EAP reliability, with their definitions and provenance. | | Empirical score recovery | How closely do scores reproduce an external reference? | Correlation, ranks, spread, and score differences against truth or a dense estimate. | | Held-out predictive performance | How well do probabilities predict new judgments? | Log loss, Brier score, and pairwise accuracy on outcomes withheld from selection and fitting. | The arrows organize the workflow; they do not imply that passing one step proves the next. A dense score estimate is itself uncertain, and high internal reliability does not establish agreement with it, judge validity, or predictive calibration. Calibration means that predicted chances agree with observed win frequencies over comparable predictions. This guide uses synthetic outcomes only. It makes no provider or network calls. The executable examples require the suggested `withr` package for local seeds; if it is absent, the code is displayed without execution. The conventional and Firth examples have additional optional-engine guards. Bayesian sampling is shown separately and never runs during document builds. Example sizes, penalties, and replicate counts illustrate interfaces, not recommended study settings. ## Start with comparisons chosen in advance Here `theta` is relative strength on a log-odds scale. For the simple BT model, the probability that A wins is `plogis(theta[A] - theta[B])`. Zero is a reference location, not a passing score. Separately fitted panels do not automatically share an origin or scale. We choose all ordered pairs before drawing any winners. A fixed training set, a denser reference set, and an evaluation set use separate random streams. They share items and pair identities but contain independently generated judgments. No evaluation outcome is used to choose pairs, fit scores, or select settings. This assesses new judgments on known items, not generalization to unseen items or judges. ```{r synthetic-data} ids <- letters[1:6] truth <- setNames(seq(-1, 1, length.out = length(ids)), ids) pairs <- expand.grid(object1 = ids, object2 = ids, stringsAsFactors = FALSE) pairs <- pairs[pairs$object1 != pairs$object2, ] simulate_rows <- function(repeats, seed) { dat <- pairs[rep(seq_len(nrow(pairs)), each = repeats), ] p <- plogis(truth[dat$object1] - truth[dat$object2]) dat$result <- withr::with_seed(seed, rbinom(nrow(dat), 1, p)) rownames(dat) <- NULL dat } fixed_data <- simulate_rows(2L, 30601L) reference_data <- simulate_rows(40L, 30602L) heldout_data <- simulate_rows(20L, 30603L) example_design <- data.frame( dataset = c("fixed training", "dense reference", "held-out evaluation"), outcome_seed = c(30601L, 30602L, 30603L), comparisons = c(nrow(fixed_data), nrow(reference_data), nrow(heldout_data)), selection = "all ordered pairs chosen before outcomes" ) example_design ``` `fit_bt_model()` accepts these three columns: `object1`, `object2`, and binary `result`, where 1 means the first item wins. For recorded winners in `ID1`/`ID2`/`better_id` or `A_id`/`B_id`/`better_id` columns, first use `build_bt_data()`. The [getting-started guide](getting-started.html) demonstrates that conversion with bundled public data. ### Conventional fitting and estimator provenance The default `engine = "auto"` tries sirt, then BradleyTerry2 if sirt is missing or its execution fails. Explicit engines never switch to another estimator. Validation failures such as disconnected comparisons are not repaired by that automatic fallback. Use an explicit engine when an analysis requires a particular method, and retain the returned `provenance`, including effective settings, identification, convergence, engine version, and any fallback reason. ```{r conventional-fit, eval=requireNamespace("withr", quietly=TRUE) && requireNamespace("sirt", quietly=TRUE)} conventional_fit <- fit_bt_model( fixed_data, engine = "sirt", sirt_eps = 0.3, verbose = FALSE ) summarize_bt_fit(conventional_fit) conventional_fit$ssr conventional_fit$provenance[c( "requested_engine", "engine", "engine_version", "package_version", "adjustment", "convergence", "fallback_reason" )] ``` The explicit `sirt_eps` above controls sirt's conventional epsilon adjustment; omitting it preserves the installed engine's default. It is not a lapse probability. sirt reports iterations rather than an explicit convergence flag; inspect its recorded stopping status as well as reliability. ### What the adjustments change | Estimator | Point-estimate adjustment | Reported uncertainty | | --- | --- | --- | | `engine = "sirt"` | Conventional epsilon adjustment depends on observed win proportions. | Engine SEs and independently checked SSR; retain resolved sirt settings. | | `engine = "brglm2"` | Firth / mean bias reduction adds half the log determinant of expected information to the log likelihood (`AS_mean`). | Inverse original-data expected information at the adjusted estimate, transformed to centered item coordinates. | | `engine = "alpha"` | Hamilton-style adjustment adds equal pseudo-wins in both directions for every possible pair, including unobserved pairs. | Inverse original-data expected information at the adjusted estimate, transformed to centered item coordinates. | Firth is an explicit option for random or nonadaptive schedules, including connected data with undefeated or winless items. Alpha adjustment is motivated by adaptive scheduling. The package does not infer a schedule type from outcomes, choose alpha, or establish that either adjustment is universally superior. Neither adjustment selects pairs. For \(N\) items, alpha adds \(c=\alpha/(N-1)\) pseudo-wins in each direction to every unordered pair. With \(p_{ij}=\operatorname{logit}^{-1}(\theta_i-\theta_j)\), the objective is \[ \ell_\alpha(\theta)=\ell(\theta)+ \frac{\alpha}{N-1}\sum_{i= 0.5) == y) ) } predictive_results <- rbind( adaptive = prediction_metrics(p_adaptive, heldout_data$result), dense_reference = prediction_metrics(p_reference, heldout_data$result), doubled_reference = prediction_metrics(p_expanded, heldout_data$result) ) predictive_results ``` The example classifies exactly 0.5 as an A win. It does not clip probabilities: a probability-zero realized event gives infinite log loss. Predictions here are plug-in probabilities from the alpha fits. The doubled reference retains the same predicted winners while moving probabilities farther from one half. Held-out losses can expose overconfidence that rank correlation or accuracy misses; no single finite test set guarantees this ordering. Keep evaluation outcomes out of adaptive state updates, reference fitting, penalty selection, and stopping decisions. If you tune a method using evaluation results, reserve another independent test set for the final assessment. Sharing item identities is intentional here, but copying the same judgment into training and evaluation would not be independent evaluation. ## Schedule-aware parametric bootstrap `bootstrap_bt_model()` simulates new binary BT outcomes from the initial fitted scores, refits the chosen estimator, aligns item IDs, and centers scores. For each item it computes \[ \widehat{\mathrm{bias}}_i=\overline{\theta_i^*}-\hat\theta_i, \qquad \hat\theta_{i,\mathrm{corrected}}=\hat\theta_i-\widehat{\mathrm{bias}}_i. \] This estimates point-estimate bias under the fitted generating model and the specified design. Corrections can change ranks and need not improve recovery. ### Fixed versus adaptive scheduling Fixed mode retains an outcome-independent schedule's ordered rows and repeats, and draws fresh outcomes. It is appropriate for the training pairs chosen in advance above: ```{r fixed-bootstrap} fixed_boot <- bootstrap_bt_model(fixed_fit, mode = "fixed", n_rep = 20L, seed = 1304L, keep = "theta") fixed_boot$theta fixed_boot$provenance[c("mode", "seed", "pairing_strategy", "refit", "versions")] ``` An adaptive schedule depends on earlier outcomes. Its bootstrap must repeat the selection decisions after simulating those outcomes. Freezing the observed adaptive pairs conditions on a realized schedule and answers a different question. The package uses the canonical runner, not a separate approximation to p50, Pollitt-inspired, random, or hybrid selection: ```{r adaptive-bootstrap} adaptive_boot <- bootstrap_bt_model(adaptive_fit, mode = "adaptive", n_rep = 4L, seed = 2304L, initial_state = initial, budget = 12L, btl_config = schedule_config, keep = "theta") adaptive_boot$theta adaptive_boot$replicates[, c( "replicate", "selector", "outcome", "scheduling_refit", "estimator", "success", "n_comparisons", "schedule_digest" )] adaptive_boot$provenance[c("mode", "seed", "pairing_strategy", "refit", "versions")] ``` The pristine initial connected pairs and presentations remain fixed; subsequent selector randomness and simulated outcomes use deterministic per-replicate seeds. Separate seeds cover selection, outcomes, scheduling refits, and the final estimator. Compatible software and deterministic callbacks are needed for exact reproduction. Serial and optional parallel execution use the same replicate seeds and aggregation order. Keep the original scheduling configuration. This example deliberately has no scheduled refits within its budget; a design that uses Bayesian refits must rerun them. In hybrid, those refits can affect later routing and tapering. The final bootstrap estimator is separate from the scheduling fitter. The bootstrap budget counts committed comparisons including initialization; statistical stopping does not shorten that fixed-budget experiment, but terminal selection exhaustion fails a replicate. Hybrid recovery remains hybrid, including inside bootstrap. ### What the Monte Carlo summaries mean | Output | Meaning | | --- | --- | | `bias`, `theta_corrected` | Estimated bias and corrected point estimate under the fitted model and specified schedule semantics. | | `bootstrap_sd` | Dispersion of the successfully refitted replicate estimates. | | `mcse_bias` | Simulation error in the estimated bias, calculated as `bootstrap_sd / sqrt(n_success)`. | | `n_success`, `n_failed`, `replicates` | Whether the correction has sufficient successful simulations and what happened in each replicate. | Neither bootstrap SD nor Monte Carlo error is a corrected-score standard error. The API does not provide corrected-score confidence intervals or corrected SSR. These small replicate counts keep the example fast; their Monte Carlo error can be substantial and they are not study recommendations. The default requires all replicates to succeed. A deliberately lower `min_success` excludes failures and therefore changes the summary to successful refits; inspect every failure. An unmet threshold raises an error carrying `condition$result` diagnostics and missing corrected scores. The built-in final estimators are alpha and Firth; other supported estimators require an explicit callback. The generating model is simple binary BT, without lapse, position, or ties. Lapse fits are rejected. Keep full fit/bootstrap objects to retain settings and provenance. The [bootstrap guide](bt-bootstrap.html) explains callback requirements, optional parallel workers, and artifact retention. ## Experimental lapse and positional-bias model matching `fit_bt_model(..., engine = "lapse")` uses the Bayesian `btl_e_b` likelihood with frequentist unpenalized maximum likelihood: \[ P(A\text{ wins})=(1-\epsilon) \operatorname{logit}^{-1}(\theta_A-\theta_B+\beta)+\epsilon/2. \] The item strengths sum to zero; positive positional beta favors the first presented item; lapse epsilon is the probability of guessing. Unlike conventional sirt epsilon, lapse epsilon is an estimated likelihood parameter. The estimator jointly fits all three parameter groups without Bayesian priors. It helps separate differences in probability models from differences in estimation; it is experimental, not the default BT estimator or a Bayesian replacement. The following separate four-item example uses both presentation directions and many inexpensive simulated binary rows to identify the extra parameters. The counts are demonstration choices, not guidance on the data needed in practice. ```{r lapse-fits} lapse_ids <- letters[1:4] lapse_truth <- setNames(seq(-2, 2, length.out = 4), lapse_ids) lapse_pairs <- expand.grid(object1 = lapse_ids, object2 = lapse_ids, stringsAsFactors = FALSE) lapse_pairs <- lapse_pairs[lapse_pairs$object1 != lapse_pairs$object2, ] lapse_pairs <- lapse_pairs[order(lapse_pairs$object1, lapse_pairs$object2), ] fit_lapse_example <- function(beta, epsilon, seed) { p <- (1 - epsilon) * plogis(lapse_truth[lapse_pairs$object1] - lapse_truth[lapse_pairs$object2] + beta) + epsilon / 2 wins <- withr::with_seed(seed, rbinom(nrow(lapse_pairs), 2000L, p)) dat <- lapse_pairs[rep(seq_len(nrow(lapse_pairs)), each = 2000L), ] dat$result <- unlist(lapply(wins, function(w) c(rep(1L, w), rep(0L, 2000L - w)))) fit_bt_model(dat, engine = "lapse", verbose = FALSE) } lapse_interior <- fit_lapse_example(beta = 0.3, epsilon = 0.2, seed = 305L) lapse_boundary <- fit_lapse_example(beta = 0, epsilon = 0, seed = 30501L) lapse_examples <- list(interior = lapse_interior, boundary = lapse_boundary) lapply(lapse_examples, function(fit) { list(engine = fit$engine, beta = fit$beta, epsilon = fit$epsilon, convergence = fit$provenance$convergence$status, uncertainty = fit$provenance$uncertainty, ssr = fit$ssr) }) list(selection = "all ordered pairs chosen before outcomes", outcome_seeds = c(interior = 305L, boundary = 30501L), model_variant = lapse_interior$model_variant, adjustment = lapse_interior$provenance$adjustment, package_version = lapse_interior$provenance$package_version) predict(lapse_boundary, newdata = lapse_pairs) ``` A valid **interior MLE** has \(0<\epsilon<1\) and passes identification, stationarity, and information/Hessian checks. Its joint covariance includes estimation of beta and epsilon; item SEs are local model-based approximations conditional on the realized graph, not a claim of repeated-sample calibration. A valid **epsilon-zero boundary MLE** needs no guessing component. It retains theta, beta, likelihoods, and predictions, with convergence status `converged_boundary`. Ordinary joint interior uncertainty is unavailable: `theta$se` is missing, `vcov` and `parameter_vcov` are `NULL`, and uncertainty status is `nonregular_boundary`. A small positive epsilon alone does not establish a boundary optimum. Epsilon one leaves strengths and beta unidentified and is rejected. Connectedness alone is insufficient for this model's identification, and genuine numerical/identification failures still error. Conventional SSR is unavailable for **both** interior and boundary lapse fits. Do not compute it manually from the interior SEs, or feed a lapse fit into the simple-BT bootstrap. See `fit_bt_model()` and `predict.pairwiseLLM_bt_lapse()` for the full experimental contract and failure diagnostics. ## Reporting the complete workflow Report which strategy collected the comparisons, which estimator fit them, its settings and uncertainty convention, and which internal reliability formula was used. Then report recovery against the stated reference separately from held-out predictive performance. Include score spread, evaluation-data provenance, seeds, and software versions. Retain selection logs and complete fit/bootstrap objects. The examples illustrate those distinctions. They do not establish universal estimator or strategy superiority, universal reliability calibration, suitability for a particular study, or equivalence of internal reliability and empirical recovery. ## Citation > Mercer, S. H. (2026). *Adaptive comparative judgment: estimation, reliability, and evaluation* [R package vignette]. Comprehensive R Archive Network. https://doi.org/10.32614/CRAN.package.pairwiseLLM