Error bars for barplot only in one direction

It's actually pretty easy to implement this for use in the general case (where you can't hide the lower error bar under another plot element) now that they've made it easy to extend ggplot2 with custom geoms.

Copy the code from geom_errorbar from the github repository for ggplot2 into a new .R file. Then make a few modifications, as follows:

geom_uperrorbar <- function(mapping = NULL, data = NULL,
   stat = "identity", position = "identity",
   ...,
   na.rm = FALSE,
   show.legend = NA,
   inherit.aes = TRUE) {
   layer(
      data = data,
      mapping = mapping,
      stat = stat,
      geom = GeomUperrorbar,
      position = position,
      show.legend = show.legend,
      inherit.aes = inherit.aes,
      params = list(
         na.rm = na.rm,
         ...
      )
   )
}

The only two changes necessary above were to change geom_errorbar to geom_uperrorbar and geom = GeomErrorbar to geom = GeomUperrorbar.

GeomUperrorbar <- ggproto("GeomUperrorbar", Geom,
   default_aes = aes(colour = "black", size = 0.5, linetype = 1, width = 0.5,
      alpha = NA),

   draw_key = draw_key_path,

Note that this comment is interrupting partway through a function which continues in the code blocks below. Above we just changed GeomErrorbar to GeomUperrorbar twice.

   required_aes = c("x", "y", "ymax"),

   setup_data = function(data, params) {
      data$width <- data$width %||%
         params$width %||% (resolution(data$x, FALSE) * 0.9)

      transform(data,
         xmin = x - width / 2, xmax = x + width / 2, width = NULL
      )
   },

Interrupting the function again. Above we changed the required aesthetics to be x, y and ymax, i.e. replacing ymin with y. We need y to start the vertical line there (instead of at ymin) and we no longer need ymin because there's not going to be a horizontal line there.

   draw_panel = function(data, panel_scales, coord, width = NULL) {
      GeomPath$draw_panel(data.frame(
         x = as.vector(rbind(data$xmin, data$xmax, NA, data$x,   data$x)),
         y = as.vector(rbind(data$ymax, data$ymax, NA, data$ymax, data$y)),
         colour = rep(data$colour, each = 5),
         alpha = rep(data$alpha, each = 5),
         size = rep(data$size, each = 5),
         linetype = rep(data$linetype, each = 5),
         group = rep(1:(nrow(data)), each = 5),
         stringsAsFactors = FALSE,
         row.names = 1:(nrow(data) * 5)
      ), panel_scales, coord)
   }
)

Here we removed the last three elements of the vectors passed to x and y, which were for the lower error bar. In addition, we changed the last element from ymin to y, because we want the line to start at y, not ymin.

"%||%" <- function(a, b) {
   if (!is.null(a)) a else b
}

This last bit is just a convenience function used in the code that needs to be defined.

If you source the document including all of this code, then you can use geom_uperrorbar just like geom_errorbar, or even pass geom = "uperrorbar" to stat_summary, using y instead of ymin.


An easy work-around would be to plot the error bars first:

p +
  geom_errorbar(limits, position = dodge, width=0.25) +
  geom_bar(position = dodge, stat = "identity")

enter image description here


Henrik's advice is excellent in this case, but I would suggest that you take a look at where the upper and lower limits are set.

 limits <- aes(ymax = resp + se, ymin = resp - se)

In that line, you explicitly tell ggplot to put the lower extension by setting ymin as resp - se; if you just set it as resp, then you'd have only the upper extension.

 limits <- aes(ymax = resp + se, ymin = resp)

... although you'd also have a black line at the top of the bar. To make this look cleaner, you could add a black outline to the entire bar.

p <- ggplot(df, aes(fill = group, y = resp, x = trt))+
  geom_bar(position = dodge, stat = "identity") +
  geom_bar(position = dodge, stat = "identity", 
           color="black", show_guide=FALSE)+
  geom_errorbar(limits, position = dodge, width = 0.25)
p

bar plot

Notice how I duplicated the bar plot layer, but with a "black" color, which adds that outline. The legend was turned off in the colored layer because I personally prefer to avoid the diagonal lines in the legend.