Five Awesome Tips to Deal with Overlapped Error Bars

In this tutorial, we’ll talk about five powerful solutions to better visualize overlapped error bars.

Create a dummy example

library(ggplot2)library(dplyr)theme_set(theme_bw(base_size = 16) )set.seed(123)  # for reproducible randomization 
# set the x valuesn <- 6 x <- 1:n# set the y values, corresponding to two categoriesy1 <- x ^.1y2 <- x ^.2# set standard deviation SDSD <- runif(2*n, min = .1, max = .2)
# Create a data framemydata <- data.frame( x = c(x, x), y = c(y1, y2), SD = SD, category = rep(c("A", "B"), each = n))
head(mydata, n = 3)

Output:

x y SD category
1 1 1.000000 0.1287578 A
2 2 1.071773 0.1788305 A
3 3 1.116123 0.1408977 A
p <- mydata %>%   ggplot(aes(x, y, color = category)) 
p + geom_point() + geom_line() + geom_errorbar(aes(ymin = y + SD, ymax = y - SD))

Method 1: Draw error bars in half

Use the ifelse() function in the aesthetic mapping to help create halved error bars.

p +   geom_point() +  geom_line() +  geom_errorbar(aes(ymin = ifelse(category == "A", y - SD,  y),                     ymax = ifelse(category == "A", y, y + SD)))

To remove the error cap connected to the lines, one trick is to reduce the cap width, and overlay with points of larger size.

p +   geom_line() +  geom_errorbar(aes(ymin = ifelse(category == "A", y - SD,  y),                     ymax = ifelse(category == "A", y, y + SD)),                width = .2) + # reduce cap width  geom_point(size = 4) 

geom_pointrange() is very similar to geom_errorbar(), but does not draw the cap at the end of the error bar. The size of the point is controlled by the size argument (yet on a different size scale from the regular geom_point()), and the line thickness is controlled separately by linewidth.

p +   geom_line() +  geom_pointrange(aes(ymin = ifelse(category == "A", y - SD,  y),                       ymax = ifelse(category == "A", y, y + SD)),                  size = 1, # for point                  linewidth = 1)  # for error bar

Method 2: Stagger error bars with position in dodge

Applying the dodge position to stagger the error bars (and other geometrics) is a very effective approach to reduce overlap. All three geometric layers should be dodged with the same width to be synchronized in position.

p +   geom_point(position = position_dodge(width = .3)) +  geom_line(position = position_dodge(width = .3)) +  geom_errorbar(aes(ymin = y + SD,                     ymax = y - SD),                position = position_dodge(width = .3))

Method 3: Draw error ribbons in place of error bars

When there are too many error bars, using the error ribbons can be a visually cleaner alternative. The argument color = NA removes the ribbon outline.

p +   geom_point() +   geom_line() +  geom_ribbon(aes(ymin = y + SD,                   ymax = y - SD,                   fill = category),              alpha = .2, color = NA) 

Check these two great illustrations (plot 1, plot 2) to quickly generate error ribbons (standard deviation, standard error of the mean, confidence interval) on the fly using the stat_summary() function.

Method 4: Facet the plot into subplots

When the dataset is large enough, faceting the graphic into subplots can be very useful. Here we demonstrate with the base R dataset ChickWeight, which shows the changing weight of each individual chick on different diets across different time points.

slice_sample(ChickWeight, n = 4) # display 4 random rows

Output:

weight Time Chick Diet
1 146 18 33 3
2 110 12 8 1
3 138 14 31 3
4 65 4 32 3

Visualize the average weight of chicken at different time points on different diets, and create ribbons spanning one standard deviation above and below the mean using stat_summary().

w <- ChickWeight %>%   ggplot(aes(Time, weight, color = Diet, fill = Diet)) +  stat_summary(geom = "line", fun = mean, linewidth = 1) +  stat_summary(geom = "ribbon",                # mean, with standard deviation limits (sdl)               fun.data = mean_sdl,                # mean ± 1 × sdl. By default, 2 × sd is displayed                fun.args = list(mult = 1),               alpha = .4, color = NA)w

w + facet_wrap(~Diet, nrow = 1)

Method 5: Employ interativity by the ggiraph package

ggiraph is a powerful ggplot2 extension package to create interactive visualization. Here we use it to highlight the hovered group of data and dim out the other data at the same time.

An interactive geom_* is typically created by adding the suffix “interactive” after a regular geom name, e.g., geom_ribbon_interactive. However, in the special case of stat_summary(), we need to add the GeomInteractive prefix before the geom name; as it is not an exported subject from the package, it needs to explicitly extracted from the package using :::.

library(ggiraph)
CW <- ChickWeight %>% ggplot(aes( Time, weight, color = Diet, fill = Diet, # When hovered: # 1. show all graphical elements associated with the same "Diet" data_id = Diet, # 2. text in the "Diet" column is displayed in the tooltip tooltip = paste("This is Diet #", Diet)) ) + # trend line stat_summary(geom = ggiraph:::GeomInteractiveLine, fun = mean, linewidth = 1) + # error ribbon stat_summary(geom = ggiraph:::GeomInteractiveRibbon, fun.data = mean_sdl, fun.args = list(mult = 1), alpha = .5, color = NA) CW

CW by this step is the same static plot as w shown above. To render it interactive is as simple as calling girafe(ggobj = CW). For better readability, the following script highlights the hovered ribbon and dims out the others.

girafe(  ggobj = CW,   options = list(    opts_hover(      css = girafe_css(        # For hovered ribbon, use same color as in static plot        # instead of using the default color in orange        css = "opacity:1;",       )    ),    # dim out non-hovered elements into high transparency    opts_hover_inv(      css = "opacity:.2;"    )  ))