Facetas anidadas en ggplot2 que abarcan grupos
(2)
Encontré una situación en la que quiero crear un gráfico que fue facetado por tres variables de agrupación.
Para hacerlo, simplemente usaría
facet_grid(f1 ~ f2 + f3)
, pero el problema aquí es que las etiquetas para f2 serían redundantes, y sería mucho mejor que abarquen las facetas de f3 anidadas dentro de f2.
MWE:
library(''tibble'')
library(''ggplot2'')
df <- tribble(
~x, ~y, ~f1, ~f2, ~f3,
0.5, 0.5, "a", "a", "a",
0.5, 0.5, "b", "a", "a",
0.5, 0.5, "a", "b", "a",
0.5, 0.5, "b", "b", "a",
0.5, 0.5, "a", "a", "b",
0.5, 0.5, "b", "a", "b",
0.5, 0.5, "a", "b", "b",
0.5, 0.5, "b", "b", "b"
)
p <- ggplot(df, aes(x = x, y = y)) +
geom_point() +
facet_grid(f1 ~ f2 + f3)
Nuevamente, estoy buscando combinar las etiquetas para f2 para que no sean tan redundantes.
Editar: Esto es diferente de otras preguntas, ya que pregunta cómo usar las agrupaciones existentes para modificar una faceta en lugar de agregar una nueva.
La respuesta a esto se encuentra dentro de los paquetes de
grid
y
gtable
.
Todo en la trama se presenta en un orden particular y puedes encontrar dónde está todo si cavas un poco.
library(''gtable'')
library(''grid'')
library(''magrittr'') # for the %>% that I love so well
# First get the grob
z <- ggplotGrob(p)
El objetivo final de esta operación es superponer la etiqueta de la faceta superior, pero el truco es que ambas facetas existen en la misma fila en el espacio de la cuadrícula.
Son una tabla dentro de una tabla (mire las filas con el nombre "tira", también tome nota de
zeroGrob
; estos serán útiles más adelante):
z
## TableGrob (13 x 14) "layout": 34 grobs
## z cells name grob
## 1 0 ( 1-13, 1-14) background rect[plot.background..rect.522]
## 2 1 ( 7- 7, 4- 4) panel-1-1 gTree[panel-1.gTree.292]
...
## 20 3 ( 7- 7,12-12) axis-r-1 zeroGrob[NULL]
## 21 3 ( 9- 9,12-12) axis-r-2 zeroGrob[NULL]
## 22 2 ( 6- 6, 4- 4) strip-t-1 gtable[strip]
## 23 2 ( 6- 6, 6- 6) strip-t-2 gtable[strip]
## 24 2 ( 6- 6, 8- 8) strip-t-3 gtable[strip]
## 25 2 ( 6- 6,10-10) strip-t-4 gtable[strip]
## 26 2 ( 7- 7,11-11) strip-r-1 gtable[strip]
## 27 2 ( 9- 9,11-11) strip-r-2 gtable[strip]
...
## 32 8 ( 3- 3, 4-10) subtitle zeroGrob[plot.subtitle..zeroGrob.519]
## 33 9 ( 2- 2, 4-10) title zeroGrob[plot.title..zeroGrob.518]
## 34 10 (12-12, 4-10) caption zeroGrob[plot.caption..zeroGrob.520]
Si amplía la primera franja, puede ver la estructura anidada:
z$grob[[22]]
## TableGrob (2 x 1) "strip": 2 grobs
## z cells name grob
## 1 1 (1-1,1-1) strip absoluteGrob[strip.absoluteGrob.451]
## 2 2 (2-2,1-1) strip absoluteGrob[strip.absoluteGrob.475]
Para cada grob, tenemos un objeto que enumera el orden en que se traza ( z ), la posición en la cuadrícula ( celdas ), una etiqueta ( nombre ) y una geometría ( grob ).
Como podemos crear gtables dentro de gtables, vamos a usar esto para trazar sobre nuestro diagrama original. Primero, necesitamos encontrar las posiciones en el diagrama que necesitan ser reemplazadas.
# Find the location of the strips in the main plot
locations <- grep("strip-t", z$layout$name)
# Filter out the strips (trim = FALSE is important here for positions relative to the main plot)
strip <- gtable_filter(z, "strip-t", trim = FALSE)
# Gathering our positions for the main plot
top <- strip$layout$t[1]
l <- strip$layout$l[c(1, 3)]
r <- strip$layout$r[c(2, 4)]
Una vez que tenemos las posiciones, necesitamos crear una tabla de reemplazo.
Podemos hacer esto con una matriz de listas (sí, es extraño. Simplemente rodar con él).
Esta matriz necesita tener tres columnas y dos filas en nuestro caso debido a las dos facetas y la brecha entre ellas.
Como solo vamos a reemplazar los datos en la matriz más adelante, crearemos uno con
zeroGrob
s:
mat <- matrix(vector("list", length = 6), nrow = 2)
mat[] <- list(zeroGrob())
# The separator for the facets has zero width
res <- gtable_matrix("toprow", mat, unit(c(1, 0, 1), "null"), unit(c(1, 1), "null"))
La máscara se crea en dos pasos, cubriendo el primer grupo de facetas y luego el segundo.
En la primera parte, estamos utilizando la ubicación que registramos anteriormente para tomar el grob apropiado del gráfico original y agregarlo en la parte superior de nuestra matriz de reemplazo de
res
, que abarca toda la longitud.
Luego agregamos esa matriz en la parte superior de nuestra gráfica.
# Adding the first layer
zz <- res %>%
gtable_add_grob(z$grobs[[locations[1]]]$grobs[[1]], 1, 1, 1, 3) %>%
gtable_add_grob(z, ., t = top, l = l[1], b = top, r = r[1], name = c("add-strip"))
# Adding the second layer (note the indices)
pp <- gtable_add_grob(res, z$grobs[[locations[3]]]$grobs[[1]], 1, 1, 1, 3) %>%
gtable_add_grob(zz, ., t = top, l = l[2], b = top, r = r[2], name = c("add-strip"))
# Plotting
grid.newpage()
print(grid.draw(pp))
Lamento necroing este hilo y autopromoción involuntaria, pero tuve la oportunidad de generalizar esto a una función
facet_nested()
y el código se puede encontrar
aquí
.
El código debe ser bastante independiente, es decir, no debe depender de otras funciones en el paquete (que es prácticamente un paquete personal público con algunas funciones de trazado aleatorio en este punto).
La función no se prueba ampliamente, pero pensé que podría ser de alguna conveniencia para las personas. Tal vez algunos buenos comentarios vendrán de esto.
Hay otras dos modificaciones que hice en esta función más allá del alcance de las tiras de agrupación.
Una es que no expande automáticamente las variables que faltan.
Esto se debe a que era de la opinión de que las facetas anidadas deberían poder coexistir con facetas no anidadas sin ninguna entrada al segundo o más argumentos en
vars()
al trazar con dos marcos de datos.
El segundo es que ordena las tiras de exterior a interior, de modo que el interior está más cerca de los paneles que el exterior, incluso cuando se establece el
switch
.
La reproducción de la trama en esta pregunta sería la siguiente, suponiendo que
df
es el
df
en la pregunta anterior:
p <- ggplot(df, aes(x = x, y = y)) +
geom_point() +
facet_nested(f1 ~ f2 + f3)
También hubo
una pregunta relacionada
con una trama de ejemplo más real, que funcionaría de la siguiente manera, suponiendo que
df
es el
df
de esa pregunta:
p <- ggplot(df, aes("", density)) +
geom_boxplot(width=0.7, position=position_dodge(0.7)) +
theme_bw() +
facet_nested(. ~ species + location + position) +
theme(panel.spacing=unit(0,"lines"),
strip.background=element_rect(color="grey30", fill="grey90"),
panel.border=element_rect(color="grey90"),
axis.ticks.x=element_blank()) +
labs(x="")