msa op• Data kinds: table → table
• Call: import fullseye as fs; fs.ledger.msa_gauge_rr(table, part='part', operator='operator', value='value', tolerance=None, pool_interaction='auto', pool_alpha=0.25) (to call the implementation directly, import spc; spc.msa_gauge_rr(table, part='part', operator='operator', value='value', tolerance=None, pool_interaction='auto', pool_alpha=0.25); from the registry, opsspc.get("msa_gauge_rr"))
Gauge repeatability and reproducibility by the analysis-of-variance method (table).
Repeatability EV (the scatter when the same person re-measures the same item), reproducibility AV (the scatter when the person changes; includes the interaction), their combination GRR, part-to-part PV, total variation TV, %GRR = 100 GRR/TV, and the number of distinct categories ndc = 1.41 PV/GRR.
Passing *tolerance* also returns the ratio to the tolerance %tolerance = 100 GRR/tolerance.
★ The variance components come from the expected mean squares of a balanced crossed design:
var_repeat = MS_err
var_inter = (MS_inter - MS_err) / r
var_oper = (MS_oper - MS_inter) / (p r)
var_part = (MS_part - MS_inter) / (o r)
★★ Negative variance components are rounded to 0, but the rounding is not hidden (clamped column). A difference of expected mean squares is an estimator, so it can go negative when the true component is near 0. Silently setting it to 0 reads as "there is no operator difference", but the correct reading is "it could not be estimated". And this is not a rare edge case -- running synthetic data with 60 parts x 4 operators x 5 repeats and the operator difference set exactly to 0 over 40 seeds, the reproducibility component comes out negative in 31 seeds (measured 2026-09-23). An implementation that does not declare the rounding would assert "no difference" in all 31 of them.
★ The scatter of the estimator is not hidden either. With the same 60x4x5, a repeatability with ground truth 0.4 comes out over 40 seeds at mean 0.39710, standard deviation 0.00788 -- the same order as the theoretical sigma/sqrt(2 df) (0.00913 at df = 960), so do not trust 1 seed to 1 % precision. Reproducibility is worse: it is estimated from 4 operators (3 degrees of freedom), so against a ground truth of 0.5 its standard deviation is 0.159 -- a quantity where getting the order right is already good.
★★ The answer changes depending on whether the interaction is kept or pooled into the error. The procedure of the standard is "if the F test of the interaction is not significant, drop the interaction term and recompute", and *pool_interaction* is that choice:
• "auto" (default) -- when the interaction p value exceeds *pool_alpha* (default 0.25), it pools.
★★ 0.25 is not a number stated in the standard. What the reference manual text says is only the qualitative instruction "choose a high significance level to lower the risk of missing the interaction" and no number is written. 0.25 is the majority convention on the software side, and another implementation uses 0.05 as its default. Making α larger makes p > alpha less likely = the interaction is kept more easily, so 0.25 is the side faithful to the text's policy (conservative). On the other hand, the worked example table of the standard says in a footnote "judged at α = 0.05", so a check that claims to reproduce that table should pass pool_alpha=0.05 explicitly (the matter of the default and the matter of the conditions for reproducing a published example are separate). In this example F = 0.434 is non-significant at either α, so the result does not change, but for data near the boundary the difference in default moves EV / AV by several % -- measured, switching the model moved EV by 7.3 %.
• False -- always keeps the interaction.
• True -- always pools.
The returned interaction_pooled / interaction_p / pool_alpha always record which one was used. Switching silently would make the same tool return different numbers for the same process, with the reason left nowhere in the output.
★ Measured (the standard's example, 90 points of 10 parts x 3 operators x 3 trials): keeping the interaction gives EV = 0.214435, pooling gives EV = 0.199933 -- a 7.3 % difference. The choice is decided by a p value threshold, so the model jumps when the data move slightly. In this example F = 0.4337 / p = 0.9741, i.e. the interaction has no effect at all, so the standard publishes the pooled side as its published value (this implementation reproduces that value to 3e-7).
★ Exact relations usable as a gate: TV^2 == GRR^2 + PV^2 and GRR^2 == EV^2 + AV^2 (the definitions themselves), the contributions summing to 100 %, and with 1 operator, EV matches the mean of the per-cell sample variances exactly (numpy becomes the ground truth).
• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).
• Operator provenance and references — the sources of the research/methods this op family came from.
• The canonical algorithm (author, year) and its uses are named in the family usage guide above.
• poc_measurement_system_analysis — py -3.11 examples/poc_measurement_system_analysis.py
table as input)msa_anova_table · msa_bias_linearity · msa_attribute_agreement · gum_standard_uncertainty · gum_propagate · gum_expanded · gum_monte_carlo · gum_validate
msa)msa_anova_table · msa_bias_linearity · msa_attribute_agreement
*Provenance: spc.py — SPC operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.