10 Graphing in R
Excel draws a chart from a selection. R takes a different approach: you describe the chart – which variable goes on which axis, what shape to draw – and ggplot2 builds it. That takes more typing for a single quick chart and far less for the twentieth one.
Learning Objectives
By the end of this chapter, you will be able to:
- Build a plot with
ggplot(), an aesthetic mapping, and a geom - Choose the geom that suits your data
- Tell the difference between mapping an aesthetic to a variable and setting it to a constant
- Add titles, axis labels and a theme
- Split a plot into small multiples with facets, and save the result
10.1 Why a New Grammar?
You know how to make charts in Excel (Module 1). Why learn another way?
The short answer is that ggplot2 — R’s most popular visualization package — produces charts that are better than Excel’s by default, more flexible when you need to customize them, and reproducible because they are generated from code. The long answer is that ggplot2 is built on an idea called the grammar of graphics (from Leland Wilkinson’s 1999 book of the same name), which organizes charts in a way that, once you understand it, makes making new kinds of charts feel like building with LEGO instead of hunting through menus.
The grammar of graphics says that every chart is a combination of a few basic elements:
- Data — the dataset being plotted.
- Aesthetics — mappings from data columns to visual properties (x, y, color, size, shape).
- Geometries — the shapes that appear on the page (points, lines, bars, boxes).
- Statistics — transformations of the data done as part of plotting (binning for histograms, smoothing for trend lines).
- Scales — how data values map to aesthetic values (linear vs log axes, color palettes).
- Coordinate systems — Cartesian, polar, etc.
- Faceting — splitting the chart into small multiples by a categorical variable.
Every ggplot2 chart is built by adding these layers together. Once you understand the grammar, you can construct any chart you can imagine.
10.2 Your First ggplot
ggplot2 is part of the tidyverse, so if you have loaded the tidyverse, it’s already available.
A basic scatter plot:
ggplot(yields, aes(x = acres, y = yield_bu_acre)) +
geom_point()Break it down:
ggplot(yields, ...)— start a new plot using theyieldsdata frame.aes(x = acres, y = yield_bu_acre)— the aesthetic mapping. The x position is mapped to theacrescolumn, the y position to theyield_bu_acrecolumn.+ geom_point()— add a point geometry. Each row in the data becomes one point.
Note the + — ggplot2 layers are added with +, not the pipe. A common beginner mistake is to use |> instead. Don’t.
10.3 The Main Geoms
A tour of the most important geom_ functions. In each case, you set the aesthetic mapping with aes() and the geom draws something based on those mappings.
geom_point() — Scatter Plots
For showing the relationship between two continuous variables.
ggplot(yields, aes(x = acres, y = yield_bu_acre)) +
geom_point(alpha = 0.5) # alpha = transparency, helps when points overlapgeom_line() — Line Charts
For showing how a value changes over a continuous variable (usually time).
annual_mean <- yields |>
group_by(year) |>
summarise(mean_yield = mean(yield_bu_acre, na.rm = TRUE))
ggplot(annual_mean, aes(x = year, y = mean_yield)) +
geom_line() +
geom_point()Notice that we summarized first, then plotted. ggplot2 does not have a built-in “line connecting the means” geom; you compute the means and then plot them.
geom_col() and geom_bar() — Bar Charts
geom_col() plots bars with heights equal to values you give it. Use this when you have already computed the values.
region_means <- yields |>
group_by(region) |>
summarise(mean_yield = mean(yield_bu_acre, na.rm = TRUE))
ggplot(region_means, aes(x = region, y = mean_yield)) +
geom_col()geom_bar() counts rows automatically. Use it when you have un-summarized data and want bar heights to be counts.
ggplot(yields, aes(x = region)) + geom_bar()These confuse new users constantly. Rule of thumb: if your data is already summarized, use geom_col; if you want ggplot to count things for you, use geom_bar.
geom_histogram() — Histograms
For showing the distribution of a single continuous variable.
ggplot(yields, aes(x = yield_bu_acre)) +
geom_histogram(bins = 30)The bins argument controls how many bins to use. ggplot will print a warning if you don’t set it, nudging you to think about bin width.
geom_boxplot() — Box Plots
For comparing distributions across categories.
ggplot(yields, aes(x = region, y = yield_bu_acre)) +
geom_boxplot()This shows, for each region, the median, quartiles, and outliers of yield — side by side.
geom_smooth() — Trend Lines
Adds a smoother to a scatter plot.
ggplot(yields, aes(x = acres, y = yield_bu_acre)) +
geom_point(alpha = 0.3) +
geom_smooth(method = "lm") # lm = linear regressionUse method = "lm" for a straight line; omit for a flexible nonparametric smoother. Useful for visual inspection of trends, but don’t confuse a visual smoother for a rigorous model — that comes later.
10.4 Mapping vs Setting Aesthetics
A critical distinction. Consider these two:
# Mapping: color depends on region
ggplot(yields, aes(x = acres, y = yield_bu_acre, color = region)) +
geom_point()
# Setting: all points are blue
ggplot(yields, aes(x = acres, y = yield_bu_acre)) +
geom_point(color = "blue")When you want an aesthetic to depend on the data (each region gets its own color), put it inside aes(). When you want to set it to a constant (all points blue), put it outside aes(). This trips people up. If you see colors that don’t look right, check whether your color argument is in the right place.
10.5 Labels, Titles, and Themes
A bare ggplot is functional but ugly. Polish it with labs() and a theme_:
ggplot(yields, aes(x = acres, y = yield_bu_acre, color = region)) +
geom_point(alpha = 0.6) +
labs(
title = "Canola yield by field size",
subtitle = "Saskatchewan, 2025",
x = "Field size (acres)",
y = "Yield (bu/acre)",
color = "Region",
caption = "Data: [TBD]"
) +
theme_minimal()theme_minimal() is a clean, publication-ready default. Other good ones: theme_classic(), theme_bw(). Avoid the default grey background for anything you will show in public.
10.6 Faceting
Often the most powerful thing you can do with ggplot is split the chart into small multiples:
ggplot(yields, aes(x = yield_bu_acre)) +
geom_histogram(bins = 30) +
facet_wrap(~ region)This produces one histogram per region, in a grid. Much easier to read than a single chart with overlapping histograms or legends.
facet_grid(region ~ variety) makes a two-dimensional grid. Useful for comparing combinations.
10.7 Saving Plots
my_plot <- ggplot(yields, aes(x = region, y = yield_bu_acre)) +
geom_boxplot()
ggsave("region_boxplot.png", plot = my_plot, width = 6, height = 4, dpi = 300)Specify dimensions in inches and a DPI of 300 for print quality. PNG is fine for slides and web; PDF or SVG for publication.