

Chapter 14: Lattice Graphics
Shih Chien University
2026-08-02
In the early 1990s, Richard Becker and William Cleveland at Bell Labs built Trellis graphics, grounded in Cleveland’s research on how people actually perceive information. The lattice package is R’s implementation (some function names still say “trellis”).
Lattice’s real strength: splitting a chart into panels (a grid) or groups (colors/symbols) using conditioning or grouping variables — plus clean, readable output by default. Familiar charts (scatter, bar, histogram) and new friends (dot plots, strip plots, Q-Q plots) all included.
The vocabulary: rectangular drawing areas are panels; the data sent to a panel is a packet; panel functions actually draw packets. The lifecycle:
"trellis" — hence print.trellis, plot.trellis);plot.trellis lays out the panel matrix, assigns packets, and calls the panel function for each.Warning
Inside functions and scripts, lattice plots nothing unless you print the object — the classic lattice gotcha (first met in Chapter 3).
Lattice arguments are consistent: a formula first (positional — its name has changed before!), then data. The same toy data, plain and then conditioned:


y~x|z reads “y against x, conditioned on z.”
Grouping keeps a single panel but distinguishes the groups by color/symbol — the third way to show the same data:
| graphics function | lattice function | Chart |
|---|---|---|
barplot |
barchart |
bar/column charts |
dotchart |
dotplot |
Cleveland dot plots |
hist |
histogram |
histograms |
density+plot |
densityplot |
kernel density plots |
stripchart |
stripplot |
strip charts |
qqnorm (stats) |
qqmath |
theoretical Q-Q plots |
plot |
xyplot |
scatter plots |
qqplot (stats) |
qq |
two-sample Q-Q plots |
pairs |
splom |
scatter plot matrices |
image |
levelplot |
image plots |
contour |
contourplot |
contour plots |
persp |
cloud, wireframe |
3-D perspective |
Most functions accept a formula + data frame; many also take arrays, matrices, tables, or vectors directly (e.g., barchart happily eats a table).
To superimpose extra elements, replace the panel function — typically a wrapper that adds something, then delegates:
Running example: every US birth of 2006 (CDC natality file — 3.1 GB raw; the book preprocessed it with a Perl unpack script, recoded factors in R, and sampled 10% into births2006.smpl). Variables include DOB_MM/DOB_WK (month/weekday of birth), MAGER (mother’s age), WTGAIN, SEX, APGAR5, DMEDUC, UPREVIS, ESTGEST, DMETH_REC (delivery method), DPLURAL (single/twin/…), DBWT (birth weight, grams):
Births by day of week (1 = Sunday):
Far more babies arrive on weekdays — surprising! Is it the delivery method? Tabulate by weekday × method (dropping unknowns):
Unstack and add a key; still hard to compare — so drop the color groups and switch to columns in two panels:


Now it’s plain: vaginal births dip ~25–30% on weekends, C-sections drop 50–60%. Lattice’s quick iteration — stack/unstack, group/panel, flip — is exactly how you zero in on the story.
Cleveland dot plots show one point per category — calmer than bars for bigger tables. Is the weekday pattern seasonal? Count by week × month × method and plot:
Slight seasonal wiggles; the weekly pattern persists year-round.
NHTSA’s 2003 “Stepped-Up Speed to Failure” tests (post-Firestone recall, run in hot Phoenix): tires run at increasing speeds until failure. Six tire types (B–L, various sizes/brands). Variables: Time_To_Failure, Speed_At_Failure_km_h (a stepped value — treat as a factor!), Tire_Type:
All type D tires died fast at 180 km/h; some type H tires endured. (Revisited in Chapter 18.)
Birth weight by plurality — note alphabetical panel order (hence the numbered level names) and the space-filling square panels:
Stack the panels with layout for comparability:
Roughly normal within groups; mean weight falls as plurality rises. (Other arguments: type = "percent"/"count"/"density", nint/breaks/endpoints/equal.widths for binning.)
One smooth line instead of bins — and stackable. With 427k observations we suppress the per-point strip (plot.points=FALSE):
Density plots superimpose gracefully — turn the conditioning variable into a grouping one:
Strip plots are one-dimensional scatter plots — many values per category (vs. dot plots’ one), ideal for small data. Weights of quadruplets-and-up (44 observations), jittered:
qqmath compares sample quantiles to a theoretical distribution (default: normal via qnorm; also f.value, distribution, qtype). Sanity check with actual normals, then the births:


Birth weights: not quite normal.
Second running example: 3,281 San Francisco home sales, 2008-02-13 to 2009-07-14, compiled by the author from newspaper listings, geocoded, and joined to Zillow neighborhood shapefiles (with sp/maptools and point.in.polygon). Variables (as packaged — the book’s text names a few differently): line, county, street, city, zip, date (sale date), price, bedrooms, squarefeet, lotsize, year (built), latitude/longitude, month, neighborhood. Real data — imperfect, partially missing:
Prices range $100,000–$9,500,000 — intuitively not normal:
The log transform straightens the line nicely.
Now split by bedrooms — groups, smooth lines instead of points (type="smooth", an argument that flows to the panel function), explicit subset to keep the key clean:
Separated lines, higher for more bedrooms — the same trick works for square-footage sextiles (Hmisc::cut2(squarefeet, g=6) as groups) and neighborhoods.
Price vs. size — first naively, then trimmed (price < $4M, < 6,000 sq ft) and conditioned by zip code with custom strips:


The linear relationship sharpens within each zip — neighborhood conditioning (price~squarefeet|neighborhood, with pch=19, cex=.2) tells the same story with better labels.
Distribution of prices over time — round dates to months with cut. The raw attempt drowns in outliers; log-transform and rotate the labels:
Medians wandered; the interquartile range moved less; the high end visibly thinned — yet the basic shape stayed stable month to month. (Volume note: the data starts mid-February and ends mid-July — don’t over-read those months.)
splom(x, data, ...) — scatter plot matrices (lattice’s pairs); panels drawn by panel.splom, the matrix orchestrated by superpanel=panel.pairs, axis detail via pscales, names via varnames.qq(x, data, ...) — two-sample quantile-quantile plots, comparing two observed distributions (formula syntax factor ~ value).Flat grids colored by a third value (drawn by panel.levelplot; key arguments at, cuts=15, region, colorkey, col.regions, contour). Coordinates are too precise to plot raw — bin first with cut + table:

Mean price by area? Swap table for tapply:
A third factor makes panels — sales location by (capped) bedroom count:
contourplot is the topographic-map sibling (cuts=7, labels, contour=TRUE, region=FALSE).
cloud — 3-D point clouds (projections of 3-D points onto 2-D); arguments include aspect=c(1,1), panel.aspect, drawing handled by panel.cloud → panel.3dscatter.wireframe — 3-D surfaces, lattice’s persp (panel.3dwire underneath).rfs (residual and fit spread plots for a model, optionally with distribution=qnorm) and parallel (parallel-coordinate plots).Shared by the high-level functions:
| Argument | Description |
|---|---|
x (formula), data |
what to plot, where the variables live |
allow.multiple, outer |
handling of y1+y2~x formulas: superpose or separate panels |
groups / subset / subscripts |
grouping variable; row filter; expose indices to panels |
panel |
the panel function — the customization hook |
aspect |
panel aspect ratio ("fill", "xy" banking to 45°, numeric) |
auto.key |
quick legend (logical or list) |
prepanel |
function determining per-panel space/limits |
strip |
strip-drawing function (e.g., strip.custom); strip.left for left strips |
xlab, ylab, main, sub, xlim, ylim |
annotation and limits |
scales |
axis control (next slide) |
layout, between, skip |
panel arrangement, spacing |
drop.unused.levels, default.scales, lattice.options |
the long tail (passed via trellis.skeleton) |
scales is a list (optionally with x and y sublists):
relation — "same" (shared limits), "free" (per-panel), "sliced" (equal-length slices);draw, alternating, tick.number, at, labels, cex, rot, font, col, log, abbreviate…Seen in action earlier: scales=list(x=list(rot=90)) for rotated month labels, scales=list(draw=FALSE) to silence map axes.
Lattice ignores par; its own settings live in a deep parameter list:
$alpha
[1] 1
$cex
[1] 0.8
$col
[1] "black"
$font
[1] 1
$lineheight
[1] 1
Groups include superpose.symbol/superpose.line (groups), plot.polygon (bars/histograms), box.rectangle/box.umbrella (bwplot), plot.line/plot.symbol (xyplot et al.), reference.line, regions+shade.colors (levelplot/wireframe), strip.background/strip.shingle, fontsize, par.main.text and friends.
plot.trellis — arguments controlling drawing: position/split/more (plot several trellis objects on one page), panel.height/panel.width, packet.panel (which packet lands in which panel; default packet.panel.default), panel.error.strip.default(which.given, which.panel, var.name, factor.levels, shingle.intervals, strip.names, strip.levels, style, bg, fg, par.strip.text) — how strips display conditioning levels; customize via strip.custom.simpleKey(text, points=TRUE, rectangles=FALSE, lines=FALSE, col, cex, ...) — builds the legend that auto.key requests.Grid-based primitives mirror base graphics: llines/panel.lines, lpoints/panel.points, ltext/panel.text, lsegments/panel.segments, lpolygon/panel.polygon, larrows/panel.arrows, lrect/panel.rect, plus panel.axis and panel.superpose.
Ready-made panel helpers to compose into custom panels:
panel.abline, panel.curve, panel.rug, panel.grid, panel.fill;panel.mathdensity (overlay a theoretical density), panel.average;panel.loess (smoother), panel.lmline (regression line);panel.* (e.g., panel.xyplot, panel.barchart, panel.histogram) for delegation — the pattern from our first custom panel.Copyright. These slides are adapted from R in a Nutshell: A Desktop Quick Reference (2nd ed.) by Joseph Adler, O’Reilly Media. All rights reserved by the original author and publisher.
Non-commercial use only. These materials are strictly for educational purposes and may not be used for commercial gain.
Attribution. Any reproduction, distribution, or use of these materials must properly credit the original source.
R in a Nutshell: A Desktop Quick Reference