在ggplot2中的facet_wrap中添加空图表

16

我正在尝试创建3个图形,所有面板的大小应该相同,所以我认为解决方案可能是facet_wrap。我的问题是,我不想在每个图形中拥有相同数量的子图。

df <- data.frame(group=c(1,1,2,2,2,3,3),
                 name=c('a','b','c','d','e','f','g'),
                 x=c(1,2,3,4,5,6,7),
                 y=c(2,3,4,5,6,7,8))
ggplot(df, aes(x,y)) + geom_point() + facet_wrap(~ name, ncol=3)

这个结果将图形按照顺序组合在一起,但是我希望能够按照我的分组列进行分组。

所以我期望的结果是

a b _
c d e 
f g _

但是我得到的是

a b c
d e f
g _ _

有人有建议如何创建像这样的图形吗,也许是通过添加空图形?我找到了一个其他的主题 Force facet_wrap to fill bottom row... 但在我的情况下,我无法使其运行。

2个回答

31

facet_wrap 只是将一个图表放在另一个图表后面,并在适当数量的图表后插入“换行符”。但也有 facet_grid,它允许您指定行和列索引。您的行索引是group,现在我将添加列索引如下:

cols<-c(a=1,c=1,f=1,b=2,d=2,g=2,e=3)
df$col <- as.factor(cols[as.character(df$name)])

现在你可以使用 facet grid 绘图。
ggplot(df, aes(x,y)) + geom_point() + facet_grid(group~col)

当然,根据你的问题不同,你需要考虑一个适当的方式来设置列索引。
这是图表: enter image description here 编辑:
针对你的评论并参考此答案,我创建了第二个解决方案。它使用gridExtra并直接操作ggplot grob。我认为它可以得到所需的结果,但以我呈现的形式,它是“手动工作”。此解决方案使用facet_wrap而不是facet_grid
首先,我向变量name添加了一个“占位符级别”,这将确保创建空面板,然后创建图表:
df$name2 <- factor(df$name,levels=c('a','b','','c','d','e','f','g',' '))
p <- ggplot(df, aes(x,y)) + geom_point() + facet_wrap(~name2,ncol=3,drop=FALSE)

drop=FALSE 是导致 ggplot 绘制空面板的原因。 这个图与我的第一个解决方案唯一不同的是如何标记面板。现在来到棘手的部分:

library(gridExtra)
g <- ggplotGrob(p)
## remove empty panels
g$grobs[names(g$grobs) %in% c("panel3", "panel9", "strip_t3", "strip_t9")] <- NULL
## remove them from the layout
g$layout <- g$layout[!(g$layout$name %in% c("panel-3", "panel-9", 
                                           "strip_t-3", "strip_t-9")),]
## move axis closer to panel
g$layout[g$layout$name == "axis_b-9", c("t", "b")] = c(9,9)

这个代码的作用基本上就是评论所说的。如果你使用另一组图形,请查看names(g$grobs)g$layout$name的输出,以确定需要删除哪些元素。

现在,您可以使用以下命令创建图形:

grid.newpage()
grid.draw(g)

enter image description here

编辑2:

对于更新版本的ggplot2,上述解决方案不起作用。不幸的是,我不知道从哪个版本开始出现这种情况,但它绝对不适用于2.2.1版本。

必须更改的部分是grob的修改:

g <- ggplotGrob(p)
# get the grobs that must be removed
rm_grobs <- g$layout$name %in% c("panel-1-3", "panel-3-3", "strip-t-3-1", "strip-t-3-3")
# remove grobs
g$grobs[rm_grobs] <- NULL
g$layout <- g$layout[!rm_grobs, ]
## move axis closer to panel
g$layout[g$layout$name == "axis-b-3-3", c("t", "b")] = c(14.5, 14.5)
grid.newpage()
grid.draw(g)

主要更改是g$grobs不再是命名列表,并且grobs的名称已更改。请注意,面板的标签为"panel-row-col",而对于灰色条,它是"strip-t-col-row"enter image description here

2

对@Stibu的出色回答的一个小补充:

不幸的是,在使用facet_wrap时,g$grobs中的面板名称并不总是与"panel-row-col"一致(而条带名称是一致的,并且在使用facet_grid时一切正常)。

这是一个基于@Stibu回答的函数,我用它来避免记住任何细节:

remove_facets <- function(plot, layout) {
  layout <- strsplit(layout, split = '\n')[[1]]
  layout <- lapply(layout, trimws)
  layout <- matrix(unlist(sapply(layout, strsplit, "")),
                   nrow = length(layout), byrow = T)
  layout <- which(layout == "#", arr.ind = TRUE)
  prm <- apply(layout,1,\(x) {
    c(glue::glue("panel-{x[1]}-{x[2]}"),
      glue::glue("strip-t-{x[2]}-{x[1]}"))
  })
  # https://dev59.com/Fl0a5IYBdhLWcg3waH_j#30372692
  g <- ggplot2::ggplotGrob(plot)
  rm_grobs <- g$layout$name %in% prm
  g$grobs[rm_grobs] <- NULL
  g$layout <- g$layout[!rm_grobs, ]
  ggpubr::as_ggplot(g)
}

在这里,plot是一个ggplotlayout是一个表示分面绘图的字符字符串,其中要删除的分面由#表示。
这是一个简单的例子:
(p1 <- ggplot(mtcars, aes(mpg, disp)) +
  geom_point() +
  facet_grid(rows=vars(cyl), cols=vars(gear)))

# any character can indicate a facet, '#' indicates a facet that will be removed
a <- c("a#a
        ##a
        a##")

remove_facets(p1, a)

Example plot. Left the original, right with some facets removed.

如上所述,该功能并不稳健:
- 在使用`facet_grid`时效果最佳,可能需要手动调整`facet_wrap` - 对于`facet_wrap`,期望在顶部有条带 - 返回类型是`ggpubr::as_ggplot`,但它并不完全是一个`ggplot`;根据您对图表的处理方式,返回或直接绘制为`grob`可能更好 - 当与其他图表组合时,这种伪`grob`更适合由cowplot而不是patchwork来处理

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接