ggplot2 position_dodge() in R: Side-by-Side Bars and Points
The position_dodge() function in ggplot2 shifts overlapping objects horizontally so they sit side by side. Use it for grouped bar charts, dodged points, or aligned error bars.
geom_bar(position = "dodge") # quick grouped bars geom_bar(position = position_dodge(width = 0.9)) # explicit width geom_point(position = position_dodge(width = 0.5)) # dodge points geom_errorbar(position = position_dodge(width = 0.9), width=.2) # dodge error bars geom_bar(position = position_dodge(preserve = "single")) # keep bar widths geom_col(position = position_dodge2(width = 0.9)) # mixed widths geom_point(position = position_dodge(0.6)) # first arg = width
Need explanation? Read on for examples and pitfalls.
What position_dodge() does in one sentence
position_dodge() shifts overlapping objects side by side along the x axis, so groups within a category become visible instead of overlapping. It is a position adjustment, not a geom; you pass it to a geom's position = argument.
The default position for most discrete geoms is "stack" (bars stack vertically) or "identity" (points draw on top of each other). position_dodge is what turns those into the familiar grouped layout.
Syntax
The function takes two arguments: width (horizontal shift) and preserve (how to handle uneven group sizes).
The full signature is:
position_dodge(width = NULL, preserve = c("total", "single"))
position_dodge2(width = NULL, preserve = c("total", "single"),
padding = 0.1, reverse = FALSE)
width: the horizontal distance between dodged objects. For bars use the bar width (default0.9). For points pick a value like0.3to0.7.preserve:"total"(default) keeps the combined width of all dodged objects constant;"single"keeps each object's width constant. Use"single"when groups have different counts.padding(position_dodge2only): gap between objects, fraction of width.reverse: flip the dodge order.
width across layers. If you dodge bars with width = 0.9 and add error bars, pass the same value to geom_errorbar(position = position_dodge(width = 0.9)) or they will not align.Six common patterns
1. Grouped bar chart with the shortcut
Passing position = "dodge" is shorthand for position_dodge() with default arguments. The bars within each class separate by drv (4-wheel, front, rear).
2. Explicit width control
The result looks identical to example 1, but you now control the spacing. Reducing width to 0.7 packs bars closer; raising it to 1.0 pushes them apart.
3. Dodge points within categories
Points by drv separate horizontally inside each class. Without dodge, points from all three drivetrains stack on the same x position and hide each other.
4. Align bars and error bars
Both layers share width = 0.9, so the error bars sit centered on their bars. Mismatched widths are the most common cause of "floating" error bars.
5. Keep bar widths fixed across uneven groups
Compact has only 2 drv groups; midsize and suv have 3. With preserve = "total" (default), compact bars would balloon to fill the slot. preserve = "single" keeps every bar the same width.
6. Mixed-width bars with position_dodge2
position_dodge2() handles varying bar widths cleanly. Plain position_dodge assumes equal widths and produces overlaps when widths differ.
position_dodge vs alternatives
Pick the position adjustment that matches the visual question you are asking.
| Position adjustment | Effect | Use when |
|---|---|---|
position_dodge() |
Side-by-side groups | Compare group values within a category |
position_stack() |
Stack on top of each other | Show composition of a total |
position_fill() |
Stack scaled to 100% | Compare proportions across categories |
position_jitter() |
Random noise | Reduce overplotting of identical x values |
position_jitterdodge() |
Dodge then jitter | Grouped boxplots with raw points overlaid |
position_dodge2() |
Smarter dodge | Varying widths or preserve = "single" cases |
Decision rule: if your group variable maps to fill or color and you want a visible split, reach for position_dodge. If you want a total bar broken into parts, reach for position_stack.
barplot()? Side-by-side bars come from beside = TRUE. In ggplot2 the equivalent is geom_bar(position = "dodge").Common pitfalls
Pitfall 1: width mismatch. Bars use width = 0.9 by default, but error bars or points often default to other widths. Always set position_dodge(width = X) to the same value across all layers in a grouped plot.
Pitfall 2: forgetting preserve = "single". When groups have different counts (e.g., some categories lack one level), the default preserve = "total" makes those bars wider. Use "single" for visually consistent widths.
Pitfall 3: dodging a single-group plot. If your data has only one level in fill, dodge has nothing to separate and your bars sit centered as if no dodge was applied. Verify your group variable actually varies.
Try it yourself
Try it: Build a grouped bar of mpg counts by class, filled by drv, with explicit width = 0.9. Save to ex_plot.
Click to reveal solution
Explanation: Setting width = 0.9 matches the bar default, so the dodged groups stay within their class slot and remain visually aligned with any error bars you might overlay later.
Related ggplot2 functions
After mastering position_dodge, look at:
position_stack(): stack bars or areas on top of each otherposition_fill(): stack to 100% proportionposition_jitter(): add random noise to reduce overplottingposition_jitterdodge(): combine dodge and jitter for grouped pointsposition_dodge2(): dodge with varying widths and smarter preserve
See the official reference at ggplot2.tidyverse.org for the full signature.
FAQ
What does position_dodge do in ggplot2?
position_dodge() shifts overlapping geom objects horizontally so groups within a category appear side by side. It does not change values; it only adjusts x positions. The default width matches a bar's width (0.9), and you typically apply it through geom_bar(position = "dodge") or geom_point(position = position_dodge(width = 0.5)) to compare groups.
What is the difference between position_dodge and position_dodge2?
position_dodge assumes equal object widths within a group and can overlap or distort when widths vary. position_dodge2 handles variable widths and applies preserve = "single" more intuitively, keeping each object's width constant across uneven group counts. Use position_dodge2 whenever bars carry a width aesthetic or groups have different numbers of levels.
How do I set the width in position_dodge?
Pass width as the first argument: position_dodge(width = 0.9). The value is in x-axis units; 0.9 matches the default geom_bar width. Smaller values pack bars closer; larger values push them apart. To align error bars or points with grouped bars, set the same width on every layer.
How do I dodge error bars over bars?
Use the same position_dodge(width = 0.9) on both geom_col() (or geom_bar) and geom_errorbar(). If the widths differ, error bars float off-center. Set geom_errorbar(width = 0.2) separately to control the horizontal cap size; that argument is distinct from the dodge width.
What is preserve = "single" in position_dodge?
preserve controls how dodge handles groups of unequal size. The default "total" keeps the combined slot width constant, so missing categories make the remaining bars wider. preserve = "single" keeps each bar the same width regardless of group count, which is usually what you want when comparing categories that lack some levels.