19

Huge fan of facet plots in ggplot2. However, sometimes I have too many subplots and it'd be nice to break them up into a list of plots. For example

df <- data.frame(x=seq(1,24,1), y=seq(1,24,1), z=rep(seq(1,12),each=2))
df
    x  y  z
1   1  1  1
2   2  2  1
3   3  3  2
4   4  4  2
5   5  5  3
.   .  .  .
.   .  .  .

myplot <- ggplot(df,aes(x=x, y=y))+geom_point()+facet_wrap(~z)
myplot

enter image description here

How would I write a function to take the resulting plot and split it into a list of plots? Something along these lines

splitFacet <- function(subsPerPlot){
  # Method to break a single facet plot into a list of facet plots, each with at most `subsPerPlot` subplots

  # code...

  return(listOfPlots)
}
zx8754
  • 52,746
  • 12
  • 114
  • 209
Ben
  • 20,038
  • 30
  • 112
  • 189
  • 4
    Wouldn't it be easier to create seperate plots? E.g. like `myplots <- lapply(unique(df$z), function(id) ggplot(subset(df, z == id), aes(x=x, y=y))+geom_point() + ylim(range(df$y)) + xlim(range(df$x)) ); do.call(gridExtra::grid.arrange, myplots[3:6])`. – lukeA May 28 '15 at 15:38
  • 1
    You should add `facet_wrap(~z)` if you want a strip label with the z value to be included in the plot. – eipi10 May 28 '15 at 15:56
  • 1
    @lukeA I'm not sure that plays nicely with legends – Ben May 28 '15 at 16:02
  • @Ben did you figure this out in the end? I'm looking for a similar solution. – Pete900 Sep 15 '15 at 06:37
  • @Pete900 Unfortunately no – Ben Sep 15 '15 at 14:52

4 Answers4

19

Split plot into individual plots

We build a function along these steps :

  1. We go through the structure of the object to get the names of the variables used for faceting (here 'z').
  2. We overwrite the facet element of our plot object with the one from the empty ggplot object (so if we print it at this stage facets are gone).
  3. We extract the data and split it along the variables we identified in 1st step.
  4. We overwrite the original data with each subset (12 times here) and store all outputs in a list.

code

splitFacet <- function(x){
  facet_vars <- names(x$facet$params$facets)         # 1
  x$facet    <- ggplot2::ggplot()$facet              # 2
  datasets   <- split(x$data, x$data[facet_vars])    # 3
  new_plots  <- lapply(datasets,function(new_data) { # 4
    x$data <- new_data
    x})
}    

new_plots <- splitFacet(myplot)
length(new_plots) # [1] 12
new_plots[[3]]    # 3rd plot


Split plot into faceted plots of n subplots max

If we want to keep the facets but have less plots by facet we can skip step 2, and rework our split instead so it includes several values of the variables used for faceting.

Rather than making a separate function we'll generalize the 1st, n is the number of facets you get by plot.

n = NULL means you get the previous output, which is slightly different from n = 1 (one facet by plot).

splitFacet <- function(x, n = NULL){
  facet_vars <- names(x$facet$params$facets)               # 1
  if(is.null(n)){
    x$facet  <- ggplot2::ggplot()$facet                    # 2a
    datasets <- split(x$data, x$data[facet_vars])          # 3a
  } else {
    inter0 <- interaction(x$data[facet_vars], drop = TRUE) # 2b
    inter  <- ceiling(as.numeric(inter0)/n)
    datasets <- split(x$data, inter)                       # 3b
  }
  new_plots  <- lapply(datasets,function(new_data) {       # 4
    x$data <- new_data
    x})
} 

new_plots2 <- splitFacet(myplot,4)
length(new_plots2) # [1] 3
new_plots2[[2]]    


This might come in handy too :

unfacet <- function(x){
  x$facet <- ggplot2::ggplot()$facet
  x
}

The tidy way

If the code is available, no need to go through all this trouble, we can split the data before feeding it to ggplot :

library(tidyverse)
myplots3 <-
  df %>% 
  split(ceiling(group_indices(.,z)/n_facets)) %>% 
  map(~ggplot(.,aes(x =x, y=y))+geom_point()+facet_wrap(~z))

myplots3[[3]]

moodymudskipper
  • 46,417
  • 11
  • 121
  • 167
5

While I was looking for a solution for this I can across ggplus. Specifically the function facet_multiple:

https://github.com/guiastrennec/ggplus

It lets you split a facet over a number of pages by specifying the amount of plots you want per page. In your example it would be:

library(ggplus)

df <- data.frame(x=seq(1,24,1), y=seq(1,24,1), z=rep(seq(1,12),each=2))

myplot <- ggplot(df,aes(x=x, y=y))+geom_point()

facet_multiple(plot = myplot, facets = 'z', ncol = 2, nrow = 2)

Is this the sort of thing you need? It worked a treat for me.

Pete900
  • 2,016
  • 1
  • 21
  • 44
  • install.packages("ggplus") Installing package into ... Warning in install.packages : package ‘ggplus’ is not available (for R version 3.2.2) Anything to solve that ? – Matias Andina Oct 15 '15 at 00:11
  • 1
    Ah, sorry I'm not sure. Maybe you could post on the github page and ask for help: https://github.com/guiastrennec/ggplus. I'm still on v 3.2.1 and now you've mentioned that I'm not going to upgrade. Let me know if you resolve the problem. Thanks. Pete – Pete900 Oct 15 '15 at 05:17
3

This is similar to Moody_Muddskipper's answer, but works with any type of faceting (facet_grid or facet_wrap), handles arbitrary expressions in facets, and doesn't draw facet strip bars.

library(rlang)
library(ggplot2)

split_facets <- function(x) {
  facet_expr <- unlist(x[["facet"]][["params"]][c("cols", "rows", "facets")])
  facet_levels <- lapply(facet_expr, rlang::eval_tidy, data = x[["data"]])
  facet_id <- do.call(interaction, facet_levels)
  panel_data <- split(x[["data"]], facet_id)
  plots <- vector("list", length(panel_data))
  for (ii in seq_along(plots)) {
    plots[[ii]] <- x
    plots[[ii]][["data"]] <- panel_data[[ii]]
    plots[[ii]][["facet"]] <- facet_null()
  }
  plots
}

split_facets(ggplot(df,aes(x=x, y=y))+geom_point()+facet_wrap(~z))
split_facets(ggplot(df,aes(x=x, y=y))+geom_point()+facet_grid(z %% 2 ~ z %% 5))

It uses rlang::eval_tidy to evaluate the facet expressions, combines them into a single categorical factor, then uses that to split the data. It also "suppresses" each subplot's faceting part by replacing it with facet_null().

Nathan Werth
  • 5,093
  • 18
  • 25
0

Posting this for anyone wanting to use ggplus. ggplus will work with later versions of R, but you need to install it using the developer's directions, i.e.

 devtools::install_github("guiastrennec/ggplus")

I ran into the same issue when trying to install it using RStudio, then realized that it's just not one of the "standard packages." I'm using 3.4.4.

luvdata
  • 9
  • 1