当你第一次拿到TCGA结肠癌和直肠癌的转录组数据时,可能会被样本量之大所震撼——701个样本,19938个基因。但更让人头疼的是,这些数据来自不同研究项目(TCGA-COAD和TCGA-READ),就像把不同实验室、不同时期做的实验结果混在一起分析。这就是典型的批次效应问题,它会让本应显著的生物学差异被技术变异所掩盖。
我在分析乳腺癌多中心数据时就踩过坑:两个医疗中心提供的基因表达数据,在未校正时聚类结果完全按中心分组,而非预期的肿瘤亚型。这种情况在公共数据库分析中尤为常见,比如:
批次效应最直观的表现是:当用PCA降维可视化时,样本不是按生物学分组(如肿瘤/正常)聚集,而是按批次来源聚集成簇。这会导致后续差异分析、生存分析等结果的可靠性大打折扣。
用easyTCGA包获取数据确实方便,但新手常会忽略几个关键步骤。以结肠癌数据为例,获取TPM矩阵后必须进行log2转换(记得加0.1的伪计数避免取对数出错):
r复制library(easyTCGA)
getmrnaexpr(c("TCGA-COAD","TCGA-READ"))
# 加载数据并转换
load("TCGA-COAD_TCGA-READ_mrna_expr_tpm.rdata")
exprset <- log2(mrna_expr_tpm + 0.1)
临床信息中的project_id(TCGA-COAD/TCGA-READ)就是天然的批次标签,而sample_type需要简化为tumor/normal两类:
r复制clin_info$sample_type <- ifelse(
as.numeric(substr(clin_info$barcode,14,15)) < 10,
"tumor",
"normal"
)
tinyarray包的draw_pca函数能快速生成诊断图。先看生物学分组(肿瘤vs正常)的分布:
r复制library(tinyarray)
draw_pca(exp = exprset, group_list = factor(clin_info$sample_type))
如果发现样本按project_id分组而非sample_type聚集(如下图右侧),就是批次效应的典型表现。这时就需要进行批次校正:

层次聚类也能辅助判断。用dendextend包给聚类树添加颜色标记,可以同时观察批次和生物学分组的影响:
r复制library(dendextend)
tmp <- exprset
colnames(tmp) <- 1:ncol(tmp)
h.clust <- as.dendrogram(hclust(dist(scale(t(tmp)))))
sample_colors <- ifelse(clin_info$sample_type == "tumor", "red", "green")
par(mar=c(15,1,1,1))
plot(h.clust)
colored_bars(colors = sample_colors, dend = h.clust)
sva包的ComBat是校正批次效应的经典选择。其优势在于:
基础用法只需要指定表达矩阵和批次信息:
r复制library(sva)
expr_combat <- ComBat(dat = exprset, batch = clin_info$project_id)
但这里有个常见陷阱:ComBat默认会过滤在所有批次中表达为零的基因(示例中过滤了620个基因)。如果这些基因可能具有生物学意义,可以通过keep.zero=TRUE参数保留:
r复制expr_combat <- ComBat(
dat = exprset,
batch = clin_info$project_id,
keep.zero = TRUE
)
更推荐的做法是加入mod参数,明确告诉算法需要保留的生物学分组(如肿瘤/正常),避免过度校正:
r复制mod <- model.matrix(~factor(clin_info$sample_type))
expr_combat <- ComBat(
dat = exprset,
batch = clin_info$project_id,
mod = mod
)
我曾在一个肺癌项目中对比过两种方式:未指定mod参数时,关键癌基因TP53的表达差异被削弱了30%;而正确使用mod参数后,既消除了批次效应,又保留了真实的生物学差异。
校正效果可以通过前后PCA对比来验证。理想情况下,校正后的数据应该:
limma包的removeBatchEffect更适合需要自主控制校正程度的情况。与ComBat不同,它:
基本用法与ComBat类似:
r复制library(limma)
mod <- model.matrix(~factor(clin_info$sample_type))
expr_rbe <- removeBatchEffect(
exprset,
batch = clin_info$project_id,
design = mod
)
当存在多层级批次时(如不同中心+不同处理批次),可以传入多个批次变量:
r复制# 假设clin_info中有center和processing_batch两列
expr_rbe <- removeBatchEffect(
exprset,
batch = clin_info$project_id,
batch2 = clin_info$processing_batch,
design = mod
)
在分析阿尔茨海默症的多中心数据时,我发现同时校正中心和RNA提取批次后,原本被掩盖的疾病相关通路(如Aβ代谢)变得显著。
对于RNA-seq的原始count数据,sva包的ComBat_seq是更好的选择。它:
r复制load("TCGA-COAD_TCGA-READ_mrna_expr_counts.rdata")
expr_count_combat <- ComBat_seq(
counts = as.matrix(mrna_expr_counts),
batch = clin_info$project_id,
group = clin_info$sample_type
)
DESeq2虽然不能直接输出校正后的矩阵,但在差异分析时可通过设计公式隐式校正:
r复制library(DESeq2)
dds <- DESeqDataSetFromMatrix(
countData = mrna_expr_counts,
colData = clin_info,
design = ~ project_id + sample_type
)
dds <- DESeq(dds)
这种方式在校正批次的同时,直接获得考虑批次效应的差异分析结果。但要注意设计矩阵中变量的顺序:最后一个变量应该是主要比较因素。
校正后必须验证效果,我推荐组合使用:
r复制# 校正前后PCA对比
par(mfrow=c(1,2))
draw_pca(exprset, factor(clin_info$project_id), main="Before")
draw_pca(expr_combat, factor(clin_info$project_id), main="After")
除了可视化,还可以计算:
r复制# 计算批次混淆分数
library(kBET)
batch_est <- kBET(
t(expr_combat),
clin_info$project_id,
plot = FALSE
)
在最近一项多组学研究中,经过ComBat校正后,批次对基因表达变异的解释度从15%降至3%,而疾病状态的解释度从8%提升到12%。
当某些生物学分组只存在于特定批次时(如所有正常样本都来自一个中心),常规方法可能失效。这时可以:
对于同时包含mRNA、miRNA和甲基化的数据,建议:
我曾用这种方法成功整合了TCGA乳腺癌的三种组学数据,使生存预测模型的AUC从0.72提升到0.81。
在临床数据中,批次效应往往与真实生物学信号交织。有次分析儿童白血病数据时,最初以为是批次效应的信号,后来发现其实反映了不同的治疗反应群体。这提醒我们:
存储校正结果时,建议保留关键参数和版本信息:
r复制save(expr_combat, clin_info,
file = "COAD_READ_combat_v2_202405.rdata",
version = 2)