Written and maintained by CASRAI Editorial Board
Published
On this page
Researchers and graduate students searching for a stacked bar chart in R usually need one thing: a figure that shows how a total splits into categories, with readable segments and a legend that matches the stack. This guide shows the ggplot2 route first, then the base R route, and spends most of its length on the parts the basic recipes skip: why stack order and legend order can disagree, how to place labels inside segments, what happens with negative values, and how to build a horizontal stack. For plain bars, see barplot in R.
What a stacked bar chart is used for in research
A stacked bar chart draws one bar per category on the x (or y) axis and divides each bar into segments, one per level of a second variable. It answers “how large is each total, and what is it made of?”. Typical research uses are counts of survey responses by group, composition of samples by condition, or category counts per year. When the question is a comparison of the parts across bars, or when parts must be read precisely, a grouped (side-by-side) bar chart is usually easier to read, because in a stack only the bottom segment shares a common baseline. See designing effective scientific figures for the wider design reasoning.
Install or access
R is distributed from the Comprehensive R Archive Network (CRAN) at cran.r-project.org. ggplot2 is a CRAN package maintained as part of the tidyverse; its documentation is at ggplot2.tidyverse.org. Install and load it from the R console:
install.packages("ggplot2") library(ggplot2)
The base R route needs no installation: the barplot() function ships in the graphics package that loads with R.
Step-by-step: a stacked bar chart in ggplot2
Step 1. Put the data in long format
ggplot2 expects one row per segment: a column for the bar (x), a column for the segment (fill) and a column for the value.
df <- data.frame( group = rep(c("A", "B", "C"), each = 3), response = rep(c("Low", "Medium", "High"), times = 3), n = c(12, 18, 9, 20, 15, 11, 8, 14, 22) )
Step 2. Draw the stack
Use geom_col() when the data already contain the values. Stacking is the default position for bars, so no position argument is needed, though you can state it explicitly.
ggplot(df, aes(x = group, y = n, fill = response)) + geom_col(position = "stack")
If your data are one row per observation and you want ggplot2 to count them, use geom_bar() with aes(x = group, fill = response) and no y aesthetic.
Step 3. Show proportions instead of counts (percent stacked)
Switch the position to "fill" to make every bar the same height, so segments show shares of each bar. Format the axis as percent with the scales package.
ggplot(df, aes(x = group, y = n, fill = response)) + geom_col(position = "fill") + scale_y_continuous(labels = scales::percent)
Percent stacking removes the information about bar totals, so report the group sizes in the caption or a table.
Step 4. Put labels inside the segments
The ggplot2 documentation for position_stack() notes that the default label position is not well suited to stacked bars, and shows centring the labels with vjust = 0.5:
ggplot(df, aes(x = group, y = n, fill = response)) + geom_col() + geom_text(aes(label = n), position = position_stack(vjust = 0.5))
Here vjust is 1 by default (top of each segment), 0 places the label at the bottom and 0.5 in the middle.
Step 5. Control the order of the segments and the legend
By default ggplot2 stacks in the reverse order of the group aesthetic (for bars, usually fill), so that bar colours line up with the legend. The documentation lists three ways to override this, each affecting something different:
| Goal | Method from the ggplot2 documentation | Changes |
|---|---|---|
| Change both the stacking order and the legend order | Reorder the factor levels of the fill variable | Stack and legend |
| Change only the legend order | Set the legend breaks on the fill scale | Legend only |
| Change only the stacking order | Set the group aesthetic manually |
Stack only |
The most common fix is the first one. Make response an ordered factor before plotting:
df$response <- factor(df$response, levels = c("Low", "Medium", "High"))
For ordinal survey scales this is usually what you want, because the legend and the stack then both follow the scale. To flip the stack without touching the data, pass position_stack(reverse = TRUE) (or position_fill(reverse = TRUE)).
Horizontal stacked bar chart in R
Add coord_flip() to turn the bars sideways. The documentation notes that reverse = TRUE helps when you rotate both the plot and the legend, for example with the legend at the top:
ggplot(df, aes(x = group, y = n, fill = response)) + geom_col(position = position_stack(reverse = TRUE)) + coord_flip() + theme(legend.position = "top")
Long category names read better on a horizontal axis; if you keep a vertical layout instead, see how to rotate x-axis labels in ggplot2.
Grouped versus stacked bars in ggplot2
The only change between a stacked and a grouped bar chart is the position: position = "stack" (default), "fill" for percent stacking, or position = "dodge" for side-by-side bars. Use dodge when readers must compare the segments across bars. The grouped and stacked basics are also covered in barplot in R.
Negative values
Per the position_stack() documentation, positive values stack upward and negative values stack downward, separately, from zero. A stack with mixed signs therefore does not sum to a single bar end, so avoid stacking quantities where the total is meaningful and the signs differ; use a diverging or grouped chart instead.
Step-by-step: a stacked bar chart in base R
In the barplot() function of the graphics package, when height is a matrix and beside = FALSE (the default), each bar corresponds to one column of the matrix and is divided into sub-bars by its rows. Setting beside = TRUE draws them side by side instead.
m <- matrix(c(12, 18, 9, 20, 15, 11, 8, 14, 22), nrow = 3, dimnames = list(c("Low", "Medium", "High"), c("A", "B", "C"))) barplot(m, legend.text = TRUE, args.legend = list(x = "topright"), xlab = "Group", ylab = "Count")
Row names label the legend when legend.text = TRUE, and args.legend is a list of arguments passed to legend(). Set horiz = TRUE for horizontal bars (the first bar is then at the bottom). Without a col argument, a matrix gets a grey palette, so pass a colour vector with one colour per row.
Output and how to read it
Each bar is a total; each coloured segment is one level of the fill variable, and its height (or width, if horizontal) is its value. Read the bottom segment against the axis directly. Other segments start where the one below ends, so judging their size means comparing lengths, not positions. In a percent-stacked chart, every bar reaches 100%, so segments show composition only. Always state in the caption what the segments sum to and, for percent stacks, the number of observations behind each bar.
Troubleshooting
- Legend order is the reverse of the stack order, or the stack is in the wrong order. See Step 5: reorder the factor levels, set the legend breaks, or set
group. - Bars are side by side instead of stacked. Check for
position = "dodge", or, in base R,beside = TRUE. - Every bar has the same height. You are using
position = "fill"; use"stack"to show totals. - Labels sit at the top of each segment. The default
vjustis 1; useposition_stack(vjust = 0.5)ingeom_text(). - Only one bar, or the segments are not split. The fill variable must be mapped inside
aes()and be a factor or character column, not a single numeric value per bar.
Free alternatives
Both routes in this guide are free. The table lists the documented function for each.
| Tool | Stacked-bar route |
|---|---|
| R (base graphics) | barplot() with a matrix and beside = FALSE |
| R (ggplot2) | geom_col() or geom_bar() with position = "stack" |
For the Stata equivalent, see bar charts in Stata.
FAQ
How do I make a stacked bar chart in R with ggplot2?
Map the bar category to x, the value to y and the segment variable to fill, then use geom_col() (or geom_bar() for raw rows). Stacking is the default position, so ggplot(df, aes(group, n, fill = response)) + geom_col() is enough.
How do I make a stacked bar chart in base R?
Pass a matrix to barplot(). With beside = FALSE (the default) each column becomes one stacked bar.
How do I show percentages in a stacked bar chart?
Use position = "fill" and format the y axis with scales::percent.
How do I change the order of the stack?
Reorder the factor levels of the fill variable (changes stack and legend), use position_stack(reverse = TRUE), set legend breaks (legend only) or set group (stack only).
What is the difference between a grouped and a stacked bar chart in ggplot2?
Only the position: "dodge" places segments side by side; "stack" piles them on one bar.
Sources
- ggplot2 reference,
position_stack()andposition_fill(): ggplot2.tidyverse.org/reference/position_stack.html (read 2026-11-07) - R graphics manual,
barplot(): stat.ethz.ch/R-manual/R-devel/library/graphics/html/barplot.html (read 2026-11-07)







