ggplot2 跨越组中的嵌套构面

Posted

技术标签:

【中文标题】ggplot2 跨越组中的嵌套构面【英文标题】:Nested facets in ggplot2 spanning groups 【发布时间】:2017-03-12 00:01:10 【问题描述】:

我遇到了一种情况,我想创建一个由三个分组变量分面的图。为此,我会简单地使用facet_grid(f1 ~ f2 + f3),但这里的问题是 f2 的标签是多余的,最好让它们跨越嵌套在 f2 中的 f3 的方面。

MWE:

library('tibble')
library('ggplot2')
df <- tribble(
  ~x, ~y, ~f1, ~f2, ~f3,
  0.5, 0.5, "a", "a", "a",
  0.5, 0.5, "b", "a", "a",
  0.5, 0.5, "a", "b", "a",
  0.5, 0.5, "b", "b", "a",
  0.5, 0.5, "a", "a", "b",
  0.5, 0.5, "b", "a", "b",
  0.5, 0.5, "a", "b", "b",
  0.5, 0.5, "b", "b", "b"
)


p <- ggplot(df, aes(x = x, y = y)) +
  geom_point() +
  facet_grid(f1 ~ f2 + f3)

再次,我希望将 f2 的标签组合起来,这样它们就不会那么多余了。

编辑:这与其他问题的不同之处在于它询问如何使用现有分组来修改构面,而不是添加新的构面。

【问题讨论】:

【参考方案1】:

很抱歉破坏了这个线程和无意的自我推销,但我尝试将它推广到 facet_nested() 函数,它可以在 ggh4x 包中找到。

该功能没有经过广泛测试,但我认为它可能对人们有些方便。也许会从中得到一些好的反馈。

我在这个函数中做了另外两个修改,超出了分组条的范围。一是它不会自动扩展缺失的变量。这是因为我认为嵌套构面应该能够与非嵌套构面共存,而在使用两个 data.frame 进行绘图时,vars() 中的第二个或更多参数没有任何条目。第二个是它从外到内对条进行排序,因此即使设置了switch,内也比外更靠近面板。

假设df 是上述问题中的df,则重现此问题中的情节如下:

# library(ggh4x)
p <- ggplot(df, aes(x = x, y = y)) +
  geom_point() +
  facet_nested(f1 ~ f2 + f3)

还有a related question 带有更真实的示例图,假设df 是该问题中的df,其工作原理如下:

p <- ggplot(df, aes("", density)) + 
  geom_boxplot(width=0.7, position=position_dodge(0.7)) + 
  theme_bw() +
  facet_nested(. ~ species + location +  position) +
  theme(panel.spacing=unit(0,"lines"),
        strip.background=element_rect(color="grey30", fill="grey90"),
        panel.border=element_rect(color="grey90"),
        axis.ticks.x=element_blank()) +
  labs(x="")

【讨论】:

或许这个功能可以去ggforce包?它有很多关于注释的功能,当颜色、形状要编码其他东西时,它可以很方便。 我认为它更属于 ggplot2 包本身,因为它依赖于几个内部 ggplot2 函数。 我认为 ggplot 坚持 ggplot2 包中的功能应该是 1. 普遍适用且维护成本低或 2. 是基本功能的理念。鉴于它没有经过大量测试(高维护)并且不是基本功能,这将最适合扩展包。 facet_nested 绝对是我一直想要的东西;感谢您重新提出。就功能而言,唯一需要的是一个额外的主题控制杆,通过嵌套调整面板间距。可能嵌套深度乘数就足够了 - 即,如果最低级别的面板之间的间距为 1,则其 1.1 上一级,然后 1.1^2 上两级,依此类推。 我明白你的意思,确实这将是主题设置可以处理的事情。不幸的是,我对这些主题知之甚少,所以我不知道从哪里开始实施它。例如,当您有 1A 1B 2A 2B 嵌套时,始终可以选择手动设置 theme(panel.spacing = unit(c(5,6,5), "points"))【参考方案2】:

答案在gridgtable 包中。情节中的所有内容都按特定顺序排列,如果您稍微挖掘一下,您可以找到所有内容的位置。

library('gtable')
library('grid')
library('magrittr') # for the %>% that I love so well

# First get the grob
z <- ggplotGrob(p) 

这个操作的最终目标是覆盖顶部的分面标签,但诀窍是这两个分面都存在于网格空间的同一行上。它们是表中的表(查看名称为“strip”的行,还要注意zeroGrob;这些稍后会有用):

z
## TableGrob (13 x 14) "layout": 34 grobs
##     z         cells       name                                   grob
## 1   0 ( 1-13, 1-14) background        rect[plot.background..rect.522]
## 2   1 ( 7- 7, 4- 4)  panel-1-1               gTree[panel-1.gTree.292]

                                    ...

## 20  3 ( 7- 7,12-12)   axis-r-1                         zeroGrob[NULL]
## 21  3 ( 9- 9,12-12)   axis-r-2                         zeroGrob[NULL]
## 22  2 ( 6- 6, 4- 4)  strip-t-1                          gtable[strip]
## 23  2 ( 6- 6, 6- 6)  strip-t-2                          gtable[strip]
## 24  2 ( 6- 6, 8- 8)  strip-t-3                          gtable[strip]
## 25  2 ( 6- 6,10-10)  strip-t-4                          gtable[strip]
## 26  2 ( 7- 7,11-11)  strip-r-1                          gtable[strip]
## 27  2 ( 9- 9,11-11)  strip-r-2                          gtable[strip]

                                    ...

## 32  8 ( 3- 3, 4-10)   subtitle  zeroGrob[plot.subtitle..zeroGrob.519]
## 33  9 ( 2- 2, 4-10)      title     zeroGrob[plot.title..zeroGrob.518]
## 34 10 (12-12, 4-10)    caption   zeroGrob[plot.caption..zeroGrob.520]

如果放大第一条,可以看到嵌套结构:

z$grob[[22]]
## TableGrob (2 x 1) "strip": 2 grobs
##   z     cells  name                                 grob
## 1 1 (1-1,1-1) strip absoluteGrob[strip.absoluteGrob.451]
## 2 2 (2-2,1-1) strip absoluteGrob[strip.absoluteGrob.475]

对于每个 grob,我们有一个对象列出了它的绘制顺序 (z)、网格中的位置 (cells)、标签 (name)和几何图形(grob)。

由于我们可以在 gtables 中创建 gtables,我们将使用它来绘制原始图。首先,我们需要在图中找到需要替换的位置。

# Find the location of the strips in the main plot
locations <- grep("strip-t", z$layout$name)

# Filter out the strips (trim = FALSE is important here for positions relative to the main plot)
strip <- gtable_filter(z, "strip-t", trim = FALSE)

# Gathering our positions for the main plot
top <- strip$layout$t[1]
l   <- strip$layout$l[c(1, 3)]
r   <- strip$layout$r[c(2, 4)]

一旦我们有了位置,我们需要创建一个替换表。我们可以使用列表矩阵来做到这一点(是的,这很奇怪。随它去吧)。在我们的例子中,这个矩阵需要有三列和两行,因为有两个方面和它们之间的间隙。由于我们稍后将替换矩阵中的数据,因此我们将使用zeroGrobs 创建一个:

mat   <- matrix(vector("list", length = 6), nrow = 2)
mat[] <- list(zeroGrob())

# The separator for the facets has zero width
res <- gtable_matrix("toprow", mat, unit(c(1, 0, 1), "null"), unit(c(1, 1), "null"))

蒙版分两步创建,覆盖第一个构面组,然后覆盖第二个构面组。在第一部分中,我们使用之前记录的位置从原始图中获取适当的 grob,并将其添加到我们的替换矩阵 res 的顶部,跨越整个长度。然后,我们将该矩阵添加到绘图顶部。

# Adding the first layer
zz <- res %>%
  gtable_add_grob(z$grobs[[locations[1]]]$grobs[[1]], 1, 1, 1, 3) %>%
  gtable_add_grob(z, ., t = top,  l = l[1],  b = top,  r = r[1], name = c("add-strip"))

# Adding the second layer (note the indices)
pp <- gtable_add_grob(res, z$grobs[[locations[3]]]$grobs[[1]], 1, 1, 1, 3) %>%
  gtable_add_grob(zz, ., t = top,  l = l[2],  b = top,  r = r[2], name = c("add-strip"))

# Plotting
grid.newpage()
print(grid.draw(pp))

【讨论】:

非常感谢您的解决方案。我一直在努力研究如何推广此解决方案以修改构面标签(如果它们出现在图的右侧)。您能否展示如何修改您的解决方案以适应布局基于绘图功能ggplot(cbind(df,df), aes(x = x, y = y)) + geom_point() + facet_grid(f1 + f2 ~ f3) 的情况?我还需要将 12 个方面行减少到 6 个外部右侧标签而不是 4 个和 2 个(因为我提供的重写函数会产生)的情况。如果有帮助,很高兴提供一个明确的例子。非常感谢! 嗨@nickb,你可能想看看***.com/a/55911134/2752888 感谢分享一个不错的解决方案。出于某种原因,当 facet_wrap(~id + id2, nrow = 4, ncol=8) 设置中有多行时,我无法使其工作。这可能只是我不完全理解我需要如何调整您的解决方案或忽略一些愚蠢的事情。请参阅***.com/questions/62652308/…(带有最小可行示例和创建的图)。

以上是关于ggplot2 跨越组中的嵌套构面的主要内容,如果未能解决你的问题,请参考以下文章

ggplot2 移动构面布局

ggplot2 facet_grid 带有构面标题

在 ggplot2 facet_grid 中旋转切换的构面标签

如何根据 ggplot2 绘制的图上的 y 轴值覆盖构面标签?

ggplot2:将各个facet_wrap构面另存为单独的图对象

根据ggplot2中最后一个分面网格的递减值对条形图的Y轴进行排序