1

I am trying to store multiple plots produced by ggplot2 into a list.

I am attempting to use the list function suggested in a previous thread, however I am having difficulty creating my own function to meet my needs.

First, I split a dataframe based on a factor into a list with the following code:

heatlist.germ <- split(heatlist.germ, f=as.factor(heatlist.germ$plot))

Afterwhich, I attempt to create a list function that I can later use lapply with.

plot_data_fcn <- function (heatlist.germ) {
  ggplot(heatlist.germ[[i]], aes(x=posX, y=posY, fill=germ_bin)) + 
    geom_tile(aes(fill=germ_bin)) + 
    geom_text(aes(label=germ_bin)) +
    scale_fill_gradient(low = "gray90", high="darkolivegreen4") +
    ggtitle(plot) +
    scale_x_continuous("Position X", breaks=seq(1,30)) +
    scale_y_continuous("Position Y (REVERSED)", breaks=seq(1,20))
  }

heatlist.test <- lapply(heatlist.germ[[i]], plot_data_fcn)

Two main things I am trying to accomplish:

  1. Store the 12 ggplots (hence 12 factors of plot) in a list.
  2. Create a title called "Plot [i] Germination".

Any help would be appreciated.

Cameron So
  • 139
  • 11
  • 2
    `plots <- lapply(heatlist.germ, plot_data_fcn)`, and remove the `[[i]]` from inside your function as well. – r2evans Jan 29 '20 at 04:44

1 Answers1

4

I don't have your data, so I'll simplify the plotting mechanism.

The first problem is that you should not use your [[i]] referencing in your function. Just have your function deal with data as-is, it really doesn't know that its argument is (in another environment) an element with a list. It knows just the object itself.

# a simple plot function
myfunc <- function(x) ggplot(x, aes_string(names(x)[1], names(x)[2])) + geom_point()
# a list of frames, nothing fancy here
datalist <- replicate(3, mtcars, simplify = FALSE)
# just call it ...
myplots <- lapply(datalist, myfunc)
class(myplots[[1]])
# [1] "gg"     "ggplot"

When myfunc is called, its argument x is just a data.frame, the function has no idea that x is the first (or second or third) frame in a list of frames.

If you want to include the nth frame with an index indicating which element it is, this is in my view "zipping" data together, so I suggest Map. (You can also use purrr::imap or related tidyverse functions.)

myfunc2 <- function(x, title = "") ggplot(x, aes_string(names(x)[1], names(x)[2])) + geom_point() + labs(title = title)
myplots <- Map(myfunc2, datalist, sprintf("Plot number %s", seq_along(datalist)))
class(myplots[[1]])
# [1] "gg"     "ggplot"

To understand how Map relates to lapply, then understand that lapply(datalist, myfunc) is "unrolled" to something like:

myfunc(datalist[[1]])
myfunc(datalist[[2]])
myfunc(datalist[[3]])

With Map, however, it takes one function that must accept one or more arguments in each call. With that, Map accepts as many lists (or vectors) as the function accepts arguments. The two functions are synonomously

lapply(datalist, myfunc) # data first,     function second
Map(myfunc, datalist)    # function first, data second

and a more complicated call unrolls like thus:

titles <- sprintf("Plot number %d", seq_along(datalist)) # "Plot number 1", ...
Map(myfunc2, datalist, titles)
# equivalent to
myfunc2(datalist[[1]], titles[[1]])
myfunc2(datalist[[2]], titles[[2]])
myfunc2(datalist[[3]], titles[[3]])

It doesn't really matter if each of the arguments is a true list (as in datalist) or a vector (as in titles), as long as they are the same length (or length 1).

r2evans
  • 141,215
  • 6
  • 77
  • 149
  • 1
    Thank you very much!! This is a very thorough explanation of the behind-the-scenes of each function. I appreciate this answer much more as it expands my understanding of the code itself. Thank you! – Cameron So Jan 29 '20 at 05:01