Nested faces in ggplot2 groups

I came across a situation in which I want to create a plot that was faceted by three grouping variables. For this, I would simply use facet_grid(f1 ~ f2 + f3) , but the problem here is that the labels for f2 will be redundant, and it would be much better to cover the edges for f3 nested in 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) 

MWE Nested Facet Graphics

Again, I want to combine labels for f2 so that they are not so redundant.

Edit: this differs from other questions in that it asks how to use existing groupings to change the facet, rather than adding a new one.

+10
source share
2 answers

The answer to this question lies in the grid and gtable . Everything in the plot is laid out in a certain order, and you can find where everything is, if you dig a little.

 library('gtable') library('grid') library('magrittr') # for the %>% that I love so well # First get the grob z <- ggplotGrob(p) 

The ultimate goal of this operation is to overlay the top facet label, but the trick is that both of these faces exist on the same line in the grid space. They are a table inside a table (look at the lines called "strip", also pay attention to zeroGrob , they will be useful later):

 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] 

If you zoom in to the first bar, you can see the nested structure:

 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] 

For each grob, we have an object that lists the order in which it is depicted ( z ), position in the grid ( cells ), label ( name ) and geometry ( grob ).

Since we can create gtables inside gtables, we will use this to build on our original plot. First, we need to find positions in the plot that need to be replaced.

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

Once we have the positions, we need to create a replacement table. We can do this with a list matrix (yes, that's weird, just roll with it). This matrix should have three columns and two rows in our case due to two faces and a gap between them. Since we are just going to replace the data in the matrix later, we are going to create one with 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")) 

The mask is created in two stages, covering the first group of facets, and then the second. In the first part, we use the previously recorded location to grab the corresponding bar from the original graph and add it on top of our replacement matrix res , covering the entire length. Then we add this matrix on top of our graph.

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

Facet Nested Labels

+20
source

I apologize for the fact that this thread and unintentional self-promotion were doomed, but I have facet_nested() generalizing this to the facet_nested() function and the code can be found here . The code should be fairly autonomous, i.e. Should not rely on other features in the package (which is pretty much a publicly accessible personal package with some random build features).

The function has not been thoroughly tested, but I thought it might be useful to people. Maybe this will give a good review.

There are two other modifications that I made in this function outside of the grouping of bands. One of them is that it does not automatically expand missing variables. This is because I was of the opinion that nested facets should be able to coexist with non-nested facets without any entries for the 2nd or extra arguments in vars() when building with two data.frames. Secondly, it arranges the stripes from the outer to the inner, so that the inner part is closer to the panels than the outer one, even if the switch set.

The reproduction of the plot in this question will be as follows, assuming df is df in the question above:

 p <- ggplot(df, aes(x = x, y = y)) + geom_point() + facet_nested(f1 ~ f2 + f3) 

enter image description here

There was also a related question with a more realistic example of a graph that would work as follows, assuming df is the df from this question:

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

enter image description here

0
source

Source: https://habr.com/ru/post/1258971/


All Articles