[R] Understanding position_dodge() and position_dodge2() in ggplot2

Wait 5 sec.

[This article was first published on R on Zhenguo Zhang's Blog, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.Zhenguo Zhang’s Blog https://fortune9.netlify.app/2026/08/30/r-understanding-position-dodge-and-position-dodge2-in-ggplot2/ –In ggplot2, when displaying grouped data along categorical axes (such as grouped bar charts, boxplots, or error bars), horizontal dodging is used to prevent elements from overlapping at each categorical position. ggplot2 provides two primary dodging position functions: position_dodge() and position_dodge2().In this post, we will explore:How the width parameter in position_dodge() interacts with the parent geom’s width.The core differences between position_dodge2() and position_dodge(), including why changing width in position_dodge2() often has no visual effect.How preserve = "single" behaves when elements of a group variable are missing at a given x-axis level.Definition of TermsTo keep our explanations clear and consistent throughout this post, we define two key concepts:x-axis level: The discrete value or category on the x-axis. In our examples using mtcars, the x-axis level is cyl (with levels 4, 6, and 8).group variable: The variable that divides items into subgroups at each x-axis level, typically mapped to an aesthetic like fill or color. In our examples, the group variable is gear (with levels 3, 4, and 5).dodged elements: The individual visual marks (e.g. bars) corresponding to each level of the group variable at a specific x-axis level.library(ggplot2)library(patchwork)1. The width Parameter in position_dodge()What does width control?In position_dodge(width = ...), width specifies the total dodging span (interval) on the x-axis into which all dodged elements for a given x-axis level are arranged side-by-side.By default, if you do not explicitly supply width to position_dodge(), it inherits its value from the parent geom (e.g., geom_bar() defaults to width = 0.9).Interaction between geom_bar(width) and position_dodge(width)geom_bar(width = ...): Sets the total physical width allocated to the bars at each x-axis level. The individual bar width is derived by dividing this value by the number of elements at that x-axis level.position_dodge(width = ...): Sets the total span on the x-axis across which the center positions of the dodged elements are calculated and spaced.Both parameters use x-axis coordinate units (where the distance between adjacent discrete x-axis levels is 1.0).Let’s examine how varying both parameters affects the layout using the mtcars dataset (plotting mean mpg across x-axis level cyl, with group variable gear mapped to fill):# Base plot configurationbase_p bars overlapp3