20260910 113033 宏基因组微生物分类鉴定全流程实现详解

20260910_113033_宏基因组微生物分类鉴定全流程实现详解.md

宏基因组微生物分类鉴定全流程实现详解

基于 bioSkills 技能库(561 个生物信息学技能)的案例 3 实现
涉及技能:5 个 bio-metagenomics- / bio-microbiome- 技能 | 场景:肠道宏基因组 shotgun 测序数据


场景假设

研究者拿到一批人类肠道宏基因组(shotgun 全基因组鸟枪法)测序数据(双端 150bp),目标是:

  1. 回答”样本里有哪些微生物”(分类鉴定,物种水平)
  2. 估计各物种的相对丰度(细胞分数 vs 读段分数)
  3. 解析群落的功能潜能(基因家族与代谢通路)
  4. 比较不同分组(健康 vs 疾病)的群落多样性

数据特征:paired-end reads,含宿主污染(人类 DNA)、接头与低质量碱基,需要先质控再去宿主。


第一步:质控与宿主去除(前置必备)

技能:bio-fastq-quality-control + bio-contamination-screening(或 bio-contamination-controls)
工具:fastp + BBDuk / KneadData

1.1 质控

fastp -i R1.fastq.gz -I R2.fastq.gz \
    -o clean_R1.fastq.gz -O clean_R2.fastq.gz \
    --qualified_quality_phred 20 --length_required 50 \
    --detect_adapter_for_pe --thread 8

关键点:
- --length_required 50 保留至少 50 bp 的读段(Kraken2 对短读分类效果差)
- 去接头是分类的前置条件,接头会制造”宿主 DNA”假信号

1.2 宿主去除

bowtie2 -x human_genome_index -1 clean_R1.fastq.gz -2 clean_R2.fastq.gz \
    --un-conc-gz host_removed_R%.fastq.gz --very-sensitive

关键点:宿主 reads 占低生物量样本的绝大部分,不去除会淹没微生物信号;且 GenBank 中 >200 万条记录带有人类/载体污染序列(Steinegger & Salzberg 2020),必须去宿主。


二、Kraken2 分类 + Bracken 丰度重估

技能:bio-metagenomics-kraken(primary_tool: Kraken2)+ bio-abundance-estimation(Bracken)
工具:Kraken2 + Bracken + KrakenTools

2.1 基础分类命令

kraken2 --db "$KRAKEN_DB" \
    --paired --gzip-compressed --threads 8 \
    --confidence 0.1 \            # 从默认0提高到0.1-0.4,抑制单k-mer假阳性
    --minimum-hit-groups 2 \      # 要求>=2个独立hit区域(临床建议3)
    --report out.kreport \
    --output out.kraken \
    R1.fq.gz R2.fq.gz

核心洞察——Kraken 报告是数据库条件的相似性账本,不是样本清单:
1. 分类 ≠ 存在:--confidence 0 时一个共享 k-mer 就把读分类到数据库最近亲属,即使真实生物不存在
2. 读数 ≠ 丰度:counts 随基因组长度和拷贝数变化,报告百分比是片段分数,不是细胞分数。Bracken 修复的是错误分类层级问题,不修复这个
3. 报告尾部的分类单元是假设,不是发现:单区域 hit、哈希碰撞、污染参考序列会填充长尾

2.2 假阳性控制:独特 minimizer

kraken2 --db "$KRAKEN_DB" --paired --confidence 0.1 \
    --report-minimizer-data \     # 插入2列:总 minimizer + 独特 minimizer
    --report out.krep --output /dev/null \
    R1.fq.gz R2.fq.gz
# 高reads但低独特minimizer的物种 = 假阳性(单一保守区域反复命中)

不要硬编码 “>=1024 unique k-mers”——那是临床数据/特定数据库的经验值,不是常量。用阴性对照标定自己的阈值。

2.3 自建数据库

kraken2-build --download-taxonomy --db custom_db
for lib in bacteria archaea viral human UniVec_Core; do
    kraken2-build --download-library "$lib" --db custom_db
done
kraken2-build --build --db custom_db --threads 16
kraken2-build --clean --db custom_db   # 删除 library/ + taxonomy/ 减小体积
bracken-build -d custom_db -t 16 -k 35 -l 150   # -k 必须等于 Kraken2 的 k(35);-l = 读长

大坑:
- --max-db-size 会随机下采样 k-mers 来凑容量,这就是预建 8GB/16GB 数据库的做法,也是为什么高 confidence 会崩溃——读太少无法通过高阈值
- 数据库版本是最大的实验变量:数据库里没有的分类单元,无论怎么调参都检测不到

2.4 交接给 Bracken

bracken -d "$KRAKEN_DB" -i out.kreport -o out.bracken -w out.bracken.kreport \
    -r 150 \   # 必须匹配 built databaseRLENmers.kmer_distrib 且等于真实读长
    -l S -t 10 # species level;-t 是重分配下限,不是置信度

Bracken 修复的只有”错误层级”问题——它把滞留在 genus 的 reads 按基因组先验重新分配到物种。它不修复基因组大小偏差,也不修复假阳性(甚至可能把不存在物种的 reads 重新分配给最近的近亲来放大它)。所以 先做假阳性控制再跑 Bracken。

2.5 常见错误对照表

错误/症状 原因 解决
成百上千个不可能物种 --confidence 0,无 hit-group 下限 提高 confidence;--minimum-hit-groups >=2
Bracken “kmer_distrib file not found” -r 没有匹配的内置分布 bracken-build -l <readlen>
报告列错位 --report-minimizer-data 插入了2列 用 8 列布局解析
小型数据库近乎零分类 截断数据库 + 高 confidence 用大数据库,或降低 confidence

三、MetaPhlAn 4:标记基因法(互补视角)

技能:bio-metagenomics-metaphlan(primary_tool: MetaPhlAn)
工具:MetaPhlAn 4.1+ / Bowtie2 / minimap2

3.1 基本分类

# paired-end 作为 ONE 逗号分隔参数(MetaPhlAn 按单端处理,不用配对信息)
metaphlan reads_R1.fastq.gz,reads_R2.fastq.gz \
    --input_type fastq \
    --index mpa_vJun23_CHOCOPhlAnSGB_202403 \   # 钉住 index,数据库版本是批次变量
    --nproc 8 \
    --mapout sample.map.bz2 \                    # 缓存 read->marker 映射(4.2 前是 --bowtie2out)
    --output_file profile.txt

核心洞察——MetaPhlAn 百分比是细胞分数,不是读段分数:
- 标记基因测 WHO 在(细胞);k-mer 测 HOW MUCH DNA 在(读段)
- MetaPhlAn 的百分比是基因组大小归一化的分类丰度;Kraken/Bracken 的百分比是 reads 的序列丰度
- 两者不可合并:它们衡量不同量纲的东西(Sun 2021 Nat Methods 18:618),合并成一张表互相比对是常见错误

3.2 SGB 单位

MetaPhlAn 4 的原子分类单位是 SGB(物种级基因组分箱,~95% ANI 簇):
- kSGB 有培养参考基因组、有拉丁名
- uSGB 只由 MAG 定义(需要 >=5 个 MAG),报告为占位 ID 没有名字——不要丢弃 uSGB 行,这是 MetaPhlAn 4 的核心创新,能解释 ~20% 更多肠道读段

3.3 从映射缓存重剖分

metaphlan sample.map.bz2 --input_type mapout \   # 前4.2是 bowtie2out
    --tax_lev s \            # k,p,c,o,f,g,s,t
    --stat_q 0.2 \          # 截断均值:去掉上下20%,平均中间60%
    --output_file profile_species.txt

保存一次 --mapout,之后不同层级/估计器设置无需重新比对——重比对是昂贵步骤,下游全部免费。

3.4 未知分数的缩放效应

  • 未知估计关闭(4.2 前默认):已知 taxa 吸收 100%,数据库缺失群落不可见,高估每个已知分类单元
  • 未知估计开启(4.2 默认):出现 UNCLASSIFIED 行,每个已知丰度按比例缩小
  • 在土壤/海洋/瘤胃中,未知分数可能是样本中最大的”taxon”
  • 4.2 两个 flag 重命名:--bowtie2out → --mapout;--bowtie2db → --db_dir

3.5 MetaPhlAn 常见错误

错误 原因 解决
与 Kraken 百分比合并 分子分数 vs 读段分数,量纲不同 分开放;如需统一用基因组长度换算
低映射率 = QC 失败 只打标记基因,低映射正常 低映射正常;大未知 = 数据库缺失
合并后行不齐 不同 index 构建的 profiles 钉住一个 index 全部重建

四、HUMAnN 3:功能谱分析

技能:bio-metagenomics-functional-profiling(primary_tool: HUMAnN)
工具:HUMAnN 3.6+ / MetaPhlAn / DIAMOND / Bowtie2

4.1 分层搜索算法

HUMAnN 不是对每条读段暴力翻译。三个层级逐层过滤:

  1. 分类学预筛(MetaPhlAn):检测物种,然后只对高于 --prescreen-threshold(默认 0.01%)的物种构建样本特异 ChocoPhlAn 泛基因组
  2. 核苷酸泛基因组搜索(Bowtie2):比对到泛基因组的读段得到 UniRef90 家族 + 高置信物种标签
  3. 翻译蛋白搜索(DIAMOND):未通过的读段 6 框翻译后比对全 UniRef90/50;只有家族,物种是推断的 |unclassified

4.2 运行命令

# 预QC:接头/质量trim + 去宿主(例如 KneadData)。宿主读段膨胀 UNMAPPED 且浪费 DIAMOND 时间
cat sample_R1.fq.gz sample_R2.fq.gz > sample.fq.gz   # 配对读段没有原生配对,拼接成单文件
humann --input sample.fq.gz --output out/ \
    --taxonomic-profile sample_metaphlan.tsv \   # 复用 MetaPhlAn profile;不要 --remove-temp-output
    --threads 8   # 默认:prescreen 0.01, translated-id 80, gap-fill on, minpath on

# 归一化:跨样本统计前先 PER SAMPLE 归一化(RPK 是深度依赖的)
humann_renorm_table -i out/sample_genefamilies.tsv -o out/sample_cpm.tsv -u cpm
humann_join_tables -i out -o merged_pathabundance.tsv --file_name pathabundance
humann_regroup_table -i merged_pathabundance.tsv -g uniref90_ko -o merged_ko.tsv   # 会加 UNGROUPED 行
humann_split_stratified_table -i merged_pathabundance.tsv -o .   # 统计跑在 UNSTRATIFIED 文件上

4.3 不要丢弃 UNMAPPED/UNINTEGRATED

import pandas as pd

df = pd.read_csv('merged_pathabundance_unstratified.tsv', sep='\t', index_col=0)
meta = pd.read_csv('metadata.tsv', sep='\t', index_col=0)
unmapped = df.loc[['UNMAPPED', 'UNINTEGRATED']]     # 分母,检查但不要丢弃
g1 = meta.index[meta['condition'] == 'healthy']
g2 = meta.index[meta['condition'] == 'disease']
unmapped_shift = unmapped[g2].mean(axis=1) - unmapped[g1].mean(axis=1)
print(unmapped_shift.round(1))
# 然后交给 MaAsLin2/ANCOM-BC(对组分与零膨胀建模),而不是裸 t-test/Mann-Whitney

为什么不能丢:丢弃后重新归一化会把已分配的特征重新缩放到和为 1,凭空制造丰度并抹掉数据库覆盖信号。两个样本 UNMAPPED 20% vs 60% 会看起来一模一样。

4.4 功能谱 = 潜力,不是活性

一个基因家族或”完整通路”是该群落编码的能力——从来不是速率、通量或表达证据。RNA(宏转录组)才说表达。叙述”疾病微生物组上调 X”是绝对错误——它只是更丰富地携带了该基因。


五、多样性分析(α 和 β 多样性)

技能:bio-microbiome-diversity-analysis(primary_tool: phyloseq)
工具:QIIME2 / phyloseq / vegan / scikit-bio

5.1 三个”把手”决定答案

一个多样性值不是测量,而是三个选择的输出:
1. 抽平深度(rarefaction depth)
2. 系统发育树(tree)
3. 度量(metric)

--p-sampling-depth 是”披着归一化外衣的样本删除旋钮”:core-metrics 把每个样本稀平到该深度,并静默丢弃总计数低于它的样本——不警告,只是 PCoA 上少几个点。丢弃的通常是最低产出的样本(最低生物量的拭子、病重患者、失败提取),丢失几乎从不随机。

5.2 选择抽样深度

qiime feature-table summarize --i-table table.qza --o-visualization table.qzv  # 深度信息在这
qiime diversity alpha-rarefaction \
    --i-table table.qza --i-phylogeny rooted-tree.qza \
    --p-max-depth 20000 \
    --m-metadata-file metadata.tsv --o-visualization alpha-rarefaction.qza
qiime diversity core-metrics-phylogenetic \
    --i-phylogeny rooted-tree.qza --i-table table.qza \
    --p-sampling-depth 10000 \    # 在 observed-features 平台上;会静默丢弃低深度样本
    --m-metadata-file metadata.tsv --output-dir core-metrics-results

规则:深度要在 observed-features 平台的饱和区;不要用 min(sample_sums)——一个小文库会把所有样本拖到欠饱和噪声;报告丢弃样本列表;在邻近深度确认结论。

5.3 建树(建模选择)

优先 SEPP 片段插入到全长参考树(或 Greengenes2),而不是 de novo MAFFT+FastTree。~250bp 读长的 de novo 树分辨率差、中点根任意,会让 unweighted UniFrac 不稳定(Janssen 2018)。

qiime fragment-insertion sepp \
    --i-representative-sequences rep-seqs.qda \
    --i-reference-database sepp-refs-gg-13-8.qda \
    --p-threads 4 \
    --o-tree insertion-tree.qda --o-placements insertion-placements.qda
qiime fragment-insertion filter-features \
    --i-table table.qda --i-tree insertion-tree.qda \
    --o-filtered-table table-sepp.qda --o-removed-table removed-table.qda

5.3 α 多样性(R)

library(phyloseq); library(vegan)
ps_rare <- rarefy_even_depth(ps, sample.size = chosen_depth, rngseed = 42, replace = FALSE)
alpha <- estimate_richness(ps_rare, measures = c('Observed', 'Shannon', 'InvSimpson'))
alpha$Group <- sample_data(ps_rare)$Group
alpha$Shannon_eff <- exp(alpha$Shannon)   # 有效种数(Hill q=1),跨工具可比
kruskal.test(Shannon ~ Group, data = alpha)

QIIME2 的 Shannon 是 log2(bits),R 是自然对数(nats)——数值差一个常数因子,直接比较看起来像真实效应。报告 exp(H') 可绕过基数问题。

5.4 β 多样性(必须加权 + 非加权都要报告)

wu  <- UniFrac(ps_rare, weighted = TRUE)    # 优势谱系视角
uwu <- UniFrac(ps_rare, weighted = FALSE)   # 稀有谱系 + 拓扑视角
gu  <- as.dist(GUniFrac::GUniFrac(t(as(otu_table(ps_rare), 'matrix')), phy_tree(ps_rare), alpha = 0.5)$unifracs[, , 'd_0.5'])

meta <- data.frame(sample_data(ps_rare))
adonis2(wu ~ Group, data = meta, permutations = 999)   # 显著 = 质心位置差异
permutest(betadisper(wu, meta$Group))                  # 强制:是离散度差异还是位置差异?

如果不做 betadisper,adonis2 的显著可能只是”一组变异更大”而不是”群落组成不同”——尤其不平衡设计(Anderson & Walsh 2013)。unweighted 与 weighted 结论相反时,说明变化在稀有谱系 vs 优势谱系,必须都报告并说明各自暗示的谱系轴。


六、可视化与群落统计

技能:bio-metagenomics-visualization(primary_tool: phyloseq)
工具:phyloseq / vegan / microViz / pandas + scikit-bio / matplotlib

6.1 诚实的组成图

Krona 是”里面有什么”的最诚实答案——完整可钻取层级,而不是预先折叠到 top-10 的柱状图:

krepdeport2krona.py -r kraken.kreport -o krona.txt && ktImportText krona.txt -o krona.html

柱状图规则:把稀有 taxa 折叠成”Other”时必须标注隐藏了多少 taxa 和多少百分比;每 n 展示(绝不叠加一个代表样本);≤12 种色盲安全色;如果有总生物量数据就同时展示绝对负载(相对丰度会隐藏群落 bloom/collapse 的真实幅度)。

6.2 α 多样性:Hill 数 + 丰富度诚实性

alpha <- estimate_richness(ps_counts, measures = c('Shannon', 'InvSimpson'))
hill_q1 <- exp(alpha$Shannon)   # 有效种数 q=1
hill_q2 <- alpha$InvSimpson     # q=2 优势度加权

shotgun richness 是数据库 + 置信度阈值的读出,不是生物学:25-70% 的 shotgun 物种可能是假阳性。Observed/Chao1 只能用在整数 counts(Bracken 可以,MetaPhlAn 百分比不行)。

6.3 β 多样性:组成学正确选择

距离 组成学正确 需要树 何时
Bray-Curtis 否 否 领域默认、直观;标注不协调
Aitchison(CLR 上的欧氏) 是 否 组成学正确选择,需处理零
RPCA (DEICODE) 是 否 稀疏无伪计数
UniFrac(加权/非加权) 部分 是 仅当有树(MetaPhl SGB 树)
library(vegan)
dist_bc <- vegdist(otu_matrix, method = 'bray')
pm <- adonis2(dist_bc ~ group, permutations = 999, by = 'terms')
bd <- betadisper(dist_bc, group); permutest(bd, permutations = 999)  # 必须配对

6.4 Python 组成学排序

import pandas as pd
from skbio.stats.composition import clr, multi_replace
from sklearn.decomposition import PCA

ab = pd.read_csv('merged_abundance.txt', sep='\t', index_col=0)
ab = ab[ab.index.str.contains(r'\|s__') & ~ab.index.str.contains(r'\|t__')]  # 物种行
proportions = (ab.T.values / ab.T.values.sum(axis=1, keepdims=True))
clr_mat = clr(multi_replace(proportions))      # 零替换 + CLR(不是 StandardScaler)
pca = PCA(n_components=2).fit(clr_mat)
coords = pca.transform(clr_mat)

6.5 差异丰度:共识,不是单工具

DA 方法跨数据集中差异巨大(Nearing 2022):报告的显著 taxa 更依赖工具而非生物学。至少跑两个组成学感知的方法,交集 = 高置信,并集 = 探索性,全部报告。ALDEx2 与 ANCOM-II 最保守/一致;LinDA 与 ANCOM-BC FDR 控制好。禁止:未校正的 Wilcoxon/t-test 直接作用于原始相对丰度——一行代码同时错在组成学和多重检验上。

工具 模型 引用
ALDEx2 Dirichlet 蒙特卡洛 → CLR Fernandes 2014 Microbiome
ANCOM-BC log-linear + 抽样分数偏倚校正 Lin & Peddada 2020 Nat Commun
MaAsLin2 一般线性模型 + 协变量 Mallick 2021 PLoS Comput Biol
LinDA CLR 上线性模型 + 偏倚校正 Zhou 2022 Genome Biol

七、全流程总结

原始 shotgun reads(R1+R2)
  │
  ├─ Step 1: fastp 质控(去接头、去低质量)→ Bowtie2 宿主去除
  │
  ├─ Step 2: Kraken2 分类(--confidence 0.1 --minimum-hit-groups 2)
  │          → Bracken 丰度重估计(-r 读长匹配)
  │          → 假阳性控制(--report-minimizer-data / KrakenUniq)
  │
  ├─ Step 3: MetaPhlAn 4 标记基因剖面(细胞分数,uSGB 量化未知 taxa)
  │          (注意:与 Kraken 分数不可合并)
  │
  ├─ Step 4: HUMAnN 3 功能谱(MetaPhl 预筛 → Bowtie2 泛基因组 → DIAMOND)
  │          → 保留 UNMAPPED/UNINTEGRATED → 归一化到 CPM
  │
  ├─ Step 5: 多样性(α: Hill q=0/1/2;β: adonis2 + betadisper 配对)
  │
  └─ Step 6: 可视化(Krona / 堆叠柱 / CLR-PCA / 差异丰度共识)

涉及技能一览

步骤 技能名 工具 核心作用
1 bio-metagenomics-kraken Kraken2 + Bracken k-mer 分类 + 丰度重估计
1-2 bio-metagenomics-metaphlan MetaPhlAn 4 标记基因分类(细胞分数)
3 bio-metagenomics-functional-profiling HUMAnN 3 功能基因 + 通路丰度
4 bio-microbiome-diversity-analysis QIIME2/phyloseq α/β 多样性
5 bio-metagenomics-visualization phyloseq/vegan/microViz 可视化 + 群落统计

三步走总结

  1. “谁在里面”:Kraken2 + Bracken(读段分数)+ MetaPhlAn(细胞分数)双视角
  2. “能做什么”:HUMAnN 功能谱(潜力,不是活性)
  3. “怎么比较”:多样性 + 排序 + 差异丰度共识