如何使用 patchwork 包

1. patchwork 包简介
patchwork
是一个由 Thomas Lin Pedersen 开发的 R 包,旨在极大地简化组合多个 ggplot
对象的过程。它的核心理念是让图表组合变得像数学运算一样直观。你不再需要学习复杂的 grid
或 cowplot
函数参数,只需使用 +
, /
, |
等操作符即可。
最重要的是: patchwork
包自动让图片的边缘对齐,尤其是 ggplot2 制作的图。cowplot
, ggarrage
, grid.arrange
这些功能都有可能会出现图片错位。
2. 安装
如果你还没有安装 patchwork
,可以通过 CRAN 进行安装:
shell
# install.packages("patchwork")
# install.packages("ggplot2") # patchwork 通常与 ggplot2 一起使用
3. 核心操作符
patchwork
最强大的地方在于其直观的操作符。
3.1 +
(并排或堆叠)
+
操作符是 patchwork
的基础。当单独使用时,它会尝试智能地将图表并排放置。结合 plot_layout()
可以更精确地控制布局。
bash
# 示例1: 两个图并排 (默认行为)
p1 + p2

bash
# 注释:
# p1: 第一个ggplot对象
# p2: 第二个ggplot对象
# + : patchwork操作符,将p1和p2组合起来。
# 默认情况下,两个图会尝试水平排列。
3.2 /
(垂直堆叠)
/
操作符用于将图表垂直堆叠。
bash
# 示例: 两个图垂直堆叠
p1 / p2

bash
# 注释:
# p1: 第一个ggplot对象
# p2: 第二个ggplot对象
# / : patchwork操作符,将p1垂直堆叠在p2之上。
3.3 |
(并排,更明确)
|
操作符也用于将图表并排放置,有时比 +
更能清晰地表达水平排列的意图,尤其是在更复杂的组合中。
bash
# 示例: 两个图并排
p1 | p2

shell
# 注释:
# p1: 第一个ggplot对象
# p2: 第二个ggplot对象
# | : patchwork操作符,将p1和p2水平并排放置。
# 效果与单独使用 '+' 类似,但在复杂布局中可增加可读性。
3.4 组合操作符与括号
操作符可以像数学表达式一样组合使用,并使用括号 ()
控制运算优先级。
bash
# 示例: 复杂布局
# (p1 | p2) 在顶部,p3 在底部横跨整个宽度
(p1 | p2) / p3

bash
# 注释:
# (p1 | p2): p1和p2首先水平并排。括号确保这个组合先发生。
# / p3: 然后将上面的组合 (p1|p2) 作为一个整体,堆叠在p3的上面。
bash
# 示例: 左侧 p1,右侧 p2 堆叠在 p3 上方
p1 | (p2 / p3)

shell
# 注释:
# (p2 / p3): p2垂直堆叠在p3之上。括号确保这个组合先发生。
# p1 | ...: 然后将p1水平放置在 (p2/p3) 组合的左侧。
4. plot_layout()
函数
plot_layout()
是 patchwork
中用于精细控制组合图布局的核心函数。它可以作为 +
操作符的最后一个元素添加到图表组合中。
4.1 plot_layout()
参数详解
shell
# 函数原型 (主要参数):
# plot_layout(
# ncol = NULL, # 列数
# nrow = NULL, # 行数
# byrow = TRUE, # 是否按行填充
# widths = NULL, # 各列的相对宽度
# heights = NULL, # 各行的相对高度
# guides = "collect", # 图例处理方式 ('collect', 'keep', 'auto')
# tag_level = NULL, # 配合 plot_annotation() 函数进行多层标签嵌套
# design = NULL # 使用文本设计布局
# )
4.1.1 ncol
和 nrow
-
ncol
: 指定布局的列数。- 类型 : 整数 (e.g.,
1
,2
,3
). - 取值 : 大于 0 的整数。如果同时指定
ncol
和nrow
,patchwork
会尝试满足两者,但通常只指定一个即可。
- 类型 : 整数 (e.g.,
-
nrow
: 指定布局的行数。- 类型 : 整数 (e.g.,
1
,2
,3
). - 取值: 大于 0 的整数。
- 类型 : 整数 (e.g.,
ini
# 示例1: 强制两列布局
p1 + p2 + p3 + plot_layout(ncol = 2)

bash
# 注释:
# p1 + p2 + p3: 组合三个图。
# plot_layout(ncol = 2): 指定这三个图按两列排列。
# 默认按行填充 (byrow = TRUE),所以p1, p2在第一行,p3在第二行左侧。
ini
# 示例2: 强制一行布局
p1 + p2 + p3 + plot_layout(nrow = 1)

bash
# 注释:
# plot_layout(nrow = 1): 指定这三个图按一行排列。
ini
# 示例3: 强制三行布局
p1 + p2 + p3 + plot_layout(nrow = 3)

bash
# 注释:
# plot_layout(nrow = 3): 指定这三个图按三行排列。
4.1.2 byrow
-
byrow
: 决定图表是按行填充还是按列填充网格。-
类型 : 逻辑值 (
TRUE
或FALSE
). -
取值:
TRUE
(默认): 图表按行填充。FALSE
: 图表按列填充。
-
ini
# 示例: 按列填充
p1 + p2 + p3 + p4 + plot_layout(ncol = 2, byrow = FALSE)

bash
# 注释:
# plot_layout(ncol = 2, byrow = FALSE):
# 指定两列布局,并且图表按列填充。
# 所以p1, p2在第一列,p3, p4在第二列。
4.1.3 widths
和 heights
-
widths
: 控制各列的相对宽度。- 类型: 数值向量。
- 取值 : 例如
c(1, 2)
表示第二列的宽度是第一列的两倍。向量长度应与列数匹配。也可以使用grid::unit
对象 (e.g.,unit(1, "cm")
)。
-
heights
: 控制各行的相对高度。- 类型: 数值向量。
- 取值 : 例如
c(1, 2)
表示第二行的高度是第一行的两倍。向量长度应与行数匹配。也可以使用grid::unit
对象。
ini
# 示例1: 控制列宽
(p1 | p2) + plot_layout(widths = c(2, 1))

bash
# 注释:
# plot_layout(widths = c(2, 1)):
# p1 (第一列) 的宽度是 p2 (第二列) 的两倍。
ini
# 示例2: 控制行高
(p1 / p2) + plot_layout(heights = c(1, 2))

bash
# 注释:
# plot_layout(heights = c(1, 2)):
# p2 (第二行) 的高度是 p1 (第一行) 的两倍。
css
# 示例3: 结合 ncol/nrow 控制更复杂布局的宽高
p1 + p2 + p3 + p4 + plot_layout(ncol = 2, widths = c(1,2), heights = c(2,1))

bash
# 注释:
# ncol = 2: 两列布局。
# widths = c(1,2): 第一列宽度为1份,第二列宽度为2份。
# heights = c(2,1): 第一行高度为2份,第二行高度为1份。
# (p1, p2) 在第一行, (p3, p4) 在第二行。
# p1宽1高2, p2宽2高2
# p3宽1高1, p4宽2高1
4.1.4 guides
-
guides
: 控制组合图中图例的收集与显示方式。-
类型: 字符。
-
取值:
'collect'
(默认): 收集所有图例,如果图例相同则合并,并放置在组合图的整体区域。'keep'
: 保留每个子图各自的图例,不进行收集或合并。'auto'
: 与'collect'
类似,但如果所有图都没有图例,则不会占用图例空间。
-
scss
# 准备两个带相似图例的图
plot_a <- ggplot(mpg, aes(displ, hwy, colour = class)) + geom_point() + ggtitle("Plot A")
plot_b <- ggplot(mpg, aes(cty, hwy, colour = class)) + geom_point() + ggtitle("Plot B")
plot_c <- ggplot(mpg, aes(cty, displ, colour = drv)) + geom_point() + ggtitle("Plot C (different legend)")
# 示例1: 'collect' (默认)
(plot_a + plot_b) + plot_layout(guides = 'collect')

shell
# 注释:
# plot_layout(guides = 'collect'):
# plot_a 和 plot_b 有相同的图例 (基于 'class'),它们会被合并为一个图例。
ini
# 示例2: 'collect' 不同图例
(plot_a + plot_c) + plot_layout(guides = 'collect')

bash
# 注释:
# 不同的图例会被收集并并排放置。
ini
# 示例3: 'keep'
(plot_a + plot_b) + plot_layout(guides = 'keep')

shell
# 注释:
# plot_layout(guides = 'keep'):
# plot_a 和 plot_b 各自保留自己的图例。
ini
# 示例4: 'collect' 多个图例
(p_legend | p_legend2) + p2 + plot_layout(guides = 'collect')

bash
# 注释:
# p_legend 和 p_legend2 的图例会被收集,p2没有图例。
# 最终的图例区域会显示两个图例。
ini
# 示例5: 'collect' 结合图例位置 (通过主题)
(plot_a + plot_b) +
plot_layout(guides = 'collect') &
theme(legend.position = 'bottom')

shell
# 注释:
# '&' 操作符用于将主题应用到所有子图中,或者如果用在 plot_layout 之后,
# 则应用于收集的图例所在的层级。
# 这里 theme(legend.position = 'bottom') 会将收集到的图例放在底部。
注意 : &
操作符用于将后续的 theme()
或其他 ggplot2
修改递归地应用到组合中的所有子图。当与 guides = 'collect'
结合使用时,如果 theme()
是在 plot_layout()
之后通过 &
添加的,它会影响整个拼凑图的图例区域。
4.1.5 design
: 真 TMD 是个天才设计
-
design
: 允许使用 ASCII art (文本) 来定义复杂的、非网格状的布局。-
类型: 字符。
-
取值: 一个多行字符串,其中:
- 不同的字母代表不同的图。图按添加到
patchwork
对象的顺序(从左到右,从上到下)映射到设计中的字母(按字母顺序)。 - 相同的字母在设计中多次出现,表示该图占据这些单元格。
#
表示一个空单元格或占位符。area(t, l, b, r)
函数可以与design
一起使用,以更编程的方式定义区域。t,b
:区域的上下边;l,r
:区域的左右边。
- 不同的字母代表不同的图。图按添加到
-
css
# 示例1: 基本的 design 布局
design <- "
AAB
AAC
DDC
"
# 或者 design <- c("AAB", "AAC", "DDC")
# 或者使用 patchwork::area
# design <- rbind(
# c(area(1,1,2,2, name="A"), area(1,3,1,3, name="B")),
# c( area(2,3,3,3, name="C")),
# c(area(3,1,3,2, name="D"))
# )
# 注意: 这里的图p1, p2, p3, p4将按顺序映射到 A, B, C, D
p1 + p2 + p3 + p4 + plot_layout(design = design)

bash
# 注释:
# plot_layout(design = design):
# A -> p1 (占据左上角2x1区域)
# B -> p2 (占据右上角1x1区域)
# C -> p3 (占据右侧,从第二行开始的2x1区域)
# D -> p4 (占据左下角1x2区域)
ini
# 示例2: 使用 '#' 作为空单元格
design_with_empty <- "
A#B
CCC
"
p1 + p2 + p3 + plot_layout(design = design_with_empty)

bash
# 注释:
# A -> p1
# B -> p2 (注意中间有个空位)
# C -> p3
ini
# 示例3: 使用 area() 函数定义设计 (更灵活)
# 假设我们只有三个图 p1, p2, p3
# p1 在 (1,1) 到 (2,2)
# p2 在 (1,3) 到 (1,3)
# p3 在 (2,3) 到 (2,3)
design_area <- "
AAX
AAY
"
# 这里 X 对应 p2, Y 对应 p3
p1 + p2 + p3 + plot_layout(design = design_area)

ini
# 使用 `area()` 助手函数可以更清晰地定义非矩形或特定命名的区域
# (虽然上面的design字符串通常更直观)
layout <- c(
area(t = 1, l = 1, b = 2, r = 2), # p1
area(t = 1, l = 3, b = 1, r = 3), # p2
area(t = 2, l = 3, b = 2, r = 3) # p3
)
p1 + p2 + p3 + plot_layout(design = layout)
shell
# 更复杂的例子,用 area() 指定命名区域,然后用 design 字符串引用这些命名区域
layout_spec <- "
title title
plotA plotB
"
# 我们需要 plot_spacer() 或文本Grob来填充 'title'
# 为了简单起见,这里假设你有 plotA 和 plotB
# p1 + p2 + plot_layout(design = layout_spec) # 这需要你有与区域名称匹配的图
# design 更适合于基于字母索引的布局。
5. plot_annotation()
函数
plot_annotation()
用于给整个组合图添加统一的标题、副标题、说明文字以及自动标记(如 A, B, C)。
5.1 plot_annotation()
参数详解
shell
# 函数原型 (主要参数):
# plot_annotation(
# title = NULL, # 整体标题
# subtitle = NULL, # 整体副标题
# caption = NULL, # 整体说明文字
# tag_levels = NULL, # 自动标记的样式 ('A', 'a', '1', 'I', 'i', 或包含这些的列表)
# tag_prefix = NULL, # 标记前缀
# tag_suffix = NULL, # 标记后缀
# tag_sep = NULL, # 标记与子图标题间的分隔符
# theme = NULL # 应用于注解层的主题
# )
5.1.1 title
, subtitle
, caption
-
title
: 组合图的整体主标题。- 类型: 字符串。
-
subtitle
: 组合图的整体副标题。- 类型: 字符串。
-
caption
: 组合图的整体说明文字(通常在底部)。- 类型: 字符串。
ini
# 示例: 添加整体标题、副标题和说明
(p1 + p2) / p3 +
plot_annotation(
title = "MTCars 数据集探索性分析",
subtitle = "展示了不同变量间的关系",
caption = "数据来源: R内置mtcars数据集"
)

bash
# 注释:
# title, subtitle, caption 参数分别设置了整个组合图的标题、副标题和底部说明。
5.1.2 tag_levels
-
tag_levels
: 定义子图自动标记的样式和层级。-
类型: 字符或列表。
-
取值:
- 单个字符:
'A'
(A, B, C...),'a'
(a, b, c...),'1'
(1, 2, 3...),'I'
(I, II, III...),'i'
(i, ii, iii...). NULL
(默认): 不添加自动标记。- 字符向量: 例如
c('A', '1')
可以用于嵌套标记,第一层用A
,B
, ...,第二层用1
,2
, ... (当与嵌套 patchwork 对象结合时)。通常用于单层标记。
- 单个字符:
-
ini
# 示例1: 使用大写字母标记
(p1 + p2) / p3 +
plot_annotation(tag_levels = 'A')

less
# 注释:
# tag_levels = 'A': 子图会被自动标记为 A, B, C。
ini
# 示例2: 使用小写罗马数字标记
p1 + p2 + plot_layout(ncol=1) +
plot_annotation(tag_levels = 'i')

shell
# 注释:
# tag_levels = 'i': 子图会被自动标记为 i, ii。
ini
# 示例3: 嵌套标记 (更高级用法,通常与手动嵌套的patchwork对象结合)
# patch1 <- p1 + p2
# patch2 <- p3 + p4
# (patch1 / patch2) + plot_annotation(tag_levels = c('A', '1'))
# 这会给 patch1 和 patch2 标记为 A, B
# 然后 patch1 内的 p1, p2 会被标记为 1, 2,同样 patch2 内的 p3, p4 也会被标记为 1, 2
# (需要更复杂的结构来展示嵌套tag_levels的真正威力)
# 简单演示单层标记的tag_levels列表 (通常你只会用一个字符)
# 如果你给一个非嵌套列表,它通常只会用第一个元素
(p1 + p2) + plot_annotation(tag_levels = list('A')) # 等同于 tag_levels = 'A'

5.1.3 tag_prefix
, tag_suffix
, tag_sep
-
tag_prefix
: 添加到每个标记之前的前缀。- 类型: 字符串。
- 取值 : 例如
"(", "图 "
.
-
tag_suffix
: 添加到每个标记之后的后缀。- 类型: 字符串。
- 取值 : 例如
")", ":"
.
-
tag_sep
: 标记与子图标题之间的分隔符 (如果子图有标题)。- 类型: 字符串。
- 取值 : 例如
": "
,". "
.
ini
# 示例: 自定义标记样式
(p1 + p2) +
plot_annotation(
tag_levels = 'A',
tag_prefix = "图 ",
tag_suffix = ":",
caption = "自定义标记示例"
)

shell
# 注释:
# tag_prefix = "图 ": 标记前加 "图 ",如 "图 A"。
# tag_suffix = ":": 标记后加 ":",如 "图 A:"。
# 如果子图本身有标题 (如p1的"图1: MPG vs Displacement"),
# 自动标记会尝试替换或添加到子图标题前。
# Patchwork的标记行为会优先考虑子图已有的 ggtitle。
# 如果想让标记独立于子图标题,通常将子图的 ggtitle 清空,
# 或者使用 tag_sep (但这更多是针对将 tag 附加到现有标题的情况)。
ini
# 为了更好地演示 tag_sep,我们移除子图的 ggtitle
p1_no_title <- p1 + ggtitle(NULL)
p2_no_title <- p2 + ggtitle(NULL)
(p1_no_title + p2_no_title) +
plot_annotation(
tag_levels = '1',
tag_prefix = 'Fig. ',
tag_suffix = ')',
# tag_sep = '. ' # 当子图无标题时,tag_sep效果不明显
title = "子图无原生标题时的标记"
)

shell
# 注释: 标记将是 "Fig. 1)" 和 "Fig. 2)"
# tag_sep 主要用于 tag 和子图自身标题的结合,patchwork 的tag默认添加到左上角。
关于标记和子图标题的重要说明 : patchwork
的自动标记默认放置在每个子图的左上角。如果子图已经通过 ggtitle()
设置了标题,标记会出现在标题的左侧或上方,具体行为可能因版本和上下文略有不同。tag_sep
的设计初衷是处理标记和子图标题之间的关系,但现代 patchwork
版本通常将标记视为独立的元素。
5.1.4 theme
-
theme
: 一个ggplot2::theme()
对象,用于自定义plot_annotation
添加的文本元素(标题、副标题、说明)的样式。- 类型 :
theme
对象。 - 取值 : 例如
theme(plot.title = element_text(size = 20, hjust = 0.5))
.
- 类型 :
ini
# 示例: 自定义注解文本的样式
(p1 + p2) +
plot_annotation(
title = "主标题样式自定义",
subtitle = "副标题也变大了",
caption = "说明文字居中并用斜体",
tag_levels = 'A',
theme = theme(
plot.title = element_text(size = 22, face = "bold", color = "darkblue", hjust = 0.5),
plot.subtitle = element_text(size = 16, color = "blue", hjust = 0.5),
plot.caption = element_text(size = 10, face = "italic", color = "grey40", hjust = 0.5),
plot.tag = element_text(size = 14, face = "bold.italic", color="red")
)
)

bash
# 注释:
# theme(...) 参数允许我们精细控制 plot_annotation 添加的文本元素的视觉样式。
# plot.title, plot.subtitle, plot.caption, plot.tag 分别对应主标题、副标题、说明和标签的样式。
# hjust = 0.5 用于居中对齐。
6. plot_spacer()
函数
plot_spacer()
用于在布局中创建一个空白占位符。这在你想在网格中留出空位时非常有用。
css
# 示例: 使用 plot_spacer() 创建空位
(p1 + plot_spacer() + p2) / (plot_spacer() + p3 + plot_spacer()) +
plot_layout(widths = c(1,0.5,1), heights = c(1,1))

bash
# 注释:
# 第一行: p1, 空白, p2。通过 plot_layout 控制了中间空白区域的相对宽度较小。
# 第二行: 空白, p3, 空白。
# plot_spacer() 创建了一个不显示任何内容的图块。
ini
# 配合 design 布局也很有用
design_with_spacer <- "
A#B
#C#
"
p1 + p2 + p3 + plot_layout(design = design_with_spacer,
# A, B, C 对应 p1, p2, p3
# # 区域会自动由 plot_spacer() 填充 (如果 plot 数量不足以填充 design)
# 或者可以显式传递
# p1 + plot_spacer() + p2 + plot_spacer() + p3 + plot_spacer() +
# plot_layout(design = design_with_spacer)
# 但更常见的是让 patchwork 自动处理不足的图,或者仅在需要特定空位时插入
)

scss
# 更明确的用法
p1 + plot_spacer() + p2 + plot_spacer() + p3 + plot_spacer() +
plot_layout(design = "AB\nCD\nEF") # 这里A=p1, B=spacer, C=p2, D=spacer, E=p3, F=spacer

注意 : 在 design
布局中,如果提供的图少于设计中的字母区域,patchwork
会自动用空白填充。#
字符是显式定义空白区域的方式。plot_spacer()
允许你将空白区域像普通图一样插入到 +
, /
, |
的序列中。
7. wrap_plots()
函数
wrap_plots()
是一个便捷函数,用于收集一系列图(可以直接作为参数传递,或以列表形式传递)并应用统一的布局。它实质上是 Reduce( patchwork:::
+, list_of_plots ) + plot_layout(...)
的一个快捷方式。
7.1 wrap_plots()
参数详解
shell
# 函数原型 (主要参数):
# wrap_plots(
# ..., # ggplot对象,或包含ggplot对象的列表
# plots = NULL, # 一个包含ggplot对象的列表 (替代...)
# ncol = NULL, # 列数
# nrow = NULL, # 行数
# byrow = NULL, # 是否按行填充 (默认TRUE)
# widths = NULL, # 各列的相对宽度
# heights = NULL, # 各行的相对高度
# guides = NULL, # 图例处理方式 ('collect', 'keep', 'auto')
# tag_level = NULL, # (已弃用)
# design = NULL # 使用文本设计布局
# # collect_guides = NULL # (旧参数,现在统一用 guides)
# )
可以看到,wrap_plots()
的布局参数与 plot_layout()
非常相似。
ini
# 示例1: 直接传递图对象
wrap_plots(p1, p2, p3, p4, ncol = 2)

bash
# 注释:
# p1, p2, p3, p4: 作为独立参数传递。
# ncol = 2: 指定两列布局。
ini
# 示例2: 使用列表传递图对象
plot_list <- list(p1, p2, p3, p4)
wrap_plots(plots = plot_list, nrow = 2, guides = 'collect')

shell
# 注释:
# plots = plot_list: 将包含图的列表传递给 plots 参数。
# nrow = 1: 指定单行布局。
# guides = 'collect': 收集图例 (虽然这些示例图的图例各不相同或没有)。
ini
# 示例3: 结合不同图例的收集
plot_list_legends <- list(p_legend, p_legend2, p2) # p2没有图例
wrap_plots(plots = plot_list_legends, ncol = 1, guides = 'collect') &
theme(legend.position = 'bottom')

shell
# 注释:
# guides = 'collect': 收集 p_legend 和 p_legend2 的图例。
# & theme(...): 将收集到的图例统一放置在底部。
ini
# 示例4: 使用 design 参数
design_for_wrap <- "
AABB
CCDD
"
# wrap_plots 会按顺序将列表中的图映射到 design 中的 A, B, C, D
wrap_plots(p1, p2, p3, p4, design = design_for_wrap)

rust
# 注释:
# A -> p1, B -> p2, C -> p3, D -> p4
# p1, p2, p3, p4 会按其在 design 中的区域形状进行排列。
ini
# 示例5: 控制宽度
wrap_plots(p1, p2, ncol = 2, widths = c(2,1))

bash
# 注释:
# p1的宽度是p2的两倍。
8. 嵌套组合 (Nesting)
patchwork
允许将一个组合图本身作为另一个组合图的一部分,从而实现复杂的嵌套布局。这通常通过括号 ()
来实现。
r
# 示例1: 简单的嵌套
# (p1 在 p2 上方) 这个整体 与 p3 并排
nested_patch <- (p1 / p2) | p3
nested_patch

ini
# 示例2: 更复杂的嵌套,并应用整体注解
top_row <- p1 + p2 + plot_layout(widths = c(1,2)) # 顶部行,p2是p1的两倍宽
bottom_plot <- p3
final_patch <- top_row / bottom_plot +
plot_layout(heights = c(1, 1.5)) + # 底部图更高
plot_annotation(
title = "嵌套组合图示例",
tag_levels = 'A' # 标记 A, B, C (A给top_row中的p1, B给top_row中的p2, C给bottom_plot)
)
final_patch

ini
# 标记嵌套结构 (更精细的控制)
# A. (p1 + p2) / B. p3
# 如果想标记 (p1+p2) 为 A,p3 为 B,然后再对 A 内部的 p1, p2 标记 1, 2
patch_A <- p1 + p2 + plot_layout(tag_level = 'new') # A内部标记
patch_B <- p3
(patch_A / patch_B) + plot_annotation(tag_levels = list("A","1"))

ini
# 正确的嵌套标记方式
# ((p1 + p2 + plot_layout(tag_level = '1')) /
# (p3 + p4 + plot_layout(tag_level = '1'))) +
# plot_layout(tag_level = 'A') # 旧的 tag_level 参数
# 使用新版 patchwork 的思路:
# 为子组合添加 plot_annotation
sub_patch1 <- p1 + p2 + plot_layout(tag_level = "new")
sub_patch2 <- p3 + p4 + plot_layout(tag_level = "new")
(sub_patch1 / sub_patch2) + plot_annotation(title="嵌套标记", tag_levels = list("A","1"))

ini
sub_patch1 <- p1 + p2 + plot_layout(tag_level = "keep")
sub_patch2 <- p3 + p4 + plot_layout(tag_level = "new")
(sub_patch1 / sub_patch2) + plot_annotation(title="嵌套标记", tag_levels = list("A","1"))

ini
sub_patch1 <- p1 + p2 + plot_layout(tag_level = "keep")
sub_patch2 <- p3 + p4 + plot_layout(tag_level = "keep")
(sub_patch1 / sub_patch2) + plot_annotation(title="嵌套标记", tag_levels = list("A","1"))

9. inset_element()
函数
inset_element()
允许将一个图(或任何 grob)作为插图嵌入到另一个主图的特定区域。
9.1 inset_element()
参数详解
shell
# 函数原型 (主要参数):
# inset_element(
# plot, # 要插入的ggplot对象或grob
# left, # 插图左边界位置 (0-1, 相对于父区域)
# bottom, # 插图下边界位置 (0-1)
# right, # 插图右边界位置 (0-1)
# top, # 插图上边界位置 (0-1)
# align_to = "panel", # 对齐参考区域 ('panel', 'plot', 'full', 'figure')
# on_top = TRUE, # 插图是否在主图之上
# clip = TRUE # 是否裁剪插图到边界
# )
-
plot
: 要作为插图的ggplot
对象或一个grob
(graphical object)。 -
left
,bottom
,right
,top
: 定义插图位置的数值,介于 0 和 1 之间。这些值是相对于align_to
参数指定的区域。- 例如,
left=0, bottom=0, right=0.4, top=0.4
表示插图位于参考区域的左下角,占据 40%的宽度和 40%的高度。
- 例如,
-
align_to
: 指定位置坐标所参照的区域。-
类型: 字符。
-
取值:
'panel'
(默认): 相对于主图的绘图面板区域(不包括轴、标题等)。'plot'
: 相对于主图的整个绘图区域(包括轴、标题等,但不包括外边距和图例)。'full'
或'figure'
: 相对于分配给该图的完整区域(包括图例、边距等)。
-
-
on_top
: 插图是否绘制在主图之上。- 类型 : 逻辑值 (
TRUE
或FALSE
). - 取值 :
TRUE
(默认) 插图在上,FALSE
插图在下。
- 类型 : 逻辑值 (
-
clip
: 是否将插图裁剪到其定义的边界框内。- 类型 : 逻辑值 (
TRUE
或FALSE
). - 取值 :
TRUE
(默认) 裁剪,FALSE
不裁剪。
- 类型 : 逻辑值 (
ini
# 主图
main_plot <- ggplot(mtcars, aes(x = wt, y = mpg)) +
geom_point(alpha=0.7) +
ggtitle("主图: Weight vs MPG") +
theme_bw()
# 插图
inset_plot <- ggplot(mtcars, aes(x = factor(cyl), fill = factor(cyl))) +
geom_bar() +
labs(title = "插图: Cylinders", x=NULL, y=NULL) +
theme_minimal() + # 简化插图主题
theme(legend.position = "none",
plot.title = element_text(size=8),
axis.text = element_text(size=6))
# 示例1: 基本插图,对齐到 panel
main_plot + inset_element(inset_plot,
left = 0.6, bottom = 0.6,
right = 0.98, top = 0.98)

bash
# 注释:
# inset_plot 被插入到 main_plot 的右上角区域 (基于 panel)。
# left = 0.6: 插图左边界在 panel 宽度的 60% 处。
# bottom = 0.6: 插图下边界在 panel 高度的 60% 处。
# right = 0.98: 插图右边界在 panel 宽度的 98% 处。
# top = 0.98: 插图上边界在 panel 高度的 98% 处。
ini
# 示例2: 对齐到 plot 区域
main_plot + inset_element(inset_plot,
left = 0.5, bottom = 0.6,
right = 0.9, top = 1.1,
align_to = 'plot')

shell
# 注释:
# align_to = 'plot': 位置坐标现在相对于整个'plot'区域 (包括轴标签和标题)。
# 插图现在位于右上角。而且都已经出界了。
ini
# 示例3: 在组合图中使用插图
# (p1 带插图) / p2
p1_with_inset <- p1 + inset_element(p3 + theme_void() + ggtitle(NULL), # 用p3做插图,简化
left = 0.1, bottom = 0.6,
right = 0.5, top = 0.95,
align_to = 'panel')
(p1_with_inset / p2) + plot_annotation(title = "带插图的组合")

ini
# 示例4: 插入非ggplot对象 (例如一个文本grob)
library(grid)
text_grob <- textGrob("这是一个文本插图!", gp = gpar(fontsize = 10, col = "blue"))
main_plot + inset_element(text_grob,
left = 0.5, bottom = 0.01,
right = 0.99, top = 0.15,
align_to = 'plot')

bash
# 注释:
# 可以插入任何 grid grob 对象。
10. 其他技巧与注意事项
- 修改所有子图 : 使用
&
操作符可以将其后的theme()
或labs()
等修改应用到组合中的所有子图。
scss
(p1 + p2) & theme_minimal(base_size = 10) & labs(x = "X轴统一标签", y = "Y轴统一标签")

bash
# 注释:
# & theme_minimal(...): 将 minimal 主题应用到 p1 和 p2。
# & labs(...): 将 X 和 Y 轴标签应用到 p1 和 p2 (如果它们有这些美学映射)。
- 保存 patchwork 对象 : 保存
patchwork
创建的图与保存单个ggplot
对象一样,使用ggsave()
。
arduino
final_plot <- p1 + p2
ggsave("", plot = final_plot, width = 8, height = 4)
# 注释:
# plot = final_plot: 指定要保存的patchwork对象。
# width, height: 指定输出尺寸 (默认单位是英寸)。
11. 总结
patchwork
包核心功能包括:
- 使用
+
,/
,|
进行基本的图表组合。 plot_layout()
用于精细控制网格布局、尺寸和图例。plot_annotation()
添加整体标题、说明和自动标记。wrap_plots()
方便地组合图列表。plot_spacer()
添加空白占位符。inset_element()
创建图内插图。- 通过
()
实现嵌套布局。 - 使用
&
统一修改子图主题或标签。