This vignette covers the package-native route for:
For a broader workflow guide, see
vignette("mfrmr-workflow", package = "mfrmr"). For the
shorter help-page map, see
help("mfrmr_linking_and_dff", package = "mfrmr").
An anchor is a parameter value held fixed during estimation. A shared Person, rater or task is an observed link; sharing an identity does not require fixing its value. For example, a direct Rater anchor at 0.35 logits holds that rater’s location at 0.35, whereas a group anchor at 0.20 for two raters fixes their sum at 0.40 and lets their individual values differ.
Start by loading the packaged synthetic teaching ratings. Each row is one Person’s score from one Rater on one Criterion, on a 1–4 rubric. We split the 48 Persons into two teaching cohorts with the same four raters and four criteria. This illustrates reuse of known identities; it is not empirical evidence that a real rater or criterion stays invariant over time.
library(mfrmr)
ratings <- load_mfrmr_data("example_core")
head(ratings[, c("Person", "Rater", "Criterion", "Score")])
people <- unique(ratings$Person)
baseline_data <- ratings[ratings$Person %in% people[1:24], , drop = FALSE]
new_data <- ratings[ratings$Person %in% people[25:48], , drop = FALSE]Fit the reference and select its Criterion locations.
make_anchor_table() requires a numerically eligible source,
but does not establish substantive invariance. If it refuses a source,
inspect that model’s diagnostics; exporting with
readiness_policy = "review" is for inspection, not for
bypassing the requirement in a new calibration.
baseline <- fit_mfrm(
baseline_data, "Person", c("Rater", "Criterion"), "Score",
method = "MML", rating_min = 1, rating_max = 4,
category_policy = "preserve"
)
anchors <- make_anchor_table(baseline, facets = "Criterion")
anchorsThe columns name the facet, its exact level ID, and its fixed
location in internal logit coordinates. They do not contain thresholds,
slopes or source standard errors. Values transformed for display with
Umean/Uscale must not be copied into this
table as though they were internal logits. The default
digits = NULL preserves fitted values for reuse. Rounding
each row can break a whole-facet zero sum, so round a display copy
rather than the anchor input.
Review the intended constraints against the receiving data before fitting. Unknown IDs, duplicate rows and incompatible targets need resolution; keeping the last duplicate row mechanically does not decide which value is justified.
anchor_review <- review_mfrm_anchors(
new_data, "Person", c("Rater", "Criterion"), "Score", anchors = anchors,
rating_min = 1, rating_max = 4, category_policy = "preserve"
)
anchor_review$issue_counts
anchor_review$anchors
anchor_review$recommendationsThe recommendation about five anchors is a configurable count screen,
not a universal minimum or proof that a design is linked.
FreeLevels counts levels outside direct/group constraints,
not independent parameters: group members can vary subject to their
mean, while centering removes a degree of freedom.
Now fit the new cohort. This example fixes all four Criterion locations, so that facet has no unanchored remainder. Rater locations and category thresholds are estimated again. The same rubric is specified explicitly in both fits.
linked <- anchor_to_baseline(
new_data, baseline, "Person", c("Rater", "Criterion"), "Score",
anchor_facets = "Criterion", anchor_policy = "error",
rating_min = 1, rating_max = 4, category_policy = "preserve"
)
summary(linked)
linked$fit$facets$others
linked$drift[, c("Facet", "Level", "Baseline", "New", "Drift")]Read linked$fit$facets$others for the fitted facet
locations. linked$drift compares only retained
fixed non-Person anchors: its zero differences are
expected by construction and do not test stability. For independent
screening, fit waves separately and use
detect_anchor_drift(), retaining its offset, common-element
and uncertainty limitations. Keep the full linked object
when saving the analysis, so its fitted model and supplied anchors stay
together.
noncenter_facet,
centering_scope = "facet" makes all its levels sum to zero.
With R01 = 0.70, the other ungrouped raters sum to -0.70; a single
remaining rater is fixed at -0.70. Group totals also contribute.
Incompatible totals stop before fitting, even when
anchor_policy = "silent".
centering_scope = "remainder" reproduces the earlier
restriction that the ungrouped, unanchored levels sum to zero. Saved
results are not converted by reloading them; fitting under a different
convention requires re-estimation. For ordinary additive RSM/PCM JML,
centering_scope = "anchor_origin" instead lets direct or
group anchors replace their facet’s centering, as FACETS does. Choose an
unanchored, active noncenter facet and repeat the same arithmetic mean
target for each group member. This does not change the default or
account for estimated-anchor error."remainder". Repeat the intended category policy,
rubric, orientation, PCM/GPCM roles, uncentered facet and population
model. RSM/PCM MML without a population model fixes ability to a
standard-normal distribution; it does not estimate a changed cohort
mean/variance. An explicitly requested
population_formula = ~ 1 estimates normal-population
moments and needs person_data; see
help("fit_mfrm") for its assumptions.extract_mfrm_calibration(), validate and
freeze the saved calibration, then call
score_mfrm_calibration(). See
help("mfrm_calibration_workflow") for its supported models
and required checks.The FACETS centering rules help distinguish an origin constraint from a fixed external value, but are not mfrmr’s partial-anchor convention. Uto (2021, pp. 1442–1444) explains common-rater/task linking and its design dependence. That simulation fixes generated common parameters and removes a redundant rater constraint; its Bayesian estimation does not validate mfrmr’s different constraints or supply uncertainty propagation for estimated anchors.
Suppose each year’s contestants give a three-minute English speech. Most judges return and use the same rubric. Before the contest, they rate a set of recorded speeches used in previous years. You want to know whether judges became more severe, whether the whole panel changed its standard, and whether the new contestant group performed differently on a common scale.
The common recordings offer a link: the performance in a fixed recording does not improve from one year to the next. Its interpretation must still be comparable under the rubric and viewing conditions. An unchanged time limit or criterion name alone does not establish this. Because contestants change, a difference between annual groups is not individual growth.
This section explains the design and the limits of current functions. A complete recording-based annual analysis, including formal year-difference intervals, is not yet verified in mfrmr. The teaching cohorts in the first example above do not contain annual reratings of common recordings.
Keep one row per individual rating on one criterion. The following IDs illustrate identities to preserve; they are not supplied example data.
| Observation | Performance ID | Judge ID | Rating year |
|---|---|---|---|
| Judge A rates reference video V01 in 2025 | V01 | A | 2025 |
| Judge A rates that same video in 2026 | V01 | A | 2026 |
| Judge A rates a new 2026 contestant’s speech | Speech_2026_01 | A | 2026 |
Also retain criterion, score, session, training phase and video/live mode. The same speaker giving a new speech needs a new performance ID when the measurement target is that speech. Preserve individual ratings before discussion or disclosure of reference scores. Consensus scores alone cannot recover differences among judges. Include performances spanning relevant levels and criterion profiles; there is no universal adequate recording count. The five-common-element helper warning is not such a rule.
| Output | What it shows | What it does not establish |
|---|---|---|
linked$fit$facets$others from
anchor_to_baseline() |
Facet locations under the chosen constraints | A formal annual comparison without compatible scales and an appropriate contrast analysis |
linked$drift |
Whether retained fixed non-Person anchors equal the supplied values | Stability of those elements, or a drift estimate for the reference recordings |
drift$drift_table from
detect_anchor_drift() |
Common-element differences after removing
LinkOffset |
Absolute panel change, or calibrated significance of the SE ratio |
chain$cumulative from
build_equating_chain() |
Accumulated screened adjacent-wave offsets | Propagated uncertainty or proof of annual scale comparability |
When judges are the linking set, detect_anchor_drift()
subtracts their common shift. If all selected differences are +0.40
logits, LinkOffset is +0.40 and every residual
Drift is zero. This describes relative alignment, not
evidence that the panel was stable. Interpreting LinkOffset
itself as panel change is also unjustified without a separate scale
reference.
Agreement after training, operational stability and correctness against an expert benchmark are different outcomes. Check familiar-score memory, for example with additional recordings not used to teach the reference scores. If the contest is rated live but the calibration uses video, paired live/video ratings of some performances can help assess whether the estimated severity transfers. Repeated ratings can be dependent even when their IDs are correct.
Annual-difference uncertainty needs the covariance between the relevant estimates, or propagation of reference-calibration uncertainty where applicable. Adding two squared SEs assumes away covariance; marginal interval overlap is not a test of change. Report the reference, assumptions, number of common performances and unavailable results alongside any point comparisons.
An anchor does not make every interval unavailable. For ordinary
unit-weight RSM/PCM MML with a fixed standard-normal population and
fixed-grid integration, mfrm_facet_intervals() can supply
model-information or cluster-sandwich intervals for free facet locations
and contrasts, subject to its checks. They condition on the supplied
anchors. A fixed target has SE zero and no inferential bounds; this does
not describe the uncertainty of its original calibration.
If the population mean and variance are also estimated, anchored
point estimation is supported, but the native location and population
interval helpers currently exclude anchors and group constraints.
Inspect fit$readiness$fit:
EstimabilityState = "not_evaluated" means the
identification review is incomplete, not that numerical optimization
failed or the model was proved unidentified. The separate
fit$data_review$estimability$nonlinear_local_estimability
can pass its local-rank check while overall readiness remains under
review. That local result alone does not authorize intervals or
exporting anchors for reuse. Choose the population assumption for the
assessment question, not simply because one configuration supplies
intervals.
For one-family GPCM, model-information and
cluster-sandwich slope and curve intervals can include fixed facet
anchors or group means when their own numerical checks pass. Use
confint(fit, parm = "slopes") for relative slopes and
mfrm_curve_intervals(fit, newdata) for curves at specified
ability values and rating contexts. Their returned explanations state
that source-anchor uncertainty is excluded. Fixing a rater’s location
does not fix its slope, category thresholds or entire response curve. A
group mean does not fix each member.
The restrictions differ by output. Anchored one-family GPCM currently
has no native-location or profile-slope interval route, and no
portable-calibration export. Fitted-object scoring with
predict_mfrm_units() has separate source and scoring
checks. An available slope interval does not certify that source for
scoring: its finer-grid integration check may still fail. Explicit
readiness_policy = "review" retains review-only values and
does not make them operational scores. These distinctions also apply
after saving results.
Start with what you want to preserve or change. Saving a new copy does not convert the statistical model.
| Intended task | Required action | What changes? |
|---|---|---|
| Reopen the original fit | readRDS(); inspect
summary(fit, compute = "never") and
specifications_report(fit) |
No estimates or constraints. A native fit lacking convention metadata retains legacy remainder centering. |
| Repeat the original estimation | Generate build_mfrm_replay_script() and run it with the
original data |
A new fit under the original convention, with newly evaluated readiness. |
| Adopt whole-facet centering | Refit with centering_scope = "facet", preserving the
intended other settings |
The anchored constraints and potentially estimates, SEs and conclusions. |
| Update interval explanations or fixed-contrast derivatives | Recompute the interval object from its unchanged source fit | Derived output; no model refit is needed solely for this update. |
| Repair earlier anchored subgroup DFF | Rerun analyze_dff() from the baseline |
Subgroup models are refitted; reading the old DFF object does not repair them. |
| Describe an old portable calibration more precisely | Re-extract from its source fit with the required checks, then validate and freeze a new artifact | Explicit constraint labels; this alone does not change its coefficients or convention. |
For a runnable illustration, create an analysis with the legacy convention using the packaged ratings already loaded above. If you already have an old fit file, start by reading that file instead. The example fixes a Rater location, uses RSM with a fixed standard-normal population and keeps Person as the uncentered facet. These choices illustrate migration; choose the population assumption for your assessment question.
migration_anchor <- data.frame(Facet = "Rater", Level = "R01", Anchor = 0.70)
legacy_fit <- fit_mfrm(
ratings, "Person", c("Rater", "Criterion"), "Score",
model = "RSM", method = "MML", anchors = migration_anchor,
noncenter_facet = "Person", centering_scope = "remainder",
rating_min = 1, rating_max = 4, category_policy = "preserve",
quad_points = 31, maxit = 400, reltol = 1e-9
)
fit_file <- tempfile(fileext = ".rds")
saveRDS(legacy_fit, fit_file)
restored_fit <- readRDS(fit_file)
unlink(fit_file)
spec <- specifications_report(restored_fit)$data_spec
spec[spec$Setting %in% c("NonCenteredFacet", "CenteringScope"), ]CenteringScope = "remainder" describes the fitted
constraint, including for native files saved before this field existed.
It is not changed to the new default merely because a newer package
reads the file. Do not assign a new value to
fit$config$centering_scope or recenter its coefficient
table: the likelihood, parameter mapping and uncertainty still belong to
the original fit.
To change the convention, fit a separate model from the original responses. Here only the declared centering changes. Inspect its numerical and design status; the number of quadrature points is not an accuracy guarantee.
updated_fit <- fit_mfrm(
ratings, "Person", c("Rater", "Criterion"), "Score",
model = "RSM", method = "MML", anchors = migration_anchor,
noncenter_facet = "Person", centering_scope = "facet",
rating_min = 1, rating_max = 4, category_policy = "preserve",
quad_points = 31, maxit = 400, reltol = 1e-9
)
summary(updated_fit, compute = "never", detail = "brief")
old_raters <- subset(restored_fit$facets$others, Facet == "Rater")
new_raters <- subset(updated_fit$facets$others, Facet == "Rater")
data.frame(
Convention = c("remainder", "facet"),
FixedR01 = c(old_raters$Estimate[old_raters$Level == "R01"],
new_raters$Estimate[new_raters$Level == "R01"]),
OtherRaterSum = c(sum(old_raters$Estimate[old_raters$Level != "R01"]),
sum(new_raters$Estimate[new_raters$Level != "R01"]))
)Both fits keep R01 at 0.70. The remaining raters sum to zero in the legacy fit and -0.70 in the whole-facet fit. This changes R01’s comparison with the other raters; it is not just a renamed column. Retain the two analyses under different filenames, and report the convention with the other model settings. Complete facet anchors can also become incompatible with a whole-facet zero sum. Resolve that model specification explicitly rather than rounding anchors or silently switching the population model.
For more complex analyses,
build_mfrm_replay_script(restored_fit, data_file = "ratings.csv")
provides a recipe with stored anchors, group means and the original
convention. It requires the original analysis data at the specified
path. Review its generated fit_mfrm() call before
deliberately changing the convention; preserve population covariates,
category handling, orientation, uncentered facets and PCM/GPCM owners.
This recipe refits. By contrast, the replay written by
export_mfrm_results() reopens its saved results bundle
without reconstructing the model.
If only a frozen calibration survives, retain its original scoring
meaning. Generic old identification labels are insufficient to infer its
centering convention. See
vignette("mfrmr-portable-calibration", package = "mfrmr")
for re-extraction and validation; changing a label is not recalibration.
Earlier MML fits containing Person anchors require special review
because those values were ignored. Current fitting rejects them;
choosing centering_scope = "remainder" does not repair that
earlier analysis.
An anchor is a fixed value in all three programs, but the surrounding constraints determine what that value means. Copying a number from another program is insufficient when its parameterization or population model differs.
| Question | mfrmr | TAM | ConQuest |
|---|---|---|---|
| What is fixed? | A named facet location in anchors |
Indexed response-design coefficients in xsi.fixed |
Response-design coefficients imported with
anchor_parameters / anchor_xsi |
| Are steps and slopes included automatically? | No; the location-anchor table omits them | Check the response and scoring designs; slope controls are separate | Check the response design and scoring parameters;
anchor_tau is separate |
| How is the population specified? | Ordinary RSM/PCM MML defaults to fixed standard-normal ability; population modeling is explicit | Specify beta.fixed, variance.fixed and the
selected fitting function’s other controls |
Specify population anchors and location/scale constraints explicitly |
| Can Person values be fixed? | Supported in JML; rejected in MML | tam.jml() documents theta.fixed for
version = 1 |
The manual states that anchor_theta is ignored in
MML |
The TAM
MML documentation, TAM
JML documentation and ConQuest command
reference describe these controls. For ConQuest, read
import together with set; importing anchors
alone does not specify all location and scale constraints. Parameter
indices must be checked against the actual model design and retained
levels. A changed index can fix the wrong coefficient without a syntax
error.
For example, let four rater severities be \(\gamma_1,\ldots,\gamma_4\), with R01 fixed at \(\gamma_1=0.70\), and no group constraints:
| Constraint choice | Remaining raters | R01 minus the mean of the remaining raters |
|---|---|---|
mfrmr legacy centering_scope = "remainder" |
\(\gamma_2+\gamma_3+\gamma_4=0\) | 0.70 |
mfrmr default centering_scope = "facet" |
\(\gamma_1+\gamma_2+\gamma_3+\gamma_4=0\), hence the remaining sum is -0.70 | \(0.70-(-0.70/3)\), approximately 0.933 |
The second row applies, for example, to TAM’s
tam.mml.mfr(formulaA = ~ item + rater + step, constraint = "cases")
and ConQuest’s model task+rater+step with
lconstraints=cases. Those settings also leave the first
task term uncentered, unlike mfrmr’s default. These are specific model
configurations, not universal software conventions. The different rater
contrasts show why applying the same anchor and then shifting the final
locations is not generally an equivalent analysis. Group-mean anchors
need corresponding linear constraints; fixing every member to the group
mean would impose a different model.
For unit-slope RSM/PCM with all facet locations centered and an estimated population mean \(\mu\), another equivalent origin uses a zero population mean and uncentered task locations: set \(\theta'=\theta-\mu\) and \(b'_{task}=b_{task}-\mu\). Rater severities, centered step ladders and population variance stay the same, so every response probability is unchanged. This mapping applies to the configuration with Rater anchors above. If a task value is also anchored, its constraint must be transformed with it. In particular, estimating population variance and fixing it to one are different models; moving the origin does not reconcile that difference. Checking this mapping is distinct from running and comparing both programs’ optimizers.
To compare programs, first match the ratings, category coding, response probabilities, free and fixed parameters, and population assumptions. A coordinate transformation must carry the anchors and population parameters with it. Then check numerical integration and optimization. Finally compare SEs for the same parameter or contrast, transforming the covariance matrix when coordinates differ. A point-estimate match alone does not validate an uncertainty method or include source-anchor uncertainty.
A bounded check using all 48 Persons, four raters and four criteria
in example_core illustrates both outcomes. Under the
whole-rater constraint above, TAM 4.3.25 and ConQuest 5.47.5 agreed to
within \(3.1\times10^{-6}\) in their
free response-design coefficients. A separate explicit TAM design
representing mfrmr’s legacy remainder constraints agreed with that mode
to within \(1.8\times10^{-7}\), when
mfrmr used 81-point Gauss–Hermite integration. Both comparisons fixed
ability to \(N(0,1)\). For the latter
comparison, 31-point integration in that legacy mode left a maximum
coefficient difference of about 0.00067; increasing integration
precision resolved it. Equal node counts across programs do not
guarantee equal quadrature rules or accuracy.
This checks one RSM configuration and its point estimates, not general compatibility for PCM, GPCM, group constraints, JML or SEs. It does not make 81 points a universal requirement. More importantly, being able to reproduce mfrmr’s restricted model in another program does not establish that its partial-anchor constraints answer a particular linking question. Decide which locations and population moments should remain free before choosing the model.
Use subset_connectivity_report() before interpreting
subgroup or cross-form contrasts.
sc <- subset_connectivity_report(fit, diagnostics = diag)
sc$summary[, c("Subset", "Observations", "ObservationPercent")]
plot(sc, type = "design_matrix", preset = "publication")Interpretation:
make_anchor_table() mechanically exports fitted values
in the direct-anchor schema. Its output is a candidate table, not a
validated anchor set.
By default, make_anchor_table() refuses a source whose
current readiness record does not have
InferenceReady = TRUE. Use
readiness_policy = "review" only to inspect review-only
candidate values, not to reuse them as anchors. For a ready reference
fit, also document cross-run element identity and invariance and confirm
compatible model, score, orientation, and population conventions. Then
use review_mfrm_anchors() to check table syntax and
receiving-data support. That review does not certify the substantive
link.
Keep the roles distinct: common ratings or common elements supply
observed design overlap; anchors fix individual parameters;
group_anchors constrain a group mean under an externally
justified target or equal-mean assumption. Neither constraint type
manufactures empirical overlap.
No anchor percentage or count is universally adequate. In sparse designs, link quality also depends on coverage, the distribution and redundancy of the links, whether one articulation point or critical edge holds the network together, the location and model fit of the linking set, and the intended use of ranks or classifications. Inspect both Rater-centered and Person/task- centered assignment graphs rather than treating a minimum count as proof of a common scale. This distinction is consistent with work on sparse linking networks by Myford and Wolfe (2000), Wind and Jones (2018), and Uto (2021).
Fixed anchor values also carry uncertainty from their source calibration. Current mfrmr drift and chain summaries do not propagate every source-fit, offset, and cross-fit covariance component. When results depend on the anchor set, compare defensible alternatives and report that sensitivity. Robitzsch (2024, pp. 1181–1182, 1187–1188) examines a different source of linking error: random uniform DIF in fixed-item 2PL calibration. Its derivation neglects Person sampling error and does not provide a variance estimator for fitted population parameters. It supports checking invariance, not claiming that mfrmr propagates all linking uncertainty.
Residual comparisons describe how observed-minus-expected scores differ between groups. They use score units and do not test differential functioning.
A group with higher average ability can score higher without differential functioning. DFF concerns a facet difference at the same ability. Residual screens inherit the fitted population assumptions, and fixed-standard-normal RSM/PCM subgroup refits do not estimate group ability distributions. Linking anchors alone do not address an omitted group difference. Review these assumptions before interpreting a residual difference involving a rater or criterion.
Representing group ability differences is necessary for a suitable population model, but does not turn the current residual screen into a calibrated DFF test. MML residuals evaluate expected scores at EAP ability estimates. Because the response function is nonlinear, a no-DFF model can still have different mean residuals across groups. This concerns the null being tested as well as its standard error.
The residual method therefore returns no p-values, confidence
intervals or positive/negative classifications. For compatibility, its
former SE and test columns contain NA. Recompute older
residual results with analyze_dff() using the existing fit
and original data; no model refit is needed.
dff_resid <- analyze_dff(
fit,
diag,
facet = "Criterion",
group = "Group",
data = bias_df,
method = "residual"
)
screen <- plot(dff_resid, type = "summary")
comparisons <- plot_data(screen, "pairs")
comparisons[, c("Level", "Group1", "Group2", "Contrast",
"N_Group1", "N_Group2", "PlotStatus")]Read the figure and this table together:
Level | Group1 | Group2.
Contrast subtracts the residual mean of Group2 from that of
Group1. A positive value means higher residual scores in Group1, not
established leniency, favoritism or unfairness.N_Group1 and N_Group2 count retained
ratings, not independent Persons. Repeated ratings can make these counts
large even when few Persons contribute. The same table has
NPersons_Group1 and NPersons_Group2 for
distinct Persons.PlotStatus separates displayed comparisons from finite
comparisons omitted by top_n and comparisons with
unavailable contrasts. None of these display states is a DFF decision.
For an unavailable row, inspect sparse and the counts: the
residual pairwise contrast requires both cells to meet
min_obs. An absent group has a count of zero, not a zero
difference.Check how the requested ratings matched the fitted responses. This matters before comparing screens from different data subsets:
cases <- plot_data(screen, "cases")
table(cases$Reason)
cases[!cases$Included, ]
plot_data(screen, "notes")InputRow identifies a row of the supplied data;
FitRow identifies its matched fitted response. Exclusion
reasons distinguish a missing group, an invalid/missing response, and a
response absent from the fitted data. A fully matched example has no
excluded rows; this does not establish independence. Response exclusions
and unavailable comparisons are different stages.
For a grid of the same pairwise differences, use
plot(dff_resid, type = "heatmap", metric = "contrast"). The
default heatmap instead shows each group’s mean observed-minus-expected
score. That cell mean can appear even below min_obs; read
its saved pairs$sparse and counts. The scaled-residual
(metric = "t") and pairwise-contrast routes withhold values
below this threshold. Missing cells/contrasts stay missing, not zero. If
every contrast is unavailable, inspect dff_resid$dif_table
or the contrast heatmap; the summary has no finite points to draw.
Reserve ScaleLinkStatus and
ContrastComparable for refit-based contrasts; their
residual-method values do not describe a failed link.
The refit route reports logit-scale point contrasts when subgroup
linking supports a common scale. Any available SE or test statistic
conditions on the baseline anchors and omits their uncertainty and
cross-refit covariance; FormalInferenceEligible remains
FALSE, even with adequate linking. The default refit
linking screen requires at least five anchored levels in total across
the non-target linking facets in each subgroup. A weak link withholds
contrast SEs and p-values. Inspect LinkingAnchoredLevels,
LinkingThreshold, and ScaleLinkStatus; five is
a local screening threshold, not a universal adequate anchor count or
proof of invariance. It differs from the per-facet common-element screen
called LinkSupportAdequate in drift and link-review
results.
Direct anchors on the target facet and JML Person anchors remain
fixed during subgroup refits. Check Fixed1 and
Fixed2: when either is true, the row retains its estimates
but has no contrast, SE or test. Supplying the same rater severity in
both groups cannot establish that the rater functions equally across
groups. Residual DFF can describe response departures under those
constraints. To estimate a free target contrast, explicitly fit a
baseline with the intended target constraints first. Older saved refit
DFF objects may have dropped target or Person anchors; rerun
analyze_dff() from the baseline fit to obtain subgroup
estimates under the corrected rule. Loading an old result does not
change it.
Check ComparisonStatus and ComparisonReason
before interpreting a number. A failed or skipped subgroup fit has no
subgroup estimate; the baseline value is not a replacement. An absent
target level remains in the comparison table with a missing contrast.
N_Group1 / N_Group2 count retained responses
for that target level, while N_Subgroup1 /
N_Subgroup2 count all retained responses in the subgroup.
Failed fits have unknown retained counts, not zero; their input counts
remain in $group_fits[[group]]$N_Input.
Sparse cells, missing SEs and Persons shared across the two subgroup
fits withhold the independent-subgroup screen. Actual point estimates
remain available, and ConditionalScreenReason explains
missing uncertainty. Disjoint Person IDs alone do not establish
independence or valid matching. The Welch degrees of freedom use
response counts and are only a screening approximation. Older refit
objects must be recomputed from the baseline and original data before
using current summary, report or plotting methods; regenerate their
exported tables and figures as well.
Bias screening answers a different question:
estimate_bias() estimates an additional shift for each cell
while holding the calibration fixed. A positive Bias Size
raises the expected score. An anchored rater can still have a nonzero
cell shift and a conditional SE for that shift. This SE does not include
uncertainty in the fitted calibration or the supplied anchors, and it is
not a joint-interaction confidence interval.
Obs-Exp Average is the observed-minus-expected average
under the baseline calibration, before fitting the shift.
AfterBiasObsExpAverage records the remaining discrepancy
with that shift included. Both are on the score scale;
Bias Size is in logits.
Refits currently refuse baselines with user-specified active
population models, fitted facet interactions, or group-anchor
constraints because those structures cannot yet be replayed and linked
completely within subgroups. The default GPCM-MML identification
intercept is handled separately. Residual DFF remains a screening
alternative; see ?analyze_dff for the exact scope.
dff_refit <- analyze_dff(
fit,
diag,
facet = "Criterion",
group = "Group",
data = bias_df,
method = "refit"
)
dff_refit$summary
dff_refit$dif_table[, c("Level", "Group1", "Group2", "Contrast",
"ScaleLinkStatus", "Fixed1", "Fixed2")]
dff_refit$dif_table[, c("Level", "LinkingAnchoredLevels1",
"LinkingAnchoredLevels2", "LinkingThreshold")]Here Contrast still means Group1 minus Group2, but its
units are logits. A positive value means a larger facet
location in Group1. Holding other parameters fixed, the usual
difficulty/severity orientation lowers expected scores. Subgroup step
ladders or slopes can also differ, so location alone does not order the
entire response curves. ContrastDirection records larger,
smaller or equal locations, including for positive-orientation facets.
Check the model’s facet signs and score coding. Do not compare the sign
as if it had the residual method’s meaning, or put the two methods on a
shared effect axis. Read LinkingDetail1 and
LinkingDetail2 when linking support is weak. A plotted
finite contrast does not by itself establish a common scale; even
ScaleLinkStatus = "linked" does not qualify formal
inference.
For schools with separate rater panels, common recordings can connect joint calibration while a particular rater still lacks observations in one school. That connection alone cannot identify that rater’s school contrast. Keep a common recording’s identity across ratings; do not duplicate it as independent school participants to manufacture a comparison.
The question is whether the rating assignments identify the requested interaction contrast, rather than merely connect the panels. This small constructed assignment isolates that question: R4 rates School A only, while the other raters rate both origins. The scores only supply the declared three-category format; they are not evidence for a school effect, and eight performances are not a recommended sample size.
school_ratings <- expand.grid(
Person = paste0("P", 1:8), Rater = paste0("R", 1:4),
Criterion = c("C1", "C2"), stringsAsFactors = FALSE
)
school_ratings$School <- ifelse(
school_ratings$Person %in% paste0("P", 1:4), "A", "B"
)
school_ratings$Score <- (as.integer(factor(school_ratings$Person)) +
as.integer(factor(school_ratings$Rater)) +
as.integer(factor(school_ratings$Criterion))) %% 3
school_ratings <- subset(school_ratings, !(Rater == "R4" & School == "B"))
school_questions <- list(
facet = "Rater", group = "School", groups = c("A", "B"),
model = "RSM", method = "JML",
contrasts = rbind(
R1_vs_panel = c(R1 = .75, R2 = -.25, R3 = -.25, R4 = -.25),
R1_vs_R2 = c(R1 = 1, R2 = -1, R3 = 0, R4 = 0)
)
)
school_review <- describe_mfrm_data(
school_ratings, "Person", c("Rater", "Criterion", "School"), "Score",
rating_min = 0, rating_max = 2, category_policy = "preserve",
include_agreement = FALSE, comparisons = school_questions
)
school_review$comparison_review$table[, c(
"Comparison", "Group1", "Group2", "State", "N_Group1", "N_Group2"
)]
school_review$comparison_review$cellsEach row applies its weights to the raters’ A-minus-B
interaction effects. R1_vs_panel subtracts the
equal-weight average across all four raters; R1_vs_R2
subtracts R2’s school difference from R1’s. The first is
target_unidentified, while the second is
target_identified_only. R4’s missing B ratings matter to
the whole-panel reference. Dropping R4 and renormalizing weights would
answer a different question.
The IdentificationBasis column makes the role of
assumptions explicit. For the same ratings and target weights, compare a
common normal ability population with a normal regression allowing
school-specific means. These reviews do not estimate either model:
population_reviews <- lapply(
list(common_normal = ~1, school_means = ~School),
function(population) {
question <- school_questions
question$method <- "MML"
question$population_formula <- population
describe_mfrm_data(
school_ratings, "Person", c("Rater", "Criterion", "School"), "Score",
rating_min = 0, rating_max = 2, category_policy = "preserve",
include_agreement = FALSE, comparisons = question
)$comparison_review
}
)
lapply(population_reviews, function(review) review$table[, c(
"Comparison", "ConditionalState", "FreePersonState", "IdentificationBasis", "State"
)])For R1_vs_panel, the common normal model reports
population_restriction: the unrestricted-Person ambiguity
disappears only after imposing that model’s population restrictions.
Allowing school-specific means restores the ambiguity, so the basis is
unidentified. No additional rating was observed in either
review. The common population assumption is substantive; it is not a
harmless change of coordinates or evidence that the missing R4/B ratings
exist.
For R1_vs_R2, both models report
free_person_response_model: the declared response map
identifies this target even with unrestricted Person locations. That
statement still assumes the response model, including its slope and step
restrictions. It does not make an MML estimate distribution-free. Both
positive MML conditional results keep
State = "not_evaluated", because the review does not prove
global marginal identification. Read IdentificationDetail
and the review’s notes before selecting a model; a more
convenient result is not a reason to assume equal ability
populations.
The declared response model includes a zero-valued School main facet
and a zero-margin Rater-by-School interaction, alongside free JML Person
locations. Its settings are saved in
school_review$comparison_review$fit_arguments. The review
does not fit this rank-deficient model, estimate the identified
contrast, or supply an interval. The ordinary full-fit guard still
refuses the model. Identification of one target is not permission to set
other effects to arbitrary values and report the resulting
estimates.
Adding genuinely shared recordings from both origins can supply
missing comparisons. Keep each recording’s original Person and School
labels across raters. Neutral recordings may connect the measurement
scale without filling the missing A/B interaction information. PCM
reviews also require an explicit step owner. GPCM does not currently
support this interaction model and is reported as
unsupported; its separate slope/product qualifications
cannot be substituted for this school review.
For the bundled data used above, explicitly request an MML prospective Rater-by-Group review, then attach it to a residual screen of the same ratings. The model in this review is a prospective interaction model; the fitted screening model remains the earlier additive RSM. No interaction fit is silently added.
rater_levels <- sort(unique(as.character(bias_df$Rater)))
panel_weights <- matrix(-1 / length(rater_levels), 1, length(rater_levels),
dimnames = list("First rater versus panel", rater_levels))
panel_weights[1, 1] <- panel_weights[1, 1] + 1
prospective <- describe_mfrm_data(
bias_df, "Person", c("Rater", "Criterion", "Group"), "Score",
rating_min = 1, rating_max = 4, category_policy = "preserve",
include_agreement = FALSE,
comparisons = list(facet = "Rater", group = "Group", groups = c("A", "B"),
contrasts = panel_weights, model = "RSM", method = "MML")
)
rater_screen <- analyze_dff(
fit, facet = "Rater", group = "Group", data = bias_df,
comparison_review = prospective
)
summary(rater_screen)$comparison_review$table
support <- plot(rater_screen, draw = FALSE)
plot_data(support, "comparison_review")$weights
report <- dif_report(rater_screen)
report$comparison_review$table
saveRDS(rater_screen, file.path(tempdir(), "rater-screen-with-review.rds"))Positive ConditionalState values concern the conditional
linear response map. Under MML, State = "not_evaluated"
deliberately withholds a positive global marginal-identification claim;
it does not mean the residual calculation failed. A proven
same-conditional-law ambiguity can still produce
target_unidentified after mixing.
FreePersonState and Basis expose the
population assumption separately. Neither rank nor a finite residual
difference qualifies a DFF test. The attached review keeps all requested
rows, weights, counts and limitations; it does not replace the residual
table with structural parameter estimates. Changed rating subsets or
origin assignments require a new review before attachment.
When the assignment supports the whole model, the next question is
the size of the specified interaction contrast in a joint fit. Use the
bundled example_bias ratings from the screen above. Its
Group labels illustrate the origin role; this synthetic example is not
evidence about actual schools. Allow Group-specific population means
through ~Group, so an equal-population restriction is not
silently used to explain the group difference. School/Group remains a
zero main facet; the population mean and rater-by-group interaction have
separate roles.
The first comparison is the first rater’s A-minus-B interaction relative to the equal-weight whole panel. The second subtracts the second rater’s A-minus-B interaction. This compares school differences, not the raters’ overall severity.
joint_questions <- prospective$comparison_review$specification
joint_questions$population_formula <- ~Group
pair_weights <- setNames(rep(0, length(rater_levels)), rater_levels)
pair_weights[1:2] <- c(1, -1)
joint_questions$contrasts <- rbind(
panel_weights, "First versus second rater" = pair_weights
)
joint_review <- describe_mfrm_data(
bias_df, "Person", c("Rater", "Criterion", "Group"), "Score",
rating_min = 1, rating_max = 4, category_policy = "preserve",
include_agreement = FALSE, comparisons = joint_questions
)
joint_fit <- do.call(fit_mfrm, c(
list(data = bias_df, quad_points = 63L),
joint_review$comparison_review$fit_arguments
))
joint_results <- summary(joint_fit, comparison_review = joint_review)
joint_comparisons <- joint_results$interaction_comparisons
joint_comparisons[, c(
"Comparison", "Group1", "Group2", "Estimate", "EstimateStatus",
"State", "ConditionalState", "QuadratureState", "FormalInferenceEligible"
)]
attr(joint_comparisons, "fit_checks")$numerical
attr(joint_comparisons, "comparison_review")$weights
saveRDS(list(fit = joint_fit, review = joint_review, results = joint_results),
file.path(tempdir(), "joint-school-comparisons.rds"))The two fitted contrasts are approximately 0.58 and 0.92 logits. A positive value indicates the weighted relative shift toward higher coded scores for A versus B under this model. It is not an absolute advantage for School A, a comparison of population means, or proof of unfairness. The values differ from the earlier residual-screen statistic because that statistic asks a different question.
interaction_effect_table(joint_fit, comparison_review = joint_review)
returns the same comparison table directly. Without
comparison_review, it continues to return individual
interaction cells. The numerical checks reevaluate the saved joint
likelihood and full gradient without optimization. For this example,
31-node fixed quadrature did not meet the finer-grid check; 63 nodes
were selected after checking agreement with 127. This order is not a
universal accuracy recommendation.
QuadratureState = "stable_at_retained_point" means the
retained point passed the specified integration check, not that the
model or its uncertainty has been validated.
EstimateStatus = "descriptive_only" keeps the fitted
value separate from identification and inference. Positive conditional
MML ranks still retain State = "not_evaluated"; this
example does not prove global marginal identification. Inspect
FitReadiness, BoundaryState,
NumericalState, EstimateReason and the saved
checks. A failed check leaves Estimate missing;
OptimizerEstimate is then only a numerical trace. This
extraction table supplies no SEs, confidence limits or formal tests. The
separate interval example below uses
mfrm_facet_intervals(comparison_review = ...) with
additional joint-information and finer-grid checks. Keep the full saved
object to preserve weights, empty cells, population assumptions and
checks; a CSV does not retain these attributes.
Suppose the last rater has no ratings from Group B. The first-versus-second rater comparison can still be identified, but the comparison with the whole panel includes an unobserved rater/group cell. An ordinary full fit correctly refuses this deficient design. Explicit comparison-only estimation can retain the observed likelihood and estimate its invariant targets without reporting arbitrary values for the unidentifiable interaction cells.
This example uses JML with free Person locations. It does not assume equal normal populations across groups. It uses the same declared interaction target as before, on the reduced assignment, but changes the estimator and the observed ratings; its numerical value is therefore not a replication of the preceding full-design MML value.
incomplete_ratings <- bias_df[!(bias_df$Rater == tail(rater_levels, 1) &
bias_df$Group == "B"), ]
partial_questions <- joint_questions
partial_questions$method <- "JML"
partial_questions$population_formula <- NULL
partial_review <- describe_mfrm_data(
incomplete_ratings, "Person", c("Rater", "Criterion", "Group"), "Score",
rating_min = 1, rating_max = 4, category_policy = "preserve",
include_agreement = FALSE, comparisons = partial_questions
)
partial_fit <- do.call(fit_mfrm, c(
list(data = incomplete_ratings, fit_scope = "comparisons",
comparison_review = partial_review, interaction_policy = "silent"),
partial_review$comparison_review$fit_arguments
))
partial_comparisons <- interaction_effect_table(partial_fit)
partial_comparisons[, c(
"Comparison", "ConditionalState", "Estimate", "EstimateStatus",
"BoundaryState", "NumericalState", "EstimateReason"
)]
partial_fit$checks$numerical
saveRDS(partial_fit, file.path(tempdir(), "partial-school-comparisons.rds"))The whole-panel comparison remains missing; the
first-versus-second rater contrast is approximately 1.08
logits, with target_identified_only and
descriptive_only. This is an A-minus-B difference between
those two raters’ interaction effects. It does not recover the missing
last-rater/B effect. summary(partial_fit) returns the same
table. This dedicated result cannot be used as an ordinary full model
for Person scoring or individual-cell inference.
Identifiability alone is insufficient. For JML, the comparison route
uses lpSolve to check whether any parameter direction can
increase the likelihood without reaching a finite maximum. A detected or
unchecked recession withholds all estimates, even if some contrasts
might have limits in an extended model; that extension is outside this
route. Failed convergence, insufficient reduced curvature or unsupported
categories also retain missing values and reasons.
For MML, declare its population model in the review before fitting.
The same coordinate reduction preserves every conditional probability
and the marginal likelihood. Multiple starting variances and finer
quadrature assess numerical adequacy; they do not prove global marginal
identification or a unique global maximum. Positive conditional results
still keep State = "not_evaluated". Neither route supplies
SEs, confidence intervals or formal DFF tests. The saved
representation and optimization are audit
coordinates, not reportable cell parameters. See ?fit_mfrm
for the supported scope and acceptance criteria.
For the same incomplete assignment, CML conditions on each Person’s total score. It needs no ability distribution and avoids estimating one incidental ability parameter per Person. The inferential target remains the declared rater-by-origin interaction contrast. Start with the bundled synthetic ratings and specify the two comparisons explicitly:
cml_data <- load_mfrmr_data("example_bias")
cml_raters <- sort(unique(as.character(cml_data$Rater)))
cml_ratings <- cml_data[!(cml_data$Rater == tail(cml_raters, 1) &
cml_data$Group == "B"), ]
cml_weights <- rbind(
panel = c(1, rep(0, length(cml_raters) - 1)) - 1 / length(cml_raters),
pair = c(1, -1, rep(0, length(cml_raters) - 2))
)
colnames(cml_weights) <- cml_raters
cml_review <- describe_mfrm_data(
cml_ratings, "Person", c("Rater", "Criterion", "Group"), "Score",
rating_min = 1, rating_max = 4, category_policy = "preserve",
include_agreement = FALSE,
comparisons = list(facet = "Rater", group = "Group", groups = c("A", "B"),
contrasts = cml_weights, model = "RSM", method = "CML")
)
cml_fit <- do.call(fit_mfrm, c(
list(data = cml_ratings, fit_scope = "comparisons",
comparison_review = cml_review, interaction_policy = "silent"),
cml_review$comparison_review$fit_arguments
))
cml_table <- interaction_effect_table(cml_fit)
cml_table[, c("Comparison", "ConditionalState", "Estimate", "SE", "Lower",
"Upper", "FormalInferenceEligible", "EstimateReason")]
cml_fit$covariance
saveRDS(cml_fit, file.path(tempdir(), "cml-school-comparisons.rds"))Read the pair row: the estimated contrast is
approximately 0.925 logits, SE 0.518,
with a pointwise 95% Wald interval of −0.090 to 1.939.
It is the first rater’s A-minus-B interaction minus the second rater’s
A-minus-B interaction. The interval includes zero. The
panel estimate, SE and limits remain NA,
because its weights involve the missing last-rater/B cell. Zero-filling
that row would change the inferential claim. The full contrast
covariance is retained in cml_fit$covariance, including
missing entries for unavailable targets; summary(cml_fit)
returns the same table.
Reuse that fit through the same interval interface used for full
models. No estimation or information calculation is repeated. The
missing panel comparison stays in the display as “not
estimated”, with no point or interval:
cml_intervals <- mfrm_facet_intervals(cml_fit)
plot(cml_intervals)
apa_table(cml_intervals, which = "intervals")$table
saveRDS(cml_intervals, file.path(tempdir(), "cml-school-intervals.rds"))Use mfrm_facet_intervals(cml_fit, level = .90) for
pointwise 90% limits; only the normal critical value changes. The saved
comparison_review retains the group order, weights and
empty cells, and fit_checks retains the numerical
qualification.
apa_table(cml_intervals, which = "comparison_cells") shows
which crossings were observed. These dedicated CML objects are not
ordinary mfrm_results() fits and do not enable Person
scoring or full-model diagnostics.
CML identification uses all possible patterns at the observed
totals, not just the unconditional response design.
Extreme-total Persons contribute no information. The fit removes
unidentified directions only from its internal coordinates, then checks
finite-MLE existence, convergence and the full conditional information
in the identified subspace. A recession or failed check withholds
inference with a reason. For PCM, declare model = "PCM" and
its step_facet in the review before fitting.
The intervals are asymptotic under a correct response model, independent Persons, fixed structural dimension and increasing information. Numerical agreement with independent calculations is not a finite-sample coverage study for every school design. These estimates do not establish a calibrated DFF classification or recover Person abilities or between-group ability means. CML estimates no population SD, so it cannot supply a contrast divided by that fit’s estimated ability SD. The separate MML standardization uses its joint delta method, including uncertainty and cross-covariance for the fitted SD. Save the whole CML object as RDS to retain weights, checks and missing-cell information; CSV exports only the displayed columns.
A contrast in logits and a contrast in ability-population SD units
answer different questions. For the latter, define the denominator using
the same fit that estimates the interaction. The
following example uses the complete example_bias assignment
and explicitly assumes one common normal ability population through
population_formula = ~1. This differs from the preceding
MML example with ~Group, which allowed group-specific
means, and from the incomplete CML example. The change is a modeling
assumption, not just a display option; do not replace
~Group with ~1 solely to obtain a standardized
result.
ratings <- load_mfrmr_data("example_bias")
raters <- sort(unique(as.character(ratings$Rater)))
weights <- rbind(
panel = c(1, rep(0, length(raters) - 1)) - 1 / length(raters),
pair = c(1, -1, rep(0, length(raters) - 2))
)
colnames(weights) <- raters
sd_review <- describe_mfrm_data(
ratings, "Person", c("Rater", "Criterion", "Group"), "Score",
rating_min = 1, rating_max = 4, category_policy = "preserve",
include_agreement = FALSE,
comparisons = list(facet = "Rater", group = "Group", groups = c("A", "B"),
contrasts = weights, model = "RSM", method = "MML", population_formula = ~1)
)
sd_fit <- do.call(fit_mfrm, c(
list(data = ratings, quad_points = 63, interaction_policy = "silent"),
sd_review$comparison_review$fit_arguments
))
sd_intervals <- mfrm_facet_intervals(
sd_fit, comparison_review = sd_review, scale = "standardized"
)
sd_intervals$settings$population_sd
summary(sd_intervals)[, c("Target", "NativeEstimate", "Estimate", "SE", "Lower", "Upper")]
saveRDS(sd_intervals, file.path(tempdir(), "standardized-school-intervals.rds"))Here the fitted SD is approximately 0.976. The
first-minus-second rater’s A-minus-B interaction is 0.927
logits, or 0.950 population SD units, with SE
0.494 and a pointwise 95% interval of −0.018 to
1.918. The interval includes zero. NativeEstimate
keeps the original scale alongside the standardized result, and
covariance retains covariance between the two requested
contrasts. The pointwise intervals do not adjust for selecting or
examining multiple contrasts.
For contrast \(d\) and log population variance \(\lambda=\log(\sigma^2)\), \(t=d/\sigma\) has derivatives \(J_d/\sigma\) and \(-t/2\). The implemented variance is
\[ \operatorname{Var}(\hat t)\approx \frac{\operatorname{Var}(\hat d) +\hat d^2\operatorname{Var}(\hat\lambda)/4 -\hat d\operatorname{Cov}(\hat d,\hat\lambda)}{\hat\sigma^2}. \]
It uses the full joint marginal-information inverse. Dividing the native SE or interval endpoints by \(\hat\sigma\) leaves out denominator uncertainty and its covariance with the contrast. The ratio can be unstable when the fitted SD is near zero; standardization does not cure weak information.
The usual saved-results path carries the same targets, scale, checks and review into plots, reports and table exports, without fitting again:
sd_results <- mfrm_results(
sd_fit, intervals = list(school = sd_intervals),
include = c("fit", "plots"), compute = "never"
)
sd_results$tables$facet_school_intervals[, c("Target", "Estimate", "ScaleReference")]
saveRDS(sd_results, file.path(tempdir(), "standardized-school-results.rds"))For a supported full MML fit with ~Group, request
scale = "native" instead; its fitted residual SD is not
substituted for a marginal population SD. The fixed N(0,1) case uses
known SD one. CML estimates no such SD, and this MML consumer does not
extend inference to partial MML fits or GPCM interactions. Numerical
eligibility means that source, information and finer-grid checks passed.
CoverageQualified = FALSE and the retained
marginal-identification state keep finite-sample performance and global
identification separate from those checks. These intervals require the
declared response/population model and independent Persons; they are not
a calibrated DFF classification.
mantel_haenszel_table() now supplies a separate binary
MH comparison with explicit matching strata and one response per Person
and target. Its default conditional exact inference uses
stats::mantelhaen.test(); an asymptotic option is also
available. analyze_dff() retains its residual and refit
routes. ordinal_logistic_table() supplies the
independent-Person cumulative-logit comparison through the optional
ordinal package. Pooled repeated-rating comparisons remain
planned work. First specify what each result describes:
| Procedure | Quantity being compared |
|---|---|
| Residual DFF | Group difference in mean observed-minus-expected score; descriptive |
| Linked refit DFF | Group difference in a facet parameter, conditional on baseline anchors |
| Binary Mantel–Haenszel (MH) | A common group odds ratio conditional on declared matching strata |
| Cumulative ordinal logistic | Group and matching-by-group terms in a proportional-odds response model |
First declare the intended response roster and rating plan before
running the methods. Passing that roster through residual
analyze_dff(data = ...) now selects the fitted observations
by Person, all facets, score, weight and repeated-response multiplicity.
It uses the original fitted expectations, without recalibrating on the
selected subset. The same contract applies to
dif_interaction_table().
Inspect $cases in each result: residual output maps
InputRow in the supplied data to FitRow in
fit$prep$data; MH/ordinal output identifies input rows with
Row. Missing groups, invalid responses and responses absent
from the fit cannot silently acquire another observation’s residual.
Requesting more copies than the fit contains is rejected. Identical
fitted copies can be exchanged for this descriptive calculation only
when their model moments agree; their FitRow assignment
does not recover an occasion identity.
Residual N counts response rows, while
NPersons counts distinct Persons. For example, 12 Persons
with three criteria give 36 rows for a rater, not 36 independent
Persons. Descriptive residual summaries can retain those rows; ordinary
MH/ordinal inference cannot treat them as independent target responses.
Earlier saved residual summaries must be recomputed from the saved fit
and source data to obtain this matching audit; a model refit is
unnecessary.
Method-specific exclusions can still differ: MH removes uninformative strata, whereas ordinal models may retain their observations. Report these differences and the matching rule; do not automatically intersect outcome-dependent inclusion masks and treat that as a prespecified common cohort. Matching rows is necessary for understanding a comparison, but does not make the methods’ quantities or null hypotheses identical.
The MH result keeps the MH point estimate separately from the
conditional maximum-likelihood estimate used by exact inference. Its
strata and cases tables retain group/category
counts, opposing effects, missing responses and strata without overlap.
Supply multiple matching columns, such as total and rating plan, through
strata; continuous values are not silently binned. The
function rejects duplicate Person-target cells. Pointwise intervals are
not simultaneous across targets; Holm adjustment applies to p-values
only. Exact probability-ordered two-sided p-values need not invert
equal-tail confidence intervals. See ?mantel_haenszel_table
for a runnable example.
The ordinal result fits matching-only (M1), matching plus group (M2),
and matching plus group and their interaction (M3) to identical complete
rows. Its table separates uniform (M2/M1), nonuniform
(M3/M2) and joint (M3/M1) likelihood-ratio tests, with Holm adjustment
across all three tests and targets. DeltaNagelkerkeR2 names
the pseudo-R-squared definition explicitly. The contrasts
table reports M3’s reference/focal upper-tail odds ratios at the
endpoints and midpoint of the overlapping matching ranges; values above
one favor higher reference scores. Fits use all complete responses. An
interaction means this odds ratio changes with matching, so one constant
group coefficient does not summarize the full difference.
The separate proportional_odds table tests whether
slopes can differ across category cuts, using a global nonproportional
alternative. This diagnostic has its own multiplicity family and does
not select which group tests are run. For binary responses it is not
applicable. models, matching and
cases preserve convergence information, group ranges and
row exclusions. Empty declared categories, absent range overlap and
numerical failures remain unavailable; repeated rows are rejected. See
?ordinal_logistic_table for a runnable example and the
exact numerical checks. All reported p-values are asymptotic:
convergence or a nonsignificant diagnostic does not establish adequate
small-sample inference or proportional odds.
A bounded known-model check illustrates this limit. With three categories, balanced groups, a fixed matching grid and total N=40, all 1,000 simulated data sets had computable group tests. Under no group difference and true proportional odds, the unadjusted joint test rejected in 65/1,000 cases (6.5%; 95% Monte Carlo Wilson interval 5.1%–8.2%). The proportional-odds diagnostic was unavailable in 42 cases and rejected in 114/958 available cases (11.9%; interval 10.0%–14.1%), at nominal 5%. Treating its numerical availability as assurance of a valid 5% test would therefore be misleading. For the default Holm-adjusted family of three group tests, at least one rejection occurred in 52/1,000 cases (5.2%; interval 4.0%–6.8%). This family is separate from the proportional-odds diagnostic and does not repair it. At N=200 and N=1,000 under the same mechanism, joint rejection was 4.7% and 4.6%, and diagnostic rejection was 5.8% and 4.5%, with all results available. These particular conditions do not establish a universal sample-size rule. The function offers asymptotic comparisons, not qualified small-sample tests. No significance cutoff or numerical tolerance was changed in response to these results; the diagnostic does not determine whether group comparisons are run.
Matching is a substantive choice. In the binary Rasch case with a common item set and invariant other items, conditioning on the total including the studied item removes the group ability distribution from that item’s conditional response. Removing the studied item can leave group differences even without IRT DIF (Holland and Thayer, 1986, pp. 14–18). This is not a general endorsement of any total score. Contamination by other DIF items, missing responses and different rater/task plans require separate attention. In a many-facet assignment, two persons with the same total may have faced different raters or tasks. Specify common exposure or an explicit plan adjustment with overlap before treating their totals as comparable.
For each target, retain the persons, group coding, category order, matching definition, exclusions and rating plan. Repeated ratings from a Person are not independent new persons in ordinary MH tables or logistic likelihoods. A comparison must account for that dependence; silently averaging ratings changes the response being analyzed. Connectedness alone does not resolve either problem.
MH can miss effects that reverse direction across matching strata. Rogers and Swaminathan’s study also used different purification procedures for MH and logistic regression, so its comparison is not solely a change of test statistic (1993, pp. 111–115). Report uniform and nonuniform terms separately when fitting a logistic comparison. In Zumbo’s three-model sequence, add the group term to the matching-only model, then add the matching-by-group interaction; the joint test compares the last model with the first (1999, pp. 23–27).
The cumulative proportional-odds assumption in that ordinal model is
different from the adjacent-category structure of RSM/PCM and is not
generally satisfied by it. Check the matching-response relationship and
model adequacy before interpreting a difference between procedures as
DFF. Pseudo-R-squared definitions and their changes must be named: the
historical .130 screen in Zumbo’s application is not a
universal cutoff or a default for mfrmr. A nonsignificant result is not
evidence that differential functioning is absent, and statistical DIF
alone does not establish bias.
Zumbo, B. D. (1999). A handbook on the theory and methods of differential item functioning (DIF): Logistic regression modeling as a unitary framework for binary and Likert-type (ordinal) item scores. Ottawa, ON: Directorate of Human Resources Research and Evaluation, Department of National Defense.
If the level-wise screen points to a specific facet, follow up with the interaction table and narrative report.
Residual bias and DFF tools screen for unusual cells after fitting
the additive model. For example, ask whether a rater scores particular
criteria unexpectedly high or low after accounting for the main facet
effects. estimate_all_bias() screens all non-Person facet
pairs by default; specify pairs to limit the search. It
holds the calibration and Person estimates fixed, so these cell shifts
differ from jointly estimated interaction effects.
ratings <- load_mfrmr_data("example_bias")
fit_add <- fit_mfrm(
ratings, "Person", c("Rater", "Criterion"), "Score",
method = "MML", model = "RSM", quad_points = 31
)
screens <- estimate_all_bias(fit_add)
report <- mfrm_report(screens, study_context = c(
question = "Which rater-criterion cells need substantive review?",
score_direction = "Higher coded scores indicate better performance"
))
report$tables$overview
head(report$tables$bias_cells)
plot(screens)
# Keep complete tables, including failed pairs and unavailable cells.
export_summary_appendix(report, output_dir = "bias-review")The signed Bias Size describes a shift in expected
coded scores; positive does not necessarily mean
favorable. Replace the declared direction with your rubric’s actual
meaning. Report the searched interactions, exclusions, cell counts and
unavailable results, rather than only flagged cells. The screening
statistics omit calibration and Person uncertainty and have no
collection-wide multiplicity control. Different pairs reuse
observations; their cell counts must not be summed as the study sample
size. This is a FACETS-like conditional exploration workflow, not a
claim of identical algorithms, probabilities or calibration conventions.
See the FACETS
bias-estimation description.
When the interaction hypothesis is specified in advance, use
facet_interactions to estimate the named two-way non-person
facet interaction in the model likelihood. An interaction selected after
inspecting these screens can also be fitted, but its same-data
comparison remains exploratory: the LRT p-value does not account for
that selection.
# Continue with ratings and fit_add from the preceding block.
fit_interaction <- fit_mfrm(
ratings,
person = "Person",
facets = c("Rater", "Criterion"),
score = "Score",
method = "MML",
model = "RSM",
facet_interactions = "Rater:Criterion",
quad_points = 31
)
interaction_effect_table(fit_interaction)
comparison <- compare_mfrm(Additive = fit_add, Interaction = fit_interaction,
labels = c("Additive", "Interaction"), nested = TRUE)
comparison
model_report <- mfrm_report(build_model_choice_review(
Additive = fit_add, Interaction = fit_interaction
))
model_report$tables$comparison_table
# The nested LRT remains in comparison$lrt; the review above reports ICs.Interpretation:
estimate_bias().mml_engine = "hybrid" initializes the fit with EM before
direct finishing; "em" retains the EM solution with its
common terminal-gradient review. Use mml_starts to examine
specified initial values, and inspect
summary(fit)$starting_values and fit readiness before
comparing models. EM can be slower and does not resolve sparse-design
identification or insufficient quadrature. The conditional cell screen
itself is not EM.When you work across administrations, the route usually moves from a
declared common-anchor design to anchored fitting and then to drift
review. The block below is schematic: wave1 and
wave2 must be administrations for which the listed facet
levels have documented cross-wave identity and unchanged meaning.
Do not substitute ej2021_study1 and
ej2021_study2 here. They are independent legacy synthetic
studies; reused raw labels do not identify common persons, raters, or
anchors and do not link their scales.
declared_common_facets <- c("Criterion")
fit1 <- fit_mfrm(
wave1, "Person", c("Rater", "Criterion"), "Score",
method = "MML"
)
anchored <- anchor_to_baseline(
wave2,
fit1,
person = "Person",
facets = c("Rater", "Criterion"),
score = "Score",
anchor_facets = declared_common_facets
)
fit2 <- fit_mfrm(
wave2, "Person", c("Rater", "Criterion"), "Score",
method = "MML"
)
drift <- detect_anchor_drift(list(Wave1 = fit1, Wave2 = fit2),
facets = declared_common_facets)
plot(drift, type = "drift", preset = "publication")The current drift and chain helpers estimate one pooled offset across
every selected common element and facet. Their SE ratios do not include
uncertainty from estimating that offset or cross-fit covariance, and
cumulative chain offsets have no propagated SE. Offset_SD
is residual spread rather than an offset standard error. Use one
coherent linking facet unless a common shift across facet blocks is
defensible, and treat flags and support counts as review screens rather
than formal tests or proof of scale equivalence.
Read OffsetStatus and OffsetReason when an
offset is missing, and ScreeningFallback to see whether
preliminary screening rejected every element and the calculation
reverted to all finite differences. Contributing and
N_Contributing distinguish elements actually used in the
offset from those merely retained: when some rows have usable paired
SEs, retained rows without them do not enter the weighted average. A
singleton retained set has an undefined sample SD, so
Offset_SD is missing and OffsetSDStatus
explains why. Missing flags are reported separately from known flagged
elements. These states preserve usable point estimates without
representing unavailable precision as zero.
Subtracting an estimated offset also changes the residual covariance.
The displayed SE ratio is therefore not guaranteed to be conservative.
Consult SERatioStatus and FlagBasis: a
magnitude-only Flag = FALSE does not mean the element
passed an SE-ratio check.
For a compact linking route:
fit_mfrm()diagnose_mfrm()subset_connectivity_report()make_anchor_table() or
review_mfrm_anchors()analyze_dff()dif_report() and plot()interaction_effect_table() after a confirmatory
facet_interactions fitanchor_to_baseline() /
detect_anchor_drift() when working across wavesThere are two different meanings of an anchor. A common
person supplies ratings that connect raters; their ability can
remain unknown and estimated. A fixed anchor supplies a
known parameter value to fit_mfrm(anchors = ...). The
latter is not needed merely because several raters score the same
person.
For example, the following existing simulation routes describe three practical assignments. Four common persons and eight raters illustrate the syntax; they are not recommended minimum counts. The common-person example uses more rating rows than the other examples, so this is not an equal-budget precision comparison.
library(mfrmr)
common <- build_mfrm_sim_spec(
n_person = 40, n_rater = 8, n_criterion = 3, raters_per_person = 2,
assignment = "sparse_linked",
sparse_controls = list(link_persons = 4, link_raters_per_person = 8,
assignment_mode = "balanced")
)
rotating <- build_mfrm_sim_spec(
n_person = 40, n_rater = 8, n_criterion = 3, raters_per_person = 2,
assignment = "rotating"
)
random <- build_mfrm_sim_spec(
n_person = 40, n_rater = 8, n_criterion = 3, raters_per_person = 2,
assignment = "sparse_linked",
sparse_controls = list(link_persons = 0, assignment_mode = "random")
)
# Use common, rotating, or random here to inspect the chosen assignment.
ratings <- simulate_mfrm_data(sim_spec = common, seed = 27309)
data_review <- describe_mfrm_data(
ratings, person = "Person", facets = c("Rater", "Criterion"),
score = "Score", include_agreement = FALSE
)
data_review$design_connectivityThe common-person design gives four persons all eight raters and the
other 36 persons two raters each. The rotating and random designs give
each person two raters. A random draw does not guarantee that all raters
are connected or equally busy. For actual data, supply the planned
assignment to describe_mfrm_data(expected_design = ...) to
distinguish missing assigned ratings from cells that were never
assigned.
Eckes’s discussion of rating designs distinguishes distributed
overlap, a single common examinee, disconnected groups and task-nested
spiral assignments (2023 edition, Section 9.1, Table 9.1, pp. 151–155).
The rotating generator here uses the selected raters on all generated
criteria. Use an explicit design_skeleton for task-specific
assignments; do not equate ordinary rotation with every spiral design.
Check model identification as well as graph connections, especially when
raters are nested within tasks.
Connectedness means paths exist, not that those paths carry enough information. Review reliance on a small linking set, its composition, and sensitivity to loss of a linking person or rater. The ability to generate a sparse design is not a guarantee that a particular estimator or its intervals work well for it.