R 数据可视化 —— 图形排列之 patchwork
前言
上一节讲的是如何使用 cowplot
包来对图形进行排列对齐,今天要讲的是,如何使用 patchwork
包来排列图形
patchwork
包主要针对的是 ggplot2
图形,也可以是其他图像系统绘制的图形。
patchwork
以一种简单的方式对图形进行排列和组合,不论多复杂的组合图形,都能确保图形之间正确对齐
安装 patchwork
install.packages("patchwork")
导入相关模块
library(ggplot2)
library(patchwork)
示例
我们主要使用如下图形进行说明
p1 <- ggplot(mtcars) +
geom_point(aes(mpg, disp), colour = "#7fc97f") +
ggtitle('Plot 1')
p2 <- ggplot(mtcars) +
geom_boxplot(aes(gear, disp, fill = factor(gear)),
show.legend = FALSE) +
ggtitle('Plot 2')
p3 <- ggplot(mtcars) +
geom_point(aes(hp, wt, colour = mpg)) +
scale_colour_gradientn(
colours = c("#66c2a5", "#fc8d62", "#8da0cb")) +
ggtitle('Plot 3')
p4 <- ggplot(mtcars) +
geom_bar(aes(gear, fill = factor(gear)),
show.legend = FALSE) +
facet_wrap(~cyl) +
ggtitle('Plot 4')
1. 组合图形
1.1 添加图形
patchwork
使用 +
来连接两个图形
p1 + p2
拼凑多个图
patch <- p1 + p2
p3 + patch
1.1.1 添加非 ggplot 图形
有时候,你可能想要添加其他类型的图片,例如,grid
系统的图形对象
p1 + grid::textGrob('Some really important text')
或者,gridExtra
的 tableGrob
p1 + gridExtra::tableGrob(mtcars[1:10, c('mpg', 'disp')])
此外,对于 base
绘图系统,可以通过单边公式的方式来添加
p1 + ~plot(mtcars$mpg, mtcars$disp, main = 'Plot 2',
col = if_else(mtcars$disp > 250, "red", "green"))
我们可以看到,两幅图并没有对齐,要将 ggplot
图形和非 ggplot
图形对齐,需要使用 par()
函数来进行调整
非 ggplot
图形可以使用 wrap_elements()
函数来添加,可以进行更加灵活的控制,例如
old_par <- par(mar = c(0, 2, 0, 0), bg = NA)
p1 + wrap_elements(panel = ~plot(mtcars$mpg, mtcars$disp), clip = FALSE)
par(old_par)
如果你想将非 ggplot
图放在最前面,例如
> grid::textGrob('Text on left side') + p1
NULL
返回的是 NULL
,这时,也需要使用 wrap_elements()
函数
wrap_elements(grid::textGrob('Text on left side')) + p1
总的来说,对齐方式比 cowplot
更加复杂和繁琐
1.1.2 堆叠和包装
+
运算符只能简单地对图形进行组合,并不能提供任何布局信息,图片是以堆叠还是并列的方式排列。
因此,patchwork
提供了两个操作符:
|
:图形并列放置,即按行
/
:图形竖直堆叠,即按列
例如
p1 | p2
p1 / p2
这三个运算符的运算顺序与数学上一致,/
和 |
比 +
优先级更高,最好的方式是使用小括号来区分组合优先级,例如
p1 / (p2 | p3)
1.1.3 组合函数
当我们需要处理绘图函数列表时,使用 +
来添加图形会显得很笨拙,wrap_plots()
允许传入一个绘图列表,或者每个绘图以参数的形式分开传递
wrap_plots(p1, p2, p3, p4)
# 或者
wrap_plots(list(p1, p2, p3, p4))
1.2 左侧嵌套
上面的运算符都是将图形添加到左侧,例如
patch <- p1 + p2
p3 + patch
我们改变添加的顺序,图形会看起来不太一样
patch + p3
这两种方式有什么不一样呢?因为 +
是按顺序逐个添加的,+
的右侧图形需要与前面连接的图形串内的图形处于同一个嵌套级别。
对于 patch + p3
,相当于是 p1 + p2 + p3
,而对于 p3 + patch
,patch
与 p3
处于同一嵌套级别
patchwork
还提供了一个 -
操作符来处理这种情况,其作为连接符而不是减号,两边的图形处于同一嵌套级别
patch - p3
或者使用 wrap_plots()
,所有输入参数都处于同一级别
wrap_plots(patch, p3)
1.3 修改图形
在我们创建一个 patchwork
时,会返回最后一个添加的图形对象,我们可以继续添加 ggplot
图形对象
p1 + p2 + geom_jitter(aes(gear, disp))
如果想要修改其他图形,可以使用双中括号加索引的方式访问
patchwork <- p1 + p2
patchwork[[1]] <- patchwork[[1]] + theme_minimal()
patchwork
修改全部
有时,我们可能想对所有图形进行统一的修改,例如修改主题,patchwork
提供了两个操作符
-
&
:为所有子图添加元素 -
*
:为当前嵌套级别的所有子图添加元素
patchwork <- p3 / (p1 | p2)
patchwork & theme_minimal()
patchwork * theme_minimal()
2. 控制布局
虽然使用 +
、|
、/
操作符可以创建复杂的图形,但是还缺少一些更灵活的控制,下面我们介绍如何使用 plot_layout()
函数来进行更多的控制
2.1 添加空白占位
plot_spacer()
函数可以添加一个空白的区域,大小与同一嵌套级别的图形一样
p1 + plot_spacer() + p2 + plot_spacer() + p3 + plot_spacer()
不同的嵌套级别的占位大小不同
(p1 + plot_spacer() + p2) / (plot_spacer() + p3 + plot_spacer())
2.2 网格布局
如果没有给出任何布局信息,会尽可能将图形按照正方形网格进行排列,如果无法排列,则会使用启发式的方法自动调整。
我们可以使用 plot_layout()
来控制行列数量,每个网格具有相同的大小
p1 + p2 + p3 + p4 +
plot_layout(ncol = 3)
使用 widths
可以控制相对宽度比
p1 + p2 + p3 + p4 +
plot_layout(widths = c(2, 1))
或者使用绝对大小,使用 heights
设置第一行高度为 5cm
,第二行为剩下的区域
p1 + p2 + p3 + p4 +
plot_layout(widths = c(2, 1), heights = unit(c(5, 1), c('cm', 'null')))
2.3 非网格布局
对于非网格布局,你可能会想到使用嵌套的方式,但是这样容易让不同嵌套级别的图形很难再对齐。另一种方式是,通过设计自定义布局来排列图形。
有两种自定义布局的方式,最简单的就是使用文本表示,例如
layout <- "
##BBBB
AACCDD
##CCDD
"
p1 + p2 + p3 + p4 +
plot_layout(design = layout)
用 #
来表示空白区域,A-D
会根据添加的顺序自动对应到图形,也可以使用数字的方式
layout <- "
##2222
113344
##3344
"
一种更具编程性的方法是使用 area()
函数来构建布局
layout <- c(
area(t = 2, l = 1, b = 5, r = 4),
area(t = 1, l = 3, b = 3, r = 5)
)
p1 + p2 +
plot_layout(design = layout)
t
、l
、b
、r
分别指定了图形所占的上、左、下、右的网格,例如
layout <- c(
area(1, 1),
area(1, 3, 3),
area(3, 1, 3, 2)
)
plot(layout)
也可以使用 wrap_plots()
函数来绘制,如果使用文本表示的布局,可以传递命名图形的方式
layout <- '
A#B
#C#
D#E
'
wrap_plots(D = p1, C = p2, B = p3, design = layout)
2.4 固定纵横比图
当我们对具有固定纵横比的图图形(如 coord_fixed()
、coord_polar()
和 coord_sf()
创建的图形)进行组合时,由于 widths
和 heights
参数值默认设置为 NA
,自动调整图形的大小看起来会比较奇怪
p_fixed <- ggplot(mtcars) +
geom_point(aes(hp, disp)) +
ggtitle('Plot F') +
coord_fixed()
p_fixed + p1 + p2 + p3
我们可以为 widths
设置值
p_fixed + p1 + p2 + p3 + plot_layout(widths = 1)
虽然其他图片对齐的很好,但是固定纵横比的图片,为了维持大小比例,在某一方向上不再对齐了
2.5 嵌入图形
前面的例子中,我们使用 area()
函数在网格布局中将一个图形嵌入到另一个图形中,还可以使用 inset_element()
函数将一个图形或图形对象嵌入到前一个图形中,你可以将它放在前一个图形区域的任何位置。
例如
p1 + inset_element(p2, left = 0.6, bottom = 0.6, right = 1, top = 1)
p1 + inset_element(p2, left = 0, bottom = 0.6, right = 0.4, top = 1, align_to = 'full')
默认定位使用的数值的单位为 npc
,我们可以调整 1cm
的位置
p1 + inset_element(
p2,
left = 0.5,
bottom = 0.5,
right = unit(1, 'npc') - unit(1, 'cm'),
top = unit(1, 'npc') - unit(1, 'cm')
)
2.6 控制图例
通常,每幅图及其图例都是一个整体,我们可以使用 guides
参数来控制图例的显示方式,可选的值为
-
auto
:如果嵌套的上层尝试收集图例,则也会进行收集,否则,放置在图形边上 -
collect
:会将制定嵌套级别的图例收集起来,并删除重复的图例。还可以是keep
-
keep
:将图例放置在对应的图形边上
例如
p1 + p2 + p3 + p4 +
plot_layout(guides = 'collect')
((p2 / p3 + plot_layout(guides = 'keep')) | p1) + plot_layout(guides = 'collect')
对于存在重复图例的组合图形,我们可能想要删除其中某一个,例如,对于如下图形
p1a <- ggplot(mtcars) +
geom_point(aes(mpg, disp, colour = mpg, size = wt)) +
scale_colour_gradientn(colours = c("#66c2a5", "#fc8d62", "#8da0cb")) +
ggtitle('Plot 1a')
p1a | (p2 / p3)
设置 guides = 'collect'
(p1a | (p2 / p3)) + plot_layout(guides = 'collect')
我们还可以使用 guide_area()
来添加图例区域,其表现方式基本与 plot_spacer()
一样,如果没有设置 collect
形式,则与 plot_spacer()
一样,如果设置了,则会将所有图例绘制在该区域
p1 + p2 + p3 + guide_area() +
plot_layout(guides = 'collect')
3. 添加注释和样式
在组合完图形之后,通常也需要添加一些注释信息,像标题或其他文本注释。patchword
提供了 plot_annotation()
函数用于添加注释
patchwork <- (p1 + p2) / p3
patchwork + plot_annotation(
title = 'The surprising truth about mtcars',
subtitle = 'These 3 plots will reveal yet-untold secrets about our beloved data-set',
caption = 'Disclaimer: None of these plots are insightful'
)
tag_level
参数用于控制标签的格式,格式包括:
-
1
:阿拉伯数字 -
a
:小写字母 -
A
:大写字母 -
i
:小写罗马数字 -
I
:大写罗马数字
patchwork + plot_annotation(tag_levels = 'A')
可以使用 theme()
函数来设置标签的样式
patchwork +
plot_annotation(tag_levels = 'A') &
theme(plot.tag = element_text(size = 8))
如果组合图形是嵌套布局,则会递归的添加图形标签,可以设置多个标签样式
patchwork[[1]] <- patchwork[[1]] + plot_layout(tag_level = 'new')
patchwork + plot_annotation(tag_levels = c('A', '1'))
还可以设置标签的分隔符、前缀和后缀
patchwork + plot_annotation(
tag_levels = c('A', '1'),
tag_prefix = 'Fig. ',
tag_sep = '.',
tag_suffix = ':'
)
patchwork + plot_annotation(
tag_levels = c('A', '1'),
tag_prefix = 'Fig. ',
tag_sep = '.',
tag_suffix = ':'
) & theme(plot.tag.position = c(0, 1),
plot.tag = element_text(size = 8, hjust = 0, vjust = 0)
)
可以为标签设置序列,如果设置的序列不够,后面的图形标签会设置为空
patchwork +
plot_annotation(tag_levels = list(c('#', '%'), '1'))
其他样式都可以使用 theme()
函数来设置,比如,文本字体
patchwork +
plot_annotation(title = 'The surprising truth about mtcars',
theme = theme(plot.title = element_text(size = 18))) &
theme(text = element_text('mono'))