# mfrmr Linking and DFF

This vignette covers the package-native route for:

- checking whether a design is connected enough for a common scale
- exporting anchor candidates from an existing fit
- screening differential facet functioning (DFF)
- deciding when subgroup contrasts are descriptive versus formally comparable

For a broader workflow guide, see `vignette("mfrmr-workflow", package = "mfrmr")`. For the shorter help-page map, see `help("mfrmr_linking_and_dff", package = "mfrmr")`.

## Minimal setup

    library(mfrmr)

    bias_df <- load_mfrmr_data("example_bias")

    # The vignette uses compact quadrature so optional local execution stays fast.
    # For final DFF or linking evidence, refit with the package default or a higher
    # quadrature setting and record that setting in the analysis log.
    fit <- fit_mfrm(
      bias_df,
      person = "Person",
      facets = c("Rater", "Criterion"),
      score = "Score",
      method = "MML",
      model = "RSM",
      quad_points = 31
    )

    diag <- diagnose_mfrm(fit, residual_pca = "none")

## 1. Check connectedness first

Use `subset_connectivity_report()` before interpreting subgroup or cross-form contrasts.

    sc <- subset_connectivity_report(fit, diagnostics = diag)

    sc$summary[, c("Subset", "Observations", "ObservationPercent")]
    plot(sc, type = "design_matrix", preset = "publication")

Interpretation:

- Sparse rows or columns indicate weaker design coverage.
- Weak coverage should lower confidence in subgroup comparisons.

## 2. Export anchor candidates

`make_anchor_table()` is the shortest route when you need reusable anchor elements from an existing calibration.

    anchors <- make_anchor_table(fit, facets = "Criterion")
    head(anchors)

Use `review_mfrm_anchors()` when you want a stricter review of anchor quality.

## 3. Residual DFF as a screening layer

Residual DFF is the fast screening route. It is useful for triage, but it is not automatically a logit-scale inferential contrast.

    dff_resid <- analyze_dff(
      fit,
      diag,
      facet = "Criterion",
      group = "Group",
      data = bias_df,
      method = "residual"
    )

    dff_resid$summary
    head(
      dff_resid$dif_table[, c("Level", "Group1", "Group2", "Classification", "ClassificationSystem")],
      8
    )
    plot_dif_heatmap(dff_resid)

Interpretation:

- Treat `residual` output as screening evidence.
- Check `ClassificationSystem` to see how the current residual screen was labeled.
- Reserve `ScaleLinkStatus` and `ContrastComparable` for refit-based contrasts.

## 4. Refit DFF when subgroup comparisons are defensible

The refit route can support logit-scale contrasts only when subgroup linking is adequate and the precision layer supports it.

    dff_refit <- analyze_dff(
      fit,
      diag,
      facet = "Criterion",
      group = "Group",
      data = bias_df,
      method = "refit"
    )

    dff_refit$summary
    head(
      dff_refit$dif_table[, c("Level", "Group1", "Group2", "Classification", "ContrastComparable")],
      8
    )

## 5. Cell-level follow-up

If the level-wise screen points to a specific facet, follow up with the interaction table and narrative report.

    dit <- dif_interaction_table(
      fit,
      diag,
      facet = "Criterion",
      group = "Group",
      data = bias_df
    )

    head(dit$table)

    dr <- dif_report(dff_resid)
    cat(dr$narrative)

## 6. Model-estimated facet interactions

Residual bias and DFF tools screen for unusual cells after fitting the additive model. When the interaction hypothesis is specified in advance, use `facet_interactions` to estimate the named two-way non-person facet interaction in the model likelihood.

    fit_add <- fit_mfrm(
      bias_df,
      person = "Person",
      facets = c("Rater", "Criterion"),
      score = "Score",
      method = "MML",
      model = "RSM",
      quad_points = 7
    )

    fit_interaction <- fit_mfrm(
      bias_df,
      person = "Person",
      facets = c("Rater", "Criterion"),
      score = "Score",
      method = "MML",
      model = "RSM",
      facet_interactions = "Rater:Criterion",
      quad_points = 31
    )

    interaction_effect_table(fit_interaction)
    compare_mfrm(Additive = fit_add, Interaction = fit_interaction, nested = TRUE)

Interpretation:

- Name the facet pair explicitly before fitting.
- Treat the interaction estimates as fixed-effect deviations from the additive MFRM under zero marginal-sum constraints.
- Inspect sparse interaction cells before reporting substantive claims.
- Keep this route separate from residual screening with `estimate_bias()`.

## 7. Multi-wave anchor review

When you work across administrations, the route usually moves from a declared common-anchor design to anchored fitting and then to drift review. The block below is schematic: `wave1` and `wave2` must be administrations for which the listed facet levels have documented cross-wave identity and unchanged meaning.

Do not substitute `ej2021_study1` and `ej2021_study2` here. They are independent legacy synthetic studies; reused raw labels do not identify common persons, raters, or anchors and do not link their scales.

    declared_common_facets <- c("Criterion")

    fit1 <- fit_mfrm(
      wave1, "Person", c("Rater", "Criterion"), "Score",
      method = "MML"
    )

    anchored <- anchor_to_baseline(
      wave2,
      fit1,
      person = "Person",
      facets = c("Rater", "Criterion"),
      score = "Score",
      anchor_facets = declared_common_facets
    )

    fit2 <- fit_mfrm(
      wave2, "Person", c("Rater", "Criterion"), "Score",
      method = "MML"
    )
    drift <- detect_anchor_drift(list(Wave1 = fit1, Wave2 = fit2))
    plot_anchor_drift(drift, type = "drift", preset = "publication")

## Recommended sequence

For a compact linking route:

1. `fit_mfrm()`
2. `diagnose_mfrm()`
3. `subset_connectivity_report()`
4. `make_anchor_table()` or `review_mfrm_anchors()`
5. `analyze_dff()`
6. `dif_report()` and `plot_dif_heatmap()`
7. `interaction_effect_table()` after a confirmatory `facet_interactions` fit
8. `anchor_to_baseline()` / `detect_anchor_drift()` when working across waves

## Related help

- `help("mfrmr_linking_and_dff", package = "mfrmr")`
- `help("subset_connectivity_report", package = "mfrmr")`
- `help("analyze_dff", package = "mfrmr")`
- `help("interaction_effect_table", package = "mfrmr")`
- `help("detect_anchor_drift", package = "mfrmr")`