Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ jobs:
sudo chmod 777 "$RENV_PATHS_CACHE"
sudo env R_CLI_DYNAMIC=false TERM=dumb Rscript --no-init-file -e 'install.packages(c("renv", "BiocManager"), repos="https://cloud.r-project.org", lib=.Library)'
sudo env RENV_PATHS_CACHE="$RENV_PATHS_CACHE" R_CLI_DYNAMIC=false TERM=dumb Rscript --no-init-file -e 'renv::restore(project=".", library=.Library, prompt=FALSE)'
Rscript --no-init-file -e 'for (pkg in c("dada2","Biostrings","ShortRead","vegan","MASS","zCompositions")) if (!require(pkg,character.only=TRUE,quietly=TRUE)) stop(pkg, " failed to install")'
Rscript --no-init-file -e 'for (pkg in c("dada2","Biostrings","ShortRead","vegan","MASS","zCompositions","glmGamPoi")) if (!require(pkg,character.only=TRUE,quietly=TRUE)) stop(pkg, " failed to install")'

- name: Check DADA2 CSV assembly
run: Rscript --vanilla test/unit/test_dada2_tables.R
Expand Down Expand Up @@ -250,7 +250,7 @@ jobs:
julia --project=. -e '
using RCall
R"cat(.libPaths(), sep=\"\n\")"
for pkg in ["dada2", "vegan", "MASS", "zCompositions"]
for pkg in ["dada2", "vegan", "MASS", "zCompositions", "glmGamPoi"]
R"if (!require($pkg, character.only=TRUE, quietly=TRUE)) stop($pkg, \" not found\")"
end
'
Expand Down
1 change: 1 addition & 0 deletions R/_renv_dependencies.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
if (FALSE) {
library(dada2)
library(vegan)
library(glmGamPoi)
library(MASS)
library(zCompositions)
}
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ is the reference. Counts are aggregated by taxon after applicable
read-count bounds to the differential-abundance request, so those bounds do
not exclude samples from this analysis.

All `analysis.differential` options below (`method`, `offset`,
All `analysis.differential` options below (`method`, `offset`, `dispersion`,
`min_prevalence` and `replacement_delta`) are resolved at study scope.
Set them in the study configuration; group and run overrides are not used
by this endpoint, including when called from a group Analysis workspace.
Expand All @@ -259,7 +259,15 @@ by this endpoint, including when called from a group Analysis workspace.
- `nb_glm` (default): a negative-binomial GLM per taxon using `MASS::glm.nb`,
with a library-size offset. The effect is log2 fold change; positive values
indicate greater abundance in the second condition. `offset` selects `tss`
(default) or `rle` scaling.
(default) or `rle` scaling. `dispersion` selects the fits and the test:
`glm_nb` (default) fits `MASS::glm.nb` per taxon with its own
maximum-likelihood theta and Wald-tests the group coefficient; `glmGamPoi`
fits one `glmGamPoi::glm_gp` across the taxa passing `min_prevalence`,
sharing a dispersion trend across them, and reports `glmGamPoi::test_de`'s
quasi-likelihood F-test. Under `glmGamPoi` the p-value comes from that
F-test, the standard error is `test_de`'s `lfc_se`, and theta is each
taxon's own estimate before shrinkage. It requires the pinned glmGamPoi R
package and is refused for `clr_welch`.
- `clr_welch`: Welch's t-test on centred log-ratios after multiplicative zero
replacement with `zCompositions::cmultRepl`. The effect is a CLR difference,
not a fold change. `replacement_delta` controls the replacement fraction
Expand Down
10 changes: 10 additions & 0 deletions config/defaults/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,16 @@ analysis:
# rle is refused when no taxon is present in every sample.
# Used by nb_glm only; clr_welch ignores it, since log-ratios carry no library size.
offset: tss
# nb_glm only: the fits and the test. glm_nb (the default) fits
# MASS::glm.nb per taxon with its maximum-likelihood theta and Wald-tests
# the group coefficient. glmGamPoi fits one glmGamPoi::glm_gp across the
# taxa passing min_prevalence, with overdispersion shrinkage toward the
# cross-taxon trend, and reports glmGamPoi::test_de's quasi-likelihood
# F-test of the group contrast with its log2 fold changes; the reported
# theta inverts glm_gp's negative-binomial overdispersion, and a taxon
# whose overdispersion is zero is marked at the Poisson boundary, as under
# glm_nb. Requires the pinned glmGamPoi R package. Refused with clr_welch.
dispersion: glm_nb
# Fraction of samples, 0 to 1, in which a taxon must have reads to be
# tested. Taxa below it are listed as filtered, without a p-value. For
# clr_welch it is applied before zero replacement, so on a sparse table it also
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,8 @@ export interface DifferentialResult {
min_prevalence: number
/** nb_glm only. */
offset?: 'tss' | 'rle'
/** nb_glm only: which fits and test produced the rows (per-taxon MASS::glm.nb with a Wald test, or one glmGamPoi::glm_gp with test_de's quasi-likelihood F-test). */
dispersion?: 'glm_nb' | 'glmGamPoi'
/** clr_welch only. */
replacement_delta?: number
}
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/components/PipelineStages.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
import { expect, test } from 'bun:test'
import { CONFIG_TYPES } from './PipelineStages'

test('dispersion uses the enum editor with both supported models', () => {
expect(CONFIG_TYPES['analysis.differential.dispersion']).toEqual({
kind: 'enum', options: ['glm_nb', 'glmGamPoi'],
})
})
2 changes: 2 additions & 0 deletions frontend/src/components/PipelineStages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export const CONFIG_DESCRIPTIONS: Record<string, string> = {
'analysis.beta.depth': 'Reads per sample for rarefy or srs; 0 uses the smallest non-empty sample.',
'analysis.differential.method': 'Model fitted to each taxon: nb_glm (negative-binomial GLM on read counts; effect is a log2 fold change) or clr_welch (Welch\'s t-test on centred log-ratios over the taxa passing min_prevalence, after zCompositions::cmultRepl zero replacement; effect is a CLR difference, not a fold change).',
'analysis.differential.offset': 'Library-size offset in each negative-binomial model: tss (total reads per sample) or rle (median-of-ratios over taxa present in every sample; refused when there are none). Used by nb_glm only.',
'analysis.differential.dispersion': 'nb_glm only: glm_nb fits MASS::glm.nb per taxon with its own maximum-likelihood theta; glmGamPoi fits one glmGamPoi::glm_gp across taxa with overdispersion shrinkage and reports glmGamPoi::test_de’s quasi-likelihood F-test with its log2 fold changes.',
'analysis.differential.min_prevalence': 'Fraction of samples, 0 to 1, in which a taxon must have reads to be tested; taxa below it are listed as filtered.',
'analysis.differential.replacement_delta': 'clr_welch only: cmultRepl\'s frac, between 0 and 1; an imputed proportion above its taxon\'s smallest observed proportion is lowered to this fraction of it. Replaced zeros are not measurements.',
'analysis.alpha.show_points': 'Show individual samples overlaid on alpha comparison boxplots.',
Expand Down Expand Up @@ -252,6 +253,7 @@ export const CONFIG_TYPES: Record<string, ConfigType> = {
'analysis.beta.depth': { kind: 'int' },
'analysis.differential.method': { kind: 'enum', options: ['nb_glm', 'clr_welch'] },
'analysis.differential.offset': { kind: 'enum', options: ['tss', 'rle'] },
'analysis.differential.dispersion': { kind: 'enum', options: ['glm_nb', 'glmGamPoi'] },
'analysis.differential.min_prevalence': { kind: 'float', min: 0, max: 1, step: 0.05 },
'analysis.differential.replacement_delta': { kind: 'float', min: 0.01, max: 0.99, step: 0.05 },
'analysis.alpha.show_points': { kind: 'boolean' },
Expand Down
4 changes: 2 additions & 2 deletions install.jl
Original file line number Diff line number Diff line change
Expand Up @@ -879,8 +879,8 @@ function setup_r_packages(; rebuild::Bool=false)::Tuple{Symbol,String}
# Verdict is whether the packages the pipeline loads are actually usable, not
# renv's exit code: restore can fail on one leaf package and still leave a
# working library, or "succeed" against a stale cache.
# MASS and zCompositions back the differential abundance methods.
pkgs = ["dada2", "vegan", "MASS", "zCompositions"]
# MASS, zCompositions and glmGamPoi back the differential abundance methods.
pkgs = ["dada2", "vegan", "MASS", "zCompositions", "glmGamPoi"]
probe = """
pkgs <- c($(join(repr.(pkgs), ", ")))
cat(paste(pkgs[!vapply(pkgs, requireNamespace, logical(1), quietly = TRUE)],
Expand Down
Loading
Loading