Skip to content

Commit 0a8f59c

Browse files
committed
Correct binomial review: retract sigma claim, record post-implementation recheck
stats::sigma() returns the Pearson dispersion for glm (verified: == sqrt(summary$dispersion), coherent with vcov()), so StrategyBinomial's sigma is correct; defect #1 was wrong and is retracted. Records that the family-aware ContrastConfiguration, variance floor (with df.prior->Inf ordering) and tests (65/65) were verified against the merged implementation.
1 parent df13687 commit 0a8f59c

1 file changed

Lines changed: 48 additions & 24 deletions

File tree

TODO/REVIEW_binomial.md

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)