ggplot2 完全解析 EP.01 从 0 到 1

ggplot2 完全解析 EP.01 从 0 到 1

视频教程

内容简介

  1. ggplot2 中会使用到的基本元件
  2. 使用 ggplot2 需要了解的基本概念
  3. 不同种类元件的具体用法浅析
  4. 实例解析 - 小提琴箱线散点图

前言

科研成果的呈现绘图是必不可少的,支持科研图标绘制的工具有很多,比如可视化操作下的 Graphpad Prism、Origin 等等,或者 Python 中的 matplotlib、seaborn、plotly 等等,这些工具我平常也都会用到,主要根据具体环境选择,但是说到生信分析当中的可视化,我还是唯独钟爱 ggplot2,它的简介性、语法一致性、结构完整性、可拓展性都是别的绘图系统不能比的,所以在此开一个坑介绍一下它的各种实际用法。

在讲到具体的各种奇技淫巧之前,还是需要这样一个总起性的章节对这个好用、易用、实用的绘图系统有一个基本的了解

ggplot2 中会使用到的基本元件

  1. geom 类:geom 是 geometry 的缩写,函数的格式类似 geom_point()/geom_line()/geom_*() 等等,主要负责使用原始数据直接绘制各种图形,是一张图标中最主要的部分;
  2. stat 类: stat 是 statistics 的缩写,函数的格式类似 stat_smooth()/stat_qq()/stat_*() 等等,负责对原始数据进行统计处理,比如 stat_smooth() 函数会根据原始数据进行回归分析,并返回预测值,stat_qq() 函数会根据原始数据进行 QQ 检验,并返回结果,是在原始数据的基础上进一步进行统计描述;
  3. annotate() 函数:标注函数,用于在图片上添加自定义注释,比如强调某个点;
  4. scale 类: 函数的格式类似 scale_x_continuous()/scale_y_discrete()/scale_*() 等等,负责调整坐标轴、颜色、尺寸、透明度等各种标尺的映射,适当的使用可以使图片更美观或者使需要强调的内容更突出;
  5. 各种主题相关函数:如 theme 类、coord 类、facet 类、guide 类等等,还有 ggtitle()/labs() 这些散在的函数,负责调整图片的样式,比如主题风格、坐标轴比例、 分面等等,这些函数的格式类似 theme_classic()/coord_polar()/facet_grid()/guide_legend() 等等,主要影响图片的美术风格和展示样式,本文主要只对 theme 类稍作展开。

对于 geom 类、stat 类、scale 类,我统称为图形类,因为他们都构成了主图的图形,scale 类我又称为标尺类。对于一张完整的图,图形类的元素是必不可少的,而标尺类与主题类都是锦上添花,而一张完整的图片,就是由这些基本元素排列组合而成的。

ggplot2 的基本概念

代码块的基本构成

首先,我们通常看到的代码块,由各个元件通过 + 号连接构成,比如:

1
2
3
4
5
6
7
8
9
10
11
# 遵循 tidyverse 推荐格式
panel <-
ggplot(...) + # 头部,必须存在,声明一个图形的启动
geom_*(...) + # 图形类中的三种元素至少要出现一个,否则就是空白图
stat_*(...) +
annotate(...) +
scale_*(...) + # 下列各种标尺类和主题类都非必须,但是如果真的都不放,图片会很丑
ggtitle(...) +
labs(...) +
theme_*(...) +
...

很有趣的一点是,ggplot2 在 python 中也得到了几乎完全的移植,如 plotnine,在 python 中,格式略有不同,但是我因为对齐清爽更喜欢这种格式,在 R 语言中也常照搬如下格式:

1
2
3
4
5
6
7
8
9
10
11
panel = (
ggplot(...)
+ geom_*(...)
+ stat_*(...)
+ annotate(...)
+ scale_*(...)
+ ggtitle(...)
+ labs(...)
+ theme_*(...)
+ ...
)

输入数据的格式

ggplot2 的输入数据必须是 data.frame 格式,当然,作为 tidyverse 家族的一员,tibble 这种更严格的 data.frame 也是兼容的。

对于 data.frame 这一类表格数据,通常有长表与宽表之分:

长表是指表格的每一行是一个样本,而每一列代表了样本的一个属性,比如:

id 颜色 大小 名称
1 苹果
2 樱桃
3 山竹
4 葡萄

宽表内的每一个数据都是一个样本,行名和列名用来定位属性,比如:

大小\颜色
苹果 山竹
樱桃 葡萄

在 ggplot2 中,必须使用长表数据,如果你手上的数据是宽表数据也不用担心,有很多种方法可以转换为长表数据,比如 reshape2 包中的 melt() 函数,或者用 tidyr 包中的 pivot_longer() 函数,当然,后者作为 tidyverse 分析流程中的一员,是更为推荐的。

比如我们通过大小为横坐标,颜色为纵坐标,可以把四种水果的标签标记在相应的象限上:

1
2
3
4
5
6
7
8
9
10
11
panel <-
ggplot(
data = fruit,
mapping = aes(
x = `大小`,
y = `颜色`,
label = `名称`
)
) +
geom_text()
panel

全局数据与局部数据

我们来观察如下两个代码块

1
2
3
4
5
6
7
8
9
10
11
panel <-
ggplot(
data = dat,
mapping = aes(
x = x,
y = y,
label = label
)
) +
geom_point() +
geom_text()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
panel <-
ggplot() +
geom_point(
data = dat1,
mapping = aes(
x = x1,
y = y1
)
) +
geom_text(
data = dat2,
mapping = aes(
x = x2,
y = y2,
label = label
)
)

在第一个代码块中,所有的数据包括标尺数据都写在 ggplot() 函数中,这代表着之后跟着的所有绘图元素,比如 geom_point()geom_text(),都会使用 dat 这个数据集进行绘图,我们称为全局数据;在第二个代码块中,数据集 dat1dat2 分别被 geom_point()geom_text() 函数所使用,两种图形依据不同的数据绘制,互不干扰,这被称为局部数据。

大部分情况下,主图中的内容都写在全局数据中,但是对于一些特殊的标注,就会用到局部数据,比如我们绘制差异基因火山图时,全局数据用于绘制所有基因的散点图,但是对于我们关注的基因,我们就会用局部数据绘制几个单独的点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
panel_volcano <-
ggplot(
data = deg, # 全局数据,包含了所有基因
mapping = aes(
x = log2FoldChange,
y = -log10(pvalue),
color = regulation
)
) +
geom_point() + # 继承自全局数据,绘制所有基因
geom_point(
data = deg_highlight, # 添加单独的局部变量,高亮关注的基因
mapping = aes(
x = log2FoldChange,
y = -log10(pvalue)
),
color = "black" # 改变这些高亮点的颜色做出区分
)
panel_volcano

如果只使用全局变量,结果就如下图:

而因为我们添加了局部变量,所以有标记我们关注的基因,如下图:

变量属性与常量属性

我们再观察如下的代码块与对应的图片:

1
2
3
4
5
6
7
8
9
10
11
panel <-
ggplot(
data = fruit,
mapping = aes(
x = `大小`,
y = `颜色`,
label = `名称`,
color = `名称`
)
) +
geom_text()

1
2
3
4
5
6
7
8
9
10
11
panel <-
ggplot(
data = fruit,
mapping = aes(
x = `大小`,
y = `颜色`,
label = `名称`
),
color = "red"
) +
geom_text()

两段代码中,只有 color 属性的位置和值不同,前者 color 放在aes() 内部,基于水果的名称分配绘图颜色,所以每种水果的绘图颜色不同,因而称为变量属性;后者 color 放在 aes() 之外,基于常量值 "red",所以所有水果的绘图颜色都是红色,为常量属性。

变量属性的作用是基于样本的不同属性为绘图元素分配不同外观,比如在通路富集气泡图中根据 p 值的大小分配颜色、根据基因的个数分配点的大小,等等;而常量属性的作用是统一规定图形的外观,比如把图中所有文字的大小都规定为 4。

变量属性与常量属性的区别只在于是否在 aes() 内,切不要与全局数据和局部数据混淆了。

图层

最后我们观察这一对代码块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
panel <-
ggplot(
data = mtcars,
mapping = aes(
x = factor(gear),
y = mpg
)
) +
geom_boxplot(
fill = 'red'
) +
geom_violin(
fill = 'blue'
)
panel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
panel <-
ggplot(
data = mtcars,
mapping = aes(
x = factor(gear),
y = mpg
)
) +
geom_violin(
fill = 'blue'
) +
geom_boxplot(
fill = 'red'
)
panel

两个示例中,boxplotviolin 的顺序不同,因而输出的图片中两种图形覆盖的顺序不同,总而言之,靠后的代码绘制的图形会覆盖靠前的代码绘制的图形。

不同种类元件的具体用法浅析

图形类

geom_point() 为例,主要的属性有:

  1. x, y:位置;
  2. color:颜色,若为实心形状则为整体颜色,若为空心图形则为边缘颜色;
  3. fill:填充颜色,只在空心图形中有效;
  4. shape:形状,常用的如 0 为 ■,1 为 ●,22为 □,21 为 ○,等等;
  5. size:大小;
  6. alpha:透明度,取值范围 0-1,0 表示完全透明,1 表示完全不透明。

这些属性都可用作变量属性或作常量属性。

标尺类

scale_color_manual 为例,主要的属性有:

  1. name:名称,显示在图例中,说明颜色标注的是哪个属性;
  2. breaks:刻度,与 values 对应;
  3. values:颜色,与 breaks 对应,规定具体的颜色,比如 breaks = c("苹果", "梨"), values = c("red", "blue"),那么苹果的颜色就是红色,梨的颜色就是蓝色;
  4. labels:标签,与 breaks 对应,比如 breaks = c("苹果", "梨"), labels = c("apple", "pear"),那么苹果对应的标签就是 apple,梨对应的标签就是 pear。

不同的 scale_*() 函数的具体属性差异巨大,具体还要查阅文档。

主题类

1、 theme 类:ggplot2 提供了各种预设的主题,比如 theme_classic()theme_bw()theme_minimal()theme_void()theme_gray() 等等,暂不展开自定义主题,以后聊到具体绘图的时候再讲解;
2. labs() 函数:为图形添加标题、图例、坐标轴标签等等,比如 labs(title = "标题", x = "X 轴", y = "Y 轴"),等等;
3. ggtitle() 函数:另一种添加标题的方法,比如 ggtitle("标题"),如果不调整主题,默认显示在图片的左上角。

实例解析 - 小提琴箱线散点图

话不多说,直接开画:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
panel <-
ggplot(
data = mtcars, # 全局数据
mapping = aes(
x = factor(gear), # 全局变量,规定横轴与纵轴基于的属性
y = mpg
)
) +
geom_violin(
mapping = aes(
fill = factor(gear) # 局部变量,规定小提琴图的填充颜色基于 gear
)
) +
geom_boxplot(width = 0.2) + # 局部常量,规定箱线图的宽度为 0.2,避免遮挡小提琴图
geom_jitter(width = 0.2) + # 局部常量,规定散点图的宽度为 0.2,避免超出箱线图范围
ggtitle( # 添加标题
"MPG ~ Gear"
) +
labs( # 添加坐标轴与图例标签
x = "Gear",
y = "MPG"
fill = "Gear"
) +
theme_classic() # 使用经典主题
panel