Tạo một hàm nhiều mặt


11

Tôi đang cố gắng tạo một facet_multi_col()hàm, tương tự như facet_col()hàm trong ggforce- cho phép bố trí một khía cạnh với một đối số không gian (không có sẵn trong facet_wrap()) - nhưng qua nhiều cột. Như trong âm mưu cuối cùng bên dưới (được tạo bằng grid.arrange()), tôi không muốn các khía cạnh nhất thiết phải căn chỉnh giữa các hàng vì độ cao trong mỗi khía cạnh sẽ thay đổi dựa trên một ybiến phân loại mà tôi muốn sử dụng.

Tôi thấy mình thoát khỏi chiều sâu của mình ggprotokhi đọc hướng dẫn mở rộng . Tôi nghĩ cách tiếp cận tốt nhất là truyền một ma trận bố cục để ra lệnh phá vỡ các cột cho các tập hợp con tương ứng của dữ liệu và xây dựng facet_col trong ggforce để bao gồm một tham số không gian - xem phần cuối của câu hỏi.

Một minh họa nhanh về các lựa chọn không đạt yêu cầu của tôi

Không có khía cạnh

library(tidyverse)
library(gapminder)
global_tile <- ggplot(data = gapminder, mapping = aes(x = year, y = fct_rev(country), fill = lifeExp)) +
  geom_tile()
global_tile

nhập mô tả hình ảnh ở đây Tôi muốn phá vỡ cốt truyện của các châu lục. Tôi không muốn một con số dài như vậy.

facet_wrap ()

global_tile +
  facet_wrap(facets = "continent", scales = "free")

nhập mô tả hình ảnh ở đây facet_wrap()không có đối số không gian có nghĩa là các ô có kích thước khác nhau ở mỗi lục địa, sử dụng coord_equal()lỗi ném

facet_col () trong ggforce

library(ggforce)
global_tile +
  facet_col(facets = "continent", scales = "free", space = "free", strip.position = "right") +
  theme(strip.text.y = element_text(angle = 0)) 

nhập mô tả hình ảnh ở đây Giống như các dải ở bên cạnh. spaceđối số đặt tất cả các ô có cùng kích thước. Vẫn còn quá dài để phù hợp với một trang.

Grid.arrange () trong GridExtra

Thêm một cột cột vào dữ liệu cho vị trí của mỗi lục địa

d <- gapminder %>%
  as_tibble() %>%
  mutate(col = as.numeric(continent), 
         col = ifelse(test = continent == "Europe", yes = 2, no = col),
         col = ifelse(test = continent == "Oceania", yes = 3, no = col))
head(d)
# # A tibble: 6 x 7
#   country     continent  year lifeExp      pop gdpPercap   col
#   <fct>       <fct>     <int>   <dbl>    <int>     <dbl> <dbl>
# 1 Afghanistan Asia       1952    28.8  8425333      779.     3
# 2 Afghanistan Asia       1957    30.3  9240934      821.     3
# 3 Afghanistan Asia       1962    32.0 10267083      853.     3
# 4 Afghanistan Asia       1967    34.0 11537966      836.     3
# 5 Afghanistan Asia       1972    36.1 13079460      740.     3
# 6 Afghanistan Asia       1977    38.4 14880372      786.     3
tail(d)
# # A tibble: 6 x 7
#   country  continent  year lifeExp      pop gdpPercap   col
#   <fct>    <fct>     <int>   <dbl>    <int>     <dbl> <dbl>
# 1 Zimbabwe Africa     1982    60.4  7636524      789.     1
# 2 Zimbabwe Africa     1987    62.4  9216418      706.     1
# 3 Zimbabwe Africa     1992    60.4 10704340      693.     1
# 4 Zimbabwe Africa     1997    46.8 11404948      792.     1
# 5 Zimbabwe Africa     2002    40.0 11926563      672.     1
# 6 Zimbabwe Africa     2007    43.5 12311143      470.     1

Sử dụng facet_col()cho âm mưu cho mỗi cột

g <- list()
for(i in unique(d$col)){
  g[[i]] <- d %>%
    filter(col == i) %>%
    ggplot(mapping = aes(x = year, y = fct_rev(country), fill = lifeExp)) +
    geom_tile() +
    facet_col(facets = "continent", scales = "free_y", space = "free", strip.position = "right") +
    theme(strip.text.y = element_text(angle = 0)) +
    # aviod legends in every column
    guides(fill = FALSE) +
    labs(x = "", y = "")
}

Tạo một huyền thoại bằng cách sử dụng get_legend()trongcowplot

library(cowplot)
gg <- ggplot(data = d, mapping = aes(x = year, y = country, fill = lifeExp)) +
  geom_tile()
leg <- get_legend(gg)

Tạo ma trận bố cục với độ cao dựa trên số lượng quốc gia trong mỗi cột.

m <- 
  d %>%
  group_by(col) %>%
  summarise(row = n_distinct(country)) %>%
  rowwise() %>%
  mutate(row = paste(1:row, collapse = ",")) %>%
  separate_rows(row) %>%
  mutate(row = as.numeric(row), 
         col = col, 
         p = col) %>% 
  xtabs(formula = p ~ row + col) %>%
  cbind(max(d$col) + 1) %>%
  ifelse(. == 0, NA, .)

head(m)
#   1 2 3  
# 1 1 2 3 4
# 2 1 2 3 4
# 3 1 2 3 4
# 4 1 2 3 4
# 5 1 2 3 4
# 6 1 2 3 4

tail(m)
#     1 2  3  
# 50  1 2 NA 4
# 51  1 2 NA 4
# 52  1 2 NA 4
# 53 NA 2 NA 4
# 54 NA 2 NA 4
# 55 NA 2 NA 4

Mang glegcùng nhau sử dụng grid.arrange()tronggridExtra

library(gridExtra)
grid.arrange(g[[1]], g[[2]], g[[3]], leg, layout_matrix = m, widths=c(0.32, 0.32, 0.32, 0.06))

nhập mô tả hình ảnh ở đây Đây gần như là những gì tôi đang theo đuổi, nhưng tôi không hài lòng vì a) các ô trong các cột khác nhau có độ rộng khác nhau vì độ dài của tên quốc gia và lục địa dài nhất không bằng nhau và b) rất nhiều mã cần được điều chỉnh mỗi thời gian tôi muốn thực hiện một âm mưu như thế này - với các dữ liệu khác tôi muốn sắp xếp các khía cạnh theo khu vực, ví dụ "Tây Âu" thay vì các lục địa hoặc số lượng quốc gia thay đổi - không có quốc gia Trung Á nào trong gapminder dữ liệu.

Tiến trình tạo hàm facet_multi_cols ()

Tôi muốn truyền ma trận bố cục cho hàm facet, trong đó ma trận sẽ tham chiếu đến từng khía cạnh và sau đó hàm có thể tìm ra độ cao dựa trên số lượng khoảng trắng trong mỗi bảng. Đối với ví dụ trên, ma trận sẽ là:

my_layout <- matrix(c(1, NA, 2, 3, 4, 5), nrow = 2)
my_layout
#      [,1] [,2] [,3]
# [1,]    1    2    4
# [2,]   NA    3    5

Như đã đề cập ở trên, tôi đã điều chỉnh từ mã facet_col()để thử và xây dựng một facet_multi_col()hàm. Tôi đã thêm một layoutđối số để cung cấp ma trận như my_layoutở trên, với ý tưởng rằng, ví dụ, mức thứ tư và thứ năm của biến được đưa ra cho facetsđối số được vẽ trong cột thứ ba.

facet_multi_col <- function(facets, layout, scales = "fixed", space = "fixed",
                      shrink = TRUE, labeller = "label_value",
                      drop = TRUE, strip.position = 'top') {
  # add space argument as in facet_col
  space <- match.arg(space, c('free', 'fixed'))
  facet <- facet_wrap(facets, col = col, dir = dir, scales = scales, shrink = shrink, labeller = labeller, drop = drop, strip.position = strip.position)
  params <- facet$params
  params <- facet$layout

  params$space_free <- space == 'free'
  ggproto(NULL, FacetMultiCols, shrink = shrink, params = params)
}

FacetMultiCols <- ggproto('FacetMultiCols', FacetWrap,
  # from FacetCols to allow for space argument to work
  draw_panels = function(self, panels, layout, x_scales, y_scales, ranges, coord, data, theme, params) {
    combined <- ggproto_parent(FacetWrap, self)$draw_panels(panels, layout, x_scales, y_scales, ranges, coord, data, theme, params)
    if (params$space_free) {
      widths <- vapply(layout$PANEL, function(i) diff(ranges[[i]]$x.range), numeric(1))
      panel_widths <- unit(widths, "null")
      combined$widths[panel_cols(combined)$l] <- panel_widths
    }
    combined
  }
  # adapt FacetWrap layout to set position on panels following the matrix given to layout in facet_multi_col().
  compute_layout = function(self, panels, layout, x_scales, y_scales, ranges, coord, data, theme, params) {
    layout <- ggproto_parent(FacetWrap, self)$compute_layout(panels, layout, x_scales, y_scales, ranges, coord, data, theme, params)
    # ???
)

Tôi nghĩ rằng tôi cần phải viết một cái gì đó cho compute_layoutphần này, nhưng tôi đang đấu tranh để tìm ra cách để làm điều này.


Thay vào đó, bạn đã thử lập một danh sách các lô, một lô cho mỗi lục địa và sắp xếp chúng với một trong các gói như cowplot hoặc chắp vá chưa? Có thể dễ dàng hơn việc xây dựng một ggproto
camille

@camille Tôi đã làm ... trong grid.arrangeví dụ trên .. trừ khi bạn có ý gì khác? Tôi nghĩ những vấn đề tương tự sẽ tồn tại với độ dài nhãn khác nhau trong mỗi cột?
gjabel

Tôi đang tưởng tượng một cái gì đó tương tự như vậy, nhưng các gói bố trí đó có thể giúp căn chỉnh tốt hơn grid.arrange. Đây là một bài viết rất dài nên rất khó để theo dõi mọi thứ bạn đã thử. Một chút hacky, nhưng bạn có thể thử một phông chữ đơn cách / gần hơn với phông chữ cách đều nhau cho các nhãn để độ dài của chúng dễ dự đoán hơn. Bạn thậm chí có thể đệm nhãn với các khoảng trắng để đảm bảo văn bản gần với cùng độ dài.
camille

Câu trả lời:


4

Khước từ

Tôi chưa bao giờ phát triển bất kỳ facet, nhưng tôi thấy câu hỏi thú vị và đủ thách thức, vì vậy tôi đã thử. Nó vẫn chưa hoàn hảo và cho đến nay vẫn chưa được thử nghiệm với tất cả sự tinh tế có thể xảy ra tùy thuộc vào cốt truyện của bạn, nhưng đó là bản nháp đầu tiên mà bạn có thể làm việc.

Ý tưởng

facet_wrapđặt ra các bảng trong một bảng và mỗi hàng có một chiều cao nhất định mà bảng này chiếm hoàn toàn. gtable_add_grobnói:

Trong mô hình gtable, các Grobs luôn lấp đầy ô bảng hoàn chỉnh. Nếu bạn muốn chứng minh tùy chỉnh, bạn có thể cần xác định kích thước grob theo đơn vị tuyệt đối hoặc đặt nó vào một gtable khác mà sau đó có thể được thêm vào gtable thay vì grob.

Đây có thể là một giải pháp thú vị. Tuy nhiên, tôi không chắc làm thế nào để theo đuổi điều đó. Vì vậy, tôi đã thực hiện một cách tiếp cận khác:

  1. Tạo bố cục tùy chỉnh, dựa trên tham số bố cục được thông qua
  2. Hãy facet_wrapkết xuất tất cả các bảng wrt vào bố cục
  3. Sử dụng gtable_filterđể lấy bảng điều khiển bao gồm các trục và dải của nó
  4. Tạo ma trận bố trí. Tôi đã thử 2 cách tiếp cận: sử dụng số lượng hàng tối thiểu và chơi với chênh lệch chiều cao. Và chỉ cần thêm khoảng nhiều hàng như có dấu tích trên trục y. Cả hai đều hoạt động tương tự, cái sau tạo mã sạch hơn, vì vậy tôi sẽ sử dụng mã này.
  5. Sử dụng gridExtra::arrangeGrobđể sắp xếp các bảng theo thiết kế đã thông qua và ma trận bố trí đã tạo

Các kết quả

Mã đầy đủ là một chút dài, nhưng có thể được tìm thấy dưới đây. Dưới đây là một số biểu đồ:

my_layout1 <- matrix(c(1, NA, 2, 3, 4, 5), nrow = 2)
my_layout2 <- matrix(c(1, 2, 3, 4, 5, NA), ncol = 2)

## Ex1
global_tile + facet_multi_col("continent", my_layout1, scales = "free_y", 
                              space = "free", strip.position = "top")

## Ex 2
global_tile + facet_multi_col("continent", my_layout1, scales = "free_y", 
                              space = "free", strip.position = "right")

## Ex 3 - shows that we need a minimum space for any plot 
global_tile + facet_multi_col("continent", my_layout1, scales = "free_y", 
                              space = "free", strip.position = "top", min_prop = 0)

## Ex 4
global_tile + facet_multi_col("continent", my_layout1, scales = "free_y", 
                              space = "fixed", strip.position = "right")

## Ex 5
global_tile + facet_multi_col("continent", my_layout2, scales = "free_y", 
                              space = "free")

Ex 1 Ex 2 Ex 3 Ex 4 Ex 5ví dụ 1 Ví dụ 2 Ví dụ 3 Ví dụ 4 Ví dụ 5

Những hạn chế

Các mã là xa để được đánh lừa. Một số vấn đề tôi đã thấy:

  • Chúng tôi (âm thầm) giả định rằng mỗi cột trong thiết kế bắt đầu bằng giá trị không NA (nói chung đối với mã sản xuất, bố cục đã qua cần phải được kiểm tra cẩn thận (kích thước có vừa không? Có nhiều mục như bảng không? V.v.)
  • Các bảng rất nhỏ không hiển thị tốt, vì vậy tôi phải thêm một giá trị tối thiểu cho chiều cao tùy thuộc vào vị trí của các dải
  • Hiệu quả của việc di chuyển hoặc thêm trục hoặc dải chưa được thử nghiệm.

Mã: một hàng cho mỗi đánh dấu

## get strip and axis of a given panel
## Assumptions:
## - axis are adjacent to the panel, that is exactly +1/-1 positions to the t/b/l/r ...
## - ... unless there is a strip then it is +2/-2 
get_whole_panel <- function(panel_name,
                            table_layout) {
  target <- table_layout$layout %>%
    dplyr::filter(name == panel_name) %>%
    dplyr::select(row = t, col = l)
  stopifnot(NROW(target) == 1)
  pos <- unlist(target)
  dirs <- list(t = c(-1, 0),
               b = c(1, 0),
               l = c(0, -1),
               r = c(0, 1))
  filter_elems <- function(dir, 
                           type = c("axis", "strip")) {
    type <- match.arg(type)
    new_pos <- pos + dir
    res <- table_layout$layout %>%
      dplyr::filter(grepl(type, name),
                    l == new_pos["col"],
                    t == new_pos["row"]) %>%
      dplyr::pull(name)
    if (length(res)) res else NA
  }
  strip <- purrr::map_chr(dirs, filter_elems, type = "strip")
  strip <- strip[!is.na(strip)]
  dirs[[names(strip)]] <- 2 * dirs[[names(strip)]]
  axes  <- purrr::map_chr(dirs, filter_elems, type = "axis")
  gtable::gtable_filter(table_layout, paste(c(panel_name, axes, strip), collapse = "|"))
}


facet_multi_col <- function(facets, layout, scales = "fixed", space = "fixed",
                            shrink = TRUE, labeller = "label_value",
                            drop = TRUE, strip.position = "top", 
                            min_prop = ifelse(strip.position %in% c("top", "bottom"), 
                                              0.12, 0.1)) {
  space <- match.arg(space, c("free", "fixed"))
  if (space == "free") {
    ## if we ask for free space we need scales everywhere, so make sure they are included
    scales <- "free"
  }
  facet <- facet_wrap(facets, ncol = 1, scales = scales, shrink = shrink, 
                      labeller = labeller, drop = drop, strip.position = strip.position)
  params <- facet$params
  params$space_free <- space == "free"
  params$layout <- layout
  params$parent <- facet
  params$min_prop <- min_prop
  ggproto(NULL, FacetMultiCol, shrink = shrink, params = params)
}



render <- function(self, panels, layout, 
                   x_scales, y_scales, ranges, 
                   coord, data, theme, params) {
  combined <- ggproto_parent(FacetWrap, self)$draw_panels(panels, layout, 
                                                          x_scales, y_scales, ranges, 
                                                          coord, data, theme, params)
  if (params$space_free) {
    panel_names <- combined$layout$name
    panels <- lapply(panel_names[grepl("panel", panel_names)],
                     get_whole_panel,
                     table_layout = combined)

    ## remove zeroGrob panels
    zG <- sapply(panels, function(tg) all(sapply(tg$grobs, ggplot2:::is.zero)))
    panels <- panels[!zG]
    ## calculate height for each panel
    heights <- matrix(NA, NROW(params$layout), NCOL(params$layout))
    ## store the rounded range in the matrix cell corresponding to its position
    ## allow for a minimum space in dependence of the overall number of rows to
    ## render small panels well

    heights[as.matrix(layout[, c("ROW", "COL")])] <- vapply(ranges, function(r) 
      round(diff(r$y.range), 0), numeric(1))

    ## 12% should be the minimum height used by any panel if strip is on top otherwise 10%
    ## these values are empirical and can be changed
    min_height <- round(params$min_prop * max(colSums(heights, TRUE)), 0)
    heights[heights < min_height] <- min_height
    idx <- c(heights)
    idx[!is.na(idx)] <- seq_along(idx[!is.na(idx)])
    len_out <- max(colSums(heights, TRUE))
    i <- 0
    layout_matrix <- apply(heights, 2, function(col) {
      res <- unlist(lapply(col, function(n) {
        i <<- i + 1
        mark <- idx[i]
        if (is.na(n)) {
          NA
        } else {
          rep(mark, n)
        }
      }))
      len <- length(res)
      if (len < len_out) {
        res <- c(res, rep(NA, len_out - len))
      }
      res
    })

    ## set width of left axis to maximum width to align plots
    max_width <- max(do.call(grid::unit.c, lapply(panels, function(gt) gt$widths[1])))
    panels <- lapply(panels, function(p) {
      p$widths[1] <- max_width
      p
    })

    combined <- gridExtra::arrangeGrob(grobs = panels,
                            layout_matrix = layout_matrix,
                            as.table = FALSE)
    ## add name, such that find_panel can find the plotting area
    combined$layout$name <- paste("panel_", layout$LAB)
  }
  combined
}

layout <- function(data, params) {
  parent_layout <- params$parent$compute_layout(data, params)
  msg <- paste0("invalid ",
                sQuote("layout"),
                ". Falling back to ",
                sQuote("facet_wrap"),
                " layout")
  if (is.null(params$layout) ||
      !is.matrix(params$layout)) {
    warning(msg)
    parent_layout
  } else {
    ## smash layout into vector and remove NAs all done by sort
    layout <- params$layout
    panel_numbers <- sort(layout)
    if (!isTRUE(all.equal(sort(as.numeric(as.character(parent_layout$PANEL))),
                          panel_numbers))) {
      warning(msg)
      parent_layout
    } else {
      ## all good
      indices <- cbind(ROW = c(row(layout)),
                       COL = c(col(layout)),
                       PANEL = c(layout))
      indices <- indices[!is.na(indices[, "PANEL"]), ]
      ## delete row and col number from parent layout
      parent_layout$ROW <- parent_layout$COL <- NULL
      new_layout <- merge(parent_layout, 
                          indices,
                          by = "PANEL") %>%
        dplyr::arrange(PANEL)
      new_layout$PANEL <- factor(new_layout$PANEL)
      labs <- new_layout %>%
        dplyr::select(-PANEL,
                      -SCALE_X,
                      -SCALE_Y,
                      -ROW,
                      -COL) %>%
        dplyr::mutate(sep = "_") %>%
        do.call(paste, .)
      new_layout$LAB <- labs
      new_layout


    }
  }
}

FacetMultiCol <- ggproto("FacetMultiCol", FacetWrap,
                         compute_layout = layout,
                         draw_panels    = render)

Mã: các hàng có độ cao khác nhau

## get strip and axis of a given panel
## Assumptions:
## - axis are adjacent to the panel, that is exactly +1/-1 positions to the t/b/l/r ...
## - ... unless there is a strip then it is +2/-2 
get_whole_panel <- function(panel_name,
                            table_layout) {
  target <- table_layout$layout %>%
    dplyr::filter(name == panel_name) %>%
    dplyr::select(row = t, col = l)
  stopifnot(NROW(target) == 1)
  pos <- unlist(target)
  dirs <- list(t = c(-1, 0),
               b = c(1, 0),
               l = c(0, -1),
               r = c(0, 1))
  filter_elems <- function(dir, 
                           type = c("axis", "strip")) {
    type <- match.arg(type)
    new_pos <- pos + dir
    res <- table_layout$layout %>%
      dplyr::filter(grepl(type, name),
                    l == new_pos["col"],
                    t == new_pos["row"]) %>%
      dplyr::pull(name)
    if (length(res)) res else NA
  }
  strip <- purrr::map_chr(dirs, filter_elems, type = "strip")
  strip <- strip[!is.na(strip)]
  dirs[[names(strip)]] <- 2 * dirs[[names(strip)]]
  axes  <- purrr::map_chr(dirs, filter_elems, type = "axis")
  gtable::gtable_filter(table_layout, paste(c(panel_name, axes, strip), collapse = "|"))
}


facet_multi_col <- function(facets, layout, scales = "fixed", space = "fixed",
                            shrink = TRUE, labeller = "label_value",
                            drop = TRUE, strip.position = "top") {
  space <- match.arg(space, c("free", "fixed"))
  if (space == "free") {
    ## if we ask for free space we need scales everywhere, so make sure they are included
    scales <- "free"
  }
  facet <- facet_wrap(facets, ncol = 1, scales = scales, shrink = shrink, 
                      labeller = labeller, drop = drop, strip.position = strip.position)
  params <- facet$params
  params$space_free <- space == "free"
  params$layout <- layout
  params$parent <- facet
  ggproto(NULL, FacetMultiCol, shrink = shrink, params = params)
}



render <- function(self, panels, layout, 
                   x_scales, y_scales, ranges, 
                   coord, data, theme, params) {
  combined <- ggproto_parent(FacetWrap, self)$draw_panels(panels, layout, 
                                                          x_scales, y_scales, ranges, 
                                                          coord, data, theme, params)
  if (params$space_free) {
    panel_names <- combined$layout$name
    panels <- lapply(panel_names[grepl("panel", panel_names)],
                     get_whole_panel,
                     table_layout = combined)

    ## remove zeroGrob panels
    zG <- sapply(panels, function(tg) all(sapply(tg$grobs, ggplot2:::is.zero)))
    panels <- panels[!zG]

    ## calculate height for each panel
    heights <- matrix(NA, NROW(params$layout), NCOL(params$layout))
    ## need to add a minimum height as otherwise the space is too narrow
    heights[as.matrix(layout[, c("ROW", "COL")])] <- vapply(layout$PANEL, function(i) 
      max(diff(ranges[[i]]$y.range), 8), numeric(1))
    heights_cum <- sort(unique(unlist(apply(heights, 2, 
                                            function(col) cumsum(col[!is.na(col)])))))
    heights_units <- unit(c(heights_cum[1], diff(heights_cum)), "null")

    ## set width of left axis to maximum width to align plots
    max_width <- max(do.call(grid::unit.c, lapply(panels, function(gt) gt$widths[1])))
    panels <- lapply(panels, function(p) {
      p$widths[1] <- max_width
      p
    })

    mark <- 0

    ## create layout matrix
    layout_matrix <- apply(heights, 2, function(h) {
      idx <- match(cumsum(h),
              cumsum(c(heights_units)))
      idx <- idx[!is.na(idx)]
      res <- unlist(purrr::imap(idx, function(len_out, pos) {
        mark <<- mark + 1
        offset <- if (pos != 1) idx[pos - 1] else 0
          rep(mark, len_out - offset)
      }))
      len_out <- length(res)
      if (len_out < length(heights_units)) {
        res <- c(res, rep(NA, length(heights_units) - len_out)) 
      }
      res
    }) 

    combined <- gridExtra::arrangeGrob(grobs = panels,
                                layout_matrix = layout_matrix,
                                heights = heights_units,
                                as.table = FALSE)
    ## add name, such that find_panel can find the plotting area
    combined$layout$name <- paste("panel_", layout$LAB)
  }
  combined
}

layout <- function(data, params) {
  parent_layout <- params$parent$compute_layout(data, params)
  msg <- paste0("invalid ",
                sQuote("layout"),
                ". Falling back to ",
                sQuote("facet_wrap"),
                " layout")
  if (is.null(params$layout) ||
      !is.matrix(params$layout)) {
    warning(msg)
    parent_layout
  } else {
    ## smash layout into vector and remove NAs all done by sort
    layout <- params$layout
    panel_numbers <- sort(layout)
    if (!isTRUE(all.equal(sort(as.numeric(as.character(parent_layout$PANEL))),
                          panel_numbers))) {
      warning(msg)
      parent_layout
    } else {
      ## all good
      indices <- cbind(ROW = c(row(layout)),
                       COL = c(col(layout)),
                       PANEL = c(layout))
      indices <- indices[!is.na(indices[, "PANEL"]), ]
      ## delete row and col number from parent layout
      parent_layout$ROW <- parent_layout$COL <- NULL
      new_layout <- merge(parent_layout, 
                          indices,
                          by = "PANEL") %>%
        dplyr::arrange(PANEL)
      new_layout$PANEL <- factor(new_layout$PANEL)
      labs <- new_layout %>%
        dplyr::select(-PANEL,
                      -SCALE_X,
                      -SCALE_Y,
                      -ROW,
                      -COL) %>%
        dplyr::mutate(sep = "_") %>%
        do.call(paste, .)
      new_layout$LAB <- labs
      new_layout


    }
  }
}

FacetMultiCol <- ggproto("FacetMultiCol", FacetWrap,
                         compute_layout = layout,
                         draw_panels    = render)

cảm ơn rất nhiều vì điều này tôi đã thử trên một số dữ liệu khác - với các vùng, thay vì các lục địa (mà tôi đã đề cập trong câu hỏi) ... tôi đặt mã ở đây ... gist.github.com/gjabel/3e4fb31214b5932aa0978dc6d3258dc1 ... nó thực sự xuất hiện hành vi lạ mà tôi không thể tìm ra?
gjabel

Bạn có thể chia sẻ (ảnh chụp nhanh) dữ liệu không? Tôi nhìn vào thực chất, nhưng không thể tạo lại vấn đề vì lý do rõ ràng ...
thothal

dữ liệu nằm trong gói wpp2019 .. trên CRAN
gjabel

ah xin lỗi, xấu của tôi sẽ thử nó.
thothal

1
Đã tìm thấy lỗi, về cơ bản bố cục phải được sắp xếp theo PANEL, nếu không nó sẽ không hoạt động. mẫu của bạn làm cho tốt bây giờ.
thothal

1

Theo đề xuất trong các bình luận, sự kết hợp giữa cowplot và chắp vá có thể giúp bạn đi khá xa. Xem giải pháp của tôi dưới đây.

Ý tưởng cơ bản là:

  • đầu tiên để tính hệ số tỷ lệ, dựa trên số lượng hàng,
  • sau đó tạo một loạt các lưới cột đơn, trong đó tôi sử dụng các ô trống để giới hạn chiều cao của các ô với hệ số tỷ lệ được tính toán. (và loại bỏ các huyền thoại)
  • sau đó tôi thêm chúng vào một lưới và cũng thêm một huyền thoại.
  • Ban đầu, tôi cũng tính toán tối đa cho thang đo điền.
library(tidyverse)
library(gapminder)
library(patchwork)
max_life <- max(gapminder$lifeExp)
generate_plot <- function(data, title){
  ggplot(data = data, mapping = aes(x = year, y = fct_rev(country), fill = lifeExp)) +
    geom_tile()+
    scale_fill_continuous(limits = c(0, max_life)) +
    ggtitle(title)
}
scale_plot <- function(plot, ratio){
  plot + theme(legend.position="none") + 
    plot_spacer() + 
    plot_layout(ncol = 1,
                heights = c(
                  ratio,
                  1-ratio
                )
    )
}
df <- gapminder %>% 
  group_by(continent) %>% 
  nest() %>% 
  ungroup() %>% 
  arrange(continent) %>% 
  mutate(
    rows = map_dbl(data, nrow),
    rel_height = (rows/max(rows)),
    plot = map2(
      data,
      continent,
      generate_plot
    ),
    spaced_plot = map2(
      plot,
      rel_height,
      scale_plot
        )
  )
wrap_plots(df$spaced_plot) + cowplot::get_legend(df$plot[[1]])

Được tạo vào ngày 2019-11-06 bởi gói reprex (v0.3.0)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.