ggplot2 coord_flip() in R: Horizontal Bar and Box Plots
ggplot2 coord_flip() in R swaps the x and y axes after the geom is drawn, so a vertical bar chart becomes a horizontal one without rewriting the aesthetic mapping. It is the simplest way to rescue long category labels that overlap on the x axis.
ggplot(df, aes(x, y)) + geom_col() + coord_flip() # vertical bars to horizontal ggplot(df, aes(x, y)) + geom_boxplot() + coord_flip() # vertical box to horizontal ggplot(df, aes(x, y)) + geom_point() + coord_flip() # rotate any geom coord_flip(xlim = c(0, 50)) # zoom the new x axis coord_flip(ylim = c(0, 100)) # zoom the new y axis coord_flip(clip = "off") # let labels spill outside coord_flip(expand = FALSE) # remove default padding geom_bar(orientation = "y") # modern alternative, no flip
Need explanation? Read on for examples and pitfalls.
What coord_flip() does in one sentence
coord_flip() is a coordinate transformation that swaps the x and y axes after every layer is drawn. Because the swap happens at the coordinate stage rather than the aesthetic stage, you keep writing aes(x = category, y = value) and the function rotates the canvas at render time. The benefit shows up immediately on bar charts with long category names: rotating the plot moves the labels onto the vertical axis where they have room to breathe.
Syntax
coord_flip() takes four arguments and zero are required.
xlimandylim: act on the axes after flipping, soxlimcontrols what used to be the y axis.expand: set toFALSEfor bar charts that should start exactly at zero.clip: switch to"off"when annotations or long bars need to render beyond the panel.
geom_bar(orientation = "y"), geom_boxplot(orientation = "y"), and geom_density(orientation = "y") produce horizontal versions without flipping the coordinates, which keeps your stats and tooltips on the correct axis. Use coord_flip() for legacy code and for geoms that lack orientation; reach for orientation = "y" on new code.Five common patterns
Pattern 1: horizontal bar chart with long labels. The classic use case. Long category names overlap on the x axis but fit comfortably on the y axis.
Pattern 2: horizontal box plot for ordered categories. Box plots benefit from flipping when category names are long or when comparing many groups vertically.
Pattern 3: horizontal lollipop chart. A lollipop combines geom_segment() and geom_point(); flipping turns it horizontal in one line.
Pattern 4: ranked bars with forcats reorder. Reordering the factor before flipping puts the longest bar at the top, the canonical layout for ranked horizontal bars.
Pattern 5: flipping vs the modern orientation argument. Both code paths produce the same chart. The orientation route keeps the stat on the correct axis, which matters for stat_summary() and tooltips.
coord_flip() reverses your visual ordering. Wrap the category in fct_reorder() or fct_rev() so the bar you expect at the top actually lands at the top.coord_flip() vs orientation vs swapping aes
Three routes reach the same horizontal chart, but they differ on stats and labels.
| Feature | coord_flip() | orientation = "y" | swap x and y in aes() |
|---|---|---|---|
| Works on any geom | yes | only listed geoms | yes |
| Keeps stats on the original axis | no | yes | yes |
| Available in ggplot2 < 3.3.0 | yes | no | yes |
| Tooltips show the correct mapping (plotly) | no | yes | yes |
| Requires no aesthetic rewrite | yes | no | no |
stat_summary() and geom_smooth() sometimes behave strangely after a flip. When you set orientation = "y", the geom itself runs its stat horizontally and the axes stay where you put them.Common pitfalls
Pitfall 1: factor levels appear reversed after flipping. ggplot2 draws factor levels from bottom to top on the y axis. After coord_flip(), the first level lands at the bottom of the chart, which feels backwards for ranked bars. Fix with fct_rev() or by sorting the factor with fct_reorder().
Pitfall 2: geom_smooth() lines disappear or look wrong. geom_smooth() fits its model along the x axis, so flipping after the fit can leave the smoother in the wrong orientation. Either use geom_smooth(orientation = "y") or fit the model on the swapped mapping directly with aes(x = y_var, y = x_var).
Pitfall 3: scale limits applied to the wrong axis. After flipping, the original scale_y_continuous(limits = ...) still targets the value axis, but visually it now sits on the horizontal. New readers often pass limits to scale_x_continuous() and see no effect. Pass limits inside coord_flip(xlim = ..., ylim = ...) or remember that scale functions act on the pre-flip axes.
Try it yourself
Try it: Build a horizontal bar chart of the median highway mileage by class from the mpg dataset, with classes ordered from highest to lowest median. Save the plot to ex_hbar.
Click to reveal solution
Explanation: summarise() produces one row per class with the median. fct_reorder() ranks the factor by that median so the largest value sits at the top after the flip. Without the reorder, classes would appear in alphabetical order from bottom to top.
Related ggplot2 functions
coord_polar(): bends an axis into a circle for pies and rose plots.coord_fixed(): enforces a fixed aspect ratio for maps and scatter plots.coord_cartesian(): zooms without dropping data, no flip applied.geom_bar(): counts rows by category; supportsorientation = "y"since 3.3.0.scale_y_reverse(): reverses the value axis without rotating the plot.
FAQ
When should I use coord_flip() versus geom_bar(orientation = "y")?
Use coord_flip() when the plot mixes layers that may not all support orientation, or when the project pins ggplot2 below 3.3.0. Use orientation = "y" on new code with geom_bar(), geom_boxplot(), geom_density(), or geom_smooth() because the stat runs on the correct axis and tooltips stay aligned. For a one-off chart with geom_col() only, both produce identical output, so pick the route that matches the rest of your codebase.
Why does coord_flip() reverse the order of my bars?
ggplot2 prints factor levels from bottom to top on the y axis. After flipping, the first level lands at the bottom of the chart, the opposite of what most readers expect. Wrap the category variable in forcats::fct_rev() or use fct_reorder() with the value to fix the order. Sorting the underlying data frame alone is not enough because the factor levels carry the order, not the row order.
Does coord_flip() work with facets?
Yes. coord_flip() composes cleanly with facet_wrap() and facet_grid(). Every panel rotates the same way, and free scales (scales = "free_x" or "free_y") act on the post-flip axes, which is occasionally surprising. If you want only some facets to flip, build them as separate plots and stitch with patchwork::wrap_plots().
Can I set axis limits after coord_flip()?
Yes, but inside coord_flip() itself: coord_flip(xlim = c(0, 100)). Passing limits to scale_x_continuous() after coord_flip() still acts on the original x axis (the post-flip vertical), which feels inverted. The cleanest pattern is to put display ranges inside the coord call and reserve scale_*_continuous() for transformations and breaks.
External reference: ggplot2 coord_flip() documentation.