ggplot2 position_stack() in R: Stacked Bars and Areas

The position_stack() function in ggplot2 stacks overlapping objects on top of each other along the y axis so a single bar or area shows the composition of a total. Use it for stacked bars, stacked areas, and centered labels on stacked geoms.

⚡ Quick Answer
geom_bar(position = "stack")                                  # default for geom_bar
geom_bar(position = position_stack())                         # explicit form
geom_col(position = position_stack(reverse = TRUE))           # flip stack order
geom_area(position = position_stack())                        # stacked area chart
geom_text(position = position_stack(vjust = 0.5))             # centered labels
geom_bar(position = position_stack(reverse = TRUE), color="white")  # legend-matched order
geom_col(position = "stack") + scale_y_continuous(labels = scales::comma)  # readable totals

Need explanation? Read on for examples and pitfalls.

📊 Is position_stack() the right tool?
STARTstack parts to show a totalposition_stack()compare proportions across categoriesposition_fill()compare group values side by sideposition_dodge()centered labels inside stack segmentsposition_stack(vjust = 0.5)match stack order to legend top-downposition_stack(reverse = TRUE)reduce overplotting on continuous dataposition_jitter()a stacked line ribbon over timegeom_area() with position_stack()

What position_stack() does in one sentence

position_stack() is a position adjustment that places each group's value on top of the previous group's value, so the y axis shows the cumulative total instead of separate values. You pass it to a geom's position = argument rather than calling it as a standalone layer.

geom_bar() and geom_area() use stacking by default, so position_stack() often appears only when you need to tweak it: reversing the order, centering labels, or applying it to a geom that does not stack by default like geom_col() with discrete groups.

Syntax

The function takes two arguments: vjust (vertical justification for labels) and reverse (flip stack order).

Run live
Run live, no install needed. Every R block on this page runs in your browser. Click Run, edit the code, re-run instantly. No setup.
RLoad ggplot2 and set up data
library(ggplot2) counts <- as.data.frame(table(mpg$class, mpg$drv)) names(counts) <- c("class", "drv", "n") head(counts, 4)

  

The full signature is:

position_stack(vjust = 1, reverse = FALSE)
  • vjust: vertical justification for stacked labels. 1 puts labels at the top of each segment (default), 0 at the bottom, 0.5 at the segment center. Only relevant for geom_text() and geom_label().
  • reverse: if TRUE, the stack order flips so the first factor level sits on top instead of at the bottom. Useful when the legend reads top-down and you want bars to match.
Tip
geom_bar() stacks by default. Writing geom_bar() is equivalent to geom_bar(position = "stack"). Only write the explicit position_stack() when you need reverse = TRUE, vjust, or want to be explicit about intent.

Six common patterns

1. Stacked bar chart with the shortcut

RStacked bars by class and drivetrain
ggplot(mpg, aes(x = class, fill = drv)) + geom_bar()

  

geom_bar() stacks by default. Each class bar shows the count of cars broken into drivetrain segments (4-wheel, front, rear). The y axis reads cumulative counts, not group-specific counts.

2. Reverse the stack order

RFlip stack order to match the legend
ggplot(mpg, aes(x = class, fill = drv)) + geom_bar(position = position_stack(reverse = TRUE))

  

By default the first factor level (4) sits at the bottom and the last (r) sits on top. The legend reads top-down (4, f, r), so the visual order does not match. reverse = TRUE flips the stack so 4 sits at the top, matching the legend.

3. Centered labels on stack segments

RStacked bars with counts centered in each segment
ggplot(counts, aes(x = class, y = n, fill = drv)) + geom_col() + geom_text(aes(label = n), position = position_stack(vjust = 0.5), color = "white", size = 3)

  

vjust = 0.5 centers the label inside each segment. vjust = 1 would push labels to the top edge; vjust = 0 to the bottom. The trick: you need the same position_stack() on the label as on the bar, or labels will float at the original (unstacked) y values.

4. Stacked area chart over time

RStacked area for cumulative shares over time
econ <- data.frame( year = rep(2000:2009, 3), sector = rep(c("A", "B", "C"), each = 10), share = c(40:31, 20:29, 40:31) + rnorm(30, 0, 1) ) ggplot(econ, aes(x = year, y = share, fill = sector)) + geom_area(position = position_stack())

  

geom_area() also stacks by default, but writing position_stack() explicitly is good practice when the chart's interpretation hinges on totals. The y axis reads cumulative share, not sector-specific share.

5. Stacked column with manual data and reverse

Rgeom_col with explicit y values
ggplot(counts, aes(x = class, y = n, fill = drv)) + geom_col(position = position_stack(reverse = TRUE))

  

geom_col() plots y as given (no counting). Combined with reverse = TRUE, you get a stack that visually matches a top-to-bottom legend. This pattern is common when summarizing pre-aggregated tables.

6. Percentage labels with stacked bars

RStacked bars with proportion labels
counts$pct <- ave(counts$n, counts$class, FUN = function(x) round(100 * x / sum(x))) ggplot(counts, aes(x = class, y = n, fill = drv)) + geom_col() + geom_text(aes(label = paste0(pct, "%")), position = position_stack(vjust = 0.5), color = "white", size = 3)

  

You compute proportions outside ggplot, then plot raw counts with proportion labels. The bar shows absolute totals; the label gives the relative share. This avoids the readability issue where small segments crowd their numeric labels.

Key Insight
Stacking is additive, not normalizing. position_stack() lays values on top of each other; it does not rescale. If you want segments scaled to add up to 100, use position_fill() instead. Mixing the two is the most common source of confusion.

position_stack vs alternatives

Pick the position adjustment based on the visual question you are answering.

Position adjustment Effect Use when
position_stack() Adds values vertically Show composition of a total
position_fill() Scales stack to 1 (100%) Compare proportions across categories
position_dodge() Side-by-side groups Compare absolute group values within categories
position_identity() No adjustment Overlay with transparency instead of stacking
position_nudge() Fixed offset Move labels slightly off a point

Decision rule: if a single bar should equal a total and the segments must add up to it, use position_stack. If you want each bar to read 0 to 100% so categories with different totals stay comparable, use position_fill.

Note
Coming from Python plotnine? The API is identical. position_stack() works the same way in plotnine, including the vjust and reverse arguments.

Common pitfalls

Pitfall 1: labels float at the wrong y position. If you add geom_text() to a stacked bar without position = position_stack(vjust = 0.5), labels appear at their raw y values, not at the stack midpoints. Always match the position adjustment between the bar layer and the label layer.

Pitfall 2: forgetting that geom_bar already stacks. Writing geom_bar(position = "stack") is redundant. Reach for position_stack() only when you need reverse, vjust, or want to override a non-default position = setting elsewhere.

Pitfall 3: stack with continuous fill. position_stack() expects a discrete fill aesthetic. If fill is continuous (e.g., a numeric column), ggplot still tries to stack but the legend and segment boundaries are usually wrong. Convert to factor first.

Pitfall 4: negative values stack into the negative axis. If a column has negative values, the negative segments stack downward from zero and positive segments stack upward. The visual can be hard to read. Split into two layers or use geom_col(position = "identity") with transparency instead.

Try it yourself

Try it: Build a stacked bar of mpg counts by class, filled by drv, with the stack order reversed so 4 sits at the top. Save the plot to ex_plot.

RYour turn: reverse-stacked mpg by drv
ex_plot <- ggplot(mpg, aes(x = class, fill = drv)) + # your code here

  
Click to reveal solution
RSolution
ex_plot <- ggplot(mpg, aes(x = class, fill = drv)) + geom_bar(position = position_stack(reverse = TRUE)) ex_plot

  

Explanation: reverse = TRUE flips the stack so the first factor level (4) appears at the top of each bar instead of the bottom. The legend, which reads top-down, now matches the visual order in every bar.

After mastering position_stack, look at:

  • position_fill(): stack scaled to 100 percent for proportion comparison
  • position_dodge(): side-by-side bars instead of stacked
  • position_identity(): overlay without any positional adjustment
  • position_nudge(): shift a layer by a fixed amount
  • geom_area(): stacked or filled area charts for continuous x
  • geom_col(): stacked columns with explicit y values

See the official reference at ggplot2.tidyverse.org for the full signature.

FAQ

What does position_stack do in ggplot2?

position_stack() is a position adjustment that places each group's values on top of the previous group's values along the y axis. The result is a single bar or area whose total equals the sum of all groups. It is the default for geom_bar() and geom_area(), so most users only invoke it explicitly when they need to reverse the order, center labels with vjust, or override a different default.

What is the difference between position_stack and position_fill?

position_stack() keeps the raw values: each bar's height equals the sum of its segments. position_fill() normalizes every bar to 1 (100%) so segments represent proportions. Use stack when absolute totals matter and you want to compare across categories. Use fill when the totals differ and you want to compare composition.

How do I label stacked bars in ggplot2?

Add geom_text(aes(label = value), position = position_stack(vjust = 0.5)). The vjust = 0.5 centers the label inside each segment. Without position_stack(), the text appears at its raw y value rather than the stack midpoint, which usually looks wrong. Match the same data and aesthetic mappings on the text layer as on the bar layer.

How do I reverse the stack order in ggplot2?

Pass reverse = TRUE to position_stack(). By default the first factor level sits at the bottom of the stack. With reverse = TRUE, the first level moves to the top, which makes the bar match a legend that reads top-down. Alternatively, reverse the factor levels in the data before plotting, but the reverse argument is less invasive.

Why is position_stack ordering my groups in an unexpected way?

ggplot2 stacks groups in the order of the factor levels of your fill or color variable. If the fill column is a character vector, R sorts alphabetically. Convert the column to a factor and set the level order explicitly with factor(x, levels = c(...)). Then position_stack() will follow your specified order, optionally flipped with reverse = TRUE.