4  Dot plots

Graphs in this chapter use fmggp2::theme_blockbuster(), modeled on a Blockbuster Video store: royal blue background, yellow ticket stub marks, white Arial text, and an Impact title.

Requirements:

✅ a numeric (continuous) variable


4.1 Description

A dot plot displays each data point as a dot on a single axis, making it useful for identifying patterns, gaps, and outliers in small to moderate datasets. Each dot represents an observation in the data, stacked over categories or spread along a number line.

In ggplot2, we can build dot plots with geom_dotplot().

4.2 Set up

PACKAGES:

Install packages.

show/hide
library(ggplot2)
library(dplyr) # for tibble() and data manipulation

DATA:

In the late 90s, most Friday nights started at Blockbuster Video.

Blockbuster Video membership card (source: Wikipedia)

We’ll simulate how many movies 60 households rented in one week with rpois(), which generates whole number counts (0, 1, 2, and so on). Setting the seed with set.seed() means you’ll get the same 60 households every time you run the code below.

show/hide
set.seed(1993) # same 60 households every time the code runs
blockbuster_rentals <- tibble(
  household_id = 1:60, # 60 different households, one row each
  rentals = rpois(n = 60, lambda = 3) # weekly rentals (about 3 on average)
)
glimpse(blockbuster_rentals)
#> Rows: 60
#> Columns: 2
#> $ household_id <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, …
#> $ rentals      <int> 2, 0, 2, 4, 3, 3, 2, 2, 5, …

4.3 Distribution

Rentals are counts: a household rents 0, 1, 2, or more movies, but never 2.5. Dot plots are a good fit for counts like these because each dot is one household, so the graph is just the data stacked into piles.

A Blockbuster store in Durham, North Carolina (source: Wikipedia)

Because we picked the rental rate in rpois(), we know what the piles should look like before we draw them. Below we’ll move from the counts, to a picture, to the formula.

COUNT IT:

Start with the first ten households.

show/hide
head(blockbuster_rentals$rentals, 10)
#>  [1] 2 0 2 4 3 3 2 2 5 0

Most of these households rented two or three movies, two rented none, and one rented five. If we count all 60 households, we can compare each pile to the number of households we’d expect (we’ll see where dpois() comes from below).

show/hide
blockbuster_rentals |> 
  count(rentals) |> 
  mutate(expected = round(60 * dpois(rentals, lambda = 3), 1))

The observed piles stay close to the expected ones. Counts like these have one more property worth checking: the mean and the variance should be about the same.

show/hide
blockbuster_rentals |> 
  summarise(mean = mean(rentals), variance = var(rentals))

DRAW IT:

The dot plot below stacks one dot per household above its number of rentals. The tallest piles sit at two and three rentals, and the piles get shorter on both sides. The dashed line marks the rental rate we used in rpois() (3 per week). The right side trails out further than the left because a household can’t rent fewer than zero movies.

show/hide
ggplot(data = blockbuster_rentals, 
    aes(x = rentals)) + 
  geom_dotplot(binwidth = 1, dotsize = 0.3) + 
  geom_vline(xintercept = 3, linetype = "dashed", color = "white") + 
  scale_x_continuous(breaks = 0:7) + 
  labs(x = "Number of rentals")

NAME IT:

The rpois() call that created the data is a single statement:

\[ X \sim \text{Poisson}(\lambda = 3) \]

The Poisson distribution describes counts of events in a fixed window of time (here, rentals in one week). It has a single parameter, the rate (\(\lambda\)), which is both the mean and the variance. That’s why our simulated mean (2.9) and variance (2.8) are so close.

The chance of a household renting exactly \(k\) movies is:

\[ P(X = k) = \frac{\lambda^k e^{-\lambda}}{k!} \]

This is what dpois() calculates. For \(k = 3\), \(P(X = 3) = 3^3 e^{-3} / 3! \approx 0.224\), so we’d expect about \(60 \times 0.224 \approx 13\) of our 60 households to rent three movies. We got 15. Try a larger lambda in rpois() and the piles move right and spread out, because the mean and the variance grow together.

4.4 Grammar

CODE:

  1. Create labels with labs()

  2. Initialize the graph with ggplot() and provide data

  3. Map rentals to the x axis

  4. Add the geom_dotplot() layer

  5. Set the binwidth to 1 (one pile per number of rentals) and adjust the dotsize

  6. Set the x axis breaks with scale_x_continuous(breaks = 0:7)

show/hide
labs_dotplot <- labs(
  title = "Blockbuster Video",
  caption = "Simulated data",
  subtitle = "Rentals per household in one week",
  x = "Number of rentals")
ggp2_dotplot <- ggplot(data = blockbuster_rentals,
      aes(x = rentals)) + 
    geom_dotplot(binwidth = 1, dotsize = 0.2) + 
    scale_x_continuous(breaks = 0:7) 
ggp2_dotplot + 
  labs_dotplot

GRAPH:

A downside of dot plots is that the y axis is hard to interpret.1 The axis is labeled count, but its numbers run from 0 to 1, even though the tallest pile in our graph holds 15 households.

These numbers don’t line up with the piles because geom_dotplot() doesn’t put counts on the y axis. It sizes each dot as a fraction of the binwidth (with dotsize = 0.2, each dot is a fifth of one rental wide). Then it stacks the dots on top of each other until the pile runs out. The height of a pile depends on how many dots it holds and how big each dot is, so changing dotsize makes every pile taller or shorter without changing the data. The y axis isn’t measuring anything; it’s just leftover space for the dots to sit in.

The number of households is still in the graph, but we read it by counting the dots in each pile, not by looking across to the axis. Because the axis can mislead readers, it’s common to hide it with scale_y_continuous(NULL, breaks = NULL).

show/hide
labs_dotplot <- labs(
  title = "Blockbuster Video",
  caption = "Simulated data",
  subtitle = "Rentals per household in one week",
  x = "Number of rentals")
ggp2_dotplot <- ggplot(data = blockbuster_rentals,
      aes(x = rentals)) + 
    geom_dotplot(binwidth = 1, dotsize = 0.2) + 
    scale_x_continuous(breaks = 0:7) + 
    scale_y_continuous(NULL, breaks = NULL)
ggp2_dotplot + 
  labs_dotplot


  1. Read more in the function reference.↩︎