跳转至

R语言可视化(R Visualization for Bioinformatics)


一句话说明

R是生信领域最常用的统计与可视化语言,ggplot2生态是制作发表级图表的标准工具。掌握R可视化是生信分析工程师从"出结果"到"出图发表"的核心能力。


2. 核心知识点

2.1 ggplot2基础——图形语法(Grammar of Graphics)

ggplot2基于Leland Wilkinson的图形语法理论,将图表拆分为独立组件,通过+号逐层叠加:

组件 英文 函数 说明
数据 Data ggplot(data) 输入的data.frame
映射 Aesthetics aes(x, y, color, fill, size) 变量→视觉属性
几何对象 Geometry geom_*() 图表类型(点/线/柱等)
统计变换 Statistics stat_*() 均值、计数、回归线
标度 Scales scale_*() 颜色、坐标轴刻度
分面 Facets facet_wrap() / facet_grid() 按变量拆分子图
坐标系 Coordinate coord_*() 笛卡尔/极坐标
主题 Theme theme_*() 字体、背景、网格线

核心公式

ggplot(data, aes(x, y)) + geom_*() + scale_*() + theme_*()

2.2 常用geom几何对象

geom 图表类型 典型用途
geom_bar() / geom_col() 柱状图 物种丰度、功能注释计数
geom_boxplot() 箱线图 Alpha多样性比较
geom_violin() 小提琴图 数据分布展示
geom_point() 散点图 PCoA/PCA/NMDS
geom_tile() 热图块 简单热图
geom_line() 折线图 稀释曲线
geom_errorbar() 误差线 均值+标准差
geom_text() / geom_label() 文字标注 图上标数值
geom_density() 密度图 reads长度分布
geom_hline() / geom_vline() 参考线 阈值线(如p=0.05)

2.3 生信常用图表

图表类型 ggplot2实现 用途 典型包
物种丰度堆叠柱状图 geom_bar(stat="identity", position="stack") 展示各样本物种组成 ggplot2
Alpha多样性箱线图 geom_boxplot() + stat_compare_means() 组间多样性差异比较 ggplot2 + ggpubr
PCoA散点图 geom_point() + stat_ellipse() Beta多样性可视化 ggplot2 + vegan
热图 独立函数(非ggplot2) 物种/基因表达聚类 pheatmap / ComplexHeatmap
韦恩图 独立函数 组间共有/特有物种 VennDiagram / UpSetR
LEfSe柱状图 geom_bar(stat="identity") + coord_flip() 差异物种展示 ggplot2
弦图/网络图 chordDiagram() 物种-功能关联 circlize
火山图 geom_point() 着色 差异分析结果 ggplot2 / EnhancedVolcano
稀释曲线 geom_line() 测序深度是否足够 ggplot2 + vegan
桑基图 独立函数 物种分类层级 ggalluvial / networkD3

2.4 差异分析R包

DESeq2(最常用)

要素 说明
输入 原始count矩阵(raw counts,未归一化)
归一化 中位数比率法(Median of Ratios)
统计模型 负二项分布(Negative Binomial)
适用场景 样本量较小(<20)、RNA-seq、宏基因组差异物种
核心函数 DESeqDataSetFromMatrix()DESeq()results()
结果 log2FoldChange, padj(BH校正后的p值)

edgeR

要素 说明
输入 原始count矩阵
归一化 TMM(Trimmed Mean of M-values)
统计模型 负二项分布
适用场景 样本量较大、比DESeq2更灵活
核心函数 DGEList()calcNormFactors()estimateDisp()exactTest()

limma-voom

要素 说明
输入 count矩阵(经voom转换)
统计模型 线性模型 + 经验贝叶斯
适用场景 样本量大(>30)、复杂实验设计

ALDEx2(组学专用)

要素 说明
输入 count矩阵
特点 基于CLR变换(Centered Log-Ratio),专为组成型数据设计
适用场景 微生物组数据(丰度是相对比例,天然组成型)

DESeq2 vs edgeR 对比

对比项 DESeq2 edgeR
归一化 Median of Ratios TMM
小样本 更稳健 需注意
速度 稍慢 更快
使用难度 更简洁 需更多手动步骤
引用量 更高 略低
推荐场景 初学者首选 高级用户/复杂设计

3. 实战代码

3.1 物种丰度Top10堆叠柱状图

# ============================================================
# 物种丰度 Top10 堆叠柱状图
# Species Abundance Top10 Stacked Bar Plot
# ============================================================

# --- 加载包 ---
library(ggplot2)    # 绘图核心包
library(dplyr)      # 数据处理
library(tidyr)      # 数据整理(长宽转换)
library(RColorBrewer) # 配色方案

# --- 读取数据 ---
# 假设文件是 Kraken2/Bracken 输出的物种丰度表
# 行 = 物种,列 = 样本,值 = 相对丰度(百分比或比例)
abundance <- read.csv("species_abundance.csv", row.names = 1, check.names = FALSE)
# check.names=FALSE 防止R自动修改列名中的特殊字符

# --- 查看数据结构 ---
head(abundance)
# 期望格式:
#              Sample1  Sample2  Sample3
# E.coli        0.42    0.57     0.35
# B.fragilis    0.15    0.08     0.20
# ...

# --- 筛选Top10物种 ---
# 按所有样本的平均丰度排序,取前10
top10_species <- abundance %>%
  mutate(species = rownames(abundance)) %>%           # 行名转列
  mutate(mean_abundance = rowMeans(select(., -species))) %>%  # 计算平均丰度
  arrange(desc(mean_abundance)) %>%                   # 降序排列
  head(10) %>%                                        # 取前10
  pull(species)                                       # 提取物种名

# --- 将非Top10合并为"Others" ---
abundance_top10 <- abundance
abundance_top10$species <- rownames(abundance_top10)
abundance_top10 <- abundance_top10 %>%
  mutate(species = ifelse(species %in% top10_species, species, "Others")) %>%
  group_by(species) %>%
  summarise(across(everything(), sum)) %>%            # 合并"Others"行
  ungroup()

# --- 转为长格式(ggplot2 要求) ---
abundance_long <- abundance_top10 %>%
  pivot_longer(cols = -species, names_to = "sample", values_to = "abundance")

# --- 设置物种顺序(Top10 + Others) ---
species_order <- c(top10_species, "Others")
abundance_long$species <- factor(abundance_long$species, levels = rev(species_order))
# rev()反转顺序,让最丰富的物种在柱子顶部

# --- 配色 ---
n_colors <- length(species_order)
my_colors <- c(brewer.pal(min(n_colors - 1, 10), "Set3"), "grey70")
# "Others"用灰色

# --- 画图 ---
p <- ggplot(abundance_long, aes(x = sample, y = abundance, fill = species)) +
  geom_bar(stat = "identity", position = "stack", width = 0.7) +  # 堆叠柱状图
  scale_fill_manual(values = my_colors) +                          # 自定义颜色
  labs(
    title = "Species Abundance (Top 10)",
    x = "Sample",
    y = "Relative Abundance",
    fill = "Species"
  ) +
  theme_classic() +                        # 简洁主题
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1, size = 10),  # X轴标签倾斜45度
    legend.position = "right",
    plot.title = element_text(hjust = 0.5, face = "bold")          # 标题居中加粗
  )

# --- 保存图片 ---
ggsave("species_top10_barplot.pdf", p, width = 10, height = 6, dpi = 300)
ggsave("species_top10_barplot.png", p, width = 10, height = 6, dpi = 300)
cat("图片已保存: species_top10_barplot.pdf / .png\n")

3.2 Alpha多样性箱线图 + Wilcoxon检验

# ============================================================
# Alpha多样性(Shannon)箱线图 + 统计检验
# Alpha Diversity (Shannon) Boxplot with Wilcoxon Test
# ============================================================

library(ggplot2)
library(ggpubr)     # stat_compare_means() 统计检验标注
library(vegan)      # diversity() 计算多样性指数

# --- 读取数据 ---
# OTU/ASV 丰度表:行=物种,列=样本
otu_table <- read.csv("otu_table.csv", row.names = 1, check.names = FALSE)

# 分组信息:sample_id + group
metadata <- read.csv("metadata.csv")
# 期望格式:
#   sample_id   group
#   Sample1     Disease
#   Sample2     Healthy

# --- 计算Shannon多样性指数 ---
# vegan::diversity() 要求输入矩阵:行=样本,列=物种(注意需要转置)
shannon <- diversity(t(otu_table), index = "shannon")
# t() 转置矩阵,因为我们的数据是物种×样本格式

# --- 合并分组信息 ---
div_df <- data.frame(
  sample_id = names(shannon),
  shannon = shannon,
  stringsAsFactors = FALSE
)
div_df <- merge(div_df, metadata, by = "sample_id")

# --- 画箱线图 ---
p <- ggplot(div_df, aes(x = group, y = shannon, fill = group)) +
  geom_boxplot(
    alpha = 0.7,           # 透明度
    outlier.shape = 16,    # 异常值形状
    width = 0.5
  ) +
  geom_jitter(             # 叠加散点,显示每个样本
    width = 0.15,
    size = 2,
    alpha = 0.6
  ) +
  stat_compare_means(      # Wilcoxon秩和检验(非参数检验)
    method = "wilcox.test",
    label = "p.signif",    # 显示显著性星号(* ** *** ns)
    label.x = 1.5,
    label.y = max(div_df$shannon) * 1.1
  ) +
  scale_fill_manual(values = c("Disease" = "#E74C3C", "Healthy" = "#3498DB")) +
  labs(
    title = "Alpha Diversity (Shannon Index)",
    x = "Group",
    y = "Shannon Index",
    fill = "Group"
  ) +
  theme_classic() +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
    legend.position = "none"   # 箱线图已有颜色区分,不需要图例
  )

# --- 保存 ---
ggsave("alpha_shannon_boxplot.pdf", p, width = 6, height = 5, dpi = 300)
cat("图片已保存: alpha_shannon_boxplot.pdf\n")

# --- 输出统计结果 ---
wilcox_result <- wilcox.test(shannon ~ group, data = div_df)
cat("Wilcoxon test p-value:", wilcox_result$p.value, "\n")

3.3 Bray-Curtis PCoA散点图

# ============================================================
# PCoA散点图(Bray-Curtis距离)
# PCoA Plot Based on Bray-Curtis Dissimilarity
# ============================================================

library(ggplot2)
library(vegan)      # vegdist() 距离矩阵 + cmdscale/ape::pcoa
library(ape)        # pcoa() 主坐标分析

# --- 读取数据 ---
otu_table <- read.csv("otu_table.csv", row.names = 1, check.names = FALSE)
metadata <- read.csv("metadata.csv")

# --- 计算 Bray-Curtis 距离矩阵 ---
# vegdist() 输入:行=样本,列=物种
bray_dist <- vegdist(t(otu_table), method = "bray")
# 返回一个距离矩阵对象(dist class)

# --- PCoA 分析 ---
pcoa_result <- pcoa(bray_dist)

# --- 提取坐标和解释度 ---
pcoa_coords <- as.data.frame(pcoa_result$vectors[, 1:2])  # 取前2个轴
colnames(pcoa_coords) <- c("PCoA1", "PCoA2")
pcoa_coords$sample_id <- rownames(pcoa_coords)

# 计算各轴解释变异的百分比
# 用 Relative_eig 更准确(已处理负特征值情况)
var_explained <- round(pcoa_result$values$Relative_eig * 100, 1)
# var_explained[1] 是第一轴解释百分比
# var_explained[2] 是第二轴解释百分比

# --- 合并分组信息 ---
pcoa_df <- merge(pcoa_coords, metadata, by = "sample_id")

# --- 画PCoA图 ---
p <- ggplot(pcoa_df, aes(x = PCoA1, y = PCoA2, color = group)) +
  geom_point(size = 4, alpha = 0.8) +               # 散点
  stat_ellipse(                                       # 95%置信椭圆
    level = 0.95,
    type = "t",                                       # t分布椭圆
    linetype = "dashed",
    linewidth = 0.8
  ) +
  scale_color_manual(values = c("Disease" = "#E74C3C", "Healthy" = "#3498DB")) +
  labs(
    title = "PCoA (Bray-Curtis Dissimilarity)",
    x = paste0("PCoA1 (", var_explained[1], "%)"),    # 轴标签加解释度
    y = paste0("PCoA2 (", var_explained[2], "%)"),
    color = "Group"
  ) +
  theme_classic() +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold"),
    legend.position = "right"
  )

# --- PERMANOVA 统计检验(与PCoA配套) ---
# 检验组间差异是否显著
permanova <- adonis2(bray_dist ~ group, data = pcoa_df, permutations = 999)
cat("PERMANOVA p-value:", permanova$`Pr(>F)`[1], "\n")
cat("PERMANOVA R2:", permanova$R2[1], "\n")
# R2 表示分组因素解释了多少变异

# 在图上添加PERMANOVA结果
p <- p + annotate(
  "text",
  x = min(pcoa_df$PCoA1),
  y = max(pcoa_df$PCoA2),
  label = paste0("PERMANOVA\nR2=", round(permanova$R2[1], 3),
                 "\np=", permanova$`Pr(>F)`[1]),
  hjust = 0, vjust = 1, size = 3.5
)

# --- 保存 ---
ggsave("pcoa_braycurtis.pdf", p, width = 8, height = 6, dpi = 300)
cat("图片已保存: pcoa_braycurtis.pdf\n")

3.4 热图(pheatmap)

# ============================================================
# 物种丰度热图
# Species Abundance Heatmap with pheatmap
# ============================================================

library(pheatmap)   # 热图专用包(自带聚类)
library(dplyr)

# --- 读取数据 ---
abundance <- read.csv("species_abundance.csv", row.names = 1, check.names = FALSE)
metadata <- read.csv("metadata.csv")

# --- 筛选 Top20 物种 ---
top20 <- names(sort(rowMeans(abundance), decreasing = TRUE))[1:20]
mat <- as.matrix(abundance[top20, ])

# --- Z-score 标准化(按行,即按物种) ---
# Z-score = (x - mean) / sd
# 目的:消除不同物种之间丰度量级差异,便于比较模式
mat_scaled <- t(scale(t(mat)))
# 内层 t(mat) 转置 → scale() 按列标准化 → 外层 t() 转回来
# 效果:每个物种(行)均值=0,标准差=1

# --- 列注释(样本分组) ---
annotation_col <- data.frame(
  Group = metadata$group,
  row.names = metadata$sample_id
)

# --- 自定义注释颜色 ---
ann_colors <- list(
  Group = c("Disease" = "#E74C3C", "Healthy" = "#3498DB")
)

# --- 画热图 ---
pdf("heatmap_top20.pdf", width = 10, height = 8)
pheatmap(
  mat_scaled,
  color = colorRampPalette(c("#2166AC", "white", "#B2182B"))(100),  # 蓝-白-红
  clustering_method = "ward.D2",       # 聚类方法:Ward最小方差法
  clustering_distance_rows = "euclidean",  # 行聚类距离
  clustering_distance_cols = "euclidean",  # 列聚类距离
  annotation_col = annotation_col,     # 列注释(分组信息)
  annotation_colors = ann_colors,      # 注释颜色
  show_colnames = TRUE,                # 显示列名(样本名)
  show_rownames = TRUE,                # 显示行名(物种名)
  fontsize_row = 8,                    # 行标签字号
  fontsize_col = 9,                    # 列标签字号
  main = "Top 20 Species Abundance (Z-score)"  # 标题
)
dev.off()
cat("图片已保存: heatmap_top20.pdf\n")

热图聚类方法选择

聚类方法 R参数 特点 推荐场景
Ward最小方差 "ward.D2" 倾向生成大小均匀的簇 最常用,默认推荐
完全连接 "complete" 保守,簇间距离大 想找明显分群
平均连接 "average" 折中 一般情况
单连接 "single" 倾向链状聚类 少用

3.5 DESeq2差异分析基本流程

# ============================================================
# DESeq2 差异分析基本流程
# DESeq2 Differential Analysis Pipeline
# ============================================================

library(DESeq2)     # 差异分析核心包
library(ggplot2)
library(dplyr)

# --- 读取数据 ---
# ⚠️ DESeq2 输入必须是原始 count(整数),不能是 TPM/FPKM/归一化后的值
count_matrix <- read.csv("raw_counts.csv", row.names = 1, check.names = FALSE)
count_matrix <- as.matrix(count_matrix)  # 转为矩阵

# 确保是整数
count_matrix <- round(count_matrix)  # 如果有小数,四舍五入

# 分组信息
col_data <- read.csv("metadata.csv", row.names = 1)
# 期望格式(行名=样本名,必须与count_matrix列名一致):
#             group
# Sample1     Disease
# Sample2     Healthy

# --- 确保样本顺序一致 ---
count_matrix <- count_matrix[, rownames(col_data)]

# --- 创建 DESeq2 数据对象 ---
dds <- DESeqDataSetFromMatrix(
  countData = count_matrix,    # 原始count矩阵
  colData = col_data,          # 样本分组信息
  design = ~ group             # 实验设计公式:按group分组比较
)

# --- 预过滤(去除低表达基因/物种) ---
# 至少在某些样本中有一定表达量,减少多重检验负担
keep <- rowSums(counts(dds)) >= 10
dds <- dds[keep, ]

# --- 运行 DESeq2 ---
dds <- DESeq(dds)
# 内部自动完成:
# 1. 归一化(Median of Ratios)
# 2. 离散度估计(dispersion estimation)
# 3. 负二项分布检验(Wald test)

# --- 提取结果 ---
res <- results(dds, contrast = c("group", "Disease", "Healthy"))
# contrast: c(分组列名, 实验组, 对照组)
# log2FoldChange > 0 表示 Disease 组上调
# log2FoldChange < 0 表示 Disease 组下调

# --- 查看结果摘要 ---
summary(res)

# --- 添加显著性标记 ---
res_df <- as.data.frame(res) %>%
  mutate(
    gene = rownames(res),
    significance = case_when(
      padj < 0.05 & log2FoldChange > 1  ~ "Up",       # 上调:padj<0.05 且 |log2FC|>1
      padj < 0.05 & log2FoldChange < -1 ~ "Down",     # 下调
      TRUE ~ "NS"                                       # 不显著(Not Significant)
    )
  )

# --- 火山图(Volcano Plot) ---
p <- ggplot(res_df, aes(x = log2FoldChange, y = -log10(padj), color = significance)) +
  geom_point(alpha = 0.6, size = 1.5) +
  scale_color_manual(values = c("Up" = "#E74C3C", "Down" = "#3498DB", "NS" = "grey60")) +
  geom_hline(yintercept = -log10(0.05), linetype = "dashed", color = "grey40") +  # p=0.05线
  geom_vline(xintercept = c(-1, 1), linetype = "dashed", color = "grey40") +       # |log2FC|=1线
  labs(
    title = "Volcano Plot (DESeq2)",
    x = "log2 Fold Change (Disease / Healthy)",
    y = "-log10(adjusted p-value)",
    color = "Significance"
  ) +
  theme_classic() +
  theme(plot.title = element_text(hjust = 0.5, face = "bold"))

# --- 保存火山图 ---
ggsave("volcano_plot.pdf", p, width = 8, height = 6, dpi = 300)

# --- 导出差异结果 ---
write.csv(res_df, "DESeq2_results.csv", row.names = FALSE)
cat("差异分析完成,结果已保存: DESeq2_results.csv + volcano_plot.pdf\n")

# --- 输出显著基因/物种数量 ---
cat("上调:", sum(res_df$significance == "Up"), "\n")
cat("下调:", sum(res_df$significance == "Down"), "\n")
cat("不显著:", sum(res_df$significance == "NS"), "\n")

4. 面试常问点

★ ggplot2的图形语法是什么?

参考答案:ggplot2基于图形语法(Grammar of Graphics)理论,把一张图拆分成数据(data)映射(aesthetics)几何对象(geom)标度(scales)分面(facets)主题(theme)等独立组件,通过+号逐层叠加。好处是灵活、可组合,比如同一个数据可以快速切换柱状图和箱线图,只要换一个geom_*()就行。

💡 加分回答:在百迈客实习时,我用ggplot2画过物种丰度堆叠柱状图和Alpha多样性箱线图,图形语法的分层设计让我可以快速调配色、分面、加统计标注。

★ 你常用R画什么图?

参考答案:在实习和个人项目中,我最常画的是: 1. 物种丰度堆叠柱状图 —— 展示各样本的物种组成 2. Alpha多样性箱线图 —— Shannon/Chao1 + Wilcoxon检验比较组间差异 3. PCoA散点图 —— 基于Bray-Curtis距离展示Beta多样性 4. 热图 —— 用pheatmap做Top物种的聚类热图 5. LEfSe柱状图 —— 展示差异物种的LDA值

这些图在宏基因组和16S分析中几乎每个项目都会用到。

★ DESeq2和edgeR的区别?

参考答案:两者都基于负二项分布检验count数据的差异,核心区别是: 1. 归一化方法不同:DESeq2用中位数比率法(Median of Ratios),edgeR用TMM 2. 小样本表现:DESeq2对小样本(<20)更稳健,因为它的离散度估计(shrinkage)更保守 3. 使用复杂度:DESeq2一步到位(DESeq()封装了所有步骤),edgeR需要手动分步执行 4. 选择建议:初学者或小样本推荐DESeq2;复杂实验设计或大样本可以用edgeR

⚠️ 关键点:两者的输入都必须是原始count(raw counts),不能用归一化后的值(如TPM、FPKM)。

★ 热图的聚类方法有哪些?

参考答案:pheatmap中常用的聚类方法有: - Ward最小方差法(ward.D2) —— 最常用,倾向生成大小均匀的簇 - 完全连接法(complete) —— 用簇间最大距离,比较保守 - 平均连接法(average/UPGMA) —— 用簇间平均距离,比较折中

距离度量通常用欧氏距离(euclidean)相关性距离(1-cor)

⚠️ 关键点:做热图之前一般要对数据做Z-score标准化(按行/按基因),否则高丰度物种会主导颜色,看不到低丰度物种的变化模式。

★ PCoA图怎么画?用什么包?

参考答案:PCoA的R语言流程是: 1. 用vegan::vegdist()计算Bray-Curtis距离矩阵 2. 用ape::pcoa()做主坐标分析,得到坐标和特征值 3. 用ggplot2::geom_point()画散点图,stat_ellipse()加置信椭圆 4. 轴标签要标注解释变异百分比(如"PCoA1 (35.2%)") 5. 配套做PERMANOVAvegan::adonis2())检验组间差异是否显著

💡 加分回答:在我的肠道宏基因组项目中,PCoA图显示Disease组和Healthy组有明显分离趋势,Bray-Curtis距离为0.20,PERMANOVA检验显著,说明两组菌群组成存在差异。


5. 易错/易混淆点

⚠️ ggplot2的+%>%不要混用

# 正确:ggplot2 用 + 连接图层
ggplot(data, aes(x, y)) +
  geom_point() +
  theme_classic()

# 错误:不能用管道符连接 ggplot 图层
ggplot(data, aes(x, y)) %>%    # 这里会报错!
  geom_point()

# 注意:数据处理可以用 %>%,但到 ggplot() 之后就要换成 +
data %>%
  filter(group == "Disease") %>%
  ggplot(aes(x, y)) +          # 从这里开始用 +
  geom_point()

⚠️ aes()里面和外面设置颜色的区别

# aes() 里面:颜色映射到变量(自动生成图例)
geom_point(aes(color = group))     # 按group分组着色

# aes() 外面:所有点设为同一颜色(不生成图例)
geom_point(color = "red")          # 所有点都是红色

# 常见错误:在 aes() 里写固定颜色
geom_point(aes(color = "red"))     # 错!R会把"red"当作一个常量变量

⚠️ 热图数据需要标准化(Z-score)

# 错误:直接用原始丰度画热图
pheatmap(raw_abundance)  # 高丰度物种主导颜色,信息丢失

# 正确:先做 Z-score 标准化
mat_scaled <- t(scale(t(raw_abundance)))  # 按行(物种)标准化
pheatmap(mat_scaled)                       # 展示相对变化模式

⚠️ DESeq2输入必须是raw count

# 错误:输入 TPM/FPKM/归一化后的值
dds <- DESeqDataSetFromMatrix(countData = tpm_matrix, ...)  # 结果不可靠!

# 正确:输入原始 count(整数)
dds <- DESeqDataSetFromMatrix(countData = raw_count_matrix, ...)

# 如果数据有小数(如Bracken输出),需要四舍五入
count_matrix <- round(count_matrix)

⚠️ padj vs pvalue

# 差异分析结果中有两个p值:
# pvalue  - 未校正的原始p值
# padj    - 经过多重检验校正(BH/FDR)后的p值

# 正确:用 padj 筛选差异基因
sig_genes <- res_df %>% filter(padj < 0.05)

# 错误:用 pvalue 筛选(假阳性太高)
sig_genes <- res_df %>% filter(pvalue < 0.05)  # 不推荐!

⚠️ pheatmap vs ComplexHeatmap

特性 pheatmap ComplexHeatmap
上手难度 简单,一个函数搞定 复杂,灵活度高
多热图拼接 不支持 支持
注释条 简单注释 复杂多层注释
推荐场景 日常使用、快速出图 发表级复杂热图
面试推荐 先说pheatmap 补充提ComplexHeatmap

6. 快速复习卡片

问题 一句话答案
ggplot2核心公式 ggplot(data, aes()) + geom_*() + scale_*() + theme_*()
图形语法几大组件 数据、映射、几何对象、标度、分面、坐标系、主题
常用热图包 pheatmap(简单)、ComplexHeatmap(复杂)
DESeq2输入 原始count(整数),不能用TPM/FPKM
DESeq2归一化 Median of Ratios(中位数比率法)
edgeR归一化 TMM(Trimmed Mean of M-values)
PCoA流程 vegdist() → pcoa() → ggplot + geom_point + stat_ellipse
PERMANOVA函数 vegan::adonis2()
Z-score标准化 t(scale(t(matrix))) 按行标准化
padj vs pvalue 用padj(BH校正后),不用原始pvalue