How can I arrange an arbitrary number of ggplots using grid.arrange? - r

This is cross-posted on the ggplot2 google group
My situation is that I'm working on a function that outputs an arbitrary number of plots (depending upon the input data supplied by the user). The function returns a list of n plots, and I'd like to lay those plots out in 2 x 2 formation. I'm struggling with the simultaneous problems of:
How can I allow the flexibility to be handed an arbitrary (n) number of plots?
How can I also specify I want them laid out 2 x 2
My current strategy uses grid.arrange from the gridExtra package. It's probably not optimal, especially since, and this is key, it totally doesn't work. Here's my commented sample code, experimenting with three plots:
library(ggplot2)
library(gridExtra)
x <- qplot(mpg, disp, data = mtcars)
y <- qplot(hp, wt, data = mtcars)
z <- qplot(qsec, wt, data = mtcars)
# A normal, plain-jane call to grid.arrange is fine for displaying all my plots
grid.arrange(x, y, z)
# But, for my purposes, I need a 2 x 2 layout. So the command below works acceptably.
grid.arrange(x, y, z, nrow = 2, ncol = 2)
# The problem is that the function I'm developing outputs a LIST of an arbitrary
# number plots, and I'd like to be able to plot every plot in the list on a 2 x 2
# laid-out page. I can at least plot a list of plots by constructing a do.call()
# expression, below. (Note: it totally even surprises me that this do.call expression
# DOES work. I'm astounded.)
plot.list <- list(x, y, z)
do.call(grid.arrange, plot.list)
# But now I need 2 x 2 pages. No problem, right? Since do.call() is taking a list of
# arguments, I'll just add my grid.layout arguments to the list. Since grid.arrange is
# supposed to pass layout arguments along to grid.layout anyway, this should work.
args.list <- c(plot.list, "nrow = 2", "ncol = 2")
# Except that the line below is going to fail, producing an "input must be grobs!"
# error
do.call(grid.arrange, args.list)
As I am wont to do, I humbly huddle in the corner, eagerly awaiting the sagacious feedback of a community far wiser than I. Especially if I'm making this harder than it needs to be.

You're ALMOST there! The problem is that do.call expects your args to be in a named list object. You've put them in the list, but as character strings, not named list items.
I think this should work:
args.list <- c(plot.list, 2,2)
names(args.list) <- c("x", "y", "z", "nrow", "ncol")
as Ben and Joshua pointed out in the comments, I could have assigned names when I created the list:
args.list <- c(plot.list,list(nrow=2,ncol=2))
or
args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)

Try this,
require(ggplot2)
require(gridExtra)
plots <- lapply(1:11, function(.x) qplot(1:10,rnorm(10), main=paste("plot",.x)))
params <- list(nrow=2, ncol=2)
n <- with(params, nrow*ncol)
## add one page if division is not complete
pages <- length(plots) %/% n + as.logical(length(plots) %% n)
groups <- split(seq_along(plots),
gl(pages, n, length(plots)))
pl <-
lapply(names(groups), function(g)
{
do.call(arrangeGrob, c(plots[groups[[g]]], params,
list(main=paste("page", g, "of", pages))))
})
class(pl) <- c("arrangelist", "ggplot", class(pl))
print.arrangelist = function(x, ...) lapply(x, function(.x) {
if(dev.interactive()) dev.new() else grid.newpage()
grid.draw(.x)
}, ...)
## interactive use; open new devices
pl
## non-interactive use, multipage pdf
ggsave("multipage.pdf", pl)

I'm answering a bit late, but stumbled on a solution at the R Graphics Cookbook that does something very similar using a custom function called multiplot. Perhaps it will help others who find this question. I'm also adding the answer as the solution may be newer than the other answers to this question.
Multiple graphs on one page (ggplot2)
Here's the current function, though please use the above link, as the author noted that it's been updated for ggplot2 0.9.3, which indicates it may change again.
# Multiple plot function
#
# ggplot objects can be passed in ..., or to plotlist (as a list of ggplot objects)
# - cols: Number of columns in layout
# - layout: A matrix specifying the layout. If present, 'cols' is ignored.
#
# If the layout is something like matrix(c(1,2,3,3), nrow=2, byrow=TRUE),
# then plot 1 will go in the upper left, 2 will go in the upper right, and
# 3 will go all the way across the bottom.
#
multiplot <- function(..., plotlist=NULL, file, cols=1, layout=NULL) {
require(grid)
# Make a list from the ... arguments and plotlist
plots <- c(list(...), plotlist)
numPlots = length(plots)
# If layout is NULL, then use 'cols' to determine layout
if (is.null(layout)) {
# Make the panel
# ncol: Number of columns of plots
# nrow: Number of rows needed, calculated from # of cols
layout <- matrix(seq(1, cols * ceiling(numPlots/cols)),
ncol = cols, nrow = ceiling(numPlots/cols))
}
if (numPlots==1) {
print(plots[[1]])
} else {
# Set up the page
grid.newpage()
pushViewport(viewport(layout = grid.layout(nrow(layout), ncol(layout))))
# Make each plot, in the correct location
for (i in 1:numPlots) {
# Get the i,j matrix positions of the regions that contain this subplot
matchidx <- as.data.frame(which(layout == i, arr.ind = TRUE))
print(plots[[i]], vp = viewport(layout.pos.row = matchidx$row,
layout.pos.col = matchidx$col))
}
}
}
One creates plot objects:
p1 <- ggplot(...)
p2 <- ggplot(...)
# etc.
And then passes them to multiplot:
multiplot(p1, p2, ..., cols = n)

Related

Retrieve facet labels from a ggplot or a gtable/gTree/grob/gDesc object

I have data I'm plotting using ggplot's facet_grid:
My data:
species <- c("spcies1","species2")
conditions <- c("cond1","cond2","cond3")
batches <- 1:6
df <- expand.grid(species=species,condition=conditions,batch=batches)
set.seed(1)
df$y <- rnorm(nrow(df))
df$replicate <- 1
df$col.fill <- paste(df$species,df$condition,df$batch,sep=".")
My plot:
integerBreaks <- function(n = 5, ...)
{
library(scales)
breaker <- pretty_breaks(n, ...)
function(x){
breaks <- breaker(x)
breaks[breaks == floor(breaks)]
}
}
library(ggplot2)
p <- ggplot(df,aes(x=replicate,y=y,color=col.fill))+
geom_point(size=3)+facet_grid(~col.fill,scales="free_x")+
scale_x_continuous(breaks=integerBreaks())+
theme_minimal()+theme(legend.position="none",axis.title=element_text(size=8))
which gives:
Obviously the labels are long and come out pretty messed up in the figure so I was wondering if there's a way edit these labels in the ggplot object (p) or the gtable/gTree/grob/gDesc object (ggplotGrob(p)).
I am aware that one way of getting better labels is to use the labeller function when the ggplot object is created but in my case I'm specifically looking for a way to edit the facet labels after the ggplot object has been created.
As I mentioned in the comments, the facet names are nested quite deeply within the gtable that ggplotGrob() gives you. However, this is still possible and since the OP explicitly wants to edit them after being plotted, you can do this with:
library(grid)
gg <- ggplotGrob(p)
edited_grobs <- mapply(FUN = function(x, y) {
x[["grobs"]][[1]][["children"]][[2]][["children"]][[1]][["label"]] <- y
return(x)
},
gg$grobs[which(grepl("strip-t",gg$layout$name))],
unique(gsub("cond","c", df$condition)),
SIMPLIFY = FALSE)
gg$grobs[which(grepl("strip-t",gg$layout$name))] <- edited_grobs
grid.draw(gg)
Note that this extracts all the strips using gg$grobs[which(grepl("strip-t",gg$layout$name))] and passes them to the mapply to be reset with the gsub(...) that OP specified in their comment.
In general, if you want to access just one of the text labels, there is a very similar structure which I made use of in my mapply:
num_to_access <- 1
gg$grobs[which(grepl("strip-t",gg$layout$name))][[num_to_access]][["grobs"]][[1]][["children"]][[2]][["children"]][[1]]$label
So to access the 4th label for example all you would need to do is change num_to_acces to be 4. Hope this helps!

plotting a list of grobs

DISCLOSURE: I'm not sure how to make a reproducible example for this question.
I'm trying to plot a list of grobs using the gridExtra package.
I have some code that looks like this:
## Make Graphic Objects for Spec and raw traces
for (i in 1:length(morletPlots)){
gridplots_Spec[[i]]=ggplotGrob(morletPlots[[i]])
gridplots_Raw[[i]]=ggplotGrob(rawPlot[[i]])
gridplots_Raw[[i]]$widths=gridplots_Spec[[i]]$widths
}
names(gridplots_Spec)=names(morletPlots)
names(gridplots_Raw)=names(rawPlot)
## Combine spec and Raw traces
g=list()
for (i in 1:length(rawPlot)){
g[[i]]=arrangeGrob(gridplots_Spec[i],gridplots_Raw[i],heights=c(4/5,1/5))
}
numPlots = as.numeric(length(g))
##Plot both
for (i in 1:numPlots){
grid.draw(g[i],ncol=2)
}
Let me walk through the code.
morletPlots = a list of ggplots
rawplot = A list of ggplots
gridplots_spec and gridplots_Raw = list of grobs from the ggplots made above.
g = a list of the two grobs above combined so combining gridplots_spec[1] and gridplots_raw[1] so on and so on for the length of the list.
now my goal would be two plot all of those into 2 columns. But whenever I pass the gridplots_spec[i] through the grid.draw loop I get an error:
Error in UseMethod("grid.draw") :
no applicable method for 'grid.draw' applied to an object of class "list"
I can't unlist it becasue it just turns into a long character vector. any ideas?
If it's absolutely crucial I can spend the time to make an reproducible example but I'm more likely just missing a simple step.
Here's my interpretation of your script, if it's not the intended result you may want to use some bits and pieces to make your question reproducible.
library(grid)
library(gridExtra)
library(ggplot2)
morletPlots <- replicate(5, ggplot(), simplify = FALSE)
rawplot <- replicate(5, ggplot(), simplify = FALSE)
glets <- lapply(morletPlots, ggplotGrob)
graws <- lapply(rawplot, ggplotGrob)
rawlet <- function(raw, let, heights=c(4,1)){
g <- rbind(let, raw)
panels <- g$layout[grepl("panel", g$layout$name), ]
# g$heights <- grid:::unit.list(g$heights) # not needed
g$heights[unique(panels$t)] <- lapply(heights, unit, "null")
g
}
combined <- mapply(rawlet, raw = graws, let=glets, SIMPLIFY = FALSE)
grid.newpage()
grid.arrange(grobs=combined, ncol=2)
Edit I can't resist this mischievous hack to colour the plots for illustration; feel free to ignore it.
palette(RColorBrewer::brewer.pal(8, "Pastel1"))
ggplot.numeric = function(i) ggplot2::ggplot() +
theme(panel.background=element_rect(fill=i))
morletPlots <- lapply(1:5, ggplot)
rawplot <- lapply(1:5, ggplot)

R: display values in levelplot stratified by a grouping variable

In this following example, I need to display the values for each of the cells in each of the panels stratified by the grouping variable class:
library("lattice")
x <- seq(pi/4, 5*pi, length.out=5)
y <- seq(pi/4, 5*pi, length.out=5)
r1 <- as.vector(sqrt(outer(x^2, y^2, "+")))
r2 <- as.vector(sqrt(outer(x^2, y^2, "/")))
grid1 <- grid2 <- expand.grid(x=x, y=y)
grid1$z <- cos(r1^2)*exp(-r1/(pi^3))
grid2$z <- cos(r2^2)*exp(-r2/(pi^3))
grid <- rbind(grid1, grid2)
grid$class <- c(rep("addition",length(x)^2), rep("division", length(x)^2))
p <- levelplot(z~x*y | factor(class), grid,
panel=function(...) {
arg <- list(...)
panel.levelplot(...)
panel.text(arg$x, arg$y, round(arg$z,1))})
print(p)
However, the cell values are superimposed on each other because the panel option dose not distinguish between the two groups. How can I get the values to display correctly in each group?
Slightly behind the scenes, lattice uses an argument called subscripts to subset data for display in different panels. Often, it does so without you needing to be aware of it, but this is not one of those cases.
A look at the source code for panel.levelplotreveals that it handles subscripts on its own. args(panel.levelplot) shows that it's among the function's formal arguments, and the function's body shows how it uses them.
panel.text(), (really just a wrapper for lattice:::ltext.default()), on the other hand, doesn't know about or do anything with subscripts. From within a call to panel.text(x,y,z), the x, y, and z that are seen are the full columns of the data.frame grid, which is why you saw the overplotting that you did.
To plot text for the values that are a part of the current panel, you need to make explicit use of the subscripts argument, like this:
myPanel <- function(x, y, z, ..., subscripts=subscripts) {
panel.levelplot(x=x, y=y, z=z, ..., subscripts=subscripts)
panel.text(x = x[subscripts],
y = y[subscripts],
labels = round(z[subscripts], 1))
}
p <- levelplot(z~x*y | factor(class), grid, panel = myPanel)
print(p)

Save multiple ggplot2 plots as R object in list and re-displaying in grid

I would like to save multiple plots (with ggplot2) to a list during a large for-loop. And then subsequently display the images in a grid (with grid.arrange)
I have tried two solutions to this:
1 storing it in a list, like so:
pltlist[["qplot"]] <- qplot
however for some reason this does save the plot correctly.
So I resorted to a second strategy which is recordPlot()
This was able to save the plot correctly, but unable to
use it in a grid.
Reproducable Example:
require(ggplot2);require(grid);require(gridExtra)
df <- data.frame(x = rnorm(100),y = rnorm(100))
histoplot <- ggplot(df, aes(x=x)) + geom_histogram(aes(y=..density..),binwidth=.1,colour="black", fill="white")
qplot <- qplot(sample = df$y, stat="qq")
pltlist <- list()
pltlist[["qplot"]] <- qplot
pltlist[["histoplot"]] <- histoplot
grid.arrange(pltlist[["qplot"]],pltlist[["histoplot"]], ncol=2)
above code works but produces the wrong graph
in my actual code
Then I tried recordPlot()
print(histoplot)
c1 <- recordPlot()
print(qplot)
c2 <- recordPlot()
I am able to display all the plots individually
but grid.arrange produces an error:
grid.arrange(replayPlot(c1),replayPlot(c2), ncol=2) # = Error
Error in gList(list(wrapvp = list(x = 0.5, y = 0.5, width = 1, height = 1, :
only 'grobs' allowed in "gList"
In this thread Saving grid.arrange() plot to file
They dicuss a solution which utilizes arrangeGrob() instead
arrangeGrob(c1, c1, ncol=2) # Error
Error in vapply(x$grobs, as.character, character(1)) :
values must be length 1,
but FUN(X[[1]]) result is length 3
I am forced to use the recordPlot() instead of saving to a list since this does not produce the same graph when saved as when it is plotted immediately, which I unfortunately cannot replicate, sorry.
In my actual code I am doing a large for-loop, looping through several variables, making a correlation with each and making scatterplots, where I name the scatterplots dependent on their significans level. I then want to re-display the plots that were significant in a grid, in a dynamic knitr report.
I am aware that I could just re-plot the plots that were significant after the for-loop instead of saving them, (I can't save as png while doing knitr either). However I would like to find a way to dynammically save the plots as R-objects and then replot them in a grid afterwards.
Thanks for Reading
"R version 3.2.1"
Windows 7 64bit - RStudio - Version 0.99.652
attached base packages:
[1] grid grDevices datasets utils graphics stats methods base
other attached packages:
[1] gridExtra_2.0.0 ggplot2_1.0.1
I can think of two solutions.
1. If your goal is to just save the list of plots as R objects, I recommend:
saveRDS(object = pltlist, file = "file_path")
This way when you wish to reload in these graphs, you can just use readRDS(). You can then put them in cowplot or gridarrange. This command works for all lists and R Objects.
One caveat to this approach is if settings/labeling for ggplot2 is dependent upon things in the environment (not the data, but stuff like settings for point size, shape, or coloring) instead of the ggplot2 function used to make the graph), your graphs won't work until you restore your dependencies. One reason to save some dependencies is to modularize your scripts to make the graphs.
Another caveat is performance: From my experience, I found it is actually faster to read in the data and remake individual graphs than load in an RDS file of all the graphs when you have a large number of graphs (100+ graphs).
2. If your goal is to save an 'image' or 'picture' of each graph (single and/or multiplot as .png, .jpeg, etc.), and later adjust things in a grid manually outside of R such as powerpoint or photoshop, I recommend:
filenames <- c("Filename_1", "Filename_2") #actual file names you want...
lapply(seq_along(pltlist), function(i) {
ggsave(filename = filenames[i], plot = pltlist[[i]], ...) #use your settings here
})
Settings I like for single plots:
lapply(seq_along(pltlist), function(i) ggsave(
plot = pltlist[[i]],
filename = paste0("plot_", i, "_", ".tiff"), #you can even paste in pltlist[[i]]$labels$title
device = "tiff", width=180, height=180, units="mm", dpi=300, compression = "lzw", #compression for tiff
path = paste0("../Blabla") #must be an existing directory.
))
You may want to do the manual approach if you're really OCD about the grid arrangement and you don't have too many of them to make for publications. Otherwise, when you do grid.arrange you'll want to do all the specifications there (adjusting font, increasing axis label size, custom colors, etc.), then adjust the width and height accordingly.
Reviving this post to add multiplot here, as it fits exactly.
require(ggplot2)
mydd <- setNames( data.frame( matrix( rep(c("x","y","z"), each=10) ),
c(rnorm(10), rnorm(10), rnorm(10)) ), c("points", "data") )
# points data
# 1 x 0.733013658
# 2 x 0.218838717
# 3 x -0.008303382
# 4 x 2.225820069
# ...
p1 <- ggplot( mydd[mydd$point == "x",] ) + geom_line( aes( 1:10, data, col=points ) )
p2 <- ggplot( mydd[mydd$point == "y",] ) + geom_line( aes( 1:10, data, col=points ) )
p3 <- ggplot( mydd[mydd$point == "z",] ) + geom_line( aes( 1:10, data, col=points ) )
multiplot(p1,p2,p3, cols=1)
multiplot:
multiplot <- function(..., plotlist=NULL, file, cols=1, layout=NULL) {
library(grid)
# Make a list from the ... arguments and plotlist
plots <- c(list(...), plotlist)
numPlots = length(plots)
# If layout is NULL, then use 'cols' to determine layout
if (is.null(layout)) {
# Make the panel
# ncol: Number of columns of plots
# nrow: Number of rows needed, calculated from # of cols
layout <- matrix(seq(1, cols * ceiling(numPlots/cols)),
ncol = cols, nrow = ceiling(numPlots/cols))
}
if (numPlots==1) {
print(plots[[1]])
} else {
# Set up the page
grid.newpage()
pushViewport(viewport(layout = grid.layout(nrow(layout), ncol(layout))))
# Make each plot, in the correct location
for (i in 1:numPlots) {
# Get the i,j matrix positions of the regions that contain this subplot
matchidx <- as.data.frame(which(layout == i, arr.ind = TRUE))
print(plots[[i]], vp = viewport(layout.pos.row = matchidx$row,
layout.pos.col = matchidx$col))
}
}
}
Result:

How to combine custom panels with splom() (or xyplot() or pairs())

I'm having trouble combining heterogenous panels with lattice package tools. I tried splom(), pairs(), and xyplot(), but unsuccessfully so far. Suppose I have a simple time series data of 3 columns as xts object:
library(xts)
S = as.xts(apply(matrix(rnorm(300), ,3), 2, cumsum), Sys.Date()+1:100)
Diagonal panels (top left to bottom right or diag(5) format) need to show 3 density plots, one for each series.
Upper triangular panels need to show latticeExtra::densityplot (or equivalently panel.densityplot) for the three series. The order doesn't matter for now; I'll work it out later.
Lower triangular panels need to show horizontal box plots. I suppose panel.bwplot would work, but could not successfully tame it.
Here is a skeleton of what may work, but I'll be thankful for any successful version.
library(lattice); library(latticeExtra)
splom(as.data.frame(S),
upper.panel=function(){
panel.abline() # temporary placeholder
},
diag.panel = function(x, ...){
yrng <- current.panel.limits()$ylim
d <- density(x, na.rm=TRUE)
d$y <- with(d, yrng[1] + 0.95 * diff(yrng) * y / max(y) )
panel.lines(d)
diag.panel.splom(x, ...)
},
lower.panel = function(x, y, ...){
panel.abline() # temporary placeholder
},
pscale=0, as.matrix = TRUE
)

Resources