
mfrmr fits unidimensional many-facet ordered-response
models in R. It supports rating-scale (RSM) and
partial-credit (PCM) models, together with a documented
bounded GPCM extension. A facet can represent a rater,
item, task, criterion, form, occasion, or another observed role that
affects an ordered score.
The package extends Rasch-family RSM/PCM work with MML, modern
diagnostics, reproducibility, network review, and reporting support. It
is not a general FACETS replacement: each fit_mfrm() call
uses one response-model family and one observed score scale, and the
current public API does not provide mixed response families, multiple
independent rating scales, general threshold anchoring, or
fixed-calibration operational scoring.
The recommended workflow is:
long-format data + describe_mfrm_data()
-> fit_mfrm(method = "MML")
-> summary(profile = "facets")
-> native Wright map with uncertainty
-> focused diagnostics
-> report and export
The native Wright map is the required first fitted-scale figure in this workflow. A separate FACETS-style renderer is available when a familiar asterisk ruler and labelled category transitions are useful.
Package website: https://ryuya-dot-com.github.io/mfrmr/
Source code: https://github.com/Ryuya-dot-com/mfrmr
Questions and bug reports: https://github.com/Ryuya-dot-com/mfrmr/issues
Install the CRAN package with:
install.packages("mfrmr")Install the GitHub version with:
if (!requireNamespace("remotes", quietly = TRUE)) {
install.packages("remotes")
}
remotes::install_github(
"Ryuya-dot-com/mfrmr",
build_vignettes = TRUE
)Load the package and list its installed guides:
library(mfrmr)
browseVignettes("mfrmr")fit_mfrm() expects one row per observed rating event. At
minimum, the data need:
For example:
head(data.frame(
Person = c("P01", "P01", "P02", "P02"),
Rater = c("R1", "R2", "R1", "R2"),
Criterion = c("Content", "Content", "Content", "Content"),
Score = c(3, 2, 2, 3)
))Facet identifiers may be character or factor columns. Scores must
represent ordered categories. Use NA for missing responses,
or document and recode special missing-value codes before fitting. The
design need not be fully crossed, but it must contain enough links among
persons and facet levels to support the intended comparisons.
Each row should represent a distinguishable rating event. Exact duplicate Person-by-facet combinations are retained but trigger a warning and a Data review state because the package does not model within-cell dependence. For a legitimate re-rating or replicated scoring study, include an event or occasion facet that distinguishes the observations before fitting.
For conventional score sentinels such as 99,
-1, N, or ., set
missing_codes = TRUE. This convenience policy recodes the
score column only; it preserves person and facet identifiers because
short labels such as N can be legitimate IDs. Supplying an
explicit character vector instead applies that user-declared code set
across the selected model columns, so inspect the returned
missing_recoding record before fitting or reporting
exclusions.
The main workflow below uses a compact synthetic dataset with a
connected two-rater assignment, moderately unequal rater workloads, and
six planned criterion-level omissions represented by absent long-format
rows, not NA or sentinel scores. Its groups contain 24
persons each; three omissions per group leave 141 observed rows in Group
A and 141 in Group B:
library(mfrmr)
dat <- load_mfrmr_data("example_operational")
data("mfrmr_example_operational_design", package = "mfrmr")
head(dat)
table(dat$Score)
list_mfrmr_data(details = TRUE)[, c("Key", "PrimaryUse", "Design", "CountBasis")]All bundled examples are synthetic. example_operational
is the applied teaching dataset, not an empirical reference dataset. Use
example_core only when an idealized complete crossing is
useful for a fast example, and use example_bias for
demonstrations with deliberately planted DFF/bias effects.
Before fitting, state the complete rubric support and inspect retained, missing, and zero-count categories:
data_review <- describe_mfrm_data(
data = dat,
person = "Person",
facets = c("Rater", "Criterion"),
score = "Score",
rating_min = 1,
rating_max = 4,
expected_design = mfrmr_example_operational_design
)
data_summary <- summary(data_review)
data_summary$structural_missingness
data_summary$design_connectivityThe assignment roster contains no scores. It tells
describe_mfrm_data() which Person x Rater x Criterion cells
were planned. The review therefore reports the six
expected-but-unobserved cells separately from ordinary column
NA counts, while confirming that both observed Person-facet
graphs remain connected. Without expected_design,
structural missingness is reported as not assessed because an absent row
may simply mean that the cell was never assigned.
rating_min and rating_max retain unobserved
boundary categories. If an intended intermediate category is also
unobserved, use keep_original = TRUE in both
describe_mfrm_data() and fit_mfrm(). Otherwise
non-consecutive observed scores such as 1, 2, 4, 5 are
mapped to a contiguous internal scale; always review the reported score
map before interpreting steps.
For a new analysis, start with marginal maximum likelihood:
fit <- fit_mfrm(
data = dat,
person = "Person",
facets = c("Rater", "Criterion"),
score = "Score",
rating_min = 1,
rating_max = 4,
method = "MML",
model = "RSM"
)MML integrates over the person distribution and returns
posterior person summaries. The default uses 31 quadrature points.
Record that setting and examine quadrature sensitivity when the
application requires it.
Use model = "PCM", step_facet = "Criterion" when
category steps differ across that facet. Choose the model from the
scoring design and measurement rationale, not from a single fit
statistic.
The default summary is deliberately lightweight:
fit_summary <- summary(
fit,
profile = "fit",
detail = "brief"
)
fit_summary$overview
fit_summary$status
fit_summary$readiness
fit_summary$data_review
fit_summary$settings_overview
fit_summary$facet_overview
fit_summary$person_overview
fit_summary$step_overviewStart with convergence and estimation settings. When optimizer code
zero is reached before the common terminal-gradient check passes,
fit_mfrm() makes a bounded sequence of warm-started
polishing attempts when the requested setting is at least as strict as
the public default (reltol <= 1e-9). It retains the best
non-worsening stage under the recorded selection rule. The requested and
selected-stage settings, every attempted stage, terminal gradients,
parameter changes, and evaluation counts remain in
fit$opt$optimizer_polish. For L-BFGS-B, the native
factr and pgtol controls are also recorded; do
not interpret EffectiveReltol as a native L-BFGS-B
argument.
Treat maxit as a computational ceiling, not a
convergence criterion or a control to tune until preferred estimates
appear. The package default is maxit = 400; smaller values
in executable examples exist only to shorten checks. Prespecify the
estimator and controls before inspecting results. If a fit ends with
ConvergenceStatus = "iteration_limit", do not interpret or
compare its estimates. Refit the same data, model, method, anchors,
optimizer, tolerance, and quadrature rule using the next ceiling in a
prespecified sequence. Use a result only after
Converged = TRUE, InferenceReady = TRUE, and
Numerical = pass; do not select among runs by coefficient
size, fit statistics, significance, or agreement with an expected
answer. Material differences between separately ready runs indicate
numerical instability that requires review.
InferenceReady is deliberately a numerical status, not a
publication decision. Require Numerical = pass, then
inspect the separate Data, Design, and Stability rows in
fit_summary$readiness. A disconnected design or a
boundary-constant facet level remains a reporting hold even when the
optimizer gradient is small. In an otherwise supported fit, a Reporting
status such as
ready_for_diagnostics_and_reporting_follow_up means that
fitting succeeded and the next diagnostic stage is pending; it does not
mean that optimization failed or that the result is already
manuscript-ready. Plots can still be generated for diagnosis, but their
returned data carry interpretation_status = "review_only",
their subtitle is marked REVIEW ONLY, the drawn title
carries the same banner, and the plotting call warns before substantive
or cross-subset interpretation. The remaining tables describe the fitted
scale; they do not create universal acceptance thresholds.
Use the facets profile for the main review:
facets_summary <- summary(
fit,
profile = "facets",
detail = "brief"
)
facets_summary
facets_summary$status
facets_summary$section_status
facets_summary$required_visual
res <- facets_summary$results
res$readiness
res$plot_map[, c(
"Type", "Available", "InterpretationStatus", "InterpretationReady"
)]This profile organizes model information, measures, uncertainty, fit
evidence, precision, category/step information, and plot routes in a
reading order that will be familiar to FACETS users. It computes the
documented diagnostics when they are needed and returns the resulting
mfrm_results object in
facets_summary$results.
The profile name describes organization, not software execution:
mfrmr estimates;Pass a matching diagnose_mfrm() object through
diagnostics = diag to reuse work already completed. Use
compute = "never" for a no-computation review; dependent
sections are then marked as not computed.
Person identifiers are omitted from the brief console view. Request detailed person output only when the analysis purpose and data-handling plan require it.
The native renderer is the primary targeting figure because it keeps uncertainty visible:
plot(
res,
type = "wright",
renderer = "native",
show_ci = TRUE,
preset = "publication"
)Read the vertical axis as the shared logit scale:
Person and non-person uncertainty can have different statistical bases. For example, an MML person interval is based on posterior uncertainty, whereas a non-person interval may use observed-information uncertainty. Keep the reported SE method with any table or figure interpretation.
The package defaults to a negative orientation for ordinary
non-person facets: higher measures indicate greater severity or
difficulty. Facets named in positive_facets have the
reverse scoring direction. State the active orientation in the figure
caption.
Set draw = FALSE to obtain the fitted coordinates for a
custom ggplot2, Quarto, or accessibility-aware figure.
top_n = Inf retains every fitted coordinate. The native
text layer remains collision-aware, so a dense map may leave some
retained points unlabeled; the returned plot data and retention table
remain the complete record.
When the fit review diagnoses boundary-separated facet levels and no
explicit wright_range is supplied, the native and
FACETS-style renderers use the same robust central range. Boundary
levels appear at the appropriate ruler end with triangles, while
OriginalEstimate, CI_Lower, and
CI_Upper retain the untruncated values.
DisplayEstimate, DisplayCI_Lower,
DisplayCI_Upper, and the CIClipped* /
CISuppressed fields describe only what was drawn. This
prevents a huge separation interval from compressing the interpretable
center or being mistaken for a complete visible interval.
Use study-specific rubric labels rather than anonymous category numbers:
rubric_labels <- c(
"1" = "Beginning",
"2" = "Developing",
"3" = "Proficient",
"4" = "Advanced"
)
plot(
res,
type = "wright",
renderer = "facets",
category_labels = rubric_labels,
rows_per_logit = 2,
show_ci = FALSE,
preset = "publication"
)The FACETS-style renderer provides:
*;1 Beginning -> 2 Developing;* = n person(s) legend.Set persons_per_star when the same star density must be
used across several figures. Otherwise, the renderer chooses a compact
value and prints it below the map.
The step display is designed to make the rating scale readable:
For RSM, the step pattern is shared by the relevant
observations. For PCM, read the step column within its
curve or step-facet group. Disordered or closely spaced steps are
prompts to inspect category use, sample support, and the scoring design;
they are not automatic instructions to collapse categories.
Use the actual rubric wording from the instrument. The names of
category_labels must match the original scores. A
two-column data frame with columns Score and
Label is also accepted.
With draw = FALSE, the returned
facets_style component exposes
category_labels, step_ruler,
score_transitions, and settings tables. For
line-printer reconstruction, RulerValue records the nearest
discrete ruler row; DrawValue records the exact coordinate
used for step and midpoint lines in the current renderer.
For a closer FACETS visual comparison, leave
show_ci = FALSE. Setting show_ci = TRUE adds
mfrmr uncertainty whiskers to the asterisk ruler. That
hybrid is useful analytically, but it is intentionally an extension of
the FACETS-style layout; its footer identifies the interval level and
the dot at each whisker marks the corresponding fitted facet
location.
Boundary-separated levels otherwise make an estimate-derived ruler
very wide. The automatic boundary-aware range described above is used by
default; set an explicit, reported range such as
wright_range = c(-4, 4) when the application requires a
prespecified display scale. Out-of-range levels remain visible in
parentheses at the ruler ends. With show_ci = TRUE,
endpoint triangles and the footer identify intervals that extend beyond
or are omitted from the displayed ruler; exact values remain in the
returned data.
The FACETS renderer reproduces the main reading grammar of a FACETS Table 6-style variable map. It does not reproduce a FACETS run.
| Question | What mfrmr provides |
|---|---|
| Familiar visual layout | Asterisk person counts, shared ruler, signed facet columns, step lines, and rubric-labelled transitions |
| Estimate source | The fitted mfrmr object |
| Pixel-identical output | Not guaranteed across graphics devices, fonts, or FACETS versions |
| Numerical equivalence | Not established by selecting renderer = "facets" |
| External comparison | Available after the user supplies output from a separately run FACETS analysis |
A defensible numerical comparison requires the same response records,
score coding, model, estimator, identification constraints, anchors,
facet orientation, extreme-score handling, and convergence criteria.
FACETS commonly uses JMLE, whereas the starter mfrmr
workflow above uses MML with EAP person summaries when its
population-model assumptions are suitable. Those choices target related
but different calculations.
When a JMLE-oriented comparison is required, refit with
method = "JML" and still document every remaining setting.
Matching the estimator family alone does not establish equivalence.
For an exported FACETS fit table, use
read_facets_fit_table() followed by
facets_fit_review(fit, diagnostics = res$diagnostics, facets_fit = ...).
This reads supplied output; it does not launch or automate FACETS.
The fit pathway uses Infit on the horizontal axis and measure on the vertical axis:
plot(
res,
type = "fit_pathway",
fit_stat = "Infit",
fit_scale = "mnsq",
include_person = TRUE,
top_n_person = 12,
person_labels = "none",
facet_labels = "flagged",
show_ci = TRUE,
preset = "publication"
)The vertical axis remains the fitted logit measure. The horizontal reference at Infit MnSq = 1 represents model expectation; the outer lines are review guides. Selected persons use a different point shape from non-person facet levels. Completing this follow-up can change the Reporting readiness row while leaving the already-passed Numerical row unchanged.
top_n_person limits the displayed person layer so a
large study remains readable. The selected persons are those with the
largest fit distances; non-person rows remain available. Use
person_subset for a prespecified case list, or
top_n_person = Inf only when displaying every person serves
a clear purpose.
Fit statistics are evidence for review, not automatic exclusion rules. Investigate response patterns, design cells, score support, and practical consequences before changing data or operational decisions.
The separate type = "pathway" route displays expected
scores and dominant-category regions across theta;
type = "fit_pathway" displays Infit or Outfit against the
fitted measure.
Start with the brief result summary:
results_summary <- summary(res, view = "brief")
results_summary$overview
results_summary$triage
results_summary$next_actions
results_summary$plot_mapBuild a report-oriented object from the same fitted results:
report <- mfrm_report(
res,
style = "qc"
)
summary(report, view = "reader")
report$first_screen
report$report_indexmfrm_report() organizes existing evidence. It does not
refit the model or turn diagnostic thresholds into a validity
decision.
Export a reader-oriented, controlled analysis archive:
exported <- export_mfrm_results(
res,
output_dir = "mfrmr-results",
prefix = "analysis01",
preset = "starter"
)
exported$summary
exported$written_filesThe starter preset includes the result summary, report
tables, replay code, manifest, native Wright map, and focused plot
routes. Existing files are not overwritten unless
overwrite = TRUE is requested explicitly.
This preset is a controlled analysis archive, not a
deidentified or automatically shareable deliverable. It includes a
complete .rds result object, and its tables, HTML, plots,
replay code, and manifest can retain direct person identifiers,
person-level estimates, original labels, or local paths. Review and
transform every file under the study’s data-handling policy before
sharing it. After making that assessment,
acknowledge_sensitive = TRUE can suppress the export
warning; it does not remove or pseudonymize any data.
For most studies, review the output in this order:
Do not reduce this sequence to a single pass/fail index. Fit, precision, targeting, category function, fairness, and validity answer different questions.
The closest translation of common FACETS concepts is:
| FACETS concept | mfrmr route |
|---|---|
| Data/specification roles | Explicit person, facets, and
score arguments |
| JMLE-oriented fit | fit_mfrm(method = "JML") |
| MML analysis | fit_mfrm(method = "MML") |
| Measures and SEs | summary(fit, profile = "facets") and its result
tables |
| Variable/Wright map | Native renderer = "native" or optional
renderer = "facets" |
| Infit/Outfit review | diagnose_mfrm() and
type = "fit_pathway" |
| Fair average | fair_average_table() |
| Bias/interaction screen | estimate_bias() and related review functions |
| Anchors | anchors and group_anchors in
fit_mfrm() |
| External fit comparison | read_facets_fit_table() then
facets_fit_review() |
For an existing FACETS-oriented script,
run_mfrm_facets() provides a one-call wrapper around
package fitting and diagnostics. For new work, the explicit
fit_mfrm() workflow is easier to review and is
recommended.
Use these guides for the full migration details:
The installed references/FACETS_manual_mapping.md maps
concepts and output routes. It is not evidence that FACETS was
executed.
mfrmr can prepare and review a narrow external-table
comparison for an MML latent-regression case. The supported overlap
is:
RSM or PCM;Within that scope, comparison targets include the population regression slope, residual variance, centered item estimates, and case-level EAP estimates.
The handoff is:
# fit_lr must satisfy the documented overlap conditions.
bundle <- build_conquest_overlap_bundle(
fit = fit_lr,
output_dir = "conquest-overlap"
)
# Run the generated .cqc file in ConQuest separately, then normalize
# the four CSV files requested by that command.
conquest_tables <- normalize_conquest_overlap_exports(
bundle,
parameter_file = "conquest-overlap/conquest_overlap_conquest_parameters.csv",
regression_file = "conquest-overlap/conquest_overlap_conquest_reg_coefficients.csv",
covariance_file = "conquest-overlap/conquest_overlap_conquest_covariance.csv",
case_file = "conquest-overlap/conquest_overlap_conquest_cases_eap.csv",
conquest_version = "5.47.5",
conquest_edition = "demo/free",
run_date = "2026-07-23"
)
conquest_review <- review_conquest_overlap(
bundle,
conquest_tables
)
summary(conquest_review)
conquest_review$attention_itemsmfrmr does not execute or control ConQuest, and it does
not parse arbitrary raw ConQuest reports. The user runs ConQuest
separately; mfrmr can normalize the four native CSV exports requested by
its generated command. The comparison reports differences and does not
declare software equivalence from a fixed tolerance.
The generated ConQuest command uses the fitted mfrmr quadrature-point count; the bundle records both values. Record the actual ConQuest version, edition, and run date during normalization so the external comparison remains reproducible.
A public aggregate comparison record of a matched 31-node check with ConQuest 5.47.5 is available in the source repository. The record is excluded from the installed CRAN package and supports only the overlap case stated above; identifier-bearing response and case-level files are not included in the package.
This route does not cover multidimensional models, arbitrary imported
design matrices, bounded GPCM latent regression, JML latent
regression, or the full ConQuest plausible-values workflow.
The ConQuest overlap bundle is also a controlled analysis bundle. Its
long and wide response files contain person identifiers and responses;
the person-data file contains identifiers and the covariate; and both
mfrmr and ConQuest case-EAP files contain identifiers and person-level
estimates. The helper warns when writing these files and creates
*_privacy_notice.csv. Store the bundle in an approved
restricted location and pseudonymize or redact it as required before
sharing.
| Area | Supported route | Important boundary |
|---|---|---|
| Latent structure | One latent dimension | No multidimensional or Q-matrix engine |
| Facets | Multiple observed facet roles | The design must remain connected for the intended contrasts |
RSM |
Shared step structure | The common rating-scale assumption must be substantively defensible |
PCM |
Step structure associated with step_facet |
Specify the step facet explicitly when the default is not intended |
Bounded GPCM |
Documented slope-aware core with
slope_facet == step_facet |
Not an unrestricted many-facet GPCM implementation |
| Estimation | MML and JML/JMLE |
Estimator choice changes person summaries and residual-fit basis |
| Latent regression | Conditional-normal, unidimensional MML population model | Not arbitrary ConQuest design-matrix or multidimensional population modeling |
| Diagnostics | Residual/EAP and strict marginal screening routes | A flag is not a deletion, fairness, or validity decision |
| FACETS and ConQuest | Exported-table review within documented overlap | Neither external program is executed by mfrmr |
For bounded GPCM, inspect the capability table before
choosing a downstream helper:
gpcm_capability_matrix()
vignette("mfrmr-gpcm-scope", package = "mfrmr")For strict MML diagnostics, keep the two evidence bases distinct:
diag_both <- diagnose_mfrm(
fit,
residual_pca = "none",
diagnostic_mode = "both"
)
summary(diag_both)$diagnostic_basisThe legacy route evaluates residual-oriented summaries using person estimates. The strict marginal route uses latent-integrated expected values. They are complementary screens and should not be collapsed into one decision rule.
The package includes the following vignettes:
Open a guide with
vignette("mfrmr-workflow", package = "mfrmr") or list them
with browseVignettes("mfrmr"). A printable API overview is
installed at cheatsheet/mfrmr-cheatsheet.pdf.
Function-level help starts with ?fit_mfrm,
?summary.mfrm_fit, and ?plot.mfrm_fit.
Use the package citation supplied with the installed version:
citation("mfrmr")The underlying ordered-response models are described by Andrich (1978), Masters (1982), and Muraki (1992). See the package help and vignettes for the references relevant to each model and diagnostic route.
mfrmr is licensed under the MIT License.
mfrmr has benefited from discussion and methodological
input from Dr. Atsushi Mizumoto and
Dr. Taichi
Yamashita.