ggplot2 geom_step() in R: Step Function Lines

The geom_step() function in ggplot2 draws connected lines that step rather than smoothly slope between points. It is the right choice for piecewise-constant data like cumulative event plots or empirical CDFs.

⚡ Quick Answer
ggplot(df, aes(x, y)) + geom_step()
ggplot(df, aes(x, y)) + geom_step(direction = "vh")  # vertical-then-horizontal
ggplot(df, aes(x, y)) + geom_step(direction = "hv")  # horizontal-then-vertical (default)
geom_line()                                          # smooth/diagonal connections
stat_ecdf()                                          # cumulative distribution

Need explanation? Read on for examples and pitfalls.

📊 Is geom_step() the right tool?
STARTpiecewise-constant time-seriesgeom_step()empirical CDFstat_ecdf() (built on geom_step)smooth line connectiongeom_line()bar chartgeom_col()cumulative countgeom_step on cumsum()

What geom_step() does in one sentence

geom_step() connects points with HORIZONTAL and VERTICAL segments instead of diagonal ones, producing a stair-step appearance. Useful for piecewise-constant data.

Syntax

geom_step(direction = "hv", ...). direction is "hv" (default) or "vh".

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.
RStep function
library(ggplot2) library(dplyr) df <- tibble(x = c(1, 3, 5, 7), y = c(10, 25, 15, 30)) ggplot(df, aes(x, y)) + geom_step()

  
Tip
Use geom_step for cumulative counts, event timelines, or any "value stays constant until next event" data. Smooth-line interpolation would mislead.

Five common patterns

1. Cumulative event count

RRunning count over time
events |> arrange(timestamp) |> mutate(count = row_number()) |> ggplot(aes(timestamp, count)) + geom_step()

  

2. Direction "vh"

RVertical first, then horizontal
ggplot(df, aes(x, y)) + geom_step(direction = "vh")

  

3. Empirical CDF

RECDF via stat_ecdf
ggplot(mtcars, aes(mpg)) + stat_ecdf()

  

4. Compare hv vs vh

RTwo directions on same data
ggplot(df, aes(x, y)) + geom_step(direction = "hv", color = "blue") + geom_step(direction = "vh", color = "red")

  

5. Multiple groups

RPer-group steps
ggplot(df, aes(x, y, color = group)) + geom_step()

  
Key Insight
Direction "hv" (default) draws horizontal first, then vertical at each step. "vh" reverses. Pick based on whether the value applies BEFORE or AFTER the x-coordinate.

geom_step() vs geom_line() vs geom_path()

Function Connection Best for
geom_step() Step (hv or vh) Piecewise-constant
geom_line() Diagonal lines Smooth trends
geom_path() Same as geom_line but follows row order Trajectories

A practical workflow

Use geom_step for "value changes at discrete events" plots.

RInteractive R
prices |> arrange(timestamp) |> ggplot(aes(timestamp, price)) + geom_step() + scale_x_datetime() + labs(title = "Price changes over time")

  

Each price persists until the next change. Steps faithfully show this.

Common pitfalls

Pitfall 1: confusing direction. Default "hv" extends the previous y until the next x; "vh" jumps to new y first then extends. Pick based on data semantics.

Pitfall 2: using on smooth data. Step shows discrete changes. For continuous trends, use geom_line.

Warning
geom_step connects points in DATA ROW ORDER. If your data isn't sorted by x, the steps will be jumbled. Always arrange(x) first.

Try it yourself

Try it: Plot a cumulative count of mtcars rows ordered by mpg using geom_step. Save to ex_plot.

RYour turn: cumulative count
ex_plot <- mtcars |> arrange(mpg) |> mutate(rank = row_number()) |> # your code here

  
Click to reveal solution
RSolution
ex_plot <- mtcars |> arrange(mpg) |> mutate(rank = row_number()) |> ggplot(aes(mpg, rank)) + geom_step()

  

Explanation: Arrange by mpg, compute cumulative rank, plot as steps.

After mastering geom_step, look at:

  • geom_line(): smooth connections
  • geom_path(): row-order line
  • stat_ecdf(): empirical CDF (built on step)
  • geom_segment(): explicit segment drawing
  • scale_x_datetime(): time-axis scaling

FAQ

What does geom_step do in ggplot2?

geom_step() connects points with horizontal and vertical line segments, producing a stair-step appearance. Used for piecewise-constant data.

What is the difference between geom_step and geom_line?

geom_step uses right-angled segments (hv or vh). geom_line uses diagonal segments. Pick based on whether your data is piecewise-constant (step) or continuous (line).

What does direction "hv" mean?

Horizontal-first then vertical: extend previous y until next x, then jump to new y. Default. "vh" reverses.

Can I use geom_step for ECDF?

Yes, indirectly: stat_ecdf() is built on geom_step and is the standard ECDF function.

Does geom_step require sorted data?

Yes. It connects points in row order; unsorted data produces jumbled steps. arrange() first.