Foundations of
Data Science

Spring 2026 | Data 2 (399)
Jeffrey M. Girard | Lecture 13a

Roadmap

  • Create multiple subplots using facet_wrap() and facet_grid()

  • Use position adjustments to stack, fill, and dodge bar charts

  • Address overplotting in scatterplots using jittering and hex-binning

Faceting

Faceting

  • Faceting creates multiple subplots
    • Each subplot shows a different subset of the data and is excellent for exploring conditional relationships
  • There are two main types of faceting:
    • Wrap: Creates a ribbon of plots
    • Grid: Creates a matrix of plots

Setup for Faceting

data("penguins", package = "datasets")
# Drop rows with NAs
penguins2 <- drop_na(penguins) 

# Create a base plot to add to
p <- 
  ggplot(
    penguins2, 
    aes(x = body_mass, y = flipper_len)
  ) + 
  geom_point()
p

Facet Wrap

# Create a subplot for each species
p + facet_wrap(facets = vars(species))

Layout Options

# Control the number of columns and rows to wrap across
p + facet_wrap(facets = vars(species), ncol = 2, nrow = 2)

Multiple Variables

# Wrap across combinations of species and sex
p + facet_wrap(facets = vars(species, sex))

Freeing Scales

# While wrapping, give each *subplot* its own scales
p + facet_wrap(facets = vars(species, sex), scales = "free")

Facet Grid

# Create a sex-by-species matrix (or grid) of subplots
p + facet_grid(rows = vars(sex), cols = vars(species))

Facet Labellers

# Label each facet "strip" using both the variable name and value
p + facet_grid(rows = vars(sex), cols = vars(species), labeller = label_both)

Freeing Scales

# While gridding, give each *row* and each *column* its own scales
p + facet_grid(rows = vars(sex), cols = vars(species), scales = "free")

Positions

Position Adjustments

  • Geoms often overlap with many groups
  • Position adjustments dictate how ggplot2 should handles this overlap
    • We can stack items on top of one another
    • We can place them side-by-side
    • We can stretch them to a common size
  • This is useful for bar and column charts
  • It is critical for aligning text labels

The Stacking Default

# The default for geom_bar is `position = "stack"`
ggplot(penguins2, aes(x = species, fill = island)) + geom_bar(color = "black")

Proportional Bars

# We can use `position = "fill"` to turn them into proportions
ggplot(penguins, aes(x = species, fill = island)) +  
  geom_bar(color = "black", position = "fill") + labs(y = "proportion")

Dodging Bars

# We can use `position = "dodge"` to put them side-by-side
ggplot(penguins, aes(x = species, fill = island)) +
  geom_bar(color = "black", position = "dodge")

Plotting from Summaries

# To explicitly plot the zeros, we can use a two-step process
penguins_sum <- 
  penguins2 |> 
  count(species, island, .drop = FALSE) |> 
  print()
    species    island   n
1    Adelie    Biscoe  44
2    Adelie     Dream  55
3    Adelie Torgersen  47
4 Chinstrap    Biscoe   0
5 Chinstrap     Dream  68
6 Chinstrap Torgersen   0
7    Gentoo    Biscoe 119
8    Gentoo     Dream   0
9    Gentoo Torgersen   0

Dodging Columns

# Now we map the y-axis to `n` and use geom_col()
ggplot(penguins_sum, aes(x = species, y = n, fill = island)) +
  geom_col(color = "black", position = "dodge")

Overplotting

# Multiple overlapping geoms can lead to "overplotting"
data("mpg", package = "ggplot2")
ggplot(mpg, aes(x = displ, y = hwy)) + geom_point(alpha = 0.5)

Jittering Points

# Jittering is pushing each point slightly in a random direction
ggplot(mpg, aes(x = displ, y = hwy)) + 
  geom_point(alpha = 0.5, position = "jitter")

Jitter Magnitude

# We can push farther but pushing too far may become misleading
ggplot(mpg, aes(x = displ, y = hwy)) + 
  geom_point(alpha = 0.5, position = position_jitter(width = .5, height = .5))

Extreme Overplotting

# With lots and lots of points, jittering is unlikely to help
data("diamonds", package = "ggplot2")
ggplot(diamonds, aes(x = carat, y = price)) + geom_point(alpha = 0.5)

Binning into Hexes

# In such cases, we can count points within hexagonal bins
library(hexbin)
ggplot(diamonds, aes(x = carat, y = price)) + geom_hex()

Discrete Overplotting

# With discrete x, we want to jitter horizontally only
ggplot(mpg, aes(x = drv, y = hwy)) + geom_point()

Horizontal Jitter

# Set height to 0 to only push left or right
ggplot(mpg, aes(x = drv, y = hwy)) + 
  geom_point(position = position_jitter(width = 0.25, height = 0))

Discrete Overplotting

## Beeswarm plots can also handle discrete overplotting well
library(ggbeeswarm)
ggplot(mpg, aes(x = drv, y = hwy)) + geom_beeswarm()