如何使用 patchwork 包

如何使用 patchwork 包

1. patchwork 包简介

patchwork 是一个由 Thomas Lin Pedersen 开发的 R 包,旨在极大地简化组合多个 ggplot 对象的过程。它的核心理念是让图表组合变得像数学运算一样直观。你不再需要学习复杂的 gridcowplot 函数参数,只需使用 +, /, | 等操作符即可。

最重要的是: 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 ncolnrow

  • ncol: 指定布局的列数。

    • 类型 : 整数 (e.g., 1, 2, 3).
    • 取值 : 大于 0 的整数。如果同时指定 ncolnrowpatchwork 会尝试满足两者,但通常只指定一个即可。
  • nrow: 指定布局的行数。

    • 类型 : 整数 (e.g., 1, 2, 3).
    • 取值: 大于 0 的整数。
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: 决定图表是按行填充还是按列填充网格。

    • 类型 : 逻辑值 (TRUEFALSE).

    • 取值:

      • 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 widthsheights

  • 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: 插图是否绘制在主图之上。

    • 类型 : 逻辑值 (TRUEFALSE).
    • 取值 : TRUE (默认) 插图在上, FALSE 插图在下。
  • clip: 是否将插图裁剪到其定义的边界框内。

    • 类型 : 逻辑值 (TRUEFALSE).
    • 取值 : 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() 创建图内插图。
  • 通过 () 实现嵌套布局。
  • 使用 & 统一修改子图主题或标签。
相关推荐
一个何包蛋!!2 天前
相关类相关的可视化图像总结
开发语言·python·数据可视化
weixin_505154463 天前
数字孪生在建设智慧城市中可以起到哪些作用或帮助?
大数据·人工智能·智慧城市·数字孪生·数据可视化
捷码小编4 天前
数据可视化大屏案例落地实战指南:捷码平台7天交付方法论
低代码·数字孪生·数据可视化
捷码小编5 天前
如何选择专业数据可视化开发工具?为您拆解捷码全功能和落地指南!
低代码·数字孪生·数据可视化
十三画者6 天前
【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
python·机器学习·数据挖掘·数据分析·r语言·数据可视化
搏博6 天前
将图形可视化工具的 Python 脚本打包为 Windows 应用程序
开发语言·windows·python·matplotlib·数据可视化
@HNUSTer8 天前
Python数据可视化科技图表绘制系列教程(一)
python·数据可视化·科技论文·专业制图·科研图表
程序员阿龙11 天前
基于Web的濒危野生动物保护信息管理系统设计(源码+定制+开发)濒危野生动物监测与保护平台开发 面向公众参与的野生动物保护与预警信息系统
前端·数据可视化·野生动物保护·濒危物种·态环境监测·web系统开发
梅一一11 天前
5款AI对决:Gemini学术封神,但日常办公我选它
大数据·人工智能·数据可视化