@@ -31,10 +31,29 @@ Investigating defect #1 (below) surfaced that the **same bug class already shipp
3131 ` sigma * unscaled == sqrt(diag(vcov)) ` ) and a ` NEWS.md ` bullet under 1.6.3. All model,
3232 contrast, facade, DEqMS and vectorize test suites pass.
3333
34- ** Implication for the binomial plan:** the fix also establishes the exact ` sigma ↔ vcov `
35- coherence invariant (and its regression test) that the future ` strategy_binomial() ` must
36- satisfy — see defect #1 . Use ` sqrt(summary(fit)$dispersion) ` (Pearson) for the quasibinomial
37- ` sigma ` , not ` stats::sigma ` .
34+ ** Implication for the binomial plan:** the fix establishes the ` sigma ↔ vcov ` coherence
35+ invariant (and its regression test). For a quasibinomial glm that invariant is * already*
36+ satisfied by ` stats::sigma ` (R returns the Pearson dispersion for glm objects), so
37+ ` StrategyBinomial ` needs no change here — see defect #1 , which is ** retracted** .
38+
39+ ## Post-implementation recheck (verified against the merged code)
40+
41+ The ` binomial_nested ` implementation (` R/BinomialNested.R ` , facade in
42+ ` R/ContrastsChildToParentFacades.R ` , moderation floor in ` R/tidyMS_moderation.R ` ) was
43+ re-verified against this review:
44+
45+ - ** Defect #1 — RETRACTED.** ` sigma = stats::sigma ` is correct for the quasibinomial glm
46+ (empirically ` stats::sigma == sqrt(summary$dispersion) ` , coherent with ` vcov() ` ).
47+ - ** Defect #5 — addressed.** The facade sets a family-aware ` ContrastConfiguration `
48+ (` supports_dea_qc = FALSE ` ); roxygen documents ` diff ` = log odds ratio and ` avgAbd ` = average
49+ linear predictor on the log-odds scale, so log-odds output is not mislabeled as abundance.
50+ - ** Variance floor — implemented as analyzed, including the ordering subtlety.** ` binomial_bound `
51+ maps to ` variance_floor = 1 ` ; floored rows get ` df.prior <- Inf ` * after* the pre-existing
52+ "all ` df.prior ` infinite" fallback, so it is not clobbered.
53+ - ** Tests green.** ` test-BinomialNested.R ` passes 65/65.
54+
55+ The remaining "statistical decisions" below were deliberate author choices (documented in
56+ ` NEWS.md ` ): the symmetric pseudo-count for separated fits and the posterior-dispersion bound.
3857
3958## What the plan gets right
4059
@@ -63,28 +82,33 @@ satisfy — see defect #1. Use `sqrt(summary(fit)$dispersion)` (Pearson) for the
6382
6483## Code-level defects (fix before implementing)
6584
66- ### 1. ` sigma = stats::sigma ` is the wrong scale for a quasibinomial glm (major / correctness )
85+ ### 1. ~~ ` sigma = stats::sigma ` is the wrong scale for a quasibinomial glm~~ — RETRACTED (my claim was WRONG )
6786
68- The most important defect. In ` .compute_contrast ` (` R/tidyMS_contrasts.R:395-428 ` ), ` std.error `
69- comes from ` vcov(m) ` (` :407 ` ) while the stored ` sigma ` comes independently from
70- ` strategy$sigma(m) ` (` :401 ` ). The moderation rescale at ` R/tidyMS_moderation.R:34 ` ,
87+ ** This claim was incorrect and is withdrawn.** It was rejected during implementation, and an
88+ empirical check confirms the rejection was right.
89+
90+ The moderation rescale at ` R/tidyMS_moderation.R ` ,
7191` moderated.statistic = statistic * sigma / sqrt(var.post) ` , is only coherent when
72- ` std.error = sigma × (unscaled) ` .
73-
74- - For an ` lm ` this holds: ` vcov = σ²(XᵀX)⁻¹ ` , ` sigma = stats::sigma ` = residual SD.
75- - For a quasibinomial glm, ` vcov = φ_Pearson × cov.unscaled ` , but ` stats::sigma(glm) ` returns
76- the ** deviance-based** scale ` sqrt(deviance/df.residual) ` , * not* ` φ_Pearson ` . The two differ
77- exactly in the sparse-count regime this model targets, leaving a spurious per-protein factor
78- ` sqrt(φ_dev/φ_Pearson) ` in every moderated t.
79-
80- ** Fix:** the strategy must set ` sigma = function(model) sqrt(summary(model)$dispersion) `
81- (Pearson), matching what ` vcov() ` embeds. (` StrategyLogistf ` sidesteps this by hardcoding
82- ` sigma = 1 ` ; the binomial strategy cannot.)
83-
84- > ** Note:** this exact bug class was already present in the shipped ` rlm ` backend and has been
85- > ** fixed** during this review — see the "Related pre-existing bug" section above. The
86- > ` rlm ` fix adds the ` sigma ↔ vcov ` coherence regression test that the binomial strategy
87- > should extend.
92+ ` std.error = sigma × (unscaled) ` . I asserted that ` stats::sigma(glm) ` returns the
93+ deviance-based scale ` sqrt(deviance/df.residual) ` and would therefore break this. ** It does
94+ not.** For a glm, R's ` sigma() ` returns the * Pearson dispersion* — the same quantity ` vcov() `
95+ uses. Verified on a quasibinomial fit:
96+
97+ ```
98+ stats::sigma(fit) = 1.149071
99+ sqrt(summary$dispersion) = 1.149071 <- equal: Pearson, what vcov() uses
100+ sqrt(deviance/df.residual) = 1.177689 <- NOT what stats::sigma returns
101+ stats::sigma * unscaled == sqrt(diag(vcov(fit))) <- reconstructs the SE exactly
102+ ```
103+
104+ So ` sigma = function(model) stats::sigma(model) ` (as implemented in ` StrategyBinomial ` ) is
105+ ** correct and coherent** for the quasibinomial glm; no change is needed.
106+
107+ My error was over-generalizing a * class-specific* behavior: the identical reasoning is genuinely
108+ correct for ` rlm ` (where ` stats::sigma ` returns the ordinary-residual scale, ≠ the robust scale
109+ ` $s ` that ` vcov() ` uses — the real bug we fixed) but wrong for ` glm ` (where ` stats::sigma ` already
110+ equals the Pearson dispersion). The ` sigma ↔ vcov ` coherence invariant still matters; ` glm ` simply
111+ satisfies it via ` stats::sigma ` out of the box. The ` rlm ` fix and its regression test stand.
88112
89113### 2. The cited facade templates are the wrong ones (major)
90114
0 commit comments