CompositionComparison CategoricalNumerical

Stacked Bar Chart

ggplot2: geom_bar(position = "stack") · Package: ggplot2 · Variables: 2 categorical + 1 numerical

WHAT IS A STACKED BAR CHART?

A stacked bar chart divides each bar into colored segments representing sub-groups, showing both the total value and its composition at once. It answers questions like "what is the total per category, and how is that total broken down?" For example, total revenue per quarter broken down by product line, or total headcount per department by seniority level. Stacked bars work best with 2-5 segments. For more segments, consider a treemap. In ggplot2, use geom_bar(position = "stack") with a fill aesthetic.

HOW TO READ A STACKED BAR CHART

A stacked bar carries two readings at once, and they are not equally reliable. Total bar length is accurate, and so is the bottom segment, because both share a common baseline. Every segment above it floats — its start position shifts with whatever sits beneath — so comparing the middle band across bars means comparing lengths with different origins, which the eye does badly. Put the series you most need to compare at the bottom and treat the rest as composition rather than measurement. The variant with position = "fill" normalises every bar to 100%, which trades away the totals entirely in exchange for making proportions comparable across categories of very different size. That is the right trade when the question is about mix rather than magnitude, and the wrong one when a reader might mistake a tall share for a large absolute value.

BEST FOR

  • · Part-to-whole within categories
  • · Total and breakdown simultaneously
  • · 2-5 segments per bar

AVOID WHEN

  • · Comparing individual sub-group values across categories
  • · More than 6 segments

R + GGPLOT2 CODE EXAMPLE

ggplot2
ggplot(mtcars, aes(x = factor(cyl), fill = factor(gear))) +
  geom_bar(position = "stack") +
  labs(title = "Gears within Cylinder Groups", fill = "Gears")

ABSOLUTE AND PERCENTAGE VIEWS SIDE BY SIDE

The pairing that avoids the main pitfall — magnitude on the left, mix on the right, so neither reading is available without the other for context.

ggplot2
library(ggplot2)
library(dplyr)
library(patchwork)

df <- mtcars |>
  count(cyl = factor(cyl), gear = factor(gear))

base <- ggplot(df, aes(x = cyl, y = n, fill = gear)) +
  scale_fill_manual(values = c("3" = "#ff6a00", "4" = "#3d9970", "5" = "#7d5fff")) +
  labs(x = "Cylinders", fill = "Gears") +
  theme_minimal(base_size = 12)

absolute <- base +
  geom_col(width = 0.7) +
  labs(title = "Counts", y = "Cars")

share <- base +
  geom_col(position = "fill", width = 0.7) +
  scale_y_continuous(labels = scales::percent) +
  labs(title = "Share within each group", y = NULL)

absolute + share + plot_layout(guides = "collect")

COMMON MISTAKES WITH THIS CHART

Expecting readers to compare middle segments across bars

Only the bottom segment and the total share a baseline. If a specific sub-group needs comparing, use a grouped bar chart or facet by sub-group.

Stacking too many categories

Past five or six segments the bands become slivers and the legend unreadable. Keep the top few and collapse the remainder with forcats::fct_lump().

Using percentage stacking without showing the totals

position = "fill" makes a category of 10 look identical to one of 10,000. Print n on each bar, or pair the chart with the absolute version.

Letting the stack order vary between bars

Set the fill variable as a factor with explicit levels so segments appear in the same order everywhere. Inconsistent ordering makes the chart unreadable.

Run this code now

Paste the code above into RChat and see the stacked bar chart rendered instantly in your browser.

Try RChat Free →

SIMILAR CHART TYPES

ALTERNATIVES FOR YOUR DATA TYPE