ggplot2::geom_bar()
geom_bar(mapping = NULL, data = NULL, stat = "count", position = "stack", ...) geom_bar() draws bars whose height is proportional to the count of observations in each category (by default) or to a value you supply. It’s the go-to for comparing frequencies across categories, and the default behaviour counts observations without you having to pre-summarise the data.
Syntax
geom_bar(mapping = NULL, data = NULL, stat = "count", position = "stack", ...)
| Argument | What it does |
|---|---|
mapping | Aesthetic mappings from aes() |
data | Data frame for this layer |
stat | "count" (default), "identity", or "fill" |
position | "stack" (default), "dodge", "fill", or "identity" |
width | Bar width as proportion of available space |
na.rm | Remove missing values silently |
Basic Usage
library(ggplot2)
# Count of diamonds by cut
ggplot(diamonds, aes(x = cut)) +
geom_bar()
geom_bar() counts how many diamonds fall into each cut category and draws a bar for each. You don’t need to call group_by() and summarise() first — geom_bar() does it for you with stat = "count".
Bar Height with stat = “identity”
To plot values that are already in the data (not counts), switch to stat = "identity" and map y to that value column:
# Pre-summarised revenue by department
dept_revenue <- data.frame(
dept = c("sales", "engineering", "design"),
revenue = c(420000, 380000, 210000)
)
ggplot(dept_revenue, aes(x = dept, y = revenue)) +
geom_bar(stat = "identity")
Now the bar height directly reflects the revenue column. stat = "identity" treats the y value as given, not counted.
Stacked, Dodged, and Filled Bars
Position controls how multiple groups are arranged within each bar:
# Stacked (default) — layers on top of each other
ggplot(diamonds, aes(x = cut, fill = clarity)) +
geom_bar(position = "stack")
# Dodged — bars side by side
ggplot(diamonds, aes(x = cut, fill = clarity)) +
geom_bar(position = "dodge")
# Filled — all bars normalised to height 1
ggplot(diamonds, aes(x = cut, fill = clarity)) +
geom_bar(position = "fill")
Filled bars are useful when you want to compare proportions across categories regardless of the total count in each group.
Colour and Fill Aesthetics
Control bar appearance with fill for the interior colour and colour for the border:
# Solid colour for all bars
ggplot(diamonds, aes(x = cut)) +
geom_bar(fill = "steelblue", colour = "darkblue")
# Colour mapped to the same variable as x — creates a gradient
ggplot(diamonds, aes(x = cut, fill = cut)) +
geom_bar()
Mapping fill to the same variable as x uses the categorical colour scale. Mapping it to a different variable splits each bar.
Width Control
By default, bars take up 90% of the available width. Adjust with width:
# Thinner bars
ggplot(diamonds, aes(x = cut)) +
geom_bar(width = 0.6)
# Fat bars (almost touching)
ggplot(diamonds, aes(x = cut)) +
geom_bar(width = 0.95)
Width of 1 means bars touch with no spacing between them.
Horizontal Bars
Rotate the plot with coord_flip() for long category names:
ggplot(diamonds, aes(y = cut)) +
geom_bar() +
coord_flip()
When y is mapped to the categorical variable, coord_flip() swaps axes and gives you horizontal bars. Much easier to read long labels this way.
geom_bar vs geom_col
geom_col() is geom_bar(stat = "identity") shorthand — it draws bars where the y value is used directly:
# geom_col: y is the value
ggplot(dept_revenue, aes(x = dept, y = revenue)) +
geom_col()
# Equivalent to geom_bar with stat = "identity"
ggplot(dept_revenue, aes(x = dept, y = revenue)) +
geom_bar(stat = "identity")
Use geom_col() when your data already has the numbers you want to display. Use geom_bar() when you want ggplot2 to count observations for you.
Dodge Position for Side-by-Side Bars
position = "dodge" places groups side by side, making comparison across groups easier:
# Dodge by transmission type
ggplot(mtcars, aes(x = factor(cyl), fill = factor(gear))) +
geom_bar(position = "dodge")
With position = "dodge", each group gets its own coloured bar within each category, spaced horizontally.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mapping | aesthetic | NULL | Aesthetic mappings |
data | data.frame | NULL | Layer data |
stat | string | "count" | "count", "identity", or "fill" |
position | string | "stack" | "stack", "dodge", "fill", "identity" |
width | numeric | 0.9 | Bar width as proportion |
na.rm | logical | FALSE | Skip missing values silently |
show.legend | logical/NA | NA | Show in legend |
inherit.aes | logical | TRUE | Inherit global aesthetics |
See Also
- /reference/tidyverse/ggplot2_geom_point/ — scatter plots for continuous vs continuous data
- /reference/tidyverse/ggplot2_geom_line/ — line charts for sequential data
- /tutorials/r-data-visualization/introduction-to-ggplot2/ — layered grammar concept behind all geoms
- /reference/tidyverse/ggplot2_aes/ — aesthetic mapping system both geoms share