Plot design

Day 04

Prof Amanda Luby

Carleton College
Stat 220 - Fall 2026

Today

  • ggplot2 review
  • customizations in ggplot2
  • Some guidelines for plot design

What we know:

  1. A basic set of geometries
  • geom_point()
  • geom_histogram()
  • geom_boxplot()
  • geom_violin()
  • geom_bar()
  1. How to map variables to aesthetics
  • x and y axis
  • color
  • shape
  • alpha
  • size
  1. How to change axis labels and titles
  • labs()

What next?

  • Setting aesthetics
  • Using facets
  • Changing scales
  • Changing coordinates
  • Changing themes
  • Adding annotations

Where does Carleton land?

In 2018, Carleton’s in-state tuition was about $55,000 a year. Was that a lot? And what did Carleton graduates earn? I’d like to make an argument about how Carleton compares to other colleges, using the tuition and pay data from Day 1.

Warm Up

  1. Log into maize
    • If you have to type your PAT in everytime you push to GitHub, follow the directions at Getting Set up with Git and GitHub #4 to tell RStudio to save your credentials (may or may not work on maize 🤷‍♀️ but definitely works on local installations)
  2. Find the .qmd template for today at the course website
  3. Open it up in maize/Rstudio
  4. Work with a neighbor to recreate this graph →

08:00

Polishing plots

We’re not quite satisfied….

  • I can’t tell 50 states apart (and I can’t find Carleton!)
  • The points pile on top of each other
  • Public and private schools are all mixed together
  • I don’t like the default color scheme
  • I don’t like the gray background

Setting aesthetics

Setting = choosing a certain value for an aesthetic

ggplot(college_pay) + 
  geom_point(
    aes(x = in_state_tuition, 
        y = mid_career_pay, 
        color = state), 
    alpha = 0.6,
    size = .5
    ) 

Facets

Make “small multiples”, where each facet shows a subset of the data

ggplot(college_pay) + 
  geom_point(
    aes(x = in_state_tuition, 
        y = mid_career_pay, 
        color = state), 
    alpha = 0.6,
    size = .5
    ) +
  facet_wrap(vars(type))

Tip

You may have seen this before using the syntax facet_wrap(~type). Both work, but vars() is the more modern/flexible syntax, so that’s what I’ve used here

Choose what to color by

50 colors are too many to tell apart. Instead, I made a group variable that splits schools into Carleton, other Minnesota colleges, and everywhere else. (We’ll learn to make variables like this next week!)

ggplot(college_pay) + 
  geom_point(
    aes(x = in_state_tuition, 
        y = mid_career_pay, 
        color = group), 
    alpha = 0.6,
    size = .5
    ) 

Changing scales

scale_<aes>_<method>()

Examples:

  • scale_color_manual()

  • scale_color_brewer()

  • scale_color_viridis_c()

  • scale_shape_manual()

Example (built-in scale)

ggplot(college_pay) + 
  geom_point(
    aes(x = in_state_tuition, 
        y = mid_career_pay, 
        color = group), 
    alpha = 0.6,
    size = 2
    ) + 
  scale_color_brewer(palette = "Dark2")

Example (built-in scale)

RColorBrewer::display.brewer.all()

Aside: types of color scales

Example (continuous color scale)

Sometimes the variable we color by is numeric, like the percent of graduates with STEM degrees. Then we need a continuous color scale.

ggplot(college_pay) + 
  geom_point(
    aes(x = in_state_tuition, 
        y = mid_career_pay, 
        color = stem_percent), 
    alpha = 0.6,
    size = 2
    ) + 
  scale_color_viridis_c()

Example (manual color palette)

Let’s make Carleton gold2, other Minnesota colleges navyblue, and everyone else gray70

ggplot(college_pay) + 
  geom_point(
    aes(x = in_state_tuition, 
        y = mid_career_pay, 
        color = group), 
    alpha = 0.6,
    size = 2
    ) + 
  scale_color_manual(values = c("gold2", "navyblue", "gray70"))

You may have to try a few things to get the colors in the right order

Can also change non-color scales

ggplot(college_pay) + 
  geom_point(
    aes(x = in_state_tuition, 
        y = mid_career_pay, 
        color = group), 
    alpha = 0.6,
    size = 2
    ) + 
  scale_color_manual(values = c("gold2", "navyblue", "gray70")) + 
  scale_x_continuous(labels = scales::dollar)

Can also change non-color scales

ggplot(college_pay) + 
  geom_point(
    aes(x = in_state_tuition, 
        y = mid_career_pay, 
        color = group), 
    alpha = 0.6,
    size = 2
    ) + 
  scale_color_manual(values = c("gold2", "navyblue", "gray70")) + 
  scale_x_log10()

Can also change non-color scales

ggplot(college_pay) + 
  geom_point(
    aes(x = in_state_tuition, 
        y = mid_career_pay, 
        color = group), 
    alpha = 0.6,
    size = 2
    ) + 
  scale_color_manual(values = c("gold2", "navyblue", "gray70")) + 
  scale_x_reverse()

Changing Themes

Theme: The non-data ink on your plots

  • background
  • tick marks
  • grid lines
  • font
  • legend position
  • legend appearance

Prepackaged themes

ggplot2 themes

  • theme_grey()
  • theme_bw()
  • theme_linedraw()
  • theme_light()
  • theme_dark()
  • theme_minimal()
  • theme_classic()
  • theme_void()
  • theme_test()

ggthemes themes

  • theme_clean()
  • theme_economist()
  • theme_excel()
  • theme_fivethirtyeight()
  • theme_gdocs()
  • theme_solarized()
  • theme_stata()
  • theme_tufte()
  • theme_wsj()
  • And more!

Using a prepackaged theme

ggplot(data = <DATA>) + 
  <GEOM_FUNCTION>(
     mapping = aes(<MAPPINGS>)
  ) +
  <FACET_FUNCTION> +
  theme_<name>()  

Try it

Apply theme_light() to the scatterplot

00:30

Even more customizations

  • Move legend
  • Clean up labels and title
  • Format the axis labels
  • Annotations

?theme

theme(line, rect, text, title, aspect.ratio, axis.title, axis.title.x,
  axis.title.x.top, axis.title.x.bottom, axis.title.y, axis.title.y.left,
  axis.title.y.right, axis.text, axis.text.x, axis.text.x.top,
  axis.text.x.bottom, axis.text.y, axis.text.y.left, axis.text.y.right,
  axis.ticks, axis.ticks.x, axis.ticks.x.top, axis.ticks.x.bottom,
  axis.ticks.y, axis.ticks.y.left, axis.ticks.y.right, axis.ticks.length,
  axis.line, axis.line.x, axis.line.x.top, axis.line.x.bottom, axis.line.y,
  axis.line.y.left, axis.line.y.right, legend.background, legend.margin,
  legend.spacing, legend.spacing.x, legend.spacing.y, legend.key,
  legend.key.size, legend.key.height, legend.key.width, legend.text,
  legend.text.align, legend.title, legend.title.align, legend.position,
  legend.direction, legend.justification, legend.box, legend.box.just,
  legend.box.margin, legend.box.background, legend.box.spacing,
  panel.background, panel.border, panel.spacing, panel.spacing.x,
  panel.spacing.y, panel.grid, panel.grid.major, panel.grid.minor,
  panel.grid.major.x, panel.grid.major.y, panel.grid.minor.x,
  panel.grid.minor.y, panel.ontop, plot.background, plot.title,
  plot.subtitle, plot.caption, plot.tag, plot.tag.position, plot.margin,
  strip.background, strip.background.x, strip.background.y,
  strip.placement, strip.text, strip.text.x, strip.text.y,
  strip.switch.pad.grid, strip.switch.pad.wrap, ..., complete = FALSE,
  validate = TRUE)

Move the legend

ggplot(college_pay) + 
  geom_point(
    aes(x = in_state_tuition, 
        y = mid_career_pay, 
        color = group), 
    alpha = 0.6,
    size = 2
    ) + 
  scale_color_manual(values = c("gold2", "navyblue", "gray70")) + 
  theme_minimal() + 
  theme(
    legend.position = "top"
  )

Clean up labels and title

ggplot(college_pay) + 
  geom_point(
    aes(x = in_state_tuition, 
        y = mid_career_pay, 
        color = group), 
    alpha = 0.6,
    size = 2
    ) + 
  scale_color_manual(values = c("gold2", "navyblue", "gray70")) + 
  theme_minimal() + 
  theme(
    legend.position = "top"
  ) + 
  labs(
    x = "In-state tuition",
    y = "Median mid-career pay",
    color = "",
    title = "Carleton: top 4% in tuition, top 13% in pay"
  )

Remove minor gridlines

ggplot(college_pay) + 
  geom_point(
    aes(x = in_state_tuition, 
        y = mid_career_pay, 
        color = group), 
    alpha = 0.6,
    size = 2
    ) + 
  scale_color_manual(values = c("gold2", "navyblue", "gray70")) + 
  theme_minimal() + 
  theme(
    legend.position = "top",
    panel.grid.minor = element_blank()
  ) + 
  labs(
    x = "In-state tuition",
    y = "Median mid-career pay",
    color = "",
    title = "Carleton: top 4% in tuition, top 13% in pay"
  )

Format the axes

ggplot(college_pay) + 
  geom_point(
    aes(x = in_state_tuition, 
        y = mid_career_pay, 
        color = group), 
    alpha = 0.6,
    size = 2
    ) + 
  scale_color_manual(values = c("gold2", "navyblue", "gray70")) + 
  scale_x_continuous(labels = scales::dollar) + 
  scale_y_continuous(labels = scales::dollar) + 
  theme_minimal() + 
  theme(
    legend.position = "top",
    panel.grid.minor = element_blank()
  ) + 
  labs(
    x = "In-state tuition",
    y = "Median mid-career pay",
    color = "",
    title = "Carleton: top 4% in tuition, top 13% in pay"
  )

Add an annotation

ggplot(college_pay) + 
  geom_point(
    aes(x = in_state_tuition, 
        y = mid_career_pay, 
        color = group), 
    alpha = 0.6,
    size = 2
    ) + 
  scale_color_manual(values = c("gold2", "navyblue", "gray70")) + 
  scale_x_continuous(labels = scales::dollar) + 
  scale_y_continuous(labels = scales::dollar) + 
  theme_minimal() + 
  theme(
    legend.position = "top",
    panel.grid.minor = element_blank()
  ) + 
  labs(
    x = "In-state tuition",
    y = "Median mid-career pay",
    color = "",
    title = "Carleton: top 4% in tuition, top 13% in pay"
  ) +
  annotate("point", 
           x = 54759, 
           y = 109900, 
           shape = 1, 
           size = 7, 
           stroke = 1.2, 
           col = "gold4") +
  annotate("text", 
           x = 52500, 
           y = 109900, 
           label = "Carleton", 
           hjust = 1, 
           fontface = "bold", 
           col = "gold4")

Tip

I had to look these values up in the data, and try a few different combinations to get it to look OK

Increase text size

ggplot(college_pay) + 
  geom_point(
    aes(x = in_state_tuition, 
        y = mid_career_pay, 
        color = group), 
    alpha = 0.6,
    size = 2
    ) + 
  scale_color_manual(values = c("gold2", "navyblue", "gray70")) + 
  scale_x_continuous(labels = scales::dollar) + 
  scale_y_continuous(labels = scales::dollar) + 
  theme_minimal(base_size = 20) + 
  theme(
    legend.position = "top",
    panel.grid.minor = element_blank()
  ) + 
  labs(
    x = "In-state tuition",
    y = "Median mid-career pay",
    color = "",
    title = "Carleton: top 4% in tuition, top 13% in pay"
  ) +
  annotate("point", 
           x = 54759, 
           y = 109900, 
           shape = 1, 
           size = 7, 
           stroke = 1.2, 
           col = "gold4") +
  annotate("text", 
           x = 52500, 
           y = 109900, 
           label = "Carleton", 
           hjust = 1, 
           fontface = "bold", 
           size = 6, 
           col = "gold4")

“Final” version

Plot Design

Which do you prefer?

General guidelines

  1. Show the data, don’t distort it
  2. Choose the right plot
  3. Use color meaningfully and with restraint
  4. Tell a story
  5. Leave out non-story details

1. Show the data, don’t distort it

1. Show the data, don’t distort it

What a huge effect!

But it isn’t the whole story

2. Choose the right plot: Which slice is the biggest/smallest?

00:30

2. Choose the right plot

  • Wilke has good suggestions in chapters 5-16

  • Always stop and think about how easy it is to see the story

  • Try a few different options

3. Use color meaningfully and with restraint

3. Use color meaningfully and with restraint

4. Tell a story

4. Tell a story

One way to do this is by highlighting the important parts

5. Leave out non-story details

Is this train schedule easy to read?

5. Leave out non-story details

Does removing gridlines make it somewhat easier?

Data visualizations have an aura of objectivity

“We focus on four conventions which imbue visualisations with a sense of objectivity, transparency and facticity. These include: a) two-dimensional viewpoints; b) clean layouts; c) geometric shapes and lines; d) the inclusion of data sources.”

Same data, two stories

Both are true. Which would go in an admissions brochure? Which would go in a critique of college costs?

Why does the graph on the right have such a strong negative trend?

Good graphs can also break these guidelines

Good graphs can also break these guidelines

Your turn

Now that we have the toolkit to make customizations to our plots, and some “rules” for good graphs, let’s break them!

  1. Choose a graph (the one from class today, one from last class, one from homework, etc.)

  2. Make it ugly

  • Change the color scale
  • Choose a complete theme
  • Make at least 3 custom tweaks to the theme options
  1. Explain why it’s ugly (what “rules” are you breaking? what makes it an ineffective graph?)

  2. Post to the “Day04” post on our Ed board when you’re done (you don’t have to post your explanation)

  3. Look through the posts and ♥ any that are especially good (bad)!