跳转至

183_RNA-seq批次效应校正

一句话概述

RNA-seq批次效应是由实验操作、测序时间、试剂批号等非生物学因素引起的系统性技术偏差,通过ComBat/ComBat-seq、limma::removeBatchEffect、Harmony等方法校正后才能进行跨批次样本的合理比较。

核心知识点表格

知识点说明
批次效应来源建库时间、操作员、测序lane、试剂批号、RNA提取方法
检测方法PCA着色、RLE图、层次聚类
ComBatsva包,经验贝叶斯方法,最经典的批次校正
ComBat-seq专为RNA-seq counts设计的ComBat变体
removeBatchEffectlimma包,在差异分析前校正可视化用
SVA隐变量分析,无需已知批次信息
RUVSeq利用对照基因去除不需要的变异
注意事项批次与实验设计完全混淆时无法校正

步骤详解

第一步:检测批次效应

白话解释:在校正之前,首先需要确认批次效应是否存在。最直观的方法是做PCA,看样本是否按批次分离而非按实验分组分离。

技术细节:检测方法包括:(1)PCA/t-SNE按批次着色;(2)相对对数表达(RLE)图检查分布一致性;(3)层次聚类看样本是否先按批次聚类。

library(DESeq2)
library(ggplot2)
library(pheatmap)

# 读取数据
counts <- read.csv("count_matrix.csv", row.names = 1)
coldata <- read.csv("sample_info.csv", row.names = 1)

# 确保有batch信息
head(coldata)
#          condition batch
# sample1  Control   batch1
# sample2  Control   batch1
# sample3  Treatment batch1
# sample4  Control   batch2
# sample5  Treatment batch2

# DESeq2标准化
dds <- DESeqDataSetFromMatrix(countData = counts,
                               colData = coldata,
                               design = ~ condition)
vsd <- vst(dds, blind = TRUE)  # 方差稳定化变换

# PCA检查批次效应
pca_data <- plotPCA(vsd, intgroup = c("condition", "batch"), returnData = TRUE)
percentVar <- round(100 * attr(pca_data, "percentVar"))

ggplot(pca_data, aes(x = PC1, y = PC2, color = batch, shape = condition)) +
  geom_point(size = 4) +
  xlab(paste0("PC1: ", percentVar[1], "% variance")) +
  ylab(paste0("PC2: ", percentVar[2], "% variance")) +
  ggtitle("PCA: Before Batch Correction") +
  theme_minimal()

# 样本间相关性热图
sampleDists <- dist(t(assay(vsd)))
sampleDistMatrix <- as.matrix(sampleDists)

annotation_col <- data.frame(
  Batch = coldata$batch,
  Condition = coldata$condition,
  row.names = rownames(coldata)
)

pheatmap(sampleDistMatrix,
         annotation_col = annotation_col,
         clustering_distance_rows = sampleDists,
         clustering_distance_cols = sampleDists,
         main = "Sample Distance (Before Correction)")

第二步:使用ComBat-seq校正counts数据

白话解释:ComBat-seq是专门为RNA-seq原始count数据设计的批次校正方法。它保持数据的count性质(整数、非负),校正后的数据可以直接用于DESeq2或edgeR分析。

技术细节:ComBat-seq基于负二项分布建模,使用经验贝叶斯方法估计和去除批次效应。与原始ComBat不同,它直接在count层面操作,不需要先做log变换,输出仍为整数counts。

library(sva)

# ComBat-seq校正
adjusted_counts <- ComBat_seq(
  counts = as.matrix(counts),
  batch = coldata$batch,
  group = coldata$condition,  # 保护的生物学变量
  covar_mod = NULL            # 额外协变量
)

# 验证校正效果
dds_adj <- DESeqDataSetFromMatrix(countData = adjusted_counts,
                                    colData = coldata,
                                    design = ~ condition)
vsd_adj <- vst(dds_adj, blind = TRUE)

pca_adj <- plotPCA(vsd_adj, intgroup = c("condition", "batch"), returnData = TRUE)
percentVar_adj <- round(100 * attr(pca_adj, "percentVar"))

ggplot(pca_adj, aes(x = PC1, y = PC2, color = batch, shape = condition)) +
  geom_point(size = 4) +
  xlab(paste0("PC1: ", percentVar_adj[1], "% variance")) +
  ylab(paste0("PC2: ", percentVar_adj[2], "% variance")) +
  ggtitle("PCA: After ComBat-seq Correction") +
  theme_minimal()

# 使用校正后的counts进行差异分析
dds_adj <- DESeq(dds_adj)
res <- results(dds_adj, contrast = c("condition", "Treatment", "Control"))

第三步:使用limma::removeBatchEffect

白话解释:limma的removeBatchEffect是最简单的批次校正方法,直接从表达矩阵中减去批次效应。但注意,它只用于可视化和探索性分析,差异分析时应在模型中加入batch作为协变量。

技术细节:removeBatchEffect使用线性模型拟合批次效应并移除,同时保留design中指定的生物学效应。它作用于连续值矩阵(log-counts或VST变换后的数据)。

library(limma)

# 对VST变换后的数据校正(用于可视化)
mat <- assay(vsd)
mat_corrected <- removeBatchEffect(
  mat,
  batch = coldata$batch,
  design = model.matrix(~ condition, data = coldata)
)

# 可视化校正后的PCA
pca_result <- prcomp(t(mat_corrected))
pca_df <- data.frame(
  PC1 = pca_result$x[, 1],
  PC2 = pca_result$x[, 2],
  Batch = coldata$batch,
  Condition = coldata$condition
)
var_pct <- round(summary(pca_result)$importance[2, 1:2] * 100, 1)

ggplot(pca_df, aes(x = PC1, y = PC2, color = Condition, shape = Batch)) +
  geom_point(size = 4) +
  labs(x = paste0("PC1 (", var_pct[1], "%)"),
       y = paste0("PC2 (", var_pct[2], "%)"),
       title = "After removeBatchEffect") +
  theme_minimal()

# 差异分析中正确的做法:在模型中加入batch
design <- model.matrix(~ batch + condition, data = coldata)
dds <- DESeqDataSetFromMatrix(counts, coldata, design = ~ batch + condition)
dds <- DESeq(dds)
res <- results(dds, contrast = c("condition", "Treatment", "Control"))

第四步:使用SVA发现隐藏的批次效应

白话解释:有时批次效应的来源不清楚,或者存在未记录的技术混淆因素。SVA(替代变量分析)可以自动发现这些隐藏的变异来源。

library(sva)

# 准备模型矩阵
mod <- model.matrix(~ condition, data = coldata)
mod0 <- model.matrix(~ 1, data = coldata)

# 估计替代变量数量
n_sv <- num.sv(as.matrix(counts), mod, method = "be")
cat("估计的替代变量数:", n_sv, "\n")

# 计算替代变量
svobj <- svaseq(as.matrix(counts), mod, mod0, n.sv = n_sv)

# 将SV加入DESeq2模型
coldata$SV1 <- svobj$sv[, 1]
coldata$SV2 <- svobj$sv[, 2]

dds <- DESeqDataSetFromMatrix(counts, coldata, design = ~ SV1 + SV2 + condition)
dds <- DESeq(dds)
res <- results(dds, contrast = c("condition", "Treatment", "Control"))

第五步:Python实现批次校正

import scanpy as sc
import numpy as np
import pandas as pd
from combat.pycombat import pycombat
import matplotlib.pyplot as plt

# 读取数据
counts = pd.read_csv("count_matrix.csv", index_col=0)
metadata = pd.read_csv("sample_info.csv", index_col=0)

# 使用pycombat校正
corrected = pycombat(
    counts,
    batch=metadata['batch'].values,
    mod=pd.get_dummies(metadata['condition'])
)

# 可视化
from sklearn.decomposition import PCA

pca = PCA(n_components=2)
log_counts = np.log2(counts.T + 1)
pca_before = pca.fit_transform(log_counts)

log_corrected = np.log2(corrected.T + 1)
pca_after = pca.fit_transform(log_corrected)

fig, axes = plt.subplots(1, 2, figsize=(14, 6))
for batch in metadata['batch'].unique():
    mask = metadata['batch'] == batch
    axes[0].scatter(pca_before[mask, 0], pca_before[mask, 1], label=batch, alpha=0.7)
    axes[1].scatter(pca_after[mask, 0], pca_after[mask, 1], label=batch, alpha=0.7)
axes[0].set_title('Before Correction')
axes[1].set_title('After Correction')
for ax in axes:
    ax.legend()
    ax.set_xlabel('PC1')
    ax.set_ylabel('PC2')
plt.tight_layout()
plt.savefig('batch_correction.png', dpi=300)

实战命令速查

# ComBat-seq一行校正
adjusted <- sva::ComBat_seq(as.matrix(counts), batch = coldata$batch, group = coldata$condition)

# limma校正可视化矩阵
corrected <- limma::removeBatchEffect(assay(vsd), batch = coldata$batch)

# DESeq2模型中加入批次
dds <- DESeqDataSetFromMatrix(counts, coldata, design = ~ batch + condition)

# SVA自动发现批次
svobj <- sva::svaseq(as.matrix(counts), mod, mod0)

# RUVSeq
library(RUVSeq)
set <- newSeqExpressionSet(as.matrix(counts), phenoData = coldata)
set <- RUVg(set, control_genes, k = 1)

面试常问点

Q1: 批次效应和生物学差异如何区分? A: 关键在于实验设计。如果所有Treatment样本在batch1、所有Control在batch2,则批次效应和生物学差异完全混淆,无法区分。好的设计应该让每个批次都包含所有实验组。可以通过PCA查看样本是按batch还是condition分离来初步判断。

Q2: ComBat和ComBat-seq的区别是什么? A: ComBat作用于连续值(如log-CPM),输出也是连续值,适合芯片数据。ComBat-seq专为RNA-seq设计,直接作用于count数据,基于负二项分布建模,输出整数counts,可直接输入DESeq2/edgeR。

Q3: removeBatchEffect可以用于差异分析吗? A: 不推荐。removeBatchEffect直接修改了表达矩阵,这会改变数据的方差结构,导致差异分析的统计检验失效(p值不可信)。正确做法是在差异分析模型中加入batch作为协变量(如~ batch + condition)。

Q4: 批次和实验设计完全混淆时怎么办? A: 无法用统计方法解决。任何校正方法都可能在去除批次效应的同时去除真实的生物学信号。唯一的解决方案是改进实验设计(平衡设计)或增加独立实验重复。

Q5: SVA和已知批次校正哪个更好? A: 如果批次信息明确可靠,直接使用ComBat-seq或在模型中加入batch更简单有效。SVA适用于批次信息未知或存在多个未记录的技术混淆时。两者也可以结合使用(先校正已知batch,再用SVA发现残余的隐藏因素)。

Q6: 批次校正会不会去除真实的生物学信号? A: 有可能。特别是当batch和condition存在一定程度的混淆时,校正可能部分移除生物学信号。ComBat和ComBat-seq通过group参数保护已知的生物学变量来缓解这个问题。

易错点

  1. batch和condition完全混淆仍强行校正:结果不可信,应从实验设计层面解决
  2. 对原始counts使用ComBat:应使用ComBat-seq,ComBat需要log变换后的数据
  3. removeBatchEffect后做差异分析:应在模型中加入batch协变量
  4. 过度校正:加入太多协变量可能移除真实信号
  5. 忽略batch平衡性:校正前应检查每个batch中各组的样本数是否平衡

补充知识

批次校正方法对比

方法输入原理适用场景
ComBat-seqCounts经验贝叶斯+负二项已知batch的RNA-seq
ComBat连续值经验贝叶斯芯片数据
removeBatchEffect连续值线性回归仅可视化
SVACounts/连续替代变量分析未知batch来源
RUVSeqCounts利用对照基因有对照基因时
Harmony降维空间迭代聚类单细胞数据

实验设计建议

  • 每个batch包含所有实验组(平衡设计)
  • 记录所有可能的技术变量(日期、操作员、试剂批号)
  • 加入技术重复检测batch效应大小
  • 避免将对照和处理分在不同批次