AEM 6850
Empirical Methods for Applied Economists
Prof. Ariel Ortiz-Bobea
Tuesday, October 6, 2026

The same data, with pdf(), par(mfrow = c(1, 3)), map(fill = TRUE), cut(x, 6) and plot(), with nothing else set. The code is at the start of Part 2.
d$house_ratio; the y label does not show| Requirement | Nature’s words | The AER’s words |
|---|---|---|
| Width | “Nature’s standard figure sizes are 89 mm wide (single column) and 183 mm wide (double column).” | Not stated |
| Font | “Lettering should be in a sans-serif typeface, preferably Helvetica or Arial, the same font throughout all figures in the paper.” | Not stated |
| Text size | “Maximum text size for all other text should be 7 pt; minimum text size should be 5 pt.” | Not stated |
| Panel labels | “Separate panels in multi-part figures should each be labelled with 8 pt bold, upright (not italic) a, b, c.” | For file names: “use letters for indicating individual panels (e.g., Figure1a.ppt, Figure1b.ppt, Figure1c.ppt, etc.)” |
| Line weight | “Line weights and strokes should be set between 0.25 and 1 pt at the final size” | Not stated |
Pages: Nature, final submission and preparing figures; AER style guide. Not stated: the rule is yours to set, once for the whole paper.
| Requirement | Nature’s words | The AER’s words |
|---|---|---|
| File format | “Vector EPS or PDF format for figures containing line drawings and graphs”; “Embed fonts (True Type 2 or 42)” | “Figures must be supplied as vector-based graphics in one of the following formats—vector PDF, EPS, AI, WMF, or PPT.” |
| Photographs and images | “All photographic images must be supplied at a minimum of 300 dpi at the maximum size they can be used.” | “Photographs and other images (maps, screenshots, etc.) should be provided at 300 dpi” |
| Colour | “Colour artwork can be provided in RGB (recommended) or CMYK format.”; “An accessible colour palette to be used” | Not stated |
| Labels | “All axes to be labelled with units in parentheses, e.g. Data (unit)” | “If there are variables (italics) or matrices and vectors (boldface) in figures, they should be designated as such.” |
Pages: Nature, final submission and preparing figures; AER style guide. The AER also asks that the files “correspond to the figures in your accompanying PDF”.
Pages: Nature, initial submission; AER, submissions; PNAS, how to submit a manuscript.
Takeaway: clear writing and readable figures help a paper with referees. No study has sent the same paper to referees in the journal’s format and in a plain one.
Draw the file at the size it will print. Then every size you set is the size the reader sees.
| Nature requires | Setting in R |
|---|---|
| 89 mm wide, one column (183 mm for two) | cairo_pdf(width = 89 / 25.4): R takes inches |
| Helvetica or Arial, embedded in the file | family = "Helvetica"; cairo_pdf() embeds the font |
| Text from 5 to 7 pt | pointsize = 7 for 7-pt text; cex.axis = 6 / 7 for 6-pt tick labels (printed size = pointsize × cex) |
| Panel letters, 8 pt bold | mtext("a", font = 2, cex = 8 / 7) |
| Lines from 0.25 to 1 pt | lwd = 0.5 / 0.75 for a 0.5-pt line (lwd = 1 is 0.75 pt) |
| A vector file | cairo_pdf() writes one |
Point (pt): a length, 1/72 inch or about 0.35 mm; a font’s size in points is the height from the top of an h to the bottom of a p. With layout() or mfrow and three or more rows or columns, R multiplies every text size by 0.66 (0.83 for two by two): add par(cex = 1) after it.
R Console: writes cars-nature.pdf
# cars comes with R: speed (mph) and
# stopping distance (ft) of 50 cars
cairo_pdf("cars-nature.pdf",
width = 89 / 25.4, height = 70 / 25.4,
pointsize = 7, family = "Helvetica")
par(mar = c(3, 3.2, 1.6, 0.6), mgp = c(1.8, 0.5, 0),
cex.axis = 6 / 7, # 6-pt tick labels
lwd = 0.5 / 0.75, # 0.5-pt box; axes 0.75 pt
las = 1)
plot(cars$speed, cars$dist, pch = 16, cex = 0.5,
xlab = "Speed (mph)",
ylab = "Stopping distance (ft)")
mtext("a", side = 3, line = 0.4, adj = 0,
font = 2, cex = 8 / 7) # 8-pt bold letter
dev.off()
The PDF, 89 × 70 mm.
“About 1 in 12 men have color vision deficiency”, most often red and green (National Eye Institute). Okabe-Ito colours: Wong (Nature Methods, 2011), the paper Nature’s guide cites. Simulation: Machado, Oliveira and Fernandes (IEEE TVCG, 2009), full severity.
# Exercise 1 (3 minutes). Pen and paper, or the Console.
#
# Nature's double column is 183 mm wide. You want text at
# 7 pt, tick labels at 6 pt and lines at 1 pt.
# There are 25.4 mm in an inch.
#
# 1. Write the cairo_pdf() call that opens figure1.pdf,
# 183 mm wide and 100 mm tall, with 7-point text in
# Helvetica.
# 2. Which cex.axis gives 6-point tick labels, and which
# lwd gives a 1-point line?
#
# Two answers to compare with the room: the width in
# inches, and the lwd.code/figure1_default.R (part); needs data_clean/ from code/2_clean_data.R
library(maps)
d <- readRDS("data_clean/counties.rds")
poly <- map("county", plot = FALSE, namesonly = TRUE)
fips <- sprintf("%05d",
county.fips$fips[match(poly, county.fips$polyname)])
pdf("figures/default/figure1.pdf")
par(mfrow = c(1, 3))
map("county", fill = TRUE,
col = heat.colors(6)[cut(d$land_ratio[match(fips, d$fips)], 6)])
title("Farmland")
map("county", fill = TRUE,
col = heat.colors(6)[cut(d$house_ratio[match(fips, d$fips)], 6)])
title("Houses")
plot(d$house_ratio, d$land_ratio)
dev.off()
d has one row per county. land_ratio and house_ratio are the 2022 value divided by the 1940 value.data folder with the one in the data zip.target/figure1.png: the figure from “Figure 1, ready for Nature” as a picture at 300 dpi (pixels per inch): 2,159 × 730 pixels, 183 mm wide in print.match/ folder holds three things: this picture, a copy of the default code, and the data.match/, with no list of Nature’s rules and none of the code that drew the target.Prompt to paste into Claude Code
code/figure1_default.R draws figures/default/figure1.pdf. target/figure1.png
is a 300-dpi picture of the figure I want, at its printed size. Edit
code/figure1_default.R so that the PDF it draws matches the target as
closely as you can: size, layout, colours, class breaks, keys, titles, axis
labels, text sizes and line weights. Run the script, turn the PDF into a
PNG with pdftoppm, compare it with the target, and repeat until you cannot
get closer. Use only the files in this folder. When you finish, list the
changes you made.match/. It takes about six minutes.match/.claude/settings.json, which comes in the project, lets it edit files and run Rscript and pdftoppm (PDF to picture) without asking. Other commands still ask.code/figure1_default.R after the test run (part)
W <- 7.2; H <- 2.433
cairo_pdf("figures/default/figure1.pdf", width = W, height = H,
family = "Helvetica", pointsize = 7)
breaks <- c(0, 2, 4, 8, 16, Inf)
pal <- c("#4575B4", "#66BD63", "#FEE08B", "#F46D43", "#A50026")
page_text(x0, 0.97, letter, adj = c(0, 0.5), font = 2, cex = 1.05)
kx <- (362 + 125.2 * (0:5)) / 2000pdf() for cairo_pdf() and set the page to 7.2 × 2.43 in, with 7-pt Helvetica.page_text(), a function it wrote, puts text at a fraction of the page, and kx holds those fractions.| Text | Nature’s rule (Part 1) | Claude Code’s code (test run) | Size in print |
|---|---|---|---|
| Panel and axis titles | at most 7 pt | cex = 1.05 and 1.04 at pointsize = 7 |
7.35 and 7.28 pt |
| Panel letters | 8-pt bold | font = 2, cex = 1.05 |
7 × 1.05 = 7.35 pt |
Five minutes, in Claude; a chat is enough.
Prompt to paste into Claude, with the picture attached
The attached picture is a 300-dpi picture of a figure, at its printed
size. My R code draws the same data but looks nothing like it:
pdf("cars.pdf")
plot(cars$speed, cars$dist)
dev.off()
Change my code so that the PDF it draws matches the picture as closely as
you can: size, font, text sizes, line weights, labels, point style and the
letter. Give me the full code.cairo_pdf(), 89 × 70 mm, pointsize = 7, cex.axis = 6 / 7, lwd = 0.5 / 0.75 for the box (axes stay 0.75 pt), letter cex = 8 / 7.Two answers to compare with the room: which numbers it matched, and which it guessed.
Three runs of the prompt before class, in a chat, with no code run:
| Setting | Code on “One Nature column” | Run 1 | Run 2 | Run 3 |
|---|---|---|---|---|
| Size | 89 × 70 mm | 89 × 70 mm | 89 × 70 mm | 89 × 69 mm |
| Tick labels | 6 pt | 5.5 pt | 5 pt | 6 pt |
| Axis lines | 0.75 pt | 1.1 pt | 1.1 pt | 1.2 pt |
| Panel letter | 8 pt | 8 pt | 8 pt | 7 pt |
| PDF function | cairo_pdf() |
pdf() |
pdf() |
pdf() |
pdf(), so the fonts are not embedded.open_fig(), panel labels, and switching journalsspecs/nature.yml, a short text file of Nature’s requirements: widths, fonts, text sizes, panel labels, line weights.tools/check_fig.R, to measure the PDFname: value per line, ending in .yml or .yaml.
{min: 5, max: 7} is the one-line form of min: 5 and max: 7 indented under the name.# after a space starts a comment._quarto.yml.github/workflows/)specs/nature.yml (part)
text_pt:
value: {min: 5, max: 7}
quote: "Maximum text size for all other text should be 7 pt; minimum text size should be 5 pt."
source: final
panel_label:
value: {pt: 8, bold: true, case: lower, placement: inside}
quote: "Separate panels in multi-part figures should each be labelled with 8 pt bold, upright (not italic) a, b, c."
source: final (size, bold, lower case); house (letter at top left)
line_pt:
value: {min: 0.25, max: 1}
quote: "Line weights and strokes should be set between 0.25 and 1 pt at the
final size (lines thinner than 0.25 pt may vanish in print)."
source: finalvalue: what R and the checking script usequote: Nature’s sentence, word for wordsource: the page, listed under pages: at the top of the file, or house for our own choice where the pages say nothingtools/check_fig.R reads specs/<journal>.yml, measures the PDF, and prints one line per rule: PASS or FAIL, and what it measured.tools/, written with Claude Code before class: 137 lines of R.brew install poppler), and the R packages yaml and magick.Prompt to paste into Claude Code, in a folder with specs/ and some PDFs (a test run, before class)
specs/nature.yml and specs/aer.yml hold two journals' rules for figures,
each with the journal's words. Write tools/check_fig.R, an R script run as
Rscript tools/check_fig.R <pdf> <journal>
that measures the PDF against specs/<journal>.yml and prints one line per
rule, PASS or FAIL, with what it measured: the page width and height
(pdfinfo), the fonts and whether each is embedded (pdffonts), the size of
every word of text (pdftotext -bbox), the panel letters, and the line
weights (pdftocairo -svg). Read every number from the spec file; type none
into the script. Test it on the PDFs in pdfs/. pdfs/cars-nature.pdf was
drawn 89 x 70 mm with 7-pt text, 6-pt tick labels, an 8-pt bold letter a
and 0.5-pt lines, with embedded fonts, so it should pass every Nature rule.Terminal, in the project folder: the tool the script runs for the width
pdffonts: each font, and whether it is embeddedpdftotext -bbox: a box around every word; its height is the text sizepdftocairo -svg: every line, with its width in pointsText sizes are exact for embedded fonts and read about half a point low for fonts that are not embedded.
| Figure | Width | Fonts embedded | Text size | Panel letters | Other five checks |
|---|---|---|---|---|---|
![]() Default code |
FAIL, 177.8 mm | FAIL, 0 of 2 | FAIL, up to 9.2 pt | FAIL, none | PASS |
![]() Claude Code (Part 2) |
PASS | PASS | FAIL, up to 7.3 pt | FAIL, 7.3 pt | PASS |
![]() Target |
PASS | PASS | PASS, 6 to 7 pt | PASS, 8 pt | PASS |
Other five checks: height, font, text on page, line weight, vector only.
Prompt to paste into Claude Code
Read Nature's instructions for figures on these two pages:
https://www.nature.com/nature/for-authors/final-submission
https://research-figure-guide.nature.com/figures/preparing-figures-our-specifications/
Write specs/nature.yml in the same format as specs/aer.yml, with the same
rule names and the same shape of value, so that R/fig_style.R and
tools/check_fig.R can read it. Add height_mm_max. For each rule give the
value, the exact words it comes from, and which page. Where the pages say
nothing, write "not stated".specs/nature.yml moved aside. It takes about two minutes.specs/aer.yml as the format to copy, since that file is still there.Rscript tools/check_fig.R cars-nature.pdf nature, with the PDF from Part 1’s cars code copied into that folder. It meets every rule, so all nine checks should print PASS.| Check | Run 1 | Run 2 |
|---|---|---|
| height | not run | PASS |
| font | FAIL | PASS |
| panel letters | not run | PASS |
| width, fonts embedded, text on page, text size, line weight, vector only | PASS | PASS |
specs/aer.yml”.height_mm for height_mm_max, size_pt for pt, and the font as a list.specs/aer.yml (part)
panel_label:
value: {placement: title}
quote: "Be sure to label your figure files clearly, taking special care to use
letters for indicating individual panels (e.g., Figure1a.ppt, Figure1b.ppt,
Figure1c.ppt, etc.)."
source: style (letters in file names); house ("Panel A." in the title)
width_mm:
value: {panel: 115}
quote: not stated
source: house
text_pt:
value: {min: 7, max: 9}
quote: not stated
source: houseHouse rule: one the journal does not set, chosen once for the whole paper and marked source: house. The AER’s style guide sets the file format, letters in the names of panel files, and where source notes go. Our house rules: one file per panel, “Panel A.” in the title, panels 115 mm wide, text 7 to 9 pt, lines 0.5 to 1.5 pt, colours that stay distinct in gray.
Prompt to paste into Claude Code, in match/, after copying the project's specs/ into it
code/figure1_default.R draws Figure 1 for Nature, with every width, font,
text size and line weight typed into the script. specs/nature.yml and
specs/aer.yml hold each journal's rules, with the journal's words.
1. Write R/fig_style.R with three functions: read_spec(journal), which
reads specs/<journal>.yml; open_fig(), which opens a PDF at the
journal's width with its font, text size and line weight; and a
function that draws a panel's label the way the journal asks.
2. Copy the script to code/figure1.R and change it so that
journal <- "nature" at the top picks the spec, and the script itself
types no width, font, text size or line weight. For the AER, write one
file per panel, as specs/aer.yml says.
3. Run it for both journals and check the PDFs with pdfinfo and pdffonts.
Do not print the data; you do not need to look at it. When you finish,
list what you changed.match/, after cp -R ../specs . copies in the spec files. It takes about four minutes.R/fig_style.R, 92 lines: read_spec(), open_fig(), a panel-label function, and one it added unasked, to undo the text shrink of R’s layout().code/figure1.R, 128 lines, with no width, font, text size or line weight typed in.R/fig_style.R.R/fig_style.R and code/3_figure1.R. The next slides use them.open_fig() opens the PDF with the spec’s settingsR/fig_style.R (part); runs in R from the project folder; needs the yaml package
read_spec <- function(journal) {
r <- read_yaml(file.path("specs", paste0(journal, ".yml")))
list(journal = journal,
width_mm = r$width_mm$value,
font = r$font$value,
text_pt = r$text_pt$value,
label = r$panel_label$value,
line_pt = r$line_pt$value,
panels = r$file$value$panels)
}
open_fig <- function(path, spec, width, height_mm) {
cairo_pdf(path, width = spec$width_mm[[width]] / 25.4,
height = height_mm / 25.4,
pointsize = spec$text_pt$max, family = spec$font)
par(las = 1, mgp = c(1.5, 0.4, 0), tcl = -0.3, lwd = line_lwd(spec))
}open_fig() is a wrapper: a short function that calls other functions with some arguments filled in.cairo_pdf() and par(). Figure scripts never type them.source("R/fig_style.R"), open_fig("cars.pdf", read_spec("nature"), "single", 70) ("single" is a width named in the spec), plot(cars), dev.off().R/fig_style.R (part); in R, from the project folder, after source("R/fig_style.R")
draw_panel_title <- function(letter, title, spec, at = par("usr")[1]) {
if (spec$label$placement == "inside") {
mtext(title, side = 3, line = 0.4)
mtext(letter, side = 3, line = 0.4, adj = 0, at = at, font = 2,
cex = spec$label$pt / spec$text_pt$max)
} else {
mtext(paste0("Panel ", toupper(letter), ". ", title), side = 3,
line = 0.4)
}
}Figure1a.pdf to Figure1c.pdf.code/3_figure1.R (part); runs once data/ holds the two licensed files
journal <- "nature" # "nature" or "aer"
spec <- read_spec(journal)
if (spec$panels == "one file") {
open_fig(sprintf("figures/%s/figure1.pdf", journal), spec, "double", 62)
layout(matrix(c(1, 2, 3, 4, 4, 3), nrow = 2, byrow = TRUE),
widths = c(1.3, 1.3, 1), heights = c(1, 0.16))
par(cex = 1) # layout() with 3 columns sets cex to 0.66
... # map a, map b, scatter c, the key
} else {
for (letter in c("a", "b")) {
open_fig(sprintf("figures/%s/Figure1%s.pdf", journal, letter), spec,
"panel", 85)
... # one map and its key
}
... # Figure1c.pdf: the scatter
}"nature" to "aer", writes the AER’s three files.Figure1a.pdf to Figure1c.pdf, each 115 mm wide, text 8 and 9 pt, “Panel A.” in each title.width = mm / 25.4, and set text and lines in points: pointsize, cex, lwd = pt / 0.75.Eleven exercises on the session page, with solutions behind a click. All of them use the project; the six marked “data” need the licensed files described in its README.
check_fig.RAEM 6850 · Session 12