---
title: "Migrating from FACETS to mfrmr"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Migrating from FACETS to mfrmr}
  %\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 walks FACETS users through the closest `mfrmr` workflow:
preparing data, fitting an `RSM`/`PCM` many-facet Rasch-family model with
FACETS-oriented settings, generating related diagnostic and reporting tables,
and reviewing the output-contract
boundary between the two systems. Bounded `GPCM` can be fit in `mfrmr`, but its slope-aware
score semantics are intentionally outside the score-side FACETS output-contract
route.

The software reference target for this migration boundary is FACETS 64-bit
4.5.1 (July 2026). A cited manual may retain its published 4.5.0 edition;
software version and documentation edition are recorded separately. The
coverage described here is not an external numerical-parity result.

## Mental model

The two stacks share the same psychometric framework but differ in
operating model.

Before treating a legacy workflow as covered, inspect the public coverage
boundary:

```r
facets_feature_coverage()
facets_feature_coverage("not_implemented")
```

This matrix describes the availability of package-native output surfaces.
`implemented` does not by itself mean that the two programs use the same
estimand, conditioning, extreme-score rule, degrees of freedom, or numerical
contract.

| Concept | FACETS (Linacre 2026) | mfrmr |
|---|---|---|
| Input | Specification file plus data file | `data.frame` in long format |
| Estimation | JMLE by default | `MML` by default; `JML` is the closest estimation route for a JMLE-oriented comparison |
| Fit-statistic basis | Residuals at JMLE estimates | Residuals at EAP person measures under `MML` (shrunken toward the mean); refit with `method = "JML"` for a JMLE-style residual basis |
| Models | Multiple model statements, rating scales, partial credit, and other response families can coexist | One response-model family per fit: `RSM`, `PCM`, or bounded `GPCM` |
| Output | Tables 0-30 plus graphic files | Returned R objects with `summary()` and `plot()` methods |
| Anchoring | Element/group anchors, rating-scale calibration, and reusable starting values | Element and group anchors; no general threshold/scale anchors or fixed-calibration starting-value bundle |
| Repeated cells | Multiple observations may be represented within a design cell | Exact Person-by-facet duplicates are retained but force Data review; distinguish legitimate repeats with an event/occasion facet |
| Bias / interaction | Table 14 | `estimate_bias()` and `bias_interaction_report()` |
| Wright map / variable map | Graphic variable-map output | `plot(fit, type = "wright")` and `plot_wright_unified()` |
| Fair average | Table 7 fair-M average | `fair_average_table()` |
| Reproducibility | Specification, input data, FACETS version, and recorded run environment/settings | `build_mfrm_manifest()` plus `build_mfrm_replay_script()` |

## A one-shot legacy-compatible call

If the goal is to translate a FACETS-style script with minimal R-side
plumbing, use `run_mfrm_facets()` (alias `mfrmRFacets()`):

```{r facets-mode}
library(mfrmr)
data("mfrmr_example_operational", package = "mfrmr")

run <- run_mfrm_facets(
  data = mfrmr_example_operational,
  person = "Person",
  facets = c("Rater", "Criterion"),
  score = "Score",
  model = "RSM",
  method = "JML"
)

names(run)
```

The wrapper returns the same `fit_mfrm()` and `diagnose_mfrm()` objects that
a step-by-step pipeline produces, plus the iteration log, fair-average
table, and rating-scale table:

```{r facets-mode-summary}
jml_status <- summary(run$fit, profile = "fit", detail = "brief")
jml_status$overview[, c(
  "Model", "Method", "Converged", "InferenceReady",
  "ConvergenceSeverity"
)]
jml_status$readiness
head(run$fair_average)
```

`method = "JML"` is shown here for a JMLE-oriented migration comparison.
Do not infer readiness from `Converged` alone: require `InferenceReady = TRUE`
for the numerical gate and review the terminal-gradient guidance when severity
is `"review"` or `"fail"`. Numerical readiness does not override a Data,
Design, or Stability hold; use the readiness table before interpreting or
reporting the fit. For new analysis scripts, prefer `fit_mfrm(method = "MML")`
directly. MML integrates over the person distribution under an N(0, 1) prior
and exposes per-person posterior SEs that JML cannot produce.

## Translating the specification file

The mapping below covers the most common FACETS specification keywords.

### FACETS and labels

```
Facets = 3
Models = ?,?,?,R5
Labels =
  1, Examinee
    1 = P01
    ...
  2, Rater
    1 = R1
    ...
  3, Criterion
    1 = Content
    ...
```

translates to:

```r
fit_mfrm(
  data = examinee_long,
  person = "Examinee",
  facets = c("Rater", "Criterion"),
  score = "Score",
  rating_min = 1,
  rating_max = 5,
  model = "RSM"
)
```

`Models = ?,?,?,R5` becomes `model = "RSM"` and the `R5` rating-scale
declaration becomes `rating_min = 1, rating_max = 5`. For a partial-credit
specification, pass `model = "PCM"` and identify the facet that carries the
step thresholds with `step_facet = "Rater"` (or the appropriate facet
name). The declaration preserves the intended category map, but it is not a
threshold anchor. A missing boundary category remains review evidence for the
separate element-boundary contract. A missing internal category in a
polytomous fitted ladder creates an unsupported adjacent-step contrast and
stops fitting before optimization. Review or revise the data support rather
than copying FACETS category-dropping behavior implicitly.

### Anchoring

A FACETS `D = 2, A =` block:

```
D = 2
A = 1, 0.0
    2, 0.5
```

becomes an `anchors` data frame:

```r
anchors <- data.frame(
  facet = "Rater",
  level = c("R1", "R2"),
  estimate = c(0.0, 0.5),
  stringsAsFactors = FALSE
)
fit <- fit_mfrm(..., anchors = anchors)
```

`review_mfrm_anchors()` validates and reports on the anchor block before the
fit runs, surfacing connectivity, overlap, and minimum-sample issues.

### Bias and interaction

For FACETS Table 14 bias output between Rater and Criterion, the closest mfrmr
screening route is:

```r
diag <- diagnose_mfrm(fit)
bias <- estimate_bias(fit, diag,
                      facet_a = "Rater", facet_b = "Criterion")
summary(bias)
```

`estimate_all_bias()` enumerates every non-person facet pair in one call.

### Wright map / variable map

For a shared-logit visual display of persons, facet levels, and step
thresholds, first create the FACETS-organized summary and retain its result
object:

```r
review <- summary(fit, profile = "facets", detail = "brief")
review$decision
res <- review$results

# Primary final-scale figure: all locations and available facet uncertainty.
plot(res, type = "wright", renderer = "native", show_ci = TRUE,
     top_n = Inf, preset = "publication")
```

Read `review$decision` before using the FACETS-organized tables. It reports the
same stored mfrmr readiness decision shown by `print(fit)`; the `facets`
profile changes the organization of the review, not the estimator and not the
evidence threshold. `FormalInference = "No"` therefore remains a stop on
substantive reporting even when a familiar FACETS-style table or plot is
available.

`plot_wright_unified()` is the corresponding explicit helper when the Wright
map is the main figure. For readers who expect the FACETS Table 6-style
asterisk ruler and horizontal, rubric-labelled category transitions, define
one label for every retained original score:

```r
rubric_labels <- setNames(
  your_rubric_labels,
  fit$prep$score_map$OriginalScore
)
plot(res, type = "wright", renderer = "facets", show_ci = FALSE,
     category_labels = rubric_labels, preset = "publication")
```

`show_ci = FALSE` is the closest FACETS-style visual grammar. Setting
`show_ci = TRUE` deliberately creates a hybrid display: the ruler is
FACETS-style, but the intervals are mfrmr uncertainty estimates. Neither
renderer implies that FACETS performed the estimation or that the two
programs are numerically equivalent.

For the Bond-and-Fox-style follow-up requested by many FACETS users, put
Infit on the horizontal axis and the measure on the vertical axis. Person
rows remain opt-in:

```r
plot(res, type = "fit_pathway", fit_stat = "Infit",
     include_person = TRUE, top_n_person = 12,
     person_labels = "none", facet_labels = "flagged")
```

Use `draw = FALSE` or `plot_data(fit, type = "wright")` when you need the
underlying coordinates for a custom `ggplot2`, base-R, or Quarto graphic.

### Fit df and ZSTD review

FACETS users often compare Infit/Outfit MnSq together with ZStd columns.
In `mfrmr`, treat MnSq as the primary fit statistic and use the df/ZSTD
columns to explain how the same MnSq values were standardized. The direct
review path is:

```r
diag <- diagnose_mfrm(fit, residual_pca = "none", fit_df_method = "both")
fm <- fit_measures_table(fit, diagnostics = diag,
                         facet = "Rater", fit_df_method = "both")

fm$facets_table
fm$df_sensitive
plot(fm, type = "df_sensitivity")
```

`df_sensitivity` reports the engine-vs-FACETS-style df comparison row by row;
`df_sensitive` keeps only rows where the df convention changes the |ZSTD| flag
or materially changes the ZSTD interpretation. The same status taxonomy is
used by `facets_fit_review()`, so a table-oriented review and an external
FACETS comparison use the same language.

### Group anchoring and DFF

FACETS `D = ..., G =` group-anchor blocks for differential facet
functioning translate to the `group_anchors` argument and the
`analyze_dff()` follow-up:

```r
group_anchors <- data.frame(
  facet = "Criterion",
  level = "Content",
  group = c("Native", "Non-native"),
  estimate = c(0.0, 0.0),
  stringsAsFactors = FALSE
)
fit_g <- fit_mfrm(..., group_anchors = group_anchors)
dff <- analyze_dff(fit_g, diag, facet = "Criterion",
                   group = "FirstLanguage", method = "refit")
```

## Reviewing output contracts and fit tables

When migrating an existing study, `facets_output_contract_review()` checks
whether the package-generated report components satisfy the FACETS-style
output contract encoded in the package:

```r
contract_review <- facets_output_contract_review(
  fit,
  diagnostics = diag,
  branch = "facets"
)
summary(contract_review)
contract_review$missing_preview
contract_review$metric_checks
```

The resulting object reviews column coverage and package-native metric checks.
It is not a claim that `mfrmr` has reproduced FACETS estimates numerically.
For external numerical comparison, use an exported FACETS fit table and
`facets_fit_review()`.

When that comparison involves an `MML` fit, remember that mfrmr evaluates
residual-based fit statistics at shrunken EAP person measures while FACETS
uses JMLE estimates, so MnSq differences can reflect the residual basis
rather than a fit-computation difference; refit with `method = "JML"`
before attributing such gaps. See `facets_fit_df_guide()` for this
boundary and for the separate df/ZSTD standardization conventions.

If you already have a FACETS fit table on disk, read it first and then run
the fit review. This does not run FACETS; it consumes an exported or otherwise
harmonized table.

```r
facets_fit <- read_facets_fit_table(
  "score.2.txt",
  facet_map = c("1" = "Person", "2" = "Rater", "3" = "Criterion")
)
review <- facets_fit_review(
  fit,
  diagnostics = diag,
  facets_fit = facets_fit,
  external_zstd_tolerance = 0.05
)

review$df_sensitivity
review$df_sensitive
review$external_table_quality
review$external_comparison
plot(review, type = "df_sensitivity")
```

Use `external_comparison` for the supplied FACETS table and `df_sensitivity`
for the engine-vs-FACETS-style df convention check. This separation keeps
external numerical differences distinct from ZSTD differences caused by df
standardization. `external_table_quality` is the first place to look if the
FACETS export only contains ZStd and T.Count columns, or if duplicate
`Facet` x `Level` rows were supplied.

## Producing FACETS-style output files

For traceability or downstream tools that expect FACETS output files,
`facets_output_file_bundle()` writes a parallel set of fixed-width or CSV
exports:

```r
files <- facets_output_file_bundle(
  fit,
  diagnostics = diag,
  out_dir = tempdir(),
  include = c("graph", "score")
)
```

For RSM and PCM the score-side helpers are available. Under bounded `GPCM`
the score-side bundle is intentionally restricted; see
`?gpcm_capability_matrix` and the `mfrmr-gpcm-scope` vignette for the
documented limitation.

## Recommended next steps

After a FACETS-oriented package-native fit is in hand, the recommended mfrmr
reporting workflow extends the analysis with:

- `review_mfrm_anchors()` before anchored fitting, and
  `detect_anchor_drift()` / `plot_anchor_drift()` when common elements define
  a cross-form or cross-wave link.
- `diagnose_mfrm(diagnostic_mode = "both")` for the strict marginal screen
  alongside the residual stack.
- `rating_scale_table()`, `category_structure_report()`, and
  `category_curves_report()` for category-functioning evidence.
- `fair_average_table()` when FACETS Table 12-style fair-average review is
  needed.
- `plot(fit, type = "wright")` or `plot_wright_unified()` for a variable-map
  view of targeting and threshold placement.
- `estimate_bias()`, `bias_interaction_report()`, and
  `bias_pairwise_report()` when FACETS Table 14-style local interaction
  screening is substantively relevant.
- `reporting_checklist()` for a manuscript-readiness summary.
- `build_apa_outputs()` for Method and Results paragraphs and APA tables.
- `build_mfrm_manifest()` and `build_mfrm_replay_script()` for the
  reproducibility bundle alongside the FACETS-style handoff
  out of the box.

The `mfrmr-workflow` vignette covers the full sequence end to end; the
`mfrmr-reporting-and-apa` vignette focuses on manuscript preparation; the
`mfrmr-linking-and-dff` vignette covers anchoring, drift, and DFF in
detail.
