利用DESeq2进行转录组差异表达分析:从count数据到结果解读全流程

引言

转录组测序(RNA-seq)已成为现代生物学研究中不可或缺的技术手段,而差异表达分析则是其中最关键的分析环节之一。


本文将基于R语言的DESeq2包,通过airway数据集实战演示「从原始count数据到结果解读」的完整分析流程,重点解决分析过程中常见的「基因注释、数据预处理、结果可视化」等关键问题。



技术亮点

本教程将深入讲解以下核心技术:


「核心分析包」:DESeq2、airway、ggplot2、pheatmap

「关键函数」:DESeqDataSetFromMatrix()、DESeq()、results()、vst()

「高级技巧」:基因ID转换策略、重复基因处理、独立过滤原理

「可视化方法」:火山图、热图、PCA图的专业绘制与解读


完整分析流程


1. 环境准备与数据加载

首先确保所有必要的R包已安装加载。这里采用「自动化检查安装」的方法,提高代码可重复性:


# 检查并安装必要的Bioconductor包

if (!require("BiocManager", quietly = TRUE)){

 install.packages("BiocManager")

}


# 自动检查、安装、加载所需包

required_packages <- c("DESeq2", "airway", "ggplot2", "pheatmap", "ggrepel",

                      "dplyr", "tidyr", "reshape2", "AnnotationDbi", "org.Hs.eg.db", "biomaRt")

for (pkg in required_packages) {

if (!require(pkg, character.only = TRUE, quietly = TRUE)) {

   message("Installing package: ", pkg)

   BiocManager::install(pkg, update = FALSE)

   if (!require(pkg, character.only = TRUE, quietly = TRUE)) {

     stop("Failed to install package: ", pkg)

   }

 }

}

加载airway数据集并查看基本信息:


# 加载airway数据集

data("airway") # RangedSummarizedExperiment对象是Bioconductor生态系统中存储基因组数据的标准容器,包含三个核心组件:assay(计数矩阵)、rowData(基因信息)和colData(样本信息)。

raw_counts <- assay(airway)  # 提取计数矩阵

col_data <- colData(airway)   # 提取样本信息


# 查看数据基本信息

cat("计数矩阵维度:", dim(raw_counts), "\n")

cat("样本分组情况:\n")

print(table(col_data$dex))

airway数据集记录了地塞米松处理对人气道平滑肌细胞的基因表达影响,包含4个处理组和4个对照组。



2. 基因注释与ID转换

原始数据通常使用Ensembl ID作为基因标识,但我们需要更易读的Gene Symbol进行后续分析和结果解读。



离线转换方法(备选)

# 获取原始基因ID(Ensembl ID)

ensembl_ids <- rownames(raw_counts)


# 使用org.Hs.eg.db包进行ID转换

convert_ensembl_to_symbol <- function(ensembl_ids) {

# 去除Ensembl ID的版本号。Ensembl ID中的版本信息(如ENSG00000000003.15)会频繁更新,而去除版本号后使用稳定ID(ENSG00000000003)能提高注释成功率。

 clean_ids <- sub("\\..*", "", ensembl_ids)


# 使用mapIds进行ID转换

 symbol_map <- mapIds(org.Hs.eg.db,

                      keys = clean_ids,

                      column = "SYMBOL",

                      keytype = "ENSEMBL",

                      multiVals = "first")  # 多重映射时取第一个

return(symbol_map)

}


# 执行ID转换

offline_symbols <- convert_ensembl_to_symbol(ensembl_ids)

multiVals = "first"参数处理一个Ensembl ID对应多个Gene Symbol的情况,虽然可能不是最优选择,但在大多数情况下是可接受的折衷方案。



在线转换方法(推荐)

convert_ensembl_to_symbol_online <- function(ensembl_ids) {

# 连接到Ensembl数据库获取最新注释

 mart <- useMart("ensembl", dataset = "hsapiens_gene_ensembl")


# 去除版本号

 clean_ids <- sub("\\..*", "", ensembl_ids)


# 获取基因Symbol

 results <- getBM(attributes = c("ensembl_gene_id", "hgnc_symbol"),

                  filters = "ensembl_gene_id",

                  values = clean_ids,

                  mart = mart)


# 创建映射

 symbol_map <- setNames(results$hgnc_symbol, results$ensembl_gene_id)


# 将原始ID(带版本号)映射到Symbol

 final_map <- symbol_map[sub("\\..*", "", ensembl_ids)]

 names(final_map) <- ensembl_ids


return(final_map)

}


# 执行ID转换

online_symbols <- convert_ensembl_to_symbol_online(ensembl_ids)

离线方法使用本地注释数据库,速度更快且可重复性好。在线方法能获取最新注释但依赖网络。实际分析中建议先尝试在线方法。



3. 基因去重处理

一个常见的挑战是多个Ensembl ID可能对应同一个Gene Symbol,需要合理处理:


# 创建基因注释数据框

gene_annotation <- data.frame(

 ensembl_id = ensembl_ids,

 symbol = online_symbols,

 stringsAsFactors = FALSE

)


# 去除没有对应Symbol的基因

annotated_genes <- gene_annotation[!is.na(gene_annotation$symbol), ]


# 处理重复Symbol:保留每个Symbol中表达量最高的Ensembl ID

resolve_duplicate_symbols <- function(count_matrix, gene_annotation) {

# 计算每个基因的平均表达量

 gene_means <- rowMeans(count_matrix)


# 合并表达量信息并去重

 unique_genes <- data.frame(

   ensembl_id = rownames(count_matrix),

   mean_expression = gene_means,

   stringsAsFactors = FALSE

 ) %>%

   inner_join(gene_annotation, by = "ensembl_id") %>%

   filter(!is.na(symbol)) %>%

   group_by(symbol) %>%

   arrange(desc(mean_expression)) %>% # 按表达量降序排列

   slice(1) %>%  # 取每个组的第一个(表达量最高的)

   ungroup()


return(unique_genes)

}


# 执行去重

unique_genes <- resolve_duplicate_symbols(raw_counts, gene_annotation)

dedup_raw_counts <- raw_counts[unique_genes$ensembl_id, ]

rownames(dedup_raw_counts) <- unique_genes$symbol

选择表达量最高的转录本通常能够捕获该基因最主要的活性形式,这在大多数生物学场景下是合理的选择。其他策略包括取平均值等,应根据研究目的选择。



4. 数据预处理与质控


创建DESeq2数据对象

# 从计数矩阵创建DESeqDataSet对象

dds <- DESeqDataSetFromMatrix(

 countData = dedup_raw_counts,

 colData = col_data,

 design = ~ dex  # 指定实验设计。`~ dex`表示我们要研究`dex`变量(处理条件)对基因表达的影响,这是后续统计检验的基础。如果存在批次效应,可以在design公式中加入批次变量,如~ batch + dex。

)


过滤低表达基因

# 过滤低表达基因

keep <- rowSums(counts(dds) >= 10) >= 3 # 我们的过滤标准(至少3个样本中计数≥10)在统计效能和基因保留之间取得了良好平衡

dds <- dds[keep,]

cat("过滤后保留", nrow(dds), "个基因\n")

「为什么要过滤低表达基因?」


「提高统计可靠性」:低计数基因方差估计不可靠,会影响离散度估计

「减少多重检验负担」:降低假阳性率

「减少技术噪音」:避免将技术误差误认为生物学信号


数据标准化与变换

# 方差稳定变换

vsd <- vst(dds, blind = FALSE) # vst(方差稳定变换):计算速度更快,特别适用于大样本量。rlog(正则化对数变换):计算速度较慢,但对于小样本量可能表现稍好。

「vst变换的作用」:


解决RNA-seq数据方差依赖均值的问题。vst(方差稳定变换)比简单的log2转换更适合RNA-seq数据。

使数据更接近正态分布,适合下游可视化

blind = FALSE考虑实验设计,能更好揭示组间差异


PCA分析检查数据质量

pca_data <- plotPCA(vsd, intgroup = "dex", returnData = TRUE)

percent_var <- round(100 * attr(pca_data, "percentVar"))

ggplot(pca_data, aes(x = PC1, y = PC2, color = group)) +

 geom_point() +

 theme_bw(base_size = 8) +

 xlab(paste0("PC1: ", percent_var[1], "% variance")) +

 ylab(paste0("PC2: ", percent_var[2], "% variance")) +

 ggtitle("PCA plot") +

 scale_color_manual(values = c("untrt" = "blue", "trt" = "red"))

PCA图可以直观显示样本间的相似性和分组情况,是重要的质控步骤。



5. DESeq2差异表达分析


运行核心分析流程

# 运行DESeq2分析(包含标准化、离散度估计、模型拟合等所有步骤)

dds <- DESeq(dds)


# 提取分析结果

res <- results(dds,

              contrast = c("dex", "trt", "untrt"),  # 指定比较:处理组 vs 对照组

              alpha = 0.05,    # 显著性阈值,控制错误发现率(FDR)为5%

              lfcThreshold = 0) # log2FC阈值


# 添加显著性标记

res_df <- as.data.frame(res)

res_df$significant <- ifelse(res_df$padj < 0.05 & abs(res_df$log2FoldChange) >= 1,

                            ifelse(res_df$log2FoldChange >= 1, "Up", "Down"),

                            "Not significant")

res_df <- na.omit(res_df)


结果统计与保存

# 筛选显著差异表达基因

sig_genes <- subset(res[order(res$padj), ], padj < 0.05 & abs(log2FoldChange) > 1)

cat("显著差异基因数量:", nrow(sig_genes), "\n")

cat("上调基因数量:", sum(sig_genes$log2FoldChange > 0, na.rm = TRUE), "\n")

cat("下调基因数量:", sum(sig_genes$log2FoldChange < 0, na.rm = TRUE), "\n")


# 保存结果

write.csv(res_df, "DESeq2_results.csv", row.names = TRUE)


6. 结果可视化与解读


火山图展示差异表达格局

volcano_plot <- ggplot(res_df, aes(x = log2FoldChange, y = -log10(padj))) +

 geom_point(aes(color = significant), alpha = 0.5) +

 scale_color_manual(name = "",

                    values = c("Not significant" = "gray",

                              "Up" = "red", "Down" = "blue")) +

 geom_hline(yintercept = -log10(0.05), linetype = "dashed", color = "grey") +

 geom_vline(xintercept = c(-1, 1), linetype = "dashed", color = "grey") +

 labs(title = "Volcano Plot of Differential Expression",

      x = expression(Log[2]~"Fold Change"),

      y = expression(-Log[10]~"P.adj")) +

 theme_bw(base_size = 8)


# 添加Top基因标签

volcano_plot +

 geom_label_repel(data = res_df[head(rownames(sig_genes), 10), ],

                  aes(label = rownames(res_df[head(rownames(sig_genes), 10), ])),

                  box.padding = 0.5,

                  max.overlaps = Inf)


热图展示基因表达模式

# 选择前50个显著差异基因

select_genes <- rownames(sig_genes)[1:min(50, nrow(sig_genes))]

mat <- assay(vsd)[select_genes, ]


# 绘制热图

pheatmap(mat,

        scale = "row",  # 对行进行Z-score标准化

        show_rownames = TRUE,    # 显示基因名

        show_colnames = TRUE,    # 显示样本名

        cluster_rows = TRUE,     # 对行聚类

        cluster_cols = TRUE,     # 对列聚类

        annotation_col = as.data.frame(col_data["dex"]),

        main = paste("Heatmap of Top", length(select_genes), "Significant DEGs"),

        fontsize_row = 8,

        color = colorRampPalette(c("blue", "white", "red"))(50)

        )


总结

正确的实验设计公式是分析的前提

合理的基因注释和去重策略保证结果可靠性

严格的质量控制和适当的过滤提高分析灵敏度

多角度的可视化帮助理解和验证结果

「完整代码已分享,欢迎在实际研究中应用并根据具体需求调整参数。如果你在分析中遇到问题或有更好的实践经验,欢迎在评论区分享交流!」