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.
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.
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).
The full signature is:
position_stack(vjust = 1, reverse = FALSE)
vjust: vertical justification for stacked labels.1puts labels at the top of each segment (default),0at the bottom,0.5at the segment center. Only relevant forgeom_text()andgeom_label().reverse: ifTRUE, 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.
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
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
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
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
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
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
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.
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.
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.
Click to reveal solution
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.
Related ggplot2 functions
After mastering position_stack, look at:
position_fill(): stack scaled to 100 percent for proportion comparisonposition_dodge(): side-by-side bars instead of stackedposition_identity(): overlay without any positional adjustmentposition_nudge(): shift a layer by a fixed amountgeom_area(): stacked or filled area charts for continuous xgeom_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.