跳转至

生信数据可视化最佳实践(Bioinformatics Data Visualization Best Practices)


一句话说明

好的图表让审稿人3秒看懂你的结论,差的图表让审稿人3秒决定拒稿——本篇讲的不是"怎么用ggplot2画图"(那是08_R语言可视化),也不是"怎么做交互式图表"(那是23_Plotly),而是讲设计原则、图表选择逻辑、配色方案、期刊投稿标准这些"画好图"的通用知识。


科研图表设计原则

1. 数据墨水比(Data-Ink Ratio)

Edward Tufte提出的核心概念:图表中用来展示数据的墨水 / 总墨水 应该尽可能高

白话说:把所有跟数据无关的装饰删掉——3D效果、花哨背景、多余网格线都是"浪费墨水"。

# ===== 反面教材 vs 正面教材 =====
library(ggplot2)

# 反面:信息密度低(大量非数据墨水)
ggplot(data, aes(x = group, y = value)) +
  geom_bar(stat = "identity", fill = "blue") +
  theme(
    panel.background = element_rect(fill = "lightyellow"),  # 花哨背景
    panel.grid.major = element_line(color = "gray"),         # 多余网格
    panel.grid.minor = element_line(color = "lightgray"),    # 更多余的网格
    plot.background = element_rect(fill = "white", color = "black", linewidth = 2)
  )  # 粗边框

# 正面:干净高效
ggplot(data, aes(x = group, y = value)) +
  geom_bar(stat = "identity", fill = "#2C3E50") +
  theme_classic() +  # 经典主题:只保留坐标轴
  theme(
    axis.text = element_text(size = 12),     # 足够大的字号
    axis.title = element_text(size = 14)
  )

Tufte的设计准则: | 原则 | 做法 | 白话解释 | |------|------|---------| | 最大化数据墨水比 | 删除不必要的装饰 | 每一笔墨水都应该传递信息 | | 避免图表垃圾 | 不用3D、渐变、阴影 | 花哨≠专业 | | 避免重复编码 | 不要同时用颜色+形状+大小表示同一变量 | 一个变量一种视觉通道 | | 小倍数原则 | 用facet拆分子图而非堆叠 | 比较时对齐比重叠更清晰 |

2. 配色原则

三大规则: 1. 不要用默认彩虹色:红橙黄绿青蓝紫看起来"热闹"但不专业,且色盲不友好 2. 连续数据用渐变色(如viridis),分类数据用离散色(如ColorBrewer Set2) 3. 考虑色盲友好:约8%的男性是红绿色盲,避免仅用红/绿区分关键信息

# ===== 色盲友好检查 =====
# 安装colorblindr包检查你的图色盲人群能否看懂
# devtools::install_github("clauswilke/colorblindr")
library(colorblindr)

p <- ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
  geom_point()

cvd_grid(p)  # 模拟不同类型色盲看到的效果
# 如果某种色盲模式下无法区分不同类别 → 换配色

3. 字号规范

元素推荐字号说明
标题14-16pt加粗
坐标轴标签12-14pt变量名
刻度标签10-12pt数字/类别名
图例标题11-13pt
图例文字10-12pt
注释文字9-11ptp值、样本量等

关键原则:图片缩小到期刊排版大小(通常单栏8.5cm,双栏17.5cm)后,最小文字仍需清晰可读。

4. 分辨率与格式

输出场景分辨率推荐格式
期刊投稿300-600 DPITIFF / EPS / PDF
学位论文300 DPIPDF / TIFF
PPT演示150 DPI即可PNG
网页/博客72-150 DPIPNG / SVG
# ===== 保存高分辨率图片 =====

# 方法1:ggsave(最方便)
ggsave("figure1.tiff", plot = p,
       width = 17.5, height = 12,  # 单位cm
       units = "cm",
       dpi = 300,                   # 分辨率
       compression = "lzw")         # TIFF压缩

# 方法2:保存为向量图(PDF/EPS,无限缩放不失真)
ggsave("figure1.pdf", plot = p,
       width = 17.5, height = 12,
       units = "cm")

# 方法3:Cairo后端(解决中文字体问题)
library(Cairo)
CairoTIFF("figure1.tiff", width = 1750, height = 1200, res = 300)
print(p)
dev.off()

5. 向量图 vs 位图

类型格式特点适合场景
向量图PDF, EPS, SVG放大不失真、文件小折线/柱状/散点等几何图形
位图TIFF, PNG, JPEG放大会糊、文件大热图、照片、复杂渐变

最佳实践:能用向量图就用向量图。热图(几千个色块)或照片类图表用位图。


生信常见图表选择指南

核心思路:数据类型决定图表类型

物种/基因丰度 → 柱状图 / 堆叠柱状图

# ===== 门水平堆叠柱状图(宏基因组经典图) =====
library(ggplot2)

ggplot(abundance_data, aes(x = sample, y = abundance, fill = phylum)) +
  geom_bar(stat = "identity", position = "fill") +  # position="fill" 归一化到100%
  scale_fill_brewer(palette = "Set3") +   # ColorBrewer离散色板
  labs(x = "样本", y = "相对丰度", fill = "门") +
  theme_classic() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))  # 样本名45度旋转
# 适合场景:展示不同样本的物种组成差异
# 注意:类别太多(>12)时颜色难以区分,可以把低丰度的合并为"Others"

差异分析 → 火山图(Volcano Plot)

# ===== 火山图 =====
ggplot(de_results, aes(x = log2FoldChange, y = -log10(padj))) +
  geom_point(aes(color = significance), size = 1, alpha = 0.6) +
  scale_color_manual(values = c(
    "Up" = "#E74C3C",     # 上调:红色
    "Down" = "#3498DB",   # 下调:蓝色
    "NS" = "#BDC3C7"      # 不显著:灰色
  )) +
  geom_hline(yintercept = -log10(0.05), linetype = "dashed", color = "gray40") +
  geom_vline(xintercept = c(-1, 1), linetype = "dashed", color = "gray40") +
  theme_classic() +
  labs(x = "log2(Fold Change)", y = "-log10(adjusted p-value)")
# x轴 = 变化倍数(正=上调,负=下调)
# y轴 = 显著性(越高越显著)
# 右上角 = 显著上调,左上角 = 显著下调

差异分析 → MA图

# ===== MA图 =====
ggplot(de_results, aes(x = log2(baseMean + 1), y = log2FoldChange)) +
  geom_point(aes(color = significance), size = 0.8, alpha = 0.5) +
  scale_color_manual(values = c("Up" = "#E74C3C", "Down" = "#3498DB", "NS" = "#BDC3C7")) +
  geom_hline(yintercept = 0, color = "gray40") +
  theme_classic() +
  labs(x = "log2(Mean Expression)", y = "log2(Fold Change)")
# x轴 = 平均表达量(A = Average)
# y轴 = 变化倍数(M = Minus = log ratio)
# 适合展示:低表达基因的fold change不太可靠

分布比较 → 箱线图 / 小提琴图

# ===== 箱线图 + 散点 =====
ggplot(data, aes(x = group, y = alpha_diversity, fill = group)) +
  geom_boxplot(alpha = 0.7, outlier.shape = NA) +  # 箱线图,隐藏outlier点
  geom_jitter(width = 0.2, size = 1.5, alpha = 0.5) +  # 叠加散点
  scale_fill_brewer(palette = "Set2") +
  stat_compare_means(method = "wilcox.test") +  # 加p值(ggpubr包)
  theme_classic() +
  labs(x = "", y = "Shannon多样性指数")
# 箱线图展示分布特征,叠加散点展示实际数据点
# 比单独的柱状图+误差棒信息量多得多

# ===== 小提琴图(展示分布形状) =====
ggplot(data, aes(x = group, y = expression, fill = group)) +
  geom_violin(alpha = 0.7) +  # 小提琴=核密度估计的镜像
  geom_boxplot(width = 0.1, fill = "white") +  # 内嵌小箱线图
  theme_classic()
# 小提琴图比箱线图多一个信息:分布是否双峰/偏态

相关性 → 散点图 / 热图

# ===== 相关性热图 =====
library(pheatmap)

# 计算相关系数矩阵
cor_matrix <- cor(expr_data, method = "spearman")

# 画热图
pheatmap(cor_matrix,
         color = colorRampPalette(c("#3498DB", "white", "#E74C3C"))(100),
         # 蓝-白-红渐变:蓝=负相关,红=正相关
         display_numbers = TRUE,       # 显示数值
         number_format = "%.2f",       # 保留2位小数
         fontsize = 10,
         fontsize_number = 8)

降维 → PCA / UMAP

# ===== PCA散点图 =====
ggplot(pca_df, aes(x = PC1, y = PC2, color = group)) +
  geom_point(size = 3, alpha = 0.8) +
  stat_ellipse(level = 0.95, linetype = "dashed") +  # 95%置信椭圆
  scale_color_brewer(palette = "Set1") +
  theme_classic() +
  labs(x = paste0("PC1 (", var_explained[1], "%)"),
       y = paste0("PC2 (", var_explained[2], "%)"))
# 坐标轴标签要写方差解释比例
# 椭圆帮助可视化分组边界

# ===== UMAP(单细胞常用) =====
ggplot(umap_df, aes(x = UMAP_1, y = UMAP_2, color = cell_type)) +
  geom_point(size = 0.5, alpha = 0.6) +
  scale_color_manual(values = custom_colors) +  # 自定义颜色
  theme_void() +  # 无坐标轴(UMAP坐标无实际意义)
  guides(color = guide_legend(override.aes = list(size = 3)))
  # 图例中的点放大方便看清

进化/系统发育 → 系统树 / 环形图

# ===== 系统发育树(ggtree) =====
library(ggtree)

# 读取Newick格式的树文件
tree <- read.tree("phylo_tree.nwk")

# 基本树形图
ggtree(tree, layout = "rectangular") +
  geom_tiplab(size = 3) +           # 显示叶节点标签
  geom_nodepoint(color = "steelblue", size = 2)  # 节点标记

# 环形树(适合大量物种)
ggtree(tree, layout = "circular") +
  geom_tiplab(size = 2, offset = 0.02)

配色方案(具体色号)

ColorBrewer 调色板

ColorBrewer由Cynthia Brewer开发,是科研图表配色的"金标准"。

# ===== ColorBrewer常用调色板 =====
library(RColorBrewer)

# 查看所有调色板
display.brewer.all()

# 定性/离散色板(分类数据)
brewer.pal(8, "Set2")
# "#66C2A5" "#FC8D62" "#8DA0CB" "#E78AC3" "#A6D854" "#FFD92F" "#E5C494" "#B3B3B3"

brewer.pal(8, "Set1")
# "#E41A1C" "#377EB8" "#4DAF4A" "#984EA3" "#FF7F00" "#FFFF33" "#A65628" "#F781BF"

brewer.pal(8, "Paired")
# "#A6CEE3" "#1F78B4" "#B2DF8A" "#33A02C" "#FB9A99" "#E31A1C" "#FDBF6F" "#FF7F00"

# 连续/顺序色板(数值数据)
brewer.pal(9, "YlOrRd")  # 黄→橙→红
brewer.pal(9, "Blues")    # 浅蓝→深蓝
brewer.pal(9, "RdYlBu")  # 红→黄→蓝(发散型)

Viridis 调色板

viridis是为感知均匀性和色盲友好性专门设计的调色板,2015年成为matplotlib默认色板。

# ===== Viridis系列 =====
library(viridis)

# viridis: 黄→绿→蓝→紫(默认,最通用)
scale_color_viridis_c()
# 色号示例(5档): "#440154" "#3B528B" "#21908C" "#5DC863" "#FDE725"

# magma: 黑→紫→橙→黄(暗色调)
scale_color_viridis_c(option = "magma")
# "#000004" "#51127C" "#B73779" "#FC8961" "#FCFDBF"

# inferno: 黑→紫→橙→黄(高对比度)
scale_color_viridis_c(option = "inferno")

# plasma: 紫→粉→橙→黄
scale_color_viridis_c(option = "plasma")

# 所有viridis系列的优势:
# 1. 色盲友好(红绿色盲也能区分)
# 2. 感知均匀(数值差异和颜色差异成正比)
# 3. 灰度打印仍可区分(亮度单调递增)

Nature 系列期刊常用配色

# ===== Nature/Science风格配色 =====

# 方案1:ggsci包(直接提供期刊配色)
library(ggsci)

# Nature风格
scale_color_npg()  # NPG = Nature Publishing Group
# "#E64B35" "#4DBBD5" "#00A087" "#3C5488" "#F39B7F" "#8491B4" "#91D1C2" "#DC0000"

# Science/AAAS风格
scale_color_aaas()
# "#3B4992" "#EE0000" "#008B45" "#631879" "#008280" "#BB0021" "#5F559B" "#A20056"

# Lancet风格
scale_color_lancet()
# "#00468B" "#ED0000" "#42B540" "#0099B4" "#925E9F" "#FDAF91" "#AD002A" "#ADB6B6"

# 方案2:手动定义Nature风格配色
nature_colors <- c(
  "#E64B35",  # 砖红(对比强烈,用于关键组)
  "#4DBBD5",  # 青色
  "#00A087",  # 墨绿
  "#3C5488",  # 深蓝
  "#F39B7F",  # 浅橙
  "#8491B4",  # 灰蓝
  "#91D1C2",  # 浅绿
  "#DC0000",  # 纯红
  "#7E6148",  # 棕色
  "#B09C85"   # 米色
)

热图常用配色

# ===== 热图配色方案 =====

# 方案1:蓝-白-红(差异表达经典配色)
# 蓝=低表达/下调,白=中等,红=高表达/上调
blue_white_red <- colorRampPalette(c("#3498DB", "white", "#E74C3C"))(100)

# 方案2:viridis(连续数据)
# 适合单方向数据(如p值、表达量)

# 方案3:RdBu反转(蓝-白-红,ColorBrewer版)
rdbu_colors <- rev(brewer.pal(11, "RdBu"))

# 方案4:pheatmap默认(蓝-白-红-黄)
# 不推荐:颜色过多容易分散注意力

Publication-Ready 图表标准

主流期刊投稿要求

期刊分辨率格式单栏宽双栏宽字号要求
Nature300 DPITIFF/EPS/PDF89mm183mm≥5pt (print), ≥7pt推荐
Science300 DPIEPS/PDF85mm174mm≥6pt
Cell300 DPIPDF/EPS优先85mm174mm≥6pt
PNAS300-600 DPITIFF/EPS87mm178mm≥6pt
Nucleic Acids Research300 DPITIFF/EPS/PDF86mm178mm≥7pt
Bioinformatics300 DPITIFF/EPS86mm178mm≥8pt
Microbiome300 DPITIFF/EPS/PDF85mm170mm≥8pt

统一出图模板

# ===== Publication-ready ggplot主题模板 =====

theme_publication <- function(base_size = 12) {
  theme_classic(base_size = base_size) %+replace%  # 基于经典主题修改
    theme(
      # 坐标轴
      axis.line = element_line(color = "black", linewidth = 0.5),
      axis.ticks = element_line(color = "black", linewidth = 0.3),
      axis.text = element_text(color = "black", size = base_size - 2),
      axis.title = element_text(color = "black", size = base_size, face = "bold"),

      # 图例
      legend.title = element_text(size = base_size - 1),
      legend.text = element_text(size = base_size - 2),
      legend.key.size = unit(0.4, "cm"),
      legend.background = element_blank(),  # 透明背景

      # 标题
      plot.title = element_text(size = base_size + 2, face = "bold", hjust = 0.5),

      # 面板
      panel.background = element_blank(),
      panel.grid = element_blank(),        # 无网格线
      strip.background = element_blank(),  # facet标题无背景
      strip.text = element_text(size = base_size, face = "bold"),

      # 边距
      plot.margin = unit(c(0.5, 0.5, 0.5, 0.5), "cm")
    )
}

# 使用
p <- ggplot(data, aes(x, y)) +
  geom_point() +
  theme_publication(base_size = 12)

# 保存为期刊要求的格式
ggsave("Fig1.tiff", plot = p,
       width = 89, height = 70, units = "mm",  # Nature单栏宽
       dpi = 300, compression = "lzw")

多面板图(Figure组合)

# ===== 多面板组合(patchwork包) =====
library(patchwork)

# 创建多个子图
p1 <- ggplot(data, aes(x, y)) + geom_point() + labs(tag = "A")  # tag加标签
p2 <- ggplot(data, aes(group, value)) + geom_boxplot() + labs(tag = "B")
p3 <- ggplot(data, aes(x)) + geom_histogram() + labs(tag = "C")

# 组合
combined <- p1 + p2 + p3 +
  plot_layout(ncol = 3) +  # 3列排列
  plot_annotation(title = "Figure 1")

# 保存
ggsave("Figure1_combined.pdf", combined,
       width = 183, height = 70, units = "mm")  # 双栏宽

常见图表错误(反面教材)

错误1:3D饼图

为什么错:3D透视让前面的扇区看起来更大、后面的更小,严重扭曲面积比例。

正确替代:用水平柱状图或堆叠柱状图。饼图本身就不推荐(人类对角度的判断不如长度准确),3D更是错上加错。

错误2:截断Y轴

为什么错:Y轴不从0开始,会让微小差异看起来像巨大差异,有误导性。

# 错误示例:Y轴从95开始(差异被夸大10倍)
# 正确做法:Y轴从0开始,或者明确标注断轴并说明理由

# 如果确实需要放大差异区域,使用断轴:
library(ggbreak)
p + scale_y_break(c(20, 80))  # 在20-80之间断开
# 但要在图注中解释为什么用断轴

错误3:误导性配色

问题: - 用彩虹色表示连续数据(色盲不友好 + 感知不均匀) - 用红色表示"正常"、绿色表示"异常"(和人的直觉相反) - 热图中间色不是白色/中性色(难以判断正负)

错误4:柱状图+误差棒隐藏分布

为什么错:柱状图+SE误差棒只展示了均值和标准误,完全隐藏了数据的分布形状(可能是双峰、有离群值等)。

正确替代:用箱线图+散点(geom_boxplot() + geom_jitter())或小提琴图。

错误5:双Y轴图(Dual-axis Chart)

为什么错:两个Y轴可以通过调整缩放比例制造"虚假相关",任何两条曲线都能通过调整Y轴范围让它们"看起来"相关。

正确替代:用两个独立子图(facet)并排放置。

错误6:不标p值和显著性

为什么错:光看图"好像有差异"但没有统计检验支持。

# 正确做法:用ggpubr加统计检验
library(ggpubr)
ggplot(data, aes(x = group, y = value)) +
  geom_boxplot() +
  stat_compare_means(
    comparisons = list(c("T2D", "Control")),
    method = "wilcox.test",
    label = "p.signif"  # 显示 * ** *** ns
  )

面试怎么答(5道)

Q1:你画科研图表时遵循什么原则?

:"遵循Tufte的数据墨水比原则——最大化展示数据的元素,最小化装饰性元素。具体来说:(1) 用theme_classic()而非默认主题,去掉多余的网格和背景;(2) 配色选用ColorBrewer或viridis等专业色板,确保色盲友好;(3) 字号足够大,缩小到期刊排版尺寸后仍可读;(4) 坐标轴标签写清楚变量名和单位;(5) 保存时用300 DPI以上的TIFF或PDF格式。"

Q2:什么数据用什么图?给几个例子。

:"遵循'数据类型决定图表类型'的原则。组间差异比较用箱线图+散点而非柱状图+误差棒,因为箱线图展示更多分布信息。差异基因展示用火山图(x轴是fold change,y轴是p值),能同时看到显著性和效应大小。样本整体关系用PCA散点图。物种组成用堆叠柱状图。相关性用热图。降维结果中,PCA适合Bulk数据,UMAP适合单细胞数据。"

Q3:你怎么处理配色问题?

:"三个原则:一是色盲友好,大约8%的男性是红绿色盲,所以避免仅用红绿区分关键信息,推荐用viridis(色盲+灰度打印都友好);二是匹配数据类型,连续数据用渐变色(viridis、Blues),分类数据用离散色(ColorBrewer Set2、ggsci的npg配色);三是不超过7-8种颜色,类别太多时把低频类别合并为Others。可以用colorblindr包模拟色盲视角来验证。"

Q4:期刊投稿的图表有什么具体要求?

:"主要关注四点:(1) 分辨率至少300 DPI,有些期刊如PNAS要求600 DPI;(2) 格式优先用向量图(PDF/EPS),热图等复杂图用TIFF;(3) 尺寸按期刊要求——Nature单栏89mm、双栏183mm;(4) 字号最小不低于6pt(打印后),通常8-12pt比较安全。另外最好用公斤制(厘米/毫米)而非英寸设置图片尺寸,因为大多数期刊用SI单位。"

Q5:你犯过什么可视化错误?怎么改的?

(诚实回答更加分):"早期用过默认彩虹色画热图,被导师指出色盲不友好。后来改用蓝-白-红配色(发散型数据)或viridis(连续数据)。另外以前画柱状图+标准误来展示组间差异,后来意识到这种图隐藏了数据分布,改成了箱线图叠加散点图,审稿人评价说图表专业了很多。现在每次出图我会检查:配色是否色盲友好、字号缩小后是否可读、是否有图表垃圾可以删除。"


速查表

图表选择决策树

你的数据是什么类型?
├── 分类比较(A组 vs B组的某个指标)
│   ├── 2-5组 → 箱线图+散点 / 小提琴图
│   ├── 时间序列 → 折线图+误差带
│   └── 多指标比较 → 分组柱状图 / 雷达图
├── 组成/比例(各成分占多少)
│   ├── 少量样本(<10) → 堆叠柱状图
│   ├── 大量样本(>10) → 堆叠面积图
│   └── 两个组成对比 → 桑基图(alluvial)
├── 相关性/关系
│   ├── 两变量 → 散点图+回归线
│   ├── 多变量两两关系 → 相关性热图
│   └── 网络关系 → 网络图(igraph)
├── 分布
│   ├── 单变量 → 直方图 / 密度图
│   ├── 多组分布比较 → 小提琴图 / 岭线图
│   └── 二维分布 → 密度等高线图
├── 降维/聚类
│   ├── Bulk数据 → PCA / MDS
│   ├── 单细胞 → UMAP / t-SNE
│   └── 聚类结果 → 热图+行列聚类树
├── 差异分析
│   ├── 整体概览 → 火山图(Volcano)
│   ├── 表达量vs差异 → MA图
│   └── 特定基因列表 → 热图 / 点图(dotplot)
└── 进化/系统发育
    ├── 物种少(<50) → 矩形系统树
    └── 物种多(>50) → 环形系统树

配色速查

场景推荐调色板关键色号
分类(2-3组)Set1#E41A1C #377EB8 #4DAF4A
分类(4-8组)Set2#66C2A5 #FC8D62 #8DA0CB #E78AC3
Nature风格ggsci::npg#E64B35 #4DBBD5 #00A087 #3C5488
连续(单方向)viridis#440154#21908C#FDE725
连续(发散)RdBu#E74C3Cwhite#3498DB
热图(上下调)蓝白红#3498DB #FFFFFF #E74C3C
安全绿红替代蓝橙#3498DB(低) #E67E22(高)

期刊要求速查

项目标准值
分辨率≥300 DPI(线条图600 DPI)
单栏宽度85-89 mm
双栏宽度170-183 mm
最小字号6-8 pt
推荐格式PDF/EPS(向量) > TIFF(位图) > PNG
TIFF压缩LZW(无损)
颜色模式RGB(在线) / CMYK(印刷)
文件大小通常<10MB/图

延伸资源

  1. 《The Visual Display of Quantitative Information》:Edward Tufte的经典著作,数据可视化圣经
  2. ColorBrewer官网:https://colorbrewer2.org/ — 在线选配色
  3. ggsci包文档:https://nanx.me/ggsci/ — Nature/Science/Lancet等期刊配色
  4. viridis官方说明:https://cran.r-project.org/web/packages/viridis/vignettes/intro-to-viridis.html
  5. Fundamentals of Data Visualization:https://clauswilke.com/dataviz/ — Claus Wilke的免费在线书(强烈推荐)
  6. ggplot2官方文档:https://ggplot2.tidyverse.org/
  7. patchwork多图组合:https://patchwork.data-imaginist.com/
  8. Nature投稿图片规范:https://www.nature.com/nature/for-authors/formatting-guide

本篇和08_R语言可视化的区别:08讲的是ggplot2/pheatmap等工具的具体用法("怎么画"),本篇讲的是设计原则、图表选择、配色方案和投稿标准("怎么画好")。和23_Plotly的区别:23讲的是交互式可视化,本篇讲的是静态图表的设计规范(主要面向论文投稿)。