Coordinates + facets

Lecture 06

Dr. Benjamin Soltoff

Cornell University
INFO 3312/5312 - Fall 2026

September 10, 2026

Announcements

Announcements

  • Homework 03
  • No class on Tuesday

Where did the school buses go?

  • What is the story?
  • How does the design account for the time gaps?

Learning objectives

  • Define coordinate systems
  • Identify methods for implementing non-Cartesian coordinate systems with {ggplot2}
  • Evaluate the effectiveness of radial charts
  • Introduce and implement waffle charts for comparing proportions
  • Utilize facets for small multiple plots

Coordinate systems

Coordinate systems

  • Combine the two position aesthetics (x and y) to produce a two-dimension position on the plot
  • Draw axes and panel backgrounds in coordination with the coordinate systems

Linear coordinate systems

Preserve the shape of geoms

  • coord_cartesian(): the default Cartesian coordinate system, where the 2D position of an element is given by the combination of the x and y positions.

  • coord_flip(): Cartesian coordinate system with x and y axes flipped.

    Just swap the x and y aesthetics instead

  • coord_fixed(): Cartesian coordinate system with a fixed aspect ratio.

    Use coord_cartesian(ratio = X) to set a specific aspect ratio.

Non-linear coordinate systems

Can change the shapes – a straight line may no longer be straight. The closest distance between two points may no longer be a straight line.

  • coord_transform(): Apply arbitrary transformations to x and y positions, after the data has been processed by the stat
  • coord_radial(): Polar coordinates
  • coord_sf(): Map projections

Setting limits: the base plot

base_plot <- ggplot(penguins, aes(x = flipper_len, y = body_mass, color = species)) +
  geom_point(alpha = 0.25) +
  geom_smooth()
base_plot

Predict: which plots will differ?

Which plots will show a different smoothing curve than Plot 1?

A. Plot 2 only   B. Plots 2 and 3   C. Plots 2, 3, and 4   D. None of them

00:30
base_plot +
  labs(title = "Plot 1")

base_plot +
  scale_x_continuous(limits = c(190, 220)) +
  scale_y_continuous(limits = c(4000, 5000)) +
  labs(title = "Plot 2")

base_plot +
  xlim(190, 220) +
  ylim(4000, 5000) +
  labs(title = "Plot 3")

base_plot +
  coord_cartesian(xlim = c(190, 220),
                  ylim = c(4000, 5000)) +
  labs(title = "Plot 4")

Setting limits: what the plots say

B. xlim()/ylim() are shortcuts for scale limits, so Plots 2 and 3 are identical. Plot 4 fits the smooth on all the data, then zooms.

Setting limits: what the warnings say

base_plot +
  labs(title = "Plot 1")
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## Warning: Removed 2 rows containing non-finite outside the scale range (`stat_smooth()`).
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_point()`).

base_plot +
  scale_x_continuous(limits = c(190, 220)) +
  scale_y_continuous(limits = c(4000, 5000)) +
  labs(title = "Plot 2")
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## Warning: Removed 235 rows containing non-finite outside the scale range (`stat_smooth()`).
## Warning: Removed 235 rows containing missing values or values outside the scale range
## (`geom_point()`).

base_plot +
  xlim(190, 220) +
  ylim(4000, 5000) +
  labs(title = "Plot 3")
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## Warning: Removed 235 rows containing non-finite outside the scale range (`stat_smooth()`).
## Removed 235 rows containing missing values or values outside the scale range (`geom_point()`).

base_plot +
  coord_cartesian(xlim = c(190, 220),
                  ylim = c(4000, 5000)) +
  labs(title = "Plot 4")
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## Warning: Removed 2 rows containing non-finite outside the scale range (`stat_smooth()`).
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_point()`).

Setting limits

  • Setting scale limits: Any data outside the limits is thrown away
    • scale_*_continuous(limits = ...)
    • xlim() and ylim()
  • Setting coordinate system limits: Use all the data, but only display a small region of the plot (zooming in)
    • coord_cartesian(xlim = ..., ylim = ...)

Fixing aspect ratio with coord_cartesian(ratio = X)

Useful when having a fixed aspect ratio makes sense, e.g. scores on two tests (reading and writing) on the same scale (0 to 100 points)

Transformations

ggplot(penguins, aes(x = bill_dep, y = body_mass)) +
  geom_point() +
  geom_smooth(method = "lm")

ggplot(
  penguins,
  aes(
    x = log10(bill_dep),
    y = log10(body_mass)
  )
) +
  geom_point() +
  geom_smooth(method = "lm")

ggplot(penguins, aes(x = bill_dep, y = body_mass)) +
  geom_point() +
  geom_smooth(method = "lm") +
  scale_x_log10() +
  scale_y_log10()

ggplot(penguins, aes(x = bill_dep, y = body_mass)) +
  geom_point() +
  geom_smooth(method = "lm") +
  coord_transform(x = "log10", y = "log10")

Radial coordinate systems

Polar coordinates

Polar coordinates in R

Crime patterns by time of day

Baby sleep patterns

Baby sleep patterns

Alright, let’s talk about pie charts

Radial charts with coord_radial()

Recall: Hierarchy of perceptual tasks

Rank Perceptual Task Chart Examples
1 Position (common scale) Bar chart, dot plot
2 Position (non-aligned) Small multiples
3 Length Stacked bar (non-baseline)
4 Angle Pie chart
5 Circular area Bubble chart
6 Rectangular area Treemap

Authentic pie chart

Pie charts

Pie charts

For categorical variables with few levels, pie charts can work well





For categorical variables with many levels, pie charts are difficult to read

What about donut charts?

Which cognitive tasks are enabled by each chart type?

  • Position
  • Length
  • Area
  • Angle

02:00

Designing effective pie/donut charts

  • Show a simple part-to-whole comparison
  • Order the slices meaningfully
  • Start the largest slice at the 12 o’clock position
  • Use direct labels

Waffle charts

  • Like with pie charts, work best when the number of levels represented is low
  • Unlike pie charts, easier to compare proportions that represent non-simple fractions

Change over time

Application exercise

ae-05

Instructions

  • Go to the course GitHub org and find your ae-05 (repo name will be suffixed with your GitHub name).
  • Clone the repo in Positron, run renv::restore() to install the required packages, open the Quarto document in the repo, and follow along and complete the exercises.
  • Render, commit, and push your edits by the AE deadline – end of the day
15:00

Facets

GDP per capita over time

Code
library(gapminder)

ggplot(data = gapminder, mapping = aes(x = year, y = gdpPercap)) +
  geom_line(aes(group = country)) +
  scale_y_continuous(labels = label_currency(scale_cut = cut_short_scale())) +
  labs(
    x = "Year",
    y = "GDP per capita"
  )

Differences across continents

Code
ggplot(data = gapminder, mapping = aes(x = year, y = gdpPercap)) +
  geom_line(aes(group = country, color = continent)) +
  scale_y_continuous(labels = label_currency(scale_cut = cut_short_scale())) +
  scale_color_discrete_qualitative() +
  labs(
    x = "Year",
    y = "GDP per capita",
    color = "Legend"
  )

Differences across continents

Code
ggplot(data = gapminder, mapping = aes(x = year, y = gdpPercap)) +
  geom_line(mapping = aes(group = country)) +
  scale_y_continuous(labels = label_currency(scale_cut = cut_short_scale())) +
  facet_wrap(facets = vars(continent)) +
  labs(
    x = "Year",
    y = "GDP per capita",
    color = "Legend"
  )

Clean up the plot

Code
ggplot(data = gapminder, mapping = aes(x = year, y = gdpPercap)) +
  geom_line(mapping = aes(group = country), color = "grey70") +
  geom_smooth(linewidth = 1.25, method = "loess", se = FALSE) +
  scale_y_log10(labels = label_currency(scale_cut = cut_short_scale())) +
  facet_wrap(facets = vars(continent)) +
  labs(
    title = "GDP per capita across continents",
    x = "Year",
    y = "GDP per capita",
    color = "Legend"
  )

Grid layout

Code
data("gss_sm", package = "socviz")

ggplot(data = gss_sm, mapping = aes(x = age, y = childs)) +
  geom_point(alpha = 0.2) +
  geom_smooth(se = FALSE) +
  facet_grid(rows = vars(sex), cols = vars(race)) +
  labs(
    x = "Age",
    y = "Number of children"
  )

facet_*()

  • facet_wrap()
    • “wraps” a 1d ribbon of panels into 2d
    • generally for faceting by a single variable
  • facet_grid() for faceting
    • produces a 2d grid of panels defined by variables which form the rows and columns
    • generally for faceting by two variables
  • facet_null(): a single plot, the default

Free the scales!

p <- ggplot(penguins, aes(
  x = flipper_len,
  y = body_mass
)) +
  geom_point()

p +
  facet_wrap(facets = vars(species)) +
  labs(title = "Same scales")

p +
  facet_wrap(
    facets = vars(species),
    scales = "free"
  ) +
  labs(title = "Free scales")

Free some scales

p +
  facet_wrap(
    facets = vars(species),
    scales = "free_x"
  ) +
  labs(title = "Free x scale")

p +
  facet_wrap(
    facets = vars(species),
    scales = "free_y"
  ) +
  labs(title = "Free y scale")

Freeing the y scale improves the display, but it’s still not satisfying. What’s wrong with it?

ggplot(penguins, aes(y = species, x = body_mass, fill = species)) +
  geom_boxplot(show.legend = FALSE) +
  facet_grid(rows = vars(island)) +
  labs(title = "Same scale and spacing")

ggplot(penguins, aes(y = species, x = body_mass, fill = species)) +
  geom_boxplot(show.legend = FALSE) +
  facet_grid(rows = vars(island), scales = "free_y") +
  labs(title = "Free y scale, same spacing")

Free spaces

ggplot(penguins, aes(y = species, x = body_mass, fill = species)) +
  geom_boxplot(show.legend = FALSE) +
  facet_grid(rows = vars(island), scales = "free_y", space = "free") +
  labs(title = "Free y scale and spacing")

Predict: how many gray points?

In the Gentoo panel, how many gray points appear?

  1. None
  2. Only the Gentoo penguins
  3. Every penguin except the Gentoo penguins
  4. Every penguin in the data
00:30
penguins_sans_species <- penguins |> select(-species)

ggplot(data = penguins, mapping = aes(x = flipper_len, y = body_mass)) +
  geom_point(data = penguins_sans_species, color = "gray") +
  geom_point(mapping = aes(color = species)) +
  facet_wrap(facets = vars(species))

Highlighting across facets

penguins_sans_species <- penguins |> select(-species)

ggplot(data = penguins, mapping = aes(x = flipper_len, y = body_mass)) +
  geom_point(data = penguins_sans_species, color = "gray") +
  geom_point(mapping = aes(color = species)) +
  facet_wrap(facets = vars(species))

D. Dropping species leaves the gray layer with nothing to facet on, so {ggplot2} draws all of it in every panel. The colored layer still has species, so it splits normally.

Themes

Complete themes

Themes from {ggthemes}

Themes and color scales from {ggthemes}

p +
  aes(color = species) +
  scale_color_wsj() +
  theme_wsj() +
  labs(title = "Wall Street Journal")

Modifying theme elements

p +
  labs(title = "Palmer penguins") +
  theme(
    plot.title = element_text(color = "red", face = "bold", family = "Comic Sans MS"),
    plot.background = element_rect(color = "red", fill = "mistyrose")
  )

Wrap up

Recap

  • Coordinate systems define how position aesthetics are drawn on the plot
  • Limits and transformations work differently when applied to scales vs. coordinate systems
  • Waffle charts are an alternative to pie charts for visualizing proportions
  • Faceting generates small multiple window plots

Acknowledgements