knitr: restraining from immediately plotting when do.call(grid.arrange, list_of_plots) - r

I have a two-rows figure which is composed by a plot on the first row and by a list of plots on the second row.
With knitr I do
\documentclass{article}
\begin{document}
<<plots, fig.width='\\textwidth', echo = FALSE, fig.height=5, fig.width = 10, warning = FALSE>>=
require(ggplot2)
plot <- ggplot() + geom_point(data=cars, aes(speed, dist)) + labs(x=NULL, y=NULL)
# create plot with function
makePlot <- function(myLabel) {
ggplot() + geom_point(data=cars, aes(speed, dist)) + labs(x=NULL,y=NULL,title=myLabel)
}
list_plot <- lapply(c("one","two","three"), makePlot)
require(gridExtra)
grob <- do.call(grid.arrange, c(list_plot, nrow=1, ncol=3)) # here R sends the plots to the graphical device!
grid.arrange(plot,
grob,
nrow=3)
#
\end{document}
which produces
The issue is that I do.call my list of plots, which immediately send to the graphical devices the plots.
Is there a fix to this, either in knitr or by avoiding do.call to spit the plot when passing it to grob?

Using ??grid.arrange we find the help page for arrangeGrob. This specifies the following:
arrangeGrob: return a grob without drawing
grid.arrange: draw on the current device
marrangeGrob: interface to arrangeGrob that can dispatch on multiple pages
The solution is therefore to use arrangeGrob instead of grid.arrange.
An added benefit is that a list of grobs can be passed with the grobs argument, so we can do away with the do.call construct.

Related

Why does ggplotly does not work in rmarkdown the same way ggplot does

I would like to use ggplotly for it's side effect the same way ggplot does or even graphics does. By this I mean when I knitr::knit or rmarkdown::render a Rmd document I expect print(obj) where obj is a ggplotly objcet to be in the report and that's not the case.
Can anyone explain what is going on?
Can anyone tell me how I can achieve want I want to do. I want to be able to plot a ggplotly graph into a function without returning the object (I want to return the underlying data of the graph) and I'd like the code to work for both ggplot and ggplotly (i.e. use the same code for a ggplot or ggplotly)
question.R file
#+ libs, echo = FALSE
suppressMessages({
library(ggplot2)
library(plotly)
library(rmarkdown)
})
#+ functions decl, echo = FALSE
df <- data.frame(x = 1:5, y = 1:5)
f_0 <- function(df) {
p <- ggplot(df, aes(x, y)) + geom_line()
# p or plot(p) or print(p) works
print(p)
return(df)
}
f_1 <- function(df) {
p <- ggplot(df, aes(x, y)) + geom_line()
p <- ggplotly(p)
# plot(p) crashes
# print p does not print in report
print(p)
# p standalone does not work either
p
return(df)
}
#' # plots
#' plot 0
#+ plot_0
res_0 <- f_0(df)
#' plot 1
#+ plot_1
res_1 <- f_1(df)
Render this file
rmarkdown::render("question.R")
The output
Editorial comment: As a point of style, it's usually a good idea to separate computation and plotting into distinct functions, because it increases modularity, makes code easier to maintain, and allows for finer control without parameter creep. The output of individual functions can then be easily mapped to separate knitr chunks.
Best solution: I know that you are specifically asking about not returning the plot object, but I just want to point out that returning it alongside the results provides the cleanest and most elegant solution:
---
output: html_document
---
```{r include=FALSE}
library( tidyverse )
df <- data_frame( x=1:5, y=1:5 )
```
```{r}
f <- function(df) {
gg <- ggplot(df, aes(x,y)) + geom_point()
list( result=df, plot=plotly::ggplotly(gg) )
}
res <- f(df)
res$plot
```
However, if you absolutely cannot return a plotly object from a function, you have some alternatives.
Option 1: Store the plotly object to the parent frame, providing access to it from the knitr chunk.
```{r}
f1 <- function(df) {
gg <- ggplot(df, aes(x,y)) + geom_point()
assign("ggp", plotly::ggplotly(gg), envir=parent.frame())
df # NOT returning a plot
}
res1 <- f1(df)
ggp # Let knitr handle the rendering naturally
```
Option 2: Render the plot to a temporary .html and then import it as an iframe
```{r, results='asis'} # <-- note the "asis" chunk option
f2 <- function(df)
{
gg <- ggplot(df, aes(x,y)) + geom_point()
htmlwidgets::saveWidget( plotly::ggplotly(gg), "temp.html")
print( htmltools::tags$iframe(src="temp.html", width=640, height=480) )
df # NOT returning a plot
}
res2 <- f2(df)
```
Explanation: Yihui can correct me if I'm wrong, but knitr basically does "Option 2" behind the scenes. It renders htmlwidgets, such as plotly objects, into temporary .html files and then combines those temporaries to produce the final document. The reason it fails inside a function is because the temporary file gets deleted when execution leaves the function scope. (You can replicate this yourself by using tempfile() instead of the persistent "temp.html"; the iframe object will display a "file not found" error in the final document.) There's probably a way to modify knitr hooks to retain the temporary files, but it's beyond my knowledge. Lastly, the reason you don't have this issue with basic ggplot objects is because their output goes to the plotting device, which persists across calling frames.

Saving ggplot graphs to pdf not formatting correctly in PDF

This is a follow on question to my original here
I am currently trying to save the outputs of a ggplot graph to .pdf but I am running into some problems.
I run the following on the dput data on the original question (I repaste everything below).
library(gridExtra)
pdf("Plots.pdf", onefile = TRUE)
for(j in 1:length(plotList)) {
grid.arrange(plotList[[j]], nrow = 2)
}
dev.off()
This saves the files as a pdf document but instead of getting two graphs per page I get one graph which takes up half a page. Is it possible to resize the graphs, when I select nrow = 3 I get the same problem, I get one graph in the top 3rd / half of the page and a blank space for the rest. I provide a screen shot of the output:
Here is a minimal example:
# Make empty list for plots
plotList <- list()
# Build plots
library(ggplot2)
for(i in 1:2){
plotList[[i]] <-
ggplot(mtcars, aes(mpg, cyl)) +
geom_point() +
labs(title = i)
}
# Save plots
library(gridExtra)
pdf("Plots.pdf", onefile = TRUE)
for(j in 1:length(plotList)) {
grid.arrange(plotList[[j]], nrow = 2)
}
dev.off()
Credit to #LachlanO
You problem comes from how you are calling grid.arrange. By including it in a loop, you are telling it to create multiple separate plots, like this:
grid.arrange(plotList[[1]], nrow = 2)
grid.arrange(plotList[[2]], nrow = 2)
grid.arrange(plotList[[3]], nrow = 2)
What you actually are trying to do is create one grid.arrange object which contains all the plots. To do this, you need call the function against the list:
do.call("grid.arrange", c(plotList, nrow=2))
Alternatively, you can use the cowplot package and use:
cowplot::plot_grid(plotlist = plotList, nrow = 2)
So to save the PDF you can use:
pdf("Plots.pdf", onefile = TRUE)
do.call("grid.arrange", c(plotList, nrow=2))
dev.off()

Plot multiple ggplot2 on same page

I have a working loop which generates and can save individual plots from each file saved in a directory.
I want to plot all of the returned plots in a single file as a 2x2 grid over multiple pages but cannot do this.
I have tried to save the plot objects in a list
pltList <- list()
pltList[]
for (f in 1:length(files)){
plot_object <- ggplot2(...) #make ggplot2 plot
print(plot_object)
pltList[[f]] <- plot_object #save ggplot2 plot in list
}
jpeg(filename.jpg)
par(mfrow=c(2,2)) #to generate 2x2 plot per page
print(pltList[[1]])
print(pltList[[2]])
...
print(pltList[[f]])
dev.off()
The problem is that the resulting saved .jpg file only contains the last plot and not a 2x2 grid of all plots over many pages which is what I want.
EDIT
My first problem is how to save each plot from the loop in the list - how can I view the saved objects from the list to make sure they have been saved correctly?
When I do print(pltList[1]), the resulting output is:
function (x, y, ...)
UseMethod("plot")
<bytecode: 0x0000000010f43b78>
<environment: namespace:graphics>
rather than the actual plot. It seems that the plots are not being saved in the list as expected. How can I correct for this?
Hopefully, once this is fixed, your plotting suggestions will work.
I did recently the same. I used grid.arrange().
library(ggplot2)
library(gridExtra)
library(grid)
p1<-ggplot()+geom_line(aes(x=1:10,y=1:10))
p2<-ggplot()+geom_line(aes(x=1:10,y=1:10))
p3<-ggplot()+geom_line(aes(x=1:10,y=1:10))
p4<-ggplot()+geom_line(aes(x=1:10,y=1:10))
grid.arrange(p1,p2,p3,p4, ncol=1, top=textGrob("Multiple Plots", gp=gpar(fontsize=12, font = 2)))
Assuming you need a PDF output where every page has multiple plots plotted as one, e.g.: if there are 12 plots then 4 plots per page.
Try this example:
library(ggplot2)
library(cowplot)
# list of 12 dummy plots, only title is changing.
pltList <- lapply(1:12, function(i){
ggplot(mtcars,aes(mpg,cyl)) +
geom_point() +
ggtitle(paste("Title",i))})
# outputs 3 jpeg files with 4 plots each.
for(i in seq(1,12,4))
ggsave(paste0("Temp",i,".jpeg"),
plot_grid(pltList[[i]],
pltList[[i+1]],
pltList[[i+2]],
pltList[[i+3]],nrow = 2))
# or we can output into 1 PDF with 3 pages using print
pdf("TempPDF.pdf")
for(i in seq(1,12,4))
print(plot_grid(pltList[[i]],
pltList[[i+1]],
pltList[[i+2]],
pltList[[i+3]],nrow = 2))
dev.off()
EDIT:
Another way using gridExtra, as suggested by #user20650:
library(gridExtra)
#output as PDF
pdf("multipage.pdf")
#use gridExtra to put plots together
marrangeGrob(pltList, nrow=2, ncol=2)
dev.off()

Multiple lattice plots with gridExtra

There is very convenient way of plotting multiple graphs and that's with gridExtra - grid.arrange:
grid.arrange(plot1,plot2,plot3,plot4,plot5,plot6,plot7,plot8,plot9, ncol=3)
The above command draws 3x3 graphs in one window.
Now, I'm using my own lattice setup to draw unique lines etc. via
trellis.par.set(my.setup)
However using the grid.arrange command for plotting multiple plots won't pass on the setup as the output plots are in default colours.
So the question is how to pass on the my.setup onto grid.arrange or alternatively how to plot easily multiple graphs in one go for lattice.
EDIT: Reproducible example:
Data <- data.frame(Col1=rnorm(10,0,1),Col2=rexp(10,2),Col3=rnorm(10,2,2),Col4=runif(10,0,2),
Time=seq(1,10,1))
trellis.par.set(col.whitebg())
newSet <- col.whitebg()
newSet$superpose.symbol$col <- c("blue3","orange2","gray1","tomato3")
newSet$superpose.symbol$pch <- 1
newSet$superpose.symbol$cex <- 1
newSet$superpose.line$col <- c("blue3","orange2","gray1","tomato3")
trellis.par.set(newSet)
Plot1 <- xyplot(Col1+Col2~Time, Data, type="spline")
Plot2 <- xyplot(Col2+Col3~Time, Data, type="spline")
Plot3 <- xyplot(Col1+Col3~Time, Data, type="spline")
Plot4 <- xyplot(Col3+Col4~Time, Data, type="spline")
grid.arrange(Plot1,Plot2,Plot3,Plot4, ncol=2)
I guess it's got something to do with the plot.trellis method not finding the global theme settings when it's wrapped in gridExtra::drawDetails.lattice. I don't understand these lattice options, but as far as I recall you can specify them explicitly at the plot level too,
pl = list(Plot1, Plot2, Plot3, Plot4)
# do.call(grid.arrange, c(pl, nrow=1))
do.call(grid.arrange, c(lapply(pl, update, par.settings=newSet), list(nrow=1)))

Printing multiple ggplots into a single pdf, multiple plots per page

I have a list, p, where each element of p is a list of ggplot2 plotting objects.
I would like to output a single pdf containing all the plots in p such that the plots in p[[1]] are on page 1, the plots in p[[2]] are on page 2, etc. How might I do this?
Here's some example code to provide you with the data structure I'm working with--apologies for the boring plots, I picked variables at random.
require(ggplot2)
p <- list()
cuts <- unique(diamonds$cut)
for(i in 1:length(cuts)){
p[[i]] <- list()
dat <- subset(diamonds, cut==cuts[i])
p[[i]][[1]] <- ggplot(dat, aes(price,table)) + geom_point() +
opts(title=cuts[i])
p[[i]][[2]] <- ggplot(dat, aes(price,depth)) + geom_point() +
opts(title=cuts[i])
}
This solution is independent of whether the lengths of the lists in the list p are different.
library(gridExtra)
pdf("plots.pdf", onefile = TRUE)
for (i in seq(length(p))) {
do.call("grid.arrange", p[[i]])
}
dev.off()
Because of onefile = TRUE the function pdf saves all graphics appearing sequentially in the same file (one page for one graphic).
Here is the most elegant solution to exporting a list of ggplot objects into a single pdf file using ggplot2::ggsave() and gridExtra::marrangeGrob().
library(ggplot2)
library(gridExtra)
Let's say you create multiple plots using lapply()
p <- lapply(names(mtcars), function(x) {
ggplot(mtcars, aes_string(x)) +
geom_histogram()
})
Save list of p plots:
ggsave(
filename = "plots.pdf",
plot = marrangeGrob(p, nrow=1, ncol=1),
width = 15, height = 9
)
Here is a simpler version of Sven's solution for the R beginners who would otherwise blindly use the do.call and nested lists that they neither need nor understand. I have empirical evidence. :)
library(ggplot2)
library(gridExtra)
pdf("plots.pdf", onefile = TRUE)
cuts <- unique(diamonds$cut)
for(i in 1:length(cuts)){
dat <- subset(diamonds, cut==cuts[i])
top.plot <- ggplot(dat, aes(price,table)) + geom_point() +
opts(title=cuts[i])
bottom.plot <- ggplot(dat, aes(price,depth)) + geom_point() +
opts(title=cuts[i])
grid.arrange(top.plot, bottom.plot)
}
dev.off()
I've tried some of these solutions but with no success. I researched a little more and found a solution that worked perfectly for me. It saves all my graphics in a single pdf file, each chart on one page.
library(ggplot2)
pdf("allplots.pdf",onefile = TRUE)
for(i in glist){
tplot <- ggplot(df, aes(x = as.factor(class), y = value))
print(tplot)
}
dev.off()
Here's one solution, but I don't particularly like it:
ggsave("test.pdf", do.call("marrangeGrob", c(unlist(p,recursive=FALSE),nrow=2,ncol=1)))
The problem is that it relies on there being the same number of plots in each group. If all(sapply(p, length) == 2) were false, then it would break.
A solution that worked for me with ggpubr package (package on Github, code for installation: devtools::install_github("kassambara/ggpubr")).
Let's say you have 4 plots p1, p2, p3 and p4.
library(ggpubr)
multi.page <- ggarrange(p1,p2,p3,p4, nrow=1, ncol=1) # for one plot per page
multi.page[[1]] # for seeing the first plot
ggexport(multi.page, filename="my-plots.pdf")
More examples of ggpubr use: http://www.sthda.com/english/articles/24-ggpubr-publication-ready-plots/81-ggplot2-easy-way-to-mix-multiple-graphs-on-the-same-page/
Here's a function based on Sven's approach, including the roxygen2 documentation and an example.
#' Save list of ggplot2 objects to single pdf
#'
#' #param list (list) List of ggplot2 objects.
#' #param filename (chr) What to call the pdf.
#'
#' #return Invisible NULL.
#' #export
#'
#' #examples
#' #plot histogram of each numeric variable in iris
#' list_iris = map(names(iris[-5]), ~ggplot(iris, aes_string(.)) + geom_histogram())
#' #save to a single pdf
#' GG_save_pdf(list_iris, "test.pdf")
GG_save_pdf = function(list, filename) {
#start pdf
pdf(filename)
#loop
for (p in list) {
print(p)
}
#end pdf
dev.off()
invisible(NULL)
}
A nice solution without the gridExtra package:
library(plyr)
library(ggplot2)
li = structure(p, class = c("gglist", "ggplot"))
print.gglist = function(x, ...) l_ply(x, print, ...)
ggsave(li, file = "test.pdf")

Resources