如何手动调整 facet 大小

32

我有一个由非常多元化数据构成的分面图。所以有些分面只有1个x值,而其他一些则有13个x值。我知道有一个参数space='free',它通过表示的数据来调整每个分面的宽度。

我的问题是,是否有可能手动调整此间距?因为我的一些分面太小了,已经无法阅读分面中的标签。我制作了一个小的可重现示例来说明我的意思。

df <- data.frame(labelx=rep(c('my long label','short'), c(2,26)),
                 labely=rep(c('a','b'), each=14),
                 x=c(letters[1:2],letters[1:26]),
                 y=LETTERS[6:7],
                 i=rnorm(28))
ggplot(df, aes(x,y,color=i)) +
  geom_point() +
  facet_grid(labely~labelx, scales='free_x', space='free_x')
根据您的屏幕大小,my long label 面板被压缩,你无法再读取标签。我在网上找到了一篇文章,似乎正是我想做的,但这在 ggplot2 中似乎不再起作用了。这篇文章是从2010年开始的。https://kohske.wordpress.com/2010/12/25/adjusting-the-relative-space-of-a-facet-grid/ 他建议使用 facet_grid(fac1 + fac2 ~ fac3 + fac4, widths = 1:4, heights = 4:1) 来手动调整每个面板的大小,所以使用 widthsheights

1
我的猜测是你需要尝试一下 ggplotGrob。此外,这里有一个函数源代码的链接。你可以尝试理解 "scales" 参数的作用,然后利用这个知识来修改最终的 ggplot。https://github.com/tidyverse/ggplot2/blob/master/R/facet-grid-.r#L314 很抱歉我不能提供更多帮助。 - Michael Harper
4个回答

51

您可以使用格网图形来调整ggplot对象的宽度。

g = ggplot(df, aes(x,y,color=i)) +
  geom_point() +
  facet_grid(labely~labelx, scales='free_x', space='free_x')

library(grid)
gt = ggplot_gtable(ggplot_build(g))
gt$widths[4] = 4*gt$widths[4]
grid.draw(gt)

enter image description here

当图形复杂且元素众多时,确定需要调整的宽度可能有点麻烦。在这个例子中,需要扩展网格列4,但对于不同的图表,情况会有所不同。有几种方法可以确定要更改哪一个,但一个相当简单且好的方法是使用 gtable包中的gtable_show_layout

gtable_show_layout(gt)

生成了以下图片:

enter image description here

我们可以看到左侧的图形在第4列。前三列提供了余白、轴标题和轴标签+刻度的空间。第5列是各个图形之间的间隔,第6列是右侧的图形。第7至12列用于右侧图形的标签、空格、图例和右边缘。

检查gtable的图形表示的另一种方法是简单地检查表格本身。实际上,如果您需要自动化这个过程,这将是做法。所以让我们来看看TableGrob:

gt
# TableGrob (13 x 12) "layout": 25 grobs
#     z         cells       name                                   grob
# 1   0 ( 1-13, 1-12) background        rect[plot.background..rect.399]
# 2   1 ( 7- 7, 4- 4)  panel-1-1               gTree[panel-1.gTree.283]
# 3   1 ( 9- 9, 4- 4)  panel-2-1               gTree[panel-3.gTree.305]
# 4   1 ( 7- 7, 6- 6)  panel-1-2               gTree[panel-2.gTree.294]
# 5   1 ( 9- 9, 6- 6)  panel-2-2               gTree[panel-4.gTree.316]
# 6   3 ( 5- 5, 4- 4)   axis-t-1                         zeroGrob[NULL]
# 7   3 ( 5- 5, 6- 6)   axis-t-2                         zeroGrob[NULL]
# 8   3 (10-10, 4- 4)   axis-b-1    absoluteGrob[GRID.absoluteGrob.329]
# 9   3 (10-10, 6- 6)   axis-b-2    absoluteGrob[GRID.absoluteGrob.336]
# 10  3 ( 7- 7, 3- 3)   axis-l-1    absoluteGrob[GRID.absoluteGrob.343]
# 11  3 ( 9- 9, 3- 3)   axis-l-2    absoluteGrob[GRID.absoluteGrob.350]
# 12  3 ( 7- 7, 8- 8)   axis-r-1                         zeroGrob[NULL]
# 13  3 ( 9- 9, 8- 8)   axis-r-2                         zeroGrob[NULL]
# 14  2 ( 6- 6, 4- 4)  strip-t-1                          gtable[strip]
# 15  2 ( 6- 6, 6- 6)  strip-t-2                          gtable[strip]
# 16  2 ( 7- 7, 7- 7)  strip-r-1                          gtable[strip]
# 17  2 ( 9- 9, 7- 7)  strip-r-2                          gtable[strip]
# 18  4 ( 4- 4, 4- 6)     xlab-t                         zeroGrob[NULL]
# 19  5 (11-11, 4- 6)     xlab-b titleGrob[axis.title.x..titleGrob.319]
# 20  6 ( 7- 9, 2- 2)     ylab-l titleGrob[axis.title.y..titleGrob.322]
# 21  7 ( 7- 9, 9- 9)     ylab-r                         zeroGrob[NULL]
# 22  8 ( 7- 9,11-11)  guide-box                      gtable[guide-box]
# 23  9 ( 3- 3, 4- 6)   subtitle  zeroGrob[plot.subtitle..zeroGrob.396]
# 24 10 ( 2- 2, 4- 6)      title     zeroGrob[plot.title..zeroGrob.395]
# 25 11 (12-12, 4- 6)    caption   zeroGrob[plot.caption..zeroGrob.397]

相关的部分是

#         cells       name  
# ( 7- 7, 4- 4)  panel-1-1      
# ( 9- 9, 4- 4)  panel-2-1              
# ( 6- 6, 4- 4)  strip-t-1

其中名称panel-x-y指的是表格中x,y坐标对应的面板,而单元格则给出了该命名面板在表格中的坐标(作为范围)。例如,左上角和左下角的两个面板都位于表格单元格中的列范围4- 4中(即仅在第四列)。左上方条带也在单元格列4中。

如果您想要以编程方式而非手动方式使用此表格来查找相关宽度(以左上角分面"panel-1-1"为例),则可以使用

gt$layout$l[grep('panel-1-1', gt$layout$name)]
# [1] 4

你能解释一下为什么是第四个索引吗?我的原始图形有点复杂,我也尝试了gig-lot_gtable方法,但从无空格和自动空格创建gtable会导致相同的$widths向量。我已经试了一个多小时了。 - drmariod
1
@drmariod编辑了答案,提供了更多关于此事的信息。希望这能有所帮助。 - dww
1
我简直不敢相信!竟然错过了gtable包!!!这个包可以节省我们花费在图形处理上的时间!天啊!非常感谢! - drmariod
1
特别是gtable_show_layout函数太酷了! :-) 我完全被震撼了! - drmariod

20

很抱歉几年后才发布这篇文章,但我曾经遇到过同样的问题,并编写了一个函数来使它更容易解决。我认为如果分享它,可能会帮助到这里的人们。在其核心部分,它也是在gtable中设置宽度/高度,但在facet级别上进行集成,以便您仍然可以添加东西。它存在于我在github上编写的软件包中(编辑:现在在CRAN上可用)。请注意,您还可以使用grid::unit(..., "cm")设置绝对大小为例。

library(ggplot2)
library(ggh4x)

df <- data.frame(labelx=rep(c('my long label','short'), c(2,26)),
                 labely=rep(c('a','b'), each=14),
                 x=c(letters[1:2],letters[1:26]),
                 y=LETTERS[6:7],
                 i=rnorm(28))
ggplot(df, aes(x,y,color=i)) +
  geom_point() +
  facet_grid(labely~labelx, scales='free_x', space='free_x') +
  force_panelsizes(cols = c(0.3, 1)) +
  theme_bw() # Just to show you can still add things

使用 reprex package (v0.3.0) 在 2021-01-21 创建


这似乎比在gtable中处理值要容易得多! - Ben
谢谢分享!它对我很有帮助,而且非常容易实现。 - Geparada
太棒了!谢谢你! - Marek Fiołka

9

很遗憾,在 facet_grid 中设置 widthsheights 的功能已经消失了。

另一个可能的解决方法是在 theme(strip.text.x=element_text(angle...)) 中设置文本角度,并在 facet_grid(... labeller=label_wrap_gen(width...)) 中设置分面文本换行,例如:

ggplot(df, aes(x,y,color=i)) +
  geom_point() +
  facet_grid(labely~labelx, scales='free_x', space='free_x', labeller=label_wrap_gen(width = 10, multi_line = TRUE)) +
  theme(strip.text.x=element_text(angle=90, hjust=0.5, vjust=0.5))

enter image description here


我曾考虑过这样的方案,但对于原始情节来说并不是一个选项。 - drmariod

7

如果你有修改 ggplot2 的兴趣,并想以其他方式进行修改,我推荐阅读vignette:

vignette("extending-ggplot2")

现在针对您手头的问题,我认为一个简洁解决方案的快捷方式如下:
library(ggplot2)
DF <- data.frame(labelx = rep(c('my long label','short'), c(2,26)),
                 labely = rep(c('a','b'), each = 14),
                 x = c(letters[1:2], letters[1:26]),
                 y = LETTERS[6:7],
                 i = rnorm(28))

# ad-hoc replacement for the "draw_panels" method, sorry for the hundred lines of code...
# only modification is marked with a comment
draw_panels_new <- function(panels, layout, x_scales, y_scales, ranges, coord, data, theme, params) {
  cols <- which(layout$ROW == 1)
  rows <- which(layout$COL == 1)
  axes <- render_axes(ranges[cols], ranges[rows], coord, theme, transpose = TRUE)
  col_vars <- unique(layout[names(params$cols)])
  row_vars <- unique(layout[names(params$rows)])
  attr(col_vars, "type") <- "cols"
  attr(col_vars, "facet") <- "grid"
  attr(row_vars, "type") <- "rows"
  attr(row_vars, "facet") <- "grid"
  strips <- render_strips(col_vars, row_vars, params$labeller, theme)
  aspect_ratio <- theme$aspect.ratio
  if (is.null(aspect_ratio) && !params$free$x && !params$free$y) {
    aspect_ratio <- coord$aspect(ranges[[1]])
  }
  if (is.null(aspect_ratio)) {
    aspect_ratio <- 1
    respect <- FALSE
  } else {
    respect <- TRUE
  }
  ncol <- max(layout$COL)
  nrow <- max(layout$ROW)
  panel_table <- matrix(panels, nrow = nrow, ncol = ncol, byrow = TRUE)
  if (params$space_free$x) {
    ps <- layout$PANEL[layout$ROW == 1]
    widths <- vapply(ps, function(i) diff(ranges[[i]]$x.range), numeric(1))
    # replaced "widths" below with custom manual values c(1,4)
    panel_widths <- unit(c(1,4), "null")
  } else {
    panel_widths <- rep(unit(1, "null"), ncol)
  }
  if (params$space_free$y) {
    ps <- layout$PANEL[layout$COL == 1]
    heights <- vapply(ps, function(i) diff(ranges[[i]]$y.range), numeric(1))
    panel_heights <- unit(heights, "null")
  } else {
    panel_heights <- rep(unit(1 * aspect_ratio, "null"), 
                         nrow)
  }
  panel_table <- gtable_matrix("layout", panel_table, panel_widths, 
                               panel_heights, respect = respect, clip = "on", z = matrix(1, ncol = ncol, nrow = nrow))
  panel_table$layout$name <- paste0("panel-", rep(seq_len(ncol), nrow), "-", rep(seq_len(nrow), each = ncol))
  panel_table <- gtable_add_col_space(panel_table, theme$panel.spacing.x %||% theme$panel.spacing)
  panel_table <- gtable_add_row_space(panel_table, theme$panel.spacing.y %||% theme$panel.spacing)
  panel_table <- gtable_add_rows(panel_table, max_height(axes$x$top), 0)
  panel_table <- gtable_add_rows(panel_table, max_height(axes$x$bottom), -1)
  panel_table <- gtable_add_cols(panel_table, max_width(axes$y$left), 0)
  panel_table <- gtable_add_cols(panel_table, max_width(axes$y$right), -1)
  panel_pos_col <- panel_cols(panel_table)
  panel_pos_rows <- panel_rows(panel_table)
  panel_table <- gtable_add_grob(panel_table, axes$x$top, 1, panel_pos_col$l, clip = "off", 
                                 name = paste0("axis-t-", seq_along(axes$x$top)), z = 3)
  panel_table <- gtable_add_grob(panel_table, axes$x$bottom, -1, panel_pos_col$l, clip = "off", 
                                 name = paste0("axis-b-", seq_along(axes$x$bottom)), z = 3)
  panel_table <- gtable_add_grob(panel_table, axes$y$left, panel_pos_rows$t, 1, clip = "off", 
                                 name = paste0("axis-l-", seq_along(axes$y$left)), z = 3)
  panel_table <- gtable_add_grob(panel_table, axes$y$right, panel_pos_rows$t, -1, clip = "off", 
                                 name = paste0("axis-r-", seq_along(axes$y$right)), z = 3)
  switch_x <- !is.null(params$switch) && params$switch %in% c("both", "x")
  switch_y <- !is.null(params$switch) && params$switch %in% c("both", "y")
  inside_x <- (theme$strip.placement.x %||% theme$strip.placement %||% "inside") == "inside"
  inside_y <- (theme$strip.placement.y %||% theme$strip.placement %||% "inside") == "inside"
  strip_padding <- convertUnit(theme$strip.switch.pad.grid, "cm")
  panel_pos_col <- panel_cols(panel_table)
  if (switch_x) {
    if (!is.null(strips$x$bottom)) {
      if (inside_x) {
        panel_table <- gtable_add_rows(panel_table, max_height(strips$x$bottom), -2)
        panel_table <- gtable_add_grob(panel_table, strips$x$bottom, -2, panel_pos_col$l, clip = "on", 
                                       name = paste0("strip-b-", seq_along(strips$x$bottom)), z = 2)
      } else {
        panel_table <- gtable_add_rows(panel_table, strip_padding, -1)
        panel_table <- gtable_add_rows(panel_table, max_height(strips$x$bottom), -1)
        panel_table <- gtable_add_grob(panel_table, strips$x$bottom, -1, panel_pos_col$l, clip = "on", 
                                       name = paste0("strip-b-", seq_along(strips$x$bottom)), z = 2)
      }
    }
  } else {
    if (!is.null(strips$x$top)) {
      if (inside_x) {
        panel_table <- gtable_add_rows(panel_table, max_height(strips$x$top), 1)
        panel_table <- gtable_add_grob(panel_table, strips$x$top, 2, panel_pos_col$l, clip = "on", 
                                       name = paste0("strip-t-", seq_along(strips$x$top)), z = 2)
      } else {
        panel_table <- gtable_add_rows(panel_table, strip_padding, 0)
        panel_table <- gtable_add_rows(panel_table, max_height(strips$x$top), 0)
        panel_table <- gtable_add_grob(panel_table, strips$x$top, 1, panel_pos_col$l, clip = "on", 
                                       name = paste0("strip-t-", seq_along(strips$x$top)), z = 2)
      }
    }
  }
  panel_pos_rows <- panel_rows(panel_table)
  if (switch_y) {
    if (!is.null(strips$y$left)) {
      if (inside_y) {
        panel_table <- gtable_add_cols(panel_table, max_width(strips$y$left), 1)
        panel_table <- gtable_add_grob(panel_table, strips$y$left, panel_pos_rows$t, 2, clip = "on", 
                                       name = paste0("strip-l-", seq_along(strips$y$left)), z = 2)
      } else {
        panel_table <- gtable_add_cols(panel_table, strip_padding, 0)
        panel_table <- gtable_add_cols(panel_table, max_width(strips$y$left), 0)
        panel_table <- gtable_add_grob(panel_table, strips$y$left, panel_pos_rows$t, 1, clip = "on", 
                                       name = paste0("strip-l-", seq_along(strips$y$left)), z = 2)
      }
    }
  } else {
    if (!is.null(strips$y$right)) {
      if (inside_y) {
        panel_table <- gtable_add_cols(panel_table, max_width(strips$y$right), -2)
        panel_table <- gtable_add_grob(panel_table, strips$y$right, panel_pos_rows$t, -2, clip = "on", 
                                       name = paste0("strip-r-", seq_along(strips$y$right)), z = 2)
      } else {
        panel_table <- gtable_add_cols(panel_table, strip_padding, -1)
        panel_table <- gtable_add_cols(panel_table, max_width(strips$y$right), -1)
        panel_table <- gtable_add_grob(panel_table, strips$y$right, panel_pos_rows$t, -1, clip = "on", 
                                       name = paste0("strip-r-", seq_along(strips$y$right)), z = 2)
      }
    }
  }
  panel_table
}

继续在新的代码块中停止滚动:

# need to pre-set the same environment to find things like e.g.
# gtable_matrix() from package gtable
environment(draw_panels_new) <- environment(FacetGrid$draw_panels)
# assign custom method
FacetGrid$draw_panels <- draw_panels_new

# happy plotting
ggplot(DF, aes(x, y, color = i)) +
  geom_point() +
  facet_grid(labely~labelx, scales = 'free_x', space = 'free_x')

我之所以说快捷方式,是因为你当然可以编写自己的 facet_grid_new 版本,此外还可以通过添加自定义的params选项来灵活地传递上述的值 c(1,4)。 当然,您也可以创建继承自 FacetGrid 的自己的 ggproto 对象...
编辑: 另一种使其更具灵活性的简单方法是添加自定义的 option,例如:
options(facet_size_manual = list(width = c(1,4), height = NULL))

这样就可以在自定义的draw_panels方法中使用它了,例如:
if (!is.null(facet_width <- getOption("facet_size_manual")$width))
  widths <- facet_width

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