7

It's often convenient to make large nested lists to keep track of plots created with ggplot2.

For example, here's how I might store a handful of plots in a large list of plots, with sublists for topics and sub-topics.

summary_plots

  1. $Demographics
    • Demographics$Socioeconomic$Household_Income_Plot
    • Demographics$Socioeconomic$Education_Plot
    • Demographics$Age_Plot
  2. $Product_Usage
    • Purchase_Frequency_Plot
    • ....

How can I extract all of the ggplot2 plot objects from this entire list? I'd like to be able to create a 'flat', one-level list containing all of the plots that were contained in the original list.


Here's a quick example list:

generic_plot <- ggplot(mtcars) + geom_point(aes(x = wt, y = mpg)) 

summary_plots <- list() 
summary_plots$Demographics$Socioeconomic$Income <- generic_plot 
summary_plots$Demographics$Socioeconomic$Education <- generic_plot 
summary_plots$Demographics$Age <- generic_plot 
summary_plots$Product_Usage$Purchase_Frequency <- generic_plot

The desired result would be the equivalent of creating a list like the following:

list('Demographics.Socioeconomic.Income' = generic_plot,
     'Demographics.Socioeconomic.Education' = generic_plot,
     ...)
bschneidr
  • 6,014
  • 1
  • 37
  • 52
  • 1
    I don't think this is specific to lists of `ggplot` objects, I think it's just an issue of flattening nested lists, such as here: https://stackoverflow.com/q/48443179/5325862 – camille Apr 17 '19 at 16:19
  • 5
    Thanks for sharing that post. The wrinkle here is that `ggplot2` objects are themselves lists (`is.list(generic_plot) is TRUE`), so it can be tricky to extract them without unlisting or flattening the `ggplot2` object itself. And I think `ggplot2` objects are such a common use case that I don't think it's excessive to have it as its own question. – bschneidr Apr 17 '19 at 16:29
  • 1
    That's true, them being lists makes a simple `unlist` call not work. – camille Apr 17 '19 at 16:36
  • 3
    Since ggplot objects have class "ggplot", maybe a workable approach here would be to recursively traverse the levels of the list, extract each object with class equal to "ggplot", and return the result as a flattened list of ggplot objects. – eipi10 Apr 17 '19 at 16:40

1 Answers1

9

The code written in this answer by @Michael will accomplish this, with a small modification.

If we change the class check in the morelists <- ... line to only flatten if the class contains 'list' but does not contain the class 'gg', then it will not flatten the plots, and will return a flat list of ggplots.

flattenlist <- function(x){  
    morelists <- sapply(x, function(xprime) {
        'list' %in% class(xprime) & !('gg' %in% class(xprime))
    })
    out <- c(x[!morelists], unlist(x[morelists], recursive=FALSE))
    if(sum(morelists)){ 
        Recall(out)
    }else{
        return(out)
    }
}


plts <- flattenlist(summary_plots)

names(plts)

[1] "Demographics.Age"                    
[2] "Product_Usage.Purchase_Frequency"    
[3] "Demographics.Socioeconomic.Income"   
[4] "Demographics.Socioeconomic.Education"


lapply(plts, class)

$Demographics.Age
[1] "gg"     "ggplot"

$Product_Usage.Purchase_Frequency
[1] "gg"     "ggplot"

$Demographics.Socioeconomic.Income
[1] "gg"     "ggplot"

$Demographics.Socioeconomic.Education
[1] "gg"     "ggplot"
divibisan
  • 11,659
  • 11
  • 40
  • 58
  • 3
    I've used R for years, and never heard of `Recall()`. Nice use case, I was having issues trying to solve this, since apparently, `list.flatten(summary_plots, classes = "ggplot")` does nothing and `list.flatten(summary_plots, classes = "gg")` produces incorrect results (returns a sub-`ggplot` object). This function seemingly only looks at the first class of an object. – CoderGuy123 Apr 17 '19 at 17:50
  • 3
    Thanks, @divibisan for this answer. I've adapted this into a function with Roxygen2 documentation so it can be neatly incorporated into an R package. It's available here for anyone who might want to use it: https://gist.github.com/bschneidr/3f9862b8856f87d2b8f8a24878f76cc9 – bschneidr Apr 17 '19 at 21:07
  • Just adding here that this doesn't seem to work if `rep()` is used like `flattenlist(rep(ggplot_obj,2))`. But works if used like this: `flattenlist(rep(list(ggplot_obj),2))`. – mindlessgreen Apr 05 '22 at 10:49