Skip to main content
v2026.11,986 entries · CC-BY 4.0

Stacked Bar Chart in R: ggplot2 and Base R

Make a stacked bar chart in R with ggplot2 or base R, fix stack and legend order, centre segment labels and flip it horizontal.

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 vjust is 1; use position_stack(vjust = 0.5) in geom_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

Follow CASRAI

Research-administration guidance, standards updates and independent tool reviews.

Ask CASRAI · free to try

Ask about Stacked Bar Chart in R: ggplot2 and Base R

Ask your first 2 questions free below. Subscribers get 150 a day for $29 a month.

An AI assistant specialized in research administration. It cites the sources behind every answer, labels web answers and says when it can't answer.

Answers draw on CASRAI's guides and dictionary plus the federal and funder documents we index: Federal Register, Grants.gov, Regulations.gov and UKRI.

Works on this site and inside Claude, Cursor and the AI tools you already use.

Everything CASRAI publishes — this page, the dictionary, the guides and the news — stays free to read, with no account and no card.

Referenced across the research world

University of Cambridge logoColumbia University logoCrossref logoUniversity of Edinburgh logoHarvard University logoUniversity of Oxford logoPrinceton University logoStanford School of Medicine logoUniversity College London logoORCID logoUniversity of Cambridge logoColumbia University logoCrossref logoUniversity of Edinburgh logoHarvard University logoUniversity of Oxford logoPrinceton University logoStanford School of Medicine logoUniversity College London logoORCID logo
  • University of Cambridge logo
  • Columbia University logo
  • Crossref logo
  • University of Edinburgh logo
  • Harvard University logo
  • University of Oxford logo
  • Princeton University logo
  • Stanford School of Medicine logo
  • University College London logo
  • ORCID logo

View CASRAI adoption →