Skip to Content
Course content

27: Faceting and Themes in ggplot2

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've been spending a lot of time lately cleaning up a few reports for a client, and it reminded me of a common struggle we all hit with ggplot2. You have a great dataset, you've got your x and y axes set, but the moment you add a third or fourth variable, the plot becomes a "hairball"—just a chaotic mess of overlapping points and colors that no one can actually read.

Let's look at the mpg dataset. I want to see if there's a relationship between engine displacement (displ) and highway mileage (hwy), but I suspect the trend changes depending on the type of car (class).

library(ggplot2)

ggplot(mpg, aes(x = displ, y = hwy)) + 
  geom_point()

The Overplotting Problem

That's a decent start, but it's too generic. If I try to distinguish the car classes using color, it gets a bit better, but it's still crowded. I'm squinting at the screen trying to figure out where the "compact" cars end and the "subcompacts" begin.

ggplot(mpg, aes(x = displ, y = hwy, color = class)) + 
  geom_point()

The colors help, but the points are still on top of each other. I don't want one giant plot; I want a series of small plots, one for each car class, all using the same scale so I can compare them side-by-side. This is where faceting comes in.

Breaking it apart with facet_wrap

I'll try facet_wrap. I like this function because it's flexible—it just takes a variable and "wraps" the resulting plots into a grid that fits the page.

ggplot(mpg, aes(x = displ, y = hwy)) + 
  geom_point() + 
  facet_wrap(~class)

That's a massive improvement. Now I can clearly see that "compact" cars have a much tighter clustering than "suvs". The ~ symbol is just R's way of saying "formula," and here it tells ggplot to split the data by the class variable.

Creating a matrix with facet_grid

But what if I want to compare two categorical variables at once? Let's say I want to see class and the drive train (drv: front-wheel, rear-wheel, or 4-wheel). If I use facet_wrap, I'd just get a long list of combinations. Instead, I'll use facet_grid to create a proper matrix.

ggplot(mpg, aes(x = displ, y = hwy)) + 
  geom_point() + 
  facet_grid(drv ~ class)

Notice the syntax: rows ~ columns. Now I have a grid where each row is a drive type and each column is a car class. It's a bit denser, but the structural comparison is immediate. I can see, for example, that 4-wheel drive cars generally have larger engines across almost all classes.

Cleaning up the aesthetic noise

Now, here is my personal gripe: the default ggplot2 grey background. It's fine for a quick check, but if I'm putting this in a presentation, it looks a bit dated and "heavy." I want something cleaner.

I'll try theme_minimal() first, which strips away the grey background and the heavy borders.

ggplot(mpg, aes(x = displ, y = hwy)) + 
  geom_point() + 
  facet_wrap(~class) + 
  theme_minimal()

That's much breathier. There are a few other built-in options like theme_bw() (classic white background with a thin black border) or theme_void() (which removes everything—useful for maps or diagrams), but theme_minimal() is usually my go-to for data exploration.

Going surgical with theme()

Sometimes a preset theme isn't enough. What if I like theme_minimal(), but I hate that the axis text is too small, or I want the plot title to be centered? This is where the theme() function comes in. Think of it as the "CSS of ggplot2."

I'll add a title and then use theme() to tweak the specific elements.

ggplot(mpg, aes(x = displ, y = hwy)) + 
  geom_point() + 
  facet_wrap(~class) + 
  labs(title = "Highway Mileage by Engine Size") + 
  theme_minimal() + 
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 16),
    axis.text = element_text(color = "darkblue"),
    strip.background = element_rect(fill = "lightgrey")
  )

A couple of things to notice here:

  • hjust = 0.5 centers the title.
  • element_text() is used for things made of text.
  • element_rect() is used for things that are boxes (like the facet labels, called "strips").
I've now moved from a cluttered "hairball" to a professional, multi-panel visualization with a customized look. The key is to build the logic first (the geom), then the structure (the facet), and finally the polish (the theme).


📋 Practical Task

Exercise: Visualizing Diamond Quality vs. Price

Using the diamonds dataset (built into ggplot2), create a visualization that explores the relationship between carat (weight) and price.

  1. Create a scatter plot of carat vs price.
  2. Use facet_wrap to create a separate panel for each cut of the diamond.
  3. Apply theme_bw() to the entire plot.
  4. Use the theme() function to:
    • Change the strip.text (the facet labels) to be italicized.
    • Change the plot.title to be centered and colored "darkred".
  5. Add a descriptive title to your plot using labs().
Rating
0 0

There are no comments for now.

to be the first to leave a comment.