Próbuję utworzyć facet_multi_col()
funkcję podobną do facet_col()
funkcji w ggforce
- która pozwala na układ aspektów z argumentem spacji (która nie jest dostępna w facet_wrap()
) - ale w wielu kolumnach. Tak jak na ostatnim wykresie poniżej (utworzonym za pomocą grid.arrange()
) nie chcę, aby aspekty musiały być wyrównane między rzędami, ponieważ wysokości w każdym aspekcie będą się różnić w zależności od y
zmiennej kategorialnej , której chcę użyć.
Znalazłem się z głębi duszy ggproto
po przeczytaniu przewodnika po rozszerzeniach . Myślę, że najlepszym podejściem jest przekazanie macierzy układu, aby określić, gdzie rozbić kolumny dla odpowiednich podzbiorów danych, i zbudować facet_col
w ggforce, aby uwzględnić parametr spacji - patrz koniec pytania.
Szybka ilustracja moich niezadowalających opcji
Bez aspektu
library(tidyverse)
library(gapminder)
global_tile <- ggplot(data = gapminder, mapping = aes(x = year, y = fct_rev(country), fill = lifeExp)) +
geom_tile()
global_tile
Chcę podzielić fabułę według kontynentów. Nie chcę tak długiej sylwetki.
facet_wrap ()
global_tile +
facet_wrap(facets = "continent", scales = "free")
facet_wrap()
nie ma argumentu spacji, co oznacza, że kafelki mają różne rozmiary na każdym kontynencie, przy użyciu coord_equal()
generuje błąd
facet_col () w ggforce
library(ggforce)
global_tile +
facet_col(facets = "continent", scales = "free", space = "free", strip.position = "right") +
theme(strip.text.y = element_text(angle = 0))
Jak paski z boku. space
argument ustawia wszystkie kafelki na ten sam rozmiar. Nadal za długo, aby zmieścić się na stronie.
grid.arrange () w gridExtra
Dodaj kolumnę kolumny do danych, w których należy umieścić każdy kontynent
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
Użyj facet_col()
dla wykresu dla każdej kolumny
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 = "")
}
Tworzyć legendę, używając get_legend()
wcowplot
library(cowplot)
gg <- ggplot(data = d, mapping = aes(x = year, y = country, fill = lifeExp)) +
geom_tile()
leg <- get_legend(gg)
Utwórz macierz układu o wysokościach opartych na liczbie krajów w każdej kolumnie.
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
Doprowadzić g
i leg
razem stosując grid.arrange()
wgridExtra
library(gridExtra)
grid.arrange(g[[1]], g[[2]], g[[3]], leg, layout_matrix = m, widths=c(0.32, 0.32, 0.32, 0.06))
To jest prawie to, czego szukam, ale nie jestem zadowolony, ponieważ a) kafelki w różnych kolumnach mają różne szerokości, ponieważ długość najdłuższych nazw krajów i kontynentów nie są równe i b) jest dużo kodu, który należy dostosować kiedy chcę stworzyć taki wykres - z innymi danymi chcę uporządkować aspekty według regionów, np. „Europy Zachodniej” zamiast kontynentów lub zmian liczby krajów - w Azji Środkowej nie ma gapminder
.
Postęp w tworzeniu funkcji facet_multi_cols ()
Chcę przekazać macierz układu do funkcji aspektu, w której matryca odnosi się do każdego aspektu, a funkcja może następnie obliczyć wysokość na podstawie liczby spacji w każdym panelu. W powyższym przykładzie macierz będzie wyglądać następująco:
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
Jak wspomniano powyżej, dostosowywałem się z kodu, facet_col()
aby spróbować zbudować facet_multi_col()
funkcję. Dodałem layout
argument, aby dostarczyć macierz, taką jak my_layout
powyżej, z myślą, że na przykład czwarty i piąty poziom zmiennej podanej facets
argumentowi jest wykreślony w trzeciej kolumnie.
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)
# ???
)
Myślę, że muszę coś napisać do tej compute_layout
części, ale staram się wymyślić, jak to zrobić.
grid.arrange
powyższym przykładzie ... chyba że masz na myśli coś innego? Myślę, że te same problemy występowałyby przy różnych długościach etykiet w każdej kolumnie?grid.arrange
. To naprawdę długi post, więc trudno jest śledzić wszystko, co próbowałeś. Trochę hacky, ale możesz spróbować czcionki monospace / bliżej jednolicie rozmieszczonych czcionek, aby ich długości były bardziej przewidywalne. Możesz nawet wtedy wstawić etykiety z pustymi spacjami, aby upewnić się, że tekst jest bliżej tej samej długości.Odpowiedzi:
Zrzeczenie się
Nigdy ich nie opracowałem
facet
, ale uznałem to pytanie za interesujące i wystarczająco trudne, więc spróbowałem. Nie jest jeszcze idealny i jak dotąd nie przetestowany ze wszystkimi subtelnościami, które mogą wystąpić w zależności od fabuły, ale jest to pierwszy szkic, nad którym możesz pracować.Pomysł
facet_wrap
ustawia panele w tabeli, a każdy rząd ma określoną wysokość, którą panel zajmuje w pełni.gtable_add_grob
mówi:To może być ciekawe rozwiązanie. Nie byłem jednak pewien, jak to osiągnąć. Dlatego zastosowałem inne podejście:
facet_wrap
wszystkie panele wrt do układugtable_filter
do chwytania panelu, w tym jego osi i paskówgridExtra::arrangeGrob
do układania paneli zgodnie z przekazanym projektem i utworzoną matrycą układuWyniki
Pełny kod jest nieco długi, ale można go znaleźć poniżej. Oto kilka wykresów:
Ex 1 Ex 2 Ex 3 Ex 4 Ex 5
Ograniczenia
Kod jest daleki od niezawodności. Niektóre problemy, które już widzę:
Kod: jeden wiersz na tik
Kod: wiersze o różnych wysokościach
źródło
Jak sugerowano w komentarzach, połączenie cowplot i patchworku może doprowadzić cię dość daleko. Zobacz moje rozwiązanie poniżej.
Podstawową ideą jest:
Utworzono 06.11.2019 przez pakiet reprezentx (v0.3.0)
źródło