rguides

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", ...)
ArgumentWhat it does
mappingAesthetic mappings from aes()
dataData frame for this layer
stat"count" (default), "identity", or "fill"
position"stack" (default), "dodge", "fill", or "identity"
widthBar width as proportion of available space
na.rmRemove 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

ParameterTypeDefaultDescription
mappingaestheticNULLAesthetic mappings
datadata.frameNULLLayer data
statstring"count""count", "identity", or "fill"
positionstring"stack""stack", "dodge", "fill", "identity"
widthnumeric0.9Bar width as proportion
na.rmlogicalFALSESkip missing values silently
show.legendlogical/NANAShow in legend
inherit.aeslogicalTRUEInherit global aesthetics

See Also