Programming for Applications

Chapter 13: Graphics

Yu-You Liou (NTU)

Shih Chien University

2026-08-03

Base Graphics

Three Plotting Systems, One Chapter Each

R plots graphics many ways; the book focuses on the three most popular packages:

  • graphics (this chapter) — the classic system: easy to customize, easy to modify, interactive on screen;
  • lattice (Chapter 14) — built for splitting data by a conditioning variable;
  • ggplot2 (Chapter 15) — a different metaphor that makes complex, pretty charts easy.

This chapter: a tour of the common chart types, then graphics devices, then the full machinery of customization (par, low-level functions).

Scatter Plots

plot: the Generic Entry Point

Our example data: 2008 cancer cases (American Cancer Society) and 2006 toxic releases by state (EPA), packaged as toxins.and.cancer:

temp <- tempfile(fileext = ".rda")
download.file("https://raw.githubusercontent.com/cran/nutshell/master/data/toxins.and.cancer.rda",
              temp, mode = "wb")
load(temp)

plot is generic — vectors, tables, time series all plot. For two vectors, plot.default runs. Cancer death rate vs. toxin release rate:

attach(toxins.and.cancer)
plot(total_toxic_chemicals/Surface_Area, deaths_total/Population)

plot.default: Arguments

plot(x, y = NULL, type = "p", xlim = NULL, ylim = NULL, log = "",
     main = NULL, sub = NULL, xlab = NULL, ylab = NULL,
     ann = par("ann"), axes = TRUE, frame.plot = axes,
     panel.first = NULL, panel.last = NULL, asp = NA, ...)
Argument Description
x, y data: two vectors, or a time series / formula / list / two-column matrix (see ?xy.coords)
type "p" points, "l" lines, "o" overplotted, "b" both, "s" steps, "h" vertical lines, "n" nothing
xlim / ylim axis limits
log logarithmic axes: "", "x", "y", "xy"
main / sub / xlab / ylab titles and axis labels
ann / axes / frame.plot draw annotation / axes / surrounding box?
panel.first / panel.last expressions evaluated before / after points are drawn
asp aspect ratio y/x
... graphical parameters (see below)

Interactive Identification, and Labels

A stronger correlation appears between airborne toxins and lung cancer:

plot(air_on_site/Surface_Area, deaths_lung/Population,
     xlab="Air Release Rate of Toxic Chemicals",
     ylab="Lung Cancer Death Rate")
text(air_on_site/Surface_Area, deaths_lung/Population,
     labels=State_Abbrev, cex=0.5, adj=c(0,-1))
  • Interactively: locator(1) prints the coordinates of a clicked point; identify(x, y, labels) labels points as you click them.
  • For all labels at once, text does it (here resized with cex, repositioned with adj). The relationship is statistically significant (Chapter 18) — though that alone argues no causal story.

matplot, smoothScatter, pairs

Three relatives for special situations:

  • matplot(x, y, type=, lty=, lwd=, pch=, col=, cex=, ..., add=) plots all columns of one matrix against all columns of another; the usual par-style arguments accept vectors, one entry per series.
  • smoothScatter shades regions by point density — the cure for overplotting with very many points (see Chapter 16).
  • pairs draws a scatter plot matrix for every pair of variables:
temp <- tempfile(fileext = ".rda")
download.file("https://raw.githubusercontent.com/cran/nutshell/master/data/batting.2008.rda",
              temp, mode = "wb")
load(temp)
pairs(batting.2008[batting.2008$AB>100, c("H", "R", "SO", "BB", "HR")])

Plotting Time Series

plot.ts and acf

plot has a time-series method (plot.type = "multiple"/"single", panel, nc, yax.flip, margin controls…). The turkey prices of Chapter 7:

temp <- tempfile(fileext = ".rda")
download.file("https://raw.githubusercontent.com/cran/nutshell/master/data/turkey.price.ts.rda",
              temp, mode = "wb")
load(temp)
plot(turkey.price.ts)

Strongly seasonal: November/December sales (Thanksgiving, Christmas), minor spring dips (Easter).

The Correlogram

The autocorrelation function shows how correlated points are with their past, by lag — acf computes it and plots by default:

acf(turkey.price.ts)

Correlated at 12-month cycles, inversely at 6-month cycles. (Time series analysis proper: Chapter 23.)

Bar Charts

barplot Basics

US doctoral degrees, 2001–2006 (Statistical Abstract), entered directly:

doctorates <- data.frame(
  year=c(2001, 2002, 2003, 2004, 2005, 2006),
  engineering=c(5323, 5511, 5079, 5280, 5777, 6425),
  science=c(20643, 20017, 19529, 20001, 20498, 21564),
  education=c(6436, 6349, 6503, 6643, 6635, 6226),
  health=c(1591, 1541, 1654, 1633, 1720, 1785),
  humanities=c(5213, 5178, 5051, 5020, 5013, 4949),
  other=c(2159, 2141, 2209, 2180, 2480, 2436))
doctorates.m <- as.matrix(doctorates[2:7])   # barplot wants a matrix,
rownames(doctorates.m) <- doctorates[, 1]    # not a data frame
barplot(doctorates.m[1, ])

Default behavior: y-axis drawn, no x-axis line, bars named from column names.

Grouped and Stacked Bars

Side-by-side horizontal bars with a legend; then stacked columns (transpose so columns = years, widen ylim to fit the legend):

barplot(doctorates.m, beside=TRUE, horiz=TRUE, legend=TRUE, cex.names=.75)

barplot(t(doctorates.m), legend=TRUE, ylim=c(0, 66000))

barplot: Arguments

Key arguments (of many):

Argument Description
height vector, or matrix (stacked if beside=FALSE, grouped if TRUE)
width / space bar widths / gaps (with beside=TRUE: c(within, between) groups)
names.arg / legend.text bar labels / legend (logical = use rownames)
beside / horiz grouped vs. stacked / horizontal vs. vertical
density / angle shading lines per inch / their slope
col / border bar colors / border color
main, sub, xlab, ylab, xlim, ylim, log, axes the usual annotations
xpd may bars overflow the plot region?
cex.axis / cex.names / axisnames / axis.lty axis text sizing and styling
inside / offset / add / args.legend separators, baseline shift, overplot, legend options

Pie Charts

pie — Use Sparingly

What happened to US-caught fish in 2006 (millions of pounds):

domestic.catch.2006 <- c(7752, 1166, 463, 108)
names(domestic.catch.2006) <- c("Fresh and frozen",
  "Reduced to meal, oil, etc.", "Canned", "Cured")
pie(domestic.catch.2006, init.angle=100, cex=.6)

Arguments: x (nonnegative values), labels, edges (polygon smoothness), radius, clockwise + init.angle, density/angle shading, col (default: six pastels), border, lty, main.

Warning

The help file itself says it: “Pie charts are a very bad way of displaying information. The eye is good at judging linear measures and bad at judging relative areas. A bar chart or dot chart is a preferable way of displaying this type of data.”

Plotting Categorical Data

Conditional Density: cdplot

How does a categorical outcome vary along a numeric axis? cdplot smooths with kernel density estimates (density underneath; arguments bw, n, from, to pass through; ylevels reorders; formula+data+subset supported). Batting hand by batting average:

batting.w.names.2008 <- transform(batting.2008,
  AVG=H/AB, bats=as.factor(bats), throws=as.factor(throws))
cdplot(bats~AVG, data=batting.w.names.2008,
       subset=(batting.w.names.2008$AB>100))

The proportion of switch hitters (B) rises with batting average.

Mosaic, Spine, and Association Plots

For two factors, three displays of the contingency structure:

mosaicplot(formula=bats~throws, data=batting.w.names.2008, color=TRUE)
  • mosaicplot boxes are proportional to counts; further arguments: sort, off (spacing), dir (orientations), shade (extended plots coloring log-linear residuals), margin, type ("pearson"/"deviance"/"FT" residuals).
  • spineplot(bats~throws, data=...) — the close sibling.
  • assocplot(table(bats, throws)) — bars showing each cell’s deviation from independence (Cohen–Friendly).
  • See also stars and fourfoldplot.

Three-Dimensional Data

The Yosemite Elevation Data

All 3-D functions plot a matrix: row index = x, column index = y, cell = z. The book’s example: USGS elevation data for Yosemite Valley, downloaded as GridFloat (a 562×253 stream of 4-byte floats read with readBin(..., size=4, endian="little"), then given dim); packaged as yosemite:

temp <- tempfile(fileext = ".rda")
download.file("https://raw.githubusercontent.com/cran/nutshell/master/data/yosemite.rda",
              temp, mode = "wb")
load(temp)
dim(yosemite)
[1] 562 253

persp: Surfaces in Perspective

Key arguments: x/y (coordinates of rows/columns), z (the matrix), theta/phi (viewing azimuth/colatitude), r/d (viewpoint distance, perspective strength), expand (z exaggeration), col/border, ltheta/lphi (light source), shade, box/axes/nticks/ticktype.

Looking toward Half Dome — flip the columns (persp draws y bottom-up), take a square 253-column subset (mind the fencepost +1!), rotate and light:

halfdome <- yosemite[(nrow(yosemite) - ncol(yosemite) + 1):562,
                     seq(from=253, to=1)]
persp(halfdome, col=grey(.25), border=NA, expand=.15,
      theta=225, phi=20, ltheta=45, lphi=20, shade=.75)

image and heatmap

image color-codes the matrix as a grid (arguments: zlim, col — typically heat.colors, topo.colors, terrain.colors, breaks, oldstyle, add):

image(yosemite, asp=253/562, ylim=c(1,0), col=sapply((0:32)/32, gray))

(Tweaks: aspect ratio matching the data, reversed y, a 33-step gray ramp (0:32)/32.) The biology-flavored relative heatmap adds optional dendrograms along the margins — Rowv/Colv, distfun/hclustfun, scale, margins, cexRow/cexCol and friends.

contour

contour joins equal values with lines — arguments nlevels/levels, labels/labcex/drawlabels/method (label placement: "simple", "edge", "flattest"), vfont, plus the usual col/lty/lwd/add:

contour(yosemite, asp=253/562, ylim=c(1, 0))

Contours are commonly added to an image plot (add=TRUE).

Plotting Distributions

Histograms

Plate appearances (PA = AB+BB+HBP+SF+SH) per batter, 2008:

batting.2008 <- transform(batting.2008, PA=AB+BB+HBP+SF+SH)
hist(batting.2008$PA)

A horde of sub-50-PA players — worth excluding from rate statistics. Zoom in and refine the bins:

hist(batting.2008[batting.2008$PA>25, "PA"], breaks=50, cex.main=.8)

Density Plots and Rugs

Many statisticians prefer density plots — more robust, easier to read. Compute with density, draw with plot, garnish with a rug (a strip plot along the axis):

plot(density(batting.2008[batting.2008$PA>25, "PA"]))
rug(batting.2008[batting.2008$PA>25, "PA"])

Quantile-Quantile Plots

A Q-Q plot charts sample quantiles against theoretical ones — points on the 45° diagonal mean the distributions match. qqnorm compares against the normal; qqplot compares two arbitrary distributions:

qqnorm(batting.2008$AB)

At-bats are decidedly not normal — the bowed shape says it instantly.

Box Plots

Anatomy and Function

A box plot compacts a distribution: box = interquartile range (25th–75th percentile), inner line = median, whiskers = adjacent values (largest observation ≤ Q3 + 1.5·IQR; smallest ≥ Q1 − 1.5·IQR), loose points = outside values.

Key boxplot arguments: formula y~grp + data + subset (or vectors directly); range (whisker reach), width/varwidth (∝ √n), notch (±1.58·IQR/√n intervals), outline, names, plot (FALSE returns statistics), border/col, log, horizontal, add, at, pars.

OBP by Team

On-base percentage by American League team, regulars only:

batting.2008 <- transform(batting.2008, OBP=(H+BB+HBP)/(AB+BB+HBP+SF))
boxplot(OBP~teamID,
        data=batting.2008[batting.2008$PA>100 & batting.2008$lgID=="AL",],
        cex.axis=.7)

Graphics Devices

Screens and Files

Graphics land on a device. Defaults by platform: windows (Windows), X11 (Unix), quartz (macOS). File devices: bmp, jpeg, png, tiff, plus postscript, pdf, pictex (LaTeX), xfig, bitmap.

  • Most accept width, height, pointsize; file devices take a file argument. (Pedantically filename for bmp/jpeg/png/tiff — but partial matching lets file work everywhere.)
  • Close the device with dev.off() to actually write the file. The book’s own figures were produced exactly this way:
png("scatter.1.png", width=4.3, height=4.3, units="in", res=72)
plot(total_toxic_chemicals/Surface_Area, deaths_total/Population)
dev.off()

Customizing Charts

Four Routes, and Common Arguments

Customize via (1) arguments to charting functions, (2) session parameters (par), (3) functions that modify an existing chart, or (4) writing your own functions. Arguments most chart functions share: add, axes, log, type, xlab/ylab, main, sub.

par queries and sets parameters on the active device — set once, affect every subsequent plot:

par("bg")
[1] "white"
par(bg="white")           # set
par()                     # list everything

All parameters are read-write except the read-only cin, cra, csi, cxy, din. Caution: some functions reuse parameter names with different meanings (e.g., bg in points = point background).

Layout: Margins and Multiple Plots

  • Margins: mai (inches) / mar (text lines, default c(5.1, 4.1, 4.1, 2.1) for bottom/left/top/right) / mex (line-size factor); title-label spacing via mgp; device size via read-only din; square plot region via pty="s".
  • Multiple figures: mfcol=c(3,2) (fill by column) or mfrow (by row) split the device; mfg jumps to a chosen cell. Outer margins: omi/oma/omd. Per-figure geometry: pin, plt, fig. Clipping: xpd.
par(mfcol=c(3, 2))
pie(c(5, 4, 3)); plot(x=c(1,2,3,4,5), y=c(1.1,1.9,3,3.9,6))
barplot(c(1,2,3,4,5)); barplot(c(1,2,3,4,5), horiz=TRUE)
pie(c(5,4,3,2,1)); plot(c(1,2,3,4,5,6), c(4,3,6,2,1,1))
par(mfcol=c(1, 1))

(Easier alternatives: layout, split.screen — or the grid/lattice packages.)

Text, Lines, Colors, Axes, Points

  • Text size: ps (point size) × cex (global scale) × cex.axis/cex.lab/cex.main/cex.sub. Typeface: family; style via font (+ .axis, .lab, .main, .sub). Alignment adj, line spacing lheight, rotation crt (characters) / srt (strings).
  • Lines: type lty (0=blank, 1=solid, 2=dashed, 3=dotted, 4=dotdash, 5=longdash, 6=twodash), width lwd, ends lend, joins ljoin/lmitre; plot box style bty ("o", "l", "7", "]", "c", "u", "n").
  • Colors: bg, fg, col (+ .axis, .lab, .main, .sub). Specify by name (colors()), "#RRGGBB", or palette index (palette()); generators: rgb, hsv, hcl, gray, rainbow.
  • Axes: lab (tick counts), las (label orientation), mgp, tick sizes tcl/tck, tick positions xaxp/yaxp, interval style xaxs/yaxs ("r" extends 4%, "i" internal), suppress with xaxt="n"/yaxt="n"; log flags xlog/ylog.
  • Points: symbol via pch (19 solid circle, 20 bullet, 21 filled circle, 22 square, 23 diamond, 24/25 triangles; see ?points); symbol size historically mkh.
  • Others: ann (annotation on/off), ask (prompt before new page), new (overplot pretender), usr (user-coordinate extremes), smo, err.

Basic Graphics Functions

The Low-Level Layer

High-level functions are assembled from low-level ones — plot calls title, plot.new, plot.window, points, lines, axis, box; pie calls polygon, lines, text, title; boxplot delegates to bxp; and so on. Knowing the low level means you can pass customization through the high level, or build charts from scratch.

  • points(x, y, type="p", ...) — add points (useful: col, bg, pch, cex, lwd); matrix version matpoints.
  • lines(x, y, ...) — add connected segments (useful: lty, lwd, col, lend, ljoin, lmitre); matrix version matlines.
  • curve(expr, from, to, n=101, add=, ...) — plot an expression in x or a function name.

Straight Lines, Shapes, Segments

abline draws one line across the plot — by intercept+slope (a, b), horizontal (h), vertical (v), regression object (reg), or coef; untf un-transforms on log axes:

plot(x=c(0, 10), y=c(0, 10))
abline(h=4); abline(v=3)
abline(a=1, b=1); abline(coef=c(10, -1))
polygon(x=c(2, 2, 4, 4), y=c(2, 4, 4, 2))
segments(x0=6, y0=6, x1=8, y1=9)

Vector arguments draw many lines at once (abline(h=1:10, v=1:10)) — though for a grid, use grid(nx, ny, col, lty, lwd). Shapes: polygon(x, y, density, angle, border, col, lty); rectangles via rect(xleft, ybottom, xright, ytop, ...); point-pair segments via segments(x0, y0, x1, y1, ...); arrows via arrows.

text, mtext, title, legend

  • text(x, y, labels, adj, pos, offset, vfont, cex, col, font) — text at coordinates (pos: 1 below, 2 left, 3 above, 4 right).
  • mtext(text, side=3, line=0, outer=, at=, ...) — text in a margin (sides 1–4 = bottom/left/top/right).
  • title(main, sub, xlab, ylab, line=, outer=) — annotate after the fact.
  • legend(x, y, legend, ...) — the kitchen sink: fill (color boxes), col/lty/lwd/pch (sample lines/points), bty/bg/box.* (the legend box), pt.bg/pt.cex/pt.lwd, xjust/yjust, x.intersp/y.intersp/adj/text.width/text.col, merge, ncol/horiz, title, inset, xpd.

axis, box, trans3d

  • axis(side, at, labels, tick, line, pos, outer, font, lty, lwd, lwd.ticks, col, col.ticks, hadj, padj) — draw an axis where and how you want (sides 1–4 as usual).
  • box(which="plot", lty="solid") — frame the "plot", "figure", "inner", or "outer" region.
  • trans3d(x, y, z, pmat) — project 3-D points onto a persp plot, using the perspective matrix that persp returns. The bridge between 3-D surfaces and 2-D annotation.