# Migrating from Facets to mfrmr

This vignette walks Facets users through the equivalent `mfrmr` workflow: preparing data, fitting an `RSM`/`PCM` many-facet Rasch-family model with Facets-compatible defaults, generating the diagnostic and reporting tables that the canonical Facets output stack provides, 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.

## 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:

    facets_feature_coverage()
    facets_feature_coverage("not_implemented")

Concept | Facets (Linacre 2026) | mfrmr
--- | --- | ---
Input | Specification file plus data file | `data.frame` in long format
Estimation | JMLE by default | `JML` (legacy default) or `MML` (recommended for new analyses)
Models | Rating-scale, partial-credit, polytomous step models | `RSM`, `PCM`, bounded `GPCM`
Output | Tables 0-30 plus graphic files | Returned R objects with `summary()` and `plot()` methods
Anchoring | `D=`, `A=` fields in the specification | `anchors` and `group_anchors` arguments to `fit_mfrm()`
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 file is the manifest | `build_mfrm_manifest()` plus `build_mfrm_replay_script()`

## A one-shot legacy-compatible call

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

    library(mfrmr)
    data("ej2021_study1", package = "mfrmr")

    run <- run_mfrm_facets(
      data = ej2021_study1,
      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:

    summary(run$fit)
    head(run$fair_average)

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:

    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).

### Anchoring

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

    D = 2
    A = 1, 0.0
        2, 0.5

becomes an `anchors` data frame:

    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

Facets Table 14 bias output between Rater and Criterion has a direct equivalent:

    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, use the Wright map route:

    plot(fit, type = "wright", preset = "publication", show_ci = TRUE)

`plot_wright_unified()` is the corresponding explicit helper when the Wright map is the main figure rather than one panel in a larger visual workflow. 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:

    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:

    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:

    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()`.

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.

    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:

    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 binding contract.

## Recommended next steps

After a Facets-equivalent fit is in hand, the canonical mfrmr reporting route 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 that Facets specifications cannot produce out of the box.

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