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

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.

Related

Test saving ggplots

I'm creating bunch of tests for ggplots. I included things like checking labels, if the output object is ggplot etc. But I have no idea how to test if the plot was saved using testthat and ggsave. Do you have any idea how it can be done ?
Here is a way to test based on file size. If the file doesn't exist it is NA and if it does exist it should be > 0.
library(testthat)
library(ggplot2)
test_that("plot is saved to file", {
file <- tempfile(fileext = ".png")
expect_equal(file.size(file), NA_real_)
plot <- ggplot(mtcars, aes(wt, mpg)) +
geom_point()
ggsave(file, plot, "png")
expect_true(file.size(file) > 0)
unlink(file)
})

Creating a multiplot from the output of a user defined function with ggplot2

I created a function to automate creating ggplot column plots. However when I call the function it outputs a list:
Plotter<- function (df,title){
Plots <-ggplot2(df,aes(x=mpg,y=wt))+geom_col()+ggtitle(title)
print(Plots)
return(Plots)
}
plot1 <- Plotter(data,"test")
plot2 <- Plotter(data,"test2")
When I call ggarrange(plot1,plot2,ncol=2),
I get an error stating that ggarrange only accepts a ggplot,glist, but you have a list??
I checked the class of plot1 and it’s a list?
I also tried converting it to a grob, but that does not work. Can someone please let me know what I’m missing??
This works for me
library(gridExtra)
Plotter<- function (df,title){
Plots <-ggplot(df,aes(x=mpg,y=wt))+geom_col()+ggtitle(title)
print(Plots)
return(Plots)
}
data = data.frame(mpg = 1:10, wt = 1:10)
plot1 <- Plotter(data,"test")
plot2 <- Plotter(data,"test2")
grid.arrange(plot1,plot2,ncol=2)

Return ggplot2 figure from sourced function

i am struggling with R's print behavior when sourcing a function.
I want to write a function that generates a ggplot and returns that plot in order
to save it later.
So it should look something like this
file: func.R
make_plot <- function(data){
p <- ggplot(...)
print(p)
return(p)
}
source('func.R')
p <- make_plot(data)
ggsave('somewhere.png', plot=p)
my problem is that the generated plots are empty, and calling print(p)
outside of the function generates a empty plot as well.
If I run the code inside of the function interactively, everything is fine.
Any ideas?
thank you for your quick responses. I am very sorry, but the fault was all mine, the real code looked something like
file: func.R
make_plot <- function(data){
p <- ggplot(...)
p + geom_vline ...
print(p)
return(p)
}
source('func.R')
p <- make_plot(data)
ggsave('somewhere.png', plot=p)
So the retuned graphic object really was just empty.
It should obviously have been
p <- ggplot(...)
p <- p + geom_vline ...
thank you very much!

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

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.

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