A vital part of statistics is producing nice plots, an area where R is outstanding. The graphical ablility of R is often listed as a major reason for choosing the language. It is therefore funny that exporting these plots is such an issue in Windows. This post is all about how to export anti-aliased, high resolution plots from R in Windows.
There are two main problems when exporting graphics from R:
- Anti-aliasing is not activated in Windows R (this does not apply to Linux or Mac). Update: An important update is that the anti-aliasing is now included in R although not activated by default. See section below.
- When increasing the resolution the labels automatically decrease and become unreadable
My previous solution to this problem has been to export my graph to a vector graphic (usually the SVG format), open it in Inkscape, and then export it to the resolution of choice. The vector graphic allows smooth scaling without me having to worry about the text becoming too small to read while at the same time adding the anti-aliasing. The down-side of this is that it is not really a convenient work-flow when you create knitr/Sweave documents where many plots are simultaneously generated.
The post has several sections:
- Raw export functions
- The Cairo-package
- The cairoDevice-package
- Raw export functions with the type=”cairo” activated
- Fills and anti-aliasing
Exporting
Let us start by looking at an exported plot from RStudio, clearly not something I would like to send to a journal:
So the first alternative would be the standard library. We can kick-off with the svg() function as I mentioned it earlier (Note: If you can’t see the plot then your browser is probably old and does not support svg images):
svg(filename="Std_SVG.svg",
width=5,
height=4,
pointsize=12)
my_sc_plot(data)
dev.off()
All images in this post use the same basic settings with minor tweaks:
- Width = 5 inches
- Height = 4 inches
- pointsize = 12
If we now do the same using the png() command we also have to set the resolution. I’m using 72 dpi (pixels/inch), as 1 point by the modern definition is 1/72 of the international inch and therefore a good start instead of the 96 dpi that Windows uses. As you can see the size of the labels are very similar to the svg():
png(filename="Std_PNG.png",
units="in",
width=5,
height=4,
pointsize=12,
res=72)
my_sc_plot(data)
dev.off()
Note: The Cairo packages are obsolete as it usually is built in nowadays, all you need to do is remember to reference it – see my update
The Cairo-package
The Cario package is an excellent choice for getting antia-aliased plots in Windows. Here’s the same plot from the Cairo package (the background is now transparent):
library(Cairo)
Cairo(file="Cairo_PNG_72_dpi.png",
type="png",
units="in",
width=5,
height=4,
pointsize=12,
dpi=72)
my_sc_plot(data)
dev.off()
If we want to increase the resolution of the plot we can’t just change the resolution parameter:
Cairo(file="Cairo_PNG_96_dpi.png",
type="png",
units="in",
width=5,
height=4,
pointsize=12,
dpi=96)
my_sc_plot(data)
dev.off()
We also have to change the point size, the formula is size * new resolution DPI / 72 DPI:
Cairo(file="Cairo_PNG_96_dpi_adj.png",
type="png",
units="in",
width=5,
height=4,
pointsize=12*92/72,
dpi=96)
my_sc_plot(data)
dev.off()
If we double the image size we also need to double the point size:
Cairo(file="Cairo_PNG_72_dpi_x_2.png",
type="png",
units="in",
width=5*2,
height=4*2,
pointsize=12*2,
dpi=72)
my_sc_plot(data)
dev.off()
The cairoDevice package
An alternative to the Cairo package is the cairoDevice package. It uses the same engine but has a slightly different syntax and due to it’s limitation in setting the resolution it really doesn’t work that well. Lets try the same as above:
# Detach the Cairo package just to make
# sure that there aren't any conflicts
detach("package:Cairo")
library(cairoDevice)
Cairo(filename="cairoDevice_SVG_pt12.svg",
surface="png",
width=5,
height=4,
pointsize=12)
my_sc_plot(data)
dev.off()
To be fair the default point size of the cairoDevice package, 8 points is less than for the Cairo package but even with that setting the image looks not that great:
After some tweaking I found that 5.2 points was fairly similar:
Summary
The Cairo package does the job nicely and without too much hassle you can get the look exactly right. If you want the background to be white just add the bg parameter:
Cairo(file="Cairo_PNG_72_dpi_white.png",
bg="white",
type="png",
units="in",
width=5,
height=4,
pointsize=12,
dpi=72)
my_sc_plot(data)
dev.off()
Update
After Matt Neilsons excellent comment I’ve discovered that the standard export functions actually allow for anti-aliasing with built-in cairo support. Another excellent part is that the dpi now works exactly as expected – you don’t need to adjust the point size 🙂
png(filename="Std_PNG_cairo.png",
type="cairo",
units="in",
width=5,
height=4,
pointsize=12,
res=96)
my_sc_plot(data)
dev.off()
Here’s the plot code:
# Create the data
data <- rnorm(100, sd=15)+1:100
# Create a simple scatterplot
# with long labels to enhance
# size comparison
my_sc_plot <- function(data){
par(cex.lab=1.5, cex.main=2)
plot(data,
main="A simple scatterplot",
xlab="A random variable plotted",
ylab="Some rnorm value",
col="steelblue")
x <- 1:100
abline(lm(data~x), lwd=2)
}
Lines and text anti-aliased - not fills/polygons
I recently found out the hard way that the png(..., type="cairo") will anti-alias everything except fills/polygons. This is due to the fact that image-maps risk of becoming distorted by the anti-aliasing function. It is therefore good to know that if you have a fill, make sure that it has a line-border if you want it to become anti-aliased. This is especially important in the ggplot2 that does not set the border by default (lattice and plain plots do).
Below is a simple example to illustrate:
n <- 10
xx <- c(0:n, n:0)
yy <- c(xx[1:(n+1)] + 2, xx[(n+2):(n*2 + 2)] -2)
png(height=400, width=800, filename="plot_w_cairo.png", type="cairo")
org_par <- par(mfrow=c(1,2), cex=1.5, mai=par("mai")*.75)
# First plot
plot (xx, yy, type = "n", xlab="", ylab="", main="Without line color")
polygon(xx, yy, col = "black", border=NA)
abline(3, 1, lwd=3)
# Second plot
plot (xx, yy, type = "n", xlab="", ylab="", main="With line color")
polygon(xx, yy, col = "black")
abline(3, 1, lwd=3)
# Reset values
par(org_par)
dev.off()
Now for the ggplot2 version. Note that the color line is by default NA.
library(ggplot2)
library(grid)
df <- data.frame(x=xx, y=yy)
png(height=400, width=800, filename="ggplot_w_cairo.png", type="cairo")
# Generate base plot
pushViewport(viewport(layout = grid.layout(1, 2)))
g <- ggplot(df, aes(x=x, y=y)) +
geom_abline(aes(intercept=3, slobe=1), size=3) +
xlab("") + ylab("") + theme(text = element_text(size=25))
# First plot
print(g + geom_polygon() + ggtitle("Without line color"), vp = viewport(layout.pos.row = 1, layout.pos.col = 1))
# Seconde plot
print(g + geom_polygon(col="black") + ggtitle("With line color"), vp = viewport(layout.pos.row = 1, layout.pos.col = 2))
dev.off()
I hope that you find all of this useful.
Using the combination of “par”, “axis” and “mtext”, you can adjust your figures’ details.
Yes, those are some of the ways to pre-export get the size of the labels to the right size. One of the issues with just using those is that I can create a 1500×1500 plot with nice axes but the points (circles in this case) will not adjust to my large canvas (as I’m aware of – although it would be an awesome R-feature if we could specify text size as npc). I find this knowledge of converting graph size very convenient as I often send low-resolution documents to my co-authors and just prior to publication I go for the 600 DPI images.
R has support for Cairo built in in most cases, and you can take advantage of it through the ‘type’ argument of png(). type = ‘cairo’ uses R’s built in png surface drawing, and type = ‘cairo-png’ will use Cairo’s png backend to do all drawing.
Thank you Matt for discovering this hidden gem! I had completely missed that option. For some reason I stumbled upon the Cairo packages when I searched for anti-aliasing and R on Windows machines, and with the knitr extensive support for the Cairo devices I didn’t think of questioning that wisdom…
It’s a pity that it’s not the default option on Windows machines, or that at least the Cairo packages have a warning when loading that there is a better option.
Nice tutorial… I just have a quick question. How can I increase the left margin a little bit so that the Y-axis label has some more white space to the left?
Thank you. The margin is a little different for the ggplot2/lattice but for basic graphs you just set the
mar
or theomar
settings using thepar()
command. See code below:Very nice post! Thank you!
Nice tutorial. Note that one can now also use high quality vector-based export to Powerpoint using the ReporteRs package – see my answer here http://stackoverflow.com/questions/9555889/producing-a-vector-graphics-image-i-e-metafile-in-r-suitable-for-printing-in/31073189#31073189
Advantage is that the output is then fully editable, high quality, vector based and that transparency is also fully supported. It works for base R plots, lattice plots & ggplot2.
Pingback: Saving graphics in R – acarioli