OmicVerse Microbiome — 16S amplicon end-to-end
Goal
Take paired-end Illumina FASTQs from a 16S rRNA gene survey through to an analysis-ready AnnData (samples × ASVs, with 7-rank SINTAX taxonomy in var and the ASV centroid sequence stored alongside), plus the de-facto downstream stack: alpha-diversity (Shannon / observed OTUs / Simpson) → beta-diversity (Bray-Curtis distance matrix) → PCoA / NMDS ordination → differential abundance (Wilcoxon by default; pyDESeq2 / ANCOM-BC available).
Cover two backends behind the same wrapper:
- vsearch / UNOISE3 — the default; fast (single-threaded C, parallelised via
jobs); slightly more reads retained per sample on noisy datasets.
- DADA2 — Bayesian denoising; longer to fit but produces tighter ASV-level resolution. Same wrapper, just
backend='dada2'.
Plus the stepwise API for users who want to inspect or substitute individual stages (merge → filter → dereplicate → UNOISE3 → uchime3 → SINTAX → OTU table → AnnData).
Quick Workflow
- Reference DB: fetch a SINTAX-formatted 16S reference once with
ov.alignment.fetch_rdp(db_dir=...) — returns the path to the .fa.gz you'll feed all subsequent calls.
- Sample metadata: build a
pd.DataFrame keyed by sample-id with at least the phenotype/factor column (here called 'group'). The wrapper writes this into adata.obs.
- One-shot pipeline: call
ov.alignment.amplicon_16s_pipeline(fastq_dir=..., workdir=..., db_fasta=..., backend='vsearch'|'dada2', sample_metadata=meta, ...). Auto-discovers R1/R2 from FASTQ names, runs the full chain, writes intermediates under workdir, returns an AnnData.
- Validate ingest: confirm
adata.shape == (n_samples, n_ASVs), 'phylum' in adata.var.columns (and the rest of the 7-rank), int(adata.X.sum()) is reasonable (>50 % of input reads typically retained).
- Alpha diversity:
ov.micro.Alpha(adata, rarefy_depth=min_depth).run(metrics=['shannon', 'observed_otus', 'simpson']) — rarefies first, writes per-sample metrics into adata.obs.
- Beta + ordination:
ov.micro.Beta(adata, rarefy_depth=min_depth).run(metric='braycurtis') then ov.micro.Ordinate(adata, dist_key='braycurtis').pcoa(n=3). Distance matrix lives at adata.obsp['braycurtis']; PCoA coords at adata.obsm['braycurtis_pcoa']; variance-explained at adata.uns['micro']['braycurtis_pcoa_var'].
- Differential abundance:
ov.micro.DA(adata).wilcoxon(group_key='group', group_a, group_b, rank='phylum'|'genus'|...). Returns DataFrame with effect size + BH-FDR.
Interface Summary
Reference DB:
ov.alignment.fetch_rdp(db_dir=None, overwrite=False) → str — alias for fetch_sintax_ref('rdp_16s_v18'). ~6.8 MB. Returns path to .fa.gz.
ov.alignment.fetch_sintax_ref(db_name, db_dir=None) → str — switch DB ('silva138', 'gtdb', etc.).
Pipeline wrapper (one-shot path):
ov.alignment.amplicon_16s_pipeline(
fastq_dir: Optional[str] = None,
samples: Optional[Sequence[(name, fq1, fq2|None)]] = None,
workdir: Optional[str] = None,
db_fasta: Optional[str] = None, *,
primer_fwd: Optional[str] = None, primer_rev: Optional[str] = None,
backend: str = 'vsearch', # or 'dada2'
threads: int = 4,
jobs: Optional[int] = None,
merge_max_diffs: int = 10, merge_min_overlap: int = 16,
filter_max_ee: float = 1.0, filter_min_len: int = 0, filter_max_len: int = 0,
derep_min_uniq: int = 2,
unoise_alpha: float = 2.0, unoise_minsize: int = 2,
chimera_removal: bool = True,
otutab_identity: float = 0.97,
sintax_cutoff: float = 0.8, sintax_strand: str = 'both',
sample_metadata: Optional[pd.DataFrame] = None,
overwrite: bool = False,
) → AnnData
Stepwise vsearch helpers (under ov.alignment.vsearch.*):
merge_pairs(samples, output_dir, max_diffs=10, min_overlap=16, threads=8, jobs=4).
filter_quality(merge_res, output_dir, max_ee=1.0, threads=8, jobs=4).
dereplicate(filt_res, output_dir, min_uniq=2, threads=8).
unoise3(uniques_fasta, output_dir, alpha=2.0, minsize=2, threads=8).
uchime3_denovo(asv_fasta, output_dir).
sintax(asv_fasta, db_fasta, output_dir, cutoff=0.8, strand='both', threads=8).
usearch_global(combined_fastq, asv_fasta, output_dir, identity=0.97, threads=8).
Composer:
ov.alignment.build_amplicon_anndata(otutab_tsv, asv_fasta, sintax_tsv=None, sample_metadata=None, sample_order=None) → AnnData — convert pre-computed OTU table + ASV centroids + SINTAX TSV into the canonical AnnData.
Diversity / ordination / DA (ov.micro):
ov.micro.Alpha(adata, rarefy_depth=None, seed=0).run(metrics=('shannon', 'observed_otus'), write_to_obs=True, tree_key='tree') → pd.DataFrame. Convenience methods: .shannon(), .observed() return individual pd.Series. Faith PD via 'faith_pd' requires a phylogenetic tree at adata.uns[tree_key] (see the phylogeny skill).
ov.micro.Beta(adata, rarefy_depth=None, seed=0).run(metric='braycurtis'|'jaccard'|'aitchison'|'unifrac_unweighted'|'unifrac_weighted', rarefy=None, tree_key='tree', write_to_obsp=True) → pd.DataFrame. UniFrac requires unifrac package + a tree.
ov.micro.Ordinate(adata, dist_key='braycurtis').pcoa(n=3, write_to_obsm=True) → pd.DataFrame (eigen-decomposition of the distance matrix; proportion_explained() gives variance fractions). .nmds(n=2, random_state=0, write_to_obsm=True) for non-metric MDS.
ov.micro.DA(adata).wilcoxon(group_key, group_a=None, group_b=None, rank=None, relative=True, min_prevalence=0.1) → pd.DataFrame with log2fc, pvalue, padj. Per-feature Mann-Whitney U on relative abundances.
ov.micro.DA(adata).deseq2(group_key, group_a=None, group_b=None, rank=None, min_prevalence=0.1, alpha=0.05) — pyDESeq2 NB-GLM on raw counts.
ov.micro.DA(adata).ancombc(group_key, rank=None, min_prevalence=0.1, pseudocount=1.0) — ANCOM-BC via skbio≥0.7.1.
Preprocessing helpers:
ov.micro.rarefy(adata, depth=None, seed=0, drop_shallow=True, save_original=True, copy=False) — subsample counts; original counts cached at adata.layers['raw_counts'] when save_original=True.
ov.micro.filter_by_prevalence(adata, min_prevalence=0.1, min_count=1, copy=False) — drop rare features.
ov.micro.collapse_taxa(adata, rank='genus', unassigned_label='Unassigned') — sum ASV counts within taxonomic rank.
ov.micro.clr(adata, layer_out='clr', copy=False) / ilr(...) — compositional transforms (post pseudo-count).
Boundary
Inside scope:
- Full vsearch / UNOISE3 pipeline.
- Full DADA2 pipeline (same wrapper,
backend='dada2').
- Stepwise vsearch composability +
build_amplicon_anndata from external outputs.
- 7-rank SINTAX taxonomy assignment with the shipped RDP DB (or any SINTAX-format reference).
- Alpha (Shannon / Simpson / Chao1 / observed OTUs) and beta (Bray-Curtis / Jaccard / Aitchison) diversity.
- PCoA / NMDS ordination on a stored distance matrix.
- Wilcoxon DA at any taxonomic rank.
Outside scope — separate skill:
- DA method comparison across Wilcoxon / pyDESeq2 / ANCOM-BC — see
omicverse-microbiome-da-comparison.
- Cross-cohort meta-analysis (
combine_studies, meta_da) — see omicverse-microbiome-meta-analysis.
- Phylogenetic tree construction + UniFrac + Faith PD — see
omicverse-microbiome-phylogeny.
- Paired microbiome + metabolomics integration (MMvec, paired Spearman / CCA) — see
omicverse-micro-metabol-paired.
- Shotgun metagenomics —
ov.alignment doesn't ship a metagenomics pipeline; this skill is amplicon-only.
Branch Selection
backend='vsearch' vs 'dada2'
- vsearch (default): faster (~minutes for the mothur SOP demo), ships a single binary, parallelises across samples (
jobs). Use for production runs and CI.
- DADA2: Bayesian denoising; tighter ASV resolution at the cost of longer runtime. Use when noise floor matters (e.g. low-biomass samples) or when reviewers expect DADA2 specifically.
- Both write the same canonical AnnData layout — switching backends doesn't change downstream code.
Primer trimming (primer_fwd / primer_rev)
- Set both to the actual primer sequences for cutadapt to run first.
- Pass
None only when you've confirmed primers are already trimmed (e.g. the mothur SOP test dataset).
- Mismatch: cutadapt errors out with a useful message — don't silence it.
filter_max_ee (expected error rate)
- 1.0 — strict (vsearch default; loses ~30 % of low-quality reads).
- 2.0 — DADA2-tutorial default (more permissive); needed on Q20+ MiSeq runs to retain enough reads.
- Empirical: try 1.0 first, drop to 2.0 if fewer than 50 % of input reads survive the filter.
unoise_minsize and derep_min_uniq
unoise_minsize=2 (defaults to 2) — UNOISE3 retains an ASV only if it appears at least this many times across the whole derep table. Lower (1) for low-biomass studies; higher (4–8) to suppress sequencing noise on deep cohorts.
derep_min_uniq is the per-sample equivalent before pooling.
sintax_cutoff (taxonomic confidence)
- 0.8 (default, RDP recommendation) — bootstrap support threshold; ranks below this become empty strings.
- 0.5 — looser; useful on novel environmental samples where genus-level resolution isn't available in any DB.
- Lower than 0.5 — the published "do-not-go-below" floor; results are not reproducible across DB versions.
otutab_identity
- 0.97 — classical OTU clustering threshold (≈ species-level for 16S V3-V4); use when you specifically want OTU-style buckets, not ASVs.
- For pure ASV workflows, this stage is just used to build the per-sample count matrix from the original reads — keep at 0.97 for compatibility with downstream tools.
Alpha metrics
shannon — diversity (counts both richness and evenness); always report this.
observed_otus — pure richness, depth-sensitive (always rarefy first).
simpson — diversity weighted toward dominant taxa.
chao1 — depth-corrected richness estimator; reports unobserved-taxa correction.
faith_pd — phylogenetic diversity; needs a tree (phylogeny skill).
Beta metrics
braycurtis — abundance-weighted, the de-facto 16S default.
jaccard — presence/absence; complement to Bray-Curtis when interpretation differs.
aitchison — CLR-Euclidean; correct for compositional data, but harder to interpret intuitively.
unifrac_* — phylogenetically aware; needs a tree (phylogeny skill).
Ordination — PCoA vs NMDS
- PCoA: linear, fast, gives variance-explained — use when distances are ≈ Euclidean (Aitchison) or for the canonical "PC1/PC2 with %variance" plot.
- NMDS: rank-based, no eigenvalues; less distorted on Bray-Curtis / Jaccard. Report the final stress (in
adata.uns['micro']).
DA method
wilcoxon — non-parametric, fast, no distributional assumptions. Default for moderate cohorts (n>=10/group).
deseq2 — NB-GLM on raw counts; better-calibrated than Wilcoxon, more conservative with small counts. Requires pip install pydeseq2.
ancombc — bias-corrected ANCOM; closest to the compositional ground-truth but slowest. Requires skbio>=0.7.1. See the DA-comparison skill for trade-offs.
Input Contract
- Pipeline ingest: paired-end Illumina FASTQs with predictable R1/R2 naming (e.g.
<sample>_R1.fastq.gz); one sample per pair. The wrapper auto-discovers from fastq_dir; pass samples=[(name, fq1, fq2)] for explicit control.
workdir: writable directory, >1 GB free space recommended for moderate cohorts. The wrapper does not fall back to $HOME or /tmp — pass an explicit path.
db_fasta: SINTAX-formatted 16S FASTA (fetch_rdp returns a valid one). Skipping (db_fasta=None) means no taxonomy in var.
sample_metadata: pd.DataFrame indexed by sample-id (matching FASTQ-derived names exactly). The phenotype column is conventionally called 'group' to match ov.micro.DA's defaults.
- After ingest,
adata.X is a sparse (or dense) integer count matrix — never log-transform before alpha / beta / DA; those functions handle scaling internally.
Minimal Execution Patterns
# vsearch one-shot pipeline
import omicverse as ov
import pandas as pd
ov.plot_set()
DB_FASTA = ov.alignment.fetch_rdp(db_dir='./db/rdp')
meta = pd.DataFrame({
'group': ['Early', 'Early', 'Late', 'Late', 'Mock'],
'day': [0, 1, 7, 8, 0],
}, index=['F3D0', 'F3D1', 'F3D7', 'F3D8', 'Mock'])
adata = ov.alignment.amplicon_16s_pipeline(
fastq_dir='./raw/MiSeq_SOP',
workdir='./run_vsearch',
db_fasta=DB_FASTA,
threads=8, jobs=4,
primer_fwd=None, primer_rev=None, # SOP is pre-trimmed
filter_max_ee=1.0,
unoise_minsize=2,
sintax_cutoff=0.8,
sample_metadata=meta,
)
print(adata) # samples × ASVs
print(int(adata.X.sum()), 'reads mapped')
# DADA2 backend — same wrapper
adata_dada2 = ov.alignment.amplicon_16s_pipeline(
samples=[('F3D0', 'raw/F3D0_R1.fastq.gz', 'raw/F3D0_R2.fastq.gz'), ...],
workdir='./run_dada2',
db_fasta=DB_FASTA,
backend='dada2',
primer_fwd=None, primer_rev=None,
filter_max_ee=2.0, # DADA2 typically wants more permissive EE
sintax_cutoff=0.8,
threads=4,
sample_metadata=meta,
)
# Stepwise (vsearch) — when you need to inspect / substitute a stage
import numpy as np
merge_res = ov.alignment.vsearch.merge_pairs(samples, output_dir='./step/merged',
max_diffs=10, min_overlap=16,
threads=8, jobs=4)
filt_res = ov.alignment.vsearch.filter_quality(merge_res, output_dir='./step/filtered',
max_ee=1.0, threads=8, jobs=4)
derep = ov.alignment.vsearch.dereplicate(filt_res, output_dir='./step/derep',
min_uniq=2, threads=8)
unoise = ov.alignment.vsearch.unoise3(derep['uniques'], output_dir='./step/asv',
alpha=2.0, minsize=2, threads=8)
nochim = ov.alignment.vsearch.uchime3_denovo(unoise['asv'], output_dir='./step/asv')
tax = ov.alignment.vsearch.sintax(nochim['asv'], db_fasta=DB_FASTA,
output_dir='./step/taxonomy',
cutoff=0.8, strand='both', threads=8)
otutab = ov.alignment.vsearch.usearch_global(derep['combined'], nochim['asv'],
output_dir='./step/otutab',
identity=0.97, threads=8)
adata_step = ov.alignment.build_amplicon_anndata(
otutab_tsv=otutab['otutab'],
asv_fasta=nochim['asv'],
sintax_tsv=tax['tsv'],
sample_metadata=meta,
sample_order=[s[0] for s in samples],
)
# Diversity + ordination + DA on the AnnData
min_depth = int(np.asarray(adata.X.sum(axis=1)).min())
# alpha — writes shannon/observed_otus/simpson into adata.obs
ov.micro.Alpha(adata, rarefy_depth=min_depth).run(
metrics=['shannon', 'observed_otus', 'simpson'],
)
# beta — distance matrix in adata.obsp['braycurtis']
ov.micro.Beta(adata, rarefy_depth=min_depth).run(metric='braycurtis')
# PCoA — coords in adata.obsm['braycurtis_pcoa'], var-fraction in uns
ord_ = ov.micro.Ordinate(adata, dist_key='braycurtis').pcoa(n=3)
pct = ord_.proportion_explained() * 100.0
# DA — Wilcoxon at phylum level
da = ov.micro.DA(adata).wilcoxon(
group_key='group', group_a='Early', group_b='Late',
rank='phylum', min_prevalence=0.1,
)
print(da.head(10))
Validation
- After ingest:
adata.shape matches the expected (n_samples, n_ASVs). The mothur SOP demo lands at ~22 samples × ~250 ASVs with vsearch defaults.
- Read retention:
int(adata.X.sum()) / int(<input read total>) > 0.5 is healthy. Below 0.3 indicates the filter / chimera / derep stages dropped too much; revisit filter_max_ee and unoise_minsize.
- Taxonomy coverage:
(adata.var['phylum'] != '').sum() / adata.n_vars should exceed 0.8 — lower means the DB is wrong species range or sintax_cutoff is too strict.
- Alpha rarefaction: pass
rarefy_depth=int(adata.X.sum(axis=1).min()) to rarefy down to the shallowest sample. If that's <1000, the cohort is too shallow for trustworthy alpha — flag this rather than silently downsampling.
- Beta rarefaction: same — uneven depths inflate Bray-Curtis distances against the deeper samples.
- After ordination:
proportion_explained() is decreasing; PC1 + PC2 typically capture 30–60 % on 16S. If PC1 alone captures >70 %, suspect a single dominant outlier sample.
- After DA:
(da['padj'] < 0.05).sum() should be sane for the cohort — for the mothur SOP 22-sample demo, expect ~3–8 phylum-level hits at padj<0.05.
- The DADA2 backend produces different ASV identities than vsearch; downstream interpretations (e.g. specific ASV names) don't transfer between backends. Diversity / ordination / DA patterns should agree.
Resource Map
- See
reference.md for compact one-shot + stepwise + diversity snippets.
- See
references/source-grounding.md for verified pipeline + ov.micro API signatures and the docstring backfill log (Alpha.shannon / Alpha.observed / Beta.braycurtis / Ordinate.nmds / Ordinate.proportion_explained / DA.deseq2).
- For DA method comparison (Wilcoxon vs pyDESeq2 vs ANCOM-BC), see
omicverse-microbiome-da-comparison.
- For cross-cohort meta-analysis on the resulting AnnDatas, see
omicverse-microbiome-meta-analysis.
- For phylogenetic tree + UniFrac + Faith PD, see
omicverse-microbiome-phylogeny.
- For paired microbiome + metabolomics analysis, see
omicverse-micro-metabol-paired.
Examples
- "Run the vsearch one-shot pipeline on the mothur MiSeq SOP FASTQs with
filter_max_ee=1.0 and the RDP v18 SINTAX DB; return an AnnData."
- "Re-run the same FASTQs with
backend='dada2' and filter_max_ee=2.0; confirm diversity patterns match the vsearch result."
- "Compute Shannon + observed-OTUs alpha at the shallowest depth, then Bray-Curtis beta + 3-D PCoA, and plot the first two PCs colored by
obs['group']."
- "Run Wilcoxon DA between Early and Late at phylum level with
min_prevalence=0.1 and report the top 10 hits."
References
- Tutorial notebooks:
- Live API verified — see
references/source-grounding.md.
1---2name: omicverse-microbiome-16s-amplicon-dada23description: 16S rRNA amplicon analysis from raw FASTQs to a samples × ASVs AnnData with 7-rank SINTAX taxonomy, plus the canonical alpha / beta / ordination / DA stack. Use when running the vsearch-or-DADA2 amplicon pipeline (`ov.alignment.amplicon_16s_pipeline`), when ingesting an existing OTU/ASV count table with `build_amplicon_anndata`, or when computing Shannon / Bray-Curtis / PCoA / Wilcoxon-DA on the resulting AnnData.4---56# OmicVerse Microbiome — 16S amplicon end-to-end78## Goal910Take paired-end Illumina FASTQs from a 16S rRNA gene survey through to an analysis-ready `AnnData` (samples × ASVs, with 7-rank SINTAX taxonomy in `var` and the ASV centroid sequence stored alongside), plus the de-facto downstream stack: alpha-diversity (Shannon / observed OTUs / Simpson) → beta-diversity (Bray-Curtis distance matrix) → PCoA / NMDS ordination → differential abundance (Wilcoxon by default; pyDESeq2 / ANCOM-BC available).1112Cover **two backends** behind the same wrapper:13- **vsearch / UNOISE3** — the default; fast (single-threaded C, parallelised via `jobs`); slightly more reads retained per sample on noisy datasets.14- **DADA2** — Bayesian denoising; longer to fit but produces tighter ASV-level resolution. Same wrapper, just `backend='dada2'`.1516Plus the **stepwise** API for users who want to inspect or substitute individual stages (merge → filter → dereplicate → UNOISE3 → uchime3 → SINTAX → OTU table → AnnData).1718## Quick Workflow19201. **Reference DB**: fetch a SINTAX-formatted 16S reference once with `ov.alignment.fetch_rdp(db_dir=...)` — returns the path to the `.fa.gz` you'll feed all subsequent calls.212. **Sample metadata**: build a `pd.DataFrame` keyed by sample-id with at least the phenotype/factor column (here called `'group'`). The wrapper writes this into `adata.obs`.223. **One-shot pipeline**: call `ov.alignment.amplicon_16s_pipeline(fastq_dir=..., workdir=..., db_fasta=..., backend='vsearch'|'dada2', sample_metadata=meta, ...)`. Auto-discovers R1/R2 from FASTQ names, runs the full chain, writes intermediates under `workdir`, returns an `AnnData`.234. **Validate ingest**: confirm `adata.shape == (n_samples, n_ASVs)`, `'phylum' in adata.var.columns` (and the rest of the 7-rank), `int(adata.X.sum())` is reasonable (>50 % of input reads typically retained).245. **Alpha diversity**: `ov.micro.Alpha(adata, rarefy_depth=min_depth).run(metrics=['shannon', 'observed_otus', 'simpson'])` — rarefies first, writes per-sample metrics into `adata.obs`.256. **Beta + ordination**: `ov.micro.Beta(adata, rarefy_depth=min_depth).run(metric='braycurtis')` then `ov.micro.Ordinate(adata, dist_key='braycurtis').pcoa(n=3)`. Distance matrix lives at `adata.obsp['braycurtis']`; PCoA coords at `adata.obsm['braycurtis_pcoa']`; variance-explained at `adata.uns['micro']['braycurtis_pcoa_var']`.267. **Differential abundance**: `ov.micro.DA(adata).wilcoxon(group_key='group', group_a, group_b, rank='phylum'|'genus'|...)`. Returns DataFrame with effect size + BH-FDR.2728## Interface Summary2930Reference DB:31- `ov.alignment.fetch_rdp(db_dir=None, overwrite=False) → str` — alias for `fetch_sintax_ref('rdp_16s_v18')`. ~6.8 MB. Returns path to `.fa.gz`.32- `ov.alignment.fetch_sintax_ref(db_name, db_dir=None) → str` — switch DB (`'silva138'`, `'gtdb'`, etc.).3334Pipeline wrapper (one-shot path):35```python36ov.alignment.amplicon_16s_pipeline(37 fastq_dir: Optional[str] = None,38 samples: Optional[Sequence[(name, fq1, fq2|None)]] = None,39 workdir: Optional[str] = None,40 db_fasta: Optional[str] = None, *,41 primer_fwd: Optional[str] = None, primer_rev: Optional[str] = None,42 backend: str = 'vsearch', # or 'dada2'43 threads: int = 4,44 jobs: Optional[int] = None,45 merge_max_diffs: int = 10, merge_min_overlap: int = 16,46 filter_max_ee: float = 1.0, filter_min_len: int = 0, filter_max_len: int = 0,47 derep_min_uniq: int = 2,48 unoise_alpha: float = 2.0, unoise_minsize: int = 2,49 chimera_removal: bool = True,50 otutab_identity: float = 0.97,51 sintax_cutoff: float = 0.8, sintax_strand: str = 'both',52 sample_metadata: Optional[pd.DataFrame] = None,53 overwrite: bool = False,54) → AnnData55```5657Stepwise vsearch helpers (under `ov.alignment.vsearch.*`):58- `merge_pairs(samples, output_dir, max_diffs=10, min_overlap=16, threads=8, jobs=4)`.59- `filter_quality(merge_res, output_dir, max_ee=1.0, threads=8, jobs=4)`.60- `dereplicate(filt_res, output_dir, min_uniq=2, threads=8)`.61- `unoise3(uniques_fasta, output_dir, alpha=2.0, minsize=2, threads=8)`.62- `uchime3_denovo(asv_fasta, output_dir)`.63- `sintax(asv_fasta, db_fasta, output_dir, cutoff=0.8, strand='both', threads=8)`.64- `usearch_global(combined_fastq, asv_fasta, output_dir, identity=0.97, threads=8)`.6566Composer:67- `ov.alignment.build_amplicon_anndata(otutab_tsv, asv_fasta, sintax_tsv=None, sample_metadata=None, sample_order=None) → AnnData` — convert pre-computed OTU table + ASV centroids + SINTAX TSV into the canonical AnnData.6869Diversity / ordination / DA (`ov.micro`):70- `ov.micro.Alpha(adata, rarefy_depth=None, seed=0).run(metrics=('shannon', 'observed_otus'), write_to_obs=True, tree_key='tree') → pd.DataFrame`. Convenience methods: `.shannon()`, `.observed()` return individual `pd.Series`. Faith PD via `'faith_pd'` requires a phylogenetic tree at `adata.uns[tree_key]` (see the phylogeny skill).71- `ov.micro.Beta(adata, rarefy_depth=None, seed=0).run(metric='braycurtis'|'jaccard'|'aitchison'|'unifrac_unweighted'|'unifrac_weighted', rarefy=None, tree_key='tree', write_to_obsp=True) → pd.DataFrame`. UniFrac requires `unifrac` package + a tree.72- `ov.micro.Ordinate(adata, dist_key='braycurtis').pcoa(n=3, write_to_obsm=True) → pd.DataFrame` (eigen-decomposition of the distance matrix; `proportion_explained()` gives variance fractions). `.nmds(n=2, random_state=0, write_to_obsm=True)` for non-metric MDS.73- `ov.micro.DA(adata).wilcoxon(group_key, group_a=None, group_b=None, rank=None, relative=True, min_prevalence=0.1) → pd.DataFrame` with `log2fc`, `pvalue`, `padj`. Per-feature Mann-Whitney U on relative abundances.74- `ov.micro.DA(adata).deseq2(group_key, group_a=None, group_b=None, rank=None, min_prevalence=0.1, alpha=0.05)` — pyDESeq2 NB-GLM on raw counts.75- `ov.micro.DA(adata).ancombc(group_key, rank=None, min_prevalence=0.1, pseudocount=1.0)` — ANCOM-BC via skbio≥0.7.1.7677Preprocessing helpers:78- `ov.micro.rarefy(adata, depth=None, seed=0, drop_shallow=True, save_original=True, copy=False)` — subsample counts; original counts cached at `adata.layers['raw_counts']` when `save_original=True`.79- `ov.micro.filter_by_prevalence(adata, min_prevalence=0.1, min_count=1, copy=False)` — drop rare features.80- `ov.micro.collapse_taxa(adata, rank='genus', unassigned_label='Unassigned')` — sum ASV counts within taxonomic rank.81- `ov.micro.clr(adata, layer_out='clr', copy=False)` / `ilr(...)` — compositional transforms (post pseudo-count).8283## Boundary8485**Inside scope:**86- Full vsearch / UNOISE3 pipeline.87- Full DADA2 pipeline (same wrapper, `backend='dada2'`).88- Stepwise vsearch composability + `build_amplicon_anndata` from external outputs.89- 7-rank SINTAX taxonomy assignment with the shipped RDP DB (or any SINTAX-format reference).90- Alpha (Shannon / Simpson / Chao1 / observed OTUs) and beta (Bray-Curtis / Jaccard / Aitchison) diversity.91- PCoA / NMDS ordination on a stored distance matrix.92- Wilcoxon DA at any taxonomic rank.9394**Outside scope — separate skill:**95- DA method *comparison* across Wilcoxon / pyDESeq2 / ANCOM-BC — see `omicverse-microbiome-da-comparison`.96- Cross-cohort meta-analysis (`combine_studies`, `meta_da`) — see `omicverse-microbiome-meta-analysis`.97- Phylogenetic tree construction + UniFrac + Faith PD — see `omicverse-microbiome-phylogeny`.98- Paired microbiome + metabolomics integration (MMvec, paired Spearman / CCA) — see `omicverse-micro-metabol-paired`.99- Shotgun metagenomics — `ov.alignment` doesn't ship a metagenomics pipeline; this skill is amplicon-only.100101## Branch Selection102103**`backend='vsearch'` vs `'dada2'`**104- vsearch (default): faster (~minutes for the mothur SOP demo), ships a single binary, parallelises across samples (`jobs`). Use for production runs and CI.105- DADA2: Bayesian denoising; tighter ASV resolution at the cost of longer runtime. Use when noise floor matters (e.g. low-biomass samples) or when reviewers expect DADA2 specifically.106- Both write the same canonical AnnData layout — switching backends doesn't change downstream code.107108**Primer trimming (`primer_fwd` / `primer_rev`)**109- Set both to the actual primer sequences for cutadapt to run first.110- Pass `None` only when you've confirmed primers are already trimmed (e.g. the mothur SOP test dataset).111- Mismatch: cutadapt errors out with a useful message — don't silence it.112113**`filter_max_ee` (expected error rate)**114- 1.0 — strict (vsearch default; loses ~30 % of low-quality reads).115- 2.0 — DADA2-tutorial default (more permissive); needed on Q20+ MiSeq runs to retain enough reads.116- Empirical: try 1.0 first, drop to 2.0 if fewer than 50 % of input reads survive the filter.117118**`unoise_minsize` and `derep_min_uniq`**119- `unoise_minsize=2` (defaults to 2) — UNOISE3 retains an ASV only if it appears at least this many times across the whole derep table. Lower (1) for low-biomass studies; higher (4–8) to suppress sequencing noise on deep cohorts.120- `derep_min_uniq` is the per-sample equivalent before pooling.121122**`sintax_cutoff` (taxonomic confidence)**123- 0.8 (default, RDP recommendation) — bootstrap support threshold; ranks below this become empty strings.124- 0.5 — looser; useful on novel environmental samples where genus-level resolution isn't available in any DB.125- Lower than 0.5 — the published "do-not-go-below" floor; results are not reproducible across DB versions.126127**`otutab_identity`**128- 0.97 — classical OTU clustering threshold (≈ species-level for 16S V3-V4); use when you specifically want OTU-style buckets, not ASVs.129- For pure ASV workflows, this stage is just used to build the per-sample count matrix from the original reads — keep at 0.97 for compatibility with downstream tools.130131**Alpha metrics**132- `shannon` — diversity (counts both richness and evenness); always report this.133- `observed_otus` — pure richness, depth-sensitive (always rarefy first).134- `simpson` — diversity weighted toward dominant taxa.135- `chao1` — depth-corrected richness estimator; reports unobserved-taxa correction.136- `faith_pd` — phylogenetic diversity; needs a tree (phylogeny skill).137138**Beta metrics**139- `braycurtis` — abundance-weighted, the de-facto 16S default.140- `jaccard` — presence/absence; complement to Bray-Curtis when interpretation differs.141- `aitchison` — CLR-Euclidean; correct for compositional data, but harder to interpret intuitively.142- `unifrac_*` — phylogenetically aware; needs a tree (phylogeny skill).143144**Ordination — PCoA vs NMDS**145- PCoA: linear, fast, gives variance-explained — use when distances are ≈ Euclidean (Aitchison) or for the canonical "PC1/PC2 with %variance" plot.146- NMDS: rank-based, no eigenvalues; less distorted on Bray-Curtis / Jaccard. Report the final stress (in `adata.uns['micro']`).147148**DA method**149- `wilcoxon` — non-parametric, fast, no distributional assumptions. Default for moderate cohorts (n>=10/group).150- `deseq2` — NB-GLM on raw counts; better-calibrated than Wilcoxon, more conservative with small counts. Requires `pip install pydeseq2`.151- `ancombc` — bias-corrected ANCOM; closest to the compositional ground-truth but slowest. Requires `skbio>=0.7.1`. See the DA-comparison skill for trade-offs.152153## Input Contract154155- Pipeline ingest: paired-end Illumina FASTQs with predictable R1/R2 naming (e.g. `<sample>_R1.fastq.gz`); one sample per pair. The wrapper auto-discovers from `fastq_dir`; pass `samples=[(name, fq1, fq2)]` for explicit control.156- `workdir`: writable directory, `>1 GB` free space recommended for moderate cohorts. The wrapper does **not** fall back to `$HOME` or `/tmp` — pass an explicit path.157- `db_fasta`: SINTAX-formatted 16S FASTA (`fetch_rdp` returns a valid one). Skipping (`db_fasta=None`) means no taxonomy in `var`.158- `sample_metadata`: `pd.DataFrame` indexed by sample-id (matching FASTQ-derived names exactly). The phenotype column is conventionally called `'group'` to match `ov.micro.DA`'s defaults.159- After ingest, `adata.X` is a sparse (or dense) integer count matrix — never log-transform before alpha / beta / DA; those functions handle scaling internally.160161## Minimal Execution Patterns162163```python164# vsearch one-shot pipeline165import omicverse as ov166import pandas as pd167168ov.plot_set()169170DB_FASTA = ov.alignment.fetch_rdp(db_dir='./db/rdp')171172meta = pd.DataFrame({173 'group': ['Early', 'Early', 'Late', 'Late', 'Mock'],174 'day': [0, 1, 7, 8, 0],175}, index=['F3D0', 'F3D1', 'F3D7', 'F3D8', 'Mock'])176177adata = ov.alignment.amplicon_16s_pipeline(178 fastq_dir='./raw/MiSeq_SOP',179 workdir='./run_vsearch',180 db_fasta=DB_FASTA,181 threads=8, jobs=4,182 primer_fwd=None, primer_rev=None, # SOP is pre-trimmed183 filter_max_ee=1.0,184 unoise_minsize=2,185 sintax_cutoff=0.8,186 sample_metadata=meta,187)188print(adata) # samples × ASVs189print(int(adata.X.sum()), 'reads mapped')190```191192```python193# DADA2 backend — same wrapper194adata_dada2 = ov.alignment.amplicon_16s_pipeline(195 samples=[('F3D0', 'raw/F3D0_R1.fastq.gz', 'raw/F3D0_R2.fastq.gz'), ...],196 workdir='./run_dada2',197 db_fasta=DB_FASTA,198 backend='dada2',199 primer_fwd=None, primer_rev=None,200 filter_max_ee=2.0, # DADA2 typically wants more permissive EE201 sintax_cutoff=0.8,202 threads=4,203 sample_metadata=meta,204)205```206207```python208# Stepwise (vsearch) — when you need to inspect / substitute a stage209import numpy as np210211merge_res = ov.alignment.vsearch.merge_pairs(samples, output_dir='./step/merged',212 max_diffs=10, min_overlap=16,213 threads=8, jobs=4)214filt_res = ov.alignment.vsearch.filter_quality(merge_res, output_dir='./step/filtered',215 max_ee=1.0, threads=8, jobs=4)216derep = ov.alignment.vsearch.dereplicate(filt_res, output_dir='./step/derep',217 min_uniq=2, threads=8)218unoise = ov.alignment.vsearch.unoise3(derep['uniques'], output_dir='./step/asv',219 alpha=2.0, minsize=2, threads=8)220nochim = ov.alignment.vsearch.uchime3_denovo(unoise['asv'], output_dir='./step/asv')221tax = ov.alignment.vsearch.sintax(nochim['asv'], db_fasta=DB_FASTA,222 output_dir='./step/taxonomy',223 cutoff=0.8, strand='both', threads=8)224otutab = ov.alignment.vsearch.usearch_global(derep['combined'], nochim['asv'],225 output_dir='./step/otutab',226 identity=0.97, threads=8)227228adata_step = ov.alignment.build_amplicon_anndata(229 otutab_tsv=otutab['otutab'],230 asv_fasta=nochim['asv'],231 sintax_tsv=tax['tsv'],232 sample_metadata=meta,233 sample_order=[s[0] for s in samples],234)235```236237```python238# Diversity + ordination + DA on the AnnData239min_depth = int(np.asarray(adata.X.sum(axis=1)).min())240241# alpha — writes shannon/observed_otus/simpson into adata.obs242ov.micro.Alpha(adata, rarefy_depth=min_depth).run(243 metrics=['shannon', 'observed_otus', 'simpson'],244)245246# beta — distance matrix in adata.obsp['braycurtis']247ov.micro.Beta(adata, rarefy_depth=min_depth).run(metric='braycurtis')248249# PCoA — coords in adata.obsm['braycurtis_pcoa'], var-fraction in uns250ord_ = ov.micro.Ordinate(adata, dist_key='braycurtis').pcoa(n=3)251pct = ord_.proportion_explained() * 100.0252253# DA — Wilcoxon at phylum level254da = ov.micro.DA(adata).wilcoxon(255 group_key='group', group_a='Early', group_b='Late',256 rank='phylum', min_prevalence=0.1,257)258print(da.head(10))259```260261## Validation262263- After ingest: `adata.shape` matches the expected `(n_samples, n_ASVs)`. The mothur SOP demo lands at ~22 samples × ~250 ASVs with vsearch defaults.264- Read retention: `int(adata.X.sum()) / int(<input read total>) > 0.5` is healthy. Below 0.3 indicates the filter / chimera / derep stages dropped too much; revisit `filter_max_ee` and `unoise_minsize`.265- Taxonomy coverage: `(adata.var['phylum'] != '').sum() / adata.n_vars` should exceed 0.8 — lower means the DB is wrong species range or `sintax_cutoff` is too strict.266- Alpha rarefaction: pass `rarefy_depth=int(adata.X.sum(axis=1).min())` to rarefy down to the shallowest sample. If that's <1000, the cohort is too shallow for trustworthy alpha — flag this rather than silently downsampling.267- Beta rarefaction: same — uneven depths inflate Bray-Curtis distances against the deeper samples.268- After ordination: `proportion_explained()` is decreasing; PC1 + PC2 typically capture 30–60 % on 16S. If PC1 alone captures >70 %, suspect a single dominant outlier sample.269- After DA: `(da['padj'] < 0.05).sum()` should be sane for the cohort — for the mothur SOP 22-sample demo, expect ~3–8 phylum-level hits at padj<0.05.270- The DADA2 backend produces *different* ASV identities than vsearch; downstream interpretations (e.g. specific ASV names) don't transfer between backends. Diversity / ordination / DA *patterns* should agree.271272## Resource Map273274- See [`reference.md`](reference.md) for compact one-shot + stepwise + diversity snippets.275- See [`references/source-grounding.md`](references/source-grounding.md) for verified pipeline + `ov.micro` API signatures and the docstring backfill log (`Alpha.shannon` / `Alpha.observed` / `Beta.braycurtis` / `Ordinate.nmds` / `Ordinate.proportion_explained` / `DA.deseq2`).276- For DA method comparison (Wilcoxon vs pyDESeq2 vs ANCOM-BC), see `omicverse-microbiome-da-comparison`.277- For cross-cohort meta-analysis on the resulting AnnDatas, see `omicverse-microbiome-meta-analysis`.278- For phylogenetic tree + UniFrac + Faith PD, see `omicverse-microbiome-phylogeny`.279- For paired microbiome + metabolomics analysis, see `omicverse-micro-metabol-paired`.280281## Examples282- "Run the vsearch one-shot pipeline on the mothur MiSeq SOP FASTQs with `filter_max_ee=1.0` and the RDP v18 SINTAX DB; return an AnnData."283- "Re-run the same FASTQs with `backend='dada2'` and `filter_max_ee=2.0`; confirm diversity patterns match the vsearch result."284- "Compute Shannon + observed-OTUs alpha at the shallowest depth, then Bray-Curtis beta + 3-D PCoA, and plot the first two PCs colored by `obs['group']`."285- "Run Wilcoxon DA between Early and Late at phylum level with `min_prevalence=0.1` and report the top 10 hits."286287## References288- Tutorial notebooks:289 - [`t_16s_amplicon.ipynb`](https://omicverse.readthedocs.io/en/latest/Tutorials-microbiome/t_16s_amplicon/) — vsearch end-to-end + alpha/beta/PCoA/DA on the mothur SOP.290 - [`t_16s_dada2.ipynb`](https://omicverse.readthedocs.io/en/latest/Tutorials-microbiome/t_16s_dada2/) — DADA2 backend on the same FASTQs.291- Live API verified — see [`references/source-grounding.md`](references/source-grounding.md).