ggplot2介绍
图形语法
基于 Wilkinson (2005) 创造的图形语法 (The Grammar of Graphics), Wickham (2009) 创造了分层图形语法 (Layered Grammar of Graphics). 其定义一个 plot 的组成部分:
- 默认的数据集 (dataset) 和美学映射 (aesthetic mapping)
- 每个美学映射对应的标度 (scale)
- 一个或多个图层 (layer),每个图层包含一个几何对象 (geometric object), 一个统计变换 (statistical transformation), 一个位置调整 (position adjustment), 以及 (可能存在的) 各自的数据集和美学映射
- 坐标系 (coordinate system)
- 分面 (facet)
其中 aesthetic mapping 指定 dataset 中的变量在图中扮演的角色, 如位置, 颜色, 形状, 大小等. 而 scale 则决定如何将它们的值映射在图上, 如将位置值映射到对数坐标轴上, 将颜色值映射到某个调色盘上等. 最重要的是 layer, 其决定了图中元素的类型, 如点, 线等.
ggplot 示例
其实以 ggplot() 开头只是为了创建一个 ggplot 对象, 使得在其上可以进行自定义的 + 运算符. 在 ggplot2 >= 4.0.0 中, ggplot() 创建一个 S7 对象.
此外, ggplot() 也提供了一个全局设置的机会, 如设置默认的数据集和美学映射. 之后的每个图层都将继承这些设置.
Aesthetic Mapping
美学映射 (aesthetic mapping) 用于指定数据中的变量在图中对应什么, 如可以指定某列作为 x/y 轴坐标, 另一列作为点的颜色. 常用的包括:
- 位置 (x, y): 坐标轴上的位置
- 颜色 (color): 点/线的颜色
- 填充 (fill): 内部的填充色
- 形状 (shape): 形状
- 大小 (size): 大小
- 透明度 (alpha): 透明度
ggplot 将各种类型的图所需要的属性名称高度统一, 例如 x 和 y 总表示 x/y 轴坐标, color 总表示点/线的颜色, fill 总表示某种封闭结构内部的填充色. 这使得不同图层可以共用美学映射来简化代码.
ggplot 中的美学映射将 dataset 中的各列映射为某种视觉元素. 通常这个 dataset 是 tidy 的, 因此相当于是为其中每个 observation 指定不同的属性 (如之前为每个点按类别指定不同的颜色). 如果希望为所有 observation 指定同样的属性, 例如将所有点的颜色设置为红色, 可以直接在图层中直接指定一个常量值 (而不是在 aes() 中指定):
Layer
有时候, 我们需要在同一张图上绘制多个元素, 例如同时绘制点和线. ggplot 从中抽离出图层的概念.
ggplot 的一个图层基本相当于其他绘图系统中的一句绘图命令. 例如, 也可以在图层中指定数据集和美学映射:
这就相当于 base R 中的 plot(x = mtcars$wt, y = mtcars$mpg). 因此, 可以说图层是 ggplot 绘图的基本单位.
Geometric Object & Statistical Transformation
有些图需要从数据中计算出一些统计量, 例如箱线图需要计算出四分位数. 由于这种可能存在的计算是图层级别的 (只有箱线图需要计算分位数), ggplot 将这样的计算作为每个图层自己的特征.
ggplot 中的每个图层都由一个几何对象 (geometric object) 和一个统计变换 (statistical transformation) 构成. 其中几何对象表示该图层呈现数据的形式, 而统计变换则表示该图层需要进行的计算. 例如, 刚刚 geom_point() 的几何对象名为 "point" (画一些点), 而统计变换名为 "identity" (不进行任何计算); geom_boxplot() 的几何对象名为 "boxplot" (画一些箱子), 而统计变换名为 "boxplot" (计算分位数). 后者的几何对象和统计变换同名只是因为它们都是箱线图专用的.
图层中二者的地位是平等的, 因此创建一个图层有两种方式:
- 使用
geom_*()函数指定几何对象, 使用 stat 参数指定统计变换, 例如geom_point(),geom_line(),geom_boxplot(). - 使用
stat_*()函数指定统计变换, 使用 geom 参数指定几何对象, 例如stat_identity(),stat_boxplot().
ggplot 以默认参数的形式自动为 geom/stat 推断其缺失的另一半.
大多数时候使用 geom_*() 即可, 但 stat_*() 也有其用处, 比如我我们希望利用 geom_boxplot 计算出的分位数, 但将其展现为 errorbar 的形式. (geom_errorbar() 也是 ggplot2 提供的一种图层类型)
after_stat()
有时候, 我们希望利用统计变换计算出的值来绘图. 例如, 我们希望将概率密度图中的密度放缩到 [0, 1] 之间.
由于密度是 geom_density() 图层在自己的统计变换中计算的, 因此无法直接对其进行操作. 对于这种需求, ggplot 提供了 after_stat() 函数, 允许我们在图层中访问统计变换计算出的值. geom_density() 计算出的密度在内部被命名为 density, 因此我们可以通过 after_stat(density) 来访问它.
Scale
Aesthetic mapping 只是指定了 dataset 中各个变量对应图中哪个视觉元素, 而 scale 决定变量的值如何映射为图中展现的值.
对于 aes() 中指定的每个美学映射, 都需要有一个对应的 scale. 未指定时, ggplot 都会自动为其创建一个默认的 scale. 例如, 对于 aes(x = wt) (数值型变量), ggplot 会创建一个 scale_x_continuous() 来将 wt 的值映射到 x 轴上. 对于 aes(color = as.factor(cyl)) (因子型变量), ggplot 会创建一个 scale_color_discrete() 来将 cyl 的值映射到颜色上.
具体而言, scales 分为两大类:
- position scales
- continuous position scales
- discrete position scales
- date/time position scales
- other Scales
- color scales
- continuous color scales
- discrete color scales
- size scales
- shape scales
- fill scales
- alpha scales
- …
- color scales
Position Scales
position scales 包括
- continuous position scales: scale_x_continuous(), scale_y_continuous()
- discrete position scales: scale_x_discrete(), scale_y_discrete()
其中 x/y 仅仅表示该 scale 作用于 x/y 轴, 用法相同. 而 continuous/discrete 则表示该 scale 作用于连续/离散变量, 用法 (参数) 略有不同. 下介绍一些常用参数.
数据范围
- limits: 控制坐标轴显示的数据范围,通常接受一个长度为 2 的向量
- expand: 控制坐标轴两端与数据边际之间的留白
由于
- 超过 limits 的数据点会被设为 NA
- expand 的计算依赖于数据范围
因此这两个设置都可以视为对”如何将数据映射/展示到坐标轴上”的调整, 而不是对坐标轴本身的调整. 这也是它们被归类在 position scales 中的原因.
刻度标签
- breaks/minor_breaks: 控制主/次刻度, 接受一个数值向量或一个函数
- labels: 控制刻度标签, 接受一个字符向量或一个函数
其被归为 position scales 的原因是它们是对数据范围的一种视觉辅助, 依旧属于”如何将数据映射/展示到坐标轴上”的范畴.
轴变换
- trans: 对坐标轴进行数学变换,接受一个字符串 (如 “log10”) 或一个 transformation object (如
scales::log_trans())
显然轴变换也是对”如何将数据映射/展示到坐标轴上”的调整, 因此被归为 position scales.
双轴图
scale_x_continuous()/scale_y_continuous() 的一个特殊参数是 sec.axis, 用于创建双轴图.
Hadley Wickham 本人并不提倡使用双轴图 (如双 y 轴图), 详见 stackoverflow 上的回答. 因此在很长一段时间内 ggplot2 中完全无法创建双轴图. 直到 ggplot2 2.2.0 中引入了 sec.axis 参数才打破这一限制.
尽管 ggplot2 向用户需求妥协了, 但其双轴图依然实现得极为别扭, 需要用户手动进行各种变换来使得两个轴的刻度对齐. 因此, 推荐使用 ggh4x 包提供的 help_secondary() 函数实现双轴图.
Other Scales
常见的 mapping 中除了位置 (x/y) 以外, 还有颜色 (color), 填充 (fill), 形状 (shape), 大小 (size), 透明度 (alpha) 等. 这里我们以 color scale 为例介绍.
color scale 也包括 continuous color scale 和 discrete color scale 两种类型, 分别对应 scale_color_continuous() 和 scale_color_discrete().
与 position scales 相比, 其只是表现形式不同 (在图上成为图例而不是坐标轴) 但本质都是以”刻度”的形式展示数据值, 因此其参数也大同小异.
例如, 其也有 limits, breaks, labels 等参数, 用法与 position scales 中的相同.
color scale 接受一个特殊参数: palette (调色盘)
Guides
scales 将变量的值展示在图上时, 根据对应 mapping 的不同, 可能成为坐标轴 (position scales) 或图例等 (other scales). 这些统称为 guides.
从哲学角度看, guides 可以视作是 scales 的逆映射, 其将图上的坐标轴或图例 (通过人眼) 映射回变量的值. 如, 在 position scales 中, guides 将坐标轴上的刻度标签映射回数据中的数值; 在 color scales 中, guides 将图例中的颜色映射回数据中的类别.
可以在设置 scale 的时候传入 guide 参数来调整其外观, 如
除了 guide_axis() 和 guide_legend() 以外, ggplot 还提供了 guide_colorbar() 对应 continuous color scales, guide_none() 对应不显示 guides, 等等.
labs() & lims()
在对 scale 的控制中, 常见的需求是控制标题 (轴标题/图例标题) 和控制范围 (x/y轴范围). ggplot 为此提供了 labs() 和 lims() 函数.
以下是一个 labs() 的使用示例. 注意其也可以控制图的标题.
lims() 的使用方法类似. 注意其也会将超出范围的值变为 NA.
after_scale()
与 layer 的统计变换会在内部计算出一些值一样, scale 也会在内部计算出一些值, 如 color scale 会计算出每个颜色对应的 RGB 值.
有时我们希望使用这些值, 如在密度图中将填充色设为淡化的轮廓颜色.
与 after_stat() 类似, ggplot 提供了 after_scale() 函数来访问这些值.
Coordinate System
position scales 只负责将变量值映射到坐标轴上. 因此, 其不能控制坐标轴在图上的摆放方式.
ggplot 提供了坐标系以完成这个任务. 坐标系分为两大类
- linear coordinate systems
coord_cartesian()coord_flip()coord_fixed()
- non-linear coordinate systems
coord_polar()coord_trans()coord_map()/coord_quickmap()/coord_sf()
这里我们仅介绍两种最常用的坐标系: coord_cartesian() 和 coord_polar()
coord_cartesian()
coord_cartesian() (笛卡尔坐标系) 是 ggplot 的默认坐标系. 对于没有显式指定坐标系的图, ggplot 会自动添加一个 coord_cartesian().
ggplot 提供了一系列参数以对 coord_cartesian() 进行微调
注意这里的 xlim/ylim 与 scale_x_continuous()/scale_y_continuous() 中不同. 前者只是调整坐标轴显示的范围, 不会丢弃超出范围的数据, 后者则会将超出范围的数据设为 NA.
coord_polar()
coord_polar() (极坐标系) 将笛卡尔坐标系转换为极坐标系.
coord_polar() 最常见的还是创建饼图. 其由堆叠条形图变化而来.
Facet
除了将类别变量展示为颜色/填充/形状外, 还可以根据分类变量的值将图分为多个小块. 分面提供了这样的功能.
上述例子中, facet_wrap(~ cyl) 根据 cyl 的值将图分为三个小块, 分别展示 cyl = 4, cyl = 6, cyl = 8 的数据.
ggplot 还提供了 facet_grid() 来同时根据两个分类变量进行分面.
没有指定分面时, ggplot 自动添加 facet_null() (不分面). facet 也是 ggplot 图的一个必要组成部分.
facet_*() 中最重要的参数是 scales, 用于控制所有面板中位置刻度是否统一
Theme
theme() 用于设置 ggplot 的整体外观. ggplot 的理念是, 先使用前述的声明式语法 (declarative syntax) 来指定数据该如何展示在图中, 再使用 theme 系统控制图中每个部分如何渲染其外观.
theme() 的参数非常多, 可以控制图中几乎每个部分的外观. 详见其文档.
除了使用 theme() 来单独设置每个部分的外观以外, ggplot 还提供了一些预设的主题函数, 如 theme_bw(), theme_minimal(), theme_classic() 等.
拼图与保存
ggsave()
要将 ggplot2 图片保存到文件, 可以使用 ggsave() 函数
patchwork
有时候, 我们需要将多个不同的 ggplot2 图拼成一个大图, 此时可以使用 patchwork 包. 其基本用法如下
patchwork 继承了 ggplot2 的设计哲学, 可以直接将 ggplot 对象相加来拼图
如果希望其竖向拼接, 可以使用 / 而不是 +:
与 ggplot2 一样, patchwork 拼成的图也可以使用 ggsave() 保存.
plot_layout()
可以添加一个 plot_layout() 来控制如何放置子图.
plot_layout() 的另一个实用参数是 guides:
plot_annotation()
plot_annotation() 可用于控制拼图的标题, 编号等:
Cheatsheet
练习
练习 1
使用 ggplot2::mpg 完成以下任务:
- 绘制
displ与hwy的散点图 - 将
drv映射到颜色 - 设置点的透明度为 0.7
- 添加一条线性拟合曲线,不显示置信区间
- 设置 x 轴刻度为 2 到 7
- 设置标题、副标题和坐标轴标签
- 使用
theme_minimal()
练习 2
继续使用 ggplot2::mpg 完成以下任务:
- 绘制
class的柱状图 - 将
drv映射到填充颜色 - 使用
position = "fill"展示各class中不同drv的比例 - 将 y 轴标签显示为百分比
- 翻转坐标轴
- 将图例放到底部,并设置图例标题
- 使用
theme_bw()
练习 3
使用 ggplot2::diamonds 完成以下任务:
- 绘制
carat的直方图 - 使用
after_stat(density)将 y 轴改为密度 - 叠加一条密度曲线
- 按
cut分面 - 仅显示
carat在 0 到 3 之间的部分 - 使用
theme_classic() - 设置标题和 x / y 轴标签