
Score new Persons under a saved external Bayesian calibration
Source:R/api-posterior-scoring.R
predict.mfrm_posterior_summary.RdEvaluate posterior ability distributions, cut-score decision probabilities,
and posterior predictive rating distributions for new Persons against a
validated read_mfrm_posterior() calibration without refitting the exported
Stan or brms model.
Usage
# S3 method for class 'mfrm_posterior_summary'
predict(
object,
newdata,
type = c("ability", "both", "predictive"),
conditioning = c("posterior", "plugin_mean"),
level = 0.95,
cut_scores = NULL,
quad_points = 31L,
missing = c("fail", "omit"),
persons = NULL,
allow_calibration_persons = FALSE,
repeated_ratings = NULL,
keep_person_draws = FALSE,
...
)
# S3 method for class 'mfrm_posterior_scores'
print(x, digits = 3L, ...)
# S3 method for class 'mfrm_posterior_scores'
summary(object, ...)
# S3 method for class 'summary.mfrm_posterior_scores'
print(x, digits = 3L, ...)
# S3 method for class 'mfrm_posterior_scores'
confint(object, parm = NULL, level = object$settings$level, ...)
# S3 method for class 'mfrm_posterior_scores'
as.data.frame(
x,
row.names = NULL,
optional = FALSE,
...,
what = c("estimates", "predictive", "decisions", "person_draws", "dispositions")
)
# S3 method for class 'mfrm_posterior_scores'
plot(
x,
type = c("intervals", "variance", "predictive"),
persons = NULL,
draw = TRUE,
reference = NULL,
preset = "standard",
show_legend = TRUE,
title = NULL,
subtitle = NULL,
caption = NULL,
...
)Arguments
- object
A validated
mfrm_posterior_summaryreturned byread_mfrm_posterior(). Supported models are ordinary"RSM", ordinary"PCM", and one-family"GPCM"(both fixed standard-normal and estimated-population Stan bundles). Two-family product-slope GPCM bundles are excluded and fail before scoring.- newdata
Non-empty
data.frameof long-format rating rows using the exported bundle's source column names (Person, declared non-Personfacets, andScore). New Person IDs are expected; all non-Person facet levels and non-missing score categories must exist in the saved calibration.- type
Output scope:
"ability"(default: Person posterior ability table and optionalcut_scoresdecisions),"both"(ability table plus row-level posterior predictive score distributions), or"predictive"(ability table plus row-level posterior predictive score distributions, defaultingas.data.frame()andplot()to predictive outputs when inspected).- conditioning
Calibration conditioning mode:
"posterior"(Default) Integrates ability continuously within each saved MCMC calibration draw \(\psi^{(m)}\) and averages the conditional posteriors equally across all \(M\) joint draws in
posterior-draws.rds. Propagates joint calibration uncertainty intoSD,[Lower, Upper],decisions, andpredictivewhile holding \(p(\psi \mid y_{\mathrm{cal}})\) fixed (new Person responses do not update calibration parameters). Fails closed whenposterior-draws.rdsis absent."plugin_mean"Scores conditional on a single calibration vector formed from marginal posterior means (
locations,steps,exp(mean(log_component_slopes))preserving unit geometric mean for one-family GPCM, andpopulation_mean/population_sdwhen present). Excludes calibration-estimation uncertainty (BetweenSDandCalibrationVarShareareNA).
Passing
"joint"fails with guidance to combine calibration and new rows inexport_mfrm_model(), because updating \(\psi\) from new Persons requires refitting the full hierarchical model.- level
Equal-tailed posterior credible interval probability strictly between
0and1(default0.95).- cut_scores
Optional finite, strictly increasing numeric vector of ability cut scores on the exported model's ability metric, with optional distinct names. Computes posterior probabilities above each cut and within each band (
Lower < ability <= Upper).- quad_points
Integer Gauss-Legendre order per sub-panel (
7to121, default31). Each Person is integrated on an eight-panel composite grid centered at the draw-specific posterior mode and verified at2 * quad_points + 1(63by default,504composite nodes). Ability summaries and predictive distributions are checked separately; discrepancies above1e-6withhold the affected output. Agreement between two orders is a numerical diagnostic, not a rigorous error bound.- missing
Missing-score policy for
newdata:"fail"(default) rejectsNAscores;"omit"omitsNArows when updating ability (returningStatus = "prior_only"if a Person has zero observed ratings, while still evaluating posterior predictive category probabilities for omitted rows whentypeincludes"predictive").- persons
Optional character vector of distinct Person IDs in
newdatato score and return;NULLscores all Persons in first-appearance order.- allow_calibration_persons
Logical (default
FALSE). Scoring a Person who already contributed to the calibration sample double-counts their ratings under \(p(\psi \mid y_{\mathrm{cal}})\). SetTRUEonly for explicit diagnostic or counterfactual comparisons; calibration members are flagged inCalibrationMemberand their out-of-samplePersonLogMarginalLikelihoodis set toNA.- repeated_ratings
Optional override (
"review"or"independent") for duplicate observed Person-by-facet cells innewdata;NULLinherits the exported bundle'srepeated_ratingssetting.- keep_person_draws
Logical (default
FALSE); ifTRUE, retains$person_drawswith each Person's per-draw conditional mean, conditional SD, and log marginal likelihood across the calibration draws.- ...
Unused; checked to be empty.
- x, parm, row.names, optional, digits, draw, reference, preset, show_legend, title, subtitle, caption
Arguments for
print(),summary(),confint(),as.data.frame(), andplot()methods onmfrm_posterior_scores.- what
Table component for
as.data.frame():"estimates"(default, or"predictive"whenx$settings$type == "predictive"),"predictive","decisions","person_draws", or"dispositions".
Value
An mfrm_posterior_scores object with:
estimatesOne row per scored Person with
Estimate,Median,SD,WithinSD,BetweenSD,CalibrationVarShare,Lower,Upper,PriorMean,PriorSD,PersonLogMarginalLikelihood,IntegrationMaxDiff,CalibrationMember,CalibrationUncertainty,SamplingReview,Status, andReason.predictiveRow-level posterior predictive expected scores,
PredictiveSD,ExpectedMeanSD,Residual,ModalScore, and category probabilities whentypeis"both"or"predictive"(NULLotherwise).IntegrationMaxDiffcompares category probabilities andExpectedMeanSDbetween quadrature orders for each row.StatusandReasondescribe predictive availability;PersonStatusdescribes the separate ability result. Failed predictive checks withhold probabilities and score summaries without suppressing otherwise valid ability results, includingprior_only. TheIntegrationMaxDiffinestimatesconcerns ability summaries, log likelihood and cut-score probabilities/SDs only.decisionsLong cut-score probability table (
above_cutandability_band) withProbabilityandProbabilityBetweenSDwhencut_scoresis supplied (NULLotherwise).person_drawsOptional per-draw conditional moments when
keep_person_draws = TRUE.source,posterior_decision,categories,data_usage,omitted_rows,settings,notesProvenance, row accounting, and interpretation notes.
Pass the result to summary(), confint(), as.data.frame(), plot(),
mfrm_report(), apa_table(), or export_summary_appendix().
Details
Three Bayesian scoring paradigms
Under the exported adjacent-category logit likelihood (\(D = 1\)), calibration parameters \(\psi = (\beta, \tau, \alpha, \mu, \sigma)\) are estimated on the calibration sample \(y_{\mathrm{cal}}\). When new rating rows \(y_{\mathrm{new}, p}\) arrive for a Person not in \(y_{\mathrm{cal}}\), three distinct quantities exist:
Fixed-calibration posterior mixture (
conditioning = "posterior"): $$p_{\mathrm{cal}}(\theta_{\mathrm{new}, p} \mid y_{\mathrm{new}, p}; y_{\mathrm{cal}}) = \int p(\theta_{\mathrm{new}, p} \mid y_{\mathrm{new}, p}, \psi)\, p(\psi \mid y_{\mathrm{cal}})\,d\psi \approx \frac{1}{M}\sum_{m=1}^{M} p(\theta_{\mathrm{new}, p} \mid y_{\mathrm{new}, p}, \psi^{(m)}).$$ New Person responses inform \(\theta_{\mathrm{new}, p}\) conditional on each draw \(\psi^{(m)}\) without reweighting or updating \(\psi\); therefore, a Person's score is invariant to which other new Persons are scored in the same batch. By the law of total variance on the equal-weight \(M\)-component mixture, $$\mathrm{SD}_p^2 = \underbrace{\frac{1}{M}\sum_{m=1}^{M} v_{p,m}}_{\mathrm{WithinSD}_p^2} + \underbrace{\frac{1}{M}\sum_{m=1}^{M}(\mu_{p,m} - \hat{\theta}_p)^2}_{\mathrm{BetweenSD}_p^2},$$ decomposing total posterior variance into conditional measurement variance (WithinSD^2) and calibration posterior variance (BetweenSD^2, shareCalibrationVarShare).Median,Lower, andUpperinvert the exact continuous mixture CDF \(F_p(x) = M^{-1}\sum_{m=1}^{M} F_{p,m}(x)\).Posterior-mean plug-in (
conditioning = "plugin_mean"): evaluates \(p(\theta_{\mathrm{new}, p} \mid y_{\mathrm{new}, p}, \hat{\psi})\) at marginal posterior means (using \(\hat{\alpha} = \exp(\overline{\log\alpha})\) for one-family GPCM so \(\prod_a \hat{\alpha}_a = 1\) holds). Available even whenposterior-draws.rdsis absent, but omits calibration uncertainty (BetweenSD = NA).Joint hierarchical refitting: updates \(\psi\) using both \(y_{\mathrm{cal}}\) and \(y_{\mathrm{new}}\); requires re-exporting the combined data with
export_mfrm_model().
Out-of-sample person likelihood vs predictive-draws.rds
Existing-Person log_lik arrays saved in predictive-draws.rds condition on
sampled calibration abilities \(\theta_j^{(m)}\) and therefore validate
conditional observation fit rather than leave-one-Person-out prediction. For
genuinely new Persons (CalibrationMember == FALSE),
PersonLogMarginalLikelihood integrates \(\theta_{\mathrm{new}, p}\) over
\(\mathcal{N}(\mu^{(m)}, (\sigma^{(m)})^2)\) within each draw before
averaging on the probability scale (logMeanExp across \(m = 1, \dots, M\)).
Examples
# 1. Full calibration-posterior mixture scoring from joint MCMC draws:
draws_dir <- system.file("extdata", "posterior-draws-example", package = "mfrmr")
joint_post <- read_mfrm_posterior(draws_dir)
new_ratings <- data.frame(
Person = c("NewP1", "NewP1", "NewP1", "NewP2", "NewP2", "NewP2"),
Rater = c("R01", "R02", "R03", "R01", "R02", "R03"),
Criterion = c("Accuracy", "Content", "Language", "Accuracy", "Content", "Language"),
Score = c(1, 2, 2, 3, 3, NA),
stringsAsFactors = FALSE
)
scored <- predict(
joint_post,
newdata = new_ratings,
type = "both",
missing = "omit",
cut_scores = c(Pass = 0.0, Merit = 1.0)
)
scored$estimates[, c("Person", "Observed", "Estimate", "SD", "WithinSD",
"BetweenSD", "Lower", "Upper", "Status")]
#> Person Observed Estimate SD WithinSD BetweenSD Lower Upper
#> 1 NewP1 3 -0.6130990 0.6609947 0.5483797 0.3690443 -2.019002 0.5408901
#> 2 NewP2 2 0.6076407 0.7293054 0.6638066 0.3020715 -0.763545 2.1501629
#> Status
#> 1 available_posterior
#> 2 available_posterior
scored$decisions[, c("Person", "Type", "Label", "Probability", "ProbabilityBetweenSD")]
#> Person Type Label Probability ProbabilityBetweenSD
#> 1 NewP1 above_cut Pass 0.180393159 0.167990971
#> 2 NewP1 above_cut Merit 0.002417658 0.006630414
#> 3 NewP1 ability_band Band1 0.819606841 0.167990971
#> 4 NewP1 ability_band Band2 0.177975501 0.165324586
#> 5 NewP1 ability_band Band3 0.002417658 0.006630414
#> 6 NewP2 above_cut Pass 0.806712676 0.113214909
#> 7 NewP2 above_cut Merit 0.273303346 0.148705013
#> 8 NewP2 ability_band Band1 0.193287324 0.113214909
#> 9 NewP2 ability_band Band2 0.533409330 0.106626782
#> 10 NewP2 ability_band Band3 0.273303346 0.148705013
head(scored$predictive[, c("Person", "Rater", "Criterion", "ObservedScore",
"ExpectedScore", "PredictiveSD")])
#> Person Rater Criterion ObservedScore ExpectedScore PredictiveSD
#> 1 NewP1 R01 Accuracy 1 2.022829 0.8962713
#> 2 NewP1 R02 Content 2 2.280687 0.9717236
#> 3 NewP1 R03 Language 2 1.912503 0.9971508
#> 4 NewP2 R01 Accuracy 3 2.808508 0.8418570
#> 5 NewP2 R02 Content 3 2.995436 0.9054100
#> 6 NewP2 R03 Language NA 2.905937 1.1059390
p_int <- plot(scored, type = "intervals", draw = FALSE)
plot_data(p_int, "table")[, c("Person", "Estimate", "Lower", "Upper", "Display")]
#> Person Estimate Lower Upper Display
#> 1 NewP1 -0.6130990 -2.019002 0.5408901 Sampling review required
#> 2 NewP2 0.6076407 -0.763545 2.1501629 Sampling review required
# 2. Marginal summary bundle without joint draws (explicit plug-in mean mode):
sum_dir <- system.file("extdata", "posterior-example", package = "mfrmr")
sum_post <- read_mfrm_posterior(sum_dir)
new_task_ratings <- data.frame(
Person = c("CandidateA", "CandidateA"),
Task = c("t1", "t2"),
Rater = c("r1", "r2"),
Score = c(2, 3),
stringsAsFactors = FALSE
)
plugin_scores <- predict(sum_post, newdata = new_task_ratings,
conditioning = "plugin_mean")
plugin_scores$estimates[, c("Person", "Estimate", "SD", "BetweenSD", "Status")]
#> Person Estimate SD BetweenSD Status
#> 1 CandidateA 0.8543098 0.7136646 NA available_conditional