623_宏基因组基因聚类¶
一句话概述: 基因聚类是宏基因组分析的关键去冗余步骤——CD-HIT经典但慢,MMseqs2/Linclust速度快2300倍且精度相当,它们将百万级预测基因按相似度合并成非冗余基因集(gene catalog),大幅减少下游分析的计算量。
核心知识点速查表¶
| 概念 | 白话解释 |
|---|---|
| Gene clustering | 基因聚类——把高度相似的基因合并成一组,只保留代表序列 |
| Non-redundant gene catalog | 非冗余基因集——去掉重复后的基因目录,每个基因只出现一次 |
| Representative sequence | 代表序列——每个聚类中最长或质量最好的那条序列 |
| Identity threshold | 相似度阈值——序列相似度≥多少才归为一类(通常95%) |
| Coverage | 覆盖度——较短序列被比对覆盖的比例 |
| CD-HIT | 经典基因聚类工具,用贪心增量算法 |
| MMseqs2 | 超快序列搜索和聚类工具(比CD-HIT快几百倍) |
| Linclust | MMseqs2中的线性时间聚类算法(速度最快) |
| Centroid | 质心/代表序列——聚类中代表整个组的那条序列 |
一、安装¶
# === CD-HIT安装 ===
conda install -c bioconda cd-hit # conda安装
cd-hit -h # 验证安装
# === MMseqs2安装 ===
conda install -c bioconda mmseqs2 # conda安装
mmseqs version # 查看版本(2026年最新为v16+)
# === 源码编译MMseqs2(可选,追求最新版)===
git clone https://github.com/soedinglab/MMseqs2.git # 克隆仓库
cd MMseqs2 && mkdir build && cd build # 创建编译目录
cmake -DCMAKE_BUILD_TYPE=RELEASE -DCMAKE_INSTALL_PREFIX=. .. # 配置
make -j8 && make install # 编译安装
二、CD-HIT基因聚类¶
2.1 核苷酸序列聚类(cd-hit-est)¶
# === 宏基因组预测基因聚类 ===
cd-hit-est \
-i predicted_genes.fna \ # 输入:所有样本预测的基因核苷酸序列
-o nr_genes_95.fna \ # 输出:非冗余基因集
-c 0.95 \ # 相似度阈值95%(同种水平)
-aS 0.9 \ # 较短序列被覆盖≥90%
-n 10 \ # word size(c≥0.95时用10)
-G 0 \ # 局部比对模式(宏基因组推荐)
-M 0 \ # 不限制内存
-T 16 \ # 16线程
-d 0 # 不截断序列名
# === 输出文件说明 ===
# nr_genes_95.fna — 代表序列(非冗余基因集)
# nr_genes_95.fna.clstr — 聚类信息文件
# === 查看聚类统计 ===
grep -c "^>" nr_genes_95.fna # 非冗余基因数
grep -c "^>Cluster" nr_genes_95.fna.clstr # 聚类数
2.2 蛋白序列聚类(cd-hit)¶
# === 蛋白序列聚类 ===
cd-hit \
-i predicted_proteins.faa \ # 输入:蛋白序列
-o nr_proteins_90.faa \ # 输出:非冗余蛋白集
-c 0.90 \ # 相似度阈值90%
-n 5 \ # word size(c≥0.7时用5)
-G 0 \ # 局部比对
-aS 0.8 \ # 较短序列覆盖≥80%
-M 0 \ # 不限制内存
-T 16 \ # 16线程
-d 0 # 不截断序列名
2.3 CD-HIT word size选择¶
# word size (-n) 必须与相似度阈值 (-c) 匹配:
# c ≥ 0.95 → n = 10-11(核苷酸),n = 5(蛋白)
# c ≥ 0.90 → n = 8-9(核苷酸),n = 5(蛋白)
# c ≥ 0.85 → n = 7(核苷酸),n = 5(蛋白)
# c ≥ 0.80 → n = 5-6(核苷酸),n = 5(蛋白)
# c ≥ 0.70 → n = 4-5(核苷酸),n = 4(蛋白)
# c ≥ 0.60 → 核苷酸不支持,n = 4(蛋白)
# c ≥ 0.50 → 核苷酸不支持,n = 3(蛋白)
# c ≥ 0.40 → 核苷酸不支持,n = 2(蛋白)
2.4 解析聚类结果¶
import re # 正则表达式
from collections import Counter # 计数器
# 解析CD-HIT聚类文件
def parse_cdhit_clstr(clstr_file):
"""解析.clstr文件,返回每个聚类的成员列表"""
clusters = {} # 存储聚类信息
current_cluster = None # 当前聚类ID
with open(clstr_file) as f: # 打开聚类文件
for line in f: # 逐行读取
if line.startswith(">Cluster"): # 新聚类开始
current_cluster = line.strip() # 记录聚类名
clusters[current_cluster] = [] # 初始化成员列表
else:
# 提取序列名
match = re.search(r'>(\S+)\.\.\.', line) # 正则匹配序列名
if match:
seq_name = match.group(1) # 获取序列名
is_rep = line.strip().endswith("*") # 星号标记代表序列
clusters[current_cluster].append(
(seq_name, is_rep)
)
return clusters # 返回聚类字典
# 使用
clusters = parse_cdhit_clstr("nr_genes_95.fna.clstr")
sizes = [len(v) for v in clusters.values()] # 每个聚类的大小
print(f"总聚类数: {len(clusters)}") # 打印聚类数
print(f"最大聚类: {max(sizes)} 条序列") # 最大聚类
print(f"单序列聚类: {sizes.count(1)} 个") # 单例数
print(f"平均聚类大小: {sum(sizes)/len(sizes):.1f}") # 平均大小
三、MMseqs2基因聚类¶
3.1 easy-cluster(推荐入门用法)¶
# === MMseqs2 easy-cluster:一条命令完成聚类 ===
mmseqs easy-cluster \
predicted_genes.fna \ # 输入序列文件
mmseqs_cluster \ # 输出前缀
tmp/ \ # 临时目录
--min-seq-id 0.95 \ # 相似度阈值95%
-c 0.9 \ # 覆盖度阈值90%
--cov-mode 1 \ # 覆盖模式:1=较短序列覆盖
--threads 16 # 16线程
# === 输出文件 ===
# mmseqs_cluster_rep_seq.fasta — 代表序列
# mmseqs_cluster_cluster.tsv — 聚类成员关系(制表符分隔)
# mmseqs_cluster_all_seqs.fasta — 所有序列(按聚类排序)
3.2 easy-linclust(线性时间聚类,最快)¶
# === Linclust:线性时间聚类,适合超大数据集 ===
mmseqs easy-linclust \
predicted_genes.fna \ # 输入序列文件
linclust_result \ # 输出前缀
tmp/ \ # 临时目录
--min-seq-id 0.95 \ # 相似度阈值95%
-c 0.9 \ # 覆盖度阈值90%
--cov-mode 1 \ # 覆盖模式
--threads 16 # 16线程
# Linclust vs easy-cluster:
# Linclust是O(n)时间复杂度——序列数翻倍,时间也只翻倍
# easy-cluster是O(n*k)——更准确但更慢
# 数据量>1亿条序列时Linclust是唯一选择
3.3 分步运行(更灵活)¶
# === 分步运行MMseqs2聚类 ===
# 第1步:创建数据库
mmseqs createdb predicted_genes.fna queryDB # 创建MMseqs2数据库
# 第2步:聚类
mmseqs cluster \
queryDB \ # 输入数据库
clusterDB \ # 聚类结果数据库
tmp/ \ # 临时目录
--min-seq-id 0.95 \ # 相似度阈值
-c 0.9 \ # 覆盖度
--cov-mode 1 \ # 覆盖模式
--threads 16 # 线程数
# 第3步:提取代表序列
mmseqs createsubdb clusterDB queryDB repDB # 创建代表序列子数据库
mmseqs convert2fasta repDB rep_seqs.fasta # 转为FASTA格式
# 第4步:输出聚类成员关系
mmseqs createtsv queryDB queryDB clusterDB cluster.tsv # 输出TSV
3.4 覆盖模式详解¶
# --cov-mode 参数说明:
# 0 = 双向覆盖:query和target都要满足覆盖度阈值
# 1 = target覆盖:较短序列被覆盖≥阈值(最常用)
# 2 = query覆盖:query被覆盖≥阈值
# 3 = target序列长度被覆盖≥阈值
# 5 = 较短序列被覆盖≥阈值(类似CD-HIT的-aS)
# 宏基因组推荐:--cov-mode 1 -c 0.9
# 含义:较短的基因序列至少90%被较长的代表序列覆盖
四、三工具性能对比¶
| 特性 | CD-HIT | MMseqs2 cluster | MMseqs2 Linclust |
|---|---|---|---|
| 算法 | 贪心增量 | k-mer预筛+Smith-Waterman | 线性时间k-mer聚类 |
| 时间复杂度 | O(n*k) | O(n*k) | O(n) |
| 速度(10M序列) | ~10小时 | ~30分钟 | ~5分钟 |
| 速度比 | 1x | ~20x | ~100-2300x |
| 灵敏度 | 高 | 高 | 中等偏高 |
| 最大数据量 | ~千万级 | ~亿级 | ~十亿级 |
| 内存使用 | 高 | 中等 | 低 |
| 最低相似度 | 核苷酸80%/蛋白40% | 无下限 | 无下限 |
| 蛋白聚类 | 支持 | 支持 | 支持 |
| 核苷酸聚类 | 支持 | 支持 | 支持 |
五、宏基因组非冗余基因集构建实战¶
#!/bin/bash
# === 完整的宏基因组非冗余基因集构建流程 ===
# 变量设置
GENE_DIR="prodigal_output" # Prodigal预测结果目录
THREADS=32 # 线程数
IDENTITY=0.95 # 相似度阈值
COVERAGE=0.9 # 覆盖度阈值
# === 第1步:合并所有样本的预测基因 ===
echo "合并所有样本的预测基因..."
cat ${GENE_DIR}/*_genes.fna > all_predicted_genes.fna # 合并核苷酸
cat ${GENE_DIR}/*_proteins.faa > all_predicted_proteins.faa # 合并蛋白
echo "总基因数: $(grep -c '^>' all_predicted_genes.fna)"
# === 第2步:去除短序列(<100bp)===
echo "过滤短序列..."
seqkit seq -m 100 all_predicted_genes.fna > filtered_genes.fna # 过滤<100bp
echo "过滤后基因数: $(grep -c '^>' filtered_genes.fna)"
# === 第3步:用MMseqs2 Linclust聚类 ===
echo "开始MMseqs2 Linclust聚类..."
mmseqs easy-linclust \
filtered_genes.fna \ # 输入
gene_catalog \ # 输出前缀
tmp_mmseqs/ \ # 临时目录
--min-seq-id ${IDENTITY} \ # 相似度95%
-c ${COVERAGE} \ # 覆盖度90%
--cov-mode 1 \ # 较短序列覆盖模式
--threads ${THREADS} # 线程数
# === 第4步:统计结果 ===
NR_GENES=$(grep -c '^>' gene_catalog_rep_seq.fasta)
TOTAL_GENES=$(grep -c '^>' filtered_genes.fna)
REDUCTION=$(echo "scale=1; (1 - ${NR_GENES}/${TOTAL_GENES}) * 100" | bc)
echo "=== 聚类结果 ==="
echo "输入基因数: ${TOTAL_GENES}"
echo "非冗余基因数: ${NR_GENES}"
echo "冗余率: ${REDUCTION}%"
# === 第5步:清理临时文件 ===
rm -rf tmp_mmseqs/ # 删除临时目录
echo "完成!非冗余基因集: gene_catalog_rep_seq.fasta"
5.1 基因丰度定量¶
# === 用非冗余基因集做丰度定量 ===
# 第1步:建立索引
bwa index gene_catalog_rep_seq.fasta # BWA建索引
# 第2步:将每个样本的reads比对到基因集
for sample in samples/*_R1.fastq.gz; do
name=$(basename "$sample" _R1.fastq.gz) # 提取样本名
# BWA比对
bwa mem \
-t 16 \ # 16线程
gene_catalog_rep_seq.fasta \ # 参考基因集
${sample} \ # 正向reads
${sample/_R1/_R2} \ # 反向reads
| samtools sort -@ 4 -o ${name}.bam # 排序
# 统计比对数
samtools idxstats ${name}.bam > ${name}_counts.tsv # 基因计数
done
# 第3步:合并所有样本的计数
python3 -c "
import pandas as pd # 导入pandas
import glob # 文件匹配
# 读取所有样本的计数文件
count_files = sorted(glob.glob('*_counts.tsv')) # 找所有计数文件
counts = {} # 存储计数
for f in count_files: # 遍历每个文件
name = f.replace('_counts.tsv', '') # 提取样本名
df = pd.read_csv(f, sep='\t', header=None,
names=['gene', 'length', 'mapped', 'unmapped'])
counts[name] = df.set_index('gene')['mapped'] # 取比对数
# 合并为矩阵
abundance = pd.DataFrame(counts) # 创建丰度矩阵
abundance = abundance.fillna(0) # 缺失值填0
# TPM归一化
gene_lengths = pd.read_csv(count_files[0], sep='\t', header=None,
names=['gene', 'length', 'mapped', 'unmapped'])
lengths = gene_lengths.set_index('gene')['length'] # 基因长度
rpk = abundance.div(lengths, axis=0) * 1e3 # reads per kilobase
tpm = rpk.div(rpk.sum(axis=0), axis=1) * 1e6 # TPM归一化
tpm.to_csv('gene_abundance_tpm.tsv', sep='\t') # 保存TPM矩阵
print(f'基因丰度矩阵: {tpm.shape[0]} 基因 x {tpm.shape[1]} 样本')
"
六、常见报错与解决¶
| 报错信息 | 原因 | 解决方案 |
|---|---|---|
cd-hit-est: word size too large | -n值与-c不匹配 | 参照word size表选择正确值 |
Not enough memory | 序列太多内存不够 | 用MMseqs2替代或增加-M值 |
mmseqs: tmp directory full | 临时目录空间不足 | 指定大磁盘上的tmp目录 |
Sequences too short | 有极短序列 | 用seqkit过滤掉<100bp序列 |
Different sequence types | 混入了蛋白/核苷酸 | 确认输入序列类型一致 |
| CD-HIT运行极慢 | 数据量太大 | 换MMseqs2 Linclust |
七、常用阈值参考¶
| 应用场景 | 工具选择 | 相似度阈值 | 覆盖度 |
|---|---|---|---|
| 宏基因组非冗余基因集 | MMseqs2 Linclust | 95% | 90% |
| 蛋白功能聚类(同功能) | CD-HIT / MMseqs2 | 50-70% | 80% |
| 去除PCR重复 | cd-hit-dup | 100% | 100% |
| UniRef构建 | MMseqs2 | 50/90/100% | 80% |
| 16S OTU聚类 | cd-hit-est | 97% | 80% |
| 泛基因组核心基因 | CD-HIT | 90-95% | 80% |
八、面试高频题¶
Q1:CD-HIT和MMseqs2的主要区别是什么?¶
答: CD-HIT用贪心增量算法——先将最长序列作为第一个代表,然后逐条比较剩余序列,相似度达标就归入已有聚类,否则创建新聚类。优点是实现简单、结果直观,缺点是O(n*k)时间复杂度,数据量大时极慢。MMseqs2用k-mer预筛+双向最优比对,速度快20-100倍。Linclust更进一步,用线性时间算法(O(n))处理超大数据集,在10亿条序列级别仍然可行。实际测试中,MMseqs2/Linclust在95%相似度下的聚类结果与CD-HIT高度一致,但速度提升数百倍。
Q2:为什么宏基因组要构建非冗余基因集?¶
答: 宏基因组通常有几十到上百个样本,每个样本可能预测出100万+基因。直接分析会有大量冗余(同一物种在不同样本中的同一基因被重复计算)。构建非冗余基因集的意义:(1) 去冗余——典型冗余率50-70%,大幅减少数据量;(2) 统一参考——所有样本的reads都比对到同一个基因集上,方便跨样本比较;(3) 提高下游分析效率——功能注释、丰度定量都只需处理非冗余集。常用标准是95%核苷酸相似度+90%覆盖度(IGC、UHGG等大型基因集都用此标准)。
Q3:聚类的覆盖度参数是什么意思?¶
答: 覆盖度是指较短序列被比对区域覆盖的比例。比如一条200bp的基因和一条300bp的基因比对,如果比对区域覆盖了200bp基因中的180bp,那覆盖度就是180/200=90%。设置覆盖度阈值是防止"部分相似就合并"——比如两个基因只有一个结构域相似但其他部分完全不同,不应该合并。宏基因组推荐覆盖度≥90%(--cov-mode 1 -c 0.9),这样只有真正高度相似的全长基因才会合并。
Q4:什么情况下用Linclust而不是MMseqs2 cluster?¶
答: Linclust适合数据量极大(>1000万条序列)且对精度要求不是特别极致的场景。Linclust是线性时间复杂度O(n),10亿条序列也能在合理时间内完成。trade-off是它可能会漏掉一些边缘相似的序列对。MMseqs2 cluster更准确(k-mer预筛后做完整比对),但O(n*k)。实际使用中,宏基因组基因集构建用Linclust足够了——结果与cluster模式差异<1%,但速度快10-100倍。如果是蛋白功能聚类(低阈值如50%)且追求高灵敏度,用cluster模式更好。
参考资料:CD-HIT: Fu et al., Bioinformatics 2012 | MMseqs2: Steinegger & Söding, Nat Biotechnol 2017 | Linclust: Steinegger & Söding, Nat Commun 2018 | Clusterize: Barrio-Hernandez et al., bioRxiv 2024 | IGC: Li et al., Nat Biotechnol 2014