如何使用 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() 创建图内插图。
  • 通过 () 实现嵌套布局。
  • 使用 & 统一修改子图主题或标签。
相关推荐
柳杉1 天前
Three.js × Blender:从建模到 Web 3D 的完整工作流深度解析
前端·javascript·数据可视化
Highcharts.js2 天前
在React中使用图表库时,优先选择组件化方案可以降低开发复杂度
前端·javascript·react.js·数据可视化·highcharts
数据科学小丫2 天前
Power BI 使用
数据分析·数据可视化·powerbi
极光代码工作室2 天前
基于Hadoop的日志数据分析系统设计
大数据·hadoop·python·数据分析·数据可视化
一颗烂土豆5 天前
拒绝 rem 计算!Vue3 大屏适配,我用 vfit 一行代码搞定
vue.js·响应式设计·数据可视化
技术民工之路5 天前
Gephi网络(图)分析与可视化工具
大数据·数据可视化
爱学习的程序媛6 天前
【Web前端】蚂蚁AntV:企业级数据可视化全栈方案
前端·信息可视化·前端框架·web·数据可视化
数字冰雹7 天前
数字孪生携手AIGC:一个指令,一座智慧城市的全景智能即刻生成
人工智能·ai·aigc·智慧城市·数字孪生·数据可视化
ayingmeizi1637 天前
从算力领先到增长领先:前沿科技企业为何需要AI原生CRM作为增长引擎
人工智能·科技·数据可视化·crm·ai-native
Highcharts.js7 天前
Highcharts React v4 迁移指南(上):核心变更解析与升级收益
前端·javascript·react.js·react·数据可视化·highcharts·v4迁移