

# ggerror <img src="man/figures/logo.png" alt="ggerror hex logo" align="right" height="150"/>

[![CRAN-status](https://www.r-pkg.org/badges/version/ggerror?cache=1.png)](https://CRAN.R-project.org/package=ggerror)
[![R-CMD-check](https://github.com/iamYannC/ggerror/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/iamYannC/ggerror/actions/workflows/R-CMD-check.yaml)
[![Codecov-coverage](https://codecov.io/gh/iamYannC/ggerror/graph/badge.svg)](https://app.codecov.io/gh/iamYannC/ggerror)

`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_neg` and `error_pos`
- Per-side styling through `colour_neg`, `width_pos`, `linetype_neg`,
  and more
- Choice of `errorbar`, `linerange`, `crossbar`, or `pointrange`
- Summary layers from raw data with `stat_error()`
- Signed residual-style bars with `sign_aware = TRUE`

## Installation

``` r
# Install the stable release from CRAN:
install.packages("ggerror") # 0.3.0

# Or install the development version from GitHub:
pak::pak("iamyannc/ggerror") # 1.0.0
```

## 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.

<details class="code-fold">
<summary>defaults and data prep</summary>

``` r
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)
```

</details>

## Simple defaults

The usual case is a symmetric error around some central value.

``` r

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"
  )
```

![](man/figures/README-default-plot-1.png)

If you want a specific error geom (4 available, [see
below](#supported-geoms)), either pick it with `error_geom = ...` or use
the pinned wrappers.

``` r
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))")
```

![](man/figures/README-pointrange-wrapper-1.png)

## 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`.

``` r
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")
```

![](man/figures/README-assym-plot-1.png)

``` r

# 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")
```

![](man/figures/README-one-side-plot-1.png)

## 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.

``` r
ggplot(aq, aes(Month, Temp)) +
  stat_error(fun = "mean_ci", error_geom = "pointrange") +
  labs(title = 'stat_error(fun = "mean_ci", error_geom = "pointrange")')
```

![](man/figures/README-error-summary-plot-1.png)

``` r

# [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:

``` r
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\")")
```

![](man/figures/README-custom-summary-plot-1.png)

## Signed quantities with `sign_aware`

For residual plots and other signed magnitudes, `sign_aware = TRUE`
assigns positive and negative values automatically:

``` r

# 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)")
```

![](man/figures/README-residual-plot-1.png)

## 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")`](https://iamyannc.github.io/ggerror/articles/ggerror.html):
  simple defaults, asymmetric bars, one-sided bars
- [`vignette("use-cases")`](https://iamyannc.github.io/ggerror/articles/use-cases.html):
  `stat_error()`, custom summaries, and `sign_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 :)
