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.
Related
I am trying to automate a series of analyses which are intended to save a number of plots for later inspection. One of the plots will be accompanied by a table of values. I'd like to have them in the same pdf so that the users don't have to jump between files.
I have checked numerous questions on SO regarding outputting data frames to pdf, here are a couple of reasons why existing answers aren't satisfactory in my case:
Not familiar with knitr/Sweave
Batch generation of figures mean that I cannot do it manually via RStudio Viewer
grid.table based solutions do not generate the entire table.
Which brings me to my problems, say I have a table 48 x 5 in proportions. If I try to plot it out with grid.table(geno) it results in a cropped table showing some 20-30 rows in the middle. If I go with grid.table(geno, gp = gpar(fontsize=8)) to decrease the fontsize I get the following error message.
Error in gtable_table(d, name = "core", fg_fun = theme$core$fg_fun, bg_fun = theme$core$bg_fun, :
unused argument (gp = list(fontsize = 8)
)
Essentially I would like to be able to use it in this way:
library(grid)
library(gridExtra)
pdf(file="gtype.pdf", title = "Genotype data")
plotGenotype(geno, text_size = 10) # outputs a custom plot
grid.newpage()
grid.table(geno) # grid.table(geno, gp = gpar(fontsize=8))
dev.off()
The problem here is that I either get a cropped table or nothing at all, on the second page. I noticed that many people add height=11, width=8.5 to the pdf() call. I am not sure if/why that would make a difference but setting paper="a4" or height/width according to A4 does not make any difference in my case.
Q1: Is it not possible to get grid.table to resize based on content and not paper?
Q2: Is there some other way to get a data frame printed to a pdf without having to go through LaTeX based solutions?
(I am currently running R 3.3.1 and gridExtra 2.2.1)
Q1: Is it not possible to get grid.table to resize based on content and not paper?
It is possible, but generally not desirable. A table is meant to be read, and if text and spacings were determined by the page rather than the content, it would often yield unreadable results. Thus the usual advice: manually tweak the font size and padding, or split the table.
It is by no means a technical limitation: feel free to set the cell size to fit the page:
grid.newpage()
pushViewport(viewport(width=unit(0.8,"npc"), height=unit(0.8,"npc")))
g <- g2 <- tableGrob(iris[1:4, 1:3], cols = NULL, rows=NULL)
g2$heights <- unit(rep(1/nrow(g2), nrow(g2)), "npc")
grid.arrange(rectGrob(), rectGrob(), nrow=1, newpage = FALSE)
grid.arrange(g, g2, nrow=1, newpage = FALSE)
but with too much content for the page it's unclear what result is better
grid.newpage()
pushViewport(viewport(width=unit(0.8,"npc"), height=unit(0.8,"npc")))
g <- g2 <- tableGrob(iris[1:20, 1:3], cols = NULL, rows=NULL)
g3 <- tableGrob(iris[1:20, 1:3], cols = NULL, rows=NULL, theme=ttheme_default(base_size=7))
g2$heights <- g3$heights <- unit(rep(1/nrow(g2), nrow(g2)), "npc")
grid.arrange(rectGrob(), rectGrob(), rectGrob(), nrow=1, newpage = FALSE)
grid.arrange(g, g2, g3, nrow=1, newpage = FALSE)
If the page size can be changed, it is usually the best option. One can query the table size before drawing, convert it to inches, and pass it to the device.
g1 <- tableGrob(iris[1:4, 1:5])
g2 <- tableGrob(iris[1:20, 1:5])
maxheight <- convertHeight(sum(g2$heights), "in", TRUE)
pdf("fit.pdf", height=maxheight)
grid.draw(g1)
grid.newpage()
grid.draw(g2)
dev.off()
However, as far as I know all pages in a given pdf will have to have the same size (there might be ways around it, but tricky).
Sometimes we make plotting functions that alter graphical parameters, par. For instance if I want to combine base and grid I need to do some magic that alters par. Now I want to allow users to plot with such a function and not have the side effects in the next call to the same function. For example the following function if pressed twice results in the following two images each time:
library(ggplot2); library(grid); library(gridBase)
plotter <- function(){
#invisible(try(dev.off()))
layout(matrix(c(1, 2), nrow = 1, byrow = TRUE))
#Draw base plot
plot.new()
graphics::par(mar=c(1, 1, 1, 1), new = TRUE)
plot(1:10)
#Draw ggplot
plot.new()
vps <- baseViewports()
print( ggplot(mtcars, aes(mpg, hp)) + geom_point(), vp = vpStack(vps$figure,vps$plot))
}
plotter()
plotter()
Notice the smushed ggplot the second go round. Now I could fix this by uncommenting out the line invisible(try(dev.off())). But...when I then want to plot to an external device the call to pdf for example below, gets turned off. How can I restart the interactive device safely, or some other answer as I may be asking the wrong question.
pdf("test.pdf")
plotter()
dev.off()
I tried adding .pardefault <- par(no.readonly = T) at the beginning of the function call and par(.pardefault) at the end via: https://stackoverflow.com/a/9292673/1000343 but this does not work. This answer https://stackoverflow.com/a/5790430/1000343 does not work either.
Perhaps there's a way to use dev.cur against a list of internal plot devices to recognize if the plot device is RStudio or windows or such and only restart in those cases.
I'm attempting to step through a dataset and create a histogram and summary table for each factor and save the output as a .svg . The histogram is created using ggplot2 and the summary table using summary().
I have successfully used the code below to save the output to a single .pdf with each page containing the relevant histogram/table. However, when I attempt to save each histogram/table combo into a set of .svg images using ggsave only the ggplot histogram is showing up in the .svg. The table is just white space.
I've tried using dev.copy Cairo and svg but all end up with the same result: Histogram renders, but table does not. If I save the image as a .png the table shows up.
I'm using the iris data as a reproducible dataset. I'm not using R-Studio which I saw was causing some "empty plot" grief for others.
#packages used
library(ggplot2)
library(gridExtra)
library(gtable)
library(Cairo)
#Create iris histogram plot
iris.hp<-ggplot(data=iris, aes(x=Sepal.Length)) +
geom_histogram(binwidth =.25,origin=-0.125,
right = TRUE,col="white", fill="steelblue4",alpha=1) +
labs(title = "Iris Sepal Length")+
labs(x="Sepal Length", y="Count")
iris.list<-by(data = iris, INDICES = iris$Species, simplify = TRUE,FUN = function(x)
{iris.hp %+% x + ggtitle(unique(x$Species))})
#Generate list of data to create summary statistics table
sum.str<-aggregate(Sepal.Length~Species,iris,summary)
spec<-sum.str[,1]
spec.stats<-sum.str[,2]
sum.data<-data.frame(spec,spec.stats)
sum.table<-tableGrob(sum.data)
colnames(sum.data) <-c("species","sep.len.min","sep.len.1stQ","sep.len.med",
"sep.len.mean","sep. len.3rdQ","sep.len.max")
table.list<-by(data = sum.data, INDICES = sum.data$"species", simplify = TRUE,
FUN = function(x) {tableGrob(x)})
#Combined histogram and summary table across multiple plots
multi.plots<-marrangeGrob(grobs=(c(rbind(iris.list,table.list))),
nrow=2, ncol=1, top = quote(paste(iris$labels$Species,'\nPage', g, 'of',pages)))
#bypass the class check per #baptiste
ggsave <- ggplot2::ggsave; body(ggsave) <- body(ggplot2::ggsave)[-2]
#
for(i in 1:3){
multi.plots<-marrangeGrob(grobs=(c(rbind(iris.list[i],table.list[i]))),
nrow=2, ncol=1,heights=c(1.65,.35),
top = quote(paste(iris$labels$Species,'\nPage', g, 'of',pages)))
prefix<-unique(iris$Species)
prefix<-prefix[i]
filename<-paste(prefix,".svg",sep="")
ggsave(filename,multi.plots)
#dev.off()
}
Edit removed theme tt3 that #rawr referenced. It was accidentally left in example code. It was not causing the problem, just in case anyone was curious.
Edit: Removing previous answer regarding it working under 32bit install and not x64 install because that was not the problem. Still unsure what was causing the issue, but it is working now. Leaving the info about grid.export as it may be a useful alternative for someone else.
Below is the loop for saving the .svg's using grid.export(), although I was having some text formatting issues with this (different dataset).
for(i in 1:3){
multi.plots<-marrangeGrob(grobs=(c(rbind(iris.list[i],table.list[i]))),
nrow=2, ncol=1,heights=c(1.65,.35), top =quote(paste(iris$labels$Species,'\nPage', g,
'of',pages)))
prefix<-unique(iris$Species)
prefix<-prefix[i]
filename<-paste(prefix,".svg",sep="")
grid.draw(multi.plots)
grid.export(filename)
grid.newpage()
}
EDIT: As for using arrangeGrob per #baptiste's comment. Below is the updated code. I was incorrectly using the single brackets [] for the returned by list, so I switched to the correct double brackets [[]] and used grid.draw to on the ggsave call.
for(i in 1:3){
prefix<-unique(iris$Species)
prefix<-prefix[i]
multi.plots<-grid.arrange(arrangeGrob(iris.list[[i]],table.list[[i]],
nrow=2,ncol=1,top = quote(paste(iris$labels$Species))))
filename<-paste(prefix,".svg",sep="")
ggsave(filename,grid.draw(multi.plots))
}
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:
I am outputting plots as png based on grouping according to the dataframe vector called "chr". This generates lots of plots but I would like to have them all in one png. I am using the plot function in r rather than ggplot2.
My code so far:
for(jj in ind){
png(paste("/Users/sebastianzeki/Desktop/SequencingScripts/Plots/",jj,".png"))
indic = which(ret$chr == jj)
plot(ret$binRight[indic],ret$SummedZScore[indic],pch=19,xlab="Locus",ylab="Summed ZScore",type="h",lwd=20, space=0)
dev.off()
How can I get all the plots on one png (or pdf if thats easier)?
Suppose length(ind) = 10
png(paste("/Users/sebastianzeki/Desktop/SequencingScripts/Plots/",jj,".png"))
par(mfrow=c(5,2))
for(jj in ind){
indic = which(ret$chr == jj)
plot(ret$binRight[indic],ret$SummedZScore[indic],pch=19,xlab="Locus",ylab="Summed ZScore",type="h",lwd=20, space=0)
}
dev.off()
This can make one png file or if you want to make a pdf file
How to print R graphics to multiple pages of a PDF and multiple PDFs?
Look at the above thread for help.
A simple example :
png("temp.png", width = 600, height = 2000)
par(mfrow=c(8,3), mar = rep(0.5, 4), oma = rep(0.5, 4))
for (i in 1:24) {
hist(runif(20), main = NULL)
}
dev.off()