Saving a graph with ggsave after using ggplot_build and ggplot_gtable - r

I am modifying a graph built with ggplot by altering the data produced by ggplot_build (for a reason similar to Include space for missing factor level used in fill aesthetics in geom_boxplot). As far as I understand the help I found on this topic, I should be able to save the result by applying ggplot_gtable and arrangeGrob before calling ggsave on the results (Saving grid.arrange() plot to file).
However I obtain an error "plot should be a ggplot2 plot", also with this simple reproductible example:
require('ggplot2')
require('gridExtra')
df <- data.frame(f1=factor(rbinom(100, 1, 0.45), label=c("m","w")),
f2=factor(rbinom(100, 1, 0.45), label=c("young","old")),
boxthis=rnorm(100))
g <- ggplot(aes(y = boxthis, x = f2, fill = f1), data = df) + geom_boxplot()
dd <- ggplot_build(g)
# Printing the graph works:
print(arrangeGrob(ggplot_gtable(dd)))
# Saving the graph doesn't:
ggsave('test.png',arrangeGrob(ggplot_gtable(dd)))
Can anybody explain why this does not work ? Is there a way to use ggsave after modifying the data by using ggplot_build() ?
(My version of the packages are gridExtra_0.9.1 and ggplot2_0.9.3.1)

it does not work because ggsave wants an object of class ggplot, while you're passing a grob. arrangeGrob will sometimes trick ggsave in pretending inheritance from ggplot, but only when at least one of the grobs belongs to this class; here, however, you're only passing a gtable.
Perhaps the easiest workaround is to clone ggsave and bypass the class check,
ggsave <- ggplot2::ggsave; body(ggsave) <- body(ggplot2::ggsave)[-2]
Edit: The dev version of ggplot2 no longer requires this hack*, as ggsave now works with any grob.
*PS: this hack works no longer, as arrangeGrob now returns a gtable, and its print method does not draw on a device.

A work around is to plot the gtable object with grid.draw() and then use dev.copy() to transfer the plot to a file.
Remember to use also dev.off() just afterward.

Related

Unexpected behavior using cbind with edited ggplotGrobs

I've been trying to edit some plots created by ggplot2 using the functions provided by the packages grid and gridExtra. I realize that ggplot2 alone can make some really nice multifaceted plots. However, in some instances I like to create individual plots and then combine then together later on. While trying to do just that, I came across some unexpected behavior using cbind() with grid.draw() or grid.arrange() when using a ggplot2 graph that had been edited. Below is the code for an MWE:
#Load libraries
library(ggplot2); library(gridExtra)
#Load data
data(mtcars)
#Ggplot
p = qplot(wt,mpg,data=mtcars,color=cyl)
grob = ggplotGrob(p)
#Bold xlabel
grobEdited = editGrob(grid.force(grob),gPath("xlab","GRID.text"),grep=TRUE,gp=gpar(fontface="bold"))
#Visualize
grid.newpage()
grid.draw(grobEdited)
It worked as expected. Now to illustrate the issue, lets cbind() two of the same edited ggplot2 graphs:
#Cbind example with edited graphs
grid.newpage()
grid.draw(cbind(grobEdited,grobEdited))
It didn't work as expected! Now test cbind() on the unedited graphs:
#Cbind example with grob
grid.newpage()
grid.draw(cbind(grob,grob))
Works as expected. I'm new to gridded figures, so is there something I'm doing wrong?
I'm posting an answer following the comment from #user20650. The easiest workaround is to cbind() the ggplot2 graphs before editing them using the editing functions provided by grid or gridExtra:
#Edit after cbind()
grobEdited = editGrob(grid.force(cbind(grob,grob)),gPath("xlab","GRID.text"),global=TRUE,grep=TRUE,gp=gpar(fontface="bold"))
#Visualize
grid.newpage()
grid.draw(grobEdited)

Multiple ggplot2 plots with plotly

I want to use ggplot2 with grid.arrange to generate multiple plots with plotly.
Some thing similar to this:
library(ggplot2)
library(gridExtra)
library(plotly)
g1<-ggplot(mpg, aes(displ, hwy, color=factor(year)))+geom_point()
g2<-ggplot(mpg, aes(cyl, hwy, color=factor(year)))+geom_point()
g<-grid.arrange(g1,g2,ncol=2)
ggplotly(g)
However, I am getting "Error in gg2list(p) : No layers in plot"
Any suggestions
Use the subplot function of plotly:
subplot(p1, p2, nrows = 2, margin = 0.04, heights = c(0.6, 0.4))
I'm having this problem myself and I don't think a solution currently exists to do this in the way you're describing.
The gg2list function contained in the call to ggplotly expects to be able to iterate over a ggplot object's layers to create the corresponding plotly object. If you step into the ggplotly function with RStudio's debugger, you can see the various ways in which it attempts to iterate across the object it receives in order to extract its properties.
It doesn't know what to do with the object returned by the arrangeGrob function because it's not just a single ggplot object (it's a grob arrangement, etc).
Calling class() on the objects in question illustrates this point somewhat.
> class(g1)
[1] "gg" "ggplot"
> class(g)
[1] "arrange" "ggplot" "gTree" "grob" "gDesc"
I think in order to have multiple plots heads up in the same plotly object, we will need to use the facet options in ggplot or the native plotly R bindings. Unfortunate, because gridExtra is very powerful and flexible, but the ggplot translation mechanism doesn't seem to be able to handle it.

Exporting graphs in R

I have two graphs that I plotted in R and I want to export it as a high-resolution picture for publication.
For example:
a<-c(1,2,3,4,5,6,7)
b<-c(2,3,4,6,7,8,9)
par(mfrow=c(2,1))
plot (a,b)
plot(a,b)
I usually export this graph by:
dev.copy(jpeg,'test.jpeg',width=80,height=150,units="mm",res=200)
dev.off()
However I always find this process a bit troublesome. The graph that was plotted in R does not necessarily look like the one that I exported. Therefore, I am wondering if there is a way to specifiy the dimensions and resolution of graphs before I plot them so that I can visually inspect the graphs before I export them?
Thank you
You can try:
png('out.png')
a<-c(1,2,3,4,5,6,7)
b<-c(2,3,4,6,7,8,9)
par(mfrow=c(2,1))
plot (a,b)
plot(a,b)
dev.off()
As baptiste said, jpeg is the worst format you can choose. You should take a look at the help for the bmp and png functions (with ?bmp and ?png). Both bmp and png have height, width, and res arguments that you can use to specifiy the dimensions and resolution of the output. Also, I wouldn't recommend the use of dev.copy. As you could see, the result of the output is not always what you expect.
To add to Bonifacio2's answer, you if you call the function first to make the plot, you can also define your margins and window size etc before doing any actual plotting. That way you have full control over all fig specs.
pdf(file='test.jpeg',width=80,height=150,units="mm") #I prefer pdf, because they are editable files
a<-c(1,2,3,4,5,6,7)
b<-c(2,3,4,6,7,8,9)
par(mfrow=c(2,1))
plot (a,b)
plot(a,b)
dev.off()
You can use cowplot package to combine multiple panels in several different ways. For example, in your case, we export one plot with two panels arranged in two rows and one column. I assume that you prefer to use base-R 'plot' function instead of ggplot.
library(cowplot)
p1 <- ~{
plot(a,b)
}
p2 <- ~{
plot(b,a)
}
png("plot.png",
width = 3.149606, # 80 mm
height = 5.905512, # 150 mm
units = 'in',
res = 500)
plot_grid(p1, p2, labels = "AUTO", nrow = 2, ncol = 1)
dev.off()
Note that you can either remove the labels if not needed or print small letters by using "auto". Regarding size of the text, axis-labels etc, use the standard arguments for generic plot function of base-R. I hope this answer helps you. Best wishes.

Use wordlayout results for ggplot geom_text

The R package wordcloud has a very useful function which is called wordlayout. It takes initial positions of words and their respective sizes an rearranges them in a way that they do not overlap. I would like to use the results of this functions to do a geom_text plot in ggplot.
I came up with the following example but soon realized that there seems to be a big difference betweetn cex (wordlayout) and size (geom_plot) since words in graphics package appear way larger.
here is my sample code. Plot 1 is the original wordcloud plot which has no overlaps:
library(wordcloud)
library(tm)
library(ggplot2)
samplesize=100
textdf <- data.frame(label=sample(stopwords("en"),samplesize,replace=TRUE),x=sample(c(1:1000),samplesize,replace=TRUE),y=sample(c(1:1000),samplesize,replace=TRUE),size=sample(c(1:5),samplesize,replace=TRUE))
#plot1
plot.new()
pdf(file="plot1.pdf")
textplot(textdf$x,textdf$y,textdf$label,textdf$size)
dev.off()
#plot2
ggplot(textdf,aes(x,y))+geom_text(aes(label = label, size = size))
ggsave("plot2.pdf")
#plot3
new_pos <- wordlayout(x=textdf$x,y=textdf$y,words=textdf$label,cex=textdf$size)
textdf$x <- new_pos[,1]
textdf$y <- new_pos[,2]
ggplot(textdf,aes(x,y))+geom_text(aes(label = label, size = size))
ggsave("plot3.pdf")
#plot4
textdf$x <- new_pos[,1]+0.5*new_pos[,3]#this is the way the wordcloud package rearranges the positions. I took this out of the textplot function
textdf$y <- new_pos[,2]+0.5*new_pos[,4]
ggplot(textdf,aes(x,y))+geom_text(aes(label = label, size = size))
ggsave("plot4.pdf")
is there a way to overcome this cex/size difference and reuse wordlayout for ggplots?
cex stands for character expansion and is the factor by which text is magnified relative the default, specified by cin - set on my installation to 0.15 in by 0.2 in: see ?par for more details.
#hadley explains that ggplot2 sizes are measured in mm. Therefore cex=1 would correspond to size=3.81 or size=5.08 depending on if it is being scaled by the width or height. Of course, font selection may cause differences.
In addition, to use absolute sizes, you need to have the size specification outside the aes otherwise it considers it a variable to map to and choose the scale itself, eg:
ggplot(textdf,aes(x,y))+geom_text(aes(label = label),size = textdf$size*3.81)
Sadly I think you're going to find the short answer is no! I think the package handles the text vector mapping differently from ggplot2, so you can tinker with size and font face/family, etc. but will struggle to replicate exactly what the package is doing.
I tried a few things:
1) Try to plot the grobs from textdata using annotation_custom
require(plyr)
require(grid)
# FIRST TRY PLOT INDIVIDUAL TEXT GROBS
qplot(0:1000,0:1000,geom="blank") +
alply(textdf,1,function(x){
annotation_custom(textGrob(label=x$label,0,0,c("center","center"),gp=gpar(cex=x$size)),x$x,x$x,x$y,x$y)
})
2) Run the wordlayout() function which should readjust the text, but difficult to see for what font (similarly doesn't work)
# THEN USE wordcloud() TO GET CO-ORDS
plot.new()
wordlayout(textdf$x,textdf$y,words=textdf$label,cex=textdf$size,xlim=c(min(textdf$x),max(textdf$x)),ylim=c(min(textdf$y),max(textdf$y)))
plotdata<-cbind(data.frame(rownames(w)),w)
colnames(plotdata)=c("word","x","y","w","h")
# PLOT WORDCLOUD DATA
qplot(0:1000,0:1000,geom="blank") +
alply(plotdata,1,function(x){
annotation_custom(textGrob(label=x$word,0,0,c("center","center"),gp=gpar(cex=x$h*40)),x$x,x$x,x$y,x$y)
})
Here's a cheat if you just want to overplot other ggplot functions on top of it (although the co-ords don't seem to match up exactly between the data and the plot). It basically images the wordcloud, removes the margins, and under-plots it at the same scale:
# make a png file of just the panel
plot.new()
png(filename="bgplot.png")
par(mar=c(0.01,0.01,0.01,0.01))
textplot(textdf$x,textdf$y,textdf$label,textdf$size,xaxt="n",yaxt="n",xlab="",ylab="",asp=1)
dev.off()
# library to get PNG file
require(png)
# then plot it behind the panel
qplot(0:1000,0:1000,geom="blank") +
annotation_custom(rasterGrob(readPNG("bgplot.png"),0,0,1,1,just=c("left","bottom")),0,1000,0,1000) +
coord_fixed(1,c(0,1000),c(0,1000))

Kernel density plots on a single figure

I have been trying to plot simple density plots using R as:
plot(density(Data$X1),col="red")
plot(density(Data$X2),col="green")
Since I want to compare, I'd like to plot both in one figure. But 'matplot' doesn't work!! I also tried with ggplot2 as:
library(ggplot2)
qplot(Data$X1, geom="density")
qplot(Data$X2, add = TRUE, geom="density")
Also in this case, plots appear separately (though I wrote add=TRUE)!! Can anyone come up with an easy solution to the problem, please?
In ggplot2 or lattice you need to reshape the data to seupose them.
For example :
dat <- data.frame(X1= rnorm(100),X2=rbeta(100,1,1))
library(reshape2)
dat.m <- melt(dat)
Using ``lattice`
densityplot(~value , groups = variable, data=dat.m,auto.key = T)
Using ``ggplot2`
ggplot(data=dat.m)+geom_density(aes(x=value, color=variable))
EDIT add X1+X2
Using lattice and the extended formua interface, it is extremely easy to do this:
densityplot(~X1+X2+I(X1+X2) , data=dat) ## no need to reshape data!!
You can try:
plot(density(Data$X1),col="red")
points(density(Data$X2),col="green")
I must add that the xlim and ylim values should ideally be set to include ranges of both X1 and X2, which could be done as follows:
foo <- density(Data$X1)
bar <- density(Data$X2)
plot(foo,col="red", xlim=c(min(foo$x,bar$x),max(foo$x,bar$x)) ylim=c(min(foo$y,bar$y),max(foo$y,bar$y))
points(bar,col="green")
In base graphics you can overlay density plots if you keep the ranges identical and use par(new=TRUE) between them. I think add=TRUE is a base graphics strategy that some functions but not all will honor.
If you specify n, from, and to in the calls to density and make sure that they match between the 2 calls then you should be able to use matplot to plot both in one step (you will need to bind the 2 sets of y values into a single matrix).

Resources