| name | omicverse-microbiome-phylogeny |
| description | Build an ASV-level phylogenetic tree (MAFFT alignment + FastTree GTR+Γ), attach it to a 16S AnnData, and run phylogenetically-aware diversity (Faith PD, weighted / unweighted UniFrac). Use when adding tree-aware metrics on top of the basic 16S amplicon AnnData, or when the cohort needs UniFrac-driven beta-diversity instead of Bray-Curtis. |
OmicVerse Microbiome — Phylogenetic Diversity
Goal
Take a 16S amplicon AnnData (samples × ASVs, with ASV centroid sequences in var) plus the ASV centroid FASTA from the amplicon pipeline, build a per-cohort phylogenetic tree (MAFFT alignment → FastTree under GTR+Γ), attach it to adata.uns['tree'], and run the phylogenetically-aware diversity stack: Faith's PD (alpha) and unweighted / weighted UniFrac (beta). Compare against non-phylogenetic baselines (Bray-Curtis) to confirm that phylogeny adds signal — or that it doesn't.
This skill assumes the upstream omicverse-microbiome-16s-amplicon-dada2 skill has already produced the AnnData and the ASV FASTA (the FASTA is the nochim['asv'] output of the stepwise pipeline, or is regenerable from the saved AnnData).
Quick Workflow
- Load the saved AnnData; identify the ASV FASTA from the workdir.
- Build the tree:
tree = ov.alignment.build_phylogeny(asvs_fasta, workdir, mafft_threads=8, fasttree_threads=4). Returns a dict with aligned, tree, newick paths/strings.
- Attach to AnnData:
ov.micro.attach_tree(adata, newick=tree['newick']). Stores at adata.uns['tree']; tip-name validation runs by default.
- Faith PD (alpha): include
'faith_pd' in Alpha(adata).run(metrics=[...]). Result column lands in adata.obs['faith_pd'].
- UniFrac (beta):
Beta(adata).run(metric='unweighted_unifrac') and metric='weighted_unifrac'. Distance matrices land in adata.obsp[<metric>]. Both call out to the unifrac package which expects rooted newick (FastTree midpoint-roots automatically).
- Compare with Bray-Curtis: run all three on the same AnnData; PCoA on each; visually compare PC1/PC2 cluster separation. UniFrac usually agrees with Bray-Curtis on cohorts dominated by abundance shifts; it diverges when the which taxa shifted matters more than how much.
- Save the now-tree-augmented AnnData (
adata.write_h5ad(...)).
Interface Summary
Tree construction:
ov.alignment.build_phylogeny(
asvs_fasta: str,
workdir: Optional[str] = None, *,
mafft_mode: str = 'auto',
fasttree_model: str = 'gtr',
gamma: bool = True,
mafft_threads: int = 4,
fasttree_threads: Optional[int] = None,
overwrite: bool = False,
) -> Dict[str, str]
Attaching the tree:
ov.micro.attach_tree(
adata: AnnData,
newick: Optional[str] = None,
tree_path: Optional[Union[str, Path]] = None,
prune: bool = True,
store_key: str = 'tree',
strict: bool = False,
) -> AnnData
Phylogenetically-aware metrics (existing ov.micro API; require the tree to be attached):
- Alpha:
Alpha(adata).run(metrics=['faith_pd', ...]) — computes Faith's phylogenetic diversity (sum of branch lengths on the subtree induced by present features) per sample. Reads tree from adata.uns[tree_key] (default 'tree').
- Beta:
Beta(adata).run(metric='unweighted_unifrac' | 'weighted_unifrac', rarefy=...). UniFrac distances read the same adata.uns[tree_key].
- Both fall back to a clear
ValueError if the tree is missing.
Boundary
Inside scope:
- ASV-level tree construction with MAFFT + FastTree.
- Tree attachment + tip-name validation against
adata.var_names.
- Faith PD alpha, unweighted/weighted UniFrac beta.
- Comparison plots / metrics across phylo and non-phylo metrics.
Outside scope — separate skill:
- Reference-based tree placement (e.g., placing query ASVs on a SILVA backbone via
gappa / epang) — current implementation builds a de-novo tree from the cohort.
- IQ-TREE / RAxML replacement of FastTree —
build_phylogeny is FastTree-only; IQ-TREE would be a separate function.
- Cross-cohort tree harmonisation — see
omicverse-microbiome-meta-analysis for the meta-analysis-side caveats; this skill builds one tree per cohort.
- Differential abundance with tree-aware tests (gneiss, balance trees) — not implemented in
ov.micro.
- 18S / ITS phylogeny — implementation assumes 16S rRNA (uses GTR+Γ; OK for 16S, less ideal for fast-evolving fungal regions).
Branch Selection
mafft_mode
'auto' (default) — MAFFT picks based on sequence count: FFT-NS-2 for >200 sequences, L-INS-i otherwise. Right answer 95 % of the time.
'L-INS-i' — most accurate for <500 sequences; slower. Use when the cohort is small and you care about alignment quality.
'E-INS-i' — for sequences with very long unalignable regions; rare in 16S V3-V4 amplicons.
fasttree_model='gtr' + gamma=True is the canonical 16S choice. Switching to JC69 ('jc') is for didactic purposes; never report a real result on JC.
fasttree_threads — None (FastTree decides) is fine. Increasing gives diminishing returns past ~4 threads on typical 16S cohorts.
Faith PD is depth-sensitive — like observed_otus, it grows with sequencing depth. Always rarefy first OR pair with Shannon (which is depth-corrected in expectation).
Unweighted vs weighted UniFrac
- Unweighted: presence/absence; sensitive to rare taxa, sensitive to differences in which taxa are present. Use when the question is "do these communities have different membership?"
- Weighted: abundance-weighted; less sensitive to rare taxa, more sensitive to differences in how much of each taxon is present. Use when the question is "how different are the communities in their dominant members?"
- Report both — they often disagree, and the disagreement is informative.
- Don't rarefy weighted UniFrac (
rarefy=False default for UniFrac in ov.micro); rarefy unweighted UniFrac if depths are very uneven.
attach_tree(prune=True) is the right default: prunes tree tips not in adata.var_names, which speeds up UniFrac. strict=True raises on tip mismatches; useful in CI.
Input Contract
adata from the 16S amplicon skill: var_names are ASV ids (asv00001, etc.), var['asv_seq'] (or similar) carries the centroid sequence string.
asvs_fasta: path to the centroid FASTA (nochim['asv'] from the stepwise pipeline or directly from workdir/asv/asvs.fasta after amplicon_16s_pipeline). Tip names must equal var_names.
- External binaries on
$PATH: mafft, FastTreeMP/FastTree. Both are installable via conda install -c bioconda mafft fasttree.
- For UniFrac:
pip install unifrac (or conda install -c bioconda unifrac). The Beta wrapper imports lazily and raises a clear error if missing.
adata.obs[group_col] populated for any group-coloured PCoA plots (not required for tree construction itself).
Minimal Execution Patterns
import omicverse as ov
import anndata as ad
from pathlib import Path
ov.plot_set()
BASE = Path('/scratch/.../cache/16s/run_mothur_sop')
adata = ad.read_h5ad(BASE / 'mothur_sop_16s.h5ad')
tree = ov.alignment.build_phylogeny(
asvs_fasta=str(BASE / 'asv' / 'asvs.fasta'),
workdir=str(BASE / 'phylogeny'),
mafft_threads=8,
fasttree_threads=4,
)
print('tree file:', tree['tree'])
ov.micro.attach_tree(adata, newick=tree['newick'])
print("tree tips:", len(adata.uns['tree']))
ov.micro.Alpha(adata).run(metrics=['shannon', 'observed_otus', 'faith_pd'])
print(adata.obs[['shannon', 'observed_otus', 'faith_pd']].describe())
b = ov.micro.Beta(adata)
b.run(metric='unweighted_unifrac', rarefy=False)
b.run(metric='weighted_unifrac', rarefy=False)
b.run(metric='braycurtis', rarefy=True)
import matplotlib.pyplot as plt, pandas pd
fig, axes = plt.subplots(, , figsize=(, ))
ax, dist_key, title (
axes,
[, , ],
[, , ],
):
ord_ = ov.micro.Ordinate(adata, dist_key=dist_key); ord_.pcoa(n=)
pct = ord_.proportion_explained() *
coords = pd.DataFrame(adata.obsm[],
index=adata.obs_names, columns=[, ])
g, sub adata.obs.groupby():
ax.scatter(coords.loc[sub.index, ], coords.loc[sub.index, ],
label=g, s=, alpha=, edgecolor=)
ax.set_xlabel()
ax.set_ylabel()
ax.set_title(title)
axes[-].legend(title=, frameon=)
plt.tight_layout(); plt.show()
adata.write_h5ad(BASE / )
Validation
tree['tree'] exists on disk (.nwk); tree['newick'] is non-empty text starting with (.
- After
attach_tree: adata.uns['tree'] is a non-empty newick string; adata.uns['micro']['tree_tips'] matches expected ASV count (default prune=True so tips are at most adata.n_vars).
- After Faith PD:
adata.obs['faith_pd'] has no NaNs and grows monotonically with observed_otus on rarefied data (a sanity check).
- UniFrac matrix shapes:
adata.obsp['unweighted_unifrac'].shape == (n_obs, n_obs); off-diagonal mean is in [0, 1] (UniFrac distance is bounded).
- The two UniFrac variants typically disagree on which samples are most similar — that disagreement is itself the signal.
- If
build_phylogeny errors: mafft / FastTree not on $PATH. Check which mafft / which FastTreeMP first.
- If
Beta(...).run(metric='unweighted_unifrac') errors: unifrac package not installed (pip install unifrac) OR tip names don't match adata.var_names (rerun attach_tree(strict=True) to surface mismatches).
Resource Map
- See
reference.md for compact copy-paste snippets.
- See
references/source-grounding.md for verified build_phylogeny / attach_tree / UniFrac dispatch and the docstring backfill log.
- For the AnnData ingest that produces the input, see
omicverse-microbiome-16s-amplicon-dada2.
- For DA on tree-augmented data (Wilcoxon / pyDESeq2 / ANCOM-BC), see
omicverse-microbiome-da-comparison — none of them use the tree directly, but reporting alongside Faith PD / UniFrac is common.
Examples
- "Build a FastTree GTR+Γ phylogeny from
asvs.fasta, attach to the AnnData, and run Faith PD + unweighted/weighted UniFrac."
- "Compare Bray-Curtis, unweighted UniFrac, and weighted UniFrac PCoAs side by side on the mothur SOP cohort."
- "Confirm UniFrac tip names match
adata.var_names (attach_tree(strict=True))."
- "Save the tree-augmented AnnData for downstream meta-analysis."
References