Skip to content

Commit 8b76b71

Browse files
hyperpolymatharena-agentcoderabbitai[bot]CodeRabbit
authored
Repair differential method union and frontend regressions (#210)
## Summary - restore the complete nine-method differential configuration and dispatch union, including multinomial/Dirichlet-multinomial, ANCOM-BC, ALDEx2, Songbird, ZINB, and hurdle NB - restore route configuration propagation, refusal mappings, method-dependent validation, and frontend result/configuration types - preserve both CladeCumulus components with distinct aliases and repair the DOI module opener - restore method-specific CSV provenance (Bayesian alpha only for Bayesian replacement), diagnostics, controls, tests, and documentation ## Validation - `npm --prefix frontend run build` — passed - `npx --yes bun@1.3.2 test frontend/src` — 85 passed - `npx --yes js-yaml config/defaults/pipeline.yml` — passed - Julia/R execution is unavailable locally; the PR CI runs `test/runtests.jl --integration --server` and will be followed to completion. Zero-replacement documentation continues to warn that imputation is biased; BH adjustment remains mandatory. --------- Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
1 parent a9b82e2 commit 8b76b71

14 files changed

Lines changed: 232 additions & 79 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,11 +400,11 @@ jobs:
400400
shell: bash
401401
run: |
402402
set +e
403-
julia --project=. -t2 --compiled-modules=no test/runtests.jl numeric_policy exact_summaries high_precision derived_analyses validated_statistics 2>&1 | tee /tmp/validated-statistics-tests.log
403+
julia --project=. -t2 --compiled-modules=no test/runtests.jl numeric_policy exact_summaries high_precision derived_analyses validated_statistics 2>&1 | tee /tmp/julia-validated-tests.log
404404
status="${PIPESTATUS[0]}"
405405
if [ "$status" -ne 0 ]; then
406406
enc() { head -c 3500 | sed -e 's/%/%25/g' -e 's/\r/%0D/g' | awk '{printf "%s%%0A", $0}'; }
407-
echo "::error title=Validated-statistics tests failed::$(tail -n 120 /tmp/validated-statistics-tests.log | enc)"
407+
echo "::error title=Validated statistics failed (tail)::$(tail -c 3500 /tmp/julia-validated-tests.log | enc)"
408408
fi
409409
exit "$status"
410410

‎README.md‎

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ not exclude samples from this analysis.
299299
calibration caveat in its diagnostics, shown with the result and written
300300
into its CSV export. The caveat changes no p-value; the null-simulation
301301
evidence and full replication protocol live in `bench/null_calibration`.
302+
The glmGamPoi path uses fixed-theta NB Wald fits, not a quasi-likelihood test.
302303
- `zinb`: a zero-inflated negative-binomial model with separate logistic
303304
zero and count components, a count-component library-size offset, a joint
304305
2-df likelihood-ratio test, and BH correction.
@@ -316,12 +317,15 @@ not exclude samples from this analysis.
316317
on `log(counts + 1)`, propagating bias-estimation variance into two-sided Wald
317318
tests. The effect is a bias-corrected log2 fold change; useful when asymmetric
318319
shifts or load changes would bias standard CLR centering (needs at least 3
319-
tested taxa; assumes fewer than half are differential).
320+
tested taxa). The implementation reports its mixture and bias diagnostics.
321+
Its assumption that fewer than half of tested taxa are differential must be
322+
checked rather than treated as a guarantee.
320323
- `aldex2`: Dirichlet(`count + 0.5`) Monte-Carlo CLR model (Fernandes et al.
321324
2013, 2014). Draws `mc_samples` (default 128, seeded by the study's `seed`)
322325
instances per sample and reports standardized CLR effect sizes with
323326
consistent-sign posterior predictive Welch p-values; suited to sparse or
324327
variable-depth cohorts where deterministic zero imputation is undesirable.
328+
The reported estimate is a standardized CLR effect size, not an absolute fold change.
325329
- `songbird`: compositional baseline-category multinomial regression with
326330
reference-frame invariant CLR-centred differentials summing to zero across
327331
fitted taxa (Morton et al. 2019, *Nat. Commun.* 10:2719; Zhang et al. 2017).
@@ -336,7 +340,8 @@ not exclude samples from this analysis.
336340
composition (La Rosa et al. 2012), which absorbs between-sample
337341
overdispersion; `dm_overdispersion_method` picks `mle` (profiled) or `mom`.
338342
Both multinomial models bound their Newton fit with `max_iter`, `tolerance`
339-
and `max_seconds`, and a fit that does not converge is refused.
343+
and `max_seconds`, and a fit that does not converge is refused. Dirichlet-
344+
multinomial diagnostics report the concentration, theta, and boundary status.
340345

341346
ZINB and hurdle models can overfit sparse data; do not select ZINB solely
342347
because counts are sparse. Compare against NB diagnostics. The methods report
@@ -818,23 +823,22 @@ analysis:
818823
normalisation: hellinger # hellinger | none | rarefy | srs
819824
depth: 0 # rarefy/srs reads per sample; 0 = smallest non-empty sample
820825
differential:
821-
method: nb_glm # nb_glm | clr_welch | zinb | hurdle_nb | ancom_bc
822-
# | aldex2 | songbird | multinomial | dirichlet_multinomial
826+
method: nb_glm # nb_glm | clr_welch | multinomial | dirichlet_multinomial
827+
# ancom_bc | aldex2 | songbird | zinb | hurdle_nb
823828
offset: tss # count models: tss | rle
824829
dispersion: ml # nb_glm: ml | glmGamPoi
825830
min_prevalence: 0.0 # fraction of samples a taxon must be seen in
826831
replacement_method: bayesian_multiplicative
827832
# clr_welch: bayesian_multiplicative | multiplicative_replacement
828833
replacement_delta: 0.65 # clr_welch: strictly between 0 and 1
829-
bayesian_alpha: ~ # clr_welch: null (GBM estimate) or a number > 0
830-
mc_samples: 128 # aldex2: Dirichlet Monte-Carlo instances in [16, 4096]
831-
mn_reference_taxon: ~ # multinomial models: null picks the most abundant taxon
832-
mn_penalty: 0.0 # multinomial models: L2 ridge penalty, >= 0
833-
dm_overdispersion_method: mle
834-
# dirichlet_multinomial: mle | mom
834+
bayesian_alpha: ~ # Bayesian replacement: null (GBM estimate) or a number > 0
835+
mn_reference_taxon: ~ # multinomial models: null selects the most abundant fitted taxon
836+
mn_penalty: 0.0 # multinomial models: non-negative L2 penalty
837+
dm_overdispersion_method: mle # dirichlet_multinomial: mle | mom
835838
max_iter: 500 # multinomial models: Newton iteration cap
836-
tolerance: 1.0e-8 # multinomial models: convergence tolerance
839+
tolerance: 0.00000001 # multinomial models: convergence tolerance
837840
max_seconds: 300.0 # multinomial models: wall-clock budget
841+
mc_samples: 128 # aldex2: Dirichlet Monte-Carlo instances in [16, 4096]
838842
nmds:
839843
max_stress: 0.2 # warn if NMDS stress exceeds this value
840844
```

‎ROADMAP.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ occupancy work. The GitHub Wiki roadmap still needs a synchronized update; see
66

77
## Available now
88

9-
- The differential-abundance config offers four per-taxon count models
9+
- The differential-abundance config offers four per-taxon methods
1010
(`nb_glm`, `clr_welch`, `zinb`, `hurdle_nb`) and five compositional models
1111
(`ancom_bc`, `aldex2`, `songbird`, `multinomial`, `dirichlet_multinomial`);
1212
see [the differential-abundance section](README.md#differential-abundance).

‎docs/occupancy-models.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ not an AnalysisConfig method or the standard endpoint. The full workflow is
66
tracked in [issue #205](https://github.com/hyperpolymath/MetaManifold-WebUI/issues/205),
77
a follow-up to #18.
88

9-
The differential-abundance configuration supports `zinb` and `hurdle_nb` in
10-
addition to the existing `nb_glm` and `clr_welch` models. The two new methods
11-
are fixed-effect, per-taxon models with a two-part negative-binomial likelihood.
9+
The differential-abundance configuration supports `zinb` and `hurdle_nb`
10+
alongside the count, CLR, and compositional methods. These two methods are
11+
fixed-effect, per-taxon models with a two-part negative-binomial likelihood.
1212
They use the configured `tss` or `rle` library-size offset in the count
1313
component and apply Benjamini–Hochberg correction to the joint tests.
1414

‎frontend/src/api/client.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import type {
2121
TreeFileInfo, TreeFile, RunRef, ReadFunnelData, ReportItem, ReportKind,
2222
PlacementDoc, PlacementSummary, PlacementDetail, PlacementQueries, PhyloStep,
2323
ReferenceTreeDoc, ReferenceTreeSummary, ReferenceTreeDetail, AlignmentQC, TrimSettings,
24+
CladisticsCreateRequest, CladisticsSummary, CladisticsDetail,
2425
CladeCumulusSourcesResponse, CladeCumulusRequest, CladeCumulusResponse,
2526
CladeMoveValidationRequest, CladeMoveValidationResponse,
26-
CladisticsSummary, CladisticsDetail, CladisticsCreateRequest,
2727
} from './types'
2828

2929
import type { FigureDoc } from '../figure/types'

‎frontend/src/components/DifferentialPanel.tsx‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,16 @@ export function DifferentialPanel({ study, runs, option, aggregate }: {
220220
`replaced values hold ${(100 * z.median_imputed_fraction).toPrecision(2)}% of the median sample ` +
221221
`and at most ${(100 * z.max_imputed_fraction).toPrecision(2)}% of any, and are not measurements.`
222222
})()}
223+
{result.diagnostics.model && (() => {
224+
const m = result.diagnostics.model
225+
return ` Reference taxon: ${m.reference_taxon}; ${m.n_parameters} coefficients, ` +
226+
`penalty ${m.penalty}, ${m.iterations} Newton steps in ${m.runtime_seconds.toPrecision(2)} s.`
227+
})()}
228+
{result.diagnostics.overdispersion && (() => {
229+
const o = result.diagnostics.overdispersion
230+
return ` Concentration ${o.concentration.toPrecision(3)} by ${o.method}, ` +
231+
`theta = ${o.theta.toPrecision(3)}${o.at_bound ? ' (at a bound: see the row notes)' : ''}.`
232+
})()}
223233
{result.diagnostics.ancom_bc && (() => {
224234
const b = result.diagnostics.ancom_bc
225235
const warn = b.warnings.length > 0 ? ` Warning: ${b.warnings.join('; ')}.` : ''

‎frontend/src/components/PipelineStages.test.ts‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
import { expect, test } from 'bun:test'
33
import { CONFIG_DESCRIPTIONS, CONFIG_TYPES } from './PipelineStages'
44

5+
const ALL_DIFFERENTIAL_METHODS = [
6+
'nb_glm', 'clr_welch', 'zinb', 'hurdle_nb', 'ancom_bc', 'aldex2', 'songbird',
7+
'multinomial', 'dirichlet_multinomial',
8+
]
9+
510
test('dispersion uses the enum editor with both supported models', () => {
611
expect(CONFIG_TYPES['analysis.differential.dispersion']).toEqual({
712
kind: 'enum', options: ['ml', 'glmGamPoi'],
@@ -10,14 +15,23 @@ test('dispersion uses the enum editor with both supported models', () => {
1015

1116
test('differential model editor exposes all nine supported methods', () => {
1217
expect(CONFIG_TYPES['analysis.differential.method']).toEqual({
13-
kind: 'enum',
14-
options: [
15-
'nb_glm', 'clr_welch', 'zinb', 'hurdle_nb', 'ancom_bc', 'aldex2', 'songbird',
16-
'multinomial', 'dirichlet_multinomial',
17-
],
18+
kind: 'enum', options: ALL_DIFFERENTIAL_METHODS,
1819
})
1920
})
2021

22+
test('multinomial settings retain their enums and optimiser budgets', () => {
23+
expect(CONFIG_TYPES['analysis.differential.dm_overdispersion_method']).toEqual({
24+
kind: 'enum', options: ['mle', 'mom'],
25+
})
26+
expect(CONFIG_TYPES['analysis.differential.mn_penalty']).toEqual({
27+
kind: 'float', min: 0, step: 0.05,
28+
})
29+
expect(CONFIG_TYPES['analysis.differential.max_seconds']).toEqual({
30+
kind: 'float', min: 1,
31+
})
32+
expect(CONFIG_TYPES['analysis.differential.max_iter']).toEqual({ kind: 'int' })
33+
})
34+
2135
test('advanced zero handling exposes both methods, bounded delta and nullable alpha', () => {
2236
expect(CONFIG_TYPES['analysis.differential.replacement_method']).toEqual({
2337
kind: 'enum', options: ['bayesian_multiplicative', 'multiplicative_replacement'],

‎frontend/src/components/PipelineStages.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,12 +270,12 @@ export const CONFIG_TYPES: Record<string, ConfigType> = {
270270
'analysis.differential.replacement_method': { kind: 'enum', options: ['bayesian_multiplicative', 'multiplicative_replacement'] },
271271
'analysis.differential.replacement_delta': { kind: 'float', min: 0, max: 1, step: 0.05 },
272272
'analysis.differential.bayesian_alpha': { kind: 'float', nullable: true, min: 0, step: 0.1 },
273-
'analysis.differential.mc_samples': { kind: 'int' },
274273
'analysis.differential.mn_penalty': { kind: 'float', min: 0, step: 0.05 },
275274
'analysis.differential.dm_overdispersion_method': { kind: 'enum', options: ['mle', 'mom'] },
276275
'analysis.differential.max_iter': { kind: 'int' },
277276
'analysis.differential.tolerance': { kind: 'float', min: 0 },
278277
'analysis.differential.max_seconds': { kind: 'float', min: 1 },
278+
'analysis.differential.mc_samples': { kind: 'int' },
279279
'analysis.alpha.show_points': { kind: 'boolean' },
280280
'analysis.alpha.annotate_significance': { kind: 'boolean' },
281281
'analysis.alpha.pairwise_brackets': { kind: 'boolean' },

0 commit comments

Comments
 (0)