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.
- 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
# Install the stable release from CRAN:
install.packages("ggerror")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)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))")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")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\")")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)")| 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() |
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 barsvignette("use-cases"):stat_error(), custom summaries, andsign_aware
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 :)








