宏基因组组装后分析(Post-Assembly Analysis)¶
一句话概述¶
宏基因组组装后分析涵盖从contigs出发的ORF预测(Prodigal)、基因聚类去冗余(CD-HIT)、功能注释(eggNOG/KEGG/dbCAN)和基因丰度定量(Salmon/CoverM),构建非冗余基因集并量化各样本的功能潜力。
核心知识点表格¶
| 知识点 | 说明 |
|---|---|
| ORF预测 | 从组装contigs上预测蛋白编码基因(Open Reading Frame) |
| Prodigal | 原核生物基因预测的标准工具,宏基因组使用-p meta模式 |
| CD-HIT | 基于序列相似性的快速聚类去冗余工具 |
| 非冗余基因集(NR gene catalog) | 去除冗余后的代表性基因集合,用于统一定量 |
| Salmon/Bowtie2 | 基因丰度定量工具(pseudo-alignment或传统比对) |
| TPM/RPKM/CPM | 丰度归一化方法 |
| eggNOG/KEGG/GO | 功能注释数据库 |
| dbCAN | 碳水化合物活性酶(CAZyme)专项数据库 |
| 功能丰度表 | KO/COG × 样本的定量矩阵,下游差异分析输入 |
| Gene catalog规模 | 人肠道参考基因集(IGC)约990万基因 |
各步骤详解¶
第一步:ORF预测(Prodigal)¶
白话解释: 组装得到的contigs是长DNA片段,里面有很多编码蛋白的区域(基因)。Prodigal用数学模型(基于密码子使用频率和核糖体结合位点特征)自动找出这些基因的位置和方向。
技术细节: - Prodigal使用动态规划算法,基于GC含量自适应训练模型 - -p meta模式:对每个contig独立训练(适合宏基因组的GC多样性) - -p single模式:所有序列用同一模型(适合单基因组) - 输出:GFF(坐标)、FAA(蛋白序列)、FNA(核苷酸序列) - 对于短contig(<500bp),预测精度下降 - Prodigal-short/Pyrodigal是优化版本 - 注意:Prodigal 3.x中-p meta已更名为-p anon(anonymous mode),但仍兼容旧语法-p meta
代码示例:
# 基本运行
prodigal -i assembled_contigs.fa \
-o genes.gff \ # GFF格式基因坐标
-a proteins.faa \ # 蛋白质序列
-d genes.fna \ # 核苷酸CDS序列
-p meta \ # 宏基因组模式(必须!)
-f gff \ # 输出格式
-q # 静默模式
# 统计预测结果
echo "Predicted genes: $(grep -c '>' proteins.faa)"
echo "Complete genes: $(grep -c 'partial=00' genes.gff)"
echo "Partial genes (5'): $(grep -c 'partial=10' genes.gff)"
echo "Partial genes (3'): $(grep -c 'partial=01' genes.gff)"
echo "Partial genes (both): $(grep -c 'partial=11' genes.gff)"
# 过滤短基因(可选,<100aa通常不可靠)
seqkit seq -m 100 proteins.faa > proteins_filtered.faa
# 多样本并行处理
for sample in sample1 sample2 sample3; do
prodigal -i ${sample}_contigs.fa \
-a ${sample}_proteins.faa \
-d ${sample}_genes.fna \
-p meta -f gff -q \
-o ${sample}_genes.gff
done
# 合并所有样本的预测基因(添加样本前缀防止ID冲突)
for sample in sample1 sample2 sample3; do
sed "s/^>/>${sample}_/" ${sample}_genes.fna >> all_genes.fna
sed "s/^>/>${sample}_/" ${sample}_proteins.faa >> all_proteins.faa
done
第二步:基因聚类去冗余(CD-HIT)¶
白话解释: 多个样本预测出来的基因有大量重复(同一物种在不同样本中都存在)。CD-HIT把序列相似的基因聚成一簇,每簇只留一条代表序列,大大减少数据量。这就是"非冗余基因集"。
技术细节: - 标准阈值:95%序列一致性 + 90%覆盖度(模拟同一基因的不同拷贝) - CD-HIT-EST用于核苷酸序列,CD-HIT用于蛋白质序列 - 对于大数据集(>1亿条),使用分步聚类或MMseqs2/Linclust替代 - 代表序列(representative)通常是每簇中最长的那条 - 输出:代表序列FASTA + 聚类关系文件(.clstr)
代码示例:
# === 核苷酸水平聚类(推荐,保留定量能力)===
cd-hit-est -i all_genes.fna \
-o nr_genes.fna \
-c 0.95 \ # 95%序列一致性
-aS 0.9 \ # 90%短序列覆盖度
-n 10 \ # word size(c>0.9时用10)
-d 0 \ # 描述信息全部保留
-M 64000 \ # 内存限制(MB)
-T 16 \ # 线程数
-g 1 # 精确聚类模式
# === 蛋白质水平聚类 ===
cd-hit -i all_proteins.faa \
-o nr_proteins.faa \
-c 0.95 \
-aS 0.9 \
-n 5 \ # word size(c>0.9时用5)
-d 0 \
-M 64000 \
-T 16 \
-g 1
# === 大数据集分步聚类策略 ===
# 先以较低阈值粗聚,再精聚
cd-hit-est -i all_genes.fna -o step1.fna -c 0.99 -aS 0.9 -n 10 -M 64000 -T 16
cd-hit-est -i step1.fna -o nr_genes.fna -c 0.95 -aS 0.9 -n 10 -M 64000 -T 16
# === 使用MMseqs2(更快,适合超大数据集)===
mmseqs easy-linclust all_genes.fna nr_genes tmp \
--min-seq-id 0.95 \
-c 0.9 \
--cov-mode 1 \
--threads 32
# 统计结果
echo "Total input genes: $(grep -c '>' all_genes.fna)"
echo "Non-redundant genes: $(grep -c '>' nr_genes.fna)"
echo "Redundancy rate: $(echo "scale=2; (1-$(grep -c '>' nr_genes.fna)/$(grep -c '>' all_genes.fna))*100" | bc)%"
# 解析聚类文件
# 获取每个cluster的成员数
grep "^>" nr_genes.fna.clstr | sed 's/.*Cluster //' | sort -n | tail
第三步:功能注释¶
白话解释: 拿到非冗余基因集后,需要知道每个基因是做什么的。通过比对到功能数据库(KEGG、COG、CAZy等),为每个基因贴上功能标签。
技术细节: - 蛋白质序列注释比核苷酸更灵敏 - 多数据库并行注释获得多维信息 - 注释工具选择:eggNOG-mapper(一站式)、KofamScan(KEGG专项)、dbCAN(CAZy专项)
代码示例:
# === 1. eggNOG-mapper(一站式注释)===
# 翻译非冗余核苷酸为蛋白质(如果之前是核苷酸聚类)
# 或直接使用蛋白质聚类结果
emapper.py -i nr_proteins.faa \
-m diamond \
--itype proteins \
--data_dir /data/eggnog_db/ \
--cpu 32 \
--output nr_gene_emapper \
--output_dir ./annotation/ \
--override
# === 2. KofamScan(KEGG KO注释,比eggNOG的KEGG注释更准确)===
# 下载KOfam数据库
wget ftp://ftp.genome.jp/pub/db/kofam/ko_list.gz
wget ftp://ftp.genome.jp/pub/db/kofam/profiles.tar.gz
gunzip ko_list.gz; tar xzf profiles.tar.gz
# 运行
exec_annotation -o kofam_results.txt \
--profile profiles/ \
--ko-list ko_list \
--cpu 32 \
--tmp-dir /tmp/kofam/ \
nr_proteins.faa
# 提取显著KO(带*号的)
grep "^\*" kofam_results.txt | awk '{print $2"\t"$3}' > significant_ko.tsv
# === 3. dbCAN(CAZyme注释)===
# 使用run_dbcan
run_dbcan nr_proteins.faa protein \
--db_dir /data/dbCAN_db/ \
--out_dir ./dbcan_results/ \
--dia_cpu 16 \
--hmm_cpu 16 \
--tf_cpu 16
# 推荐取至少2种方法一致的结果
awk '$5 >= 2' dbcan_results/overview.txt > dbcan_consensus.txt
# === 4. CARD(抗性基因)===
rgi main -i nr_proteins.faa \
-o rgi_results \
-t protein \
-a DIAMOND \
--clean \
-n 16
# === 5. VFDB(毒力因子)===
diamond blastp -d /data/VFDB/VFDB_setA_pro.dmnd \
-q nr_proteins.faa \
-o vfdb_results.txt \
--outfmt 6 \
-e 1e-5 \
--id 40 \
--query-cover 70 \
-p 16
第四步:基因丰度定量¶
白话解释: 有了非冗余基因集,还需要知道每个基因在每个样本中有多少。方法是把每个样本的原始reads比对回非冗余基因集,统计各基因被覆盖的reads数。
技术细节: - 方法1:Salmon(pseudo-alignment,更快更准,推荐) - 方法2:Bowtie2/BWA比对 + featureCounts/bedtools统计 - 方法3:CoverM(直接从BAM计算覆盖度) - 归一化:TPM(Transcripts Per Million)考虑基因长度;RPKM/FPKM;CPM - 推荐TPM:在不同样本间可比
代码示例:
# ========== 方法1:Salmon(推荐)==========
# 1. 建立Salmon索引
salmon index -t nr_genes.fna -i salmon_index -p 16
# 2. 各样本定量
for sample in sample1 sample2 sample3; do
salmon quant -i salmon_index \
-l A \
-1 ${sample}_clean_R1.fastq.gz \
-2 ${sample}_clean_R2.fastq.gz \
-o salmon_quant/${sample} \
-p 16 \
--meta \
--validateMappings
done
# 3. 合并各样本的TPM/count
# 提取TPM
for sample in sample1 sample2 sample3; do
tail -n +2 salmon_quant/${sample}/quant.sf | \
awk -v s=${sample} 'BEGIN{OFS="\t"}{print $1, $4}' > salmon_quant/${sample}_tpm.txt
done
# 合并成矩阵
paste salmon_quant/*_tpm.txt | \
awk 'BEGIN{OFS="\t"}{printf $1; for(i=2;i<=NF;i+=2) printf "\t"$i; print ""}' > gene_tpm_matrix.tsv
# ========== 方法2:Bowtie2 + SAMtools ==========
# 1. 建索引
bowtie2-build --threads 16 nr_genes.fna bt2_index
# 2. 比对
for sample in sample1 sample2 sample3; do
bowtie2 -x bt2_index \
-1 ${sample}_clean_R1.fastq.gz \
-2 ${sample}_clean_R2.fastq.gz \
-S ${sample}_mapped.sam \
--no-unal \
-p 16 \
--very-sensitive
samtools view -bS -@ 8 ${sample}_mapped.sam | \
samtools sort -@ 8 -o ${sample}_mapped_sorted.bam
samtools index ${sample}_mapped_sorted.bam
rm ${sample}_mapped.sam
done
# 3. 统计每个基因的count
# 使用featureCounts(需要GTF/SAF格式的注释)
# 先将GFF转SAF
awk -F'\t' 'BEGIN{OFS="\t"; print "GeneID\tChr\tStart\tEnd\tStrand"}
/^[^#]/ && $3=="CDS" {
match($9, /ID=([^;]+)/, arr);
print arr[1], $1, $4, $5, $7
}' genes.gff > genes.saf
featureCounts -a genes.saf -F SAF \
-o gene_counts.txt \
-T 16 \
-p --countReadPairs \
sample1_mapped_sorted.bam \
sample2_mapped_sorted.bam \
sample3_mapped_sorted.bam
# ========== 方法3:CoverM ==========
# 计算基因覆盖度
coverm genome \
--bam-files sample1.bam sample2.bam sample3.bam \
--genome-fasta-files nr_genes.fna \
--methods rpkm count covered_bases \
--threads 16 \
> coverm_results.tsv
第五步:构建功能丰度表¶
白话解释: 把基因丰度和功能注释合并:每个基因有一个丰度值,也有功能标签(如KO号)。把同一个KO号的所有基因丰度加起来,就得到该KO在该样本中的"功能丰度"。
代码示例:
# R语言构建KO丰度表
library(tidyverse)
# 读取基因丰度矩阵(TPM)
gene_tpm <- read_tsv("gene_tpm_matrix.tsv")
# 读取KO注释
ko_annotation <- read_tsv("significant_ko.tsv",
col_names = c("gene_id", "KO"))
# 合并
gene_ko_tpm <- gene_tpm %>%
inner_join(ko_annotation, by = c("gene" = "gene_id"))
# 按KO汇总丰度(同一KO的基因TPM求和)
ko_abundance <- gene_ko_tpm %>%
group_by(KO) %>%
summarise(across(starts_with("sample"), sum, na.rm = TRUE)) %>%
ungroup()
# 保存KO丰度表
write_tsv(ko_abundance, "ko_abundance_matrix.tsv")
# 同理构建COG丰度表
cog_annotation <- read_tsv("annotation/nr_gene_emapper.emapper.annotations",
comment = "##") %>%
select(query, COG_category) %>%
filter(COG_category != "-")
# KEGG Module完整性评估
# 读取KEGG Module定义(哪些KO组成一个Module)
# 计算每个样本中每个Module的完整度
# Python版本
import pandas as pd
import numpy as np
# 读取
gene_tpm = pd.read_csv("gene_tpm_matrix.tsv", sep="\t", index_col=0)
ko_anno = pd.read_csv("significant_ko.tsv", sep="\t", names=["gene_id", "KO"])
# 合并并汇总
merged = gene_tpm.merge(ko_anno, left_index=True, right_on="gene_id")
ko_abundance = merged.groupby("KO").sum(numeric_only=True)
ko_abundance.to_csv("ko_abundance_matrix.tsv", sep="\t")
# KEGG Pathway丰度
# 从KO映射到Pathway(使用KEGG REST API或本地文件)
第六步:下游差异分析¶
白话解释: 有了功能丰度表(KO × 样本矩阵),就可以做组间差异比较——哪些功能通路在疾病组vs健康组中丰度显著不同。
代码示例:
# 使用DESeq2对功能丰度做差异分析
library(DESeq2)
# 将TPM转为近似count(如果用Salmon count输出则直接用)
# 或使用ALDEx2/MaAsLin2(更适合组成性数据)
library(ALDEx2)
# KO丰度矩阵(行=KO,列=样本)
ko_matrix <- as.matrix(read.csv("ko_count_matrix.csv", row.names = 1))
conditions <- c(rep("Healthy", 5), rep("Disease", 5))
# ALDEx2差异分析
aldex_result <- aldex(ko_matrix, conditions, mc.samples = 128,
test = "t", effect = TRUE, denom = "all")
# 筛选差异KO
sig_ko <- aldex_result[aldex_result$we.eBH < 0.05 & abs(aldex_result$effect) > 0.5, ]
# KEGG pathway富集分析
library(clusterProfiler)
# 将差异KO映射到pathway做富集
实战命令(可复制)¶
# ===== 完整宏基因组组装后分析流水线 =====
# 假设已有各样本的组装结果:sample*_contigs.fa
THREADS=32
# 1. 批量ORF预测
for s in sample1 sample2 sample3 sample4 sample5; do
prodigal -i ${s}_contigs.fa -a ${s}_prot.faa -d ${s}_gene.fna \
-p meta -f gff -q -o ${s}.gff &
done
wait
# 2. 合并所有基因(加样本前缀)
> all_genes.fna
for s in sample1 sample2 sample3 sample4 sample5; do
sed "s/^>/>${s}__/" ${s}_gene.fna >> all_genes.fna
done
# 3. CD-HIT聚类去冗余
cd-hit-est -i all_genes.fna -o nr_genes.fna \
-c 0.95 -aS 0.9 -n 10 -d 0 -M 64000 -T ${THREADS} -g 1
echo "Non-redundant gene catalog: $(grep -c '>' nr_genes.fna) genes"
# 4. 翻译为蛋白序列
seqkit translate nr_genes.fna > nr_proteins.faa
# 5. 功能注释
emapper.py -i nr_proteins.faa -m diamond --itype proteins \
--data_dir /data/eggnog_db/ --cpu ${THREADS} \
--output nr_emapper --output_dir ./annotation/ --override
# 6. KofamScan
exec_annotation -o kofam_results.txt \
--profile /data/kofam/profiles/ --ko-list /data/kofam/ko_list \
--cpu ${THREADS} nr_proteins.faa
# 7. 定量(Salmon)
salmon index -t nr_genes.fna -i salmon_idx -p ${THREADS}
for s in sample1 sample2 sample3 sample4 sample5; do
salmon quant -i salmon_idx -l A \
-1 ${s}_R1.fq.gz -2 ${s}_R2.fq.gz \
-o quant/${s} -p 8 --meta --validateMappings
done
# 8. 合并定量结果
python3 -c "
import pandas as pd, os
samples = ['sample1','sample2','sample3','sample4','sample5']
dfs = []
for s in samples:
df = pd.read_csv(f'quant/{s}/quant.sf', sep='\t', usecols=['Name','TPM'])
df = df.rename(columns={'TPM': s})
dfs.append(df.set_index('Name'))
result = pd.concat(dfs, axis=1)
result.to_csv('gene_tpm_matrix.tsv', sep='\t')
print(f'Gene abundance matrix: {result.shape[0]} genes x {result.shape[1]} samples')
"
面试常问点¶
Q1: 为什么Prodigal宏基因组必须用-p meta模式?¶
A: 宏基因组包含来自不同物种的contigs,各物种GC含量和密码子偏好差异大。-p meta模式对每条contig独立学习编码特征模型,而-p single用全部序列训练一个模型,会被优势物种的特征主导,对稀有物种基因预测不准。
Q2: CD-HIT的95%一致性+90%覆盖度阈值依据是什么?¶
A: 95%核苷酸一致性近似"同一个基因的不同个体拷贝"的序列差异水平(同种内株间差异通常<5%)。90%覆盖度确保比较的是基本完整的同一基因而非片段匹配。这是MetaHIT/HMP等大型项目的标准做法,具有广泛认可度。
Q3: 为什么推荐Salmon而非Bowtie2+featureCounts进行定量?¶
A: (1) Salmon的pseudo-alignment比传统比对快10-100倍;(2) Salmon内置GC bias校正和fragment length建模;(3) Salmon用EM算法处理多映射reads(在基因集中很常见),而featureCounts默认丢弃或随机分配多映射;(4) TPM计算内置。缺点:Salmon输出没有BAM文件供后续位点级分析。
Q4: TPM和RPKM/CPM的区别及选择?¶
A: RPKM/FPKM:reads/fragments per kilobase per million mapped reads,各样本的归一化因子不同导致跨样本不直接可比。TPM:transcripts per million,先除基因长度再归一化到百万,保证各样本总和为100万,跨样本可比。宏基因组功能定量推荐TPM。CPM不考虑基因长度,适合所有基因长度相近的场景。
Q5: 如何评估非冗余基因集的质量?¶
A: (1) 基因集大小是否合理(人肠道约500万-1000万;土壤可能更多);(2) 各样本reads映射率(mapping rate > 70%说明基因集代表性好);(3) 基因长度分布(中位值应>500bp);(4) 完整基因比例(partial=00的比例>40%);(5) 功能注释率(>50%能被注释)。
Q6: 宏基因组基因定量和16S扩增子定量有什么本质区别?¶
A: (1) 16S只定量"谁在那里"(物种组成),宏基因组定量"能做什么"(功能潜力);(2) 16S受PCR偏好和16S拷贝数影响;(3) 宏基因组可以发现新基因/新功能;(4) 宏基因组能同时获得物种+功能信息。但宏基因组成本更高、分析更复杂。
Q7: MMseqs2和CD-HIT如何选择?¶
A: CD-HIT:经典工具,结果被广泛引用,适合<1亿条序列的中等数据集。MMseqs2/Linclust:速度快100x+,内存效率高,适合超大数据集(>1亿条),但参数体系不同于CD-HIT。大型项目(如Earth Microbiome Project规模)推荐MMseqs2。
Q8: 如何处理组装质量对下游分析的影响?¶
A: (1) 过滤短contig(<500bp)再预测基因,减少碎片化假基因;(2) 评估组装指标(N50、基因完整度);(3) 对于高碎片化样本,考虑co-assembly(多样本联合组装)提升连续性;(4) 使用unassembled reads直接做read-based分析(如HUMAnN3)作为补充。
易错点¶
1. 合并多样本基因时ID冲突¶
问题: Prodigal对每个样本都从ID=1开始编号,直接合并会有大量重复ID。 正确做法: 合并前给每条序列加样本前缀:sed "s/^>/>${sample}__/"。
2. CD-HIT的-n参数与-c不匹配¶
问题: word size(n)必须与identity threshold(c)匹配,否则可能遗漏相似序列。 正确做法(CD-HIT-EST核苷酸): c=0.90~1.0用n=8,9,10;c=0.88~0.9用n=7;c=0.85~0.88用n=6;c=0.80~0.85用n=5;c=0.75~0.80用n=4。CD-HIT蛋白质: c=0.7~1.0用n=5;c=0.6~0.7用n=4;c=0.5~0.6用n=3;c=0.4~0.5用n=2。
3. 定量时使用相对丰度做差异分析¶
问题: TPM本身是组成性数据(总和恒定),直接做t检验会有假阳性。 正确做法: 使用count数据+DESeq2/ALDEx2,或对TPM做CLR变换后分析。
4. 未过滤Prodigal的partial genes就进入定量¶
问题: 部分基因(只有5'或3'端)长度很短,定量时容易被多映射reads污染。 正确做法: 可选择只保留complete genes(partial=00),或设置最低长度阈值(如>300bp/100aa)。
5. Salmon定量时忘记--meta参数¶
问题: Salmon默认参数针对转录组优化,宏基因组应使用--meta调整优化策略。 正确做法: 宏基因组定量时加--meta参数。--meta的实际效果:(1) 丰度优化从均匀分布初始化(而非默认的加权组合);(2) 禁用rich equivalence classes;(3) 使用EM算法替代默认的VBEM优化(因为VBEM先验参数是针对转录组设定的)。
6. 功能丰度汇总时一个基因被计入多个KO¶
问题: eggNOG-mapper可能给一个基因分配多个KO(逗号分隔),直接匹配会漏。 正确做法: 用separate_rows()或str_split()将多KO拆分为多行,再汇总。注意这会导致总丰度"膨胀",需要决定是否平均分配。
7. 忽略mapping rate检查¶
问题: 如果大部分reads无法映射到非冗余基因集,说明基因集代表性差。 正确做法: 检查每个样本的mapping rate(Salmon日志或samtools flagstat),应>60-70%。低于此值需要检查组装质量或考虑混合策略。
补充知识¶
典型基因集规模参考¶
| 环境/项目 | 基因集大小 | 参考 |
|---|---|---|
| 人肠道(IGC) | ~990万 | Li et al. 2014 |
| 人肠道(UHGG) | ~1.7亿(基因组) | Almeida et al. 2021 |
| 海洋(OM-RGC) | ~4000万 | Salazar et al. 2019 |
| 土壤 | ~1000万+ | 项目依赖 |
替代工具对比¶
| 步骤 | 标准工具 | 替代选择 |
|---|---|---|
| ORF预测 | Prodigal | MetaGeneMark, FragGeneScan (超短reads) |
| 聚类 | CD-HIT | MMseqs2/Linclust, UCLUST |
| 注释 | eggNOG-mapper | InterProScan, GhostKOALA, DRAM |
| 定量 | Salmon | Bowtie2+featureCounts, CoverM, BWA+HTSeq |
与read-based方法的互补¶
| 方法 | 组装依赖 | 工具 | 特点 |
|---|---|---|---|
| Assembly-based | 是 | 本教程流程 | 可发现新基因,适合高深度样本 |
| Read-based | 否 | HUMAnN3, MEGAN | 不受组装质量影响,但只能注释数据库中有的功能 |
| Hybrid | 部分 | - | 组合使用,取长补短 |
本教程覆盖了宏基因组组装后分析的完整流程,从ORF预测到功能丰度定量,适合构建非冗余基因集并开展功能比较研究。