--- title: "mfrmr Visual Diagnostics" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{mfrmr Visual Diagnostics} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} is_cran_check <- !isTRUE(as.logical(Sys.getenv("NOT_CRAN", "false"))) knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 5, eval = !is_cran_check ) ``` This vignette is a compact map of the main base-R diagnostics in `mfrmr`. It is organized around four practical questions: - How well do persons, facet levels, and categories target each other? - Which observations or levels look locally unstable? - Is the design linked well enough across subsets or forms? - Where do residual structure and interaction screens point next? All examples use packaged data and `preset = "publication"` so the same code is suitable for manuscript-oriented graphics. 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. ## Minimal setup ```{r setup} library(mfrmr) toy <- load_mfrmr_data("example_core") fit <- fit_mfrm( toy, person = "Person", facets = c("Rater", "Criterion"), score = "Score", method = "JML", model = "RSM", maxit = 20 ) diag <- diagnose_mfrm(fit, residual_pca = "none") checklist <- reporting_checklist(fit, diagnostics = diag) subset( checklist$checklist, Section == "Visual Displays", c("Item", "Available", "NextAction") ) ``` ## 1. Targeting and scale structure Use the Wright map first when you want one shared logit view of persons, facet levels, and step thresholds. ```{r wright} plot(fit, type = "wright", preset = "publication", show_ci = TRUE) ``` Interpretation: - Compare person density on the left to facet and step locations on the right. - Large gaps suggest weaker targeting in that logit region. - Wide overlap in confidence whiskers means neighboring levels are not cleanly separated. Next, use the pathway map when you want to see how expected scores progress across theta. ```{r pathway} plot(fit, type = "pathway", preset = "publication") ``` Interpretation: - Steeper rises indicate stronger score progression. - Dominant-category strips show where each category is most likely to govern the score. - Flat or compressed regions suggest weaker category separation. ## 2. Local response and level issues Unexpected-response screening is useful for case-level review. ```{r unexpected} plot_unexpected( fit, diagnostics = diag, abs_z_min = 1.5, prob_max = 0.4, plot_type = "scatter", preset = "publication" ) ``` Interpretation: - Upper corners combine large residual mismatch with low model probability. - Repeated appearances of the same persons or levels are more informative than a single extreme point. Displacement focuses on level movement rather than individual responses. ```{r displacement} plot_displacement( fit, diagnostics = diag, anchored_only = FALSE, plot_type = "lollipop", preset = "publication" ) ``` Interpretation: - Large absolute displacement indicates stronger tension between observed data and current calibration. - For anchored runs, this is especially useful as an anchor-robustness screen. ### Strict marginal follow-up 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 optional local execution; final reporting should be refit with the package default or a higher quadrature setting. ```{r strict-marginal} 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_marginal_fit( diag_strict, top_n = 12, preset = "publication" ) ``` Interpretation: - Treat strict marginal plots as exploratory corroboration screens, not as standalone inferential tests. - Use the checklist rows to confirm that the current run actually supports the strict branch before routing figures into a report. - When pairwise follow-up is needed, continue with `plot_marginal_pairwise(diag_strict, preset = "publication")`. ## 3. Linking and coverage When the design may be incomplete or spread across subsets, inspect the coverage matrix before interpreting cross-subset contrasts. ```{r linking} sc <- subset_connectivity_report(fit, diagnostics = diag) plot(sc, type = "design_matrix", preset = "publication") ``` Interpretation: - Sparse rows or columns indicate weak subset coverage. - Facets with low overlap are weaker anchors for cross-subset comparisons. If you are working across administrations, follow up with anchor-drift plots: ```{r eval = FALSE} drift <- detect_anchor_drift(current_fit, baseline = baseline_anchors) plot_anchor_drift(drift, type = "heatmap", preset = "publication") ``` ## 4. Residual structure and interaction screens Residual PCA is a follow-up layer after the main fit screen. ```{r residual-pca} diag_pca <- diagnose_mfrm(fit, residual_pca = "both", pca_max_factors = 4) pca <- analyze_residual_pca(diag_pca, mode = "both") plot_residual_pca(pca, mode = "overall", plot_type = "scree", preset = "publication") ``` Interpretation: - Early components with noticeably larger eigenvalues deserve follow-up. - Scree review should usually be paired with loading review for the component of interest. For interaction screening, use the packaged bias example. ```{r bias} 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") plot_bias_interaction( bias, plot = "facet_profile", preset = "publication" ) ``` Interpretation: - Facet profiles are useful for seeing whether a small number of levels drives most flagged interaction cells. - Treat these plots as screening evidence; confirm with the corresponding tables and narrative reports. ## 5. Secondary visual layer The package ships a second-wave visual layer for teaching and diagnostic follow-up. These helpers are not default reporting figures; use them after the main screens above. - `plot_guttman_scalogram(fit, diagnostics)` renders a person x facet-level response matrix with an unexpected-response overlay, for teaching-oriented scalogram intuition and local triage. - `plot_residual_qq(fit, diagnostics)` plots a Normal Q-Q of person-level standardized residual aggregates as exploratory follow-up on residual tail behavior. - `plot_rater_trajectory(list(T1 = fit_a, T2 = fit_b))` 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_rater_agreement_heatmap(fit, 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_response_time_review()` for distribution and grouped timing plots. This is a descriptive QC layer, not a joint speed-accuracy model. - `plot_shrinkage_funnel(fit_eb, 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")`. ### Response-time QC context 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. ```{r response-time-review} 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_response_time_review(rt, type = "distribution", preset = "publication") plot_response_time_review(rt, type = "person", preset = "publication") ``` Interpretation: - Start with the distribution plot to see whether the rapid/slow thresholds are sensible for this administration. - Inspect person and facet summaries for concentrated rapid or slow rates rather than isolated events. - Keep timing flags separate from fit, bias, and validity claims unless the study design explicitly supports stronger speed-accuracy modeling. ### Small-N shrinkage with uncertainty 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. ```{r shrinkage-funnel} fit_eb <- apply_empirical_bayes_shrinkage(fit) shrink <- plot_shrinkage_funnel( fit_eb, 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_shrinkage_funnel( fit_eb, show_ci = TRUE, ci_level = 0.95, preset = "publication" ) ``` Interpretation: - Long raw-to-shrunken segments identify levels most affected by the partial-pooling prior. - Wide raw whiskers that narrow after pooling indicate estimation instability, not automatic rater-quality failure. - Report the shrinkage method and keep this display separate from bias, fit, or validity claims. ## Recommended sequence For a compact visual workflow: 1. `reporting_checklist()` when you want the package to route which figures are already supported. 2. `plot_qc_dashboard()` for one-page triage. 3. `plot_unexpected()`, `plot_displacement()`, `plot_marginal_fit()`, and `plot_interrater_agreement()` for local follow-up. 4. `plot(fit, type = "wright")` and `plot(fit, type = "pathway")` for targeting and scale interpretation. 5. `plot_residual_pca()`, `plot_bias_interaction()`, and `plot_information()` for deeper structural review. 6. `response_time_review()` and `plot_response_time_review()` when response-time metadata are available. 7. `plot_shrinkage_funnel(show_ci = TRUE)` when empirical-Bayes shrinkage was applied. 8. `plot_guttman_scalogram()`, `plot_residual_qq()`, `plot_rater_trajectory()`, and `plot_rater_agreement_heatmap()` as the teaching / drift / agreement-heatmap follow-up layer. ## Related help - `help("mfrmr_visual_diagnostics", package = "mfrmr")` - `help("mfrmr_workflow_methods", package = "mfrmr")` - `mfrmr_interval_guide("shrinkage")`