diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..b02898a4 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,83 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement: the maintainer, [@JoshuaJewell](https://github.com/JoshuaJewell), using the contact details on their GitHub profile. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..3cf88518 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,59 @@ +name: Bug report +description: Something in MetaManifold does not work as documented. +labels: [bug] +body: + - type: markdown + attributes: + value: | + Search the existing issues first. Raise issues about a fork's own code on that fork. + - type: textarea + id: steps + attributes: + label: Steps or command + description: The command you ran, or the steps in the web interface, so that someone else can repeat them. + validations: + required: true + - type: textarea + id: expected + attributes: + label: What you expected + validations: + required: true + - type: textarea + id: actual + attributes: + label: What happened + description: Include the error message, the server log lines, or a screenshot. + validations: + required: true + - type: input + id: julia + attributes: + label: Julia version + placeholder: output of `julia --version` + validations: + required: true + - type: input + id: r + attributes: + label: R version + placeholder: output of `R --version | head -1` + validations: + required: true + - type: input + id: browser + attributes: + label: Browser and version + validations: + required: true + - type: input + id: commit + attributes: + label: MetaManifold commit + placeholder: output of `git rev-parse --short HEAD` + - type: textarea + id: config + attributes: + label: Relevant configuration + description: The keys from `pipeline.yml` or the run's `run_config.yml` that bear on the problem. + render: yaml diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..312c2aec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,41 @@ +name: Feature request +description: Suggest a new feature or a change to an existing one. +labels: [enhancement] +body: + - type: markdown + attributes: + value: | + Search the existing issues first. To change an architectural decision, describe the change + here before writing any code. The settled decisions in CONTRIBUTING.md can be discussed in an + issue, but a pull request does not reopen them. + - type: textarea + id: problem + attributes: + label: Problem + description: What you are trying to do, and what stops you now. + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed change + description: What MetaManifold should do instead. For a statistical method, name the method and a reference for it. + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + - type: dropdown + id: area + attributes: + label: Area + options: + - Pipeline stages + - Analysis and statistics + - Web interface + - Configuration + - Installation and toolchain + - Other + validations: + required: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..a17d0de6 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,43 @@ +## Summary + + + +Closes # + +## Type of change + +- [ ] ๐Ÿ› Bug fix (non-breaking change that fixes an issue) +- [ ] โœจ New feature (non-breaking change that adds functionality) +- [ ] ๐Ÿ’ฅ Breaking change (would change existing behaviour, a config key or an API response) +- [ ] ๐Ÿ“Š Statistics fix (a method returned a wrong number, or a result it could not support) +- [ ] ๐Ÿ“– Documentation +- [ ] ๐Ÿงน Refactor / tech debt (behaviour-preserving) +- [ ] โšก Performance +- [ ] ๐Ÿ”ง Build / CI / tooling + +## How has this been verified? + + + +Tested with Julia `โ€ฆ`, R `โ€ฆ` and bun `โ€ฆ`. + +## What this leaves out + + + +## Checklist + +- [ ] My branch starts at the current tip of `main` (`behind_by` is `0`; see CONTRIBUTING.md) and has no merge commits from `main`. +- [ ] I ran `julia --project=. test/runtests.jl` (and `cd frontend && bun run test` for frontend changes) and they pass. +- [ ] CI ran on this PR and passes. +- [ ] No generated or vendored files, and no lockfile change the work does not need (or the reason is given above). +- [ ] New code is AGPL-3.0, or MPL-2.0 with an SPDX identifier in each file I wrote. I left existing file headers, `LICENSE`, `CITATION.cff` and the licence and acknowledgement sections of `README.md` unchanged. +- [ ] Every number shown to a user is computed from their data. When a method cannot run it raises an explicit error. +- [ ] Docs are updated, and no claim in them overstates what the code does. + +## Notes for reviewers + + diff --git a/README.md b/README.md index bdf25c98..dafa4711 100644 --- a/README.md +++ b/README.md @@ -74,9 +74,16 @@ Once a run completes, analysis is performed on request through the web UI, both - Pipeline stage read-count summaries - NMDS ordination (Bray-Curtis, via R/vegan) - PERMANOVA, with PERMDISP to check for differences in dispersion (via R/vegan) +- Differential abundance between two conditions: a negative-binomial model per taxon (via R/MASS) with Benjamini-Hochberg adjusted p-values (see [Differential abundance](#differential-abundance)) Counts may be normalised before analysis (none, rarefaction to a fixed or auto-resolved depth, or scaling with ranked subsampling (SRS), which reaches the depth by scaling counts and keeps more of the community structure), and contamination-flagged taxa may be included or excluded. Ordinations can also apply a Hellinger transform before the Bray-Curtis dissimilarity. Composition charts can be drawn as a grid faceted on any two of run, group and sub-group. All analysis charts are returned as Plotly JSON and rendered interactively in the browser. +## Documentation + +Longer documentation for users, maintainers, developers and theorists is kept as a wiki in +[metadatastician/MetaManifold-Evidence, under `docs/wikis/`](https://github.com/metadatastician/MetaManifold-Evidence/tree/main/docs/wikis). +Start at its [Home page](https://github.com/metadatastician/MetaManifold-Evidence/blob/main/docs/wikis/Home.md). + ## Prerequisites - **Julia** 1.12 (installed by `install.sh` if missing, and pinned to the Manifest version for this directory) @@ -240,6 +247,67 @@ The composition view classifies each ASV/OTU into a biological category and rend Organism-category composition of two runs as stacked bars

+### Differential abundance + +The Differential Abundance tab of a study's analysis workspace tests each taxon +for a difference in abundance between two conditions. Select exactly two runs or +pooled-run sub-groups, a results table and a rank. The first condition is the +reference, so a positive fold change means the taxon is more abundant in the +second. The rank list shows only the ranks the selected runs share. With +aggregation on, a pooled run counts as one condition rather than one per +sub-group. + +Counts are summed per taxon at the chosen rank, with rows that have no name at +that rank pooled as `Unclassified`. This happens after the read-count bounds and +any `analysis.exclude_categories` entry that applies to `differential`. Each +taxon is fitted with its own negative-binomial GLM, `MASS::glm.nb(y ~ group + +offset(log(size factor)))`, on the raw integer read counts. There are no +pseudocounts. The group coefficient gets a Wald test, and Benjamini-Hochberg +adjustment runs over the taxa that produced a p-value. Size factors come from +the whole count matrix and are centred to a geometric mean of 1. The code is in +`src/analysis/differential.jl`, and the route in `src/server/routes/analysis.jl`. + +Settings live under `analysis.differential` (see +[Configuring analysis](#configuring-analysis-analysis-in-pipelineyml)): + +- `offset`: `tss` (default) uses each sample's total reads. `rle` uses the + median of ratios over the taxa with reads in every sample. +- `min_prevalence`: a fraction from 0 to 1 (default 0). A taxon with reads in a + smaller fraction of the samples is not fitted. + +The result is a volcano plot and a table. The plot draws log2 fold change +against -log10 of the raw p-value, coloured by whether the adjusted p-value is +below 0.05. Taxa without an adjusted p-value are left off the plot but stay in +the table. Each table row has a status: + +- `ok`: the model fitted. +- `boundary`: the model fitted, but the dispersion parameter theta reached a + bound (at least 1e7, so effectively Poisson, or at most 1e-8). These taxa + keep their p-value and stay in the adjusted family. +- `failed`: no p-value, with the reason. Causes are counts that are constant + across samples, an error from `glm.nb`, a fit that did not converge, an + aliased group coefficient, or a non-finite estimate. +- `filtered`: below `min_prevalence`, so not fitted. + +A failed or filtered taxon never gets a stand-in p-value, and it is not counted +in the adjustment. The table downloads as `differential_abundance.csv` and can +be added to the report. The CSV starts with `#` lines that record the +comparison, method and settings. + +The test refuses, with an explicit error, when: + +- the selection is not exactly two conditions, or both are the same; +- the two conditions resolve to different ranks; +- a condition has no results table, no taxonomy columns or no sample columns, + or the read-count bounds remove all its samples; +- `offset` or `min_prevalence` is invalid; +- a count is not a non-negative integer, a condition has no samples, or there + are fewer than 3 samples in total; +- `tss` meets a sample with no reads, or `rle` finds no taxon with reads in + every sample (use `tss`); +- the R package MASS cannot be loaded; +- no taxon could be fitted at all. + ### Figures and report The Figures tab lays analysis charts out on a page (A4, letter or a journal @@ -554,13 +622,13 @@ Each entry names a filter in the `filters:` library of `config/composition.yml`. ### Configuring analysis (`analysis:` in `pipeline.yml`) -Controls the defaults applied to the analysis charts (alpha diversity, taxa bar, NMDS, etc.). Per-chart choices such as the taxonomic rank and relative or absolute abundance are made in the UI. +Controls the defaults applied to the analysis charts (alpha diversity, taxa bar, NMDS, differential abundance, etc.). Per-chart choices such as the taxonomic rank and relative or absolute abundance are made in the UI. ```yaml analysis: exclude_categories: # composition categories to drop from figures; [] to keep all - - {set: contamination, category: Contaminant, apply_to: [diversity, taxa, venn]} - # apply_to surfaces: diversity | taxa | composition | venn + - {set: contamination, category: Contaminant, apply_to: [diversity, taxa, venn, differential]} + # apply_to surfaces: diversity | taxa | composition | venn | differential # (omit apply_to to act on every surface) alpha: # richness, Shannon and Simpson normalisation: none # none | rarefy | srs (scaling with ranked subsampling) @@ -577,6 +645,9 @@ analysis: depth: 0 # rarefy/srs reads per sample; 0 = smallest non-empty sample nmds: max_stress: 0.2 # warn if NMDS stress exceeds this value + differential: # negative-binomial differential abundance (MASS::glm.nb) + offset: tss # tss | rle (rle is refused when no taxon has reads in every sample) + min_prevalence: 0.0 # fraction of samples, 0 to 1, a taxon needs reads in to be tested ``` ### Configuring taxonomic filtering (`filters:` in `config/composition.yml`) @@ -762,7 +833,7 @@ The server exposes a REST API under `/api/v1/`. Key endpoint groups: | Results | `GET/POST/DELETE .../results/tables/...` | List, query, filter, save, export (`.xlsx`), and delete tables; OTU member drill-down | | QC | `GET .../results/qc`, `GET .../results/dada2` | MultiQC report metadata and DADA2 figures, logs, stats | | Analysis | `POST .../runs/{run}/analysis/{alpha,chart}`, `GET .../runs/{run}/analysis/{pipeline-stats,ranks}` | Per-run charts and rank discovery | -| Cross-run | `POST /studies/{study}/analysis/{alpha,chart,chart-facet,nmds,permanova,venn,publication-tables}` | Comparison, faceted charts, NMDS, PERMANOVA, taxon overlap and publication tables across runs | +| Cross-run | `POST /studies/{study}/analysis/{alpha,chart,chart-facet,nmds,permanova,venn,publication-tables,differential}` | Comparison, faceted charts, NMDS, PERMANOVA, taxon overlap, publication tables and differential abundance across runs | | Composition | `POST .../runs/{run}/composition/summary`, `POST .../composition/{source}/query`, `POST .../composition/{source}/distinct/{column}` | Organism-composition summaries and tables for a run | | Composition library | `GET /composition`, `POST/DELETE /composition/{filters,sets}/{name}`, `GET /category-sets`, `POST/DELETE /category-sets/{name}` | Edit the filters and category sets in `config/composition.yml` | | Reference trees | `GET/POST /reference-trees`, `GET/PUT/DELETE /reference-trees/{id}`, `GET/PUT .../fasta`, `POST .../run`, `GET .../log/{step}`, `GET .../qc/{align,trim}`, `GET .../alignment/{raw,trimmed}`, `GET .../treefile`, `POST .../trim-preview` | Build and inspect the shared reference trees |