使用patchwork进行拼图的一些细节

2022-11-15 10:35:01 浏览数 (1)

说到拼图,那必须得好好学习patchwork包,这个包是Thomas大佬的作品,一经推出就火了,迅速取代了R中其他的拼图包。

大佬很强,除了这个包,还有很多好用的包都是他开发的,比如gganimate/ggraph/tidygraph/ggforce等,是不是也有一些你常用的包呢。

如果你仔细扒一扒,就会发现,我们常用的包,都是出自那几个人之手。。。

本期目录:

  • 安装
  • 拼图!
    • 简单拼图
    • 嵌套拼图
    • 和非`ggplot2`对象拼图
    • 堆叠和成组
    • 快速拼很多图
  • 修改子图形
    • 修改全部子图形
  • 控制整体布局
    • 增加空白占位图形
  • 控制行列数
  • 行列的精细控制
  • Fixed aspect plots
  • 插入图形
  • 控制图例
  • 标题、副标题和说明文字
  • 给子图添加序号
  • 修改整个图形的外观

安装

代码语言:javascript复制
# 2选1
install.packages('patchwork')

# install.packages("devtools")
devtools::install_github("thomasp85/patchwork")

拼图!

代码语言:javascript复制
library(patchwork)

首先创建一些基本图形用于演示。

代码语言:javascript复制
library(ggplot2)
p1 <- ggplot(mtcars)   
  geom_point(aes(mpg, disp))   
  ggtitle('Plot 1')

p2 <- ggplot(mtcars)   
  geom_boxplot(aes(gear, disp, group = gear))   
  ggtitle('Plot 2')

p3 <- ggplot(mtcars)   
  geom_point(aes(hp, wt, colour = mpg))   
  ggtitle('Plot 3')

p4 <- ggplot(mtcars)   
  geom_bar(aes(gear))   
  facet_wrap(~cyl)   
  ggtitle('Plot 4')

简单拼图

使用起来真的是简单,直接用 连起来就行了。

代码语言:javascript复制
p1   p2

plot of chunk unnamed-chunk-4

嵌套拼图

默认会在图形左边添加图形,所以如果你先把2个图拼一起,在和第3个图拼,就会像下面这样:

代码语言:javascript复制
patch <- p1   p2
p3   patch

plot of chunk unnamed-chunk-5

但是呢,也是可以调节的,简单换个顺序就不一样了:

代码语言:javascript复制
patch   p3

plot of chunk unnamed-chunk-6

这种情况下还可以用-,又是不一样的效果,处处都是细节!

代码语言:javascript复制
patch - p3

plot of chunk unnamed-chunk-7

和非ggplot2对象拼图

和表格拼:

代码语言:javascript复制
p1   gridExtra::tableGrob(mtcars[1:10, c('mpg', 'disp')])

plot of chunk unnamed-chunk-8

base plot拼图:

代码语言:javascript复制
p1   ~plot(mtcars$mpg, mtcars$disp, main = 'Plot 2')

plot of chunk unnamed-chunk-9

但是有个问题,拼出来对不齐,这时候就需要其他函数辅助对齐了:

代码语言:javascript复制
old_par <- par(mar = c(0, 2, 0, 0), bg = NA)
p1   wrap_elements(panel = ~plot(mtcars$mpg, mtcars$disp), clip = FALSE)

plot of chunk unnamed-chunk-10

代码语言:javascript复制
par(old_par)

文字大小还是不太一样,还可以继续调整:

代码语言:javascript复制
old_par <- par(mar = c(0, 0, 0, 0), mgp = c(1, 0.25, 0), 
               bg = NA, cex.axis = 0.75, las = 1, tcl = -0.25)
p1   
  wrap_elements(panel = ~plot(mtcars$mpg, mtcars$disp), clip = FALSE)   
  ggtitle('Plot 2')   
  theme(plot.margin = margin(5.5, 5.5, 5.5, 35))

plot of chunk unnamed-chunk-11

代码语言:javascript复制
par(old_par)

和文本内容也可以拼:

代码语言:javascript复制
p1   grid::textGrob('Some really important text')

plot of chunk unnamed-chunk-12

但是呢你要注意,如果你写反了,就拼不出来了:

代码语言:javascript复制
grid::textGrob('Text on left side')   p1
## NULL

如果你非要反着写,那要借助wrap_elements()才能显示出来:

代码语言:javascript复制
wrap_elements(grid::textGrob('Text on left side'))   p1

plot of chunk unnamed-chunk-14

堆叠和成组

  • 简单拼图
  • | 左右拼
  • / 上下拼
代码语言:javascript复制
p1 / p2

plot of chunk unnamed-chunk-15

代码语言:javascript复制
p1 | p2

plot of chunk unnamed-chunk-16

代码语言:javascript复制
p1 / (p2 | p3)

plot of chunk unnamed-chunk-17

快速拼很多图

如果你有多个图形组成的列表,再使用 可能就不太方便了,可以直接使用warap_plots()函数:

代码语言:javascript复制
wrap_plots(p1,p2,p3,p4)

plot of chunk unnamed-chunk-18

修改子图形

默认操作都会传给最后一个图形。

代码语言:javascript复制
p1   p2   geom_jitter(aes(gear, disp))

plot of chunk unnamed-chunk-19

可以通过修改子集的方式达到精准控制某个图形细节:

代码语言:javascript复制
patchwork <- p1   p2
patchwork[[1]] <- patchwork[[1]]   theme_minimal() # 修改第1个
patchwork

plot of chunk unnamed-chunk-20

修改全部子图形

  • &:将主题修改应用到所有子图形
  • *:将主题修改应用到当前嵌套水平的子图形
代码语言:javascript复制
patchwork <- p3 / (p1 | p2)
patchwork & theme_minimal()

plot of chunk unnamed-chunk-21

代码语言:javascript复制
patchwork * theme_minimal() 

plot of chunk unnamed-chunk-22

控制整体布局

还是先建立一些基本图形用于演示。

代码语言:javascript复制
library(ggplot2)
p1 <- ggplot(mtcars)   
  geom_point(aes(mpg, disp))   
  ggtitle('Plot 1')

p2 <- ggplot(mtcars)   
  geom_boxplot(aes(gear, disp, group = gear))   
  ggtitle('Plot 2')

p3 <- ggplot(mtcars)   
  geom_point(aes(hp, wt, colour = mpg))   
  ggtitle('Plot 3')

p4 <- ggplot(mtcars)   
  geom_bar(aes(gear))   
  facet_wrap(~cyl)   
  ggtitle('Plot 4')

增加空白占位图形

顾名思义,增加几个空白图形,纯属占位子用的。

代码语言:javascript复制
p1   plot_spacer()   p2   plot_spacer()   p3   plot_spacer()

plot of chunk unnamed-chunk-24

控制行列数

使用plot_layout()控制行列数:

代码语言:javascript复制
p1   p2   p3   p4   
  plot_layout(ncol = 3)

plot of chunk unnamed-chunk-25

还可以控制不同行列的高度和宽度:

代码语言:javascript复制
p1   p2   p3   p4   
  plot_layout(widths = c(2, 1),
              heights = c(3, 2)
              )

plot of chunk unnamed-chunk-26

行列的精细控制

使用自定义布局达到对行列数的精细控制。

代码语言:javascript复制
# 3行6列,4个图形,#表示占位符
layout <- "
##BBBB
AACCDD
##CCDD
"
p1   p2   p3   p4   
  plot_layout(design = layout)

plot of chunk unnamed-chunk-27

合理使用布局,可以达到叠加的效果:

代码语言:javascript复制
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)

plot of chunk unnamed-chunk-28

wrap_plots()也是可以用布局的:

代码语言:javascript复制
layout <- '
A#B
#C#
D#E
'
wrap_plots(D = p1, C = p2, B = p3, design = layout) # 指定字母代表的图形

plot of chunk unnamed-chunk-29

Fixed aspect plots

有些图形是固定好坐标轴比例的,这时候的拼图操作是不会影响原本比例的:

代码语言:javascript复制
p_fixed <- ggplot(mtcars)   
  geom_point(aes(hp, disp))   
  ggtitle('Plot F')   
  coord_fixed()
p_fixed   p1   p2   p3

plot of chunk unnamed-chunk-30

代码语言:javascript复制
p_fixed   p1   p2   p3   plot_layout(widths = 1) # 比例不变

plot of chunk unnamed-chunk-31

插入图形

把一张图插在另一张图上,使用insert_element()函数。

代码语言:javascript复制
p1   inset_element(p2, left = 0.6, bottom = 0.6, right = 1, top = 1)

plot of chunk unnamed-chunk-32

代码语言:javascript复制
p1   inset_element(p2, left = 0, bottom = 0.6, right = 0.4, top = 1, align_to = 'full')

plot of chunk unnamed-chunk-33

边距都是可以设置的:

代码语言:javascript复制
p1   inset_element(
  p2, 
  left = 0.5, 
  bottom = 0.5, 
  right = unit(1, 'npc') - unit(1, 'cm'), 
  top = unit(1, 'npc') - unit(1, 'cm')
)

plot of chunk unnamed-chunk-34

控制图例

对于多张图图例都一样时,可以直接用一个图例:

代码语言:javascript复制
p1   p2   p3   p4  
  plot_layout(guides = 'collect')

plot of chunk unnamed-chunk-35

合理使用括号改变组图顺序,达到把单个图例放到整张图右边的效果:

代码语言:javascript复制
# 默认就是auto
((p2 / p3   plot_layout(guides = 'auto')) | p1)   plot_layout(guides = 'collect')

plot of chunk unnamed-chunk-36

代码语言:javascript复制
# 改成keep
((p2 / p3   plot_layout(guides = 'keep')) | p1)   plot_layout(guides = 'collect')

plot of chunk unnamed-chunk-37

可以自动移除重复图例,比如正常的拼图会像下面这样,mpg这个图例出现了2次:

代码语言:javascript复制
p1a <- ggplot(mtcars)   
  geom_point(aes(mpg, disp, colour = mpg, size = wt))   
  ggtitle('Plot 1a')

p1a | (p2 / p3)

plot of chunk unnamed-chunk-38

使用guides = 'collect'后,重复图例被去掉了:

代码语言:javascript复制
(p1a | (p2 / p3))   plot_layout(guides = 'collect')

plot of chunk unnamed-chunk-39

还可以把图例单独放到一个子图中:

代码语言:javascript复制
p1   p2   p3   guide_area()   
  plot_layout(guides = 'collect')

plot of chunk unnamed-chunk-40

标题、副标题和说明文字

默认图形用于演示。

代码语言:javascript复制
library(ggplot2)
p1 <- ggplot(mtcars)   
  geom_point(aes(mpg, disp))   
  ggtitle('Plot 1')

p2 <- ggplot(mtcars)   
  geom_boxplot(aes(gear, disp, group = gear))   
  ggtitle('Plot 2')

p3 <- ggplot(mtcars)   
  geom_point(aes(hp, wt, colour = mpg))   
  ggtitle('Plot 3')

p4 <- ggplot(mtcars)   
  geom_bar(aes(gear))   
  facet_wrap(~cyl)   
  ggtitle('Plot 4')

使用plot_annotation()给整个图形添加标题、副标题和说明文字:

代码语言:javascript复制
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'
)

plot of chunk unnamed-chunk-42

给子图添加序号

用大写的A就是大写字母,用小写的a就是小写字母:

代码语言:javascript复制
patchwork   plot_annotation(tag_levels = 'A')

plot of chunk unnamed-chunk-43

当然外观是可以修改的:

代码语言:javascript复制
patchwork   
  plot_annotation(tag_levels = 'A') & 
  theme(plot.tag = element_text(size = 8))

plot of chunk unnamed-chunk-44

还可以在子图内部使用不同的序号:

代码语言:javascript复制
patchwork[[1]] <- patchwork[[1]]   plot_layout(tag_level = 'new')
patchwork   plot_annotation(tag_levels = c('A', '1'))

plot of chunk unnamed-chunk-45

在序号前添加前缀:

代码语言:javascript复制
patchwork   plot_annotation(tag_levels = c('A', '1'), tag_prefix = 'Fig. ',
                            tag_sep = '.', tag_suffix = ':')

plot of chunk unnamed-chunk-46

修改序号外观:

代码语言:javascript复制
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))

plot of chunk unnamed-chunk-47

使用自定义列表的形式为不同的子图安排不同的序号:

代码语言:javascript复制
patchwork   
  plot_annotation(tag_levels = list(c('#', '&'), '1'))

plot of chunk unnamed-chunk-48

修改整个图形的外观

代码语言:javascript复制
patchwork   
  plot_annotation(title = 'The surprising truth about mtcars') & 
  theme(text = element_text('mono'))

plot of chunk unnamed-chunk-49

代码语言:javascript复制
patchwork   
  plot_annotation(title = 'The surprising truth about mtcars',
                  theme = theme(plot.title = element_text(size = 18))) & 
  theme(text = element_text('mono'))

plot of chunk unnamed-chunk-50

0 人点赞