This vignette outlines a reproducible workflow for:
- loading packaged simulation data
- fitting an MFRM with flexible facets
- choosing a fast fit summary or an opt-in comprehensive summary
- producing the required Wright map on the fitted shared logit scale
- running diagnostics and residual PCA
- generating APA and visual summary outputs
- moving from fitted models into design simulation and fixed-calibration prediction
For a plot-first companion guide, see the separate
mfrmr-visual-diagnostics vignette.
For a faster preliminary run without changing the final analysis target:
- test code mechanics on a small deterministic subset or, for an MML
workflow, a temporary
quad_points = 7grid; restore the prespecified final MML grid (31 points by default) and check quadrature sensitivity before reporting - choose
method = "JML"only when its person-parameter treatment is methodologically appropriate, not merely as a faster substitute for MML - use
diagnose_mfrm(..., residual_pca = "none", diagnostic_mode = "both", fit_df_method = "both")when the diagnostics will feed a comprehensive summary - reuse the same diagnostics object in downstream reports and plots
MML and Diagnostic Modes
mfrmr treats MML and JML
differently on purpose.
-
MMLintegrates over the person distribution with Gauss-Hermite quadrature. -
mml_engine = "direct"(the default) optimizes the quadrature-based marginal log-likelihood directly.mml_engine = "em"and"hybrid"provide the documented EM and EM-warm-start routes for supported RSM/PCM fits. -
JMLis useful for JMLE-oriented comparisons and analyses that avoid a parametric person distribution.MMLis the package default and supports marginal and fixed-calibration follow-up when its response-model and population-distribution assumptions are defensible.
For RSM and PCM, diagnostics now expose two
distinct evidence paths:
-
diagnostic_mode = "legacy"keeps the residual/EAP-based stack. -
diagnostic_mode = "marginal_fit"adds the strict latent-integrated screen. -
diagnostic_mode = "both"is the safest default when you want to inspect both views side by side.
Strict marginal diagnostics are screening-oriented. Use
summary(diag)$diagnostic_basis to separate the legacy
residual evidence from the strict marginal evidence rather than pooling
them into one decision.
Load Data
library(mfrmr)
list_mfrmr_data(details = TRUE)[, c("Key", "PrimaryUse", "Design", "CountBasis")]
#> Key PrimaryUse
#> 1 example_core Idealized fast examples
#> 2 example_bias DFF and bias demonstrations with planted effects
#> 3 example_operational Beginner applied workflow
#> 4 study1 Unequal-workload sparse-design review
#> 5 study2 Larger sparse-design review
#> 6 combined Identity/linking design review; not direct fit
#> 7 study1_itercal Legacy synthetic variant review
#> 8 study2_itercal Legacy synthetic variant review
#> 9 combined_itercal Identity/linking sensitivity review; not direct fit
#> Design
#> 1 Complete crossing; no planned omissions
#> 2 Balanced two-rater assignment; planted non-null effects
#> 3 Connected two-rater assignment; six planned omissions
#> 4 Two raters per person; highly unequal rater workloads
#> 5 Two raters per person; incomplete criterion coverage
#> 6 Overlapping IDs; requires explicit anchors/linking for a common scale
#> 7 Legacy Study 1 variant; rows and scores can differ
#> 8 Legacy Study 2 variant; rows and scores can differ
#> 9 Overlapping IDs; requires explicit anchors/linking for a common scale
#> CountBasis
#> 1 unique labels
#> 2 unique labels
#> 3 unique labels
#> 4 unique labels
#> 5 unique labels
#> 6 raw labels; 513 persons and 30 raters when Study-prefixed
#> 7 unique labels
#> 8 unique labels
#> 9 raw labels; 513 persons and 30 raters when Study-prefixed
data("ej2021_study1", package = "mfrmr")
head(ej2021_study1)
#> Study Person Rater Criterion Score
#> 1 Study1 P001 R08 Global_Impression 4
#> 2 Study1 P001 R08 Linguistic_Realization 3
#> 3 Study1 P001 R08 Task_Fulfillment 3
#> 4 Study1 P001 R10 Global_Impression 4
#> 5 Study1 P001 R10 Linguistic_Realization 3
#> 6 Study1 P001 R10 Task_Fulfillment 2
study1_alt <- load_mfrmr_data("study1")
identical(names(ej2021_study1), names(study1_alt))
#> [1] TRUEApplied Runnable Example
Start with the packaged example_operational dataset. It
is intentionally compact but uses a connected two-rater assignment,
unequal rater workloads, and six planned omissions represented by absent
long-format rows rather than NA or sentinel scores. This
makes the main tutorial closer to an applied rating design without using
empirical records. The same object is also available via
data("mfrmr_example_operational", package = "mfrmr"). A
separate score-free assignment roster lets the pre-fit review identify
those six omissions without guessing which cells should have existed.
example_core remains available as an idealized
complete-crossing example for fast help-page checks.
data("mfrmr_example_operational", package = "mfrmr")
data("mfrmr_example_operational_design", package = "mfrmr")
toy <- mfrmr_example_operational
data_review_toy <- describe_mfrm_data(
data = toy,
person = "Person",
facets = c("Rater", "Criterion"),
score = "Score",
rating_min = 1,
rating_max = 4,
expected_design = mfrmr_example_operational_design
)
data_summary_toy <- summary(data_review_toy)
data_summary_toy$structural_missingness
#> Status ExpectedCells ObservedCells MatchedCells MissingExpectedCells
#> 1 declared 288 282 282 6
#> UnexpectedObservedCells CoverageRate ExpectedOnlyPersons UnexpectedPersons
#> 1 0 0.9791667 0 0
data_summary_toy$design_connectivity
#> Basis Facet PersonNodes FacetLevelNodes Edges Components
#> 1 observed Rater 48 6 96 1
#> 2 observed Criterion 48 3 144 1
#> 3 declared_expected Rater 48 6 96 1
#> 4 declared_expected Criterion 48 3 144 1
#> LargestComponentPersons LargestComponentLevels LargestComponentPercent
#> 1 48 6 100
#> 2 48 3 100
#> 3 48 6 100
#> 4 48 3 100
#> Connected
#> 1 TRUE
#> 2 TRUE
#> 3 TRUE
#> 4 TRUE
fit_toy <- fit_mfrm(
data = toy,
person = "Person",
facets = c("Rater", "Criterion"),
score = "Score",
method = "MML",
model = "RSM"
)
diag_toy <- diagnose_mfrm(
fit_toy,
residual_pca = "none",
diagnostic_mode = "both",
fit_df_method = "both"
)
# Fast fit-only summary: this does not compute diagnostics.
fit_summary_toy <- summary(fit_toy, profile = "fit", detail = "brief")
# Comprehensive review, reusing diagnostics already computed above.
facets_summary_toy <- summary(
fit_toy,
profile = "facets",
detail = "brief",
diagnostics = diag_toy
)
res_toy <- facets_summary_toy$results
fit_summary_toy$overview
#> # A tibble: 1 × 52
#> Model Method MethodUsed N Persons Facets FacetInteractions
#> <chr> <chr> <chr> <int> <int> <int> <int>
#> 1 RSM MML MML 282 48 2 0
#> # ℹ 45 more variables: InteractionParameters <int>, InteractionCells <int>,
#> # InteractionSparseCells <int>, Categories <dbl>, LogLik <dbl>, AIC <dbl>,
#> # BIC <dbl>, Converged <lgl>, InferenceReady <lgl>, Iterations <int>,
#> # IterationsBasis <chr>, MMLEngineRequested <chr>, MMLEngineUsed <chr>,
#> # MMLEngineDetail <chr>, EMIterations <int>, EMConverged <lgl>,
#> # EMRelativeChange <dbl>, OptimizerMethod <chr>,
#> # OptimizerInitialMethod <chr>, OptimizerPolished <lgl>, …
fit_summary_toy$readiness
#> Domain Status
#> 1 Numerical pass
#> 2 Data pass
#> 3 Design pass_linked
#> 4 Stability pass
#> 5 Diagnostics not_assessed
#> 6 Reporting ready_for_diagnostics_and_reporting_follow_up
#> Detail
#> 1 Optimizer returned convergence code 0.
#> 2 No preparation warning or review row was retained.
#> 3 The observed graph satisfies the connectivity requirement; review the remaining design and identification assumptions separately.
#> 4 No boundary-constant non-person facet level was detected.
#> 5 Diagnostics have not yet been incorporated into this fit-only status.
#> 6 Reporting status is the strictest applicable upstream workflow state.
fit_summary_toy$data_review
#> $status
#> Domain Status
#> 1 Data pass
#> 2 Design pass_linked
#> 3 Stability pass
#> 4 Reporting ready_for_diagnostics
#>
#> $overall_connectivity
#> $overall_connectivity$summary
#> Subset Criterion Person Rater Observations
#> 1 1 3 48 6 282
#>
#> $overall_connectivity$nodes
#> Node Component Subset Facet Level
#> 1 Person:P001 Person:P048 1 Person P001
#> 2 Person:P002 Person:P048 1 Person P002
#> 3 Person:P003 Person:P048 1 Person P003
#> 4 Person:P004 Person:P048 1 Person P004
#> 5 Person:P005 Person:P048 1 Person P005
#> 6 Person:P006 Person:P048 1 Person P006
#> 7 Person:P007 Person:P048 1 Person P007
#> 8 Person:P008 Person:P048 1 Person P008
#> 9 Person:P009 Person:P048 1 Person P009
#> 10 Person:P010 Person:P048 1 Person P010
#> 11 Person:P011 Person:P048 1 Person P011
#> 12 Person:P012 Person:P048 1 Person P012
#> 13 Person:P013 Person:P048 1 Person P013
#> 14 Person:P014 Person:P048 1 Person P014
#> 15 Person:P015 Person:P048 1 Person P015
#> 16 Person:P016 Person:P048 1 Person P016
#> 17 Person:P017 Person:P048 1 Person P017
#> 18 Person:P018 Person:P048 1 Person P018
#> 19 Person:P019 Person:P048 1 Person P019
#> 20 Person:P020 Person:P048 1 Person P020
#> 21 Person:P021 Person:P048 1 Person P021
#> 22 Person:P022 Person:P048 1 Person P022
#> 23 Person:P023 Person:P048 1 Person P023
#> 24 Person:P024 Person:P048 1 Person P024
#> 25 Person:P025 Person:P048 1 Person P025
#> 26 Person:P026 Person:P048 1 Person P026
#> 27 Person:P027 Person:P048 1 Person P027
#> 28 Person:P028 Person:P048 1 Person P028
#> 29 Person:P029 Person:P048 1 Person P029
#> 30 Person:P030 Person:P048 1 Person P030
#> 31 Person:P031 Person:P048 1 Person P031
#> 32 Person:P032 Person:P048 1 Person P032
#> 33 Person:P033 Person:P048 1 Person P033
#> 34 Person:P034 Person:P048 1 Person P034
#> 35 Person:P035 Person:P048 1 Person P035
#> 36 Person:P036 Person:P048 1 Person P036
#> 37 Person:P037 Person:P048 1 Person P037
#> 38 Person:P038 Person:P048 1 Person P038
#> 39 Person:P039 Person:P048 1 Person P039
#> 40 Person:P040 Person:P048 1 Person P040
#> 41 Person:P041 Person:P048 1 Person P041
#> 42 Person:P042 Person:P048 1 Person P042
#> 43 Person:P043 Person:P048 1 Person P043
#> 44 Person:P044 Person:P048 1 Person P044
#> 45 Person:P045 Person:P048 1 Person P045
#> 46 Person:P046 Person:P048 1 Person P046
#> 47 Person:P047 Person:P048 1 Person P047
#> 48 Person:P048 Person:P048 1 Person P048
#> 49 Rater:R01 Person:P048 1 Rater R01
#> 50 Rater:R02 Person:P048 1 Rater R02
#> 51 Rater:R03 Person:P048 1 Rater R03
#> 52 Rater:R04 Person:P048 1 Rater R04
#> 53 Rater:R05 Person:P048 1 Rater R05
#> 54 Rater:R06 Person:P048 1 Rater R06
#> 55 Criterion:Language Person:P048 1 Criterion Language
#> 56 Criterion:Organization Person:P048 1 Criterion Organization
#> 57 Criterion:Content Person:P048 1 Criterion Content
#>
#> $overall_connectivity$components
#> [1] 1
#>
#> $overall_connectivity$connected
#> [1] TRUE
#>
#> $overall_connectivity$anchors_present
#> [1] FALSE
#>
#>
#> $facet_support
#> Facet ConstantScore BoundaryConstant Level Observations WeightedN
#> 1 Rater FALSE FALSE R01 47 47
#> 2 Rater FALSE FALSE R02 56 56
#> 3 Rater FALSE FALSE R03 50 50
#> 4 Rater FALSE FALSE R04 47 47
#> 5 Rater FALSE FALSE R05 44 44
#> 6 Rater FALSE FALSE R06 38 38
#> 7 Criterion FALSE FALSE Content 94 94
#> 8 Criterion FALSE FALSE Language 94 94
#> 9 Criterion FALSE FALSE Organization 94 94
#> DistinctScores MinScore MaxScore
#> 1 4 1 4
#> 2 4 1 4
#> 3 4 1 4
#> 4 4 1 4
#> 5 4 1 4
#> 6 4 1 4
#> 7 4 1 4
#> 8 4 1 4
#> 9 4 1 4
#>
#> $boundary_levels
#> [1] Facet ConstantScore BoundaryConstant Level
#> [5] Observations WeightedN DistinctScores MinScore
#> [9] MaxScore
#> <0 rows> (or 0-length row.names)
#>
#> $single_level_facets
#> character(0)
#>
#> $preparation_notes
#> [1] Stage Condition Severity Count
#> [5] Affected Message RecommendedAction
#> <0 rows> (or 0-length row.names)
summary(diag_toy)$overview
#> # A tibble: 1 × 10
#> Observations Persons Facets Categories Subsets ResidualPCA DiagnosticMode
#> <int> <int> <int> <int> <int> <chr> <chr>
#> 1 282 48 2 4 1 none both
#> # ℹ 3 more variables: Method <chr>, PrecisionTier <chr>, MarginalFit <chr>
facets_summary_toy
#> Many-Facet Measurement Model Summary
#> Model: RSM | Method: MML | N: 282 | Persons: 48 | Facets: 2 | Categories: 4
#> MML engine: direct (requested: direct)
#>
#> Workflow profile: facets
#> FACETS-style organization; not evidence that FACETS was run and not a claim of numerical equivalence.
#>
#> Visual workflow (in order)
#> Priority Visual Required Available
#> 1 mfrmr Wright map with SE TRUE TRUE
#> 2 FACETS-style Wright map FALSE TRUE
#> 3 Infit pathway FALSE TRUE
#> InterpretationStatus InterpretationReady
#> ready_for_diagnostic_interpretation TRUE
#> ready_for_diagnostic_interpretation TRUE
#> ready_for_diagnostic_interpretation TRUE
#> Plot commands are stored in `$required_visual$Route`.
#>
#> Status
#> - Overall status: Fit completed, but data, design, stability, or diagnostics require review
#> - Convergence: converged (severity: pass, maximum absolute gradient: 1.58e-05)
#> - Estimation path: RSM / direct
#> - Reporting readiness: Review diagnostic findings before reporting
#>
#> Workflow readiness
#> Domain Status
#> Numerical pass
#> Data pass
#> Design pass_linked
#> Stability pass
#> Diagnostics review
#> Reporting review_diagnostics_before_reporting
#>
#> Key warnings
#> - No population model was requested; MML used an unconditional normal person
#> distribution.
#> - Unexpected responses flagged: 60.
#> - Flagged displacement levels: 1.
#> - MnSq screening flagged 18 element(s) outside the configured 0.5-1.5 band.
#> - Person-level fit warnings: 18 row(s); identifiers suppressed. Use
#> `include_person = TRUE` only under appropriate privacy controls.
#> - Strict marginal fit flagged 1 group-level summaries.
#>
#> Next actions
#> - Create the required native Wright map first; run the first available command
#> in `$required_visual$Route`.
#> - Use the FACETS-style ruler only when its familiar layout or rubric labels
#> help readers; it does not establish numerical equivalence.
#> - Use the optional Infit pathway after the Wright map; set `include_person =
#> TRUE` only when selected person points are needed.
#> - Inspect `$analysis` for triage and `$results$tables` for full structured
#> tables before preparing the report.
#>
#> Facet measure overview
#> Facet Levels MeanEstimate SDEstimate MinEstimate MaxEstimate Span
#> Criterion 3 0 0.302 -0.344 0.224 0.568
#> Rater 6 0 0.399 -0.606 0.412 1.018
#>
#> Person measure distribution (aggregate; no identifiers)
#> Persons Mean SD Median Min Max Span MeanPosteriorSD
#> 48 -0.155 0.824 -0.208 -1.718 1.515 3.232 0.476
#>
#> Step parameter summary
#> Steps Min Max Span Monotonic
#> 3 -1.223 1.06 2.283 TRUE
#>
#> Overall fit first screen
#> Infit Outfit InfitZSTD OutfitZSTD InfitZSTD_FACETS OutfitZSTD_FACETS DF_Infit
#> 0.866 0.857 -1.277 -1.752 -1.847 -1.913 173.534
#> DF_Outfit DF_Infit_FACETS DF_Outfit_FACETS
#> 282 352.601 334.499
#>
#> Reliability and separation first screen
#> Facet Levels PrecisionTier Reliability RealReliability Separation Strata
#> Criterion 3 model_based 0.867 0.866 2.555 3.740
#> Person 48 model_based 0.664 0.614 1.406 2.207
#> Rater 6 model_based 0.677 0.677 1.449 2.265
#> MeanInfit MeanOutfit
#> 0.867 0.857
#> 0.856 0.859
#> 0.854 0.847
#>
#> Facet chi-square first screen
#> Facet Levels FixedChiSq FixedDF FixedProb RandomChiSq RandomDF RandomProb
#> Criterion 3 14.936 2 0.001 1.998 1 0.158
#> Person 48 126.685 47 0.000 45.187 46 0.506
#> Rater 6 15.544 5 0.008 4.994 4 0.288
#>
#> Rating-scale first screen
#> Categories UsedCategories UnusedScoreCategories WeaklyIdentifiedThresholds
#> 4 4 0
#> MinCategoryCount MeanCategoryInfit MeanCategoryOutfit ThresholdMonotonic
#> 46 1.079 0.999 TRUE
#> MarginalFitAvailable MarginalFlaggedCategories
#> TRUE 0
#>
#> Labeled step transitions (first rows)
#> Step Transition LowerCategory UpperCategory Estimate GapFromPrev
#> Step_1 1 -> 2 1 2 -1.223 NA
#> Step_2 2 -> 3 2 3 0.163 1.386
#> Step_3 3 -> 4 3 4 1.060 0.896
#> ThresholdMonotonic WeaklyIdentified ThresholdCaveat
#> TRUE FALSE
#> TRUE FALSE
#> TRUE FALSE
#>
#> Analyses intentionally not run by summary
#> Section Status
#> Bias / DIF Not run automatically
#> Residual PCA Not run automatically
#> Linking / anchor drift Not run automatically
#> Detail
#> Bias/DIF requires an explicitly chosen substantive contrast and is not screened automatically.
#> Residual PCA is not computed by the summary workflow; request it explicitly with diagnose_mfrm().
#> Anchor drift/linking requires an explicit multi-fit or multi-wave design and is not inferred automatically.
#>
#> Structured result access
#> - `$analysis`: compact triage, table index, and plot map.
#> - `$results$tables`: full structured tables (not printed here).
#> - Re-run `summary(fit, profile = "facets", detail = "full")` only when more fit-level detail is needed.
# Required first fitted-scale figure.
plot(res_toy, type = "wright", preset = "publication", show_ci = TRUE, top_n = Inf)
# Optional FACETS-style ruler. Replace these examples with the study rubric.
rubric_labels <- c(
"1" = "Level 1",
"2" = "Level 2",
"3" = "Level 3",
"4" = "Level 4"
)
plot(
res_toy,
type = "wright",
renderer = "facets",
category_labels = rubric_labels,
show_ci = FALSE,
preset = "publication"
)
# Setting show_ci = TRUE on this FACETS-style ruler is available as a
# deliberate hybrid: FACETS ruler grammar plus mfrmr uncertainty intervals.
# Optional follow-up: Infit on x, measure on y; persons are explicit opt-in.
plot(
res_toy,
type = "fit_pathway",
fit_stat = "Infit",
include_person = TRUE,
top_n_person = 12,
person_labels = "none",
facet_labels = "flagged",
preset = "publication"
)
Optimizer code zero is only one numerical signal.
fit_mfrm() also checks the terminal gradient; when the
initial direct or hybrid solution stops with a larger gradient and
reltol <= 1e-9, it runs a bounded sequence of
warm-started polishing stages and retains the best non-worsening
candidate under the recorded selection rule. Inspect
fit_toy$opt$optimizer_polish$Stages when
Numerical is not pass.
maxit is only a computational ceiling. It must not be
increased until a preferred coefficient, fit statistic, or expected
conclusion appears. For a final analysis, prespecify the estimator and
controls and start from the package default maxit = 400
unless the analysis protocol states otherwise. If a run reaches
ConvergenceStatus = "iteration_limit", keep it review-only
and repeat the same data, model, method, anchors, optimizer, tolerance,
and quadrature rule with the next ceiling in a prespecified sequence.
Interpret only a run with Converged = TRUE,
InferenceReady = TRUE, and Numerical = pass.
If two separately ready runs differ materially, investigate numerical
stability instead of selecting the preferred result. Report the
requested ceiling, actual evaluations, convergence reason, and terminal
gradient.
InferenceReady describes this numerical gate only: Data,
Design, Stability, Diagnostics, and Reporting remain separate rows in
fit_summary_toy$readiness. For example, a disconnected
design remains a reporting hold even if its optimizer converges.
Conversely, ready_for_diagnostics_and_reporting_follow_up
means that fitting completed and diagnostic review is the next stage; it
is neither an optimization failure nor a claim that the analysis is
already manuscript-ready.
expected_design is a declared roster, not an inferred
complete crossing. When no roster is available, the
structural-missingness status is "not_declared"; the
package does not label unassigned cells as missing.
The facets profile is FACETS-style organization, not
evidence that FACETS was run and not a numerical-equivalence claim. Its
brief print is selective and does not print person identifiers; full
tables remain in facets_summary_toy$results$tables. For a
report-oriented result set, use profile = "reporting". To
inspect availability without allowing diagnostic computation, use
compute = "never"; requested dependent sections are then
recorded as not_computed:
reporting_summary_toy <- summary(
fit_toy,
profile = "reporting",
diagnostics = diag_toy
)
availability_only <- summary(
fit_toy,
profile = "facets",
compute = "never"
)
availability_only$section_statusAvailability and interpretability are intentionally separate. Review
res_toy$readiness and the InterpretationStatus
columns in res_toy$plot_map before treating an available
plot as a final result. Plots remain available during a numerical, data,
design, or stability review, but they warn and carry
REVIEW ONLY in the returned subtitle and drawn title until
the readiness issue is resolved.
Bias/DIF, residual PCA, and anchor-drift/linking analyses are deliberately not auto-run by any summary profile. They require explicit contrasts, diagnostic settings, or multi-fit designs.
The same fit can then move through the recommended first reporting workflow:
report_toy <- mfrm_report(res_toy, style = "qc")
summary(res_toy)$next_actions
#> Priority Area
#> 1 1 Overview
#> 3 2 Triage
#> 2 2 Wright map
#> 4 3 Diagnostics
#> 5 4 Visual diagnostics
#> 6 5 Fit pathway
#> 7 11 Tables
#> Action
#> 1 Read the compact results summary.
#> 3 Read the first-screen triage before branching.
#> 2 Create and inspect the required shared-logit scale map.
#> 4 Review diagnostic key warnings before report drafting.
#> 5 Open the QC dashboard after reviewing the Wright map.
#> 6 Review Infit against measure, including selected person rows when useful.
#> 7 Create an appendix-ready summary-table bundle.
#> Route
#> 1 summary(res)
#> 3 summary(res)$triage
#> 2 plot(res, type = "wright", preset = "publication", show_ci = TRUE, top_n = Inf)
#> 4 summary(res$diagnostics)$key_warnings
#> 5 plot(res, type = "qc", preset = "publication")
#> 6 plot(res, type = "fit_pathway", fit_stat = "Infit", include_person = TRUE, top_n_person = 12, person_labels = "none", facet_labels = "flagged", preset = "publication")
#> 7 build_summary_table_bundle(res)
#> Reason
#> 1 Confirms input mode, model, method, section status, table coverage, and available figures.
#> 3 Triage orders unavailable, review, information, and OK signals across diagnostics, tables, plots, and reporting outputs.
#> 2 The Wright map is the primary fitted-scale figure: compare person targeting with facet levels and step thresholds before branching into diagnostics.
#> 4 Diagnostic warnings identify the highest-priority fit, precision, residual, or category follow-up checks.
#> 5 The QC dashboard gives a focused follow-up view of fit, residual, and category summaries.
#> 6 This follow-up separates measure uncertainty from fit displacement while keeping person inclusion explicit.
#> 7 The bundle exposes table roles, plot readiness, and conservative appendix presets.
summary(report_toy)$overview
#> Style OverallStatus FirstAction ReviewAreas NotComputedAreas CaveatAreas
#> 1 qc review Start with Fit. 1 0 1
#> OptionalAreas UnavailableAreas OkAreas
#> 1 3 0 0
#> SourceInclude
#> 1 fit, diagnostics, tables, categories, plots, facets_fit
# This is a controlled analysis archive, not a deidentified shareable export.
export_dir <- file.path(tempdir(), "mfrmr-workflow-export")
export_toy <- export_mfrm_results(
res_toy,
output_dir = export_dir,
include = c("default", "report"),
overwrite = TRUE,
acknowledge_sensitive = TRUE
)
head(export_toy$written_files)
#> Component Format
#> 1 summary_overview csv
#> 2 summary_status csv
#> 3 summary_component_index csv
#> 4 summary_table_index csv
#> 5 summary_plot_map csv
#> 6 summary_triage csv
#> Path
#> 1 /tmp/Rtmpb18VEP/mfrmr-workflow-export/mfrmr_results_summary_overview.csv
#> 2 /tmp/Rtmpb18VEP/mfrmr-workflow-export/mfrmr_results_summary_status.csv
#> 3 /tmp/Rtmpb18VEP/mfrmr-workflow-export/mfrmr_results_summary_component_index.csv
#> 4 /tmp/Rtmpb18VEP/mfrmr-workflow-export/mfrmr_results_summary_table_index.csv
#> 5 /tmp/Rtmpb18VEP/mfrmr-workflow-export/mfrmr_results_summary_plot_map.csv
#> 6 /tmp/Rtmpb18VEP/mfrmr-workflow-export/mfrmr_results_summary_triage.csv
#> Note DataHandling
#> 1 review_before_sharing
#> 2 review_before_sharing
#> 3 review_before_sharing
#> 4 review_before_sharing
#> 5 review_before_sharing
#> 6 review_before_sharingThe acknowledgement suppresses the warning only; it does not redact person identifiers, person-level results, local paths, or the complete RDS object. Review every exported file under the study’s data-handling policy before sharing.
Diagnostics and Reporting
t4_toy <- unexpected_response_table(
fit_toy,
diagnostics = diag_toy,
abs_z_min = 1.5,
prob_max = 0.4,
top_n = 10
)
t12_toy <- fair_average_table(fit_toy, diagnostics = diag_toy)
t13_toy <- bias_interaction_report(
estimate_bias(fit_toy, diag_toy,
facet_a = "Rater", facet_b = "Criterion",
max_iter = 2),
top_n = 10
)
class(summary(t4_toy))
#> [1] "summary.mfrm_bundle"
class(summary(t12_toy))
#> [1] "summary.mfrm_bundle"
class(summary(t13_toy))
#> [1] "summary.mfrm_bundle"
names(plot(t4_toy, draw = FALSE))
#> [1] "name" "data"
names(plot(t12_toy, draw = FALSE))
#> [1] "name" "data"
names(plot(t13_toy, draw = FALSE))
#> [1] "name" "data"
chk_toy <- reporting_checklist(fit_toy, diagnostics = diag_toy)
subset(
chk_toy$checklist,
Section == "Visual Displays",
c("Item", "DraftReady", "NextAction")
)
#> Item DraftReady
#> 25 Wright map TRUE
#> 26 QC / facet dashboard TRUE
#> 27 Residual PCA visuals FALSE
#> 28 Connectivity / design-matrix visual TRUE
#> 29 Inter-rater / displacement visuals TRUE
#> 30 Strict marginal visuals FALSE
#> 31 Bias / DIF visuals FALSE
#> 32 Precision / information curves TRUE
#> 33 Fit/category visuals TRUE
#> NextAction
#> 25 Include a Wright map when the manuscript benefits from a shared-scale targeting display.
#> 26 Use the dashboard as a first-pass triage view, then move to the specific follow-up plot behind each flag.
#> 27 Run residual PCA if you want scree/loadings visuals for residual-structure follow-up.
#> 28 Use the design-matrix view to support linkage and comparability claims.
#> 29 Use displacement and inter-rater views to localize QC issues after dashboard screening.
#> 30 Treat strict marginal plots as exploratory corroboration screens, then corroborate with design review and legacy diagnostics.
#> 31 Run bias or DIF screening before discussing interaction-level visuals.
#> 32 Use information curves to describe precision across theta when that is the reporting question.
#> 33 Use category curves and fit visuals as local descriptive follow-up after QC screening.Fit and Diagnose with Full Data
For a larger sparse synthetic illustration, use the packaged Study 1 dataset:
fit <- fit_mfrm(
data = ej2021_study1,
person = "Person",
facets = c("Rater", "Criterion"),
score = "Score",
method = "MML",
model = "RSM",
quad_points = 7
)
diag <- diagnose_mfrm(
fit,
residual_pca = "none",
diagnostic_mode = "both",
fit_df_method = "both"
)
summary(fit, profile = "fit", detail = "brief")
#> Many-Facet Measurement Model Summary
#> Model: RSM | Method: MML | N: 1842 | Persons: 307 | Facets: 2 | Categories: 4
#> MML engine: direct (requested: direct)
#>
#> Visual workflow (in order)
#> Priority Visual Required Available
#> 1 mfrmr Wright map with SE TRUE TRUE
#> 2 FACETS-style Wright map FALSE TRUE
#> 3 Infit pathway FALSE FALSE
#> InterpretationStatus InterpretationReady
#> ready_for_diagnostic_interpretation TRUE
#> ready_for_diagnostic_interpretation TRUE
#> not_available FALSE
#> Plot commands are stored in `$required_visual$Route`.
#>
#> Status
#> - Overall status: Fit completed; review diagnostics before reporting
#> - Convergence: converged (severity: pass, maximum absolute gradient: 7.44e-05)
#> - Estimation path: RSM / direct
#> - Reporting readiness: Fit completed; diagnostics and reporting review can proceed
#>
#> Workflow readiness
#> Domain Status
#> Numerical pass
#> Data pass
#> Design pass_linked
#> Stability pass
#> Diagnostics not_assessed
#> Reporting ready_for_diagnostics_and_reporting_follow_up
#>
#> Key warnings
#> - No population model was requested; MML used an unconditional normal person
#> distribution.
#>
#> Next actions
#> - After reviewing convergence, run `review <- summary(fit, profile = "facets",
#> detail = "brief")` for the comprehensive FACETS-organized result surface.
#> - Then draw the complete native Wright map with `plot(fit, type = "wright",
#> show_ci = TRUE, top_n = Inf, preset = "publication")`.
#> - Reuse `review$results$diagnostics`; call `diagnose_mfrm()` again only for
#> residual PCA or other custom settings.
#> - Use `reporting_checklist(fit, diagnostics = review$results$diagnostics)` for
#> reporting readiness.
#>
#> Facet measure overview
#> Facet Levels MeanEstimate SDEstimate MinEstimate MaxEstimate Span
#> Criterion 3 0 0.693 -0.799 0.431 1.230
#> Rater 18 0 0.667 -0.948 1.622 2.569
#>
#> Person measure distribution (aggregate; no identifiers)
#> Persons Mean SD Median Min Max Span MeanPosteriorSD
#> 307 0.414 0.812 0.436 -1.451 2.385 3.835 0.482
#>
#> Step parameter summary
#> Steps Min Max Span Monotonic
#> 3 -1.093 0.958 2.051 TRUE
#>
#> Analyses intentionally not run by summary
#> Section Status
#> Bias / DIF Not run automatically
#> Residual PCA Not run automatically
#> Linking / anchor drift Not run automatically
#> Detail
#> Bias/DIF requires an explicitly chosen substantive contrast and is not screened automatically.
#> Residual PCA is not computed by the summary workflow; request it explicitly with diagnose_mfrm().
#> Anchor drift/linking requires an explicit multi-fit or multi-wave design and is not inferred automatically.
#>
#> Section availability requiring attention
#> Section Status
#> diagnostics not_requested
#> Detail
#> The fit profile does not compute diagnostics. Use profile = 'facets' or 'reporting' to request them.
#>
#> Structured result access
#> - Use `summary(fit, profile = "facets")` for the computed FACETS-organized review.
#> - Use `summary(fit, detail = "full")` for legacy fit-level detail.
summary(diag)
#> Many-Facet Measurement Diagnostics Summary
#> Observations: 1842 | Persons: 307 | Facets: 2 | Categories: 4 | Subsets: 1
#> Residual PCA mode: none
#> Method: MML | Precision tier: Model-based precision
#> Diagnostic mode: Legacy and strict marginal
#> Strict marginal fit: Available
#>
#> Status
#> - Overall status: Follow-up needed
#> - Diagnostic path: Legacy and strict marginal
#> - Strict marginal fit: Available
#> - Precision tier: Model-based precision
#> - Primary screen: Read strict marginal fit first; use legacy residuals for
#> continuity and follow-up.
#>
#> Key warnings
#> - Unexpected responses flagged: 100.
#> - Flagged displacement levels: 40.
#> - MnSq screening flagged 130 element(s) outside the configured 0.5-1.5 band.
#> - Person-level fit warnings: 130 row(s); identifiers suppressed. Use
#> `include_person = TRUE` only under appropriate privacy controls.
#> - Strict marginal fit flagged 4 group-level summaries.
#>
#> Next actions
#> - Inspect `diagnostic_basis` before comparing legacy residual evidence with
#> strict marginal evidence.
#> - Review `top_marginal_cells` and `rating_scale_table(..., diagnostics =
#> diag)` for first-order strict marginal follow-up.
#> - Review `top_marginal_pairs` for pairwise local-dependence follow-up.
#> - Use `unexpected_response_table()` / `plot_unexpected()` and
#> `displacement_table()` / `plot_displacement()` for case-level follow-up.
#>
#> Overall fit
#> Infit Outfit InfitZSTD OutfitZSTD DF_Infit DF_Outfit DF_Infit_FACETS
#> 0.811 0.786 -4.629 -7.01 1058.853 1842 2068.188
#> DF_Outfit_FACETS DF_Infit_ENGINE DF_Outfit_ENGINE InfitZSTD_ENGINE
#> 1613.625 1058.853 1842 -4.629
#> OutfitZSTD_ENGINE InfitZSTD_FACETS OutfitZSTD_FACETS
#> -7.01 -6.48 -6.56
#> FitDfMethod FitZSTDTransform FitZSTDCap
#> engine_primary_facets_available Wilson-Hilferty 9
#>
#> Flag counts
#> Metric Count
#> Unexpected responses 100
#> Flagged displacement levels 40
#> Interaction rows 20
#> Inter-rater pairs 153
#> Marginal fit flagged groups 4
#> Marginal pairwise flagged level pairs 90
#>
#> Facet precision and spread
#> Facet Levels Separation Strata Reliability RealSeparation RealStrata
#> Criterion 3 14.918 20.223 0.996 14.918 20.223
#> Person 307 1.322 2.096 0.636 1.226 1.968
#> Rater 18 3.121 4.495 0.907 3.110 4.480
#> RealReliability MeanInfit MeanOutfit
#> 0.996 0.810 0.786
#> 0.600 0.798 0.786
#> 0.906 0.813 0.786
#>
#> Highest-priority non-person fit rows
#> Facet Level Infit Outfit InfitZSTD OutfitZSTD DF_Infit
#> Criterion Global_Impression 0.799 0.744 -2.590 -4.913 292.462
#> Rater R08 0.702 0.661 -2.434 -4.103 110.307
#> Criterion Linguistic_Realization 0.803 0.798 -2.907 -3.799 382.619
#> Criterion Task_Fulfillment 0.830 0.816 -2.481 -3.416 383.772
#> Rater R10 0.738 0.726 -2.187 -2.939 118.696
#> DF_Outfit InfitZSTD_FACETS OutfitZSTD_FACETS DF_Infit_FACETS DF_Outfit_FACETS
#> 614 -3.621 -3.881 565.702 384.918
#> 228 -3.420 -3.134 213.893 134.449
#> 614 -4.086 -3.969 750.062 669.476
#> 614 -3.491 -3.578 752.499 673.020
#> 192 -3.085 -3.012 231.661 201.432
#> AbsZ
#> 4.913
#> 4.103
#> 3.799
#> 3.416
#> 2.939
#>
#> Further detail
#> - Additional tables remain in the structured summary; use `detail = "full"` to
#> print them.
# Keep the final figure flow explicit: fit -> Wright map -> follow-up plots.
s <- summary(fit, profile = "facets", diagnostics = diag)
res <- s$results
s
#> Many-Facet Measurement Model Summary
#> Model: RSM | Method: MML | N: 1842 | Persons: 307 | Facets: 2 | Categories: 4
#> MML engine: direct (requested: direct)
#>
#> Workflow profile: facets
#> FACETS-style organization; not evidence that FACETS was run and not a claim of numerical equivalence.
#>
#> Visual workflow (in order)
#> Priority Visual Required Available
#> 1 mfrmr Wright map with SE TRUE TRUE
#> 2 FACETS-style Wright map FALSE TRUE
#> 3 Infit pathway FALSE TRUE
#> InterpretationStatus InterpretationReady
#> ready_for_diagnostic_interpretation TRUE
#> ready_for_diagnostic_interpretation TRUE
#> ready_for_diagnostic_interpretation TRUE
#> Plot commands are stored in `$required_visual$Route`.
#>
#> Status
#> - Overall status: Fit completed, but data, design, stability, or diagnostics require review
#> - Convergence: converged (severity: pass, maximum absolute gradient: 7.44e-05)
#> - Estimation path: RSM / direct
#> - Reporting readiness: Review diagnostic findings before reporting
#>
#> Workflow readiness
#> Domain Status
#> Numerical pass
#> Data pass
#> Design pass_linked
#> Stability pass
#> Diagnostics review
#> Reporting review_diagnostics_before_reporting
#>
#> Key warnings
#> - No population model was requested; MML used an unconditional normal person
#> distribution.
#> - Unexpected responses flagged: 100.
#> - Flagged displacement levels: 40.
#> - MnSq screening flagged 130 element(s) outside the configured 0.5-1.5 band.
#> - Person-level fit warnings: 130 row(s); identifiers suppressed. Use
#> `include_person = TRUE` only under appropriate privacy controls.
#> - Strict marginal fit flagged 4 group-level summaries.
#>
#> Next actions
#> - Create the required native Wright map first; run the first available command
#> in `$required_visual$Route`.
#> - Use the FACETS-style ruler only when its familiar layout or rubric labels
#> help readers; it does not establish numerical equivalence.
#> - Use the optional Infit pathway after the Wright map; set `include_person =
#> TRUE` only when selected person points are needed.
#> - Inspect `$analysis` for triage and `$results$tables` for full structured
#> tables before preparing the report.
#>
#> Facet measure overview
#> Facet Levels MeanEstimate SDEstimate MinEstimate MaxEstimate Span
#> Criterion 3 0 0.693 -0.799 0.431 1.230
#> Rater 18 0 0.667 -0.948 1.622 2.569
#>
#> Person measure distribution (aggregate; no identifiers)
#> Persons Mean SD Median Min Max Span MeanPosteriorSD
#> 307 0.414 0.812 0.436 -1.451 2.385 3.835 0.482
#>
#> Step parameter summary
#> Steps Min Max Span Monotonic
#> 3 -1.093 0.958 2.051 TRUE
#>
#> Overall fit first screen
#> Infit Outfit InfitZSTD OutfitZSTD InfitZSTD_FACETS OutfitZSTD_FACETS DF_Infit
#> 0.811 0.786 -4.629 -7.01 -6.48 -6.56 1058.853
#> DF_Outfit DF_Infit_FACETS DF_Outfit_FACETS
#> 1842 2068.188 1613.625
#>
#> Reliability and separation first screen
#> Facet Levels PrecisionTier Reliability RealReliability Separation Strata
#> Criterion 3 model_based 0.996 0.996 14.918 20.223
#> Person 307 model_based 0.636 0.600 1.322 2.096
#> Rater 18 model_based 0.907 0.906 3.121 4.495
#> MeanInfit MeanOutfit
#> 0.810 0.786
#> 0.798 0.786
#> 0.813 0.786
#>
#> Facet chi-square first screen
#> Facet Levels FixedChiSq FixedDF FixedProb RandomChiSq RandomDF RandomProb
#> Criterion 3 413.138 2 0 1.999 1 0.157
#> Person 307 888.264 306 0 302.434 305 0.531
#> Rater 18 248.677 17 0 17.049 16 0.382
#>
#> Rating-scale first screen
#> Categories UsedCategories UnusedScoreCategories WeaklyIdentifiedThresholds
#> 4 4 0
#> MinCategoryCount MeanCategoryInfit MeanCategoryOutfit ThresholdMonotonic
#> 215 0.948 0.864 TRUE
#> MarginalFitAvailable MarginalFlaggedCategories
#> TRUE 2
#>
#> Labeled step transitions (first rows)
#> Step Transition LowerCategory UpperCategory Estimate GapFromPrev
#> Step_1 1 -> 2 1 2 -1.093 NA
#> Step_2 2 -> 3 2 3 0.134 1.227
#> Step_3 3 -> 4 3 4 0.958 0.824
#> ThresholdMonotonic WeaklyIdentified ThresholdCaveat
#> TRUE FALSE
#> TRUE FALSE
#> TRUE FALSE
#>
#> Analyses intentionally not run by summary
#> Section Status
#> Bias / DIF Not run automatically
#> Residual PCA Not run automatically
#> Linking / anchor drift Not run automatically
#> Detail
#> Bias/DIF requires an explicitly chosen substantive contrast and is not screened automatically.
#> Residual PCA is not computed by the summary workflow; request it explicitly with diagnose_mfrm().
#> Anchor drift/linking requires an explicit multi-fit or multi-wave design and is not inferred automatically.
#>
#> Structured result access
#> - `$analysis`: compact triage, table index, and plot map.
#> - `$results$tables`: full structured tables (not printed here).
#> - Re-run `summary(fit, profile = "facets", detail = "full")` only when more fit-level detail is needed.
plot(res, type = "wright", preset = "publication", show_ci = TRUE, top_n = Inf)
# Optional closest FACETS-style asterisk ruler (without mfrmr CI overlays).
plot(res, type = "wright", renderer = "facets",
category_labels = rubric_labels, show_ci = FALSE,
preset = "publication")
plot(
res,
type = "fit_pathway",
fit_stat = "Infit",
include_person = TRUE,
top_n_person = 12,
person_labels = "none",
facet_labels = "flagged",
preset = "publication"
)
This full-data figure caps the displayed person layer at 12 and
suppresses routine point labels to keep the first screen legible. The
selected person IDs and every retained facet row remain in
plot(..., draw = FALSE)$data$table; use
person_labels = "all" or facet_labels = "all"
for a point-identification figure.
If you need residual-structure evidence for a final report, you can add residual PCA after the initial diagnostic pass. Treat this as an exploratory screen, not as a standalone unidimensionality test or as a DIMTEST/UNIDIM substitute. In MFRM reporting, a cautious claim should combine global residual fit, element-level fit, residual PCA, and local-dependence screens, for example: “evidence consistent with essential unidimensionality under the specified facet structure.”
diag_pca <- diagnose_mfrm(
fit,
residual_pca = "both",
pca_max_factors = 6
)
summary(diag_pca)
#> Many-Facet Measurement Diagnostics Summary
#> Observations: 1842 | Persons: 307 | Facets: 2 | Categories: 4 | Subsets: 1
#> Residual PCA mode: both
#> Method: MML | Precision tier: Model-based precision
#> Diagnostic mode: Legacy and strict marginal
#> Strict marginal fit: Available
#>
#> Status
#> - Overall status: Follow-up needed
#> - Diagnostic path: Legacy and strict marginal
#> - Strict marginal fit: Available
#> - Precision tier: Model-based precision
#> - Primary screen: Read strict marginal fit first; use legacy residuals for
#> continuity and follow-up.
#>
#> Key warnings
#> - Unexpected responses flagged: 100.
#> - Flagged displacement levels: 40.
#> - MnSq screening flagged 130 element(s) outside the configured 0.5-1.5 band.
#> - Person-level fit warnings: 130 row(s); identifiers suppressed. Use
#> `include_person = TRUE` only under appropriate privacy controls.
#> - Strict marginal fit flagged 4 group-level summaries.
#>
#> Next actions
#> - Inspect `diagnostic_basis` before comparing legacy residual evidence with
#> strict marginal evidence.
#> - Review `top_marginal_cells` and `rating_scale_table(..., diagnostics =
#> diag)` for first-order strict marginal follow-up.
#> - Review `top_marginal_pairs` for pairwise local-dependence follow-up.
#> - Use `unexpected_response_table()` / `plot_unexpected()` and
#> `displacement_table()` / `plot_displacement()` for case-level follow-up.
#>
#> Overall fit
#> Infit Outfit InfitZSTD OutfitZSTD DF_Infit DF_Outfit
#> 0.811 0.786 -4.629 -7.01 1058.853 1842
#>
#> Flag counts
#> Metric Count
#> Unexpected responses 100
#> Flagged displacement levels 40
#> Interaction rows 20
#> Inter-rater pairs 153
#> Marginal fit flagged groups 4
#> Marginal pairwise flagged level pairs 90
#>
#> Facet precision and spread
#> Facet Levels Separation Strata Reliability RealSeparation RealStrata
#> Criterion 3 14.918 20.223 0.996 14.918 20.223
#> Person 307 1.322 2.096 0.636 1.226 1.968
#> Rater 18 3.121 4.495 0.907 3.110 4.480
#> RealReliability MeanInfit MeanOutfit
#> 0.996 0.810 0.786
#> 0.600 0.798 0.786
#> 0.906 0.813 0.786
#>
#> Highest-priority non-person fit rows
#> Facet Level Infit Outfit InfitZSTD OutfitZSTD DF_Infit
#> Criterion Global_Impression 0.799 0.744 -2.590 -4.913 292.462
#> Rater R08 0.702 0.661 -2.434 -4.103 110.307
#> Criterion Linguistic_Realization 0.803 0.798 -2.907 -3.799 382.619
#> Criterion Task_Fulfillment 0.830 0.816 -2.481 -3.416 383.772
#> Rater R10 0.738 0.726 -2.187 -2.939 118.696
#> DF_Outfit AbsZ
#> 614 4.913
#> 228 4.103
#> 614 3.799
#> 614 3.416
#> 192 2.939
#>
#> Further detail
#> - Additional tables remain in the structured summary; use `detail = "full"` to
#> print them.Strict Diagnostics for RSM and PCM
For RSM and PCM, the package can now keep
the legacy residual path and the strict marginal path side by side:
fit_rsm_strict <- fit_mfrm(
data = toy,
person = "Person",
facets = c("Rater", "Criterion"),
score = "Score",
method = "MML",
model = "RSM",
quad_points = 7,
maxit = 30
)
diag_rsm_strict <- diagnose_mfrm(
fit_rsm_strict,
diagnostic_mode = "both",
residual_pca = "none"
)
fit_pcm_strict <- fit_mfrm(
data = toy,
person = "Person",
facets = c("Rater", "Criterion"),
score = "Score",
method = "MML",
model = "PCM",
step_facet = "Criterion",
quad_points = 7,
maxit = 30
)
diag_pcm_strict <- diagnose_mfrm(
fit_pcm_strict,
diagnostic_mode = "both",
residual_pca = "none"
)
summary(diag_rsm_strict)$diagnostic_basis[, c("DiagnosticPath", "Status", "Basis")]
#> # A tibble: 4 × 3
#> DiagnosticPath Status Basis
#> <chr> <chr> <chr>
#> 1 legacy_residual_fit computed plugin_residuals_and_eap_tables
#> 2 strict_marginal_fit computed latent_integrated_first_order_…
#> 3 strict_pairwise_local_dependence computed latent_integrated_second_order…
#> 4 posterior_predictive_follow_up not_available posterior_predictive_replicati…
summary(diag_pcm_strict)$diagnostic_basis[, c("DiagnosticPath", "Status", "Basis")]
#> # A tibble: 4 × 3
#> DiagnosticPath Status Basis
#> <chr> <chr> <chr>
#> 1 legacy_residual_fit computed plugin_residuals_and_eap_tables
#> 2 strict_marginal_fit computed latent_integrated_first_order_…
#> 3 strict_pairwise_local_dependence computed latent_integrated_second_order…
#> 4 posterior_predictive_follow_up not_available posterior_predictive_replicati…When you want a compact simulation-based screening check for the
strict branch, use evaluate_mfrm_diagnostic_screening() on
a small design:
screen_rsm <- evaluate_mfrm_diagnostic_screening(
design = list(person = 18, rater = 3, criterion = 3, assignment = 3),
reps = 1,
scenarios = c("well_specified", "local_dependence"),
model = "RSM",
maxit = 30,
quad_points = 7,
seed = 123
)
screen_pcm <- evaluate_mfrm_diagnostic_screening(
design = list(person = 18, rater = 3, criterion = 3, assignment = 3),
reps = 1,
scenarios = c("well_specified", "step_structure_misspecification"),
model = "PCM",
maxit = 30,
quad_points = 7,
seed = 123
)
screen_rsm$performance_summary[, c("Scenario", "EvaluationUse", "LegacyAnyFlagRate", "StrictAnyFlagRate")]
#> # A tibble: 2 × 4
#> Scenario EvaluationUse LegacyAnyFlagRate StrictAnyFlagRate
#> <chr> <chr> <dbl> <dbl>
#> 1 local_dependence sensitivity_proxy 0 1
#> 2 well_specified type_I_proxy 1 1
screen_pcm$performance_summary[, c("Scenario", "EvaluationUse", "LegacySensitivityProxy", "StrictSensitivityProxy", "DeltaStrictMinusLegacyFlagRate")]
#> # A tibble: 2 × 5
#> Scenario EvaluationUse LegacySensitivityProxy StrictSensitivityProxy
#> <chr> <chr> <dbl> <dbl>
#> 1 step_structure_mi… sensitivity_… 1 1
#> 2 well_specified type_I_proxy NA NA
#> # ℹ 1 more variable: DeltaStrictMinusLegacyFlagRate <dbl>The same strict branch is now reflected in the reporting router:
chk_rsm_strict <- reporting_checklist(fit_rsm_strict, diagnostics = diag_rsm_strict)
subset(
chk_rsm_strict$checklist,
Section == "Visual Displays" &
Item %in% c("QC / facet dashboard", "Strict marginal visuals", "Precision / information curves"),
c("Item", "Available", "DraftReady", "NextAction")
)
#> Item Available DraftReady
#> 26 QC / facet dashboard TRUE TRUE
#> 30 Strict marginal visuals TRUE FALSE
#> 32 Precision / information curves TRUE TRUE
#> NextAction
#> 26 Use the dashboard as a first-pass triage view, then move to the specific follow-up plot behind each flag.
#> 30 Treat strict marginal plots as exploratory corroboration screens, then corroborate with design review and legacy diagnostics.
#> 32 Use information curves to describe precision across theta when that is the reporting question.Residual PCA and Reporting
pca <- analyze_residual_pca(diag_pca, mode = "both")
plot_residual_pca(pca, mode = "overall", plot_type = "scree")
data("mfrmr_example_bias", package = "mfrmr")
bias_df <- mfrmr_example_bias
fit_bias <- fit_mfrm(
bias_df,
person = "Person",
facets = c("Rater", "Criterion"),
score = "Score",
method = "MML",
model = "RSM",
quad_points = 7
)
diag_bias <- diagnose_mfrm(fit_bias, residual_pca = "none")
bias <- estimate_bias(fit_bias, diag_bias, facet_a = "Rater", facet_b = "Criterion")
fixed <- build_fixed_reports(bias)
apa <- build_apa_outputs(fit_bias, diag_bias, bias_results = bias)
mfrm_threshold_profiles()
#> mfrmr Threshold Profile Summary
#>
#> Overview
#> Profiles ThresholdCount PCAReferenceCount DefaultProfile
#> 3 11 7 standard
#>
#> Profile thresholds
#> Threshold strict standard lenient
#> expected_var_min 0.30 2e-01 0.10
#> low_cat_min 15.00 1e+01 5.00
#> min_facet_levels 4.00 3e+00 2.00
#> misfit_ratio_warn 0.08 1e-01 0.15
#> missing_fit_ratio_warn 0.15 2e-01 0.30
#> n_obs_min 200.00 1e+02 60.00
#> n_person_min 50.00 3e+01 20.00
#> pca_first_eigen_warn 1.50 2e+00 3.00
#> pca_first_prop_warn 0.10 1e-01 0.20
#> zstd2_ratio_warn 0.08 1e-01 0.15
#> zstd3_ratio_warn 0.03 5e-02 0.08
#>
#> Threshold ranges across profiles
#> Threshold Min Median Max Span
#> expected_var_min 0.10 2e-01 0.30 0.20
#> low_cat_min 5.00 1e+01 15.00 10.00
#> min_facet_levels 2.00 3e+00 4.00 2.00
#> misfit_ratio_warn 0.08 1e-01 0.15 0.07
#> missing_fit_ratio_warn 0.15 2e-01 0.30 0.15
#> n_obs_min 60.00 1e+02 200.00 140.00
#> n_person_min 20.00 3e+01 50.00 30.00
#> pca_first_eigen_warn 1.50 2e+00 3.00 1.50
#> pca_first_prop_warn 0.10 1e-01 0.20 0.10
#> zstd2_ratio_warn 0.08 1e-01 0.15 0.07
#> zstd3_ratio_warn 0.03 5e-02 0.08 0.05
#>
#> PCA reference bands
#> Band Key Value
#> eigenvalue critical_minimum 1.40
#> eigenvalue caution 1.50
#> eigenvalue common 2.00
#> eigenvalue strong 3.00
#> proportion minor 0.05
#> proportion caution 0.10
#> proportion strong 0.20
#>
#> Notes
#> - Profiles tune warning strictness for build_visual_summaries().Use `thresholds` in build_visual_summaries() to override selected values.
vis <- build_visual_summaries(fit_bias, diag_bias, threshold_profile = "standard")
vis$warning_map$residual_pca_overall
#> [1] "Threshold profile: standard (PC1 EV >= 2.0, variance >= 10%)."
#> [2] "Heuristic reference bands: EV >= 1.4 (critical minimum), >= 1.5 (caution), >= 2.0 (common), >= 3.0 (strong); variance >= 5% (minor), >= 10% (caution), >= 20% (strong)."
#> [3] "Current exploratory PC1 checks: EV>=1.5:Y, EV>=2.0:Y, EV>=3.0:Y, Var>=10%:Y, Var>=20%:Y."
#> [4] "Overall residual PCA PC1 exceeds the current heuristic eigenvalue band (3.22)."
#> [5] "Overall residual PCA PC1 explains 20.1% variance."The same example_bias dataset also carries a
Group variable so DIF-oriented examples can show a non-null
pattern instead of a fully clean result. It can be loaded either with
load_mfrmr_data("example_bias") or
data("mfrmr_example_bias", package = "mfrmr").
Human-Readable Reporting API
spec <- specifications_report(fit, title = "Study run")
data_qc <- data_quality_report(
fit,
data = ej2021_study1,
person = "Person",
facets = c("Rater", "Criterion"),
score = "Score"
)
iter <- estimation_iteration_report(fit, max_iter = 8)
subset_rep <- subset_connectivity_report(fit, diagnostics = diag)
facet_stats <- facet_statistics_report(fit, diagnostics = diag)
cat_structure <- category_structure_report(fit, diagnostics = diag)
cat_curves <- category_curves_report(fit, theta_points = 101)
bias_rep <- bias_interaction_report(bias, top_n = 20)
plot_bias_interaction(bias_rep, plot = "scatter")
Design Simulation and Prediction
The package also supports a separate simulation/prediction layer. The key distinction is:
-
evaluate_mfrm_recovery()checks whether known generating parameters are recovered under a stated simulation design. It is the first simulation check to run when you are validating a model specification or a planned design. -
evaluate_mfrm_design()andpredict_mfrm_population()are design-level helpers that summarize expected operating characteristics under an explicit simulation specification. -
mfrm_generalizability()andmfrm_d_study()summarize observed univariate G-study components and analytic D-study projections. ReadIdentificationStatus,GStatus, andPhiStatusbefore reporting projected coefficients; boundary or singular mixed-model fits are design-identification warnings rather than high-stakes-ready reliability evidence. -
predict_mfrm_units()andsample_mfrm_plausible_values()score future or partially observed persons under a fixedMMLcalibration.
if (requireNamespace("lme4", quietly = TRUE)) {
gt <- mfrm_generalizability(fit)
gt$coefficients[, c("G", "Phi", "GStatus", "PhiStatus",
"IdentificationStatus")]
ds <- mfrm_d_study(
gt,
data.frame(Rater = c(2, 3, 4), Criterion = 4),
residual_scaling = "sensitivity"
)
ds[, c("n_Rater", "n_Criterion", "ResidualScaling",
"G", "Phi", "GStatus", "PhiStatus", "IdentificationStatus")]
}
sim_spec <- build_mfrm_sim_spec(
n_person = 30,
n_rater = 4,
n_criterion = 4,
raters_per_person = 2,
assignment = "rotating"
)
recovery <- suppressWarnings(
evaluate_mfrm_recovery(
sim_spec = sim_spec,
reps = 2,
maxit = 30,
include_diagnostics = TRUE,
diagnostic_fit_df_method = "both",
seed = 2
)
)
summary(recovery)$recovery_summary[, c("ParameterType", "Facet", "RMSE", "Bias")]
#> # A tibble: 4 × 4
#> ParameterType Facet RMSE Bias
#> <chr> <chr> <dbl> <dbl>
#> 1 facet Criterion 0.151 -3.47e-18
#> 2 facet Rater 0.161 -3.47e-18
#> 3 person Person 0.480 1.27e-17
#> 4 step Common 0.200 -4.62e-18
plot(recovery, type = "summary", metric = "rmse", draw = FALSE)$data$plot_table
#> # A tibble: 4 × 22
#> ParameterType Facet ComparisonScale Rows Reps ComparableRate MeanTruth
#> <chr> <chr> <chr> <int> <int> <dbl> <dbl>
#> 1 facet Criterion logit 8 2 1 -0.176
#> 2 facet Rater logit 8 2 1 -0.0221
#> 3 person Person logit 60 2 1 0.0229
#> 4 step Common logit 6 2 1 0
#> # ℹ 15 more variables: MeanEstimate <dbl>, Bias <dbl>, McseBias <dbl>,
#> # RMSE <dbl>, McseRMSE <dbl>, MAE <dbl>, RawBias <dbl>, RawRMSE <dbl>,
#> # Correlation <dbl>, MeanSE <dbl>, SEAvailableRate <dbl>, Coverage95 <dbl>,
#> # RecoveryBasis <chr>, PlotGroup <chr>, Value <dbl>
recovery_review <- assess_mfrm_recovery(
recovery,
min_reps = 2,
min_se_available = NULL,
max_mcse_rmse_ratio = NULL,
max_rmse = c(facet = 1, step = 1, default = 1.5),
max_abs_bias = c(default = 0.75)
)
summary(recovery_review)$checklist[, c("Section", "Item", "Status")]
#> # A tibble: 11 × 3
#> Section Item Status
#> <chr> <chr> <chr>
#> 1 Run completion Replication count ok
#> 2 Run completion Simulation and refit success ok
#> 3 Run completion Reported convergence concern
#> 4 Recovery content Recoverable truth-estimate rows ok
#> 5 Generator conditions Bounded-GPCM slope regime not_assessed
#> 6 Generator conditions Generated score-category support ok
#> 7 Uncertainty Standard-error availability not_assessed
#> 8 Uncertainty Coverage review
#> 9 Monte Carlo precision RMSE Monte Carlo error not_assessed
#> 10 Practical thresholds RMSE threshold ok
#> 11 Practical thresholds Bias threshold ok
summary(recovery_review)$reading_order
#> # A tibble: 6 × 4
#> Step Route WhatToRead Purpose
#> <int> <chr> <chr> <chr>
#> 1 1 "summary(recovery_review)" Overall r… Decide…
#> 2 2 "recovery_review$condition_reporting_notes, then rec… Generator… Separa…
#> 3 3 "recovery_review$diagnostic_reporting_notes, then re… Reporter-… Check …
#> 4 4 "plot(recovery_review, type = \"status\")" Checklist… Find t…
#> 5 5 "plot(recovery_review, type = \"metrics\")" Parameter… Identi…
#> 6 6 "recovery_review$source$recovery" Row-level… Diagno…
summary(recovery_review)$condition_reporting_notes
#> # A tibble: 2 × 10
#> Model GPCMSlopeRegime StressLevel ConditionArea ReportingAttention
#> <chr> <chr> <chr> <chr> <chr>
#> 1 RSM NA not_applicable slope_regime context
#> 2 RSM NA not_applicable score_support context
#> # ℹ 5 more variables: ConditionFinding <chr>, Evidence <chr>,
#> # ReportingImplication <chr>, NextAction <chr>, ValidationUse <chr>
summary(recovery_review)$condition_review
#> # A tibble: 1 × 16
#> Model GPCMSlopeRegime StressLevel SlopeLevels MaxAbsCenteredLogSlope
#> <chr> <chr> <chr> <int> <dbl>
#> 1 RSM NA not_applicable NA NA
#> # ℹ 11 more variables: Replications <int>, ScoreSupportReplications <int>,
#> # MinScoreCount <int>, MinScoreProportion <dbl>, MaxZeroScoreLevels <int>,
#> # ScoreSupportStatus <chr>, Status <chr>, Interpretation <chr>,
#> # ScoreSupportInterpretation <chr>, ScoreSupportNextAction <chr>,
#> # NextAction <chr>
summary(recovery_review)$diagnostic_reporting_notes
#> # A tibble: 4 × 7
#> Facet ReportingAttention DiagnosticFinding Evidence ReportingImplication
#> <chr> <chr> <chr> <chr> <chr>
#> 1 Criterion reporting_review zero_separation_or… replica… The Rasch/FACETS-st…
#> 2 Person reporting_review abs_zstd_flags_pre… replica… At least one replic…
#> 3 Person reporting_review df_sensitive_zstd_… replica… Fit-ZSTD flagging c…
#> 4 Rater context diagnostic_context… replica… Fit/separation diag…
#> # ℹ 2 more variables: NextAction <chr>, ValidationUse <chr>
summary(recovery_review)$diagnostic_review
#> # A tibble: 3 × 21
#> Facet Replications MeanLevels MeanSeparation MeanReliability MeanStrata
#> <chr> <int> <dbl> <dbl> <dbl> <dbl>
#> 1 Criterion 2 4 0 0 0.333
#> 2 Person 2 30 2.02 0.799 3.02
#> 3 Rater 2 4 1.76 0.751 2.68
#> # ℹ 15 more variables: MeanRealSeparation <dbl>, MeanRealReliability <dbl>,
#> # MeanRealStrata <dbl>, MeanInfit <dbl>, MeanOutfit <dbl>,
#> # MeanMisfitRateAbsZ2 <dbl>, MaxMisfitRateAbsZ2 <dbl>, MeanMaxAbsZSTD <dbl>,
#> # MeanDfSensitiveFlagRate <dbl>, FitDfMethods <chr>, ValidationUse <chr>,
#> # DiagnosticAvailability <chr>, Status <chr>, Interpretation <chr>,
#> # NextAction <chr>
status_plot <- plot(recovery_review, type = "status", draw = FALSE)
status_plot$data$section_status
#> Section Status Checks StatusRank AttentionOrder
#> 1 Run completion concern 1 4 1
#> 2 Uncertainty review 1 3 2
#> 3 Generator conditions not_assessed 1 2 3
#> 4 Monte Carlo precision not_assessed 1 2 4
#> 5 Uncertainty not_assessed 1 2 5
#> 6 Generator conditions ok 1 1 6
#> 7 Practical thresholds ok 2 1 7
#> 8 Recovery content ok 1 1 8
#> 9 Run completion ok 2 1 9
status_plot$data$reading_order
#> Step
#> 1 1
#> 2 2
#> 3 3
#> 4 4
#> 5 5
#> 6 6
#> Route
#> 1 summary(recovery_review)
#> 2 recovery_review$condition_reporting_notes, then recovery_review$condition_review
#> 3 recovery_review$diagnostic_reporting_notes, then recovery_review$diagnostic_review
#> 4 plot(recovery_review, type = "status")
#> 5 plot(recovery_review, type = "metrics")
#> 6 recovery_review$source$recovery
#> WhatToRead
#> 1 Overall run status, next actions, and compact checklist.
#> 2 Generator-condition caveats, then GPCM slope-regime and generated score-support metadata.
#> 3 Reporter-facing fit/separation caveats, then optional operating characteristics retained by include_diagnostics = TRUE.
#> 4 Checklist domains ordered by attention status.
#> 5 Parameter groups behind RMSE, bias, coverage, SE, or Monte Carlo statuses.
#> 6 Row-level truth-estimate comparisons for the parameter groups that need follow-up.
#> Purpose
#> 1 Decide whether the assessment is ready to inspect.
#> 2 Separate generator stress conditions and sparse score support from parameter-recovery performance before interpreting metrics.
#> 3 Check diagnostic behavior without treating fit or separation as parameter-recovery criteria.
#> 4 Find the part of the assessment that needs attention first.
#> 5 Identify the specific parameter group and metric driving the status.
#> 6 Diagnose the underlying recovery pattern before changing design or fit settings.
metric_plot <- plot(recovery_review, type = "metrics", metric = "rmse", draw = FALSE)
metric_plot$data$plot_table
#> ParameterType Facet ComparisonScale PlotGroup Metric
#> 1 person Person logit person / Person / logit rmse
#> 2 step Common logit step / Common / logit rmse
#> 3 facet Rater logit facet / Rater / logit rmse
#> 4 facet Criterion logit facet / Criterion / logit rmse
#> Value Limit Status OverallStatus StatusRank AttentionOrder
#> 1 0.4802121 1 ok review 1 1
#> 2 0.2002376 1 ok review 1 2
#> 3 0.1608166 1 ok review 1 3
#> 4 0.1509518 1 ok review 1 4
metric_plot$data$guidance
#> [1] "This metric plot is sorted by status priority, then by RMSE."
#> [2] "Inspect concern/review rows before ok rows."
#> [3] "Use the row-level recovery table only after identifying the parameter group that needs follow-up."
recovery_bundle <- build_summary_table_bundle(
recovery_review,
appendix_preset = "recommended"
)
recovery_bundle$table_index[, c("Table", "Rows", "Role")]
#> Table Rows Role
#> 1 overview 1 recovery_assessment_overview
#> 2 reading_order 6 recovery_assessment_reading_order
#> 3 checklist 11 recovery_assessment_checklist
#> 4 condition_reporting_notes 2 recovery_condition_reporting_notes
#> 5 condition_review 1 recovery_condition_review
#> 6 diagnostic_reporting_notes 4 recovery_diagnostic_reporting_notes
#> 7 diagnostic_review 3 recovery_diagnostic_review
#> 8 metric_review 4 recovery_metric_review
#> 9 uncertainty_review 4 recovery_uncertainty_review
#> 10 next_actions 6 repair_recommendations
#> 11 thresholds 9 review_settings
pred_pop <- predict_mfrm_population(
sim_spec = sim_spec,
reps = 2,
maxit = 30,
seed = 1
)
#> Warning: Unknown or uninitialised column: `ConvergenceRate`.
#> Warning: Unknown or uninitialised column: `MeanMinCategoryCount`.
#> Warning: Unknown or uninitialised column: `MeanSeparation`.
summary(pred_pop)$forecast[, c("Facet", "MeanSeparation", "McseSeparation")]
#> # A tibble: 3 × 3
#> Facet MeanSeparation McseSeparation
#> <chr> <dbl> <dbl>
#> 1 Criterion 1.87 0.085
#> 2 Person 2.04 0.04
#> 3 Rater 0.759 0.759
keep_people <- unique(toy$Person)[1:18]
toy_mml <- suppressWarnings(
fit_mfrm(
toy[toy$Person %in% keep_people, , drop = FALSE],
person = "Person",
facets = c("Rater", "Criterion"),
score = "Score",
method = "MML",
quad_points = 5,
maxit = 30
)
)
new_units <- data.frame(
Person = c("NEW01", "NEW01"),
Rater = unique(toy$Rater)[1],
Criterion = unique(toy$Criterion)[1:2],
Score = c(2, 3)
)
pred_units <- predict_mfrm_units(toy_mml, new_units, n_draws = 0)
pv_units <- sample_mfrm_plausible_values(toy_mml, new_units, n_draws = 2, seed = 1)
summary(pred_units)$estimates[, c("Person", "Estimate", "Lower", "Upper")]
#> # A tibble: 1 × 4
#> Person Estimate Lower Upper
#> <chr> <dbl> <dbl> <dbl>
#> 1 NEW01 -0.178 -1.36 1.36
summary(pv_units)$draw_summary[, c("Person", "Draws", "MeanValue")]
#> # A tibble: 1 × 3
#> Person Draws MeanValue
#> <chr> <dbl> <dbl>
#> 1 NEW01 2 0For a report or appendix handoff, pass the recovery objects through the same summary-table export route used by the rest of the package:
export_summary_appendix(
list(recovery = recovery, recovery_review = recovery_review),
output_dir = tempdir(),
prefix = "mfrmr_recovery_appendix",
preset = "recommended",
include_html = FALSE,
overwrite = TRUE
)For an initial exploratory run, reps = 2 or another very
small value is useful only to check the data-generating setup and refit
path. For a study report, increase reps, keep the
ADEMP-style metadata in the exported tables, and set substantive
RMSE/Bias thresholds so that assess_mfrm_recovery() can
mark those rows as ok, review, or
concern rather than not_assessed.
