多数据集合并与批次校正:整合TCGA和GEO数据

生信学长
2025年10月21日
TCGA数据挖掘
多数据集合并与批次校正:整合TCGA和GEO数据

点击卡片关注,一起学习生信分析!



大家早上好,今天给大家讲一下 多数据集合并与批次校正,以TCGA和GEO的数据来做展示

1. 背景介绍

在生信分析中,我们经常需要整合来自不同来源的数据集,以增加样本量、提高统计效力或进行meta分析。然而,不同数据集之间存在批次效应(Batch Effect),即由于实验条件、测序平台、处理时间等非生物学因素导致的系统性差异。

批次效应会掩盖真实的生物学信号,导致分析结果不准确。因此,在合并多个数据集之前,必须进行批次校正(Batch Correction)。

「应用场景」

  • 整合TCGA和GEO数据增加样本量
  • 合并多个GEO数据集进行meta分析
  • 消除不同测序批次间的系统性差异
  • 提高差异基因检测的统计效力

2. 分析原理

2.1 批次效应的来源

「技术性批次效应」

  • 不同的测序平台(如Illumina vs Affymetrix)
  • 不同的实验批次(不同时间处理的样本)
  • 不同的操作人员
  • 不同的试剂批次

「数据类型差异」

  • TCGA数据:RNA-seq,TPM/FPKM/Counts
  • GEO数据:芯片数据,原始信号值或标准化值

2.2 批次校正方法

本文使用 「ComBat」 方法进行批次校正:

  • ComBat是经验贝叶斯方法
  • 可以有效去除批次效应
  • 保留生物学变异
  • 广泛应用于基因表达数据整合

2.3 数据合并流程

数据准备 → 数据标准化 → 提取共同基因 → 数据合并 → 批次校正 → 导出结果 

「关键步骤」

  1. 「数据标准化」 :TCGA数据log2转换,GEO数据分位数标准化
  2. 「基因匹配」 :提取所有数据集的共同基因
  3. 「批次定义」 :标记每个样本来自哪个数据集
  4. 「ComBat校正」 :去除批次效应

3. 环境准备

3.1 R包安装

# 安装Bioconductor管理器
if (!requireNamespace("BiocManager", quietly = TRUE))
    install.packages("BiocManager")

# 安装limma包(用于数据标准化)
BiocManager::install("limma")
# 安装sva包(包含ComBat批次校正方法)
BiocManager::install("sva")

3.2 数据准备

本教程需要三个数据集:

  • 「TCGA_LUSC_TPM.txt」 :TCGA肺鳞癌数据(TPM值)
  • 「GSE30219.txt」 :GEO芯片数据集1
  • 「GSE74777.txt」 :GEO芯片数据集2

「数据格式要求」

  • 第一列:基因Symbol
  • 其余列:样本表达值
  • 行:基因,列:样本

4. 代码实现

方法一:手动逐步合并(适合初学者)

这种方法逐步展示每个处理步骤,便于理解整个流程。

4.1 加载R包

# 加载必要的R包
library(limma)    # 用于数据标准化
library(sva)      # 用于ComBat批次校正

4.2 设置工作目录

# 设置工作目录为数据文件所在位置
setwd("你的工作目录")

# 检查当前工作目录
getwd()

# 检查数据文件是否存在
file.exists("TCGA_LUSC_TPM.txt")
file.exists("GSE30219.txt")
file.exists("GSE74777.txt")

4.3 读取数据集

# 读取TCGA数据
rt1 <- read.table("TCGA_LUSC_TPM.txt",
                  header = TRUE,
                  sep = "\t",
                  check.names = FALSE,
                  row.names = 1)

# 查看数据维度
cat("TCGA数据:", nrow(rt1), "个基因 x", ncol(rt1), "个样本\n")
# 转换为数值型矩阵
dimnames <- list(rownames(rt1), colnames(rt1))
rt1 <- matrix(as.numeric(as.matrix(rt1)),
              nrow = nrow(rt1),
              dimnames = dimnames)

# 查看数据前几行
rt1[1:51:3]
# 读取GEO数据集1 (GSE30219)
rt2 <- read.table("GSE30219.txt",
                  header = TRUE,
                  sep = "\t",
                  check.names = FALSE,
                  row.names = 1)

dimnames <- list(rownames(rt2), colnames(rt2))
rt2 <- matrix(as.numeric(as.matrix(rt2)),
              nrow = nrow(rt2),
              dimnames = dimnames)

cat("GSE30219数据:", nrow(rt2), "个基因 x", ncol(rt2), "个样本\n")
# 读取GEO数据集2 (GSE74777)
rt3 <- read.table("GSE74777.txt",
                  header = TRUE,
                  sep = "\t",
                  check.names = FALSE,
                  row.names = 1)

dimnames <- list(rownames(rt3), colnames(rt3))
rt3 <- matrix(as.numeric(as.matrix(rt3)),
              nrow = nrow(rt3),
              dimnames = dimnames)

cat("GSE74777数据:", nrow(rt3), "个基因 x", ncol(rt3), "个样本\n")

4.4 数据标准化

不同来源的数据需要不同的标准化方法。

# 对TCGA数据进行log2转换
# TCGA的TPM数据通常未经log转换
rt1 <- log2(rt1 + 1)
cat("✓ TCGA数据log2转换完成\n")
# 对GEO芯片数据进行分位数标准化
# 使用limma包的normalizeBetweenArrays函数
rt2 <- normalizeBetweenArrays(rt2)
cat("✓ GSE30219数据标准化完成\n")

rt3 <- normalizeBetweenArrays(rt3)
cat("✓ GSE74777数据标准化完成\n")

「为什么不同数据需要不同处理?」

  • TCGA的TPM数据:线性尺度,需要log转换使其接近正态分布
  • GEO芯片数据:可能已经log转换,使用分位数标准化使样本间分布一致

4.5 提取共同基因

只有所有数据集都包含的基因才能用于合并。

# 获取前两个数据集的共同基因
gene1 <- intersect(rownames(rt1), rownames(rt2))
cat("TCGA和GSE30219的共同基因:", length(gene1), "个\n")
# 获取三个数据集的共同基因
gene2 <- intersect(gene1, rownames(rt3))
cat("三个数据集的共同基因:", length(gene2), "个\n")

# 查看共同基因示例
head(gene2, 10)

4.6 按共同基因合并数据

# 创建空数据框用于合并
allmerge1 <- data.frame()

# 合并TCGA和GSE30219
allmerge1 <- cbind(rt1[gene2, ], rt2[gene2, ])
cat("合并TCGA和GSE30219后:", nrow(allmerge1), "个基因 x", ncol(allmerge1), "个样本\n")
# 继续合并GSE74777
allmerge1 <- cbind(allmerge1, rt3[gene2, ])
cat("最终合并矩阵:", nrow(allmerge1), "个基因 x", ncol(allmerge1), "个样本\n")

# 查看合并后的数据
allmerge1[1:5, c(1:3, (ncol(rt1)+1):(ncol(rt1)+3), (ncol(allmerge1)-2):ncol(allmerge1))]

4.7 定义批次信息

ComBat需要知道每个样本属于哪个批次(数据集)。

# 创建批次标签向量
batchType1 <- c()

# TCGA数据标记为批次1
batchType1 <- c(batchType1, rep(1, ncol(rt1)))

# GSE30219标记为批次2
batchType1 <- c(batchType1, rep(2, ncol(rt2)))

# GSE74777标记为批次3
batchType1 <- c(batchType1, rep(3, ncol(rt3)))

# 查看批次信息
cat("批次信息:\n")
cat("批次1 (TCGA):", sum(batchType1 == 1), "个样本\n")
cat("批次2 (GSE30219):", sum(batchType1 == 2), "个样本\n")
cat("批次3 (GSE74777):", sum(batchType1 == 3), "个样本\n")

4.8 执行ComBat批次校正

# 使用ComBat进行批次校正
cat("\n开始ComBat批次校正...\n")
outTab1 <- ComBat(dat = allmerge1,
                  batch = batchType1,
                  par.prior = TRUE)   # 使用参数先验

cat("✓ 批次校正完成\n")

「ComBat参数说明」

  • dat :表达矩阵(基因×样本)
  • batch :批次标签向量
  • par.prior :是否使用参数先验(TRUE更稳健)

4.9 格式化并导出数据

# 在第一行添加样本ID
outTab1 <- rbind(ID = colnames(outTab1), outTab1)

# 查看校正后的数据
outTab1[1:61:5]
# 导出批次校正后的数据
write.table(outTab1,
            file = "merge1.txt",
            sep = "\t",
            quote = FALSE,
            col.names = FALSE)

cat("✓ 批次校正后的数据已保存到 merge1.txt\n")
cat("最终矩阵:", nrow(outTab1)-1"个基因 x", ncol(outTab1), "个样本\n")

方法二:循环批量处理(适合多数据集)

当需要合并多个数据集时,使用循环可以使代码更简洁高效。

4.10 准备文件列表

# 加载R包
library(limma)
library(sva)

# 设置工作目录
setwd("你的工作目录")
# 定义要合并的文件列表
files <- c("TCGA_LUSC_TPM.txt""GSE30219.txt""GSE74777.txt")

cat("准备合并", length(files), "个数据集\n")
for (i in 1:length(files)) {
  cat(i, ".", files[i], "\n")
}

4.11 循环读取并标准化数据

# 创建列表存储每个数据集
geneList <- list()

cat("\n开始读取和处理数据...\n")
for (i in1:length(files)) {

# 读取数据
  rt <- read.table(files[i],
                   header = TRUE,
                   sep = "\t",
                   check.names = FALSE,
                   row.names = 1)

# 转换为数值矩阵
  dimnames <- list(rownames(rt), colnames(rt))
  rt <- matrix(as.numeric(as.matrix(rt)),
               nrow = nrow(rt),
               dimnames = dimnames)

# 根据样本ID前缀判断数据类型并标准化
# TCGA样本ID以"TCGA"开头
if (substr(colnames(rt)[1], 13) == "TCG") {
    rt <- log2(rt + 1)
    cat(i, ". ", files[i], " - TCGA数据,已log2转换\n")
  }

# GEO样本ID以"GSM"开头
if (substr(colnames(rt)[1], 13) == "GSM") {
    rt <- normalizeBetweenArrays(rt)
    cat(i, ". ", files[i], " - GEO数据,已标准化\n")
  }

# 保存到列表
  geneList[[i]] <- rt

# 获取共同基因
if (i == 1) {
    gene3 <- rownames(geneList[[i]])
  } else {
    gene3 <- intersect(gene3, rownames(geneList[[i]]))
  }
}

cat("\n所有数据集的共同基因:", length(gene3), "个\n")

4.12 循环合并数据并定义批次

# 初始化合并矩阵和批次向量
allmerge2 <- data.frame()
batchType2 <- c()

cat("\n开始合并数据集...\n")
for (i in1:length(files)) {

# 合并数据(只保留共同基因)
if (i == 1) {
    allmerge2 <- geneList[[i]][gene3, ]
    cat("初始化矩阵:", nrow(allmerge2), "基因 x", ncol(allmerge2), "样本\n")
  } else {
    allmerge2 <- cbind(allmerge2, geneList[[i]][gene3, ])
    cat("合并第", i, "个数据集,当前:", nrow(allmerge2), "基因 x", ncol(allmerge2), "样本\n")
  }

# 添加批次信息
  batchType2 <- c(batchType2, rep(i, ncol(geneList[[i]])))
}

cat("\n合并完成!\n")
cat("批次分布:\n")
for (i in1:length(files)) {
  cat("批次", i, "(", files[i], "):", sum(batchType2 == i), "个样本\n")
}

4.13 执行批次校正并导出

# ComBat批次校正
cat("\n开始ComBat批次校正...\n")
outTab2 <- ComBat(dat = allmerge2,
                  batch = batchType2,
                  par.prior = TRUE)
cat("✓ 批次校正完成\n")
# 添加样本ID行并导出
outTab2 <- rbind(ID = colnames(outTab2), outTab2)
write.table(outTab2,
            file = "merge2.txt",
            sep = "\t",
            quote = FALSE,
            col.names = FALSE)

cat("✓ 数据已保存到 merge2.txt\n")
cat("最终矩阵:", nrow(outTab2)-1"个基因 x", ncol(outTab2), "个样本\n")

5. 结果解读

5.1 输出文件说明

运行脚本后会生成两个文件(两种方法结果相同):

「merge1.txt / merge2.txt」

  • 第一行:样本ID
  • 第一列:基因Symbol
  • 数值:批次校正后的标准化表达值

5.2 批次校正前后对比

「校正前」

  • 来自不同数据集的样本会聚类在一起
  • 批次效应掩盖了真实的生物学分组
  • PCA图显示样本按数据集分开

「校正后」

  • 批次效应被去除
  • 样本按生物学特征(如肿瘤vs正常)分组
  • 可以进行联合差异分析

5.3 数据质量检查

# 读取批次校正后的数据
merged_data <- read.table("merge1.txt", header = TRUE, sep = "\t",
                          check.names = FALSE, row.names = 1)
merged_data <- merged_data[-1, ]  # 删除ID行
merged_data <- as.matrix(merged_data)

# 转换为数值型
mode(merged_data) <- "numeric"

# 查看数据分布
summary(as.vector(merged_data))

# 检查缺失值
sum(is.na(merged_data))

# 查看数据维度
dim(merged_data)

5.4 批次校正效果评估

「方法一:PCA分析」

# 需要安装FactoMineR包
# install.packages("FactoMineR")
library(FactoMineR)

# 进行PCA分析
pca_result <- PCA(t(merged_data), graph = FALSE)

# 可视化PCA结果
plot(pca_result, choix = "ind")

「方法二:样本相关性热图」

# 计算样本间相关性
cor_matrix <- cor(merged_data)

# 绘制热图
library(pheatmap)
pheatmap(cor_matrix,
         show_rownames = FALSE,
         show_colnames = FALSE)

5.5 合并数据的典型统计

假设合并了TCGA、GSE30219、GSE74777三个数据集:

数据集
原始基因数
原始样本数
数据类型
TCGA-LUSC
~20,000
~500
RNA-seq
GSE30219
~13,000
~85
芯片
GSE74777
~15,000
~107
芯片
「合并后」
~10,000
~692
标准化

6. 常见问题

6.1 共同基因太少怎么办?

「问题」 :三个数据集合并后只剩下很少的基因

「原因」

  • GEO芯片平台不同,覆盖的基因不同
  • 基因命名不一致(Symbol vs Ensembl ID)

「解决方法」

# 检查各数据集的基因数量
cat("TCGA基因数:", length(rownames(rt1)), "\n")
cat("GSE30219基因数:", length(rownames(rt2)), "\n")
cat("GSE74777基因数:", length(rownames(rt3)), "\n")

# 两两比较共同基因数
length(intersect(rownames(rt1), rownames(rt2)))
length(intersect(rownames(rt1), rownames(rt3)))
length(intersect(rownames(rt2), rownames(rt3)))

6.2 ComBat运行报错

「错误1」 batch has too many levels

「原因」 :批次数量过多,样本量太少

「解决」

  • 确保每个批次至少有3-5个样本
  • 合并相似的批次

「错误2」 missing values in data

「原因」 :数据中有NA值

「解决」

# 删除包含NA的基因
merged_data <- merged_data[complete.cases(merged_data), ]

# 或者用均值填充
merged_data[is.na(merged_data)] <- mean(merged_data, na.rm = TRUE)

6.3 数据尺度不一致

「问题」 :TCGA和GEO数据的数值范围差异很大

「检查」

# 查看数据范围
range(rt1, na.rm = TRUE)
range(rt2, na.rm = TRUE)
range(rt3, na.rm = TRUE)

「解决」 :确保所有数据都经过适当的标准化

  • TCGA:log2(TPM + 1)
  • GEO:normalizeBetweenArrays()

6.4 如何判断是否需要批次校正?

「方法1:查看样本来源是否聚类」

# 绘制聚类热图
pheatmap(cor(allmerge1),
         annotation_col = data.frame(
           Batch = factor(batchType1)
         ))

「方法2:PCA分析」

  • 如果PCA图中样本按数据集分组,需要批次校正
  • 如果按生物学分组,可能不需要

6.5 批次校正过度怎么办?

过度校正可能去除真实的生物学差异。

「预防措施」

  • 保留原始数据用于比较
  • 如果有分组信息,在ComBat中使用mod参数保护生物学差异
# 创建模型矩阵保护分组信息
# group <- c(rep("Tumor", n_tumor), rep("Normal", n_normal))
# mod <- model.matrix(~as.factor(group))
# outTab <- ComBat(allmerge1, batchType1, mod = mod)

6.6 大数据集内存不足

「问题」 :合并大量样本时内存溢出

「解决」

# 增加R的内存限制
memory.limit(size = 50000)  # Windows
# options(java.parameters = "-Xmx8g")  # 增加Java堆内存

# 或者分批处理
# 先合并一部分数据集,再合并其他数据集


如果需要文章代码,联系老师免费获取。

( 请发送文章链接告知老师获取哪个代码)






送你一份科研小礼物,开启科研生涯!

点击即可领取:

》〉戳我,领取一份科研小礼物