Start with the interactive ggplot2 gallery for three worked examples with Plotly views, static figures and underlying values.
This vignette connects figures to questions about fitted ratings, external attributes and observed-score planning. It is organized around six practical questions:
Model examples use packaged data; the feature hierarchy uses eight
fictional raters. The "publication" and
"monochrome" presets illustrate reusable styles, including
views that remain interpretable without colour.
If you are selecting figures for a report, use
reporting_checklist() before or alongside this vignette.
Its "Visual Displays" rows now mirror the public plotting
family shown here.
Use plot() on the fitted model or saved analysis result.
In 0.2.4, the 33 former standalone drawing helpers are no longer
exported. For existing scripts,
help("mfrmr_plot_migration") gives every replacement and
explains which type, view and
metric controls apply.
For observed rating agreement before model fitting, the rating-agreement
tutorial shows matched-score counts and pairwise QWK with retained
sample sizes. Those custom ggplots use
pairwise_kappa_table() output. They are distinct from the
fitted-workflow exact-agreement/correlation heatmap below.
For group comparisons of a rater or task, the DFF tutorial pairs a summary figure with response counts, display omissions and exclusion reasons. It explains why residual differences and linked-refit differences have different sign interpretations and what subgroup linking can establish.
Use the plot guide before choosing a specialized function. It includes rater feedback, category curves, external-feature groups and G/D-study planning.
Open a preview for the full figure, interpretation and the values used to draw it. These are different questions: a severe rater need not misfit; feature groups describe backgrounds; a D-study plans an observed-score measurement.
Compare persons, facet levels and category steps
Base R and dedicated ggplot conversion
Review severity together with response fit
Base R and dedicated ggplot conversion
Base R and dedicated ggplot conversion
Inspect observed coverage across rating subsets
Base R; ggplot offers a different, table-based view
Inspect a hierarchy of external-feature groups
Base R and dedicated ggplot conversion
Plan reliability for a multivariate score or composite
Base R and dedicated ggplot conversion
figures <- mfrmr::mfrmr_output_guide("plots")
knitr::kable(figures[, c("Question", "ResultFunction", "GGPlot")])| Question | ResultFunction | GGPlot |
|---|---|---|
| Inspect signed pairwise rater agreement | rater_network_analysis | unavailable |
| Compare weighted network inconsistency indices | rater_network_analysis | unavailable |
| Inspect conditional binary-network coefficients | fit_rater_network | unavailable |
| Compare a binary network with model simulations | fit_rater_network | unavailable |
| Compare persons, facet levels and category steps | fit_mfrm | dedicated |
| Show expected scores across ability | fit_mfrm | dedicated |
| Review severity together with response fit | fit_mfrm | dedicated |
| Compare facet locations and their uncertainty | fit_mfrm | dedicated |
| Inspect category functioning | fit_mfrm | dedicated |
| Review pooled category counts | rating_scale_table | dedicated |
| Review steps within their threshold owner | rating_scale_table | dedicated |
| Compare category curve families | category_curves_report | unavailable |
| Compare fixed-rater estimates and interval methods | mfrm_facet_intervals | dedicated |
| Review conditional interaction departures | bias_interaction_report | dedicated |
| Explore bias across all facet pairs | estimate_all_bias | dedicated |
| Read latent-regression coefficients and uncertainty | mfrm_population_intervals | dedicated |
| Read prespecified conditional population contrasts | mfrm_population_intervals | dedicated |
| Compare same-rater and other-rater score associations | rater_halo_network_analysis | dedicated |
| Show GPCM slope uncertainty | confint.mfrm_fit | native |
| Show GPCM probability or information uncertainty | mfrm_curve_intervals | native |
| Compare observed raters in a shared-rater model | fit_mfrm_random_rater | dedicated |
| Inspect testlet-model facet estimates | fit_mfrm_testlet | dedicated |
| Review conditional Person scores from a shared-rater model | score_mfrm_random_rater | dedicated |
| Review conditional Person scores from a testlet model | predict.mfrm_testlet | dedicated |
| Review response residuals under supported RSMs, two-family GPCM MML or corrected GPCM JML | mfrm_response_diagnostics | dedicated |
| Compare ordinary and extended model results | compare_mfrm | dedicated |
| Compare screening rules across known-truth conditions | mfrm_screening_sensitivity | dedicated |
| Show screening performance with uncertainty | mfrm_screening_performance | unavailable |
| Show pooled fixed-facet intervals after multiple imputation | pool_mfrm_imputed | dedicated |
| Report saved external posterior intervals | read_mfrm_posterior | dedicated |
| Inspect all residual associations and missing pairs | mfrm_residual_network | dedicated |
| Locate residual co-movement after model review | mfrm_residual_network | unavailable |
| Inspect exploratory groups of residual profiles | mfrm_residual_network | unavailable |
| Inspect separation of external-feature groups | mfrm_cluster | dedicated |
| Describe an external feature within groups | mfrm_cluster | dedicated |
| Inspect a hierarchy of external-feature groups | mfrm_cluster_hierarchical | dedicated |
| Inspect group stability across feature imputations | mfrm_cluster_imputed | dedicated |
| Choose how many external-feature components to inspect | mfrm_pca | dedicated |
| Locate entities on external-feature components | mfrm_pca | dedicated |
| Identify features contributing to a component | mfrm_pca | dedicated |
| Plan facet counts with an observed-score D-study | mfrm_d_study | unavailable |
| Inspect sources of observed-score variation | mfrm_g_study | dedicated |
| Plan reliability for a multivariate score or composite | mfrm_d_study | dedicated |
| Plan absolute or relative error in score units | mfrm_d_study | dedicated |
| Explain which error sources change across plans | mfrm_d_study | dedicated |
| Compare D-study plans including the reference | mfrm_multivariate_d_compare | unavailable |
| Inspect observed coverage across rating subsets | subset_connectivity_report | generic |
| Customize the underlying precision values | compute_information | generic |
Open the help for ResultFunction if you have not created
the required result. Choose a row and read PlotCall,
GGPlotCall, DataComponent and
Notes. Replace x in PlotCall with
the indicated result, and save its return value as p. The
call uses draw = FALSE to prepare the figure without
opening a window. Change it to TRUE to display it, or
follow GGPlotCall to customize it with ggplot2. A data
component is a table to inspect; passing that component to
as_ggplot() may produce a different view from the full
figure.
dedicated means there is a converter for the figure;
native means the plot already returns ggplot.
generic describes a table-based graphic that need not
reproduce the original layout or annotations. unavailable
still allows the original plot and custom graphics from
plot_data(p). This is a selected map, not a claim that
every plot or component has been covered. Graphical support does not
extend the model’s statistical assumptions.
Latent regression asks how the conditional mean of ability relates to Person predictors. It differs from a residual PCA or a plot of rater attributes. An example coefficient might describe a group difference under the fitted coding, rather than an individual learner’s score or a rater’s severity.
The bundled-data example in ?mfrm_population_intervals
fits an RSM model with a clearly labeled fictional predictor, calculates
intervals once and creates ci. After running that example,
inspect the result as follows:
summary(ci) # estimates, intervals and status
p <- plot(ci, type = "contrasts") # the saved comparison, without refitting
plot_data(p, "contrast_weights") # which conditional means were compared?
plot_data(p, "population_coding") # factor levels and coding, if applicable
plot_data(p, "notes") # assumptions and experimental limits
as_ggplot(p) # the same saved values for customizationUse plot(ci) for coefficients. A coefficient for a
continuous predictor is per unit of its fitted predictor; the intercept
depends on zero values and factor coding. Read the saved formula and
trained terms in plot_data(p, "population_spec") when
transformations or interactions are involved. Different predictor units
prevent a simple ranking of importance by coefficient magnitude. A
positive mean difference predicts higher coded ratings; the
rubric determines whether higher ratings are favorable.
Filled points and lines show available pointwise intervals. A cross
and CI unavailable label retain a point whose interval
could not be supplied; inspect its Status,
InferenceReview and saved checks. A wide interval is still
shown. Numerical availability does not establish coverage, and crossing
zero is not an automatic practical decision. These RSM/PCM intervals
remain experimental. Residual variance stays in summary(ci)
because its units differ from coefficients and mean contrasts. Save
ci to preserve all targets and numerical checks, and save
p when retaining this particular display.
Choose the proposed score interpretation before choosing a figure. The methods guide shows how to connect that claim to a design, expected pattern, competing explanation and conclusion. These are complementary views; a report need not include every figure.
| Figure | What to read | What the figure does not establish |
|---|---|---|
| Wright map | Whether fitted persons and rubric transitions occupy comparable parts of the scale, with the stated uncertainty | Coverage of all intended content or proof of one latent dimension |
| Category curves and counts | Predicted category use alongside actual category support for the relevant owner | That a rarely dominant category should automatically be merged |
| Fit pathway and residual displays | Where the selected model leaves unexpected variation | A psychological cause, halo/horn, or a rule for excluding a rater |
| G-study component/covariance plots | Where score variation and relations arise, including interactions and combined residual | A correlation scale, a latent factor solution, or which individual judge caused an effect |
| D-study curves and interval forests | Dependability/SEM for specified plans, or a separately requested paired difference | A winner, a universally adequate cutoff, or a paired test inferred from marginal interval overlap |
For example, satisfactory fit and a high G coefficient can coexist with uncertain relations between criteria. Those results address different claims; one does not override the other. Read the worked covariance example for an unresolved result and a reporting example. Keep methods, unavailable states and notes with saved figures. A colour change or a new panel layout does not change their inferential scope.
library(mfrmr)
toy <- load_mfrmr_data("example_operational")
fit <- fit_mfrm(
toy,
person = "Person",
facets = c("Rater", "Criterion"),
score = "Score",
method = "MML",
model = "RSM"
)
diag <- diagnose_mfrm(fit, residual_pca = "none")
checklist <- reporting_checklist(fit, diagnostics = diag)
subset(
checklist$checklist,
Section == "Visual Displays",
c("Item", "Available", "NextAction")
)A logit value needs a rubric direction and a model role. For comprehensibility, 1 = difficult / 4 = easy makes higher Person locations mean easier speech; 1 = easy / 4 = difficult makes them mean more difficult speech. In both cases, higher Person locations predict higher coded scores. With the default negative Rater orientation, higher Rater locations predict lower coded scores, so “stricter” is appropriate only when higher scores are more favorable.
Record both endpoint labels with the analysis.
positive_facets changes the orientation of the selected
facet parameters; it does not reverse the outcome categories. Renaming
categories in a figure also leaves the model unchanged. The visual-diagnostics
help explains recoding, anchors and the terms used below. The
package can report the fitted signs but cannot infer the substantive
meaning of your rubric.
Use the Wright map first when you want one shared logit view of persons, facet levels, and step thresholds.
Interpretation:
The locations used in the figure remain available as a table:
Read the accompanying definitions and fitted directions outside the figure:
To find a particular judge, use their ID from the fitted data. The
> prefix marks your selection in color or monochrome; it
does not flag a statistical problem. Use another facet name in the list
to select its levels instead.
judge <- as.character(toy$Rater[1])
focused <- plot(fit, type = "wright", highlight = list(Rater = judge),
draw = FALSE)
subset(plot_data(focused, "locations"), Highlighted)Selected levels remain visible even if the compact top_n
selection would omit them. They are added to the usual selection, so
inspect plot_data(focused, "retention") for the displayed
and omitted counts. top_n = Inf retains all levels.
Selection does not recenter or refit the model, and the automatic logit
range is determined before compaction.
Here Step means a transition between adjacent score categories: 1 to 2, 2 to 3, or 3 to 4. At a transition’s crossing, those two categories are equally probable; other categories remain possible, so this does not mean 50 percent each. The displayed reference sets other additive facet effects to zero. For a particular judge and criterion, their effects shift the crossing along the Person scale. A Step is not a developmental stage or a contest pass mark.
The native view above remains the recommended analytic figure because
it keeps facet uncertainty and fitted step locations visible. For a
closer FACETS-facing handoff, switch the renderer and leave
show_ci at its default FALSE. The fitted
coordinates do not change.
Every retained native facet level is labelled. Only label coordinates are displaced for collision avoidance, with leader lines back to the fitted points. Step thresholds form one vertical ladder and their labels include the fitted transition logits.
plot(
fit,
type = "wright",
renderer = "facets",
category_labels = c(
`1` = "Beginning", `2` = "Developing", `3` = "Secure", `4` = "Advanced"
)
)This renderer uses one common logit ruler, a *
person-frequency column, signed facet headers, every facet level, and
short labeled score-transition lines. It is FACETS-style visual
correspondence, not a claim that mfrmr and FACETS produce
numerically identical estimates. A numerical comparison requires output
from a documented FACETS version and aligned estimator, identification,
score, and orientation settings. Set show_ci = TRUE only
when you deliberately want a hybrid FACETS-style ruler with the mfrmr
uncertainty extension. Use draw = FALSE and inspect the
facets_style tables when rebuilding the display with
ggplot2 or another graphics system.
Next, use the pathway map when you want to see how expected scores progress across theta.
Interpretation:
The expected-score pathway is not a fit pathway. To review measure against Infit, place Infit on the horizontal axis. This focused view retains all facet levels and the 12 persons furthest from expected fit. It is a review screen, not a representative sample of person fit. Labels are limited to flagged facet levels; the table below identifies every displayed row.
fit_pathway <- plot(
fit,
type = "fit_pathway",
diagnostics = diag,
fit_stat = "Infit",
fit_scale = "mnsq",
include_person = TRUE,
top_n_person = 12,
person_labels = "none",
facet_labels = "flagged",
show_ci = TRUE,
preset = "publication"
)Interpretation:
Overfit means less residual variation than the model expects (mean square below 1); underfit means more (above 1). The selected bands determine screening flags, so not every deviation from 1 triggers a flag. Infit and Outfit weight residuals differently; ZSTD has a zero-centered reference. Calibrated speech-contest judges can agree or have similar severity without overfit. If low fit values occur, inspect actual residual patterns, category use and rating independence before attributing them to training. Shared discussion/copying and consistent independent rubric use are different processes. Neither a low nor a high value alone establishes the cause or justifies removing a judge. See the linked help for the definitions and sources.
fit_values <- plot_data(fit_pathway, component = "table")
fit_values[, c("Facet", "Level", "FitValue", "Measure", "CIEligible")]CIEligible = FALSE means that a location interval is
unavailable under the stored inference rules, not that its uncertainty
is zero. Inspect CIBasis and CIUse in the same
table before interpreting whiskers. Set top_n_person = Inf
to retain all persons, or include_person = FALSE to focus
on facet levels. A crowded view can be read through its table without
changing the model or the fit-screening rules.
Category curves ask which ordered score is most probable at each ability. They complement the expected-score pathway: a single expected score can hide which categories contribute to it.
These are reference-profile probabilities, not observed category frequencies or uncertainty bands. For PCM/GPCM, select and interpret the relevant step/slope owner. A rarely dominant category is a reason to inspect counts and rubric meaning, not automatic evidence that categories should be merged.
head(plot_data(category_curves, component = "probabilities"))
# Optional customization without refitting:
# as_ggplot(category_curves)For observed counts and stored step points, save
rating_scale_table(fit, diag) and use
plot(result, type = "counts") or
type = "thresholds"; both support as_ggplot().
Counts in that figure are pooled: inspect
owner_category_table for each step owner’s denominator and
category support. Stored step SEs are not curve or gap intervals. The
separate category_curves_report() bundle uses base graphics
for its multi-family overview and selected curve views; custom graphics
from plot_data() must retain the curve family and
group.
Unexpected-response screening is useful for case-level review.
plot(fit, type = "unexpected", diagnostics = diag, abs_z_min = 1.5, prob_max = 0.4, view = "scatter", preset = "publication")Interpretation:
Displacement focuses on level movement rather than individual responses.
plot(fit, type = "displacement", diagnostics = diag, anchored_only = FALSE, view = "lollipop", preset = "publication")Interpretation:
When you need the package’s latent-integrated follow-up path, switch
to MML and request diagnostic_mode = "both" so
the legacy and strict branches stay visible side by side. The chunk
below uses compact quadrature for a shorter runtime; final reporting
should be based on a refit with the package default or a higher
quadrature setting.
fit_strict <- fit_mfrm(
toy,
person = "Person",
facets = c("Rater", "Criterion"),
score = "Score",
method = "MML",
model = "RSM",
quad_points = 7,
maxit = 40
)
diag_strict <- diagnose_mfrm(
fit_strict,
residual_pca = "none",
diagnostic_mode = "both"
)
strict_checklist <- reporting_checklist(fit_strict, diagnostics = diag_strict)
subset(
strict_checklist$checklist,
Section == "Visual Displays" &
Item %in% c("QC / facet dashboard", "Strict marginal visuals"),
c("Item", "Available", "NextAction")
)
plot(diag_strict, type = "marginal_fit", top_n = 12, preset = "publication")Interpretation:
plot(diag_strict, type = "marginal_pairwise", preset = "publication").When the design may be incomplete or spread across subsets, inspect the coverage matrix before interpreting cross-subset contrasts.
sc <- subset_connectivity_report(fit, diagnostics = diag)
coverage <- plot(sc, type = "design_matrix", preset = "publication")The cells divide each subset’s observed number of facet levels by the largest such count for that facet among the observed subsets. They do not show the proportion of planned ratings that were completed. A single connected subset can therefore have coverage values of one even with a sparse rating design. Low relative counts invite a closer assignment review; they do not alone establish weak linking, informative missingness, or random missingness.
plot_data(coverage, component = "matrix")
head(plot_data(coverage, component = "table"))
# Only the matrix is converted; the observation-share panel is not included.
# as_ggplot(coverage, component = "matrix")Compare an explicit assignment roster with observed ratings when distinguishing unassigned cells from missing assigned scores. Neither should be coded as zero.
Keep three uses of network separate.
mfrm_network_analysis() analyzes the
assignment/co-observation graph and can expose disconnected measurement
subsets. rater_network_analysis() analyzes pairwise score
relations, and rater_halo_network_analysis() analyzes
rater-by-criterion score-profile correlations. The latter two do not
establish assignment connectedness or a common scale. Their centrality
statistics are graph-theoretic quantities, not rating-scale central
tendency, MFRM severity logits, or causal halo evidence.
For a severity comparison, use
plot(fit, type = "rater_severity", diagnostics = diagnostics, draw = FALSE)
and inspect its returned data component with
plot_data(). CIUse, SE_Method and
IntervalStatus explain whether the whiskers support
reporting, screening or further review. They are normal uncertainty
bands; “severity profile” does not mean a likelihood-profile interval.
Ordinary JML bands remain exploratory. Fixed values use diamonds, points
without intervals use crosses, and unavailable estimates keep their
rows. as_ggplot() preserves the saved endpoints and notes.
Comparing two individual whiskers does not replace an interval for their
contrast.
No magnitude bands are drawn by default. Explicit
show_bands = TRUE adds gray distance-from-zero guides;
their numerical ranges are retained in the Band column.
They are not homogeneity or equivalence decisions. Locations of -0.5 and
+0.5 are one logit apart even though both lie in the central range. An
origin shift can change the ranges without changing that difference. Use
analyze_facet_equivalence() with a substantively justified
bound and eligible MML contrast uncertainty for a separate equivalence
analysis. An insignificant difference or overlapping individual
intervals is insufficient. Older saved plots retain their original
colors and labels; redraw from the saved fit and matching diagnostics to
adopt the new default.
If you are working across administrations, follow up with anchor-drift plots:
Residual PCA is a follow-up layer after the main fit screen.
diag_pca <- diagnose_mfrm(fit, residual_pca = "none")
pca <- analyze_residual_pca(diag_pca, mode = "both", pca_max_factors = 4)
summary(pca)
if (nrow(pca$overall_table) > 0L) {
plot(pca, type = "residual_pca", mode = "overall", view = "scree", preset = "publication")
}The incomplete rating design in this example leaves some pairs without enough shared Persons to compute their residual correlation, so the overall PCA is unavailable. The summary retains the reason and any available facet-specific results. Inspect shared-Person counts and residual variation; do not replace missing correlations with zero or interpret the absence of a scree plot as evidence for one dimension. A facet-specific PCA describes its own residual matrix and cannot replace the unavailable overall analysis.
Interpretation:
A single intended construct is a substantive hypothesis, not a result of choosing a one-dimensional estimator. Ask whether one ability provides a useful account of the ratings for the intended score and decision. Also ask whether remaining associations arise from common tasks, performances, assessors or category use. Several slopes do not create several abilities, and one ability plus a testlet effect is not the same hypothesis as two substantive abilities.
Current diagnostic entry points are available, but there is no dedicated API that certifies unidimensionality or selects a latent dimension count:
| Question | Current mfrmr route | Interpretation |
|---|---|---|
| Is there organized residual variation? | analyze_residual_pca() and plot();
optionally parallel = TRUE. |
Exploratory PCA of standardized residuals. The default reference
permutes residual columns.
parallel_method = "model_bootstrap" generates ratings and
refits supported RSM/PCM MML models; it is not yet a calibrated
dimensionality test. |
| Which levels have associated residuals? | q3_statistic(fit, facet = "Criterion") and
plot(fit, type = "local_dependence", facet = "Criterion");
use your intended facet. |
Standardized residuals are averaged within Person/level cells and correlated across shared persons. This differs from original raw-residual Yen Q3. Default facet Rater asks about raters, not automatically about rubric dimensions. |
| Which category or rating-context margins are poorly reproduced? | diagnose_mfrm(..., diagnostic_mode = "both"), then
plot(diag, type = "marginal_fit") or
plot(diag, type = "marginal_pairwise"). |
Descriptive marginal screens. Their scales omit parts of joint/calibration uncertainty; they are not a calibrated M2/C2 omnibus test. |
| Is a substantive two-dimensional model preferable? | No native multidimensional GMFRM fitting/comparison route yet. | Requires a specified alternative and comparable likelihood or predictive targets; PCA does not supply that comparison. |
A supported first-pass workflow, with the paired facet chosen to match the question, is:
diag <- diagnose_mfrm(fit, residual_pca = "none", diagnostic_mode = "both")
pca <- analyze_residual_pca(diag, mode = "both", parallel = TRUE,
parallel_reps = 200, seed = 9028)
summary(pca)
# Replace Criterion with the fitted facet relevant to the question.
q3 <- q3_statistic(fit, diagnostics = diag, facet = "Criterion")
q3$pairs
q3$notesplot(pca, type = "scree") draws the saved overall PCA;
plot(pca, type = "loadings", mode = "facet", facet = "Criterion")
uses its saved facet analysis. Preserve the full pca result
with saveRDS() so its reference, unavailable scopes and
warnings accompany the figure. There is no dedicated
plot(q3) method: obtain the companion heatmap from
fit with the same diagnostics,
facet and min_pairs, and save its draw-free
payload. as_ggplot() can then convert that payload without
recalculating residuals; plot_data(p, "notes") retains the
qualifications outside the figure.
Two hundred permutations are an example setting, not a qualification threshold. Check missing comparisons, overlap counts, aggregation and the reported reference method before interpreting a cutoff. A small first component or few flags can reflect low power or restricted exposure. A large component does not identify its cause. Neither counting eigenvalues above one/two nor adding one to the number of residual components estimates the number of substantive abilities. The extended testlet/shared-rater models use their own posterior response diagnostics; the ordinary residual-PCA/Q3 contract is not inherited.
For a supported RSM/PCM MML fit, a second reference asks: would similar residual structure arise when this fitted one-ability model generates the ratings and is estimated again? The Person’s generated ability is shared across their rating rows; it is not redrawn separately for every rating.
# fit must be an RSM/PCM MML fit with fixed standard-normal population,
# fixed quadrature, additive facets, unit weights, and no anchors or shrinkage.
reference <- analyze_residual_pca(
fit, mode = "both", parallel = TRUE,
parallel_method = "model_bootstrap", parallel_reps = 200, seed = 9028
)
reference$bootstrap_settings
reference$bootstrap_trials # All attempts, including failures and warnings
reference$parallel_status # Availability separately for each PCA scope
plot(reference, type = "overall_parallel_scree")
# If the reference is available:
# as_ggplot(plot(reference, type = "overall_parallel_scree", draw = FALSE))This is more expensive than permuting residuals: 200 replicates mean 200 model refits, shared across the requested overall/facet analyses. It is an example, not a universal recommended budget or an error-rate guarantee. The method currently excludes GPCM, JML, estimated population models, adaptive quadrature, anchors and extended testlet/shared-rater fits. Generating parameters are fitted point estimates; refitting propagates their sampling variation under that null.
The existing analyzed assignment is held fixed. Missing scores and unassigned ratings are not filled, so this does not test or correct a missingness mechanism. All source/replicate fits must pass numerical convergence checks. Every planned replicate is recorded; if a scope cannot be calculated in any replicate, its reference cutoff is withheld. Inspect the recorded reason instead of dropping failed replicates. Undefined or indefinite sparse correlation matrices remain unavailable. Increasing the replicate count does not repair them.
The plotted cutoff is a componentwise exploratory reference, not a multiplicity-adjusted decision across components or facets. A component above it motivates investigating task dependence, rater effects, assignment and substantive dimensions; it does not identify which explanation is correct. The first component and an any-component rule answer different questions: one can miss a departure that the other flags. Choosing whichever rule looks most striking after viewing the results changes the decision procedure. General false-flag rates and sensitivity across those scenarios still require qualification. Q3 and residual-network references are not supplied by this PCA option.
The same response distribution can sometimes have both a testlet and a multiple-trait interpretation. For example, with independent standard-normal variables, set
\[ z_b = \sqrt{\rho}\,\theta + \sqrt{1-\rho}\,u_b, \qquad b=1,2,\quad 0<\rho<1. \]
Each block predictor has variance one and their correlation is \(\rho\). Using \(z_b\) for the criteria in block \(b\) gives either a shared component plus Person-by-block effects, or two correlated block traits with the same response probabilities. This equality holds for this specified structure; it does not equate all testlet and multidimensional models. The broader relationships are discussed by Rijmen (2010).
Consequently, even an excellent residual screen cannot decide from these ratings alone whether the blocks represent two intended constructs, common performance effects or another shared influence. Task design, theory, external variables and additional measurements distinguish interpretations. A positive residual flag is evidence to investigate the fitted model; a small or absent flag can also reflect limited sensitivity. Fitting a multidimensional model is useful for a specified alternative, but does not automatically resolve this interpretation problem.
Other approaches address different parts of this question:
Thus a multidimensional fitter is needed to estimate and compare that specific alternative, but it is not a prerequisite for checking the adequacy of the one-dimensional model. Even after fitting alternatives, do not assume an ordinary parameter-count chi-square test: a one-dimensional reduction can put a latent correlation at one or leave added loadings unidentified. Match scales, response events, populations and numerical integration, and separate exploratory model discovery from confirmation on new data or held-out complete units.
A graph consists of nodes and edges, but their meanings must be specified. The assignment graph records who rated what; its connectivity concerns linking. A score-correlation graph describes observed associations. A residual graph instead describes associations left after a fitted response model. A residual Pearson correlation edge and a partial-correlation edge conditional on all other residual variables are different estimands. None is automatically causal.
Exploratory
Graph Analysis (EGA; Golino and Epskamp, 2017) uses estimated
networks and community detection to explore dimension structure.
Applying the idea to fitted residuals asks about remaining
structure and is not the same procedure as EGA on the original item
responses. The existing mfrm_network_analysis(),
rater_network_analysis() and
rater_halo_network_analysis() do not implement a residual
EGA procedure. mfrm_residual_network() now supplies a
native signed correlation network and optional
hierarchical clustering, using standardized residual means per Person
and facet level. It is not EGA, which uses a different network and
community estimation procedure.
# Reuse a fitted model and its matching diagnostics.
net <- mfrm_residual_network(fit, diagnostics = diag, facet = "Criterion",
missing = "complete", clusters = 2, min_abs = .2)
net$settings
net$pairs # include unavailable pairs and shared-person N
net$person_usage # complete-person inclusion, not rating-row N
plot(net, type = "network") # sign/strength; circular layout is not a distance
plot(net, type = "matrix") # all correlations, before the display threshold
if (net$clustering$status == "available") plot(net, type = "dendrogram")
report <- mfrm_report(net)
export_summary_appendix(report, output_dir = "residual-review")
saveRDS(net, "residual-review/network.rds")Clustering uses sqrt(2*(1-r)) distances between centered
unit-length residual profiles on a common complete-person sample.
Average or complete linkage and the supplied number of groups define an
exploratory partition. Strong negative correlations are dissimilarity,
not similarity by absolute value. The display threshold never changes
the groups. Without a common usable sample or variation at every level,
clustering is unavailable; the network retains that reason. Use
missing = "pairwise" without clustering when pair-specific
overlap is the intended descriptive analysis. Neither route estimates
group stability.
Random forests belong to a separately designed prediction stage. Wesolowski (2019) formed rater groups by k-means on differential-rater-functioning indices and then predicted those labels with random forests. Predicting exploratory labels does not validate latent classes. The function help explains unsupervised learning, supervised prediction, leakage, resampling and why out-of-bag accuracy alone cannot validate labels discovered on all data.
For example, residual associations among accuracy criteria across different tasks and assessors may motivate a second substantive-domain hypothesis. Associations confined to one performance may instead motivate a testlet model. Associations organized by assessor or assignment panel require review of those effects. These patterns guide follow-up; the graph alone cannot distinguish all competing explanations. Communities, centrality and edge density are not proof of dimensions, rater quality or an omitted causal mechanism. No edges can also reflect regularization or weak data rather than independence.
For any residual-network workflow, specify nodes (criterion, task-by-criterion, or another justified unit), residual type, aggregation and pairwise person counts. Sparse pairwise correlations may be undefined or form an invalid correlation matrix. Do not encode unassigned cells as zero, treat a missing edge as a zero association, or silently repair a matrix and then claim the same estimand. Record regularization and community choices; examine edge and community stability with resampling appropriate to the shared effects. A single nominal sample size can misrepresent strongly unequal pair overlap.
bootEGA offers network/dimension stability analyses, but applying it to a fixed residual matrix does not propagate uncertainty from fitting the MFRM. Its empirical-correlation bootstrap is not a one-dimensional MFRM null simulation. Finally, residual network modeling (Epskamp, Rhemtulla and Borsboom, 2017) models residual dependencies jointly with latent variables. Drawing a graph from fitted residuals does not fit such a joint model or produce its likelihood. A future mfrmr network view should reuse the checked residual/overlap outputs and provide a signed heatmap/table alongside the network, with the same edge values and uncertainty. It should remain exploratory until its statistical reference is qualified.
For interaction screening, use the packaged bias example.
Here bias means an additional departure for a
particular combination, such as one judge scoring Content, after
accounting for the fitted baseline. It is distinct from overall rater
severity or proof of unfairness. A positive Bias Size
increases expected coded scores; with high-is-good scoring this favors
the combination, whereas a positive default Rater severity reduces
scores. Reverse-coded rubrics reverse that substantive interpretation.
The profile below uses mean absolute bias, so it
describes magnitude; inspect signed rows in bias$table to
see direction. Its uncertainty holds the baseline calibration fixed.
bias_df <- load_mfrmr_data("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")
bias_figure <- plot(bias, type = "bias", view = "facet_profile", preset = "publication")The definition, sign, magnitude-only profile and uncertainty
qualifications are retained in
plot_data(bias_figure, "notes") and shown by
print(bias_figure).
Interpretation:
For repeat plotting, save
bias_interaction_report(bias, ...) after choosing the
review thresholds. plot(result, type = "scatter") compares
the observed-minus-expected score average with conditional bias in
logits; type = "ranked" shows saved interaction estimates.
These two views support as_ggplot(), including conditional
bounds when show_ci = TRUE. They hold calibration fixed.
The other bias views use base or custom graphics.
Rater centrality and halo have different targets.
rater_centrality_table() returns indirect indices in a
plain list; save the list and review source status and unavailable-index
reasons alongside its data frames.
rater_halo_network_analysis() returns observed-score
profile correlations. Its edge_distribution, selected
halo_summary metric and retained-edge matrix
views support as_ggplot(); use base graphics for the
network. Save the whole halo result to retain its pair counts, edge
filters and caveats. Neither result is automatically added to an
aggregate mfrm_report(). These views do not distinguish
halo from horn effects: a negative impression can lower several
criterion ratings together without creating a negative correlation. For
a beginner explanation of severity, centrality, halo/horn and local
dependence, see help("mfrmr_methods_reference").
The built-in plots are intended as safe defaults. Use
preset = "monochrome" when a journal, accessibility review,
or print workflow needs grayscale output. For journal figures, teaching
material, dashboards, or lab-specific styles, use
draw = FALSE and the plot-data accessors instead of editing
screenshots.
plot(fit, type = "wright", preset = "monochrome")
wright_payload <- plot(fit, type = "wright", draw = FALSE, preset = "publication")
plot_data_components(wright_payload)
locations <- plot_data(wright_payload, component = "locations")
head(locations)
pathway_long <- plot_data(
fit,
type = "pathway",
component = "pathway_long",
preset = "publication"
)
head(pathway_long[, c("Layer", "CurveGroup", "Theta", "Value")])When you build a custom figure, keep the helper’s guidance tables with the plot data:
Those metadata are the guardrails for captions and interpretation. They let you change colors, labels, panels, or rendering technology while preserving the same measurement scale, reference lines, caveats, and reporting role used by the package-native plot.
Saving only a PNG loses the underlying values and interpretation notes. Save the complete returned object as well. This example uses a temporary filename; choose a project filename to keep it beyond this R session. Keep the rubric alongside the plot: the labels below are the illustrative labels used earlier, not wording that mfrmr inferred from the scores. Use your actual rubric wording.
figure_file <- tempfile(fileext = ".rds")
figure_record <- list(plot = wright,
rubric = c(`1` = "Beginning", `2` = "Developing", `3` = "Secure", `4` = "Advanced"))
saveRDS(figure_record, figure_file)
saved_record <- readRDS(figure_file)
saved_figure <- saved_record$plot
saved_record$rubric
print(saved_figure)
identical(plot_data(saved_figure, "locations"), plot_data(wright, "locations"))For the supported Wright-map conversion, redraw the saved values without refitting or recalculating uncertainty:
as_ggplot() requires the optional ggplot2 package. Save
the fit and matching diagnostics too if you want to choose a different
analysis view later. The plot guide above distinguishes dedicated
conversions, different table-based views and plots without automatic
conversion. This example does not promise the same layout for every
renderer or conversion of every plot family.
Use options(mfrmr.plot_preset = "monochrome") to set a
common default in the development version. This example restores the
previous option when it ends:
local({
old <- options(mfrmr.plot_preset = "monochrome")
on.exit(options(old))
saved <- plot(fit, type = "ccc", draw = FALSE)
special <- plot(fit, type = "ccc", preset = "publication", draw = FALSE)
c(session_default = saved$data$preset, explicit_override = special$data$preset)
})The choices are "standard", "publication",
"compact" and "monochrome". Explicit
preset wins over the option; omitting both retains
"standard". Explicit preset = NULL also
selects the package default. Remove the session setting with
options(mfrmr.plot_preset = NULL).
A supported as_ggplot(saved) conversion uses the saved
preset, whereas a new plot(fit, ...) call uses the current
option. Set the option in a script or use explicit arguments to
reproduce its appearance in another session. This applies to the common
preset controls, including category, coverage and network report plots.
Extended-model plots with separate palette controls retain
their own settings. It does not change estimates, confidence levels,
screening thresholds or the user’s global ggplot theme.
In the development version, eight existing helpers accept
title: plot(), plot(),
plot(), plot(), plot(),
plot(), plot() and plot().
plain <- plot(fit, type = "bubble", diagnostics = diag, view = "infit_outfit", title = NULL, preset = "monochrome", draw = FALSE)
as_ggplot(plain)
# Reuse the saved values, screening references and interval settings.
plain$data$reference_linesOmit title for the default heading, supply
title = "Scoring patterns" to replace it, or use
title = NULL (or "") to hide it. This does not
remove subtitles, legends, screening settings or interpretation notes.
The dashboard method also accepts
plot(dashboard, title = NULL). Keep review-only limitations
in the figure caption or accompanying report if its heading is
hidden.
Existing main calls and positional arguments still work
without deprecation warnings. main = NULL retains its
earlier meaning of “use the default heading”. Do not supply both
main and title, even with the same value.
These aliases do not imply that every plot has an
as_ggplot() converter; consult
mfrmr_output_guide("plots") for the supported route.
Use view = "measure" to relate measures to fair scores,
"scatter" to compare observed and fair scores, or
"difference" to rank their gaps. FairM uses mean reference
measures; FairZ uses zero reference measures. FairZ is an expected score
on the internal score scale, not a z-score. Observed-minus-fair gaps
also reflect assignment and person mix, so they do not by themselves
establish rater bias.
p_fair <- plot(fit, type = "fair_average", diagnostics = diag, facet = "Rater", metric = "FairZ", view = "measure", show_ci = TRUE, preset = "monochrome", show_title = FALSE, show_notes = FALSE, draw = FALSE)
p_fair$data$notes
p_fair$data$plot_data
p_fair$data$excluded
# Requires ggplot2:
g_fair <- as_ggplot(p_fair)
g_fair
attr(g_fair, "mfrmr_notes")Use draw = TRUE to draw the base-R version. Titles and
notes remain in the returned object when hidden in the figure. The same
annotation controls apply to Wright, expected-score pathway, and CCC
plots; they do not change readiness or interval eligibility. Keep the
applicable notes in the surrounding caption or report. Other plotting
helpers have their own documented options.
Fair-score intervals remain diagnostic-only
(CI_Eligible = FALSE), with full-refit coverage unverified.
RSM/PCM plots propagate only the focal measure SE, holding thresholds
and reference measures fixed; they require a fitted model, not just a
stored fair-average bundle. GPCM-MML plots use available structural
delta-method SEs for non-Person rows, conditioning on Person
EAP/reference means. The table option fair_se = TRUE
supplies the GPCM route, not RSM/PCM fair-score SEs. Historical table
SE columns describe measures. Difference-view whiskers hold
observed means fixed and are not confidence intervals for the
observed-minus-fair gap.
Expected-score and CCC curves use six line types: solid, dashed, dotted, dotdash, longdash, and twodash. Fair-score plots use six point shapes. These encodings repeat for larger sets; select facets or use panels to keep a grayscale figure readable. Check labels and line separation at the intended output size rather than relying on color or gray levels alone.
The package ships a complementary visual layer for teaching and diagnostic follow-up. These helpers are not default reporting figures; use them after the main screens above.
plot(fit, type = "guttman", diagnostics = diagnostics)
renders a person x facet-level response matrix with an
unexpected-response overlay, for teaching-oriented scalogram intuition
and local triage.plot(fit, type = "residual_qq", diagnostics = diagnostics)
plots a Normal Q-Q of person-level standardized residual aggregates as
exploratory follow-up on residual tail behavior.plot(fit_a, type = "trajectory", comparison = list(T2 = fit_b), labels = c("T1", "T2"))
tracks rater severity across named waves. The helper does not perform
linking; supply waves that have already been placed on a common anchored
scale (see vignette("mfrmr-linking-and-dff")) before
interpreting movement as rater drift.plot(fit, type = "agreement", view = "heatmap", diagnostics = diagnostics)
renders a compact pairwise rater x rater agreement matrix; pass
metric = "correlation" to colour by the Pearson-style
Corr column instead of exact agreement.response_time_review(data, person, facets, time)
summarizes response-time metadata by person, facet, and score category.
Pair it with plot() for distribution and grouped timing
plots. This is a descriptive QC layer, not a joint speed-accuracy
model.plot(fit_eb, type = "shrinkage", view = "funnel", show_ci = TRUE)
draws raw and empirical-Bayes shrunken facet estimates on the same row,
with optional confidence whiskers for both estimates. Use this only
after apply_empirical_bayes_shrinkage() or
fit_mfrm(..., facet_shrinkage = "empirical_bayes").Keep the agreement table when drawing several views. Its matched-context counts and saved thresholds explain the figure; correlation describes score ordering, while exact agreement describes equal scores. Neither supplies an automatic rater-quality decision. Model-expected agreement is a fitted-model baseline, not chance agreement.
agreement <- interrater_agreement_table(fit, diagnostics = diag, rater_facet = "Rater")
p_agreement <- plot(agreement)
# Alternative saved view: plot(agreement, type = "agreement", metric = "correlation")
# Optional ggplot conversion: as_ggplot(p_agreement)Save agreement with saveRDS() to choose
another pair view after reopening, or save p_agreement to
retain the selected view and its appearance. Inspect
plot_data(p_agreement, "notes") for the qualifications
outside the figure. Bar colors follow the combined exact/correlation
flag, so a bar above its displayed threshold can still be flagged by the
other rule. To change the rules, recreate agreement from
the existing fit with explicit exact_warn and
corr_warn; changing a reference line alone would contradict
the flags.
If your rating-event data include response times, review them separately from the MFRM likelihood. Rapid and slow response-time flags are descriptive quality-control prompts; they do not change measures and should not be treated as proof of disengagement, cheating, or speededness.
toy_rt <- toy
toy_rt$ResponseTime <- 12 + (seq_len(nrow(toy_rt)) %% 7) +
as.numeric(toy_rt$Score)
toy_rt$ResponseTime[1] <- 2
toy_rt$ResponseTime[2] <- 38
rt <- response_time_review(
toy_rt,
person = "Person",
facets = c("Rater", "Criterion"),
score = "Score",
time = "ResponseTime",
rapid_quantile = 0.10,
slow_quantile = 0.90
)
summary(rt)
plot(rt, type = "distribution", preset = "publication")
plot(rt, type = "person", preset = "publication")Interpretation:
When a non-person facet has few levels or sparse observations, a large raw severity estimate can be a noisy estimate rather than a stable facet signal. The shrinkage funnel shows how far empirical-Bayes pooling moved each level toward the facet mean and whether the uncertainty remains wide after pooling.
fit_eb <- apply_empirical_bayes_shrinkage(fit)
shrink <- plot(fit_eb, type = "shrinkage", view = "funnel", show_ci = TRUE, ci_level = 0.95, preset = "publication", draw = FALSE)
head(shrink$data$table[, c(
"Facet", "Level", "RawEstimate", "RawCI_Lower", "RawCI_Upper",
"ShrunkEstimate", "ShrunkCI_Lower", "ShrunkCI_Upper",
"ShrinkageFactor"
)])
plot(fit_eb, type = "shrinkage", view = "funnel", show_ci = TRUE, ci_level = 0.95, preset = "publication")Interpretation:
A hierarchy can summarize raters’ backgrounds before discussing training. These eight fictional raters illustrate the mechanics; their groups are not estimates of severity, ability or rater quality. Choose attributes and their coding for the substantive question before interpreting a tree.
backgrounds <- data.frame(
Rater = paste0("R", 1:8),
Years = c(1, 2, 4, 6, 8, 10, 12, 15),
Training = ordered(c("Basic", "Basic", "Advanced", "Basic",
"Advanced", "Advanced", "Specialist", "Specialist"),
levels = c("Basic", "Advanced", "Specialist")))
features <- mfrm_features(backgrounds, "Rater", c("Years", "Training"))
hierarchy <- mfrm_cluster_hierarchical(features, k = 3, linkage = "average")Three groups were requested; the diagram does not select that number or establish that the groups are real. Feature weights, coding and linkage can change the branches. The full tree and memberships in leaf order are available for inspection. The development version supports both the base plot and a dedicated ggplot conversion of this saved tree.
plot_data(hierarchy_plot, component = "table")
tree <- plot_data(hierarchy_plot, component = "tree")if (requireNamespace("ggplot2", quietly = TRUE)) {
hierarchy_figure <- as_ggplot(hierarchy_plot, component = "tree") +
ggplot2::labs(title = NULL, subtitle = NULL)
print(hierarchy_figure)
}The conversion keeps the saved label setting and preset. Use
labels = FALSE when creating a crowded tree to hide IDs
without removing leaves. Where heights tie, boxes follow the stored
merge-order groups; they need not correspond to a unique horizontal
height cut. Removing titles or captions with
ggplot2::labs() leaves the tree and memberships in
plot_data().
For imputed features, group comparisons and PCA, continue with Exploring person, rater, and task attributes.
For data preparation, interpretation of plan differences and saved
reopening, start with Plan an assessment
with G theory. For several score criteria in a single panel, use
plot(d, score = c("V", "W"), metric = "Phi"), with names
from your result. Select named composites with
composite = c("Equal", "Difference"). For two-facet
results, add x_var = "Tasks", panel_by = "Raters", ncol = 2
to separate the rater counts into panels with shared axes and score
colors; reverse x_var and panel_by to separate
task counts. as_ggplot() retains this layout using
facet_wrap(). Panels retain unavailable results and nested
count labels such as “Raters per Task”. Add reference = 0.8
only if that is your chosen criterion; guides are not universal adequacy
rules. Both plot families omit reference lines by default;
plot(main_effects_d, reference = c(0.7, 0.8)) restores its
earlier guides.
Use three complementary views: plot(g, weights = ...)
shows the G-study’s raw sources of score variation; plot(d)
and plot(d, type = "sem") show future plan projections;
plot(comparison, view = "differences") shows the eligible
paired differences with uncertainty. Raw components are in squared score
units and retain negative estimates. They are not percentages or
future-plan error contributions. G-study component and D-study
projection plots support as_ggplot() for editing; retrieve
cautions with plot_data(p, "notes") and preserve them
outside the figure.
A D-study asks how a specified measurement design would perform if we changed facet counts. The packaged MGENOVA manual example has two scores, V and W, observed for Persons crossed with Tasks. Here the target is their equal-weight composite. There is no rater facet in this example.
tasks <- read.csv(system.file("extdata", "mgenova-table12.csv", package = "mfrmr"))
gstudy <- mfrm_g_study(tasks, c("V", "W"), rater = NULL)
planned <- mfrm_d_study(gstudy,
data.frame(Tasks = c(3, 6, 12)), weights = c(V = 0.5, W = 0.5))G concerns relative comparisons among Persons; Phi concerns absolute score interpretation. More tasks reduce the modeled task-related error while holding the estimated covariance components and composite weights fixed. These are observed-score projections, not MFRM ability reliability or a guarantee for a new task population. Uncertainty in the estimated components is omitted.
plot_data(planning_plot, component = "table")
# Customizable conversion of this selected score/composite:
# as_ggplot(planning_plot)
# Error in score units is a different view:
# plot(planned, type = "sem")Use ?mfrm_d_study for score/composite selection and
supported crossed or nested designs. An ordinary
mfrm_d_study object has its own base plot; its automatic
ggplot conversion is currently unavailable.
A batch returned by score_mfrm_calibration() is not a
fitted-model diagnostic object. Review it through its own methods:
summary(scores)
plot(scores, type = "interval", preset = "publication")
plot(scores, type = "precision", preset = "publication")
plot(scores, type = "edge_mass", preset = "publication")The interval view highlights scored_review Persons. The
precision view shows valid response rows against posterior SD, and the
edge-mass view compares outer-node posterior mass with the artifact’s
recorded review threshold. Persons with no valid responses remain in the
summary and draw-free plot payload rather than being assigned an
artificial coordinate. These displays do not replace source-fit
diagnostics, and their uncertainty is conditional on the frozen point
calibration. See
vignette("mfrmr-portable-calibration", package = "mfrmr")
for the complete fresh-session workflow.
For a compact visual workflow:
reporting_checklist() when you want the package to
route which figures are already supported.plot() for one-page triage.plot(), plot(), plot(), and
plot() for local follow-up.plot(fit, type = "wright") and
plot(fit, type = "pathway") for targeting and scale
interpretation.plot(), plot(), and plot()
for deeper structural review.response_time_review() and plot() when
response-time metadata are available.plot(show_ci = TRUE, type = "shrinkage", view = "funnel")
when empirical-Bayes shrinkage was applied.summary(scores) and plot(scores) for a
separately created portable score batch; do not use these as source-fit
diagnostics.plot(), plot(), plot(), and
plot() as the teaching / drift / agreement-heatmap
follow-up layer.