ggerror extends ggplot2’s error geoms with one error-focused API. Pass error for symmetric errors, or error_neg and error_pos for asymmetric and one-sided cases. ggerror infers orientation and lets you style each side independently. It computes summaries from raw data with stat_error(), and draws signed quantities such as residuals with sign_aware.
TL;DR
- One aesthetic for the common symmetric case:
aes(error = ...) - Asymmetric and one-sided bars with
error_neganderror_pos - Per-side styling through
colour_neg,width_pos,linetype_neg, and more - Choice of
errorbar,linerange,crossbar, orpointrange - Summary layers from raw data with
stat_error() - Signed residual-style bars with
sign_aware = TRUE
Installation
# Install the stable release from CRAN:
install.packages("ggerror")Setup
The examples below use airquality, the same dataset used throughout the package vignettes. For convenience I defined a theme, and did minimal data processing on the original dataset.
defaults and data prep
library(ggplot2)
library(ggerror)
theme_set(
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(family = "mono", size = 11, hjust = 0),
plot.caption = element_text(face = "italic"),
legend.position = "none"
)
)
aq <- airquality
aq$Month <- factor(aq$Month, labels = month.abb[5:9])
# A function to transform numbered day (1-31) to name, given the year & month (1973, May - Sep)
day_in_month <- function(day_in_month, month, year) {
days_abbr <- format(
as.Date(sprintf("%d-%02d-%02d", year, month, day_in_month)),
"%a"
)
factor(days_abbr,
levels = c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"),
ordered = TRUE)
}
aq$Day <- day_in_month(aq$Day, airquality$Month, 1973)
# Computing some centrality/spread measures with base R!
aq_monthly_avg <- aggregate(
Temp ~ Month,
data = aq,
FUN = function(x) c(
mean = mean(x),
sd = sd(x),
se = sd(x) / sqrt(length(x))
)
)
aq_monthly_avg <- do.call(data.frame, aq_monthly_avg)Simple defaults
The usual case is a symmetric error around some central value.
ggplot(aq_monthly_avg, aes(Temp.mean, Month)) +
geom_error(aes(error = Temp.sd), width = 0.4) +
labs(x=NULL, y=NULL,
title = "geom_error(aes(error = Temp.sd))",
caption = "Default base geom: errorbar"
)
If you want a specific error geom (4 available, see below), either pick it with error_geom = ... or use the pinned wrappers.
ggplot(aq_monthly_avg, aes(Temp.mean, Month)) +
geom_error_pointrange(aes(error = Temp.sd), size = 0.7) +
labs(title = "geom_error_pointrange(aes(error = Temp.sd))")
Asymmetric and one-sided errors
error_neg and error_pos extend in opposite directions. That makes it easy to show genuinely different quantities on each side, or to suppress one side entirely with NA.
ggplot(aq_monthly_avg, aes(Temp.mean, Month)) +
geom_error(
aes(error_neg = Temp.se, error_pos = Temp.sd),
error_geom = "crossbar",
fill_neg = "#d7e3f4",
fill_pos = "#f6d3c4",
width_neg = 0.25,
width_pos = 0.55
) +
labs(title = "error_neg = Temp.se ; error_pos = Temp.sd")
# Grabbing only the first week in the data and computing for each Temp, its distance from the minimum Temp and maximum Temp.
week_may <- subset(aq, Month == "May")[1:7, ]
week_may$dist2min <- week_may$Temp - min(week_may$Temp, na.rm = TRUE)
week_may$dist2max <- max(week_may$Temp, na.rm = TRUE) - week_may$Temp
ggplot(week_may, aes(Temp, Day)) +
geom_error(
aes(error_neg = dist2min, error_pos = NA),
colour = "steelblue",
linewidth = 1
) +
geom_point(size = 1.5) +
labs(title = "One-sided: error_pos = NA")
Summaries from raw data with stat_error()
When you do not want to pre-compute the bounds yourself, stat_error() computes them directly and supports default and custom functions.
ggplot(aq, aes(Month, Temp)) +
stat_error(fun = "mean_ci", error_geom = "pointrange") +
labs(title = 'stat_error(fun = "mean_ci", error_geom = "pointrange")')
# [1] `stat_error()` using fun = "mean_ci" and conf.int = 0.95.Custom summary functions work too, as long as they return data frames of one row with y, ymin, and ymax columns:
mae_summary <- function(x, scale_by = 1) {
md <- median(x)
mae <- mean(abs(x - md)) * scale_by
data.frame(y = md, ymin = md - mae, ymax = md + mae)
}
ggplot(aq, aes(Month, Temp)) +
stat_error(fun = mae_summary, error_geom = "crossbar", fill = "grey90") +
labs(title = "stat_error(fun = mae_summary, error_geom = \"crossbar\")")
Signed quantities with sign_aware
For residual plots and other signed magnitudes, sign_aware = TRUE assigns positive and negative values automatically:
# Residual plot made easy
fit_temp <- lm(Temp ~ Wind, data = aq)
aq_fit <- data.frame(
Wind = aq$Wind,
Temp = aq$Temp,
fitted = predict(fit_temp),
residual = resid(fit_temp)
)
ggplot(aq_fit, aes(Wind, fitted)) +
geom_line(linewidth = 0.4, colour = "grey40") +
geom_point(aes(y = Temp), alpha = 0.45) +
geom_error(
aes(error = residual),
sign_aware = TRUE,
orientation = "x",
colour_pos = "firebrick",
colour_neg = "steelblue",
linewidth = 0.5,
alpha = 0.85
) +
labs(title = "geom_error(aes(error = residual), sign_aware = TRUE)")
Supported geoms
| ggplot2 base | geom_error(error_geom = ...) |
Pinned wrapper |
|---|---|---|
geom_errorbar |
"errorbar" (default) |
geom_error() |
geom_linerange |
"linerange" |
geom_error_linerange() |
geom_pointrange |
"pointrange" |
geom_error_pointrange() |
geom_crossbar |
"crossbar" |
geom_error_crossbar() |
Learn more
I tried to be as comprehensive as possible, without taking too much of your attention. For the full walkthroughs, see the package vignettes:
-
vignette("ggerror"): simple defaults, asymmetric bars, one-sided bars -
vignette("use-cases"):stat_error(), custom summaries, andsign_aware
Disclaimer
This package was developed with the assistance of AI tools. All code has been reviewed by the author, who remains responsible for its quality. Ideas for new geoms are welcome and features are very welcome.
Thank you for reading, forget everything and give me your best cookies recipe 🍪
Yann :)

