在高分文章中,单细胞转录组的细胞比例图怎么画最好看?
Nature的经典配色是什么?代码如何运用在科研绘图当中呢?
今天小编就来带你开箱:单细胞绘图的方法。快来mark这篇干货推文吧!
一、为什么但细胞比例图这么重要呢?
在单细胞转录组(scRNA-seq)的分析里面,细胞比例图(Cell Propotion Plot)是每一篇高分文章的加分项。
细胞比例图主要回答了:在不同的样本/条件/时间点之间,各细胞亚群的组成比例有没有变化?
这张图往往出现在:疾病 vs 正常对照的细胞组成差异、治疗前后细胞亚群的重塑等变化。
图画得好不好,直接影响了审稿人对数据和文章的第一印象。
二、搞清楚有哪些画法
1. 堆叠柱状图(Stacked Bar Plot):最经典,最常见,信息密度高。适合展示多样本整体组成对比。
2. 分组柱状图(Stacked Bar Plot):适合比较少量细胞类型的精确数值,便于统计检验。
3. 桑基图 / 冲积图(Alluvial / Sankey Plot):适合展示细胞状态转变,视觉冲击力强,Nature 文章最爱。
4. 气泡比例图(Bubble Proportion Plot):适合多维度展示,兼顾比例和统计显著性。
5. 小提琴/箱线图叠加比例图:适合多样本统计检验场景,可以直接展示p值。
三、Nature的配色从哪里来的?
高分文章的配色有三个共同特点:饱和度适中(不刺眼,印刷友好)、色相区分度高(色盲友好)、有层次感(主色+辅色搭配)。
NPG 配色(Nature Publishing Group)
#E64B35 #4DBBD5 #00A087 #3C5488 #F39B7F #8491B4 #91D1C2 #DC0000
D3 Category10(单细胞最常用)
#1F77B4 #FF7F0E #2CA02C #D62728 #9467BD #8C564B #E377C2 #7F7F7F
推荐使用 ggsci 包,一行代码 scale_fill_npg() 即可调用 NPG 配色,无需手动输入色值。
四、代码实践
首先,要准备好代码环境:
<!--
/* Font Definitions */
@font-face
{font-family:"MS Mincho";
panose-1:2 2 6 9 4 2 5 8 3 4;
mso-font-charset:128;
mso-generic-font-family:modern;
mso-font-pitch:fixed;
mso-font-signature:-536870145 1791491579 134217746 0 131231 0;}
@font-face
{font-family:"Cambria Math";
panose-1:2 4 5 3 5 4 6 3 2 4;
mso-font-charset:0;
mso-generic-font-family:roman;
mso-font-pitch:variable;
mso-font-signature:-536869121 1107305727 33554432 0 415 0;}
@font-face
{font-family:Cambria;
panose-1:2 4 5 3 5 4 6 3 2 4;
mso-font-charset:0;
mso-generic-font-family:roman;
mso-font-pitch:variable;
mso-font-signature:-536869121 1107305727 33554432 0 415 0;}
@font-face
{font-family:"\@MS Mincho";
panose-1:2 2 6 9 4 2 5 8 3 4;
mso-font-charset:128;
mso-generic-font-family:modern;
mso-font-pitch:fixed;
mso-font-signature:-536870145 1791491579 134217746 0 131231 0;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{mso-style-unhide:no;
mso-style-qformat:yes;
mso-style-parent:"";
margin-top:0cm;
margin-right:0cm;
margin-bottom:10.0pt;
margin-left:0cm;
line-height:115%;
mso-pagination:widow-orphan;
font-size:11.0pt;
font-family:"Cambria",serif;
mso-ascii-font-family:Cambria;
mso-ascii-theme-font:minor-latin;
mso-fareast-font-family:"MS Mincho";
mso-fareast-theme-font:minor-fareast;
mso-hansi-font-family:Cambria;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;
mso-fareast-language:EN-US;}
.MsoChpDefault
{mso-style-type:export-only;
mso-default-props:yes;
mso-bidi-font-size:11.0pt;
font-family:"Cambria",serif;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;
mso-font-kerning:0pt;
mso-ligatures:none;
mso-fareast-language:EN-US;}
.MsoPapDefault
{mso-style-type:export-only;
margin-bottom:10.0pt;
line-height:115%;}
/* Page Definitions */
@page
{mso-page-border-surround-header:no;
mso-page-border-surround-footer:no;}
@page WordSection1
{size:612.0pt 792.0pt;
margin:72.0pt 90.0pt 72.0pt 90.0pt;
mso-header-margin:36.0pt;
mso-footer-margin:36.0pt;
mso-paper-source:0;}
div.WordSection1
{page:WordSection1;}
-->
install.packages(c("ggplot2",
"dplyr", "tidyr", "ggsci",
"ggalluvial", "RColorBrewer"))
BiocManager::install("scater")
图一:经典堆叠柱状图
<!--
/* Font Definitions */
@font-face
{font-family:"MS Mincho";
panose-1:2 2 6 9 4 2 5 8 3 4;
mso-font-charset:128;
mso-generic-font-family:modern;
mso-font-pitch:fixed;
mso-font-signature:-536870145 1791491579 134217746 0 131231 0;}
@font-face
{font-family:"Cambria Math";
panose-1:2 4 5 3 5 4 6 3 2 4;
mso-font-charset:0;
mso-generic-font-family:roman;
mso-font-pitch:variable;
mso-font-signature:-536869121 1107305727 33554432 0 415 0;}
@font-face
{font-family:Cambria;
panose-1:2 4 5 3 5 4 6 3 2 4;
mso-font-charset:0;
mso-generic-font-family:roman;
mso-font-pitch:variable;
mso-font-signature:-536869121 1107305727 33554432 0 415 0;}
@font-face
{font-family:"\@MS Mincho";
panose-1:2 2 6 9 4 2 5 8 3 4;
mso-font-charset:128;
mso-generic-font-family:modern;
mso-font-pitch:fixed;
mso-font-signature:-536870145 1791491579 134217746 0 131231 0;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{mso-style-unhide:no;
mso-style-qformat:yes;
mso-style-parent:"";
margin-top:0cm;
margin-right:0cm;
margin-bottom:10.0pt;
margin-left:0cm;
line-height:115%;
mso-pagination:widow-orphan;
font-size:11.0pt;
font-family:"Cambria",serif;
mso-ascii-font-family:Cambria;
mso-ascii-theme-font:minor-latin;
mso-fareast-font-family:"MS Mincho";
mso-fareast-theme-font:minor-fareast;
mso-hansi-font-family:Cambria;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;
mso-fareast-language:EN-US;}
.MsoChpDefault
{mso-style-type:export-only;
mso-default-props:yes;
mso-bidi-font-size:11.0pt;
font-family:"Cambria",serif;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;
mso-font-kerning:0pt;
mso-ligatures:none;
mso-fareast-language:EN-US;}
.MsoPapDefault
{mso-style-type:export-only;
margin-bottom:10.0pt;
line-height:115%;}
/* Page Definitions */
@page
{mso-page-border-surround-header:no;
mso-page-border-surround-footer:no;}
@page WordSection1
{size:612.0pt 792.0pt;
margin:72.0pt 90.0pt 72.0pt 90.0pt;
mso-header-margin:36.0pt;
mso-footer-margin:36.0pt;
mso-paper-source:0;}
div.WordSection1
{page:WordSection1;}
-->
library(ggplot2)
library(dplyr)
library(ggsci)
set.seed(42)
cell_types <- c("T cell","B cell","NK
cell","Monocyte","DC","Macrophage","Neutrophil","Plasma
cell")
samples <- paste0("Sample_", 1:8)
df <- expand.grid(CellType = cell_types, Sample = samples) %>%
mutate(Proportion = runif(n(), 0.02,
0.35),
Group =
ifelse(grepl("1|2|3|4", Sample), "Control",
"Disease")) %>%
group_by(Sample) %>%
mutate(Proportion = Proportion /
sum(Proportion)) %>%
ungroup()
p1 <- ggplot(df, aes(x = Sample, y = Proportion, fill = CellType)) +
geom_bar(stat = "identity",
width = 0.75, color = "white", linewidth = 0.3) +
scale_fill_npg() +
scale_y_continuous(labels =
scales::percent_format()) +
facet_grid(~ Group, scales =
"free_x", space = "free") +
labs(title = "Cell Type
Composition Across Samples",
x = NULL, y = "Cell
Proportion", fill = "Cell Type") +
theme_classic(base_size = 12) +
theme(axis.text.x = element_text(angle
= 45, hjust = 1, size = 9),
strip.background =
element_rect(fill = "#F0F0F0", color = NA),
strip.text = element_text(face =
"bold", size = 11),
legend.position =
"right",
legend.key.size = unit(0.4,
"cm"),
plot.title = element_text(face =
"bold", hjust = 0.5))
ggsave("stacked_bar_NPG.pdf", p1, width = 9, height = 5, useDingbats
= FALSE)
color = "white" 加白色描边让各色块边界清晰;useDingbats = FALSE 保证 PDF 字体在 Illustrator 中可编辑。
图2:冲积图(Alluvial Plot)——这图好啊,这图可太吸睛了(下面代码建议收藏)
<!--
/* Font Definitions */
@font-face
{font-family:"MS Mincho";
panose-1:2 2 6 9 4 2 5 8 3 4;
mso-font-charset:128;
mso-generic-font-family:modern;
mso-font-pitch:fixed;
mso-font-signature:-536870145 1791491579 134217746 0 131231 0;}
@font-face
{font-family:"Cambria Math";
panose-1:2 4 5 3 5 4 6 3 2 4;
mso-font-charset:0;
mso-generic-font-family:roman;
mso-font-pitch:variable;
mso-font-signature:-536869121 1107305727 33554432 0 415 0;}
@font-face
{font-family:Cambria;
panose-1:2 4 5 3 5 4 6 3 2 4;
mso-font-charset:0;
mso-generic-font-family:roman;
mso-font-pitch:variable;
mso-font-signature:-536869121 1107305727 33554432 0 415 0;}
@font-face
{font-family:"\@MS Mincho";
panose-1:2 2 6 9 4 2 5 8 3 4;
mso-font-charset:128;
mso-generic-font-family:modern;
mso-font-pitch:fixed;
mso-font-signature:-536870145 1791491579 134217746 0 131231 0;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{mso-style-unhide:no;
mso-style-qformat:yes;
mso-style-parent:"";
margin-top:0cm;
margin-right:0cm;
margin-bottom:10.0pt;
margin-left:0cm;
line-height:115%;
mso-pagination:widow-orphan;
font-size:11.0pt;
font-family:"Cambria",serif;
mso-ascii-font-family:Cambria;
mso-ascii-theme-font:minor-latin;
mso-fareast-font-family:"MS Mincho";
mso-fareast-theme-font:minor-fareast;
mso-hansi-font-family:Cambria;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;
mso-fareast-language:EN-US;}
.MsoChpDefault
{mso-style-type:export-only;
mso-default-props:yes;
mso-bidi-font-size:11.0pt;
font-family:"Cambria",serif;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;
mso-font-kerning:0pt;
mso-ligatures:none;
mso-fareast-language:EN-US;}
.MsoPapDefault
{mso-style-type:export-only;
margin-bottom:10.0pt;
line-height:115%;}
/* Page Definitions */
@page
{mso-page-border-surround-header:no;
mso-page-border-surround-footer:no;}
@page WordSection1
{size:612.0pt 792.0pt;
margin:72.0pt 90.0pt 72.0pt 90.0pt;
mso-header-margin:36.0pt;
mso-footer-margin:36.0pt;
mso-paper-source:0;}
div.WordSection1
{page:WordSection1;}
-->
library(ggalluvial)
df_alluvial <- data.frame(
Cluster = rep(paste0("C", 1:8), each = 3),
CellType = rep(cell_types[1:3], 8),
Condition = rep(c("Normal",
"Early", "Late"), 8),
Freq = sample(50:500, 24, replace = TRUE)
)
p3 <- ggplot(df_alluvial,
aes(axis1 = Cluster, axis2 =
CellType, axis3 = Condition, y = Freq)) +
geom_alluvium(aes(fill = CellType),
width = 1/12, alpha = 0.75, knot.pos = 0.4) +
geom_stratum(width = 1/6, fill =
"grey90", color = "grey50", linewidth = 0.3) +
geom_text(stat = "stratum",
aes(label = after_stat(stratum)), size = 3, fontface = "bold") +
scale_fill_d3("category10") +
scale_x_discrete(limits =
c("Cluster", "Cell Type", "Condition"), expand =
c(0.1, 0.1)) +
labs(title = "Cell State
Transition Across Conditions",
y = "Cell Count", fill =
"Cell Type") +
theme_minimal(base_size = 12) +
theme(panel.grid = element_blank(),
axis.text.y = element_blank(),
plot.title = element_text(face =
"bold", hjust = 0.5, size = 14))
ggsave("alluvial_D3.pdf", p3, width = 10, height = 7, useDingbats =
FALSE)
封装函数:一键出图
<!--
/* Font Definitions */
@font-face
{font-family:"MS Mincho";
panose-1:2 2 6 9 4 2 5 8 3 4;
mso-font-charset:128;
mso-generic-font-family:modern;
mso-font-pitch:fixed;
mso-font-signature:-536870145 1791491579 134217746 0 131231 0;}
@font-face
{font-family:"Cambria Math";
panose-1:2 4 5 3 5 4 6 3 2 4;
mso-font-charset:0;
mso-generic-font-family:roman;
mso-font-pitch:variable;
mso-font-signature:-536869121 1107305727 33554432 0 415 0;}
@font-face
{font-family:Cambria;
panose-1:2 4 5 3 5 4 6 3 2 4;
mso-font-charset:0;
mso-generic-font-family:roman;
mso-font-pitch:variable;
mso-font-signature:-536869121 1107305727 33554432 0 415 0;}
@font-face
{font-family:"\@MS Mincho";
panose-1:2 2 6 9 4 2 5 8 3 4;
mso-font-charset:128;
mso-generic-font-family:modern;
mso-font-pitch:fixed;
mso-font-signature:-536870145 1791491579 134217746 0 131231 0;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{mso-style-unhide:no;
mso-style-qformat:yes;
mso-style-parent:"";
margin-top:0cm;
margin-right:0cm;
margin-bottom:10.0pt;
margin-left:0cm;
line-height:115%;
mso-pagination:widow-orphan;
font-size:11.0pt;
font-family:"Cambria",serif;
mso-ascii-font-family:Cambria;
mso-ascii-theme-font:minor-latin;
mso-fareast-font-family:"MS Mincho";
mso-fareast-theme-font:minor-fareast;
mso-hansi-font-family:Cambria;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;
mso-fareast-language:EN-US;}
.MsoChpDefault
{mso-style-type:export-only;
mso-default-props:yes;
mso-bidi-font-size:11.0pt;
font-family:"Cambria",serif;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;
mso-font-kerning:0pt;
mso-ligatures:none;
mso-fareast-language:EN-US;}
.MsoPapDefault
{mso-style-type:export-only;
margin-bottom:10.0pt;
line-height:115%;}
/* Page Definitions */
@page
{mso-page-border-surround-header:no;
mso-page-border-surround-footer:no;}
@page WordSection1
{size:612.0pt 792.0pt;
margin:72.0pt 90.0pt 72.0pt 90.0pt;
mso-header-margin:36.0pt;
mso-footer-margin:36.0pt;
mso-paper-source:0;}
div.WordSection1
{page:WordSection1;}
-->
plot_cell_proportion <- function(seurat_obj,
group_by = "orig.ident",
cell_type_col = "celltype",
palette
= "npg") {
df <- seurat_obj@meta.data %>%
count(.data[[group_by]],
.data[[cell_type_col]]) %>%
group_by(.data[[group_by]]) %>%
mutate(prop = n / sum(n)) %>%
ungroup()
color_fn <- switch(palette,
"npg" = scale_fill_npg(),
"nejm" = scale_fill_nejm(),
"d3" = scale_fill_d3(),
"lancet" =
scale_fill_lancet()
)
ggplot(df, aes(x = .data[[group_by]], y
= prop, fill = .data[[cell_type_col]])) +
geom_bar(stat = "identity",
width = 0.75, color = "white", linewidth = 0.3) +
color_fn +
scale_y_continuous(labels =
scales::percent_format()) +
labs(x = NULL, y = "Cell
Proportion", fill = "Cell Type") +
theme_classic(base_size = 12) +
theme(axis.text.x =
element_text(angle = 45, hjust = 1))
}
# 使用示例
# plot_cell_proportion(pbmc, group_by = "sample", cell_type_col =
"celltype", palette = "npg")
五、让图拥有“高级感”的5个细节
【字体】 普通:默认 Arial → 高分:Helvetica / Myriad Pro
【描边】 普通:无描边 → 高分:白色细描边 0.3pt
【图例】 普通:右侧大图例 → 高分:精简图例,key.size 缩小
【导出】 普通:PNG 300dpi → 高分:PDF 矢量图
【配色】 普通:默认 ggplot2 彩虹色 → 高分:ggsci NPG / NEJM / D3
六、常见的踩坑!
Q:颜色太多,超过 10 种细胞类型怎么办?
A:用 colorRampPalette 在 NPG 基础上插值扩展,或使用 Polychrome 包的 palette36,专为多类别设计。
Q:PDF 导出后字体变成方块?
A:加 useDingbats = FALSE,或者用 cairo_pdf() 替代 pdf()。
Q:Seurat 对象如何直接提取比例数据?
A:用table(seurat@meta.data$sample, seurat@meta.data$celltype) 提取,再用 prop.table() 转换为比例。
有关图像编辑和图像修改更多的建议,可以也直接在百沐一下中进行提问!
一张好的细胞比例图 = 合适的图形类型 + Nature 级配色 + 精细主题调整 + 矢量格式导出
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)