S Graphics Hints and Tricks

R Graphics Setup Templates

Besides the Hmisc package's setpdf and setps functions, you can use commands like the following to make publication-ready graphics.

pdf('/tmp/z.pdf', pointsize=16)
par(mar=c(3.5, 3.5,.5,.5), lwd=2, mgp = c(2.2, 0.45, 0), tcl = -0.4)
for(i in 1:2)plot(runif(100),xlab='Label for X',ylab='Label for Y')
dev.off()


postscript('/tmp/z.ps', pointsize=24)
par(mar=c(2.825, 3, .125, 1.5), lwd=2, mgp = c(2, 0.45, 0), tcl = -0.4)
for(i in 1:2)plot(runif(100),xlab='Label for X',ylab='Label for Y')
dev.off()

postscript('/tmp/z.eps', pointsize=14,
           onefile=FALSE, paper='special',
           horizontal=FALSE, height=4, width=5)
par(mar=c(3, 3.25, .25, .5), lwd=2, mgp = c(2, 0.45, 0), tcl = -0.4)
plot(runif(100),xlab='Label for X',ylab='Label for Y')
dev.off()
The following function sets up for single low-level plots and matrices of plots in a way that is useful with Sweave. Note that par parameters have only been optimized for single and 2x2 plots.

spar <- function(mar=if(!axes)
                 c(2.25+bot-.45*multi,1.5+left,.5+top+.25*multi,.5+rt) else
                 c(3.25+bot-.45*multi,3.5+left,.5+top+.25*multi,.5+rt),
                 lwd = if(multi)1 else 1.75,
                 mgp = if(!axes) mgp=c(.75, .1, 0) else
                 if(multi) c(1.5, .365, 0) else c(2.4-.4, 0.475, 0),
                 tcl = if(multi)-0.25 else -0.4,
                 bot=0, left=0, top=0, rt=0, ps=if(multi) 14 else 10,
                 mfrow=NULL, axes=TRUE, ...)
  {
    multi <- length(mfrow) > 0
    par(mar=mar, lwd=lwd, mgp=mgp, tcl=tcl, ps=ps, ...)
    if(multi) par(mfrow=mfrow)
  }
Example calls:

spar()               # single plot
spar(mfrow=c(2,2))   # 2x2 matrix of plots, minimal space between panels
spar(top=1)          # single plot, leave room for title
Under Sweave you might invoke spar right after <<fig=T>>=. If not using Sweave, spar might be called right after postscript or pdf.

Customizing Lattice Axis Spacing

Like the mgp par element in base graphics, you can set the spacing between tick marks and the axis numbers using the following model:

xyplot(y ~ x, data=d, 
          par.settings=list(axis.components=list(left=list(pad1=.3), bottom=list(pad1=.4))))

Adding panel numbers in margins of multi-panel graphs

For submitting multi-panel graphs made with low-level S graphics for publication, journals often request that panel numbers be placed on each panel. The following setup provides good margin settings for this and shows how to add the panel descriptors outside the top left corner of each graph.

Panel <- function(panel)
  {
    par(xpd=NA)
    u <- par('usr')
    text(u[1]-(u[2]-u[1])*.125, u[4]+(u[4]-u[3])*.1, panel)
    par(xpd=FALSE)
  }
par(mfrow=c(2,2), mar=c(3.5, 3.75, 1.75, .75))
plot( )
Panel('1A')
hist( )
Panel('1B')
plot( )
Panel('1C')
hist( )
Panel('1D')

Embedding fonts

Certain journals require all fonts (including Helvetica) to be embedded when depositing pdf or eps files with them. According to Brian Ripley, the problem is "having a legitmately licensed version of those copyright fonts that allows you to send them (or subsetted versions)" to the journal. If the journal really feels that way, the journal should supply the fonts. "One possible way out is to use Nimbus Sans aka URW Helvetica, which embedFonts can embed." Here is an example. If using postscript() instead of pdf() be sure to specify a paper argument equal to 'special'.

pdf('my.pdf', onefile=FALSE, pointsize=18,
    family="NimbusSan",height=6,width=8,paper="special")
...
dev.off()
embedFonts('my.pdf')

See http://colinm.org/tips/latex for some useful information. If the journal requires you to use Microsoft .ttf fonts, see http://corefonts.sourceforge.net (note that Microsoft forgot to copyright these fonts) or do sudo apt-get install msttcorefonts. This will provide the arial font to linux.

According to PLoS Guidelines one must generate .afm files before using them in R, along the lines of:
  ttf2afm /usr/share/fonts/msttcorefonts/arial.ttf > /foo/arial.afm
  ttf2afm /usr/share/fonts/msttcorefonts/ariali.ttf > /foo/ariali.afm
  ttf2afm /usr/share/fonts/msttcorefonts/arialbd.ttf > /foo/arialbd.afm
  ttf2afm /usr/share/fonts/msttcorefonts/arialbi.ttf > /foo/arialbi.afm
and then one can use them in R's postscript() with the family argument on the lines of

postscript(file="try.ps", horizontal=F, onefile=F, width=4, height=4,
   family=c("/foo/arial.afm", "/foo/arialbd.afm",
   "/foo/ariali.afm", "/foo/arialbi.afm"),
    pointsize=12)
You will need the texlive-font-utils package on your system. ttf and arial information is thanks to Ted Harding, Deepayan Sarkar, Dirk Eddelbeutel. Sarkar also pointed out the following potentially simpler solution:

cairo_ps()
par(family = "Arial") # or par(family = "Andale mono") to be really sure this works
plot(1:10)
dev.off()
He also said to see the 2006 article in Rnews referenced in the help file for postscript().

Making plots with Two Y-axes

Someone posted this to S-news, with the following note. I would like to thank Naomi Robbins, Luciano Molinari, Samantha Choy, Melanie Cox, Z. Todd Taylor, Anne York and Ronald Fehd for their replies. I ended up using the function that Anne York sent me. I have modified it somewhat and will post the modifed version latter. Here is the original form:

Twoplots <-
function(x1, y1, x2, y2, type = "l", xlim = NULL, ptitle = "Twoplots", xlab
= "x", ylab1 = "y1", ylab2 = "y2", ...)
{
# Written by AE York Sept. 1996
# Plots y1 vs x1 and y2 vs x2 on the same plot
# Labels for y1 are on the left and labels for y2 on the right
        oldpar <- par()
        par(mar = rep(6, 4))    #
        if(missing(xlim))
                xlim <- range(pretty(range(c(x1, x2))))
        plot(x1, y1, lty = 1, pch = 1, type = type, xlim = xlim, xlab =
                xlab, ylab = ylab1, ...)
        par(new = T)
        plot(x2, y2, lty = 2, pch = 16, type = type, axes = F, xlim = xlim,
                xlab = "", ylab = "")
        axis(side = 4)
        mtext(ylab2, side = 4, line = 2, outer = F)
        par(oldpar)
        title(ptitle)
}

Compressing PDF Files

PDF files produced by R are uncompressed. In may cases you can get an almost 10-fold reduction in file size by compressing the PDF graphics files. To to this, first make sure the pdftk package is installed in your system, then define the following bash script in your ~/bin directory and name it cmpdf:
#! /bin/bash
for f in $@
do
 pdftk $f output /tmp/$f compress
 mv -f /tmp/$f .
done
To use it, here are some examples:
cmpdf mygraph.pdf     # compress one file
cmpdf a.pdf b.pdf     # compress two files
cmpdf *.pdf           # compress all .pdf files
cmpdf a*.pdf          # compress all .pdf files beginning with the letter a

Making Small Postscript Files

Tim Hesterberg of Insightful Corp. in 2000 provided the following examples:

postscript(file="CLT2.ps", height=2.2, width=6.5, horizontal=F, onefile=F)
par(mfrow=c(1,3), mar=c(2.1,2.1,4.1,2.1))

postscript(file="CLT3.ps", height=2.2*2, width=3, horizontal=F, onefile=F)
par(mfrow=c(2,1), mar=c(2.1,2.1,1.1,0.1))
par(cex=.7, mex=.7)

postscript(file="misc-three1.ps", height=2.2, width=6.5/3, horizontal=F, 
           onefile=F)
par(mar=c(2.1,2.1,1.1,0.1))
par(cex=.6, mex=.6)
Note that sometimes it is necessary to use two calls to par(), that combining the calls into a single call to par() fails. I believe this is because changing some graphical options changes others, so to get what you want for the latter they must be set in a later call to par().

Modifying Postscript Files for Customizing Trees

Samuel Buttrey (sebuttre@monterey.nps.navy.mil) wrote on 04May99 the following. Here is a simple Splus function that takes in a Postscript file written by post.tree, plus the (regression) tree object itself, and writes a new Postscript file in which the number under each leaf is the number of observations, not the deviance.

If anyone is interested I have a couple of these manipulation-type functions for classification trees, too. Warning: they rely very heavily on the particular Postscript layout that I've observed Splus to use. If post.tree ever changes, I presume these functions will have to, too.

I don't have a function that will add the standard error, but this technique can be adapted.

fix.ps.reg <- function(file.in, tree.in, file.out)
{
#
# Fix.ps.reg: function to change the labels below the nodes of
# a regression tree.
#
# Arguments: file.in: file with Postscript (output from post.tree)
#            tree.in: Splus tree
#           file.out: new Postscript file
#
# Read in the file.in file. Find the lines with "centershow" in
# them; these come in pairs. Replace the first line of each pair
# with a line of the form "(n) centershow" where n is the number
# of items in the node. We get n from tree.in.
#
   strs <- scan(file.in, what = "", sep = "\n", strip.white = T)
   rights <- regexpr(")", strs)
   lefts <- regexpr("(", strs)
   shows <- regexpr("centershow", strs)
   goods <- (1:length(strs))[shows > 0 & rights > 0 & lefts > 0]
   goods <- goods[-1]   # Skip over title
   goods <- goods[seq(1, by = 2, length = length(goods)/2)]   #
#
# Okay. Goods now holds the numbers of the relevant lines.
#
   devs <- strs[goods]   #
   first.occurrence <- function(str, char)
   {
#
# first.occurrence: function to find the number of the first occurrence
# of character "char" in string "str"
#
#    Arguments: str: string in which to find character
#              char: character to be found
#    Return value: number giving the position of the first occurrence
# of "char" in "str." If char isn't found, return 0.
#
      all.chars <- substring(str, 1:nchar(str), 1:nchar(str))
      first <- (1:nchar(str))[all.chars == char][1]
      return(ifelse(length(first) == 0, 0, first))
   }
   for(i in 1:length(devs)) {
      left.paren <- first.occurrence(devs[i], "(")
      right.paren <- first.occurrence(devs[i], ")")
      devs[i] <- paste("(", tree.in$frame[i, "n"], ") centershow")
   }
   strs[goods] <- devs
   if(!missing(file.out))
      sink(file.out)
   cat(paste(strs, "\n"))
   if(!missing(file.out))
      sink()
   invisible()
} 

Changing Trellis/Lattice Strip Characteristics

You can turn off the shading in the strip labels as well as tell lattice to use black and white with

ltheme <- canonical.theme(color = FALSE)
ltheme$strip.background$col <- "transparent"
lattice.options(default.theme = ltheme)      ## set as default  
Here is another approach:

trellis.par.set(strip.background = list(col = 'transparent'))
or

strip <- function(...) strip.default(..., style=1)
Another function posted to S-news is:

"trellis.strip"<-
function(b = 0, s = 0)
{
# Sets trellis parameters for strip colors
# Default will eliminate shading from trellis strips
# Use trellis.strip(5,12) for trellis defaults
 if(is.null(attr(get(".Device", where = 0), "trellis.settings"))) stop(
   "Current device is not a trellis device")
 s.b <- trellis.par.get("strip.background")
 s.b$col[1] <- b
 trellis.par.set("strip.background", s.b)
 s.s <- trellis.par.get("strip.shingle")
 s.s$col[1] <- s
 trellis.par.set("strip.shingle", s.s)
}
Here's some other postings:

   strip.back<- trellis.par.get("strip.background")
   strip.back$col<-rep(0,7)
   trellis.par.set("strip.background",strip.back)

   remove.shading <- function()
      {
      # Removes shading on shingles above Trellis plots
              strip.background <- trellis.par.get("strip.background")
              strip.background$col <- 0
              trellis.par.set("strip.background", strip.background)
              strip.shingle <- trellis.par.get("strip.shingle")
              strip.shingle$col <- 0
              trellis.par.set("strip.shingle", strip.shingle)
      }

      stripplot(Sut ~ lpelong | Knottype, jitter=T,
                strip=remove.shading(),
                par.strip.text = list(cex=1.2), scale = list(x=list(cex=1.2),
                y=list(cex=1.4)), xlab=list("Loop elongation (mm)", cex=1.6) )

More Hints About Exporting R Graphics to Office

To make a high resolution graph in R that can be pasted into word, you can use the png function. If you make it a large size with high resolution, if it is resized, it will still have good quality.

png("name.png", pointsize = 10, width = 6, height = 6, units = "in", res = 1600)
...
dev.off()

Sometimes it's simplest just to create a high enough resolution png file once you've created a pdf file in R. To easily control the resolution of the resulting png file and to scale text accordingly, use the ImageMagick convert command, e.g. convert -density 300x300 myplot.pdf myplot.png .

Another ImageMagick command that has worked well is convert -verbose -density 150 -trim my.pdf -quality 100 /tmp/my.png . Note that the output file my.png is put in a different directory than the one holding my.pdf to keep LaTeX from being confused with two graphics files with the same base name.


Topic revision: r22 - 07 Apr 2012, FrankHarrell
 

This site is powered by FoswikiCopyright © 2013-2022 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Vanderbilt Biostatistics Wiki? Send feedback