tl;dr: Use pryr::%<a-%
to save a series of R commands as a kind of macro you can call repeatedly.
Saving grid-based plots to objects
One nice thing about ggplot (and grid graphics in general) is that you can save plots as objects and use them later in other functions like gridExtra::grid.arrange()
:
library(tidyverse)
library(gridExtra)
<- data_frame(x = 1:100, y=rnorm(100),
df z=sample(LETTERS[1:3], 100, replace=TRUE))
# Scatterplot
<- ggplot(df, aes(x=x, y=y, color=z)) + geom_point()
p1
# Distribution
<- ggplot(df, aes(x=y, fill=z)) + geom_density(alpha=0.5)
p2
# Combine plots
# arrangeGrob() is basically grid.arrange(), but allows you to save as an object
<- arrangeGrob(p1, p2)
p.both ::grid.draw(p.both) grid
This is particularly useful when saving plots with ggsave
; you can simultaneously make PDF and PNG versions of your plots for use in LaTeX (PDF) or Word, PowerPoint, or HTML (PNG).
ggsave(p.both, filename="blah.pdf")
ggsave(p.both, filename="blah.png")
Saving base graphics-based plots to objects
However, saving base R graphics to objects for later reuse is a little trickier, since plots are built line-by-line into specific devices. One approach is to make plots on a null device, record the plot, and then display it later:
# Save plot to an object using a null PDF device
# http://stackoverflow.com/a/14742001/120898
pdf(NULL)
dev.control(displaylist="enable")
plot(df$x, df$y)
text(40, 0, "Random")
text(60, 2, "Text")
lines(stats::lowess(df$x, df$y))
<- recordPlot()
p1.base invisible(dev.off())
# Display the saved plot
::grid.newpage()
grid p1.base
One advantage of this is that you can then reuse the plot object to simultaneously save a PDF and a PNG without recreating the plot:
pdf("blah_base.pdf")
p1.basedev.off()
png("blah_base.png")
p1.basedev.off()
This is particularly helpful if you want to use nicer fonts or higher resolutions with Cairo:
cairo_pdf("blah_base.pdf",
width=8, height=3.5, family="Comic Sans")
p1.basedev.off()
png("blah_base.png",
width=8, height=3.5, family="Comic Sans",
bg="white", units="in", res=300, type="cairo")
p1.basedev.off()
There are situations, though, where recorded base graphics plot are clunky and don’t work well, since the plots have device information embedded in them. Rearranging saved plots with par(mfrow()
, layout()
or other multipanel techniques can be tricky. Saved plots that make heavy modifications to par()
(like Sankey diagrams created from the riverplot package) can also be unwieldy when laying out with multiple base graphics plots. It seems that the best way to use multipanel techniques with base R is to use the actual plot commands rather than inserting recorded plots. Which means that if you want to simultaneously output a PNG and a PDF, you have to repeat the code twice, which is awful.
pdf("blah.pdf")
split.screen(c(1, 2))
screen(1)
plot(df$x, df$y)
text(40, 0, "Random")
text(60, 2, "Text")
lines(stats::lowess(df$x, df$y))
screen(2)
plot(density(df$y))
close.screen(all=TRUE)
dev.off()
# Once more, with feeling
png("blah.png")
split.screen(c(1, 2))
screen(1)
plot(df$x, df$y)
text(40, 0, "Random")
text(60, 2, "Text")
lines(stats::lowess(df$x, df$y))
screen(2)
plot(density(df$y))
close.screen(all=TRUE)
dev.off()
A solution: active bindings in pryr
Instead of recording a plot and saving all its device-specific settings, you can use the %<a-%
function in Hadley Wickham’s pryr
package to essentially save a chunk of R code as an object that can then be re-run later, almost like a macro.
library(pryr)
%<a-% {
p1.pryr plot(df$x, df$y)
text(40, 0, "Random")
text(60, 2, "Text")
lines(stats::lowess(df$x, df$y))
}
%<a-% {
p2.pryr plot(density(df$y))
}
Every time p1.pryr
is called from now on, R will run everything assinged to it in {}
. The plot itself is not saved as an object—rather, the code that generates the plot is saved as an object.
p1.pryr
p2.pryr
Including these plots in a multipanel layout is trivial:
split.screen(c(1, 2))
screen(1)
p1.pryr
screen(2)
p2.pryr
close.screen(all=TRUE)
Saving these plots as PDFs and PNGs is the same as saving recorded plots:
pdf("blah_base_pryr.pdf")
p1.pryrdev.off()
png("blah_base_pryr.png")
p1.pryrdev.off()
But even better is saving multipanel plots as PDFs and PNGs. Save the whole multipanel plot code as an actively bound object and then wrap it in pdf()
or png()
:
%<a-% {
p.both.pryr split.screen(c(1, 2))
screen(1)
p1.pryr
screen(2)
p2.pryr
close.screen(all=TRUE)
}
pdf("blah_base_both_pryr.pdf")
p.both.pryrdev.off()
png("blah_base_both_pryr.png")
p.both.pryrdev.off()
Again, actively bound objects are not the objects themselves, but an expression that gets run every time the object is accessed. It is far easier to deal with saved base graphics as code instead of as recorded plots, since there is no inherent device information to contend with.
In the end, it’s easiest to save grid-based graphics to objects, since they’re designed to do that. But you can fake it with base graphics using pryr
.
One quick real-world use case. As mentioned, Sankey diagrams from riverplot package override all graphical parameters and can’t be easily plotted in multipanel layouts if you record them with recordPlot()
. With pryr
, though, it’s easy.
library(riverplot)
<- riverplot.example()
x
%<a-% {
riverplot.saved plot(x)
text(3, 0, "Some text\n", adj=c(0.5, -0.5), font=2)
points(1:5, rep(2, 5), pch=19)
}
%<a-% {
p.both.river split.screen(c(1, 2))
screen(1)
riverplot.saved
screen(2)
plot(1:10)
close.screen(all=TRUE)
}
p.both.river