前言
上一節講的是如何使用 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'))