This is the first CRAN-facing release after 0.1.5. Version 0.1.6 was used as an unpublished development line, so most users will experience 0.2.0 as a direct upgrade from CRAN 0.1.5 to CRAN 0.2.0. Read this section as the public change log relative to 0.1.5; the later "mfrmr 0.1.6 (development-only)" section is retained only as detailed development history.
0.2.0 is a substantial analysis and reporting release. It expands the bounded-GPCM route, adds shrinkage and design-audit workflows, improves APA/reporting handoff, adds residual dimensionality and classical DIF screening helpers, broadens the visualization surface, and corrects several statistical documentation and output contracts.
Review these points before rerunning an existing 0.1.5 analysis script:
diagnose_mfrm() now defaults to
diagnostic_mode = "both" rather than "legacy"; default MML
quadrature uses quad_points = 31 rather than 15; and plot(fit)
now returns the Wright map alone. Use
diagnostic_mode = "legacy", quad_points = 15, and
plot(fit, type = "bundle") to recover the old first-pass workflow.compute_person_fit_indices() now computes
lz from the model category probability of the observed category.
Development snapshots briefly exposed an ECI4 column; it is not
part of 0.2.0 because it duplicated the standardized chi-square /
Outfit-ZSTD approximation rather than Tatsuoka and Tatsuoka's
extended-caution index. lz_star is now a JML-only Snijders-style
score-projection statistic; the finite-N heuristic is explicitly
named lz_finite_n. MML/EAP fits return lz_star = NA with
lz_star_method = "unavailable_for_eap_mml".print(build_apa_outputs(...)) now
prints manuscript-style Method / Results prose by default. Use
summary(apa) or print(apa, qa = TRUE) for the QA/completeness
view. as_kable() and as_flextable() adapters are available for
table export.fit_mfrm() can recode common
missing-code sentinels through missing_codes, while still preserving
the default NULL behavior for scripts that already clean data
upstream. Ordered-score handling, score maps, binary ordered data,
and retained zero-count categories have more explicit messages and
documentation.facets_parity_report() and
facets_output_file_bundle(include = "score")) remain blocked for
GPCM.analyze_dif_classical()
adds a limited classical screen using generalized CMH and explicit
thresholded binary logistic DIF; it is not SIBTEST and does not claim
ETS classifications.as.data.frame() methods, and plot controls for
user-selected planning metrics.mirt, TAM, and
eRm expose external fit objects through the same measurement-side
mfrm_fit interface. These are plotting/table bridges, not full
replay, bias, DIF, or anchor-bundle imports.For package maintainers and users who installed GitHub snapshots during development: the 0.1.6 section below records what happened during that unpublished development line, but 0.2.0 is the public release boundary.
R/api-shrinkage.R references corrected from
Rasch Measurement Transactions, 12(2), 638 to 632-633
(page 638 in the same RMT issue is a different paper; verified at
https://www.rasch.org/rmt/rmt122.htm).reporting_checklist(): the bare
"Linacre (1989, 2004)" tag in R/api-reporting-checklist.R is now
Linacre (1989, 2002). The 2002 paper is "Optimizing rating scale
category effectiveness," JAM, 3(1), 85-106 -- the canonical Linacre
reference for rating-scale guidance. (No bibliographic entry existed
for "Linacre (2004)".)(cf. Eckes, 2011; ...)
caveat in dif_report() now has a complete @references entry
pointing to Introduction to Many-Facet Rasch Measurement (1st ed.,
Peter Lang). McNamara & Knoch (2012) is also fully cited.?mfrmr-package previously attributed
the context-specific bands (high-stakes / clinical / survey) to
Linacre (2002). The actual source is Wright & Linacre (1994),
RMT 8(3), 370. The band assignments were also swapped: high-stakes
MCQ is 0.8-1.2 (not 0.6-1.4), survey is 0.6-1.4, clinical
observation is 0.5-1.7. Corrected.q3_statistic()): previously stated mfrmr's Q3 uses
standardized residuals as if matching Yen (1984). Yen's eq. 7 (p. 127)
uses raw residuals; mfrmr's standardized-residual choice is now
documented as a deliberate departure. The |Q3| > 0.20 cutoff was
attributed to Yen but is from Chen & Thissen (1997), JEBS,
22(3), 265-289. Re-attributed.q3_statistic(): the central
finding of Christensen et al. is that no single critical value is
appropriate across designs and that a parametric bootstrap should
be used. Documentation now states this clearly; the fixed
relative_offset = 0.20 is described as a screening default rather
than as a re-implementation of Q3_*.?apply_empirical_bayes_shrinkage was dimensionally wrong:
previously written 2 B^2 (tau^2 + SE^2)^2 / (K - 3), which is
SE^4-units. The actual Morris (1983, eq. 4.1-4.2, p. 51) correction
is (2 / (K - r - 2)) * B^2 * delta^2. Corrected, with re-derived
magnitude examples (SE understated by ~73% at K=3, ~29% at K=5,
~7% at K=15).compute_facet_icc()
previously placed ICC = 0.9 in Excellent (>= 0.9). Koo & Li
(2016, p. 161) write "values greater than 0.90 indicate excellent
reliability" -- strict >. Code at R/api-hierarchical-audit.R
now uses > 0.9 for Excellent; ICC = 0.9 reads as Good.print(build_apa_outputs(...)) now prints the
concise Method / Results draft by default. Use summary(apa) or
print(apa, qa = TRUE) for the structured QA view; legacy
print(apa, top_n = ..., preview_chars = ...) calls route to that QA view
with a warning. The README, vignette, and help pages now separate manuscript
prose (apa$report_text / apa) from completeness checks (summary(apa)).visual_reporting_template() now includes
ReadFirst, NextLook, ReportDecision, and GPCMBoundary columns.
reporting_checklist()$visual_scope, build_visual_summaries(), the
README, and the reporting/visual vignettes now point users from first-pass
dashboard plots to the appropriate component helper and reporting caveat.
Bounded GPCM visual, fair-average, bias, APA, and QC routes now carry a
consistent support_status / caveat contract.?mfrmr-package now notes
that the default mml_engine = "direct" optimises the marginal
log-likelihood by gradient methods (BFGS / L-BFGS-B), not by Bock &
Aitkin's signature EM. The "em" and "hybrid" engines follow the
EM template but with a BFGS M-step (rather than B&A's probit IRLS),
because the target is the polytomous Rasch family rather than 2PL.mfrm_core.R and reporting.R
now describe the bands as "adapted from Linacre (1994)" rather than
"follow Linacre (1994)". Only the 30-examinee floor is Linacre's;
the < 10 sparse and < 50 standard watermarks are mfrmr-specific
screening choices.compute_person_fit_indices() does not
report the development finite-N placeholder under the lz_star name.
For JML fits, lz_star now uses the Snijders-style score-projection
correction, conditional on the fitted non-person parameters. The
lz / sqrt(1 + 1/N) screen is retained as lz_finite_n. For MML/EAP
fits, lz_star is deliberately NA with lz_star_method = "unavailable_for_eap_mml" because EAP posterior means do not satisfy
the ML person-score estimating equation used by Snijders' correction.|Q3| > 0.30: documented as a community convention
Marais cites, not as her own recommendation; her actual recommendation
is the relative-to-mean comparison.The subsections below give the detailed 0.2.0 release notes. They cover both the unpublished 0.1.6 development work and the final 0.2.0 changes because CRAN users are upgrading directly from 0.1.5.
Relative to CRAN 0.1.5, three user-visible defaults change:
diagnose_mfrm(diagnostic_mode = ...) defaults to "both", so
strict marginal screens are produced automatically for RSM / PCM
fits. Pass diagnostic_mode = "legacy" to recover the earlier
diagnostics-only path.fit_mfrm(quad_points = ...) defaults to 31 rather than 15, so
default MML fits are more stable for direct reporting. Pass
quad_points = 15 for the previous exploratory speed setting.plot(fit) returns the Wright map alone. The earlier three-plot
overview remains available via plot(fit, type = "bundle").For reporting consistency, plot_person_fit(), plot_bubble(), and
plot_facet_quality_dashboard() now inherit the active MnSq screening band
from mfrm_misfit_thresholds() when no manual band is supplied. Pass
explicit plot thresholds (for example lower = 0.5, upper = 1.5,
fit_range = c(0.5, 1.5), or misfit_warn = 1.5) to freeze a manual
review band.
check_residual_dimensionality() adds a parallel-analysis layer for
residual PCA. It compares observed residual eigenvalues with null
eigenvalues from independent residual matrices, column-wise residual
permutations, or fitted-model parametric simulations. The companion
plot_residual_dimensionality() returns the same mfrm_plot_data
payload style as other visual helpers, and as.data.frame() exposes
comparison, observed, and null-distribution tables for CSV export or
custom plotting. The help page explicitly distinguishes this exploratory
residual-structure diagnostic from FACETS ZSTD, TAM itemfit ZSTD, and
mirt's S-X2 statistic.
New GitHub Actions workflows added alongside the existing
pkgdown.yaml: R-CMD-check.yaml runs the matrix on Ubuntu
(release / devel / oldrel-1) plus macos-latest and windows-latest
(release), and test-coverage.yaml runs covr with artifact
upload (no external service contacted).
plot_dif_heatmap() gains display controls for cell labels
(show_values, value_digits), absolute flag thresholds
(flag_threshold, flag_color), and shared symmetric color limits
(scale_limit) so several heatmaps can be drawn on a comparable scale.
plot_dif_summary() gains optional normal-approximation confidence
intervals, effect-threshold guide lines, method-aware axis labels, and
an interpretation-guide payload that downstream code can render
alongside the figure.
print.mfrm_plot_data() is now defined, so the headline draw = FALSE
return value renders as a compact summary (name, title, payload
shapes, legend / reference-line counts) instead of a raw list dump.
analyze_dif_classical() adds a limited classical screening route for
long-format many-facet data. It supports generalized Mantel-Haenszel /
Cochran-Mantel-Haenszel screening over ordered score categories and
binary logistic DIF screening when the dichotomization is explicit
through logistic_threshold. It does not implement SIBTEST, does not
estimate subgroup MFRM parameters, and does not claim ETS A/B/C labels.
Four classic plot entry points are now exported:
plot_expected_score_curve(), plot_test_characteristic_curve(),
plot_cumulative_category_curve(), and plot_kidmap(). They reuse
the package's existing category-curve, design-weighted expectation, and
person-fit payloads while giving mirt/TAM/FACETS users familiar names.
fair_average_table() and estimate_bias() no longer hard-stop on
GPCM fits. Both helpers now use the slope-aware element-conditional
GPCM construction:
fair_average_table(): for slope-facet element rows, the
fair-average uses that element's own discrimination a_{j*} and
threshold structure: FA_{p,j*} = sum_k k * P_GPCM(X = k | theta_p, a_{j*}, delta_{j*}). For non-slope facets (Person, Rater, ...), the
fair-average uses the geometric-mean-one slope by GPCM
identification, so the construction is continuous with the PCM
Linacre fair-average and reduces to it exactly when all slopes
equal one (regression-tested at machine precision).
estimate_bias(): the per-cell bias parameter is the additive
shift on the linear predictor that maximises the per-cell GPCM
log-likelihood. The dispatch routes the inner nll and the
per-iteration category_prob calls through the GPCM kernel instead
of the PCM kernel; SE / t / Prob columns retain the screening-tier
semantics documented in ?estimate_bias.
Both helpers gain method = "GPCM-slope-aware" and a caveat
field that names the slope convention and reminds the user that the
SE columns are not delta-method standard errors of the
fair-average / bias values. A delta-method SE for both is planned for
a future release; it requires a vcov() method on the joint covariance
of (theta, a, delta), which is not yet exposed.
See ?fair_average_table, ?estimate_bias, and
gpcm_capability_matrix() for the full support contract.
build_apa_outputs(), build_mfrm_manifest(),
build_mfrm_replay_script(), and export_mfrm_bundle() now route bounded
GPCM fits through package-native outputs with explicit caveats.
facets_parity_report() and facets_output_file_bundle(include = "score")
remain blocked under GPCM in 0.2.0 because those FACETS-compatibility outputs
are Rasch-family score-side contracts.
diagnose_mfrm() now attaches the slope-aware GPCM fair-average table.
build_visual_summaries() and
run_qc_pipeline() now accept bounded GPCM fits and return
support_status = "supported_with_caveat". Their caveat states that
fair-average and bias checks are GPCM-specific exploratory screens, not
Rasch-family invariance evidence.
compute_person_fit_indices() now computes lz from the model
category probability of the observed category directly (true
Drasgow, Levine & Williams (1985) polytomous form), via three new
intermediate columns PrObserved, ItemEntropy, and ItemVarLogP
on compute_obs_table(). The previous Gaussian-residual
approximation overstated Var[log P] by roughly a factor of five
on a 4-category fixture and pulled lz toward zero.ECI4 column is not part of the 0.2.0 public
compute_person_fit_indices() contract. Its previous implementation
was the standardized chi-square
(sum StdSq - n) / sqrt(2 * n), which is the linear (Smith)
approximation to OutfitZSTD, not the Tatsuoka & Tatsuoka (1983)
extended-caution index. Users who want the equivalent statistic
should use OutfitZSTD directly. The development finite-N
lz_star placeholder is not reported as lz_star; it is explicitly
named lz_finite_n, while JML fits receive the score-projection
corrected lz_star.displacement_table()$summary now returns NA_real_ for
MaxAbsDisplacement and MaxAbsDisplacementT when every flagged
level has zero information (so every Displacement is NA).
Previously the helper called max(..., na.rm = TRUE) on an
all-NA vector, which returned -Inf and emitted a "no
non-missing arguments to max; returning -Inf" warning. The
guarded version is regression-tested in test-core-coverage-gaps.R.analyze_dff() and dif_interaction_table() now reject invalid
p_adjust, non-integer min_obs, invalid focal groups, and
all-missing group columns up front, instead of failing later inside
the contrast computation. Missing or empty group rows are dropped
with a message().?analyze_dff, ?plot_dif_summary, ?mfrmr_linking_and_dff,
and ?mfrmr_visual_diagnostics now distinguish residual-method
screening labels from refit-method ETS A/B/C classifications more
explicitly and route users to both plot_dif_heatmap() and
plot_dif_summary().
?compute_person_fit_indices now distinguishes lz, JML-only
Snijders-style lz_star, and the explicitly named lz_finite_n
heuristic. The help page states why lz_star is unavailable for
MML/EAP fits.
?mfrm_generalizability now discloses that the lme4 random-effects
model is main-effects only (Score ~ 1 + (1|Person) + (1|Facet) + ... + Residual, no explicit (1|Person:Facet) interaction terms),
which folds two-way interaction variance into Residual and can
bias G downward. The reported Phi does not apply Brennan
(2001) D-study scalings (1/n_r, 1/n_i, 1/(n_r * n_i)). Users
who need a full p x r x i decomposition with D-study scaling
should treat this output as a screening summary.
?q3_statistic now discloses that, when the chosen facet has
multiple residual rows per (Person, Level) cell because of
additional facets in the design, the standardized residuals are
mean-aggregated to one value per cell before the Pearson
correlation. Yen's (1984) original definition takes the
correlation over per-(Person, Item) residuals without aggregation,
so the published |Q3| > 0.20 threshold and the Christensen et
al. (2017) critical values were derived for the original
formulation; the values returned here should be treated as a
screening summary rather than a direct substitute for those
thresholds.
?bias_pairwise_report now discloses that the contrast SE uses
the independence approximation sqrt(SE_i^2 + SE_j^2). For
same-facet bias values that share a sum-to-zero identification
the true Cov(b_i, b_j) < 0, so the reported SE is an
over-estimate and the t-statistic / p-value are conservative
(the true significance is higher than reported). For across-facet
contrasts the covariance term is approximately zero and the
approximation is appropriate.
Two new vignettes ship in the Migration and Scope section of the
pkgdown article navigation: vignette("mfrmr-facets-migration")
walks Facets users through the equivalent mfrmr workflow and
numeric-parity checks, and vignette("mfrmr-gpcm-scope") documents
which downstream helpers the bounded GPCM route currently
supports versus restricts and what to use as a substitute when a
helper is restricted.
.Rbuildignore tightened the inst/references/ source-package boundary.
The two runtime / user-facing files in that directory --
facets_column_contract.csv (read at runtime by
facets_parity_report()) and FACETS_manual_mapping.md (the
FACETS Table to mfrmr helper mapping cited in the README) -- are
preserved.
The 0.2.0 release line adds regression coverage for the public
upgrade surface from 0.1.5: GPCM scope, empirical fit plots, adjusted
fit p-value tables, classical DIF screens, classic curve front doors,
JML lz_star, residual dimensionality, arbitrary-facet simulation,
bias/signal detection simulation, APA/reporting output, namespace
contracts, and exception datasets with missing values or sparse score
categories. The release candidate was validated with the full local
test suite, Rd parsing, R CMD build, and R CMD check.
The cpp11 MML backend (src/mml_backend.cpp, RSM and PCM only) is
opt-in via options(mfrmr.use_cpp11_backend = TRUE) for this release.
It is validated against the pure-R reference at tolerance = 1e-12
on a fixed regression fixture. The default flip to ON is planned for
a follow-up release after a cycle of community testing.
Scoped during 0.2.0 prep but not shipped in 0.2.0; carried over to a later release:
facets_parity_report() and
facets_output_file_bundle(include = "score"). (fair_average_table(),
estimate_bias(), build_apa_outputs(), build_mfrm_manifest(),
build_mfrm_replay_script(), export_mfrm_bundle(),
build_visual_summaries(), and run_qc_pipeline() are unblocked
above with caveats.)?analyze_dif_classical (the migration and bounded-GPCM-scope vignettes
ship in this release; see the Documentation section above).These are scheduled for a follow-up release.
This unpublished development line added empirical-Bayes shrinkage for small-N facets, a hierarchical-structure and sample-adequacy audit layer, integrated missing-code pre-processing, APA output adapters for Word / HTML, model-estimated two-way non-person facet interactions, confidence-interval propagation through the plot surface and the ICC reporting family, and expanded reproducibility manifests. Its public changes ship as part of 0.2.0, not as a separate CRAN release. Some development-only entries below were later corrected before 0.2.0; the top 0.2.0 section is authoritative for current behavior.
Three default values were flipped during this development line and ship publicly in 0.2.0. Scripts that explicitly pass the old value are unaffected; scripts that rely on the 0.1.5 defaults should be reviewed.
diagnose_mfrm(diagnostic_mode = ...) default flips from "legacy"
to "both". Strict marginal screens are produced automatically for
RSM / PCM fits without the caller having to request them.
Pass diagnostic_mode = "legacy" to restore the earlier behaviour.plot(fit) default output is now the Wright map alone, returned as
an mfrm_plot_data object. The previous three-plot overview
(Wright + pathway + CCC) remains available via
plot(fit, type = "bundle"), which returns an mfrm_plot_bundle
with the same three slots.fit_mfrm(quad_points = ...) default increases from 15 to 31
so a default MML fit is stable enough for direct manuscript
reporting. Pass quad_points = 15 (or 7) to restore the earlier
iteration speed for exploratory scans.fit_mfrm() gains facet_interactions for confirmatory two-way interactions
between non-person facets in RSM and PCM fits, for example
facet_interactions = "Rater:Criterion". These terms are estimated
simultaneously with the main MFRM parameters as fixed effects under zero
marginal-sum constraints, contributing (A - 1) * (B - 1) free parameters for
an A x B interaction block.
New supporting pieces:
interaction_effect_table(fit) returns one row per interaction cell, with
estimates, weighted counts, sparse-cell flags, and the identification note.summary(fit) reports a compact interaction overview when interaction terms
are present.compare_mfrm(..., nested = TRUE) now recognizes same-family additive-vs-
interaction comparisons as nested when all other structural settings match
and the smaller model's interaction set is a subset of the larger model's
set.The feature is intentionally narrow in its 0.2.0 public form:
person-involving interactions, higher-order interactions, GPCM interactions,
and random-effect facet interactions are deferred. Residual bias screening via
estimate_bias() and estimate_all_bias() remains separate from these
model-estimated fixed effects.
fit_mfrm(..., facet_shrinkage = "empirical_bayes") applies
James-Stein / empirical-Bayes shrinkage to each non-person facet's
fixed-effect estimates. fit$facets$others gains ShrunkEstimate,
ShrunkSE, and ShrinkageFactor columns, and fit$shrinkage_report
summarises the per-facet prior variance, mean shrinkage, and
effective degrees of freedom.
The estimator is the classical method-of-moments form (Efron & Morris, 1973):
tau_hat^2 = max(0, mean(delta_hat_j^2) - mean(SE_j^2)), using the
raw second moment under mfrmr's sum-to-zero identification (the
facet mean is exactly 0 by construction, so no degree of freedom is
consumed).B_j = SE_j^2 / (tau_hat^2 + SE_j^2) (shrinkage factor).delta_hat_j^EB = (1 - B_j) * delta_hat_j and
SE_j^EB = sqrt((1 - B_j) * SE_j^2) (posterior mean / SE; the
posterior SE treats tau_hat^2 as known, omitting the Morris
(1983) correction for tau_hat^2 uncertainty).Two post-hoc helpers make shrinkage available to existing fits:
apply_empirical_bayes_shrinkage(fit, facet_prior_sd = NULL, shrink_person = FALSE) augments an existing mfrm_fit.shrinkage_report(fit) returns the per-facet summary table.The "laplace" alias currently routes to the empirical-Bayes path
and is reserved for a future penalised-MML implementation.
Integration: summary(fit) exposes FacetShrinkage and
FacetShrinkageTau2Mean; build_apa_outputs() adds a Method-section
sentence naming the mode, mean tau_hat^2, and mean shrinkage
with a Efron & Morris (1973) citation; build_mfrm_manifest() gains
a shrinkage_audit table; reporting_checklist() gains an
"Empirical-Bayes shrinkage" item.
Five new exported functions describe the observed design, flag small-N facet levels, and quantify ICC / design effect. Estimation remains fixed-effects MFRM; these helpers are purely descriptive and do not alter the fit.
detect_facet_nesting(data, facets, person) classifies every
ordered pair of facets (plus Person, optionally) as Fully nested,
Near-perfectly nested, Partially nested, or Crossed using the
conditional-entropy index 1 - H(B|A)/H(B).facet_small_sample_audit(fit) returns per-level
N / Estimate / SE / Infit / Outfit / SampleCategory for every
facet. SampleCategory is one of "sparse" (< 10), "marginal"
(< 30), "standard" (< 50), "strong" (>= 50). Thresholds follow
Linacre (1994) and are configurable.compute_facet_icc(data, facets, score, person) fits
lme4::lmer(Score ~ 1 + (1|Person) + (1|Facet1) + ...) and reports
the variance-component share per facet. Person uses the Koo & Li
(2016) reliability bands; other facets use a "variance share" label
(Trivial / Small / Moderate / Large).compute_facet_design_effect(data, facets, icc_table) computes the
Kish (1965) Deff = 1 + (m - 1) * rho and effective N per facet.analyze_hierarchical_structure(data, facets, ...) bundles the
four helpers above and (when igraph is available) a bipartite
connectivity summary over Person * facet-level edges.Fit- and reporting-stack integration:
fit$summary carries FacetSampleSizeFlag, FacetMinLevelN, and
FacetSparseCount.reporting_checklist() gains two items: "Facet sample-size
adequacy" (auto-ready when the flag is "standard" / "strong")
and "Hierarchical structure audit" (ready when the user passes
hierarchical_structure = analyze_hierarchical_structure(...)).build_apa_outputs() adds a Method sentence naming the
sample-adequacy band and linking to facet_small_sample_audit().build_mfrm_manifest() gains a hierarchical_audit table.recommend_mfrm_design()$caveats now points users at the three
post-fit audit functions.Optional dependencies igraph and lme4 move to Suggests; when
either is absent the relevant report is omitted with a clear
message().
fit_mfrm() now accepts missing_codes = NULL | TRUE | "default" | <character vector>, forwarded to prepare_mfrm_data(),
audit_mfrm_anchors(), and describe_mfrm_data(). When active, the
standard FACETS / SPSS / SAS sentinels ("99", "999", "-1",
"N", "NA", "n/a", ".", "" by default, or any caller-
supplied set) are converted to NA on the person, facets, and
score columns before any downstream processing. Replacement counts
are recorded in fit$prep$missing_recoding and surfaced through
build_mfrm_manifest()$missing_recoding. The default
(missing_codes = NULL) is strictly backward-compatible.
A standalone recode_missing_codes() helper is also exported for
users who prefer to recode before calling fit_mfrm().
as_kable.apa_table() converts an apa_table into a
knitr::kable() object with the caption above and the note below.
When kableExtra is installed the note becomes a proper table
footnote; otherwise it is appended as Markdown.as_flextable.apa_table() produces a flextable::flextable()
with caption and note pre-wired, suitable for officer / Word /
PowerPoint exports.as_kable() and as_flextable(), are exported so
other mfrmr classes (or third-party wrappers) can register
compatible methods.build_apa_outputs(..., context = list(output_mode = "reflow"))
now returns the Method / Results paragraphs as single long lines
per sentence-joined paragraph, which is the format Word / Quarto /
RMarkdown prefer. The default "wrapped" keeps the 92-column
layout for console readability.kableExtra and flextable join Suggests.
plot(fit, type = "shrinkage") renders a horizontal forest-style
dotplot of original and shrunk facet-level estimates, with arrows
indicating shrinkage direction, optional 95 % CI error bars
(show_ci = TRUE), and a reference line at zero. When shrinkage
is not applied the plot becomes a placeholder inviting the user to
re-fit with facet_shrinkage = "empirical_bayes".plot.mfrm_facet_sample_audit() draws a horizontal bar chart of
per-level observation counts coloured by Linacre band, with dashed
vertical lines at the thresholds.plot.mfrm_facet_nesting() renders the pairwise nesting index as
a heatmap with numeric cell labels.All three methods follow the existing
preset = c("standard", "publication", "compact") convention and
use base-R graphics.
plot_bias_interaction(show_ci = TRUE, ci_level = 0.95) draws
BiasSize +/- z * SE whiskers on the scatter and ranked views.plot_displacement(show_ci = TRUE) draws
Displacement +/- z * DisplacementSE whiskers in the lollipop
view.plot_fair_average(show_ci = TRUE) draws fair-average CI whiskers
on the observed-score scale using a delta-method propagation
SE_fair = Var(X | Measure) * ModelSE from the logit Measure
error. Rows near a rating boundary (where the implied score
variance is effectively zero) are excluded from the whiskers,
drawn as open circles, and counted in the subtitle.compute_facet_icc(ci_method = "profile" | "boot") returns ICC
confidence intervals in new ICC_CI_Lower / ICC_CI_Upper /
ICC_CI_Level / ICC_CI_Method columns, propagated through
analyze_hierarchical_structure() and drawn as whiskers on
plot.mfrm_hierarchical_structure(type = "icc"). The default
ci_method = "none" keeps the point-estimate-only behaviour.Fourteen additions across the plot surface, all base-R / additive (default behaviours unchanged):
plot_threshold_ladder() (new) — vertical ladder of
Rasch-Andrich thresholds for RSM and PCM, with disordered-step
crossings highlighted in the preset's fail colour. The returned
payload exposes per-step Group / Step / Threshold / Disordered
rows.plot(fit, type = "ccc_overlay") (new branch on
plot.mfrm_fit) — observed category proportions binned by person
measure overlaid on the model CCC curves, for an at-a-glance
model-data fit visual.plot_person_fit() (new) — FACETS Table 6 style per-person
Infit / Outfit bubble using the active MnSq screening band (default
0.5-1.5; configurable via mfrm_misfit_thresholds()).plot_bias_interaction(plot = "heatmap") (new mode) — diverging
Rater x Criterion grid coloured by bias size, with flagged cells
outlined for emphasis.plot(fit, type = "wright", group = ..., group_data = ...)
(new option) — overlays per-group person-density curves on the
Wright map's left density column, useful for DIF / DFF screening.plot_rater_severity_profile() (new) — per-rater severity
ranking with CI whiskers and optional +/-0.5 (gentle) and
+/-1.0 (strict) guidance bands for rater-training feedback.plot_anchor_drift(type = "forest") (new mode) — per-wave
anchor-element CI forest with point estimate + z * SE whiskers.plot.mfrm_equating_chain() (new S3 method) — type = "common_anchors" (default bar chart of pairwise common-anchor
counts) and type = "graph" (bipartite Wave x anchor element
graph via igraph).plot_apa_figure_one() (new) — 2x2 publication composite
bundling Wright map, rater severity profile, threshold ladder, and
a one-panel summary block.plot_dif_summary() (new) — compact effect-size summary for
[analyze_dff()] / [analyze_dif()] with ETS A / B / C colour coding.plot_guttman_scalogram() (new) — Person x facet-level
observed-category matrix, ordered by person measure and location
measure, with unexpected cells highlighted.plot_residual_qq() (new) — normal Q-Q plot of person-level
standardized residuals for distributional misfit diagnostics.plot_rater_trajectory() (new) — per-rater severity
trajectory across an ordered wave / session variable with CI
whiskers. Accepts a named list of fits.plot_rater_agreement_heatmap() (new) — symmetric rater x
rater agreement matrix colored by exact agreement (default) or the
Pearson-style Corr column from interrater_agreement_table().
Quadratic-weighted kappa is not currently computed by that helper
and is therefore not exposed as a metric option.igraph is already in Suggests; the equating-graph view falls
back to the bar chart when igraph is not installed.
Direct regression tests for these development additions:
test-attach-diagnostics.R — 18 assertions covering the
attach_diagnostics = TRUE merge, type validation, idempotence,
and MML / JML parity.test-icc-ci-method.R — 25 assertions covering
compute_facet_icc(ci_method = "profile" / "boot"), bootstrap
seed reproducibility, range validation, deprecated
icc_ci_method alias, and plot.mfrm_hierarchical_structure(type = "icc") integration.test-ci-api-consistency.R — 21 assertions covering the
lifecycle::deprecate_warn() path for conf_level, show_ci /
ci_level on plot_fair_average / plot_displacement /
plot_bias_interaction, and CI column schema.test-messaging-and-guards.R — 8 assertions covering the single
"Rating range inferred" message, analyze_dff(method = "refit")
missing(diagnostics) guard, and missing_codes integration.test-lme4-confint-helper.R — 17 assertions covering
.lme4_confint_components() across terse and verbose lme4
row-name conventions.test-plotting-extras.R + test-plotting-screening.R — 78
assertions covering all 14 new plot helpers.row_max_fast() and the three category_prob_* polytomous-response
kernels are now in R/core-category-probabilities.R instead of
inline in R/mfrm_core.R. Pure file-level reorganization; no
behaviour change. The remaining structural split of mfrm_core.R
(likelihood / optimizer / EM / gradients / prep / report tables) is
scheduled for a future release.
mfrm_misfit_thresholds() returns the lower / upper active MnSq
screening band that mfrmr screens use when flagging element-level Infit /
Outfit MnSq misfit. Defaults are c(lower = 0.5, upper = 1.5) and can
be overridden globally via R options:
options(mfrmr.misfit_lower = 0.7)options(mfrmr.misfit_upper = 1.3)Helpers that consume the band include summary(diagnose_mfrm(...))
(misfit_flagged block + key_warnings auto-flag),
build_misfit_casebook() (the new element_fit source family),
the bias / misfit narrative inside build_apa_outputs(), and
facet_quality_dashboard() when misfit_warn = NULL. Setting the
options once at the top of an analysis script therefore changes
every downstream screen at once.
Four new public helpers extend the diagnostic plot family:
plot_local_dependence_heatmap(fit) -- N x N Q3-style
pairwise residual correlation heatmap between facet levels.
Complements plot_marginal_pairwise() by showing every pair on a
shared color scale rather than a top-N bar list.plot_reliability_snapshot(fit) -- compact facet x reliability /
separation / strata bar overview built from
diagnostics$reliability. Useful as a single small figure for "are
persons / raters / criteria distinguishable?".plot_residual_matrix(fit) -- person x facet-level standardized
residual heatmap. Complements plot_guttman_scalogram() by showing
residual sign and magnitude rather than the raw response code.plot_shrinkage_funnel(fit) -- empirical-Bayes shrinkage
caterpillar / funnel for fits augmented via
apply_empirical_bayes_shrinkage().plot_bubble() gains a view = c("measure", "infit_outfit")
argument. The default "measure" keeps the historical Measure
(logit) x MnSq bubble layout; view = "infit_outfit" switches to the
Winsteps Table 30 layout (Infit MnSq on x, Outfit MnSq on y, bubble
size defaults to N). Both views return the same mfrm_plot_data
contract.
plot_dif_heatmap(draw = FALSE) now returns an mfrm_plot_data payload
whose data$matrix is the metric matrix (was previously the bare
matrix only).
plot_information(..., draw = FALSE) payloads now include a
series field listing which curves the legend describes
("Information", "SE", or both for type = "both"), so downstream
ggplot2 re-renderers can map the right column without inspecting
type manually.
summary(diagnose_mfrm(...)) now prints the fixed-effect chi-square
block ("are all elements equal?") directly from
diag$facets_chisq (Facet, Levels, MeanMeasure, SD,
FixedChiSq, FixedDF, FixedProb, plus the random-effect
counterparts when present) and the inter-rater agreement summary
(Exact / Expected / Adjacent agreement, MeanAbsDiff, MeanCorr,
RaterSeparation, RaterReliability) instead of leaving them in the
diagnostics object only. The new summary(diag)$facets_chisq and
summary(diag)$interrater slots also expose the same tables for
programmatic use.summary(diagnose_mfrm(...))$key_warnings now names the worst
MnSq-misfit elements (e.g. MnSq misfit: Person:P023 (Infit=1.70, Outfit=2.40; outside 0.5-1.5).) and prints a dedicated
MnSq misfit block showing every flagged element. Threshold pair
is exposed at summary(diag)$misfit_thresholds and is steered by
mfrm_misfit_thresholds() (see above).summary(diagnose_mfrm(...)) now prints a category usage block
(one row per observed score with Count, AvgMeasure, and a
Disordering flag when the average measure decreases across
adjacent categories). Exposed programmatically at
summary(diag)$category_usage.summary(fit_mfrm(...)) now prints a targeting block
(Person mean - Facet mean, plus PersonSD / FacetSD /
SpreadRatio) for every non-person facet. Under the package's
sum-to-zero identification this collapses to the person mean by
construction; the row labels make that explicit and the spread
ratio surfaces whether persons or facets dominate the test scale.summary(estimate_bias(...)) now reports Bonferroni and
Holm significant-cell counts alongside the raw screen-positive
count. Both are exposed in summary(bias)$overview as
BonferroniSignificant and HolmSignificant.print(fit) and print(summary(fit)) now show an "Attached
diagnostics" line when fit_mfrm(..., attach_diagnostics = TRUE)
has merged per-element fit columns onto fit$facets. The
attach-diagnostics path now extends to the person-facet table,
so per-person Infit, Outfit, InfitZSTD, OutfitZSTD, and
PtMeaCorr columns are visible in summary(fit)$person_high
and summary(fit)$person_low.To improve navigability of the core estimation engine, four
self-contained sections moved out of R/mfrm_core.R into focused
files. All functions remain internal and the public API is
unchanged.
R/core-likelihood.R -- polytomous Rasch likelihoods and
cumulative response-probability helpers.R/core-data-prep.R -- data validation, indexing, and small
formatting utilities.R/core-anchor-audit.R -- anchor-table reading, normalization,
and connectivity / overlap audit.R/core-optimizer.R -- optim() / EM dispatch and MML-EM
scaffolding.R/api-simulation.R similarly grew an
R/api-simulation-future-branch.R companion file holding the
future-branch design-schema layer. Public simulation entry points
(simulate_mfrm_data, evaluate_mfrm_design,
evaluate_mfrm_diagnostic_screening,
evaluate_mfrm_signal_detection) remain in R/api-simulation.R.
R/api-plotting-extras2.R was renamed to
R/api-plotting-screening.R to drop the numerical suffix in favour
of a functional name; tests follow the same rename.
A new tests/testthat/helper-fixtures.R exposes
make_toy_fit() / make_toy_diagnostics() / local_toy_fit()
helpers so future tests can reuse the standard example_core fit
without retyping the load_mfrmr_data() + fit_mfrm() +
diagnose_mfrm() chain.
export_mfrm_bundle() and build_mfrm_replay_script() now write a
self-contained replay package:
replay.R includes every argument that affected the
original fit_mfrm() call. Earlier 0.1.x scripts silently dropped
missing_codes, mml_engine, slope_facet, anchor_policy,
min_common_anchors, min_obs_per_*, facet_shrinkage,
facet_prior_sd, shrink_person, and attach_diagnostics, so
fits that depended on those arguments did not actually replay.fit_mfrm() now records its inputs in
fit$config$replay_inputs (post match.arg) so the bundle
generator has a single source of truth.utils::packageVersion("mfrmr")
guard that warns when the installed version differs from the
recorded one.export_mfrm_bundle(..., data = ...) accepts the original analysis
data; when supplied, the data is written into the bundle as
<prefix>_replay_data.csv and the replay script reads from that
co-located file. The recorded input hash is now computed against
the user's original data (not the package's internal prep$data,
which carries synthesised columns), so users can verify their CSV
matches the recorded fingerprint.tests/testthat/test-replay-roundtrip.R actually sources the
generated replay script in a fresh environment and compares the
reproduced log-likelihood and person estimates to the original.diagnose_mfrm() on large designscalc_interrater_agreement() (the inter-rater agreement helper that
diagnose_mfrm() calls when Person is part of facet_cols)
previously used a list() for the per-context probability lookup
and c(exp_vals, ...) accumulation inside a per-row loop. This
gave near-quadratic scaling: 6,400 observations took ~2 s, but
72,000 observations took ~141 s. The lookup is now an
environment (hash-backed for character keys) and exp_vals is
preallocated and filled by index, so the helper now scales linearly
in the number of observations. On the 72,000-observation benchmark
in the audit, diagnose_mfrm() drops from ~141 s to ~15 s.
The make_union_find() helper used by the connectivity audit was
also rewritten with an iterative find_root (with path
compression) instead of the previous recursive form. Designs whose
union chain depth exceeded options(expressions) (default 5,000)
no longer error out with "evaluation is too deeply nested".
prepare_mfrm_data() now:
message() summarising how many rows it dropped due to
missing values or non-positive weights, instead of dropping them
silently;Person and facet IDs
(with a message() reporting the row count) so " P01 " and
"P01" do not silently become two persons;warning()s when the input contains duplicate Person x facet
rows (which violate MFRM's conditional-independence assumption)
but lets the fit continue rather than refusing it outright.fit_mfrm() now treats NaN / Inf for maxit, reltol, and
quad_points as invalid input with a localised English error,
instead of falling through to R's locale-dependent
"missing value where TRUE/FALSE needed" message.
The two-page landscape cheatsheet now ships in pre-rendered form at
system.file("cheatsheet", "mfrmr-cheatsheet.pdf", package = "mfrmr")
alongside the existing .Rmd source. Users without a working LaTeX
toolchain can open the PDF directly; users who want to customize it
can still knit the .Rmd with rmarkdown::render(). The README and
?mfrmr package help now point at both files.
The most-visited help pages now embed concrete interpretation
comments inside their @examples blocks. Each shipped example
shows what value ranges or patterns indicate "good", what threshold
or rule of thumb applies, and what follow-up to run if the value
is off. Coverage in this development line includes:
?fit_mfrm (convergence, person SD, targeting bands).?diagnose_mfrm (key_warnings, MnSq misfit lines, facets_chisq,
inter-rater agreement minus expected).?summary.mfrm_fit and ?summary.mfrm_diagnostics (overview,
person distribution, top_fit ZSTD bands, facets_chisq, targeting).?estimate_bias, ?analyze_dff, ?compute_facet_icc,
?apply_empirical_bayes_shrinkage (effect-size bands, Penfield
classification, Koo & Li 2016 reliability bands, shrinkage factor
interpretation).?build_apa_outputs, ?reporting_checklist, ?plot_qc_dashboard,
?plot.mfrm_fit (manuscript-readiness signals, dashboard panel
status, Wright / pathway / CCC interpretation).?plot_bubble, ?plot_dif_heatmap, ?plot_local_dependence_heatmap,
?plot_reliability_snapshot, ?plot_residual_matrix,
?plot_shrinkage_funnel, ?plot_guttman_scalogram,
?plot_residual_qq, ?plot_rater_trajectory,
?plot_rater_agreement_heatmap (cell / band thresholds,
reference-line interpretation).\donttest{}Several main entry points now expose a small fast-path block (a
JML fit on example_core plus a single diagnostic / plot call)
before the heavier \donttest{} block. The fast path is below
R CMD check's example-time budget and provides a regression net
that runs every check, while the full \donttest{} block
continues to showcase the larger MML / publication-route examples.
Affected pages: ?fit_mfrm, ?diagnose_mfrm, ?plot_qc_dashboard,
?reporting_checklist, ?build_apa_outputs.
?mfrmr_visual_diagnostics adds a "Cross-reference to FACETS /
Winsteps tables" section that lists the closest mfrmr helper for
each canonical Rasch / MFRM table or figure family (Wright map,
pathway / probability curves, test information, misfit, bias /
interaction, DIF / DRF, inter-rater agreement, anchoring /
linking).?mfrmr_visual_diagnostics and the visual reporting template now
enumerate the 4 secondary plot helpers and the 4 screening
helpers added during this development line.?diagnose_mfrm cites Wright & Masters (1982) at the
separation / strata / reliability section and reproduces the
formulae (G = TrueSD / RMSE, R = G^2 / (1 + G^2),
H = (4G + 1) / 3) so the reliability outputs are traceable to
source.?fit_mfrm example block now flags the quad_points = 7 opening
fit as an exploratory speed setting.?mfrmr package help now point at the public
cheatsheet (system.file("cheatsheet", "mfrmr-cheatsheet.Rmd", package = "mfrmr")).|ZSTD| (or
|MnSq - 1| when ZSTD is unavailable) instead of the generic
|metric| placeholder.build_misfit_casebook() now also draws element-level Infit /
Outfit MnSq misfit cases from diagnostics$fit (in addition to
marginal cells, pairwise screens, unexpected responses, and
displacement). The casebook therefore matches what its name
implies.q3_statistic(fit, diagnostics) returns the Yen (1984) Q3 index
between every facet-level pair, with three published reporting
thresholds (Yen 0.20, Marais 0.30, Christensen et al. relative
0.20) and a textual Interpretation column that names which
flag(s) each pair triggered. The helper reuses the standardized-
residual pivot that plot_local_dependence_heatmap() already
draws, so the table and the heatmap stay numerically consistent.
compute_person_fit_indices(diagnostics, fit) was introduced during
development as an extension to the Infit / Outfit / ZSTD columns that
diagnose_mfrm() already exposes. Its final 0.2.0 public contract is:
NA with an audit label because EAP posterior means do not satisfy
the ML person-score equation used by the correction.lz / sqrt(1 + 1/N), retained under an explicit non-Snijders name.The development-only ECI4 column was removed before the 0.2.0 public
release because it duplicated the standardized chi-square /
Outfit-ZSTD approximation rather than implementing Tatsuoka and
Tatsuoka's extended-caution index. Use OutfitZSTD for the equivalent
screen.
mfrm_generalizability(fit) re-fits the rating data as a crossed
random-effects model Score ~ 1 + (1 | Person) + (1 | Facet1) + ...
via lme4::lmer and returns the canonical G / Phi coefficients
plus per-source variance components. Useful when a reviewer asks
for a generalizability-theory complement to the Rasch-style
separation / reliability statistics that diagnose_mfrm()
already emits.
Three thin importers expose external fit objects via the same
mfrm_fit interface that the mfrmr plot and table helpers
consume:
import_mirt_fit(fit, model) accepts a mirt::mirt() result.import_tam_fit(fit, model) accepts TAM::tam.mml() /
TAM::tam.jml().import_erm_fit(fit, model) accepts eRm::PCM() /
eRm::RM() / eRm::RSM().The imported objects carry the mfrm_imported_fit class and
populate measurement-side slots (facets$person,
facets$others, steps, summary) only. Bias / DIF / anchor /
replay slots are explicitly not populated; full bundle import is
planned for a future release.
compute_facet_icc(boot = "boot") gains ci_boot_parallel
("no" / "multicore" / "snow") and ci_boot_ncpus arguments
that are forwarded to lme4::bootMer(). The per-replicate cli
progress bar is suppressed under parallel execution because
worker processes hold their own copy of the progress state.
evaluate_mfrm_design() accepts a parallel = c("no", "future")
argument. When "future" is requested and the future.apply
Suggests package is installed, the rep loop within each design
row honours whatever future::plan() is currently active;
cross-design-row parallelism is planned for a future release. Without
future.apply the call falls back to serial execution with an
explicit message.
fit_mfrm() accepts a checkpoint = list(file = ..., every_iter = ...)
argument. When supplied to a mml_engine = "em" (or hybrid)
fit, the EM scaffolding writes its state to file every
every_iter outer iterations using saveRDS(). If the file
exists when a subsequent call starts, the engine resumes from the
recorded iteration. The direct optim() engine ignores the
checkpoint; non-EM fits run unaffected.
A new tests/testthat/test-gpcm-verification.R exercises every
"supported" and "supported_with_caveat" row of
gpcm_capability_matrix() on a toy dataset and asserts the
documented helper returns the expected shape. "blocked" and
"deferred" rows have negative tests that confirm the helper
either refuses to run or returns an explicit caveat. These tests
make the GPCM scope a contract that future commits cannot
silently shrink.
fit_mfrm(attach_diagnostics = TRUE) runs diagnose_mfrm() once
after the fit and merges the per-level SE, Infit, Outfit, and
PtMeaCorr columns onto fit$facets$others. This makes the facet
table look like a FACETS Table 7 summary without a separate call.
The default FALSE preserves the minimal Facet / Level /
Estimate layout from 0.1.5.
build_mfrm_manifest() gains several new tables so replay bundles
carry everything a deterministic re-run needs:
environment now records RNGKind, RNGSeedDigest, Locale, and
a UTC ISO-8601 timestamp in addition to the existing package and
platform fields.dependencies (new) records the installed version of every
Imports and Suggests dependency, with a Role column.input_hash (new) hashes the input data, anchors, group anchors,
and score_map with SHA-256 (via digest, now in Suggests) or
an MD5-of-RDS fallback. The hash is deterministic across sessions.session_info (new) unrolls utils::sessionInfo() into a long
data frame (Scope / Package / Version).hierarchical_audit, missing_recoding, and shrinkage_audit
(new) surface the three new audit layers in one place.digest is added to Suggests.
estimate_bias() and
estimate_all_bias() previously returned NA for every cell's
S.E., t, Prob., Obs-Exp Average, Infit, and Outfit,
and Significant counts collapsed to zero. Root cause was an
nzchar(NA_character_) call (which returns TRUE) in an internal
predicate. Downstream helpers such as bias_interaction_report()
and plot_bias_interaction() are now populated again.estimate_bias() silent failure on typo'd facet names. A
mis-spelled facet_a / facet_b (e.g. "Raters" with trailing
s) previously returned an empty list() with no warning. It now
raises an informative error naming the available facets. Missing
diagnostics argument likewise raises an explicit mfrmr error
rather than falling through to R's locale-dependent missing-
argument message.zstd_from_mnsq() was numerically unstable for very
small degrees of freedom and could return large positive ZSTD when
MnSq was close to zero, flipping the sign relative to the
companion Outfit ZSTD for the same element. A df >= 1 guard
returns NA in degenerate cells.prepare_mfrm_data() now stops when any
observed Score falls outside the declared
[rating_min, rating_max] range. Previously negative score_k
values passed through m[cbind(i, 0)], silently dropping those
rows from the likelihood while n_obs kept its original value.sanitize_noncenter_facet(),
sanitize_dummy_facets(), and build_facet_signs() now emit a
warning when supplied facet names are not part of the fitted
model. Previously typos such as positive_facets = "rater"
(lowercase) or noncenter_facet = "Raters" could silently flip
the sign convention of facet measures.apply_plot_preset() and
.draw_shrinkage_plot() now restore the user's par() on exit,
per "Writing R Extensions" 2.1. All plot methods that relied on
apply_plot_preset() inherit this automatically.analyze_dff() adds a
ContrastDirection column to the residual and refit branches.
The two methods use opposite sign conventions by design, so the
new column spells out which interpretation applies.compute_facet_icc() singular fit. Total variance below
sqrt(.Machine$double.eps) is now reported as ICC = NA with
Interpretation = "Non-identifiable" instead of a falsely
meaningful value. The first lme4 convergence diagnostic surfaces
as a message() rather than being silently suppressed.as.data.frame.mfrm_fit()
now carries the new Extreme column through to ggplot2 / CSV
pipelines instead of dropping it.as_kable(format = "pipe") output. Previously silently
returned HTML when kableExtra was installed and the apa_table
carried a non-empty note. "pipe" now consistently returns the
Markdown table with an appended Note. line.audit_mfrm_anchors() false positives. Overlap-adequacy risk
flags are skipped when no anchors or group anchors were supplied,
so single-wave analyses no longer emit "high severity" warnings
because OverlapLevels == 0 everywhere.sqrt(.Machine$double.eps) (~1.5e-8) to 1e-6, so integer
codes like 1.0000001 that round-trip through CSV floats are now
accepted. Genuinely fractional scores (1.5, 2.75) are still
caught.rating_min / rating_max were inferred from the observed scores,
the informational message was emitted twice per fit_mfrm() call
(once from audit_mfrm_anchors() and once from mfrm_estimate()).
fit_mfrm() now uses a session-scoped option to suppress the
duplicate; standalone calls to prepare_mfrm_data() continue to
announce normally.plot(fit, type = ...). Passing an
unknown type previously raised R's locale-dependent
match.arg() error. It now raises an English mfrmr-style error
listing the valid choices.plot_dif_heatmap(draw = FALSE) payload contract. The helper
documented an mfrm_plot_data payload but invisibly returned the
bare matrix, breaking the documented contract used by sibling
plot_* helpers. It now returns an mfrm_plot_data whose data
slot bundles matrix, pairs, metric, and value_column. Code
that relied on the old shape should switch from dim(heat) to
dim(heat$data$matrix).plot_bias_interaction(show_ci = TRUE, ci_level = 0.95) (scatter
and ranked modes) now draws BiasSize \u00b1 z \u00b7 SE whiskers
using the per-cell SE from [estimate_bias()]. plot_displacement( show_ci = TRUE) (lollipop mode) draws
Displacement \u00b1 z \u00b7 DisplacementSE whiskers from the
audit-table standard error. Both functions now populate
CI_Lower / CI_Upper / CI_Level columns on the returned
plot-data element so downstream pipelines can reuse the bounds.
plot_fair_average() CI support (now implemented later in this
release) uses a delta-method propagation because the
fair-average SE lives on the logit scale while the plot uses the
observed-score scale, which requires a delta-method transformation.fit_mfrm() emits a one-time message() when called with
anchor_policy = "silent" while the anchor audit flags issues.prepare_mfrm_data() announces when rating_min / rating_max
have been inferred from the observed scores rather than supplied
explicitly, and flags facets with only one observed level
(structurally fixed at 0 by the sum-to-zero constraint)."low", "medium", "high") now raise
a targeted error up front instead of surfacing as the opaque
"No valid observations remain" message.detect_anchor_drift() and build_equating_chain() thin-linking
warnings now list per-facet retained-vs-threshold counts
(e.g. "Rater (3/5)").bias_interaction_report()$summary carries a FlagStatus column
so empty ranked_table rows are no longer ambiguous between
"nothing flagged" and "nothing computed".rcond(mm) < 1e-8), catching numerically collinear
covariates rather than only exact rank deficiency.apply_empirical_bayes_shrinkage() docstring and R comments now
document the shrinkage-variance formula as
max(0, K^{-1} * sum(delta^2) - mean(SE^2)), matching the
implementation under sum-to-zero identification.?fit_mfrm "Input requirements" now states the MFRM conditional
independence assumption (Linacre, 1989) and points at
diagnose_mfrm(..., diagnostic_mode = "both") /
strict_pairwise_local_dependence as the exploratory follow-up.?fit_mfrm example block now flags the quad_points = 7 opening
fit as an exploratory speed setting and reminds readers that the
package default quad_points = 31 is the publication tier, so the
example no longer reads as a recommendation against the new default.?fit_mfrm now presents the recommended quad_points tiers as a
\tabular{} block (7 fast scan, 15 default, 31+ publication)
so readers do not have to re-extract the recommendation from prose.
The "adapted from Linacre (1994)" wording for the sample-size bands
and the wall-clock cost of diagnostic_mode = "both" are also
spelled out.?fit_mfrm missing_codes docstring is now an itemised list of
the three branches (NULL / TRUE / custom vector) instead of
dense prose.?run_mfrm_facets now notes that method = "JML" is the default
for legacy FACETS-style output continuity and points users at
fit_mfrm(..., method = "MML") for new analysis scripts.?apply_empirical_bayes_shrinkage now states that
EffectiveDF = Σ(1 − B_j) matches the "effective number of
parameters" from Efron & Morris (1973).?compute_facet_icc notes that Koo & Li (2016) recommend applying
the reliability bands to the 95% confidence interval of the ICC,
while the current implementation bands the point estimate only.?analyze_facet_equivalence adds Kass & Raftery (1995) as the
reference for the BIC-based Bayes-factor approximation
BF_{01} ≈ exp((BIC_{H1} − BIC_{H0}) / 2).?mfrmr-package now cites Wilson & Hilferty
(1931) explicitly alongside Wright & Linacre (1994).calc_displacement_table() now cites the Winsteps user guide for
the combined |Displacement| > 0.5 logit and |t| > 2 flagging
rule.analyze_facet_equivalence() docstring frames the
equivalence_bound = 0.5 default as a starting point.print() S3 methods for 13 classes that previously fell
back to the default list printer (mfrm_apa_outputs, mfrm_bias,
mfrm_bundle, mfrm_design_evaluation, mfrm_diagnostics,
mfrm_facet_dashboard, mfrm_future_branch_active_branch,
mfrm_plausible_values, mfrm_population_prediction,
mfrm_reporting_checklist, mfrm_signal_detection,
mfrm_threshold_profiles, mfrm_unit_prediction). Each delegates
to the existing summary() method.Reference citations corrected:
379-402 (was 379-421).plot_qc_dashboard() plots a signed ZSTD distribution combining
Infit and Outfit ZSTD, with reference lines at
-3 / -2 / 0 / 2 / 3. The previous absolute-value histogram
collapsed over-fit and under-fit tails.plot_residual_pca(..., plot_type = "scree") draws Rasch
secondary-dimension reference lines at 1.0 / 1.4 / 2.0 / 3.0,
consistent with the Winsteps user guide. The legend and returned
reference_lines record the new entries.content_checks entry uses a case-insensitive
regex so the check passes whether the APA contract uses
"Residual PCA" or the longer "Exploratory residual PCA".fit$facets$person exposes PosteriorSD and SE aliases
alongside the legacy SD. MML fits populate all three with the
posterior SD under the Gauss-Hermite prior; JML fits set
SE = NA_real_ and note that per-person SEs should be pulled
from diagnose_mfrm()$measures.analyze_dff(method = "refit") subgroup fits return a
LinkingAudit column that captures the anchor-audit messages
emitted during the refit, replacing the previous
hard-coded anchor_policy = "silent" silence.detect_anchor_drift() returns common_vs_reference and
n_common_all_waves alongside the existing pairwise
common_elements table, for 3+ wave linking reviews.analyze_residual_pca(..., pca_max_factors = "auto") caps the
factor count at min(10, ncol - 1, nrow - 1) per matrix; this
value was previously silently coerced to NA.describe_mfrm_data() returns two new components:
missing_rate_summary (per-column missing / non-missing counts)
and facet_crosstabs (long-format pairwise observation-count
tables, suitable for heatmap plotting).Author / Maintainer fields
auto-generated by CRAN; Authors@R remains the single source of
truth. The Description: field is now three sentences (was 10
lines of prose), improving CRAN web readability while retaining
the two rating-scale / partial-credit DOI references.inst/CITATION now tracks meta$Version and meta$Title
dynamically, so citation("mfrmr") prints the current installed
version rather than a hard-coded string.At this development checkpoint, 6,380+ tests passed (up from 6,343 in 0.1.5), with 0 failures and 0 errors. Additional 0.2.0 release tests were added later for GPCM, empirical fit, classical DIF, residual dimensionality, arbitrary-facet simulation, and reporting routes. New test files at this checkpoint:
test-shrinkage.R (40 tests) covers the closed-form math, edge
cases (K < 3, tau^2 <= 0, user-supplied prior), fit_mfrm
integration, reporting and manifest trails, and the three new
plot methods.test-missing-codes-integration.R (17 tests) covers fit_mfrm,
describe_mfrm_data, audit_mfrm_anchors, and manifest paths.test-hierarchical-audit.R (10 tests) covers the five new
hierarchical-audit helpers and their integration points.Pre-existing test-harness errors unrelated to 0.1.5 behaviour have
also been cleaned up (S3 dispatch, GPCM scope wording, internal-helper
prefixing with mfrmr:::).
print(fit), summary(fit), and
summary(diagnose_mfrm(...)) so results start with Status,
Key warnings, and Next actions.MML, review diagnostics with diagnostic_mode = "both", then move to
reporting helpers.score_map, and clearer warnings for retained
zero-count categories.MML branch for ordered RSM /
PCM fits with person covariates, including simulation and scoring support
for the fitted population model.GPCM support for the documented direct route, including core
summaries, diagnostics, plots, posterior scoring, and information checks,
while keeping unsupported downstream routes explicit.RSM / PCM
use, fixed-calibration scoring after JML, and PCM information curves.plot_marginal_fit() and
plot_marginal_pairwise().reporting_checklist(),
build_summary_table_bundle(), export_summary_appendix(), and
visual_reporting_template() for manuscript-oriented tables, appendix
artifacts, and figure-placement guidance.plot(fit, type = "ccc_surface", draw = FALSE) output
for advanced visualization while keeping 2D Wright/pathway/category plots as
the default reporting route.DESCRIPTION references to use the requested authors (year) <doi:...>
format.facet_quality_dashboard(),
including the output class, structure, and interpretation.\dontrun{} with \donttest{} for executable examples so CRAN can
exercise those examples during checks.DESCRIPTION metadata to avoid CRAN incoming spell-check notes on
cited proper names.fit_mfrm() and method selection (MML default, JML).estimate_bias().build_fixed_reports()).build_apa_outputs()).build_visual_summaries()) with configurable threshold profiles.analyze_residual_pca(), plot_residual_pca()).data/ and inst/extdata/.R CMD check.CONTRIBUTING.md, CODE_OF_CONDUCT.md, and SECURITY.md.inst/CITATION, CITATION.cff).