Migrating from FACETS to mfrmr

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. GPCM can be fit in mfrmr, but its slope-aware score semantics are intentionally outside the score-side FACETS output-contract route.

The local numerical comparison uses FACETS 64-bit 4.5.0. The online update history lists 4.5.3 (October 2026); a current web manual is not a version-pinned executable specification. The 4.5.1 history says the gtheory interface was replaced while the linked help still names gtheory. Keep the executable, manual edition and statistical decomposition distinct. The bounded executed comparisons below do not certify all FACETS options or versions.

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

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 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 13 cell effects; Table 14 pairwise comparisons estimate_bias() and bias_interaction_report(); conditional screens with the differences below
Wright map / variable map Graphic variable-map output plot(fit, type = "wright") and plot()
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()

Visualize an existing FACETS analysis

read_facets_results() reads supported Scorefile exports without refitting. The included small synthetic example has one row per facet element, with fixed anchors and extreme scores. Inspect summary() for available measures and fit statistics; these counts do not assess the quality of the source analysis.

library(mfrmr)
score_directory <- system.file("extdata", "facets-example", package = "mfrmr")
facets_result <- read_facets_results(score_directory,
  facet_map = c("Person", "Rater", "Task"), source_version = "4.5.0")
summary(facets_result)
facets_result$table[, c("Facet", "ElementID", "Label", "Estimate", "SourceStatus")]
plot(facets_result, type = "measure", facets = "Person")

Squares identify FACETS Status=1, which can denote a direct anchor or a member of a group anchor. The import retains SourceStatus = "anchored", but this does not establish that the individual’s measure was fixed. A group member may be estimated around the constrained mean. GroupRaw can also be a reporting group; its value, the number of imported group members and a reported SE cannot identify the anchor subtype. Inspect the original Labels= for the anchor mode and Table 7 for row annotations. A Table 7 G annotation alone need not distinguish the G and X input modes. summary() therefore calls this count AnchorFlagged, replacing Anchored from earlier candidate versions. For saved imports, call plot() again to refresh the legend and notes; the source values and availability remain intact.

Extreme members of G/X groups can carry Status -2/-3 instead of 1, so AnchorFlagged is not a count of every member of an anchored group. Their triangles identify the source’s extreme-score status. A finite reported location does not establish which adjustment produced it: G and X can report different locations. The import preserves these locations and marginal SEs, while keeping their Infit/Outfit placeholders unavailable for fit plots.

type = "fit" shows measure versus Infit or Outfit; type = "infit_outfit" compares the two mean squares. Inspect the plotted rows and unavailable reasons before interpreting missing points:

fit_points <- plot_data(facets_result, component = "plot_data",
  type = "infit_outfit", facets = "Person")
fit_points[, c("Label", "Infit", "Outfit", "Available", "Reason")]
plot_data(facets_result, component = "notes")

For your own analysis, supply named comma/tab/semicolon files, a vector of per-facet exports, or a directory of score.N.txt files. Use the actual source encoding (for example encoding = "CP932" for an applicable Japanese Windows export). For FACETS text output, the following specifications retain the column headings and quoted labels needed by the reader:

Scorefile = score.txt
Heading lines = Yes
QM = Double
CSV = Semicolons

CSV = Commas or CSV = Tabs selects the other supported separators. Keep the generated facet heading line; the Scorefile reader finds the column header. Check that the files exist and that their labels are intact before importing. A successful process exit alone does not establish a successful export. The reader’s encoding converts an existing file; it neither changes FACETS input settings nor restores labels already lost in the external output.

You can keep FACETS’ exported IDs and assign local display names in R. The map uses the imported Facet and ElementID, not row positions or source labels. Store IDs as text to retain any leading zeros. Here only two Persons are renamed; other elements retain their original labels:

display_names <- data.frame(Facet = "Person", ElementID = c("1", "2"),
  DisplayLabel = c("受検者 甲", "受検者 乙"))
labelled <- plot(facets_result, facets = "Person",
  label_map = display_names, draw = FALSE)
labelled$data$plot_data[, c("ElementID", "Label", "DisplayLabel")]

The original result and its labels remain unchanged; labelled$data$label_map retains the supplied correspondence. Unknown or repeated keys are errors. Repeated display names are distinguished by element key. Only measure plots draw element labels; other element plots keep the names in their returned data. This map does not apply to Graphfile category labels.

Japanese text also needs a graphics device and font with the required glyphs. With Cairo support and Meiryo installed on Windows, save the plot as follows; choose an available Japanese font on another system. Check the saved file for missing glyphs. This does not require Japanese input to FACETS or change its results. See R’s Cairo device documentation.

png("facets-japanese.png", width = 7, height = 5, units = "in", res = 200,
  type = "cairo", family = "Meiryo")
plot(facets_result, facets = "Person", label_map = display_names)
dev.off()

cairo_pdf("facets-japanese.pdf", width = 7, height = 5, family = "Meiryo")
plot(facets_result, facets = "Person", label_map = display_names)
dev.off()

Fixed-width imports require the supported ASCII all-field layout. Element IDs distinguish duplicate labels. Source signs and reported values are preserved; no automatic recentering or sign reversal takes place. A point-only Wright map (type = "wright") additionally requires common_scale = TRUE at import after checking compatible units and origins in the original FACETS specification.

Extreme-score rows are flagged; their status does not identify the adjustment rule used by FACETS. Their fit values can be placeholders and are unavailable in fit plots. Source SEs are retained in the table; no confidence intervals are constructed. These descriptive results do not provide new-person scoring, threshold reconstruction or validation of the FACETS analysis. saveRDS(facets_result, ...) preserves the complete result; as.data.frame(facets_result) exports its element table. See ?read_facets_results and ?plot.mfrm_facets_results for the supported scope.

For category-probability and expected-score curves, export a FACETS Graphfile with headings. This separate synthetic PCM example contains one coordinate row per scale and reported measure; it is a different analysis from the binary score example above.

graph_path <- system.file("extdata", "facets-graph-example", "graph.txt",
  package = "mfrmr")
facets_curves <- read_facets_results(graph_file = graph_path,
  source_version = "4.5.0")
summary(facets_curves, component = "curves")
plot(facets_curves, type = "ccc", scales = 1)

Use type = "expected" for the exported expected-score curve. Inspect the actual coordinates and source-consistency checks with:

expected_points <- plot_data(facets_curves, "plot_data",
  type = "expected", scales = 1)
head(expected_points[, c("Scale", "Measure", "Value", "Available", "Reason")])
head(facets_curves$graph$diagnostics)

The reader preserves Graphfile scale numbers and category codes. A scale number identifies a Table 8 subtable; it does not automatically identify an item. Even binary exports may use category codes 1/2 instead of the original 0/1 responses. Check the source specification before interpreting expected scores. The graph Measure axis is separate from the element measure declarations; importing both files does not align them. When both exports belong to your analysis, supply file = ... and graph_file = ... in one call, after checking that correspondence.

Missing or invalid coordinates remain in as.data.frame(facets_curves, component = "curves") and break the plotted lines. Probability sums are checked using exported decimal precision, without renormalizing. These checks do not certify model fit; connecting segments do not calculate new model predictions.

Why results and decisions can differ

Match the statistical question before comparing numbers. A FACETS-style table name does not guarantee the same calculation. For an existing FACETS study, first align the response model, retained categories, orientation, anchors and centering, observations and replication weights, and extreme-score treatment. Compare unrounded logits before user scaling. Then compare response probabilities, mean squares, df and standardization separately. FACETS uses score-residual and measure-change convergence criteria; an optimizer’s success code alone does not establish agreement with them.

The corrected candidate has a bounded executed comparison against FACETS 4.5.0: complete binary ordinary JML data with unit weights and matched signs. Locations, SEs, MnSq, PTMEA and WHEXACT YES/NO agree within external report precision; bias estimates, baseline expected scores and the zero-bias summary also agree. This is not a drop-in equivalence claim for other models or options.

Quantity Current definition Consequence for interpretation
Fit ZSTD Diagnostics default to FACETS fourth-moment df. whexact = FALSE is YES and TRUE is NO; both use the cube root. Use fit_df_method = "engine" and whexact = "legacy_linear" to reproduce the former engine/TRUE convention. "both" exposes df sensitivity. Cutoff decisions may change.
Point-measure correlation PTMEA now uses the oriented measure sum for each response, including Person rows. Old Person-only correlations must be recomputed.
Displacement Updates now include facet orientation and the score derivative. Negative-facet released estimates now move in the correct coordinate; this is still a conditional one-step diagnostic.
Facet chi-square The statistic and df use the same finite estimates and positive finite SEs. Excluded levels are reported; a missing SE cannot silently contribute df.
Expected point-measure correlation Ordinary additive RSM/PCM JML exposes PTMEAExpected and PTMEAExpectedAsymptotic. The former includes Linacre’s published (N-2)/N variance correction with frequency count N. Matched PCM output agrees within print precision. This is a conditional reporting approximation; below count two or with unbounded/incomplete calibration, the corrected reference remains unavailable. Recreate diagnostics to obtain it.
PCM fair averages Expectations under administered scales are averaged using response weights. Averaging thresholds first can give different scores and is no longer used.
Bias cell tail area Continuous weighted count minus 1 is retained; DFDisplay separately rounds positive df for display comparison. FACETS 4.5.0 reports count 8 and df 7 in the executed example, although Table 13 says count minus 2. Checked fractional-count native tails match an integer-df finite sum extended outside its valid domain and can change a 0.05 screen. mfrmr retains the continuous Student reference and records probability availability; nonpositive df withhold the tail. These are conditional screens, not calibrated fairness tests.
Bias expected score Expctd Score and Obs-Exp Average now refer to the main analysis. Separate after-bias columns show how much of that departure the shift explains.
Bias summary chi-square Tests the cells against zero with df equal to the cell count. A common nonzero bias no longer disappears through centering.
Pairwise bias Uses target + bias/TargetSign, conditional cell SEs and modified Rasch-Welch df rounded down, matching FACETS 4.5.0 unequal-count results. df_method = "classical" retains the earlier continuous Welch reference. Missing references withhold comparisons; neither mode includes calibration uncertainty.
Unexpected responses Defaults to abs(z) >= 3, rule = "residual". The older probability OR rule is opt-in; rankings now treat tied modes symmetrically.

Definitions are given in the official WHEXACT, point-measure, Table 7, fair-average, and Table 13 documentation. The count-minus-1 observation above describes the executed version and example, not a correction to the external manual or a claim about every FACETS version.

Recompute diagnose_mfrm() from a saved fit, then recreate estimate_bias() and reports. Corrected native diagnostics and bias results carry calculation_version = 2; do not add that field manually to old values. These diagnostic changes do not require refitting the calibration.

For a negatively oriented rater, the model predictor contains -severity + bias, so its context-specific severity is severity - bias. The Table 14 examples make this direction explicit. Reversing the contrast leaves a two-sided tail area unchanged but reverses the substantive feedback. Pairwise df also needs its own version specification: the current t-statistics manual describes a 2025 modification alongside classical Welch. The executed FACETS 4.5.0 unequal-count comparison confirms the modified formula and integer truncation for the tail area. DFUnrounded and DFMethod retain that choice; df_method = "classical" reproduces the earlier reference.

Corrected pairwise tables carry CalculationVersion = 3. Recreate build_fixed_reports() and bias_pairwise_report() from the current bias bundle. First recreate old bias bundles from the saved fit and current diagnostics; no main calibration refit is needed. Historical report values are not automatically corrected by reloading them. Local and contrast SEs describe conditional cell uncertainty; calibration uncertainty and cross-cell covariance are not propagated. The earlier claim that these contrasts were generally conservative was incorrect. Check ScreenUnavailable in the pairwise summary before reading Flagged; the summary covers the full family even when top_n limits the table.

The Table 4 rule and its default threshold now match the native residual-only default. To retain the exploratory probability screen, request abs_z_min = 2, prob_max = .30, rule = "either". A fixed .30 cutoff has no universal interpretation: five equally likely categories each have probability .2, so it flags every response. For tied modes, MostLikely is missing, MostLikelyCount records the tie and CategoryGap uses the nearest mode. This ranking cannot decide which rater is unfair or which ratings to remove.

Category reports now distinguish the observed generating-measure average (AvgMeasure), model-expected generating-measure average (ExpectedMeasure), Person-only mean and expected response score. Category residual MnSq is descriptive: conditioning on the observed category removes the universal unit baseline, so category ZSTD and its flags are unavailable. This does not turn a missing fit flag into evidence that the category functions well.

In category_structure_report(), MedianThreshold now means a cumulative 50% boundary; AndrichThreshold retains the adjacent-category step. Recreate saved structure reports. Half-score crossings outside theta_range stay missing, and modal peaks describe only the requested grid. The random chi-square likewise cannot establish normality: with equal SEs and positive adjusted variance it is fixed by the number of eligible elements, regardless of shape. Chi-square summaries retain unavailable flags and use the full family before truncation.

For external files, fixed-field import requires validated all-field headings and element number after the statistics; other layouts should use named delimited columns. Native score exports contain observations, not the elements in a FACETS Scorefile. Native Tables 5 and 11 omit parts of FACETS’ response moments and variance reports. A single co-observation graph component also does not prove identification; inspect the fitted design audit. The historical conditional_probability plotting alias is ordinary P(k), not FACETS’ adjacent-conditional probability; use ccc to name the native quantity clearly.

Match denominators as well as cutoffs. FACETS Totalscore defaults to No, while fair_average_table() defaults to totalscore = TRUE. Native raw TotalScore/TotalCount and weighted WeightdScore/WeightdCount are distinct; ObservedAverage uses the weighted values. Scores 0 and 2 with weights 1 and 3 have raw total/count 2/2 but weighted total/count 6/4 and average 1.5. Keeping both counts is useful for describing the data, provided the comparison uses the same replication semantics and extreme-row inclusion. Weights do not by themselves establish the independence or sampling uncertainty of repeated ratings.

When a native definition has a useful purpose

Decisions near a cutoff

Record the estimand, method, scale, reference population, eligibility, statistic and exact comparison rule with a decision. Read both the magnitude of the departure and its uncertainty. A fit flag or a bias-screening tail area alone does not identify an unfair rater or justify deleting ratings.

Use unrounded values for an available calculation. A FACETS token such as 2.00 at a ZSTD cutoff cannot reveal whether the unreported value was below or above 2; retain a boundary-indeterminate comparison. Do not choose between YES/NO, df conventions, priors or category recoding after seeing which one crosses a desired cutoff. A change of convention that changes a decision requires a stated rationale and sensitivity report, even if both calculations are internally valid. Missing uncertainty is not evidence that a criterion has been met.

For pass/fail work, keep the operational cut rule separate from a model’s expected score or fair average. classification_table() describes its supplied calibrated rating plan and cut rules; posterior scoring answers a different conditional question. Neither automatically validates the cuts, future rating plan or substantive consequences.

Ordinary JML, corrected JML and FACETS

FACETS’ estimation documentation states that it does not correct JMLE estimation bias. Accordingly, ordinary method = "JML" under matched RSM/PCM models, anchors and origin constraints is the relevant mfrmr comparison. Equal fitted values establish neither unbiased estimation nor calibrated intervals. More Persons alone need not remove the incidental-parameter bias when each Person still supplies few ratings.

These operations have different targets and should be recorded separately:

Operation What it changes Relationship to mfrmr
FACETS ordinary JMLE Solves the joint estimation equations. Compare with ordinary RSM/PCM JML under aligned model and numerical controls.
FACETS Xtreme Supplies finite extrapolations for extreme totals. xtreme_method = "facets" implements the documented reporting convention within its supported scope; it is not a general estimation-bias correction.
mfrmr jml_correction_order Changes the profiled structural score equation. An experimental GPCM method requiring the same facet to own steps and slopes; it does not reproduce FACETS JMLE.

For the corrected route, fit_mfrm(model = "GPCM", method = "JML", jml_correction_order = ...) solves the adjusted equation described in ?fit_mfrm. This is an existing implementation, not an RSM/PCM correction switch. It currently excludes anchors and supplies local-root RootSE, not validated structural-truth confidence intervals. Its fixed/random-roster covariance describes repeated independent Person cohorts; the common-plan delta procedure instead holds each Person’s ability fixed across repeated responses. Passing that RootSE into the common-plan interval calculation would change both the estimator and the sampling target. Corrected JML is therefore not admitted by confint.mfrm_fair_average().

The benefit of retaining both estimators is that external compatibility and a possible reduction in incidental-parameter bias can be studied separately. The cost is a distinct mathematical, numerical and inference contract: correction order, remaining bias, variance and solver stability all require evaluation. FACETS agreement cannot select the correction order or qualify its uncertainty. Keep ordinary JML as the comparison/default route and select corrected JML explicitly within its documented scope. FACETS’ separate Bias interaction analysis also should not be confused with correction of an estimator’s bias.

Using TAM JML as a third comparison

TAM’s tam.jml() helps check the common RSM/PCM likelihood, but its default corrections differ from ordinary mfrmr JML. Match category support, cumulative difficulties, anchors and location constraints first. The TAM documentation describes its separate adjustment and bias arguments.

Comparison What to align or retain
Ordinary interior JML Use bias = FALSE and adj = 0 for the TAM comparison. This requires finite interior solutions; it is not a recipe for extreme totals.
Category support If a Rater-Task column lacks the highest allowed category, inspect returned A and B. With custom full-category A, supply intended category scores in B; a generated score matrix can define a different model.
Classical correction The checked TAM bias = TRUE multiplies free item coefficients by (I-1)/I, using response-column count. Its returned Person estimates and SEs are not all recomputed at the corrected coefficients. This is distinct from mfrmr’s profile-score adjustment selected by jml_correction_order.
Extreme totals TAM adj modifies calibration scores. mfrmr’s explicit xtreme_method = "facets" conditions on saved calibration and retains infinite primary estimates. Matched extrapolations do not establish matched calibration procedures.
Person uncertainty Compare TAM theta with MLEs and retain WLE separately. errorWLE is not the MLE’s SE. Conditional information, joint sampling uncertainty and coverage are separate questions.
Fit statistics tam.jml.fit(trim_val = Inf) exposes untrimmed residual fit; its default trims large squared residuals. Unit-weight untrimmed fit has a matched fourth-moment transform, without certifying every FACETS reporting field.
Weights TAM normalizes Person pweights; these are not arbitrary response replication weights. With missingness and unequal weights, its checked item Outfit denominator also differs from normalization by observed weight sum.
Anchors Match the origin constraints as well as fixed values. FACETS ignores centering on anchored facets; ordinary mfrmr JML offers centering_scope = "anchor_origin" for direct and group anchors. This does not extend the executed TAM group-anchor comparison scope. The checked TAM version routes Person anchors through version 1; an all-item-fixed case with a Person anchor fails.

Executed TAM 4.3-25 comparisons support close ordinary-JML agreement for matched complete/sparse RSM/PCM designs and supported partial Person anchors. They do not establish general coverage, every FACETS anchoring convention, or a new JML import route: import_tam_fit() remains MML-only. Retain unavailable uncertainty instead of substituting WLE SEs or conditional information for it.

Inspect returned anchors too. In the checked TAM 4.3-25 version-1 path, theta.fixed with xsi.fixed = NULL still recenters every Person after an update, moving a supplied Person anchor. A free-intercept probe did not retain the anchor and reached its iteration limit. An empty fixed-item matrix prevented that recentering in this version, but the probe still did not reach the required score-equation accuracy within the same limit. This is a version-specific diagnostic observation, not a general workaround recommendation. The matching reference instead uses an independently fitted unanchored TAM solution translated to the specified Person origin, preserving every response probability. Keep that algebraic comparison separate from a successful direct anchored TAM execution.

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()):

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:

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-readiness criteria 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. Choose the estimator for the intended question. The default MML route integrates over a fixed N(0, 1) population and exposes Person posterior SDs; other supported population specifications must be selected explicitly. Its assumptions and shrinkage must be justified for the target population. A posterior SD and a JML sampling SE are different quantities.

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). 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 Labels= block with direct anchoring:

Labels =
2, Rater, A
1 = R1, 0.0
2 = R2, 0.5
*

becomes an anchors data frame:

anchors <- data.frame(
  Facet = "Rater",
  Level = c("R1", "R2"),
  Anchor = c(0.0, 0.5),
  stringsAsFactors = FALSE
)
fit <- fit_mfrm(..., method = "JML", anchors = anchors,
                centering_scope = "anchor_origin", anchor_policy = "error")

Use labels that match the input data. Anchor is a recognized value column; estimate is not. A malformed nonempty anchor schema stops fitting. For migration, also use anchor_policy = "error" for review issues and inspect the normalized anchors before interpreting results. These values must be on mfrmr’s logit scale. Convert user-scaled FACETS measures back using the original origin and multiplier; copying a scaled number into a logit anchor changes the model.

FACETS disables centering on anchored facets. The explicit "anchor_origin" option follows this rule for direct and group anchors in ordinary additive RSM/PCM JML. One unanchored, active facet must be declared noncenter_facet; other unanchored facets remain centered. Thus, a Person anchor requires a different noncenter facet, such as Rater. Merely adding a Person anchor while centering every other facet can produce FACETS overconstraint warnings and nonzero score residuals. Correcting that specification resolved the checked sparse interior RSM/PCM discrepancies. Multiple fixed values may impose substantive restrictions beyond the origin. Other estimators and fitted interactions are outside this option. An origin anchor does not guarantee a finite maximum. Without Person group constraints, if every retained response of the Person anchors is at the same declared minimum or maximum, free Person and noncenter-facet locations can diverge together while respecting the fixed values. This case stops before optimization, without requiring lpSolve. An extreme anchor with additional interior or opposite-boundary anchor support does not by itself meet this rule; other boundary diagnostics still apply.

In matched sparse RSM/PCM examples, FACETS 4.5.0 retained all observations and the extreme Person’s fixed value. Its reported calibration implied an expected anchor total of about 0.299 for an observed total of 0, or 17.701 for 18. These values are consistent with the specified Xtreme=0.3, but the ordinary likelihood can still increase along a direction that preserves the anchors. The exact internal adjustment is not established by this comparison. mfrmr therefore does not reproduce these finite outputs as ordinary JML estimates. This is a difference in estimation behavior, not an import or rounding error. Other detected joint additive recession directions affecting calibration also stop this route; passing these checks is not a general finite-MLE certificate.

Adding an informative interior Person anchor can restore a finite ordinary solution, but does not by itself establish native calibration agreement. In matched RSM/PCM examples with this support, FACETS retained the fixed extreme Person’s responses. Fair scores and conditional Rater-by-Task bias calculations agreed with independent calculations using the native reported calibration. The main estimates still differed from ordinary JML: free-Person score residuals remained, and the maximum-score examples reached the main iteration limit. These results separate conditional reporting agreement from calibration agreement; they do not identify every internal cause of the native behavior.

For ordinary additive RSM/PCM JML, fair_average_table(..., totalscore = FALSE) now retains finite direct anchors under both xtreme_method conventions. An anchored element’s all-minimum/all-maximum responses do not themselves make it an unanchored infinite estimate. Other unanchored extreme elements still trigger exclusion. The PCM fair score and its conditional plot derivative use the same retained scale mixture. Recreate affected older tables and plots from the saved fit and matching diagnostics; no calibration refit is required.

estimate_bias() also retains directly fixed finite anchors in ordinary additive RSM/PCM JML, including with the default xtreme = 0. Person-by-Rater cells should not disappear merely because that fixed Person’s observed scores are all minimum or maximum. Without extrapolation, a boundary cell keeps its bounded point and unavailable screening SE/tail area. Recreate affected bias tables and collections from the saved fit and matching diagnostics.

The FACETS Xtreme specification has separate main-effect and bias corrections. To match its usual bias correction use estimate_bias(..., xtreme = .5); .3 is the usual main-effect correction. Executed Person-by-Rater checks agree with the conditional calculation on matched main calibrations. The correction can even change the sign relative to the observed departure: an all-zero cell with expected total below 0.5 needs a positive shift to attain the amended total 0.5. Read PrimaryBias, ExtremeAdjustment and Obs-Exp Average together. This remains an extrapolated screen for expert review, not a fairness finding.

For an executable example, each row of the bundled ratings is a Person’s score from one Rater on one Criterion. Treat the R01 anchor below as a supplied external value for illustration, not an independently validated calibration:

ratings <- load_mfrmr_data("example_core")
rater_anchor <- data.frame(Facet = "Rater", Level = "R01", Anchor = 0.7)
anchored_fit <- fit_mfrm(
  ratings, person = "Person", facets = c("Rater", "Criterion"), score = "Score",
  method = "JML", anchors = rater_anchor, centering_scope = "anchor_origin"
)
anchored_fit$config$anchor_review$facet_summary

The table’s Centered and OriginBasis columns show which origin constraints were applied. This leaves Person noncentered, fixes R01, frees the other Rater locations from a zero-sum requirement, and centers Criterion. Existing "facet" (default) and "remainder" conventions remain available and impose different constraints with partial anchors. Refitting is required to change conventions. All options condition on supplied anchors; they do not propagate uncertainty in previously estimated anchor values.

For a group origin, translate the native group total before fitting. In FACETS Labels= ...,G, contributions 0.2 and 1.0 for two members fix their sum at 1.2. mfrmr uses the mean 0.6, repeated for both rows:

ratings <- load_mfrmr_data("example_core")
group <- data.frame(
  Facet = "Rater", Level = c("R01", "R02"), Group = "panel",
  GroupValue = 0.6
)
group_fit <- fit_mfrm(
  ratings, person = "Person", facets = c("Rater", "Criterion"), score = "Score",
  method = "JML", group_anchors = group, centering_scope = "anchor_origin",
  anchor_policy = "error"
)
group_fit$config$anchor_review$facet_summary
subset(group_fit$facets$others, Facet == "Rater")

The first table identifies the Rater origin as group_anchors. In the second, R01 and R02 average 0.6 but their individual measures are estimated; the other Raters need not sum to zero. The supplied group mean is a substantive assumption, and its uncertainty is not propagated. Unequal GroupValue rows are conflicting mean targets, not contributions to be averaged by the normalizer.

FACETS group codes also need translation: group 0 specifies direct anchors, so use anchors, not Group = "0". A missing native contribution does not participate in the group anchor. The native X option excludes extreme elements; mfrmr retains its declared members and does not reproduce that option automatically.

Executed interior RSM/PCM examples agree closely when the group only selects an origin. Adding a second, restrictive group exposed a smaller but real difference: FACETS 4.5.0 preserved both targets, yet a constraint-preserving change increased the ordinary likelihood. mfrmr therefore retains constrained maximum likelihood rather than emulating that numerical result. Matching group means does not establish identical estimates, SEs or interval coverage. General group-anchor extreme extrapolation remains unsupported.

review_mfrm_anchors() normalizes and reports on the constraint block before the fit runs, surfacing duplicate/direct-group overlap and local support-count issues. It does not evaluate assignment connectedness, source-fit readiness, or cross-run element invariance; use the design-network route and substantive identity evidence separately.

Bias and interaction

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

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.

screens <- estimate_all_bias(fit, diagnostics = diag)
summary(screens)                     # Pair counts, availability and magnitude
head(summary(screens)$cells)         # Signed effects, SEs and observed counts
plot(screens)                       # All retained cells grouped by pair
plot(screens, pair = "Rater x Criterion")  # Individual heatmap

These are exploratory conditional screens: baseline calibration and Person uncertainty are held fixed. ScreenPositive counts available statistics with absolute value at least two; it is NA when none is available. Review errors, unavailable cells and observed counts before interpreting zero flags. The batch does not supply collection-wide multiplicity control, and a flagged cell alone does not establish unfairness.

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:

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" withholds the corresponding formal inferential claim even when a familiar table or plot is available. It does not turn every descriptive result into an unusable result: report a valid descriptive target with its limitation, and identify which comparisons or uncertainty statements are unavailable.

plot() 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:

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:

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:

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 G group anchors constrain sums over specified elements. mfrmr’s GroupValue is the mean target for the group. For example, the following schematic input constrains the mean of two Rater elements to zero; it does not define response groups for a DFF analysis:

group_anchors <- data.frame(
  Facet = "Rater",
  Level = c("R1", "R2"),
  Group = c("PanelA", "PanelA"),
  GroupValue = c(0.0, 0.0),
  stringsAsFactors = FALSE
)
fit_g <- fit_mfrm(..., group_anchors = group_anchors, anchor_policy = "error")
diag_g <- diagnose_mfrm(fit_g)
dff <- analyze_dff(fit_g, diag_g, facet = "Criterion",
                   group = "FirstLanguage", data = ratings_with_language,
                   method = "refit")

Replace ... with the fitting arguments for your data and supply ratings_with_language containing the response-group column FirstLanguage. Use diagnostics from fit_g, not from a different fit. Assigning the same element to two different groups is not this mean-constraint construction. A group-anchor target is an external mean constraint, not an empirical link. Its use requires a defensible equal-mean or known-target assumption for the declared elements and does not connect otherwise unobserved rating subsets.

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

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.

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 GPCM the score-side bundle is intentionally restricted; see ?gpcm_capability_matrix and the mfrmr-gpcm-scope vignette for the documented limitation.