Fit a rubric calibration to completed comparative judgments
Source:R/rubric_calibration.R
fit_rubric_calibration.RdEstablish a trait-specific rubric conversion from completed Bayesian BTL results. Percentile scoring assigns deterministic, distribution-matched performance levels. Linear ordinal calibration fits human rubric labels with a proportional-odds cumulative-logit model. Monotone ordinal calibration replaces its linear effect with a nondecreasing penalized cubic spline.
Usage
fit_rubric_calibration(
cj,
rubric = NULL,
method = "ordinal_linear",
calibration_design = "same_set",
trait = NULL,
levels = NULL,
K = NULL,
target_distribution = NULL,
...
)Arguments
- cj
A completed
fit_bayes_btl_mcmc()result, a completed within-setadaptive_rank()result (or itsadaptive_state), or an import-ready Phase A artifact list. Fixed results with several refits use the last refit. Raw score tables and intermediate adaptive states are not accepted.- rubric
Data frame with
item_idandrubric_score, optionallytrait. Rows align by ID, never position. Missing scores denote unlabeled items. Use global item IDs for adaptive results and Phase A artifacts that provide them; otherwise use the original fixed-fit IDs. Omit for percentile scoring.- method
One of
"ordinal_linear"(default),"ordinal_monotone", or"percentile". Percentile scoring is norm-referenced/distribution-matched.- calibration_design
"same_set"for one completed CJ scale, or"linked_anchors"for a reusable Phase A rubric reference artifact. Both ordinal methods support both designs. Percentile scoring supports only"same_set".- trait
Single trait identifier. Required when absent from CJ metadata. Each analytic trait requires its own CJ analysis and calibration.
- levels
Ordered original rubric labels, from lowest to highest quality. When omitted, use ordered-factor levels or ascending numeric rubric labels. Character and unordered-factor scores require explicit levels.
- K
Optional number of levels, an integer >= 3. Must agree with
levelsand any target proportions. Typically 3–6.- target_distribution
Optional positive proportions in level order, summing to one, for percentile scoring. Omission requests equal proportions. The absolute sum tolerance is
1e-8; accepted proportions are normalized to sum to one for computation, while the requested values are retained. Named proportions must match the labels in their specified order.- ...
For
ordinal_monotoneonly, uniquely namedkandspcontrols.kis the cubic basis dimension (default 6, integer >= 5). It is reduced with a warning to the number of unique labeled CJ scores if necessary; fewer than five unique scores fail.sp = NULL(default) estimates smoothing by extended Fellner-Schall (EFS); a finite positive scalar fixes the penalty parameter. Other methods require empty dots. Unknown controls fail.
Value
For percentile scoring, a fitted pairwiseLLM_rubric_calibration
containing labels, K, normalized cj, source calibration_range, trait,
provenance, diagnostics, and an identity transformation (center 0, scale 1).
Its backend stores quantile_type, cutpoints, cumulative_probs,
requested_proportions, normalized effective_proportions,
achieved_proportions, achieved_counts, and cutpoint_tie_counts.
Category summaries include every requested level, including empty levels.
target_distribution retains the requested proportions (or equal defaults).
calibration_data and category_counts remain NULL because no human labels
are fitted. diagnostics$category_probabilities_available is FALSE.
For linear ordinal calibration, the same class retains ID-aligned
calibration_data (including unlabeled items), category_counts, and the
labeled calibration_range. transformation$center and $scale store
mu_cal and sigma_cal. The backend contains name, version, link,
threshold, the fitted model, thresholds, slope, vcov,
standard_errors, and convergence. With positive slope, cutpoints_z
stores tau / beta and cutpoints_theta maps these to the original scale.
Otherwise cutpoints are omitted. Unavailable coefficient uncertainty is
represented by NA, with a warning. diagnostics$ordinal reports numerical
convergence, gradient, Hessian condition, covariance availability,
nonpositive slope, singleton categories, and conditioning on CJ locations.
warnings retains ordinal diagnostic messages and
diagnostics$category_probabilities_available is TRUE.
Monotone ordinal calibration uses the same class, data, transformation,
and probability availability flag. Its backend stores name, version,
cumulative link, latent_link, fitted model, thresholds, intercept,
basis (requested/effective dimension, constraint, order, knots),
smoothing (method, requested/fitted penalty parameters, penalty matrices),
total edf, smooth_edf, convergence, threshold_standard_errors,
cutpoints_z, cutpoints_theta, and cutpoint_status ("unique",
"outside_range", or "flat_or_unresolved"). diagnostics$ordinal stores
convergence, singleton categories, conditioning on CJ, unavailable threshold
uncertainty, and numerical monotonicity results.
Linked calibrations additionally retain reference: the reference set_id,
sorted stable item IDs with original Phase A locations/SDs, canonical
fit_contract, original fit_contract_hash, and within-set evidence/hash.
Original reference SDs are retained separately from Phase B's locked hub SDs.
Details
Higher CJ locations and higher ordered rubric levels must mean better performance; no orientation is silently reversed. All requested categories must be observed for ordinal calibration.
Fitting ordinal_linear requires the optional package ordinal.
Install it with install.packages("ordinal"). Percentile scoring and
prediction from an already fitted linear calibration do not require it.
Monotone ordinal fitting and prediction require optional mgcv >= 1.9-4
(install.packages("mgcv")). Fitting also requires optional withr
(install.packages("withr")) to isolate backend RNG use with a fixed internal
seed. Saved monotone models require mgcv for spline prediction.
For both ordinal methods, calibration items must belong to one completed
trait-specific CJ fit. Only labeled rows estimate the calibration:
z = (theta - mu_cal) / sigma_cal, where mu_cal is their mean and
sigma_cal their sample standard deviation. Both are stored and reused.
ordinal::clm() fits logit P(Y <= k | z) = tau_k - beta * z with flexible
ordered thresholds. Zero or negative slopes produce a diagnostic warning;
coding is never reversed. Missing categories or degenerate calibration scores
fail. Singleton categories and numerical convergence/identification problems
produce warnings; no universal minimum calibration sample size is enforced.
Finite ordered estimates can be retained with warnings even when convergence
or uncertainty is unreliable. Such warnings require review before use.
Standard errors condition on estimated CJ point locations; CJ measurement
uncertainty is not propagated. evaluate_rubric_predictions() supplies
metrics and optional assumption diagnostics; diagnostic refits run only when
requested during evaluation. Internal rubric-label validation holds CJ fixed
and refits calibration from training labels alone.
ordinal_monotone uses mgcv::scasm() with
s(z, bs = "sc", xt = "m+", k = k) and mgcv::ocat() with integer categories
1:K. The model is logit P(Y <= k | z) = tau_k - eta(z), where eta includes
an intercept and a nondecreasing smooth. The backend fixes the first threshold
at -1 for identification; its identity link describes the latent location,
while category cumulative probabilities follow the logistic distribution.
Bootstrap is disabled. Smoothing is estimated by EFS unless sp is fixed.
A 1,001-point calibration grid verifies nondecreasing latent locations and
nonincreasing cumulative probabilities with tolerance 1e-8. Invalid
probabilities or monotonicity failures abort; no unconstrained fallback is used.
Essentially flat effects, singleton categories, and convergence problems warn.
Small bases and some reversed/separated label patterns can fail in the backend;
these return contextual fit errors. The five-unique-score requirement is a
backend feasibility guard, not a recommended calibration sample size.
Threshold uncertainty is unavailable from this wrapper (the fixed first
threshold has standard error zero; the remaining entries are NA).
Numerical median cutpoints are reported only for unique crossings within the
calibration range. Flat or unresolved crossings and thresholds outside the
range have NA cutpoints and an explanatory status; scoring always uses
probabilities. Predictions beyond the labeled range are flagged and retain
the backend's spline extrapolation; their calibration is not established.
Percentile scoring uses stats::quantile() with type 8 at cumulative target
proportions. This quantile type is fixed. A score equal to a cutpoint enters
the higher category: category is one plus the number of cutpoints less than
or equal to the score. Repeated cutpoints can leave categories empty; all
exact-score ties receive the same category, even when every score is equal
(in which case all items enter the highest category). Requested proportions
may be unattainable because of ties or finite sample size. Cutpoint tie counts
count all source observations exactly equal to each cutpoint, including a
count of one when only one observation equals that cutpoint.
These are norm-referenced CJ-derived performance levels, not criterion-referenced scores. Matching a historical rubric distribution does not establish agreement with rubric raters. Recomputing cutpoints on another cohort changes the reference distribution. Stored percentile cutpoints support reuse of the original completed result only; independent cohorts and linked target prediction are not supported. Scoring conditions on accepted point locations, even when CJ posterior draws are available. Percentile category probabilities and uncertainty propagation are unavailable.
Phase A artifacts are checked using existing import-readiness rules, including the existing explicit quality-gate override. Import readiness does not assert that the originating adaptive run terminated. Existing CJ diagnostics are retained; failed diagnostics produce a warning.
With linked_anchors, first obtain an import-ready Phase A artifact for the
human-scored rubric_reference_set and fit its ordinal calibration. Next run
Phase A for the target set, then the existing adaptive_rank() Phase B linking
workflow with the rubric reference set as hub and targets as one or more
spokes. Pass the completed linking result (or its state) to predict().
Rubric labels are used for calibration; Phase B does not require them.
A rubric reference set contains externally scored material; a Phase B
hub anchor is an item selected for routing recurring comparisons.
Historical prediction validates the stored reference set, stable item IDs,
exact reference locations and uncertainty, within-set evidence, trait,
orientation, and canonical fit contract. A configuration hash alone does not
establish metric identity. Original hashes are retained as provenance;
compatible legacy hashes follow the existing Phase A import rules.
Target-only Phase A scores cannot be used with the stored calibration.
Prediction consumes accepted Phase B common-scale scores and reuses the
stored reference transformation, never the target cohort's mean or SD.
Phase B's theta_link_eap field
represents its accepted MAP location with Laplace/Hessian uncertainty.
Rubric scoring is downstream of CJ estimation and does not run comparisons
or change Phase B estimation. Ordinal calibration conditions on accepted
CJ locations; joint CJ/rubric likelihood estimation is outside this API.
See also
predict.pairwiseLLM_rubric_calibration(), evaluate_rubric_predictions(),
vignette("rubric-calibration", package = "pairwiseLLM") for practical workflows.
Other rubric calibration:
evaluate_rubric_predictions(),
predict.pairwiseLLM_rubric_calibration()
Examples
if (FALSE) { # \dontrun{
# Starting from an already completed CJ result; no sampling occurs here.
fit <- fit_rubric_calibration(completed_cj, method = "percentile",
trait = "organization", levels = c("developing", "proficient", "advanced"),
target_distribution = c(0.2, 0.5, 0.3))
predict(fit)
fit$backend$achieved_proportions
fit$backend$cutpoint_tie_counts
# Human labels cover a subset of this same completed CJ result.
if (requireNamespace("ordinal", quietly = TRUE)) {
ordinal_fit <- fit_rubric_calibration(completed_cj, rubric = rubric_labels,
trait = "organization", levels = c("developing", "proficient", "advanced"))
predictions <- predict(ordinal_fit)
predictions$probabilities
predictions$expected_level
}
if (requireNamespace("mgcv", quietly = TRUE) &&
packageVersion("mgcv") >= "1.9.4" && requireNamespace("withr", quietly = TRUE)) {
monotone_fit <- fit_rubric_calibration(completed_cj, rubric = rubric_labels,
method = "ordinal_monotone", trait = "organization",
levels = c("developing", "proficient", "advanced"), k = 6)
predict(monotone_fit)$probabilities
}
# Reference and target Phase A, followed by Phase B, are completed upstream.
if (requireNamespace("ordinal", quietly = TRUE)) {
linked_fit <- fit_rubric_calibration(reference_phase_a, reference_labels,
calibration_design = "linked_anchors", trait = "organization",
levels = c("developing", "proficient", "advanced"))
predict(linked_fit) # Original reference items.
target_scores <- predict(linked_fit, completed_phase_b)
target_scores$set_id
attr(target_scores, "linking")$diagnostics
}
} # }