Membuat fungsi segi banyak kolom

11

Saya mencoba membuat facet_multi_col()fungsi, mirip dengan facet_col()fungsi di ggforce- yang memungkinkan untuk tata letak segi dengan argumen ruang (yang tidak tersedia di facet_wrap()) - tetapi lebih dari beberapa kolom. Seperti pada plot terakhir di bawah ini (dibuat dengan grid.arrange()) saya tidak ingin sisi-sisi harus sejajar di setiap baris karena ketinggian di setiap sisi akan bervariasi berdasarkan pada yvariabel kategorikal yang ingin saya gunakan.

Saya menemukan diri saya keluar dari kedalaman dengan ggprotomembaca panduan ekstensi . Saya pikir pendekatan terbaik adalah dengan melewatkan matriks tata letak untuk menentukan di mana memecah kolom untuk himpunan bagian data yang sesuai, dan untuk membangun facet_col di ggforce untuk memasukkan parameter ruang - lihat bagian akhir pertanyaan.

Ilustrasi singkat opsi tidak memuaskan saya

Tidak ada segi

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

masukkan deskripsi gambar di sini Saya ingin memecah plot oleh benua. Saya tidak ingin sosok yang panjang.

facet_wrap ()

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

masukkan deskripsi gambar di sini facet_wrap()tidak memiliki argumen ruang yang berarti ubin berbeda ukuran di setiap benua, menggunakan coord_equal()kesalahan melempar

facet_col () di ggforce

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

masukkan deskripsi gambar di sini Seperti strip di samping. spaceargumen mengatur semua ubin ke ukuran yang sama. Masih terlalu panjang untuk masuk ke halaman.

grid.arrange () di gridExtra

Tambahkan kolom kolom ke data di mana setiap benua harus ditempatkan

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

Gunakan facet_col()untuk plot untuk setiap kolom

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 = "")
}

Buat legenda menggunakan get_legend()dicowplot

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

Buat matriks tata letak dengan ketinggian berdasarkan jumlah negara di setiap kolom.

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

Membawa gdan legbersama-sama menggunakan grid.arrange()digridExtra

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

masukkan deskripsi gambar di sini Ini hampir seperti apa yang saya kejar, tetapi saya tidak puas karena a) ubin di kolom yang berbeda memiliki lebar yang berbeda karena panjang nama negara dan benua terpanjang tidak sama dan b) banyak kode yang perlu diubah setiap kali saya ingin membuat plot seperti ini - dengan data lain saya ingin mengatur segi berdasarkan wilayah, misalnya "Eropa Barat" daripada benua atau jumlah negara yang berubah - tidak ada negara Asia Tengah dalam gapminderdata.

Kemajuan dengan membuat fungsi facet_multi_cols ()

Saya ingin melewatkan matriks tata letak ke fungsi facet, di mana matriks akan merujuk ke setiap facet, dan fungsi itu kemudian bisa mengetahui ketinggian berdasarkan jumlah ruang di setiap panel. Untuk contoh di atas matriksnya adalah:

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

Seperti yang disebutkan di atas, saya telah beradaptasi dari kode facet_col()untuk mencoba dan membangun suatu facet_multi_col()fungsi. Saya telah menambahkan layoutargumen untuk memberikan matriks seperti di my_layoutatas, dengan gagasan bahwa, misalnya, tingkat keempat dan kelima dari variabel yang diberikan kepada facetsargumen diplot di kolom ketiga.

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)
    # ???
)

Saya pikir saya perlu menulis sesuatu untuk compute_layoutbagian ini, tetapi saya berjuang untuk mencari tahu bagaimana melakukan ini.

gjabel
sumber
Sudahkah Anda mencoba membuat daftar plot, satu untuk setiap benua, dan menyelaraskannya dengan salah satu paket seperti cowplot atau patchwork? Mungkin lebih mudah daripada membangun ggproto
camille
@ Camille Saya agak melakukan ... dalam grid.arrangecontoh di atas .. kecuali Anda bermaksud sesuatu yang berbeda? Saya pikir masalah yang sama akan ada dengan panjang label yang berbeda di setiap kolom?
gjabel
Saya membayangkan sesuatu yang mirip dengan itu, tetapi paket tata letak itu mungkin membantu penyelarasan lebih baik daripada grid.arrange. Ini adalah posting yang sangat panjang sehingga sulit untuk mengikuti semua yang Anda coba. Sedikit berantakan, tetapi Anda dapat mencoba monospace / lebih dekat ke font spasi seragam untuk label sehingga panjangnya lebih mudah diprediksi. Anda bahkan dapat menempelkan label dengan spasi kosong untuk memastikan teks lebih dekat dengan panjang yang sama.
camille

Jawaban:

4

Penolakan

Saya belum pernah mengembangkannya facet, tetapi saya menemukan pertanyaan itu menarik dan cukup menantang, jadi saya mencobanya. Ini belum sempurna dan sejauh ini tidak diuji dengan semua seluk-beluk yang mungkin terjadi tergantung pada plot Anda, tetapi ini adalah konsep pertama yang darinya Anda dapat mengerjakannya.

Ide

facet_wrapmenetapkan panel dalam tabel dan setiap baris memiliki ketinggian tertentu, yang sepenuhnya ditempati panel. gtable_add_grobmengatakan:

Dalam model gtable, grobs selalu mengisi sel tabel lengkap. Jika Anda menginginkan justifikasi khusus, Anda mungkin perlu mendefinisikan dimensi grob dalam satuan absolut, atau memasukkannya ke dalam gtable lain yang kemudian dapat ditambahkan ke gtable alih-alih grob.

Ini bisa menjadi solusi yang menarik. Namun, saya tidak yakin bagaimana mengejar itu. Jadi, saya mengambil pendekatan yang berbeda:

  1. Buat tata letak khusus, berdasarkan parameter tata letak yang diteruskan
  2. Membiarkan facet_wrap render semua panel sesuai tata letak
  3. Menggunakan gtable_filter untuk mengambil panel termasuk kapak dan stripnya
  4. Buat matriks tata letak. Saya mencoba 2 pendekatan: menggunakan jumlah baris minimum dan bermain dengan perbedaan ketinggian. Dan cukup menambahkan kira-kira sebanyak baris karena ada kutu pada sumbu y. Keduanya bekerja sama, yang terakhir menghasilkan kode bersih, jadi saya akan menggunakan ini.
  5. Gunakan gridExtra::arrangeGrobuntuk mengatur panel sesuai dengan desain yang lulus dan matriks tata letak yang dibuat

Hasil

Kode lengkapnya agak panjang, tetapi dapat ditemukan di bawah. Berikut ini beberapa grafik:

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 5Contoh 1 Contoh 2 Contoh 3 Contoh 4 Contoh 5

Batasan

Kode ini jauh dari mudah. Beberapa masalah yang sudah saya lihat:

  • Kami (diam-diam) mengasumsikan bahwa setiap kolom dalam desain dimulai dengan nilai bukan NA (secara umum untuk kode produktif, tata letak yang disahkan perlu diperiksa dengan hati-hati (apakah dimensi cocok? Apakah ada banyak entri seperti panel? Dll.)
  • Panel yang sangat kecil tidak menghasilkan dengan baik, jadi saya harus menambahkan nilai minimum untuk ketinggian tergantung pada posisi strip
  • Efek memindahkan atau menambahkan kapak atau strip belum diuji.

Kode: satu baris per centang

## 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)

Kode: baris dengan ketinggian berbeda

## 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)
thothal
sumber
terima kasih banyak atas ini. saya telah mencoba beberapa data lain - dengan daerah, bukan benua (yang saya sebutkan dalam pertanyaan) ... saya meletakkan kode di sini ... gist.github.com/gjabel/3e4fb31214b5932aa0978dc6d3258dc1 ... itu memunculkan beberapa benar-benar perilaku aneh yang saya tidak tahu?
gjabel
Bisakah Anda membagikan (snapshot) data? Saya melihat intinya, tetapi tidak dapat mereproduksi masalah karena alasan yang jelas ...
thothal
datanya ada di paket wpp2019 .. yang ada di CRAN
gjabel
ah maaf, salahku. akan mencobanya.
thothal
1
Menemukan bug, pada dasarnya tata letak harus diurutkan sesuai dengan PANEL, jika tidak maka tidak akan berfungsi. sampel Anda baik-baik saja sekarang.
thothal
1

Seperti yang disarankan dalam komentar, kombinasi cowplot dan tambal sulam dapat membuat Anda cukup jauh. Lihat solusi saya di bawah ini.

Ide dasarnya adalah:

  • untuk pertama menghitung faktor penskalaan, berdasarkan jumlah baris,
  • kemudian buat serangkaian grid kolom tunggal, di mana saya menggunakan plot kosong untuk membatasi ketinggian plot dengan faktor penskalaan yang dihitung. (dan menghapus legenda)
  • kemudian saya menambahkan ini ke kotak dan juga menambahkan legenda
  • pada awalnya, saya juga menghitung maksimum untuk skala isian.
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]])

Dibuat pada 2019-11-06 oleh paket reprex (v0.3.0)

Bernd Konfuzius
sumber