Waffle charts for visualizing proportions

Suggested answers

Application exercise
Answers
Modified

September 11, 2026

Important

These are suggested answers. This document should be used as reference only, it’s not designed to be an exhaustive key.

library(tidyverse)
library(waffle)
library(viridis)

# fix seed value for reproducibility
set.seed(123)

theme_set(theme_void())

Waffle charts

{waffle} provides a {ggplot2} implementation of waffle plots. The typical workflow consists of preparing the data by tabulating in advance and then plotting it with {ggplot2} and geom_waffle().

Round 1: estimate from a pie chart

Demonstration: Run the chunk below to draw a pie chart of penguins by species.

penguins |>
  count(species) |>
  ggplot(mapping = aes(x = "", y = n, fill = species)) +
  geom_col(color = "white") +

  coord_radial(
    theta = "y",
    reverse = "theta",
    expand = FALSE
  ) +
  scale_fill_viridis_d(end = 0.8) +
  labs(
    title = "Penguins by species",
    x = NULL,
    y = NULL,
    fill = NULL
  ) +
  theme_void() +
  theme(legend.position = "top")
1
Set x mapping to a constant value
2
Use coord_radial() to make it a pie chart
3
theta = "y" means we use y values for the angles
4
reverse = "theta" orders the slices clockwise instead of counterclockwise
5
expand = FALSE removes a gap between the first and last slice

Your turn: Looking only at the pie chart, estimate what percentage of the penguins belong to each species. Do not count anything and do not write any code – just read the chart. Your three estimates should sum to about 100%.

Answers will vary. In practice the most common error pattern is overestimating Chinstrap (the smallest slice) and underestimating Gentoo, because the Gentoo slice spans the bottom of the circle where the eye has no vertical reference to anchor against.

Basic waffle chart

Demonstration: Prepare the penguins data frame to visualize the number of penguins by species.

penguins |>
  count(species)
    species   n
1    Adelie 152
2 Chinstrap  68
3    Gentoo 124

Demonstration: Use the prepared data to draw a basic color-coded waffle chart

penguins |>
  count(species) |>
  ggplot(mapping = aes(fill = species, values = n)) +
  geom_waffle() +
  labs(
    title = "Penguins by species",
    x = NULL,
    y = NULL,
    fill = NULL
  )

Improve the waffle chart

Your turn: Adjust the waffle chart to use a fixed aspect ratio so the symbols are squares. Rotate the chart so the squares are stacked vertically.

penguins |>
  count(species) |>
  ggplot(mapping = aes(fill = species, values = n)) +
  geom_waffle(
    n_rows = 20,
    size = 1,
    color = "white",
    flip = TRUE
  ) +
  labs(
    title = "Penguins by species",
    x = NULL,
    y = NULL,
    fill = NULL
  ) +
  coord_cartesian(ratio = 1)

Demonstration: {waffle} will draw all observations on the chart. For larger datasets, this is problematic. Instead, we might want to visualize the proportion of observations in each category. Use geom_waffle() to represent the data as proportions instead.

penguins |>
  count(species) |>
  ggplot(mapping = aes(fill = species, values = n)) +
  geom_waffle(
    size = 1,
    color = "white",
    make_proportional = TRUE
  ) +
  labs(
    title = "Penguins by species",
    x = NULL,
    y = NULL,
    fill = NULL
  ) +
  coord_cartesian(ratio = 1)

Your turn: Adjust the waffle chart to use a better color palette and move the legend to the top.

penguins |>
  count(species) |>
  ggplot(mapping = aes(fill = species, values = n)) +
  geom_waffle(
    size = 1,
    color = "white",
    make_proportional = TRUE
  ) +
  scale_fill_viridis_d(end = 0.8) +
  labs(
    title = "Penguins by species",
    x = NULL,
    y = NULL,
    fill = NULL
  ) +
  coord_cartesian(ratio = 1) +
  theme(legend.position = "top")

Round 2: estimate from your waffle chart

Your turn: Now read the same three proportions off the waffle chart you just built. Again, do not count the squares one by one and do not write any code – read the chart the way a reader would.

Answers will vary, but they usually cluster much closer to the truth. With make_proportional = TRUE the grid is 10 x 10, so each square is exactly one percentage point and students can count rows instead of judging angles. Expect estimates that land on round multiples of 5 or 10.

Round 3: compute the true proportions

Your turn: Now compute the actual percentage of penguins in each species. Use count() to tabulate and mutate() to convert the counts into percentages.

penguins |>
  count(species) |>
  mutate(prop = n / sum(n))
    species   n      prop
1    Adelie 152 0.4418605
2 Chinstrap  68 0.1976744
3    Gentoo 124 0.3604651

Adelie is 152 of 344 penguins (44.2%), Chinstrap is 68 (19.8%), and Gentoo is 124 (36%).

Compare

Your turn: Fill in the table using your two sets of estimates and the values you just computed. Error is your estimate minus the truth, so a positive number means you overestimated.

Your turn: Which chart produced smaller errors for you? Was the gap the same for all three species, or larger for some than others?

Your turn: In lecture we ranked perceptual tasks by how accurately people read them: position on a common scale is most accurate, then length, then angle, then area. A pie chart asks you to judge angle; a waffle chart asks you to judge counts of discrete squares. Do your errors line up with that ranking? If they do not, what else about these charts might explain it?

Your turn: Compare your results with the person next to you. Where your errors disagree, what about how each of you read the chart might account for the difference?

One more comparison: donut charts

Demonstration: A donut chart is a pie chart with the center removed.

penguins |>
  count(species) |>
  # map x to a constant value of 2 to create space in the middle
  ggplot(mapping = aes(x = 2, y = n, fill = species)) +
  geom_col(color = "white") +
  coord_radial(theta = "y", expand = FALSE) +
  # increase limits to produce white space on the x dimension
  scale_x_continuous(limits = c(0.5, 2.5)) +
  scale_fill_viridis_d(end = 0.8) +
  labs(
    title = "Penguins by species",
    x = NULL,
    y = NULL,
    fill = NULL
  ) +
  theme_void() +
  theme(legend.position = "top")

Your turn: Removing the center removes the vertex of every angle. Based on what you measured above, would you expect estimates from the donut chart to be better or worse than from the pie chart? Explain your reasoning.

A donut chart removes the vertex, so the angle judgment is no longer available and the reader falls back on arc length – length ranks above angle in the hierarchy, which argues the donut should be no worse and possibly slightly better. The counterargument is that arc length is measured along a curve of varying radius, which is harder than the straight-line lengths the hierarchy was established on. But honestly, the difference is small either way, and both are beaten by the waffle chart for the two non-simple proportions.

sessioninfo::session_info()
─ Session info ───────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.6.1 (2026-06-24)
 os       macOS Tahoe 26.6.2
 system   aarch64, darwin23
 ui       X11
 language (EN)
 collate  en_US.UTF-8
 ctype    en_US.UTF-8
 tz       America/New_York
 date     2026-09-11
 pandoc   3.4 @ /usr/local/bin/ (via rmarkdown)
 quarto   1.10.18 @ /usr/local/bin/quarto

─ Packages ───────────────────────────────────────────────────────────────────
 ! package      * version date (UTC) lib source
 P cli            3.6.6   2026-04-09 [?] RSPM
 P curl           7.1.0   2026-04-22 [?] RSPM
 P digest         0.6.39  2025-11-19 [?] RSPM
 P dplyr        * 1.2.1   2026-04-03 [?] RSPM
 P DT             0.34.0  2025-09-02 [?] RSPM
 P evaluate       1.0.5   2025-08-27 [?] RSPM
 P extrafont      0.20    2025-09-24 [?] RSPM
 P extrafontdb    1.1     2025-09-28 [?] RSPM
 P farver         2.1.2   2024-05-13 [?] RSPM
 P fastmap        1.2.0   2024-05-15 [?] RSPM
 P forcats      * 1.0.1   2025-09-25 [?] RSPM
 P generics       0.1.4   2025-05-09 [?] RSPM
 P ggplot2      * 4.0.3   2026-04-22 [?] RSPM
 P glue           1.8.1   2026-04-17 [?] RSPM
 P gridExtra      2.3.1   2026-06-25 [?] RSPM
 P gtable         0.3.6   2024-10-25 [?] RSPM
 P here           1.0.2   2025-09-15 [?] RSPM
 P hms            1.1.4   2025-10-17 [?] RSPM
 P htmltools      0.5.9   2025-12-04 [?] RSPM
 P htmlwidgets    1.6.4   2023-12-06 [?] RSPM
 P jsonlite       2.0.0   2025-03-27 [?] RSPM
 P knitr          1.51    2025-12-20 [?] RSPM
 P labeling       0.4.3   2023-08-29 [?] RSPM
 P lifecycle      1.0.5   2026-01-08 [?] RSPM
 P lubridate    * 1.9.5   2026-02-04 [?] RSPM
 P magrittr       2.0.5   2026-04-04 [?] RSPM
 P otel           0.2.0   2025-08-29 [?] RSPM
 P pillar         1.11.1  2025-09-17 [?] RSPM
 P pkgconfig      2.0.3   2019-09-22 [?] RSPM
 P plyr           1.8.9   2023-10-02 [?] RSPM
 P purrr        * 1.2.2   2026-04-10 [?] RSPM
 P R6             2.6.1   2025-02-15 [?] RSPM
 P RColorBrewer   1.1-3   2022-04-03 [?] RSPM
 P Rcpp           1.1.2   2026-07-05 [?] RSPM
 P readr        * 2.2.0   2026-02-19 [?] RSPM
 P renv           1.2.2   2026-04-16 [?] RSPM
 P rlang          1.3.0   2026-07-05 [?] RSPM
 P rmarkdown      2.31    2026-03-26 [?] RSPM
 P rprojroot      2.1.1   2025-08-26 [?] RSPM
 P Rttf2pt1       1.3.14  2025-09-26 [?] RSPM
 P S7             0.2.2   2026-04-22 [?] RSPM
 P scales         1.4.0   2025-04-24 [?] RSPM
 P sessioninfo    1.2.4   2026-06-04 [?] RSPM
 P stringi        1.8.9   2026-08-04 [?] RSPM
 P stringr      * 1.6.0   2025-11-04 [?] RSPM
 P tibble       * 3.3.1   2026-01-11 [?] RSPM
 P tidyr        * 1.3.2   2025-12-19 [?] RSPM
 P tidyselect     1.2.1   2024-03-11 [?] RSPM
 P tidyverse    * 2.0.0   2023-02-22 [?] RSPM
 P timechange     0.4.0   2026-01-29 [?] RSPM
 P tzdb           0.5.0   2025-03-15 [?] RSPM
 P vctrs          0.7.3   2026-04-11 [?] RSPM
 P viridis      * 0.6.5   2024-01-29 [?] RSPM
 P viridisLite  * 0.4.3   2026-02-04 [?] RSPM
 P waffle       * 1.0.2   2026-08-06 [?] Github (hrbrmstr/waffle@767875b)
 P withr          3.0.3   2026-06-19 [?] RSPM
 P xfun           0.60    2026-07-09 [?] RSPM
 P yaml           2.3.12  2025-12-10 [?] RSPM

 [1] /Users/bcs88/Projects/info-3312/course-site/renv/library/macos/R-4.6/aarch64-apple-darwin23
 [2] /Users/bcs88/Library/Caches/org.R-project.R/R/renv/sandbox/macos/R-4.6/aarch64-apple-darwin23/46003b10

 * ── Packages attached to the search path.
 P ── Loaded and on-disk path mismatch.

──────────────────────────────────────────────────────────────────────────────