跳转至

679 微生物组基因丰度定量

一句话概述:宏基因组基因丰度定量将原始reads映射到基因目录,用TPM/RPKM/Coverage等指标标准化后比较不同样本间的基因丰度差异。

核心知识点速查表

知识点关键内容
RPKMReads Per Kilobase per Million mapped reads
TPMTranscripts Per Million,样本间更可比
Coverage基因的平均测序深度(覆盖度)
基因目录非冗余基因集合(去冗余后的所有预测基因)
CoverM基于比对的覆盖度计算工具
Salmon无比对定量(快速,原用于转录组)

一、基因丰度定量是什么?(白话解释)

打个比方:假设你要统计一个图书馆每本书的"受欢迎程度"。你可以数每本书被借阅的次数(raw counts),但长书天然被翻阅页数更多(类比长基因更容易被reads覆盖)。所以需要用"每页被借阅的次数"(类似RPKM)来公平比较。

为什么要做基因丰度定量? - 知道群落里有哪些功能基因还不够 - 需要知道每个基因"有多少"——丰度高的基因影响更大 - 不同样本间的基因丰度比较是差异分析的基础

二、标准化指标对比

# RPKM vs FPKM vs TPM vs Coverage

# RPKM (Reads Per Kilobase per Million mapped reads)
RPKM_i = (reads_i × 10^9) / (total_reads × gene_length_i)
# 先除以总reads(百万),再除以基因长度(千碱基)
# 问题:不同样本的RPKM总和不同,样本间不可直接比较

# FPKM (Fragments Per Kilobase per Million)
# = RPKM的双端测序版本(一对reads算一个fragment)

# TPM (Transcripts Per Million)
# 步骤1: RPK_i = reads_i / gene_length_i (千碱基)
# 步骤2: TPM_i = RPK_i / sum(RPK) × 10^6
# 优点:每个样本的TPM总和=10^6,样本间可直接比较

# Coverage (覆盖度/深度)
Coverage_i = (reads_i × read_length) / gene_length_i
# 平均每个碱基被测了多少次
指标消除基因长度偏差消除测序深度偏差样本间可比推荐度
Raw counts×××用于统计模型(DESeq2)
RPKM△(总和不同)不推荐
TPM✓(总和=10^6)推荐
Coverage××需额外标准化

三、实战流程

# ============ 完整的基因丰度定量流程 ============

# 1. 基因预测(从contigs预测ORF)
prodigal \
  -i contigs.fasta \             # 输入组装contigs
  -o genes.gff \                 # GFF格式输出
  -a proteins.faa \              # 蛋白质序列
  -d genes.fna \                 # 核酸序列
  -p meta \                      # 宏基因组模式
  -f gff                        # 输出格式

# 2. 构建非冗余基因目录(去冗余)
cd-hit-est \
  -i all_genes.fna \             # 所有样本的基因合并
  -o gene_catalog.fna \          # 非冗余基因目录
  -c 0.95 \                      # 95%一致性阈值
  -aS 0.9 \                      # 90%短序列覆盖度
  -G 0 \                         # 全局比对
  -M 0 \                         # 不限内存
  -T 16 \                        # 线程数
  -d 0                          # 不截断基因名

echo "基因目录大小:"
grep -c ">" gene_catalog.fna     # 统计基因数量

# 3. 构建比对索引
# 方法1: BWA-MEM2(推荐)
bwa-mem2 index gene_catalog.fna  # 建BWA索引

# 方法2: Bowtie2
bowtie2-build \
  --threads 16 \                 # 线程数
  gene_catalog.fna \             # 基因目录
  gene_catalog_bt2               # 索引前缀

# 方法3: Minimap2(更快)
minimap2 -d gene_catalog.mmi gene_catalog.fna  # 建索引

# 4. Reads比对到基因目录
# 使用BWA-MEM2
bwa-mem2 mem \
  -t 16 \                       # 线程数
  gene_catalog.fna \             # 基因目录
  sample_R1.fastq.gz \           # 正向reads
  sample_R2.fastq.gz \           # 反向reads
  | samtools sort -@ 4 -o sample_sorted.bam  # 排序BAM

samtools index sample_sorted.bam  # 建索引

# 5. 计算基因丰度
# 方法1: featureCounts(推荐,快速准确)
featureCounts \
  -a gene_catalog.gff \          # 基因注释文件
  -o gene_counts.txt \           # 输出计数
  -T 16 \                       # 线程数
  -p \                           # 双端测序
  --countReadPairs \             # 按片段计数
  -B \                           # 两端都比对
  -Q 10 \                       # 最低比对质量
  sample_sorted.bam              # 输入BAM文件

# 方法2: CoverM(覆盖度计算)
coverm genome \
  -b sample_sorted.bam \         # BAM文件
  -m trimmed_mean \              # 方法:截尾均值
  --min-covered-fraction 0.1 \   # 最低覆盖比例
  -o coverage_results.tsv        # 输出

# 方法3: samtools + bedtools
# 计算每个基因的reads数
bedtools coverage \
  -a gene_catalog.bed \          # 基因坐标
  -b sample_sorted.bam \         # BAM文件
  -counts \                      # 只输出计数
  > gene_raw_counts.txt          # 原始计数

四、TPM/RPKM计算与标准化

# Python实现基因丰度标准化
import numpy as np   # 数值计算
import pandas as pd  # 数据处理

# 1. 读取featureCounts输出
def read_featurecounts(filepath):
    """读取featureCounts输出文件"""
    df = pd.read_csv(filepath, sep='\t', comment='#')  # 读取
    # featureCounts输出列:Geneid, Chr, Start, End, Strand, Length, counts...
    gene_ids = df["Geneid"]              # 基因ID
    lengths = df["Length"]               # 基因长度
    count_cols = [c for c in df.columns if c.endswith(".bam")]  # 计数列
    counts = df[count_cols]              # 提取计数矩阵
    # 清理列名
    counts.columns = [c.replace("_sorted.bam", "") for c in counts.columns]
    counts.index = gene_ids              # 设基因ID为索引
    return counts, lengths

counts, gene_lengths = read_featurecounts("gene_counts.txt")
print(f"基因数: {len(counts)}")
print(f"样本数: {len(counts.columns)}")

# 2. 计算RPKM
def calculate_rpkm(counts, gene_lengths):
    """计算RPKM值"""
    # RPKM = (reads × 10^9) / (total_reads × gene_length)
    total_reads = counts.sum(axis=0)     # 每个样本的总reads
    rpkm = counts.copy().astype(float)   # 初始化
    for sample in counts.columns:
        rpkm[sample] = (counts[sample] * 1e9) / \
                        (total_reads[sample] * gene_lengths)  # RPKM公式
    return rpkm

rpkm = calculate_rpkm(counts, gene_lengths)
print("\nRPKM范围:")
print(rpkm.describe())

# 3. 计算TPM(推荐)
def calculate_tpm(counts, gene_lengths):
    """计算TPM值"""
    # 步骤1: 计算RPK (reads per kilobase)
    rpk = counts.divide(gene_lengths / 1000, axis=0)  # 除以基因长度(kb)
    # 步骤2: 缩放到百万
    tpm = rpk.divide(rpk.sum(axis=0), axis=1) * 1e6   # 每个样本总和=10^6
    return tpm

tpm = calculate_tpm(counts, gene_lengths)
print("\nTPM范围:")
print(tpm.describe())
# 验证:每个样本的TPM总和应该=10^6
print("\n各样本TPM总和(应为1e6):")
print(tpm.sum(axis=0))

# 4. 计算Coverage
def calculate_coverage(counts, gene_lengths, read_length=150):
    """计算平均覆盖度"""
    # Coverage = (reads × read_length) / gene_length
    coverage = counts.multiply(read_length, axis=0).divide(
        gene_lengths, axis=0)            # 覆盖度公式
    return coverage

cov = calculate_coverage(counts, gene_lengths)

# 5. 过滤低丰度基因
def filter_low_abundance(tpm, min_tpm=1, min_samples=0.1):
    """过滤低丰度基因"""
    n_samples = len(tpm.columns)         # 样本数
    min_n = int(n_samples * min_samples) # 最少出现的样本数
    # 在至少min_samples比例的样本中TPM≥min_tpm
    keep = (tpm >= min_tpm).sum(axis=1) >= min_n  # 过滤条件
    filtered = tpm[keep]                 # 保留的基因
    print(f"过滤前: {len(tpm)} 基因")
    print(f"过滤后: {len(filtered)} 基因")
    print(f"去除: {len(tpm) - len(filtered)} 个低丰度基因")
    return filtered

tpm_filtered = filter_low_abundance(tpm, min_tpm=1, min_samples=0.1)

# 6. 保存结果
tpm_filtered.to_csv("gene_tpm_filtered.tsv", sep='\t')  # 保存TPM表
print("TPM表已保存: gene_tpm_filtered.tsv")

五、功能丰度汇总

# 将基因丰度汇总到功能注释级别
import pandas as pd  # 数据处理

# 1. 读取功能注释(eggNOG-mapper/KEGG/COG)
def summarize_to_function(tpm, annotation_file, level="KO"):
    """将基因TPM汇总到功能分类级别"""
    # 读取功能注释
    annot = pd.read_csv(annotation_file, sep='\t',
                        index_col=0)     # 基因→功能映射
    # 合并TPM和注释
    tpm_annot = tpm.join(annot[[level]], how="inner")  # 内连接
    # 按功能分组求和
    func_tpm = tpm_annot.groupby(level).sum()  # 汇总到功能级别
    print(f"基因级别: {len(tpm)} 条 → 功能级别: {len(func_tpm)} 条")
    return func_tpm

# KEGG pathway汇总
# ko_tpm = summarize_to_function(tpm, "eggnog_annotations.tsv", level="KEGG_ko")

# 2. KEGG module completeness
def kegg_module_completeness(ko_tpm, module_definitions):
    """计算KEGG模块的完整度"""
    results = {}
    for module, kos in module_definitions.items():
        present_kos = [ko for ko in kos if ko in ko_tpm.index]
        completeness = len(present_kos) / len(kos)  # 完整度
        if completeness > 0:
            avg_abundance = ko_tpm.loc[present_kos].mean().mean()  # 平均丰度
            results[module] = {
                "completeness": completeness,
                "n_present": len(present_kos),
                "n_total": len(kos),
                "avg_tpm": avg_abundance
            }
    return pd.DataFrame(results).T

# 3. 差异功能分析
def differential_function(func_tpm, metadata, group_col,
                           group1, group2):
    """两组间的差异功能分析"""
    from scipy.stats import mannwhitneyu  # Mann-Whitney U检验

    g1_samples = metadata[metadata[group_col] == group1].index
    g2_samples = metadata[metadata[group_col] == group2].index

    results = []
    for func in func_tpm.index:
        vals1 = func_tpm.loc[func, g1_samples]  # 组1值
        vals2 = func_tpm.loc[func, g2_samples]  # 组2值
        stat, pval = mannwhitneyu(vals1, vals2,
                                  alternative='two-sided')  # U检验
        fc = vals1.mean() / (vals2.mean() + 1e-10)  # fold change
        results.append({
            "function": func,
            "mean_g1": vals1.mean(),
            "mean_g2": vals2.mean(),
            "fold_change": fc,
            "log2fc": np.log2(fc + 1e-10),
            "pvalue": pval
        })

    result_df = pd.DataFrame(results)
    result_df["padj"] = result_df["pvalue"] * len(result_df)  # Bonferroni
    result_df["padj"] = result_df["padj"].clip(upper=1.0)
    return result_df.sort_values("pvalue")

常见报错与解决

报错原因解决方案
比对率很低(<10%)基因目录不包含样本物种检查基因目录来源,使用IGC2等公共目录
featureCounts报多重比对reads比对到多个基因用-M允许多重比对或--primary只用主比对
TPM值全为0计数列解析错误检查featureCounts输出列名
内存不足(BWA建索引)基因目录太大分批建索引或使用Minimap2
基因目录去冗余太慢基因数>10M使用MMseqs2 linclust替代cd-hit

速查表

# 基因丰度定量流程
1. 基因预测: Prodigal (meta模式)
2. 构建基因目录: CD-HIT-EST (95%一致性)
3. Reads比对: BWA-MEM2 / Minimap2
4. 计数: featureCounts / bedtools
5. 标准化: TPM(推荐)
6. 过滤: TPM≥1 在≥10%样本中
7. 功能汇总: 按KO/COG/Pathway聚合

# 标准化选择
样本间比较: TPM(总和恒定=10^6)
统计模型输入: Raw counts(DESeq2自行标准化)
展示丰度: TPM 或 相对丰度

# 公共基因目录
IGC2: 人肠道非冗余基因集(1504万基因)
UHGG: 统一人肠道基因组(28万基因组+13亿基因)
OM-RGC: 海洋参考基因目录

# 去冗余工具对比
CD-HIT-EST: 经典,适合<10M基因
MMseqs2 linclust: 更快,适合>10M基因
VSEARCH: CD-HIT替代品

# 比对工具对比
BWA-MEM2: 最准确,适合精确定量
Minimap2: 最快,适合大数据集
Bowtie2: 经典,参数丰富

面试高频问题

Q1:RPKM和TPM有什么区别?为什么推荐TPM? A:两者都校正了基因长度和测序深度。关键区别:RPKM先除以总reads再除以长度,导致不同样本的RPKM总和不同,样本间不可直接比较;TPM先除以长度再做百万缩放,每个样本总和恒等于10^6,样本间可直接比较。2025年的共识是宏基因组定量用TPM。

Q2:为什么要构建非冗余基因目录? A:多个样本的宏基因组组装会产生大量重复/相似的基因。去冗余(如CD-HIT 95%阈值)将相似基因合并为代表序列,减少计算量和比对歧义。非冗余基因目录是所有样本共享的"参考字典",所有样本的reads都比对到同一个字典上,确保跨样本可比性。

Q3:基因丰度和物种丰度有什么关系? A:基因丰度反映功能的"剂量"——同一个功能可能来自多个物种。基因丰度=∑(物种i的基因拷贝数×物种i的丰度)。功能冗余意味着多个物种携带相同基因,所以功能丰度通常比单个物种的丰度更稳定。将基因丰度按物种归属分解可以揭示"谁贡献了什么功能"。

Q4:宏基因组基因定量和宏转录组有什么区别? A:宏基因组定量的是DNA水平的基因丰度——反映基因的"潜力"(群落有没有这个基因)。宏转录组定量的是RNA水平的表达量——反映基因的"活性"(基因是否正在被使用)。两者结合可以区分"有能力做"和"正在做"。通常的做法是用宏基因组定量DNA丰度,用宏转录组/DNA丰度的比值衡量基因的表达活性。