Visualizing proportions

Claus O. Wilke

2025-02-10

The archetypal visualization of proportions: pie chart

 

Party composition of the 8th German Bundestag, 1976–1980

Pie chart vs stacked bars vs side-by-side bars

 

Pie chart vs stacked bars vs side-by-side bars

 

Pie chart vs stacked bars vs side-by-side bars

 

Example where side-by-side bars are preferred

 

Example where side-by-side bars are preferred

 

Example where side-by-side bars are preferred

 

Example where side-by-side bars are preferred

 

Example where stacked bars are preferred

 

Change in the gender composition of the Rwandan parliament from 1997 to 2016

Pros and cons of different approaches

      Pie chart     Stacked bars Side-by-side bars
Allows easy comparison of relative proportions

Pros and cons of different approaches

      Pie chart     Stacked bars Side-by-side bars
Allows easy comparison of relative proportions
Shows data as proportions of a whole

Pros and cons of different approaches

      Pie chart     Stacked bars Side-by-side bars
Allows easy comparison of relative proportions
Shows data as proportions of a whole
Emphasizes simple fractions (1/2, 1/3, …)

Pros and cons of different approaches

      Pie chart     Stacked bars Side-by-side bars
Allows easy comparison of relative proportions
Shows data as proportions of a whole
Emphasizes simple fractions (1/2, 1/3, …)
Visually appealing for small datasets

Pros and cons of different approaches

      Pie chart     Stacked bars Side-by-side bars
Allows easy comparison of relative proportions
Shows data as proportions of a whole
Emphasizes simple fractions (1/2, 1/3, …)
Visually appealing for small datasets
Works well for a large number of subsets

Pros and cons of different approaches

      Pie chart     Stacked bars Side-by-side bars
Allows easy comparison of relative proportions
Shows data as proportions of a whole
Emphasizes simple fractions (1/2, 1/3, …)
Visually appealing for small datasets
Works well for a large number of subsets
Works well for time series and similar

No one visualization fits all scenarios!

Nested proportions: proportions of proportions

Mosaic plots subdivide data along two dimensions

Dataset: Bridges in Pittsburgh by construction material and era of construction

Treemaps work well for more complex cases

Dataset: Land surface area of US states

We can nest pie charts with clever coloring

Dataset: Bridges in Pittsburgh by construction material and era of construction

Parallel sets can show many subdivisions at once

Dataset: Bridges in Pittsburgh by construction material and era of construction

Don’t show nested proportions without nesting!

Dataset: Bridges in Pittsburgh by construction material and era of construction

Making pie charts with ggplot2

Making pie charts with ggplot2

We have three options:

  • geom_bar()/geom_col() with poolar coordinates
    Pros: simple
    Cons: hard to customize
  • geom_arc_bar() with stat_pie()
    Pros: relatively simple, some customization
    Cons: requires ggforce & some more complex code
  • geom_arc_bar() with manual computation
    Pros: maximum flexibility for customization
    Cons: requires ggforce & much more complex code

Making pie charts with ggplot2: polar coords

# the data
bundestag <- tibble(
  party = c("CDU/CSU", "SPD", "FDP"),
  seats = c(243, 214, 39)
)

bundestag
# A tibble: 3 × 2
  party   seats
  <chr>   <dbl>
1 CDU/CSU   243
2 SPD       214
3 FDP        39

Making pie charts with ggplot2: polar coords

# the data
bundestag <- tibble(
  party = c("CDU/CSU", "SPD", "FDP"),
  seats = c(243, 214, 39)
)

# make bar chart
ggplot(bundestag) +
  aes(seats, "YY", fill = party) + 
  geom_col()

 

Making pie charts with ggplot2: polar coords

# the data
bundestag <- tibble(
  party = c("CDU/CSU", "SPD", "FDP"),
  seats = c(243, 214, 39)
)

# make bar chart in polar coords
ggplot(bundestag) +
  aes(seats, "YY", fill = party) + 
  geom_col() +
  coord_polar()

 

Making pie charts with ggplot2: polar coords

# the data
bundestag <- tibble(
  party = c("CDU/CSU", "SPD", "FDP"),
  seats = c(243, 214, 39)
)

# make bar chart in polar coords
ggplot(bundestag) +
  aes(seats, "YY", fill = party) + 
  geom_col() +
  coord_polar() +
  scale_x_continuous(
    name = NULL, breaks = NULL
  ) +
  scale_y_discrete(
    name = NULL, breaks = NULL
  ) +
  ggtitle("German Bundestag 1976-1980")

 

Making pie charts with ggplot2: ggforce stat pie

library(ggforce)

ggplot(bundestag) +
  aes(
    x0 = 0, y0 = 0, # position of pie center
    r0 = 0, r = 1,  # inner and outer radius
    amount = seats, # size of pie slices
    fill = party
  ) + 
  geom_arc_bar(stat = "pie")

 

Making pie charts with ggplot2: ggforce stat pie

library(ggforce)

ggplot(bundestag) +
  aes(
    x0 = 0, y0 = 0, # position of pie center
    r0 = 0, r = 1,  # inner and outer radius
    amount = seats, # size of pie slices
    fill = party
  ) + 
  geom_arc_bar(stat = "pie") +
  coord_fixed() # make pie perfectly circular

 

Making pie charts with ggplot2: ggforce stat pie

library(ggforce)

ggplot(bundestag) +
  aes(
    x0 = 0, y0 = 0, # position of pie center
    r0 = 0, r = 1,  # inner and outer radius
    amount = seats, # size of pie slices
    fill = party
  ) + 
  geom_arc_bar(stat = "pie") +
  coord_fixed(  # make pie perfectly circular
    # adjust limits as needed
    xlim = c(-1, 3), ylim = c(-1, 3)
  )

 

Making pie charts with ggplot2: ggforce stat pie

library(ggforce)

ggplot(bundestag) +
  aes(
    x0 = 1, y0 = 1, # position of pie center
    r0 = 0, r = 1,  # inner and outer radius
    amount = seats, # size of pie slices
    fill = party
  ) + 
  geom_arc_bar(stat = "pie") +
  coord_fixed(  # make pie perfectly circular
    # adjust limits as needed
    xlim = c(-1, 3), ylim = c(-1, 3)
  )

 

Making pie charts with ggplot2: ggforce stat pie

library(ggforce)

ggplot(bundestag) +
  aes(
    x0 = 1, y0 = 1, # position of pie center
    r0 = 1, r = 2,  # inner and outer radius
    amount = seats, # size of pie slices
    fill = party
  ) + 
  geom_arc_bar(stat = "pie") +
  coord_fixed(  # make pie perfectly circular
    # adjust limits as needed
    xlim = c(-1, 3), ylim = c(-1, 3)
  )

 

Making pie charts with ggplot2: ggforce manual

# prepare pie data
pie_data <- bundestag |>
  arrange(seats)    # sort so pie slices end up sorted

pie_data
# A tibble: 3 × 2
  party   seats
  <chr>   <dbl>
1 FDP        39
2 SPD       214
3 CDU/CSU   243

Making pie charts with ggplot2: ggforce manual

# prepare pie data
pie_data <- bundestag |>
  arrange(seats) |> # sort so pie slices end up sorted
  mutate(
    end_angle = 2*pi*cumsum(seats)/sum(seats)    # ending angle for each pie slice
  )

pie_data
# A tibble: 3 × 3
  party   seats end_angle
  <chr>   <dbl>     <dbl>
1 FDP        39     0.494
2 SPD       214     3.20 
3 CDU/CSU   243     6.28 

Making pie charts with ggplot2: ggforce manual

# prepare pie data
pie_data <- bundestag |>
  arrange(seats) |> # sort so pie slices end up sorted
  mutate(
    end_angle = 2*pi*cumsum(seats)/sum(seats),   # ending angle for each pie slice
    start_angle = lag(end_angle, default = 0)    # starting angle for each pie slice
  )

pie_data
# A tibble: 3 × 4
  party   seats end_angle start_angle
  <chr>   <dbl>     <dbl>       <dbl>
1 FDP        39     0.494       0    
2 SPD       214     3.20        0.494
3 CDU/CSU   243     6.28        3.20 

Making pie charts with ggplot2: ggforce manual

# prepare pie data
pie_data <- bundestag |>
  arrange(seats) |> # sort so pie slices end up sorted
  mutate(
    end_angle = 2*pi*cumsum(seats)/sum(seats),   # ending angle for each pie slice
    start_angle = lag(end_angle, default = 0),   # starting angle for each pie slice
    mid_angle = 0.5*(start_angle + end_angle),   # middle of each pie slice, for text labels
  )

pie_data
# A tibble: 3 × 5
  party   seats end_angle start_angle mid_angle
  <chr>   <dbl>     <dbl>       <dbl>     <dbl>
1 FDP        39     0.494       0         0.247
2 SPD       214     3.20        0.494     1.85 
3 CDU/CSU   243     6.28        3.20      4.74 

Making pie charts with ggplot2: ggforce manual

# prepare pie data
pie_data <- bundestag |>
  arrange(seats) |> # sort so pie slices end up sorted
  mutate(
    end_angle = 2*pi*cumsum(seats)/sum(seats),   # ending angle for each pie slice
    start_angle = lag(end_angle, default = 0),   # starting angle for each pie slice
    mid_angle = 0.5*(start_angle + end_angle),   # middle of each pie slice, for text labels
    # horizontal and vertical justifications for outer labels
    hjust = if_else(mid_angle > pi, 1, 0),
    vjust = if_else(mid_angle < pi/2 | mid_angle > 3*pi/2, 0, 1)
  )

pie_data
# A tibble: 3 × 7
  party   seats end_angle start_angle mid_angle hjust vjust
  <chr>   <dbl>     <dbl>       <dbl>     <dbl> <dbl> <dbl>
1 FDP        39     0.494       0         0.247     0     0
2 SPD       214     3.20        0.494     1.85      0     1
3 CDU/CSU   243     6.28        3.20      4.74      1     0

Making pie charts with ggplot2: ggforce manual

ggplot(pie_data) +
  aes(
    x0 = 0, y0 = 0, r0 = 0, r = 1,
    start = start_angle, end = end_angle,
    fill = party
  ) +
  geom_arc_bar() +
  coord_fixed()

 

Making pie charts with ggplot2: ggforce manual

ggplot(pie_data) +
  aes(
    x0 = 0, y0 = 0, r0 = 0, r = 1,
    start = start_angle, end = end_angle,
    fill = party
  ) +
  geom_arc_bar() +
  geom_text( # place amounts inside the pie
    aes(
      x = 0.6 * sin(mid_angle),
      y = 0.6 * cos(mid_angle),
      label = seats
    )
  ) +
  coord_fixed()

 

Making pie charts with ggplot2: ggforce manual

ggplot(pie_data) +
  aes(
    x0 = 0, y0 = 0, r0 = 0, r = 1,
    start = start_angle, end = end_angle,
    fill = party
  ) +
  geom_arc_bar() +
  geom_text( # place amounts inside the pie
    aes(
      x = 0.2 * sin(mid_angle),
      y = 0.2 * cos(mid_angle),
      label = seats
    )
  ) +
  coord_fixed()

 

Making pie charts with ggplot2: ggforce manual

ggplot(pie_data) +
  aes(
    x0 = 0, y0 = 0, r0 = 0, r = 1,
    start = start_angle, end = end_angle,
    fill = party
  ) +
  geom_arc_bar() +
  geom_text( # place amounts inside the pie
    aes(
      x = 0.8 * sin(mid_angle),
      y = 0.8 * cos(mid_angle),
      label = seats
    )
  ) +
  coord_fixed()

 

Making pie charts with ggplot2: ggforce manual

ggplot(pie_data) +
  aes(
    x0 = 0, y0 = 0, r0 = 0, r = 1,
    start = start_angle, end = end_angle,
    fill = party
  ) +
  geom_arc_bar() +
  geom_text( # place amounts inside the pie
    aes(
      x = 0.6 * sin(mid_angle),
      y = 0.6 * cos(mid_angle),
      label = seats
    )
  ) +
  coord_fixed()

 

Making pie charts with ggplot2: ggforce manual

ggplot(pie_data) +
  aes(
    x0 = 0, y0 = 0, r0 = 0, r = 1,
    start = start_angle, end = end_angle,
    fill = party
  ) +
  geom_arc_bar() +
  geom_text( # place amounts inside the pie
    aes(
      x = 0.6 * sin(mid_angle),
      y = 0.6 * cos(mid_angle),
      label = seats
    )
  ) +
  geom_text( # place party name outside the pie
    aes(
      x = 1.05 * sin(mid_angle),
      y = 1.05 * cos(mid_angle),
      label = party,
      hjust = hjust, vjust = vjust
    )
  ) +
  coord_fixed()

 

Making pie charts with ggplot2: ggforce manual

ggplot(pie_data) +
  aes(
    x0 = 0, y0 = 0, r0 = 0, r = 1,
    start = start_angle, end = end_angle,
    fill = party
  ) +
  geom_arc_bar() +
  geom_text( # place amounts inside the pie
    aes(
      x = 0.6 * sin(mid_angle),
      y = 0.6 * cos(mid_angle),
      label = seats
    )
  ) +
  geom_text( # place party name outside the pie
    aes(
      x = 1.05 * sin(mid_angle),
      y = 1.05 * cos(mid_angle),
      label = party,
      hjust = hjust, vjust = vjust
    )
  ) +
  coord_fixed(
    xlim = c(-1.8, 1.3)
  )

 

Making pie charts with ggplot2: ggforce manual

ggplot(pie_data) +
  aes(
    x0 = 0, y0 = 0, r0 = 0.4, r = 1,
    start = start_angle, end = end_angle,
    fill = party
  ) +
  geom_arc_bar() +
  geom_text( # place amounts inside the pie
    aes(
      x = 0.7 * sin(mid_angle),
      y = 0.7 * cos(mid_angle),
      label = seats
    )
  ) +
  geom_text( # place party name outside the pie
    aes(
      x = 1.05 * sin(mid_angle),
      y = 1.05 * cos(mid_angle),
      label = party,
      hjust = hjust, vjust = vjust
    )
  ) +
  coord_fixed(
    xlim = c(-1.8, 1.3)
  )

 

Making pie charts with ggplot2: ggforce manual

ggplot(pie_data) +
  aes(
    x0 = 0, y0 = 0, r0 = 0.4, r = 1,
    start = start_angle, end = end_angle,
    fill = party
  ) +
  geom_arc_bar() +
  geom_text( # place amounts inside the pie
    aes(
      x = 0.7 * sin(mid_angle),
      y = 0.7 * cos(mid_angle),
      label = seats
    )
  ) +
  geom_text( # place party name outside the pie
    aes(
      x = 1.05 * sin(mid_angle),
      y = 1.05 * cos(mid_angle),
      label = party,
      hjust = hjust, vjust = vjust
    )
  ) +
  coord_fixed(
    xlim = c(-1.8, 1.3), ylim = c(-1.0, 1.1)
  ) +
  theme_void()

 

Further reading