| name | metagenomics-shotgun |
| description | Run Bowtie2 decontamination, Kraken2/Bracken classification, HUMAnN3 pathways, and MEGAHIT/MetaBAT2/CheckM MAG recovery on shotgun metagenomes. Use for WMS/WGS metagenomics, microbiome profiling, or MAG binning. |
| tool_type | bash |
| primary_tool | Kraken2 |
Metagenomics: Shotgun & 16S
When to Use
- Processing whole-metagenome shotgun sequencing (WMS) data for taxonomic and functional profiling
- Doing species/strain-resolution community profiling beyond what 16S can achieve
- Annotating community metabolic/functional pathways (UniRef, MetaCyc)
- Recovering and QC'ing metagenome-assembled genomes (MAGs)
- Running 16S amplicon analysis with QIIME2, or comparing alpha/beta diversity across groups
Version Compatibility
Kraken2 ≥2.1.3, Bracken ≥2.9, HUMAnN ≥3.9 (biobakery), MEGAHIT ≥1.2.9, MetaBAT2 ≥2.15/2.17, CheckM ≥1.2 (or CheckM2 ≥1.0), QIIME2 ≥2024.2, Bowtie2 ≥2.5, Python ≥3.10 (pandas, numpy, scipy), R ≥4.3 with vegan ≥2.6.
Prerequisites
- Tools:
bowtie2, samtools, kraken2 + a reference DB (standard or PlusPF), bracken, humann (+ UniRef90/ChocoPhlAn DBs, ~25 GB), megahit, metabat2, checkm, qiime2
- Python:
pandas, numpy, scipy; R: vegan
- Prior concepts: FASTQ QC and read alignment (see
bio-read-qc, bio-read-alignment-bowtie2-alignment)
Quick Reference
| Task | Tool | Key Command |
|---|
| Host decontamination | Bowtie2 | bowtie2 --un-conc-gz decontam |
| Taxonomic profiling | Kraken2 | kraken2 --report report.txt |
| Abundance re-estimation | Bracken | bracken -d db -i report.txt -l S |
| Functional pathways | HUMAnN3 | humann --input reads.fq.gz |
| Assembly | MEGAHIT | megahit -1 R1 -2 R2 --min-contig-len 500 |
| Binning | MetaBAT2 | metabat2 -i contigs.fa -a depths.txt |
| Bin QC | CheckM | checkm lineage_wf bins/ out/ |
| MAG annotation | Prokka | prokka --metagenome bin.fa |
| 16S analysis | QIIME2 | qiime dada2 denoise-paired |
Core Workflow
Goal: Classify reads taxonomically without host contamination inflating the counts.
Approach: align to the host genome and keep only unmapped read pairs, then classify with Kraken2 and re-estimate species-level abundance with Bracken (Kraken2's LCA algorithm over-assigns reads to higher ranks).
bowtie2 -x hg38 -1 R1.fq.gz -2 R2.fq.gz \
--un-conc-gz decontam_%.fq.gz > /dev/null
kraken2 --db standard/ --paired --gzip-compressed \
decontam_1.fq.gz decontam_2.fq.gz \
--report kraken2_report.txt --output kraken2_out.txt
bracken -d standard/ -i kraken2_report.txt \
-o bracken_species.txt -r 150 -l S -t 10
Goal: Determine what the community is doing metabolically, not just who is there.
Approach: run HUMAnN3 on decontaminated, merged reads to get gene-family and pathway abundances, then normalize and join across samples for comparison.
humann --input decontam_merged.fq.gz \
--output humann3_out/ --threads 8
humann_renorm_table --input humann3_out/sample_pathabundance.tsv \
--output pathways_cpm.tsv --units cpm
humann_join_tables --input humann3_outputs/ \
--output all_pathways.tsv --file_name pathabundance
Goal: Recover genome-resolved metagenome-assembled genomes (MAGs).
Approach: assemble contigs de novo, compute per-contig coverage depth from mapped reads, bin contigs by tetranucleotide composition + coverage, then QC each bin's completeness/contamination.
megahit -1 decontam_1.fq.gz -2 decontam_2.fq.gz \
-o megahit/ --min-contig-len 500 -t 16
bowtie2-build megahit/final.contigs.fa contigs_index
bowtie2 -x contigs_index -1 decontam_1.fq.gz -2 decontam_2.fq.gz | \
samtools sort -o contigs.bam && samtools index contigs.bam
jgi_summarize_bam_contig_depths --outputDepth depths.txt contigs.bam
metabat2 -i megahit/final.contigs.fa -a depths.txt -o bins/bin --minContig 1500
checkm lineage_wf bins/ checkm_out/ -t 8 -x fa
16S QIIME2 Workflow
qiime tools import \
--type 'SampleData[PairedEndSequencesWithQuality]' \
--input-path manifest.csv --output-path reads.qza \
--input-format PairedEndFastqManifestPhred33V2
qiime dada2 denoise-paired \
--i-demultiplexed-seqs reads.qza \
--p-trunc-len-f 250 --p-trunc-len-r 200 \
--o-table table.qza --o-representative-sequences rep_seqs.qza
qiime feature-classifier classify-sklearn \
--i-classifier silva138_classifier.qza \
--i-reads rep_seqs.qza --o-classification taxonomy.qza
qiime diversity core-metrics-phylogenetic \
--i-table table.qza --i-phylogeny rooted_tree.qza \
--p-sampling-depth 5000 --m-metadata-file metadata.tsv \
--output-dir diversity/
MAG Quality Standards (MIMAG)
| Tier | Completeness | Contamination |
|---|
| High quality | ≥ 90% | < 5% |
| Medium quality | ≥ 50% | < 10% |
| Low quality | < 50% | — |
Diversity Analysis (Python)
Goal: Quantify within-sample (alpha) and between-sample (beta) diversity from a taxa-by-sample abundance table.
Approach: compute Shannon/Simpson/richness per sample, then build a Bray-Curtis distance matrix, ordinate it with classical PCoA, and test group separation with a permutation-based PERMANOVA.
import numpy as np
import pandas as pd
from scipy.spatial.distance import braycurtis
def shannon_diversity(counts) -> float:
"""Shannon H' — richness + evenness; 0 = no diversity."""
counts = np.array(counts, dtype=float)
p = counts[counts > 0] / counts[counts > 0].sum()
return -np.sum(p * np.log(p))
def simpson_diversity(counts) -> float:
"""Simpson 1-D — probability two random reads differ; robust to rare taxa."""
counts = np.array(counts, dtype=float)
p = counts[counts > 0] / counts[counts > 0].sum()
return 1 - np.sum(p ** 2)
def observed_richness(counts) -> int:
return int(np.sum(np.array(counts) > 0))
def pcoa(dm: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
"""Classical PCoA on a distance matrix. Returns (coords n x 2, variance_explained[:2])."""
n = len(dm)
H = np.eye(n) - np.ones((n, n)) / n
B = -0.5 * H @ (dm ** 2) @ H
eigvals, eigvecs = np.linalg.eigh(B)
order = np.argsort(eigvals)[::-]
eigvals, eigvecs = eigvals[order], eigvecs[:, order]
pos = eigvals >
coords = eigvecs[:, pos] * np.sqrt(eigvals[pos])
prop = eigvals[pos] / eigvals[pos].()
coords[:, :], prop[:]
() -> [, ]:
unique = np.unique(grouping)
n = (grouping)
():
ss_tot = np.(dm ** ) / n
ss_w = (
np.(dm[np.ix_(g == grp, g == grp)] ** ) / ( * (g == grp).())
grp unique (g == grp).() >
)
df_b, df_w = (unique) - , n - (unique)
((ss_tot - ss_w) / df_b) / (ss_w / df_w) df_w ss_w
obs = f_stat(grouping)
perm_count = (f_stat(np.random.permutation(grouping)) >= obs _ (n_perm))
obs, (perm_count + ) / (n_perm + )
otu = pd.read_csv(, sep=, index_col=)
alpha = pd.DataFrame({
: otu.apply(shannon_diversity, axis=),
: otu.apply(simpson_diversity, axis=),
: otu.apply(observed_richness, axis=),
})
otu_rel = otu.div(otu.(axis=), axis=)
n = (otu_rel)
dist = np.zeros((n, n))
i (n):
j (i + , n):
dist[i, j] = dist[j, i] = braycurtis(otu_rel.iloc[i], otu_rel.iloc[j])
coords, prop_explained = pcoa(dist)
f_stat, p_value = permanova(dist, grouping=np.array([, ] * (n // )))
Diversity Analysis (R / vegan)
Goal: Same diversity/PERMANOVA workflow using the standard R ecology stack.
Approach: vegan::diversity/specnumber for alpha metrics, vegdist + adonis2 for beta diversity and group testing, betadisper to check the equal-dispersion assumption PERMANOVA relies on.
library(vegan)
otu <- read.delim("otu_table.tsv", row.names = 1, check.names = FALSE)
meta <- read.delim("metadata.tsv", row.names = 1)
alpha <- data.frame(
shannon = diversity(otu, index = "shannon"),
simpson = diversity(otu, index = "simpson"),
richness = specnumber(otu)
)
bc_dist <- vegdist(otu, method = "bray")
perm <- adonis2(bc_dist ~ group, data = meta, permutations = 999)
printperm
disp betadisperbc_dist metagroup
anovadisp
Parsing Outputs (Python)
import pandas as pd
def read_kraken2_report(path: str) -> pd.DataFrame:
"""Parse a Kraken2 report (6-column TSV: pct, clade_reads, direct_reads, rank, taxid, name)."""
cols = ["pct", "clade_reads", "direct_reads", "rank", "taxid", "name"]
df = pd.read_csv(path, sep="\t", header=None, names=cols)
df["name"] = df["name"].str.strip()
return df
def read_checkm(path: str) -> pd.DataFrame:
"""Parse CheckM lineage_wf qa output and flag MIMAG quality tiers."""
df = pd.read_csv(path, sep="\t")
df.columns = [c.strip().lower().replace(" ", "_") for c in df.columns]
high_q = df[(df["completeness"] >= 90) & (df["contamination"] < 5)]
med_q = df[(df["completeness"] >= 50) & (df["contamination"] < 10)]
print(f"High-quality MAGs: {len(high_q)}")
print(f"Medium-quality MAGs: {len(med_q)}")
return df
report = read_kraken2_report("kraken2_report.txt")
species = report[report[] == ].sort_values(, ascending=)
(species[[, ]].head().to_string(index=))
Pitfalls
- Host decontamination is critical — failure to remove host reads inflates classification rates and skews abundances
- Kraken2 database choice — standard (archaea + bacteria + viral) vs PlusPF (adds protozoa/fungi) vs custom database changes what can be detected
- Bracken threshold —
-t 10 requires ≥10 reads assigned to a taxon; lower it for low-coverage samples, but expect noisier low-abundance calls
- HUMAnN3 databases — requires UniRef90 and ChocoPhlAn reference databases (~25 GB) downloaded separately before running
- Binning quality — MetaBAT2 needs ≥2x coverage and ≥500 bp contigs; combine with CONCOCT/MaxBin2 and DAS Tool for bin refinement
- Rarefying without checking curves — rarefy only when rarefaction curves plateau, otherwise you discard real signal
- Comparing raw alpha diversity without rarefaction — deeper-sequenced samples will always appear more diverse
- PERMANOVA sensitivity to dispersion — significant
adonis2 results can reflect unequal within-group variance, not just centroid differences; always pair with betadisper
See Also
bio-read-qc — FASTQ quality control and adapter trimming before decontamination
bio-read-alignment-bowtie2-alignment — host-genome alignment mechanics used for decontamination
bio-microbiome-diversity-analysis — deeper alpha/beta diversity statistics
bio-genome-assembly-metagenome-assembly — MEGAHIT/binning assembly details