4

I have two factors and two continuous variables, and I use this to create a two-way facet plot using ggplot2. However, not all of my factor combinations have data, so I end up with dummy facets. Here's some dummy code to produce an equivalent output:

library(ggplot2)
dummy<-data.frame(x=rnorm(60),y=rnorm(60),
                  col=rep(c("A","B","C","B","C","C"),each=10),
                  row=rep(c("a","a","a","b","b","c"),each=10))
ggplot(data=dummy,aes(x=x,y=y))+
       geom_point()+
       facet_grid(row~col)

This produces this figure

Is there any way to remove the facets that don't plot any data? And, ideally, move the x and y axis labels up or right to the remaining plots? As shown in this GIMPed version

I've searched here and elsewhere and unless my search terms just aren't good enough, I can't find the same problem anywhere. Similar issues are often with unused factor levels, but here no factor level is unused, just factor level combinations. So facet_grid(drop=TRUE) or ggplot(data=droplevel(dummy)) doesn't help here. Combining the factors into a single factor and dropping unused levels of the new factor can only produce a 1-dimensional facet grid, which isn't what I want.

Note: my actual data has a third factor level which I represent by different point colours. Thus a single-plot solution allowing me to retain a legend would be ideal.

TJC
  • 185
  • 1
  • 7

4 Answers4

8

It's not too difficult to rearrange the graphical objects (grobs) manually to achieve what you're after.

  1. Load the necessary libraries.

    library(grid);
    library(gtable);
    
  2. Turn your ggplot2 plot into a grob.

    gg <- ggplot(data = dummy, aes(x = x,y = y)) +
            geom_point() +
            facet_grid(row ~ col);
    grob <- ggplotGrob(gg);
    

    Working out which facets to remove, and which axes to move where depends on the grid-structure of your grob. gtable_show_layout(grob) gives a visual representation of your grid structure, where numbers like (7, 4) denote a panel in row 7 and column 4.

  3. Remove the empty facets.

    # Remove facets
    idx <- which(grob$layout$name %in% c("panel-2-1", "panel-3-1", "panel-3-2"));
    for (i in idx) grob$grobs[[i]] <- nullGrob();
    
  4. Move the x axes up.

    # Move x axes up
    # axis-b-1 needs to move up 4 rows
    # axis-b-2 needs to move up 2 rows
    idx <- which(grob$layout$name %in% c("axis-b-1", "axis-b-2"));
    grob$layout[idx, c("t", "b")] <- grob$layout[idx, c("t", "b")] - c(4, 2);
    
  5. Move the y axes to the right.

    # Move y axes right
    # axis-l-2 needs to move 2 columns to the right
    # axis-l-3 needs ot move 4 columns to the right
    idx <- which(grob$layout$name %in% c("axis-l-2", "axis-l-3"));
    grob$layout[idx, c("l", "r")] <- grob$layout[idx, c("l", "r")] + c(2, 4);
    
  6. Plot.

    # Plot
    grid.newpage();
    grid.draw(grob);
    

enter image description here

Extending this to more facets is straightforward.

Maurits Evers
  • 49,617
  • 4
  • 47
  • 68
  • What is the pattern behind the panel naming `pane-2-1`, `panel-3-1`, etc ? One would expect it to be row-column. However, I had to modify a plot where the arrangement and naming of the panels was: row1:`"panel-1-1" | "panel-1-3"` row2:`"panel-2-1" | "panel-2-3"` row3:`"panel-1-2" | "panel-1-4"` row4:`"panel-2-2" | "panel-2-4"` Is there a general pattern or does one need to find out by trial and error? – fabern Aug 27 '18 at 16:54
  • 3
    At the risk of stating the obvious: Is there no option of automating the search for which panels to remove based on certain criteria? – M.Teich Jan 23 '20 at 12:14
  • @M.Teich That's a good question. Since the exact grid/matrix layout depends on what plot elements are shown and where, I haven't found a way to automate this other than taking a look at the output of `gtable_show_layout` and working out what's what. To see what I mean, consider moving the right facet strips to the left and observe how panel names have changed. – Maurits Evers Jan 23 '20 at 23:50
3

Maurits Evers solution worked great, but is quite cumbersome to modify. An alternative solution is to use facet_manual from {ggh4x}.

This is not equivalent though as it uses facet_wrap, but allows appropriate placement of the facets.

# devtools::install_github("teunbrand/ggh4x")
library(ggplot2)

dummy<-data.frame(x=rnorm(60),y=rnorm(60),
                  col=rep(c("A","B","C","B","C","C"),each=10),
                  row=rep(c("a","a","a","b","b","c"),each=10))

design <- "
  ABC
  #DE
  ##F
"

ggplot(data=dummy,aes(x=x,y=y))+
  geom_point()+
  ggh4x::facet_manual(vars(row,col), design = design, labeller = label_both)

Created on 2022-02-25 by the reprex package (v2.0.0)

JohannesNE
  • 1,343
  • 9
  • 14
0

One possible solution, of course, would be to create a plot for each factor combination separately and then combine them using grid.arrange() from gridExtra. This would probably lose my legend and would be an all around pain, would love to hear if anyone has any better suggestions.

TJC
  • 185
  • 1
  • 7
0

This particular case looks like a job for ggpairs (link to a SO example). I haven't used it myself, but for paired plots this seems like the best tool for the job.

In a more general case, where you're not looking for pairs, you could try creating a column with a composite (pasted) factor and facet_grid or facet_wrap by that variable (example)

Punintended
  • 727
  • 3
  • 7