ggplot2 scale_x_discrete() in R: Customize Discrete X Axis

The scale_x_discrete() function in ggplot2 customizes the X axis when x is a factor or character vector. It sets the order, labels, and gaps between categories.

⚡ Quick Answer
+ scale_x_discrete(limits = c("Q1","Q2","Q3","Q4"))
+ scale_x_discrete(labels = c(a = "Alpha", b = "Beta"))
+ scale_x_discrete(breaks = c("a","c"))   # show only these
+ scale_x_continuous()                      # different: numeric x
forcats::fct_relevel(...)                  # alternative: reorder factor

Need explanation? Read on for examples and pitfalls.

📊 Is scale_x_discrete() the right tool?
STARTfactor / character x customizationscale_x_discrete()reorder by levelsscale_x_discrete(limits = c(...))relabel categoriesscale_x_discrete(labels = c(...))numeric xscale_x_continuous()prefer fct_reorder for value-based orderingforcats package

What scale_x_discrete() does in one sentence

scale_x_discrete() controls the X axis on plots where x is a factor or character: order, labels, breaks, and expansion.

Syntax

scale_x_discrete(name = waiver(), breaks = waiver(), labels = waiver(), limits = NULL, expand = waiver(), ...).

Run live
Run live, no install needed. Every R block on this page runs in your browser. Click Run, edit the code, re-run instantly. No setup.
RReorder bars
library(ggplot2) library(dplyr) library(forcats) mtcars |> count(cyl) |> ggplot(aes(factor(cyl), n)) + geom_col() + scale_x_discrete(limits = c("8","6","4"))

  
Tip
Use limits to reorder categories; use labels to rename them. Both accept named or positional vectors.

Five common patterns

1. Reorder

RQuarters in calendar order
+ scale_x_discrete(limits = c("Q1","Q2","Q3","Q4"))

  

2. Rename labels

RFriendlier display
+ scale_x_discrete(labels = c(a = "Alpha", b = "Beta"))

  

3. Drop categories

RShow only some
+ scale_x_discrete(limits = c("a","c")) #> b dropped from plot

  

4. Combine with forcats

RReorder factor by frequency
df |> mutate(cat = forcats::fct_infreq(cat)) |> ggplot(aes(cat, value)) + geom_col()

  

5. Wide labels with rotation

RLong labels at angle
+ scale_x_discrete() + theme(axis.text.x = element_text(angle = 45, hjust = 1))

  
Key Insight
For VALUE-based reordering (e.g., sort bars by height), use forcats::fct_reorder on the data; for explicit reordering, use limits in scale_x_discrete. Both work; forcats is cleaner for data-driven order.

scale_x_discrete() vs forcats::fct_reorder vs scale_x_continuous

Approach Best for
scale_x_discrete(limits = ...) Manual ordering
forcats::fct_reorder(x, by) Data-driven ordering
scale_x_continuous() Numeric x
coord_flip() Long labels (rotate plot)

A practical workflow

For bar charts ordered by value, the cleanest pattern uses fct_reorder outside the plot.

RInteractive R
mtcars |> count(cyl) |> mutate(cyl = forcats::fct_reorder(factor(cyl), n)) |> ggplot(aes(cyl, n)) + geom_col()

  

Common pitfalls

Pitfall 1: limits drops categories. If your data has c("a","b","c") and limits is c("a","c"), b's data is dropped from the plot.

Pitfall 2: labels argument shape. Pass a named vector for safe mapping: labels = c(a = "Alpha", b = "Beta"). Positional works but is fragile.

Warning
scale_x_discrete order is determined by limits, FACTOR LEVELS, or alphabetical (last fallback). Always verify your factor's levels are what you expect.

Try it yourself

Try it: Reorder mtcars cyl bars to be 8, 6, 4 (descending). Save to ex_plot.

RYour turn: reorder bars
ex_plot <- mtcars |> count(cyl) |> ggplot(aes(factor(cyl), n)) + geom_col() + # your code here

  
Click to reveal solution
RSolution
ex_plot <- mtcars |> count(cyl) |> ggplot(aes(factor(cyl), n)) + geom_col() + scale_x_discrete(limits = c("8","6","4"))

  

Explanation: limits sets the order explicitly.

After mastering scale_x_discrete, look at:

  • scale_y_discrete(): same for y
  • scale_x_continuous(): numeric x
  • forcats::fct_reorder() / fct_infreq(): data-driven reorder
  • coord_flip(): rotate to horizontal
  • theme(axis.text.x = ...): axis text styling

FAQ

What does scale_x_discrete do in ggplot2?

scale_x_discrete() customizes the X axis when x is a factor or character: order, labels, breaks.

How do I reorder bars in ggplot2?

Either scale_x_discrete(limits = c(...)) for manual order, or forcats::fct_reorder(x, value) for data-driven order before the plot.

How do I rename axis categories?

scale_x_discrete(labels = c(old = "New")). Use a named vector to avoid positional ambiguity.

Can I drop a category from the plot?

Yes. Omit it from limits. Note: this drops the data, not just the visual.

What is the difference between scale_x_discrete and scale_x_continuous?

discrete is for factors/characters (categorical). continuous is for numeric x.