Since it is possible to export R plots to PDF or PNG or SVG etc., is it also possible to export an R plot to multiple formats at once? E.g., export a plot to PDF and PNG and SVG without recalculating the plot?
Without using ggplot2 and other packages, here are two alternative solutions.
Create a function generating a plot with specified device and sapply it
# Create pseudo-data
x <- 1:10
y <- x + rnorm(10)
# Create the function plotting with specified device
plot_in_dev <- function(device) {
do.call(
device,
args = list(paste("plot", device, sep = ".")) # You may change your filename
)
plot(x, y) # Your plotting code here
dev.off()
}
wanted_devices <- c("png", "pdf", "svg")
sapply(wanted_devices, plot_in_dev)
Use the built-in function dev.copy
# With the same pseudo-data
# Plot on the screen first
plot(x, y)
# Loop over all devices and copy the plot there
for (device in wanted_devices) {
dev.copy(
eval(parse(text = device)),
paste("plot", device, sep = ".") # You may change your filename
)
dev.off()
}
The second method may be a little tricky because it requires non-standard evaluation. Yet it works as well. Both methods work on other plotting systems including ggplot2 simply by substituting the plot-generating codes for the plot(x, y) above - you probably need to print the ggplot object explicitly though.
Yes, absolutely! Here is the code:
library(ggplot2)
library(purrr)
data("cars")
p <- ggplot(cars, aes(speed, dist)) + geom_point()
prefix <- file.path(getwd(),'test.')
devices <- c('eps', 'ps', 'pdf', 'jpeg', 'tiff', 'png', 'bmp', 'svg', 'wmf')
walk(devices,
~ ggsave(filename = file.path(paste(prefix, .x)), device = .x))
Related
Im trying to use the caTools package to combine multiple plots into a gif.
My basic code looks like :
for( i in 1:100){
plot(....) // plots few points and lines, changes slightly with each i
}
I would like to combine these to a gif to see the "evolution" of the plot.
However for write.gif() from caTools, I need to give an image as an input.
For each i, how do I convert the plot into an image without
saving to disc as an intermediate step
depending on ImageMagick or similar external dependencies.
Please feel free to point out if this is a duplicate. [ Creating a Movie from a Series of Plots in R doesnt seem to answer this ]
EDIT: Basically this requires us to convert a plot to a matrix. Since this very likely happens every time someone saves a plot, it should not be very difficult. However Im not able to get hold of how to exactly do this.
I suggest using the animation package and ImageMagick instead:
library(animation)
## make sure ImageMagick has been installed in your system
saveGIF({
for (i in 1:10) plot(runif(10), ylim = 0:1)
})
Otherwise you could try it in the veins of this (plenty of room for optimization):
library(png)
library(caTools)
library(abind)
# create gif frames and write them to pngs in a temp dir
dir.create(dir <- tempfile(""))
for (i in 1:8) {
png(file.path(dir, paste0(sprintf("%04d", i), ".png")))
plot(runif(10), ylim = 0:1, col = i)
dev.off()
}
# read pngs, create global palette, convert rasters to integer arrays and write animated gif
imgs <- lapply(list.files(dir, full.names = T), function(fn) as.raster(readPNG(fn)))
frames <- abind(imgs, along = 3) # combine raster pngs in list to an array
cols <- unique(as.vector(frames)) # determine unique colors, should be less then 257
frames <- aperm(array(match(frames, cols) - 1, dim = dim(frames)), c(2,1,3)) # replace rgb color codes (#ffffff) by integer indices in cols, beginning with 0 (note: array has to be transposed again, otherwise images are flipped)
write.gif(
image = frames, # array of integers
filename = tf <- tempfile(fileext = ".gif"), # create temporary filename
delay = 100, # 100/100=1 second delay between frames
col = c(cols, rep("#FFFFFF", 256-length(cols))) # color palette with 256 colors (fill unused color indices with white)
)
# open gif (windows)
shell.exec(tf)
Is that what you are looking for?
library(ggplot2)
a <- 0:10
df <- data.frame(a=a,b=a)
steps <-function(end){
a <- ggplot(df[1:end,], aes(a,b)) +
geom_point() +
scale_x_continuous(limits=c(0,10)) +
scale_y_continuous(limits=c(0,10))
print(a)
}
gif <- function() {
lapply(seq(1,11,1), function(i) {
steps(i)
})
}
library(animation)
saveGIF(gif(), interval = .2, movie.name="test.gif")
I liked #ttlngr's answer but I wanted something a bit simpler (it still uses ImageMagick).
saveGIF({
for (i in 1:10){
a <- ggplot(df[1:i,], aes(a,b)) +
geom_point() +
scale_x_continuous(limits=c(0,10)) +
scale_y_continuous(limits=c(0,10))
print(a)}
}, interval = .2, movie.name="test.gif")
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:
This is really one of two questions - either:
1) How do I store the result of a print() call [i.e. x <- print(something) ] without sending anything to current graphics output?
-or-
2) Is there a function or method in ggplot that will store a plot() call to a variable without calling plot() directly? ggplotGrob is in the ballpark, but a ggplotGrob object doesn't return a list with $data in it the same way you get when you store the result of print() to a variable.
I'm using a technique picked up from this SO answer to pull out the points of a geom_density curve, and then using that data to generate some annotations. I've outlined the issue below -- when I call this as a function, I get the undesired intermediate plot object in my pdf, along with the final plot. The goal is to get rid of that undesired plot; given that base hist() has a plot = FALSE option I was hopeful that someone who knows something more about R viewports would be able to fix my plot() call (solution #1), but any solution is fine, frankly.
library(ggplot2)
library(plyr)
demo <- function (df) {
p <- ggplot(
df
,aes(
x = rating
)
) +
geom_density()
#plot the object so we can access $data
render_plot <- plot(p + ggtitle("Don't want this plot"))
#grab just the DF for the density line
density_df <- render_plot$data[[1]]
#get the maximum density value
max_y <- ddply(density_df, "group", summarise, y = max(y))
#join that back to the data to find the matching row
anno <- join(density_df, max_y, type = 'inner')
#use this to annotate
p <- p + annotate(
geom = 'text'
,x = anno$x
,y = anno$y
,label = round(anno$density, 3)
) +
ggtitle('Keep this plot')
return(p)
}
#call to demo outputs an undesired plot to the graphics device
ex <- demo(movies[movies$Comedy ==1,])
plot(ex)
#this is problematic if you are trying to make a PDF
#a distinct name for the pdf to avoid filesystem issues
unq_name <- as.character(format(Sys.time(), "%X"))
unq_name <- gsub(':', '', unq_name)
pdf(paste(unq_name , '.pdf', sep=''))
p <- demo(movies[movies$Drama ==1,])
print(p)
dev.off()
Use ggplot_build:
render_plot <- ggplot_build(p + ggtitle("Don't want this plot"))
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")
I’d really appreciate your help with the following problem. I know several ways to save a single plot to a file. My question is: How do I correctly save a multiplot to a file?
To begin with, I’m not an experienced R user. I use ggplot2 to create my plots, and another thing I should probably mention is that I use the RStudio GUI. Using an example from the R Cookbook, I'm able to create multiple plots in one window.
I would like to save this so-called multiplot to a file (preferably as jpeg), but somehow fail to do this.
I’m creating the multiplot as follows:
##define multiplot function
multiplot <- function(..., plotlist=NULL, cols) {
require(grid)
# Make a list from the ... arguments and plotlist
plots <- c(list(...), plotlist)
numPlots = length(plots)
# Make the panel
plotCols = cols # Number of columns of plots
plotRows = ceiling(numPlots/plotCols) # Number of rows needed, calculated from # of cols
# Set up the page
grid.newpage()
pushViewport(viewport(layout = grid.layout(plotRows, plotCols)))
vplayout <- function(x, y)
viewport(layout.pos.row = x, layout.pos.col = y)
# Make each plot, in the correct location
for (i in 1:numPlots) {
curRow = ceiling(i/plotCols)
curCol = (i-1) %% plotCols + 1
print(plots[[i]], vp = vplayout(curRow, curCol ))
}
}
## define subplots (short example here, I specified some more aesthetics in my script)
plot1a <- qplot(variable1,variable2,data=Mydataframe1)
plot1b <- qplot(variable1,variable3,data=Mydataframe1)
plot1c <- qplot(variable1,variable2,data=Mydataframe2)
plot1d <- qplot(variable1,variable3,data=Mydataframe2)
## plot in one frame
Myplot <- multiplot(plot1a,plot1b,plot1c,plot1d, cols=2)
This gives the desired result. The problem arises when I try to save to a file. I can do this manually in RStudio (using Export -> Save plot as image), but I would like to run everything in a script. I manage to save only subplot1d (which is last_plot()), and not the complete multiplot.
What I’ve tried so far:
Using ggsave
ggsave(filename = "D:/R/plots/Myplots.jpg")
This results in only subplot 1d being saved.
Using jpeg(), print() and dev.off()
jpeg(filename = "Myplot.jpg", pointsize =12, quality = 200, bg = "white", res = NA, restoreConsole = TRUE)
print(Myplot)
dev.off()
This results in a completely white image (just the background I assume). print(Myplot) returns NULL.
Not sure what I’m doing wrong here. My lack of understanding R is the reason I am stuck trying to find a solution. Can anyone explain what I’m doing wrong and perhaps suggest a way to solve my problem(s)?
Its because Myplot is the returned value from your multiplot function, and it returns nothing (its job is to print the graphs). You need to call multiplot with the jpeg device open:
jpeg(filename = "Myplot.jpg", pointsize =12, quality = 200, bg = "white", res = NA, restoreConsole = TRUE)
multiplot(plot1a,plot1b,plot1c,plot1d, cols=2)
dev.off()
should work.
Using the example code (R cookbook), it works for me
png("chickweight.png")
multiplot(p1, p2, p3, p4, cols=2)
dev.off()
And for completeness sake, ggsave does not work as it only saves the last printed ggplot object, which in your case is just the last plot. This is caused by the fact that multiplot creates the plot by drawing the ggplot objects onto different subsets of the total graphics device. An alternative is to create the plot by combining the ggplot objects into one big ggplot object, and then printing the object. This would be compatible with ggsave. This approach is implemented by arrangeGrob in the gridExtra package.