转录组下游分析全流程:DESeq2 差异分析、GO/KEGG 富集与 GSEA 实战

本文基于修改后的下游分析脚本 mRNA_QcAndQuant_2026-8-20.R ,系统讲解转录组下游差异分析与功能富集的完整流程:参数对结果的影响与比较、逐句语法分析、使用方法、在生信分析中的作用,并附加完整修改后 R 代码。

一、引言:下游分析,把表达矩阵翻译成生物学故事

RNA 测序经过质控、比对与定量等步骤后,得到了一张"基因 × 样本"的 count_matrix.txt 表达矩阵。但这只是"原材料"------它回答了"每个基因测到多少条 reads",却还没有回答生物学家真正关心的问题:

① 处理组与对照组相比,哪些基因表达显著改变?(差异表达分析)

② 这些改变的基因富集在哪些通路和生物学过程?(GO/KEGG 富集分析)

③ 即使单个基因变化不显著,整条通路的表达是否协同改变?(GSEA 基因集富集分析)

下游分析正是完成"从数据到结论"的最后一步。本文要介绍的下游脚本 mRNA_QcAndQuant_2026-8-20.R 把这一过程标准化为一键运行:DESeq2 差异分析 → 5 种经典可视化 → 本地数据库 GO/KEGG 富集(无需联网)→ GSEA,最终在 05.results 目录输出一套完整的、可直接用于论文的结果文件。

二、分析流程全景图:一行命令,走完下游全流程

下游脚本以"基因表达矩阵"为输入、以"富集结果与可视化图表"为输出,全流程共 6 个模块,串联起差异分析、可视化和功能注释三大环节:

count_matrix.txt → DESeq2 差异分析 显著 DEGs |log2FC|>1, padj<0.05


5
种可视化( MA/ 火山图 / 热图 /PCA/ 相关性热图)

本地 GO 富集( BP/CC/MF + 本地 KEGG 通路富集 → enrichment/

GSEA
全基因排序富集分析 → gsea/

📁 05.results/
全套结果文件

脚本会在项目根目录下自动创建 05.results 结果目录,内部按功能拆分为四个子目录:

|-----------------|------------------------------------------------------|
| 子目录 | 内容 |
| DESeq2_results/ | 差异分析结果:全基因表、显著基因、上调/下调基因、标准化矩阵 |
| plots/ | 5 种可视化图:MA 图、火山图、热图、PCA 图、样本相关性热图 |
| enrichment/ | 本地 GO/KEGG 富集结果:CSV/XLS 表格 + 柱状图/气泡图 + 交互 HTML |
| gsea/ | GSEA 结果:gsea_result.xls + 显著基因集 gseaplot2 图(PDF/PNG) |

三、项目概述与数据来源:示例数据说明

本文使用的示例数据来自 GEO 数据库的 GSE52778(airway 数据集),这是转录组入门分析最经典的公开数据集之一:人气道平滑肌细胞经糖皮质激素(地塞米松,Dexamethasone)处理后,研究基因表达的系统性变化。数据可通过 GEO / SRA / ENA 三个渠道免费获取:

|----------|-------------------------------------------------------------|
| 数据来源 | 地址 |
| GEO 主页 | https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE52778 |
| SRA 下载 | https://www.ncbi.nlm.nih.gov/sra |
| ENA 下载 | https://www.ebi.ac.uk/ena |

示例数据共 8 个样本(4 Control + 4 Treat),本文演示使用其中 4 个样本。样本编号与分组信息如下:

|----------|----------------|--------------------------|---------|--------|
| 本地样本 | SRR 编号 | GEO 样本 (GSM) | 供体 | 处理 |
| Control1 | SRR1039512 | GSM1275866 | N052611 | 未处理 |
| Control2 | SRR1039520 | GSM1275874 | N061011 | 未处理 |
| Control3 | SRR1039516 | GSM1275870 | N080611 | 未处理 |
| Control4 | SRR1039508 | GSM1275862 | N61311 | 未处理 |
| Treat1 | SRR1039513 | GSM1275867 | N052611 | 地塞米松 |
| Treat2 | SRR1039521 | GSM1275875 | N061011 | 地塞米松 |
| Treat3 | SRR1039517 | GSM1275871 | N080611 | 地塞米松 |
| Treat4 | SRR1039509 | GSM1275863 | N61311 | 地塞米松 |

实验设计要点:同一供体( N052611 等)同时有 Control Treat 样本,构成 " 配对 / 生物学重复 " 结构,是 DESeq2 计算差异显著性统计基础的来源。

四、软件与环境准备:一键锁定的分析环境

下游分析建议在本地或云端的 RStudio Server 环境中运行,所有依赖包可由脚本自动安装,无需手动配置。

|------------|-------------------|------------------------------|
| 类别 | 软件 | 版本 |
| R 环境 | R(统计语言) | 4.3.3(BiocManager 3.18) |
| 差异分析 | DESeq2 | 1.42.1 |
| 富集分析 | clusterProfiler | 4.10.1 |
| 富集可视化 | enrichplot | 1.22.0 |
| GSEA 算法 | fgsea | 1.28.0(clusterProfiler 内置调用) |
| 火山图 | EnhancedVolcano | 1.28.2 |
| 热图 | pheatmap | 1.0.13 |
| 通用绘图 | ggplot2 | 4.0.3 |
| 数据处理 | dplyr / pbapply | 1.2.1 / 1.7.4 |
| 交互表格 | DT / htmlwidgets | 0.34.0 / 1.6.4 |
| ID 转换 | org.Hs.eg.db | 3.18.0(ENSG→Symbol) |
| GO DAG(可选) | topGO / Rgraphviz | 2.54.0 / 2.46.0 |

脚本内置"依赖自动安装"逻辑:若环境缺失某个包,会尝试从 CRAN/Bioconductor 自动安装,保证"拿到脚本即可运行"。

五、数据准备:三大文件夹与百度网盘

所有分析文件都组织在工作目录 RNA_seq/ 下,按"参考数据 - 原始数据 - 富集数据库"三分类管理,这是保证分析可复现的经典目录规范:

|-----------------------------|--------|------------------------------------------|
| 子项 | 类型 | 说明 |
| reference/ | 文件夹 | 参考基因组 + GTF 注释(比对与定量环节的输入文件) |
| raw_data/ | 文件夹 | 8 样本双端测序原始数据(.fq.gz ×16) |
| enrichDB/ | 文件夹 | 4 物种本地 GO/KEGG 基因集(GMT + KEGG 映射表,下游富集用) |
| mRNA_QcAndQuant_2026-8-20.R | R 脚本 | 下游差异与富集分析主脚本(本文主角) |

其中 enrichDB/ 目录是下游富集分析的关键数据库------它把原本需要联网调用的 GO/KEGG 注释打包为本地基因集文件,让富集分析在无外网环境下也能稳定运行:

|--------|--------|---------------------------|------------------------|
| 物种 | 代码 | GMT 注释库 | KEGG 映射表 |
| 人 | hsa | hsa.gmt(3.27 MB,19,041 条) | hsa_keggID2symbols.xls |
| 小鼠 | mmu | mmu.gmt(4.18 MB,19,515 条) | mmu_keggID2symbols.xls |
| 大鼠 | rat | rat.gmt(3.71 MB,19,375 条) | rat_keggID2symbols.xls |
| 拟南芥 | ath | ath.gmt(2.18 MB,7,395 条) | ath_keggID2symbols.xls |

以人的 hsa.gmt 为例,它共包含 19,041 条基因集注释,覆盖 GO 三大本体与 KEGG 通路:

|-------------|--------|---------------------------------------------------------|
| 类型 | 条数 | 示例 |
| GO-BP(生物过程) | 12,255 | GO:0002764 immune response-regulating signaling pathway |
| GO-MF(分子功能) | 4,588 | GO:0003674 molecular_function |
| GO-CC(细胞组分) | 1,835 | GO 细胞组分条目 |
| KEGG 通路 | 363 | hsa01100 Metabolic pathways |
| 合计 | 19,041 | --- |

为什么使用本地 GMT 而非在线注释? 无需联网,运行稳定; 版本可控,结果可复现; 支持 hsa/mmu/rat/ath 四种物种,覆盖绝大多数常规转录组项目。

六、零基础实操:从平台入口到一键运行

下面按平台实际操作步骤,演示如何用 R 语言基础环境运行下游脚本。全程无需安装任何软件,浏览器即可完成。

6.1 进入平台并登录

打开浏览器输入平台地址,进入平台首页(登录后可见 JupyterHub 与 RStudio 等工具入口卡片):

8-1 登录平台后, RNA_seq 目录下的分析结果文件夹( 01~04

6.2 进入 R 语言基础( RStudio Server

在工具入口卡片中点击 "R 语言基础",浏览器将打开 RStudio Server 登录页:

8-2 平台工具入口卡片中的 "R 语言基础 " RStudio Server

6.3 登录 RStudio 并进入分析环境

使用平台账号登录 RStudio Server,进入熟悉的四窗口开发界面:

8-3 RStudio Server 登录页面

8-4 RStudio 主界面(脚本 / 控制台 / 环境 / 文件四窗口)

6.4 加载并运行下游脚本

在 RStudio 中打开 mRNA_QcAndQuant_2026-8-20.R,选中全部代码后点击 Run(或 Source),脚本自动完成参数设置、数据导入、差异分析、富集分析的全流程:

8-5 RStudio 中打开下游 R 脚本

6.5 查看运行结果

脚本运行结束后,RNA_seq 目录下自动生成 05.results 结果目录,包含差异分析表格、可视化图片、GO/KEGG 富集结果与 GSEA 结果:

8-6 R 脚本运行结束,生成 05.results 结果目录
❝ 💡 小白提示: RStudio Source 按钮会静默执行整个脚本; Run 按钮则逐段执行。第一次运行时建议用 Run 观察每一步输出,确认数据导入与分组推断无误后再整段 Source

七、参数对结果的影响与比较:阈值决定你看到什么

下游分析中,几个关键阈值直接决定了"哪些基因算显著"、"哪些通路算富集",进而影响整个结论。理解它们的含义,才能在严格与宽松之间做出科学选择。

7.1 差异表达分析阈值(决定 DEG 名单)

|---------------|---------|----------------------|----------------------|
| 参数 | 默认值 | 对结果的影响 | 调整建议 |
| PADJ_CUTOFF | 0.05 | 校正后 p 值阈值,控制假阳性 | 严格分析可设为 0.01 |
| LOG2FC_CUTOFF | 1 | 两倍变化(2^1=2 倍) | 探索性分析可放宽至 0.5(1.4 倍) |
| MIN_COUNT | 10 | 单样本最小 count | 低表达基因过滤,避免噪音 |
| MIN_SAMPLES | 2 | 至少 2 个样本满足 MIN_COUNT | 保证生物学重复间的可靠性 |

严格 vs 宽松的效果: 以本文数据为例,默认阈值(|log2FC|>1 且 padj<0.05)得到 18 个显著 DEGs(7 上调 + 11 下调);若把 PADJ_CUTOFF 收紧到 0.01,DEG 数量会进一步缩减,结论更保守、更可信;若把 LOG2FC_CUTOFF 放宽到 0.5,则会纳入更多表达变化较小的基因,适合筛选"趋势性"候选基因。

7.2 富集分析参数(决定富集条目)

|--------------|-----------------|---------------------|
| 参数 | 脚本内设置 | 影响 |
| pvalueCutoff | 0.05(默认)→ 1(兜底) | 控制富集显著性,兜底避免无输出 |
| qvalueCutoff | 0.2(默认)→ 1(兜底) | FDR 控制,放宽可展示更多趋势性条目 |
| minGSSize | 1 | 允许小基因集富集,适合本地数据库 |
| maxGSSize | 1000 | 排除过大的基因集,避免泛化通路 |

脚本在默认阈值下若无显著富集条目,会自动"放宽阈值兜底"(pvalueCutoff=1, qvalueCutoff=1)重新富集,避免输出为空------这样既保证结果的严谨优先,又保证"永远有结果可看"。

7.3 GSEA 参数(决定富集算法行为)

|---------------|--------------|---------------------------|
| 参数 | 设置 | 作用 |
| eps | 1e-10(fgsea) | 数值精度,保证稳定性 |
| pAdjustMethod | BH | Benjamini-Hochberg FDR 校正 |
| seed | TRUE | 保证结果可重复 |

参数选择的黄金法则:默认阈值适合大多数场景;审稿人要求更严格时收紧 PADJ_CUTOFF ;前期探索阶段可适当放宽;所有阈值修改只需改动脚本开头的参数区,一处修改、全流程生效。

八、逐句语法分析:拆解下游 R 脚本

下面按脚本的 6 大功能模块,逐段解读关键代码的语法、用法与生信意义。为便于对照,代码段后的讲解对应脚本实际行。

8.1 参数设置区:脚本的 " 仪表盘 "

脚本开头集中定义所有关键参数,便于用户按需调整:

代码 8-1 参数设置区

复制代码
# ========== 0. 参数设置 ==========

PADJ_CUTOFF <- 0.05 # 校正后 p 值阈值(差异显著)

LOG2FC_CUTOFF <- 1 # log2 倍数变化阈值(|log2FC|>1 即 2 倍)

MIN_COUNT <- 10 # 基因在单个样本中的最小 read 数

MIN_SAMPLES <- 2 # 至少 2 个样本满足 MIN_COUNT 才保留

WORK_DIR <- "/mnt/data/RNA_seq" # 工作目录(可按需修改)

**逐句讲解:**① `<-` 是 R 的赋值运算符,等价于 `=`,表示把右侧值存入左侧变量;② 所有阈值集中在此,方便统一管理;③ 注释 `#` 后的内容不参与运行,仅作说明。

8.2 依赖包检查与自动安装:开箱即用

代码 8-2 依赖包检查与自动安装

复制代码
# ========== 0.2 自动安装缺失包 ==========

install_pkg <- function(pkg, from = "CRAN") {

if (!requireNamespace(pkg, quietly = TRUE)) {

if (from == "Bioc") BiocManager::install(pkg, ask = FALSE, update = FALSE)

else install.packages(pkg, repos = "https://cloud.r-project.org")

}

}

install_pkg("DESeq2", "Bioc"); install_pkg("clusterProfiler", "Bioc")

install_pkg("EnhancedVolcano", "Bioc"); install_pkg("fgsea", "Bioc")

**逐句讲解:**① `function(pkg, from)` 定义函数,`requireNamespace(pkg, quietly=TRUE)` 检查包是否已安装;② 未安装时,Bioconductor 包用 `BiocManager::install()`,CRAN 包用 `install.packages()`;③ `::` 符号调用包内函数,避免命名冲突(如 `BiocManager::install`)。

8.3 数据导入:三种容错方案

代码 8-3 表达矩阵导入(三方案容错)

复制代码
# ========== 4. 导入表达矩阵 ==========

mat <- tryCatch(

read.table(file.path(proj_dir, "04.counts", "count_matrix.txt"),

header = TRUE, row.names = 1, sep = "\t", check.names = FALSE),

error = function(e) NULL)

if (is.null(mat)) {

# 方案二:重建表头(首列 Geneid,其余为样本名)

mat <- read.table(file, header = TRUE, row.names = 1, sep = "\t")

}

mat[is.na(mat)] <- 0 # 缺失值补 0

**逐句讲解:**① `tryCatch(..., error=function(e) NULL)` 是 R 的异常处理机制:正常执行返回结果,出错返回 NULL 而不中断;② `read.table` 读入制表符分隔文件:`header=TRUE` 首行当表头,`row.names=1` 第一列当行名(基因名),`check.names=FALSE` 防止样本名被自动改写成合法变量名;③ `file.path()` 跨平台拼接路径,`\t` 是制表符转义。

8.4 自动分组推断:样本名前缀即分组

代码 8-4 自动分组推断

复制代码
# ========== 5. 自动推断分组 ==========

sample_names <- colnames(mat)

get_group <- function(x) {

if (grepl("^Control|^Ctrl|^WT|^control", x)) return("Control")

if (grepl("^Treat|^Treat|^KO|^treat", x)) return("Treat")

return("Unknown")

}

group_info <- vapply(sample_names, get_group, character(1))

table(group_info) # 查看分组统计

**逐句讲解:**① `grepl(pattern, x)` 用正则表达式匹配,返回 TRUE/FALSE;`^` 表示"以...开头";`|` 表示"或"------因此 "Control"、"Ctrl"、"WT" 开头的样本都归为对照组;② `vapply(样本名, 函数, 返回值类型)` 对每个样本名执行分组函数,返回字符向量;③ 分组规则覆盖了最常见的命名习惯,遇到 "Unknown" 会触发警告,提示检查命名。

8.5 DESeq2 差异分析:下游核心

代码 8-5 DESeq2 差异分析核心

复制代码
# ========== 7. DESeq2 差异分析 ==========

suppressPackageStartupMessages(library(DESeq2))

dds <- DESeqDataSetFromMatrix(

countData = mat_filtered, # 过滤后的计数矩阵

colData = data.frame(condition = factor(group_info)),

design = ~ condition) # 实验设计公式

dds <- DESeq(dds) # 拟合负二项分布模型

res <- results(dds, contrast = c("condition", "Treat", "Control"))

res_df <- as.data.frame(res)

res_df$gene <- rownames(res_df)

sig <- res_df[!is.na(res_df$padj) &

res_df$padj < PADJ_CUTOFF &

abs(res_df$log2FoldChange) > LOG2FC_CUTOFF, ]

**逐句讲解:**① `DESeqDataSetFromMatrix()` 把计数矩阵包装成 DESeq2 的数据对象,需同时提供:计数矩阵、样本信息(colData)、实验设计公式(design);② `design = ~ condition` 用公式语法声明"只看分组这一因素",这也是差异分析的统计模型骨架;③ `DESeq()` 完成估计大小因子、离散度、拟合模型三步;④ `results(contrast=c("condition","Treat","Control"))` 提取"Treat 相对 Control"的差异结果,返回 baseMean/log2FoldChange/lfcSE/stat/pvalue/padj 六列;⑤ 最后用阈值筛选显著 DEGs,`!is.na(padj)` 排除无统计信息的基因。

8.6 本地 GO/KEGG 富集:无需联网的富集分析

代码 8-6 读取本地 GMT 并运行 GO 富集

复制代码
# ========== 9.1 本地 GO 富集 ==========

read_gmt <- function(gmt_file) {

lines <- readLines(gmt_file)

do.call(rbind, lapply(lines, function(ln) {

parts <- strsplit(ln, "\t")[[1]]

data.frame(term = parts[1], name = parts[2],

gene = parts[3:length(parts)], stringsAsFactors = FALSE)

}))

}

go_anno <- read_gmt(file.path(enrichDB, "hsa.gmt"))

enricher(enrich_ids, TERM2GENE = go_anno[, c("term","gene")],

TERM2NAME = go_anno[, c("term","name")],

pvalueCutoff = 0.05, qvalueCutoff = 0.2)

**逐句讲解:**① GMT 格式:每行一个基因集,第 1 列条目 ID、第 2 列描述、其后每列一个基因,制表符分隔;② `readLines()` 按行读取文件,`strsplit(x,"\\t")` 按制表符拆分;③ `enricher()` 是 clusterProfiler 提供的"自定义基因集"富集函数:只需传入显著基因列表(enrich_ids)和基因集注释(TERM2GENE/TERM2NAME),即可完成超几何检验;④ 无需联网------注释数据全部来自本地 enrichDB/hsa.gmt。

8.7 GSEA :全基因排序,捕捉微弱但协调的变化

代码 8-7 GSEA 基因集富集分析

复制代码
# ========== 9.3 GSEA 分析 ==========

geneList <- setNames(gsea_src$log2FoldChange, gsea_src$sym)

geneList <- sort(geneList, decreasing = TRUE) # 降序排序

gsea_obj <- clusterProfiler::GSEA(

geneList, eps = 1e-10, pvalueCutoff = 1,

pAdjustMethod = "BH",

TERM2GENE = TERM2GENE, TERM2NAME = TERM2NAME,

by = "fgsea", seed = TRUE, verbose = FALSE)

**逐句讲解:**① `setNames(值, 名称)` 给数值向量命名,`sort(decreasing=TRUE)` 按 log2FC 从高到低排序------这是 GSEA 的输入格式要求(排序基因列表);② 与 ORA 富集只取显著 DEGs 不同,GSEA 使用"全部基因",能在单个基因变化不显著时发现整条通路的协调变化;③ `by="fgsea"` 调用 fgsea 算法(更快、精度更高),`eps=1e-10` 控制数值精度,`seed=TRUE` 保证随机过程可重复。

8.8 结果输出: write 系列函数

代码 8-8 结果表格输出

复制代码
# ========== 输出差异结果 ==========

write.csv(sig_up, file.path(out_dir, "DESeq2_results", "DESeq2_up_regulated.csv"),

row.names = FALSE)

write.table(res_df, file.path(out_dir, "enrichment", "GO.enrichment.xls"),

sep = "\t", row.names = FALSE, quote = FALSE)

**逐句讲解:**① `write.csv` 输出逗号分隔 CSV(Excel 直接打开);② `write.table(sep="\t", quote=FALSE)` 输出制表符分隔的 XLS 格式,兼容老版本 gmt_enrich 工具链;③ 所有输出路径统一 `file.path(输出目录, 子目录, 文件名)`,保证目录结构清晰。

九、使用方法:四种方式运行下游脚本

9.1 方式一: RStudio 逐段运行(推荐新手)

在 R 语言基础(RStudio Server)中打开 mRNA_QcAndQuant_2026-8-20.R,用 Run 逐段执行、观察输出,或直接 Source 一键运行。适合学习与调试。

9.2 方式二:命令行 Rscript 批量运行

代码 9-1 命令行运行

复制代码
# 在平台 Terminal 中执行(R 语言基础环境)

cd /mnt/data/RNA_seq

Rscript mRNA_QcAndQuant_2026-8-20.R

# 如需自定义参数,可在脚本开头直接修改 PADJ_CUTOFF 等阈值

**语法讲解:**① `Rscript 脚本.R` 以"批处理"模式运行 R 脚本,全程无需人工干预,适合定时任务与批量项目;② 运行日志逐行打印在终端,可配合 `> log.txt 2>&1` 重定向保存。

十、在生信分析中的作用:每个模块解决什么问题

|-------------|------------------|----------------------|
| 功能模块 | 生信作用 | 生物学意义 |
| DESeq2 差异分析 | 识别处理效应显著的基因 | 找到候选 biomarker 或药物靶点 |
| 可视化(MA、火山图) | 直观展示差异基因分布 | 快速评估处理效果 |
| 热图、PCA | 样本聚类与离群检测 | 验证实验重复性、发现批次效应 |
| GO 富集 | 基因功能分类(BP/CC/MF) | 揭示差异基因参与的生物过程 |
| KEGG 富集 | 通路水平分析 | 定位信号通路、代谢通路变化 |
| GSEA | 基因集整体趋势检验 | 发现微弱但协调一致的变化 |

一句话总结:差异分析回答"谁变了",GO/KEGG 回答"变在哪些功能/通路上",GSEA 回答"哪些通路整体被激活或抑制"------三者层层递进,构成完整的功能解读链条。

十一、结果文件说明: 05.results 里有什么

脚本运行完成后,05.results 目录下输出全套结果。各文件含义如下:

11.1 DESeq2_results/ :差异分析核心表格

|------------------------------|-------------------------------------------------|
| 文件 | 内容 |
| DESeq2_all_results.csv | 全部基因的差异结果(459 个基因,含 baseMean/log2FC/padj 等 8 列) |
| DESeq2_significant_genes.csv | 显著差异基因(默认阈值下 18 个) |
| DESeq2_up_regulated.csv | 上调基因(7 个) |
| DESeq2_down_regulated.csv | 下调基因(11 个) |
| filtered_count_matrix.csv | 低表达过滤后的计数矩阵(DESeq2 实际输入) |
| normalized_counts.csv | DESeq2 标准化计数(用于热图/聚类等可视化) |

11.2 plots/ :五张关键可视化图

|--------------------------------|-------------------|
| 文件 | 说明 |
| Volcano_plot.pdf | 火山图:直观展示差异基因整体分布 |
| MA_plot.pdf | MA 图:表达均值 vs 变化幅度 |
| Heatmap_top50_DEGs.pdf | 前 50 个差异基因表达热图 |
| PCA_plot.pdf | 主成分分析图:样本分组聚类 |
| Sample_correlation_heatmap.pdf | 样本相关性热图:重复性验证 |

11.3 enrichment/ GO/KEGG 富集结果

|-------------------------------------------|-----------------------------------------|
| 文件 | 内容 |
| GO_enrichment.csv / GO.enrichment.xls | GO 富集全结果(96 条,BP/MF/CC 合并,含 ontology 列) |
| GO_enrichment_BP.csv | GO 生物学过程子集(71 条,兼容旧版脚本命名) |
| GO.barplot / GO.dotplot(pdf+png) | GO 富集柱状图 / 气泡图(双格式输出) |
| KEGG_enrichment.csv / KEGG.enrichment.xls | KEGG 通路富集结果(21 条) |
| KEGG.barplot / KEGG.dotplot(pdf+png) | KEGG 富集柱状图 / 气泡图 |
| KEGG.enrichment.html | 可交互富集表格(DT 组件,含 KEGG 通路超链接) |

11.4 gsea/ GSEA 结果

|----------------------------------|---------------------------------------|
| 文件 | 内容 |
| gsea_result.xls | GSEA 全结果(BP/CC/MF/KEGG 各 ontology 合并) |
| <Ontology>_<ID>.gsea.pdf/png | 显著基因集的 gseaplot2 富集图 |

十二、修改后的完整 R 代码展示( mRNA_QcAndQuant_2026-8-20.R

以下为修改后的完整下游分析脚本,可直接复制保存为 .R 文件运行。脚本按 0→10 编号组织模块,关键处附中文注释:

完整脚本 mRNA_QcAndQuant_2026-8-20.R

复制代码
# ============================================================================

# mRNA_QcAndQuant_2026-8-20.R

# 转录组下游分析:DESeq2 差异分析 + 可视化 + 本地 GO/KEGG 富集 + GSEA

# 数据来源:GEO GSE52778(airway,8 样本,演示用 4 样本)

# 修改要点:1) 支持本地 enrichDB GMT 无需联网富集

# 2) 三方案容错导入表达矩阵

# 3) 自动安装缺失依赖包

# ============================================================================


# ---------- 0. 参数设置 ----------

PADJ_CUTOFF <- 0.05 # 校正后 p 值阈值

LOG2FC_CUTOFF <- 1 # log2 倍数变化阈值

MIN_COUNT <- 10 # 基因最小 count

MIN_SAMPLES <- 2 # 最少满足 MIN_COUNT 的样本数

WORK_DIR <- "/mnt/data/RNA_seq" # 工作目录

PROJ_DIR <- WORK_DIR # 项目目录(可单独指定)


# ---------- 0.2 自动安装依赖包 ----------

install_pkg <- function(pkg, from = "CRAN") {

if (!requireNamespace(pkg, quietly = TRUE)) {

if (from == "Bioc") BiocManager::install(pkg, ask = FALSE, update = FALSE)

else install.packages(pkg, repos = "https://cloud.r-project.org")

}

}

if (!requireNamespace("BiocManager", quietly = TRUE))

install.packages("BiocManager", repos = "https://cloud.r-project.org")

install_pkg("DESeq2", "Bioc"); install_pkg("clusterProfiler", "Bioc")

install_pkg("EnhancedVolcano", "Bioc"); install_pkg("fgsea", "Bioc")

install_pkg("org.Hs.eg.db", "Bioc"); install_pkg("enrichplot", "Bioc")

install_pkg("ggplot2"); install_pkg("dplyr"); install_pkg("pheatmap")

install_pkg("DT"); install_pkg("htmlwidgets"); install_pkg("pbapply")


suppressPackageStartupMessages({

library(DESeq2); library(clusterProfiler); library(ggplot2)

library(dplyr); library(pheatmap); library(EnhancedVolcano)

library(pbapply); library(DT)

})


log_msg <- function(...) cat("[", format(Sys.time(), "%H:%M:%S"), "]", ..., "\n")

log_msg("mRNA 下游分析开始")


# ---------- 1. 目录检测与创建 ----------

dir.create(file.path(PROJ_DIR, "05.results"), showWarnings = FALSE, recursive = TRUE)

output_dir <- file.path(PROJ_DIR, "05.results")

for (sub in c("DESeq2_results", "plots", "enrichment", "gsea"))

dir.create(file.path(output_dir, sub), showWarnings = FALSE, recursive = TRUE)


# ---------- 2. 读取表达矩阵(三方案容错) ----------

count_file <- file.path(PROJ_DIR, "04.counts", "count_matrix.txt")

mat <- NULL

if (file.exists(count_file)) {

mat <- tryCatch(

read.table(count_file, header = TRUE, row.names = 1,

sep = "\t", check.names = FALSE),

error = function(e) NULL)

}

if (is.null(mat) && file.exists(count_file)) {

# 方案二:首行第一列可能缺失表头

tmp <- readLines(count_file, n = 1)

if (!grepl("^Geneid|^gene|^ENS", tmp)) {

header <- c("Geneid", paste0("Sample", 1:(length(strsplit(tmp, "\t")[[1]]) - 1)))

mat <- read.table(count_file, header = FALSE, sep = "\t",

col.names = header, check.names = FALSE)

rownames(mat) <- mat$Geneid; mat$Geneid <- NULL

} else {

mat <- read.table(count_file, header = TRUE, row.names = 1,

sep = "\t", check.names = FALSE)

}

}

if (is.null(mat)) stop("无法读取表达矩阵,请检查 04.counts/count_matrix.txt")

mat[is.na(mat)] <- 0

log_msg(paste("表达矩阵:", nrow(mat), "基因 x", ncol(mat), "样本"))


# ---------- 3. 自动分组推断 ----------

sample_names <- colnames(mat)

get_group <- function(x) {

if (grepl("^Control|^Ctrl|^WT|^control", x)) return("Control")

if (grepl("^Treat|^Treat|^KO|^treat", x)) return("Treat")

return("Unknown")

}

group_info <- vapply(sample_names, get_group, character(1))

if (any(group_info == "Unknown"))

warning("存在无法识别的样本分组,请检查样本命名!")

log_msg(paste("分组:", paste(names(table(group_info)), table(group_info), sep = "=", collapse = ", ")))


# ---------- 4. 低表达基因过滤 ----------

keep <- rowSums(mat >= MIN_COUNT) >= MIN_SAMPLES

mat_filtered <- mat[keep, , drop = FALSE]

log_msg(paste("低表达过滤后保留基因数:", nrow(mat_filtered)))


# ---------- 5. DESeq2 差异分析 ----------

condition <- factor(group_info[colnames(mat_filtered)], levels = c("Control", "Treat"))

dds <- DESeqDataSetFromMatrix(

countData = mat_filtered,

colData = data.frame(condition = condition),

design = ~ condition)

dds <- DESeq(dds)

res <- results(dds, contrast = c("condition", "Treat", "Control"))

res_df <- as.data.frame(res)

res_df$gene <- rownames(res_df)


# 显著差异基因

sig <- res_df[!is.na(res_df$padj) & res_df$padj < PADJ_CUTOFF &

abs(res_df$log2FoldChange) > LOG2FC_CUTOFF, , drop = FALSE]

up <- sig[sig$log2FoldChange > 0, , drop = FALSE]

down <- sig[sig$log2FoldChange < 0, , drop = FALSE]

log_msg(paste("显著 DEGs:", nrow(sig), "(上调", nrow(up), "+ 下调", nrow(down), ")"))


# 标准化矩阵

normalized <- as.data.frame(counts(dds, normalized = TRUE))


# ---------- 6. 输出差异结果 ----------

write.csv(res_df, file.path(output_dir, "DESeq2_results", "DESeq2_all_results.csv"),

row.names = FALSE)

write.csv(sig, file.path(output_dir, "DESeq2_results", "DESeq2_significant_genes.csv"),

row.names = FALSE)

write.csv(up, file.path(output_dir, "DESeq2_results", "DESeq2_up_regulated.csv"),

row.names = FALSE)

write.csv(down, file.path(output_dir, "DESeq2_results", "DESeq2_down_regulated.csv"),

row.names = FALSE)

write.csv(normalized, file.path(output_dir, "DESeq2_results", "normalized_counts.csv"))

write.csv(mat_filtered, file.path(output_dir, "DESeq2_results", "filtered_count_matrix.csv"))


# ---------- 7. 可视化 ----------

if (nrow(sig) > 0) {

# 火山图

p <- EnhancedVolcano(res_df, lab = res_df$gene, x = "log2FoldChange",

y = "padj", pCutoff = PADJ_CUTOFF,

FCcutoff = LOG2FC_CUTOFF, title = "Volcano Plot")

ggsave(file.path(output_dir, "plots", "Volcano_plot.pdf"), p, width = 8, height = 6)

# MA 图

pdf(file.path(output_dir, "plots", "MA_plot.pdf"), width = 8, height = 6)

plotMA(res, main = "MA Plot"); dev.off()

# 热图(top50)

sig50 <- rownames(sig)[seq_len(min(50, nrow(sig)))]

if (length(sig50) > 1) {

pheatmap(normalized[sig50, ], scale = "row", show_rownames = FALSE,

filename = file.path(output_dir, "plots", "Heatmap_top50_DEGs.pdf"))

}

# PCA

vsd <- vst(dds, blind = TRUE)

pca <- plotPCA(vsd, intgroup = "condition") + theme_bw()

ggsave(file.path(output_dir, "plots", "PCA_plot.pdf"), pca, width = 6, height = 5)

# 样本相关性热图

pheatmap(cor(normalized), filename = file.path(output_dir, "plots",

"Sample_correlation_heatmap.pdf"))

}


# ---------- 8. 读取本地 GMT(enrichDB) ----------

read_gmt <- function(gmt_file) {

lines <- readLines(gmt_file)

do.call(rbind, lapply(lines, function(ln) {

parts <- strsplit(ln, "\t")[[1]]

data.frame(term = parts[1], name = parts[2],

gene = parts[3:length(parts)], stringsAsFactors = FALSE)

}))

}

enrichDB <- file.path(PROJ_DIR, "enrichDB")

go_anno <- read_gmt(file.path(enrichDB, "hsa.gmt"))

kegg_anno <- go_anno[grepl("^hsa", go_anno$term), , drop = FALSE]


# ---------- 9. GO 富集(本地数据库) ----------

enrich_ids <- sig$gene

run_local_enrich <- function(anno, ids, pc, qc) {

enricher(ids, TERM2GENE = unique(anno[, c("term", "gene")]),

TERM2NAME = unique(anno[, c("term", "name")]),

pvalueCutoff = pc, qvalueCutoff = qc)

}

ego <- tryCatch(run_local_enrich(go_anno, enrich_ids, 0.05, 0.2),

error = function(e) NULL)

ego_df <- as.data.frame(ego)

if (is.null(ego_df) || nrow(ego_df) == 0) { # 兜底放宽阈值

log_msg("默认阈值无显著条目,放宽阈值重新富集(仅展示)")

ego <- run_local_enrich(go_anno, enrich_ids, 1, 1)

ego_df <- as.data.frame(ego)

}

if (!is.null(ego_df) && nrow(ego_df) > 0) {

ego_df <- ego_df[order(ego_df$p.adjust), , drop = FALSE]

write.csv(ego_df, file.path(output_dir, "enrichment", "GO_enrichment.csv"),

row.names = FALSE)

pdf(file.path(output_dir, "enrichment", "GO_barplot.pdf"), width = 9, height = 8)

print(barplot(ego, showCategory = 20)); dev.off()

pdf(file.path(output_dir, "enrichment", "GO_dotplot.pdf"), width = 9, height = 8)

print(dotplot(ego, showCategory = 20)); dev.off()

write.table(ego_df, file.path(output_dir, "enrichment", "GO.enrichment.xls"),

sep = "\t", row.names = FALSE, quote = FALSE)

}


# ---------- 10. KEGG 富集(本地数据库) ----------

kk <- tryCatch(run_local_enrich(kegg_anno, enrich_ids, 0.05, 0.2),

error = function(e) NULL)

kk_df <- as.data.frame(kk)

if (is.null(kk_df) || nrow(kk_df) == 0) {

kk <- run_local_enrich(kegg_anno, enrich_ids, 1, 1)

kk_df <- as.data.frame(kk)

}

if (!is.null(kk_df) && nrow(kk_df) > 0) {

kk_df <- kk_df[order(kk_df$p.adjust), , drop = FALSE]

write.csv(kk_df, file.path(output_dir, "enrichment", "KEGG_enrichment.csv"),

row.names = FALSE)

pdf(file.path(output_dir, "enrichment", "KEGG_barplot.pdf"), width = 9, height = 8)

print(barplot(kk, showCategory = 20)); dev.off()

pdf(file.path(output_dir, "enrichment", "KEGG_dotplot.pdf"), width = 9, height = 8)

print(dotplot(kk, showCategory = 20)); dev.off()

}


# ---------- 11. GSEA(全基因排序) ----------

gsea_src <- res_df[!is.na(res_df$log2FoldChange) & is.finite(res_df$log2FoldChange), , drop = FALSE]

if (nrow(gsea_src) > 0) {

gsea_src$sym <- gsea_src$gene

gsea_src <- gsea_src[!duplicated(gsea_src$sym) & nzchar(gsea_src$sym), , drop = FALSE]

geneList <- setNames(gsea_src$log2FoldChange, gsea_src$sym)

geneList <- sort(geneList[is.finite(geneList)], decreasing = TRUE)

gsea_obj <- tryCatch(

clusterProfiler::GSEA(geneList, eps = 1e-10, pvalueCutoff = 1,

pAdjustMethod = "BH",

TERM2GENE = unique(go_anno[, c("term","gene")]),

TERM2NAME = unique(go_anno[, c("term","name")]),

by = "fgsea", seed = TRUE, verbose = FALSE),

error = function(e) NULL)

if (!is.null(gsea_obj) && nrow(as.data.frame(gsea_obj)) > 0) {

write.table(as.data.frame(gsea_obj),

file.path(output_dir, "gsea", "gsea_result.xls"),

sep = "\t", row.names = FALSE, quote = FALSE)

}

}


# ---------- 12. 完成总结 ----------

log_msg("========== 转录组下游分析全部完成!==========")

log_msg(paste("结果目录:", output_dir))

log_msg("📁 05.results/ ├─ DESeq2_results/ ├─ plots/ ├─ enrichment/ └─ gsea/")

十三、总结

mRNA_QcAndQuant_2026-8-20.R 是一个经过实战检验的转录组下游分析脚本。它以"DESeq2 差异分析 → 可视化 → 本地 GO/KEGG 富集 → GSEA"的标准流程,把 count_matrix.txt 表达矩阵转化为生物学意义明确的富集结果:差异分析给出候选基因名单,GO/KEGG 揭示其功能与通路归属,GSEA 进一步捕捉微弱但协调一致的通路变化。

脚本设计上处处体现"稳健性优先":三方案容错导入表达矩阵、自动安装依赖包、默认阈值无结果时自动兜底、gmt_enrich 风格与新版文件双格式输出......无论你是生信新手还是资深分析师,都可以基于此脚本快速搭建自己的下游分析流程。

相关推荐
JavaPub-rodert13 分钟前
一个后台系统如何同时支持 MySQL、PostgreSQL、SQLite、SQL Server?从 ShiyuAdmin 的 GORM 多数据库适配说起
数据库·mysql·postgresql
2603_9664372618 分钟前
拷贝中断文件丢失,电脑资料抢救实操方案
数据库·电脑
harmony&26 分钟前
MySQL 数据库从入门到实战:原理、安装与 SQL 详解
数据库·sql·mysql
艺杯羹35 分钟前
全栈信创落地实录:基于银河麒麟V10与达梦数据库DM8的SpringBoot工业级适配指南
java·数据库·spring boot·后端·spring
坚定信念,勇往无前6 小时前
mongodb的日志分割
数据库·mongodb
oradh9 小时前
Oracle TX 锁 Mode 4(Share)问题排查总结
数据库·oracle·tx 锁 mode 4·oracle tx 锁
yi.Ist10 小时前
数据定义语言-DDL操作
数据库·学习·mysql·oracle·大海豚
2601_9632827711 小时前
工程项目、政企采购为什么优先选择对讲机批量采购?
数据库
Doraemomo11 小时前
SQLite数据库
数据库·sqlite