Alternative graphics to “handle bar” plots

In my area of research, a popular way of displaying data is to use a combination of a bar chart with "handle-bars". For example,

The "handle-bars" alternate between standard errors and standard deviations depending on the author. Typically, the sample sizes for each "bar" are fairly small - around six.

These plots seem to be particularly popular in biological sciences - see the first few papers of BMC Biology, vol 3 for examples.

So how would you present this data?

Why I dislike these plots

Personally I don't like these plots.

1. When the sample size is small, why not just display the individual data points.
2. Is it the sd or the se that is being displayed? No-one agrees which to use.
3. Why use bars at all. The data doesn't (usually) go from 0 but a first pass at the graph suggests it does.
4. The graphs don't give an idea about range or sample size of the data.

R script

This is the R code I used to generate the plot. That way you can (if you want) use the same data.

                                        #Generate the data
set.seed(1)
names = c("A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3")
prevs = c(38, 37, 31, 31, 29, 26, 40, 32, 39)

n=6; se = numeric(length(prevs))
for(i in 1:length(prevs))
se[i] = sd(rnorm(n, prevs, 15))/n

#Basic plot
par(fin=c(6,6), pin=c(6,6), mai=c(0.8,1.0,0.0,0.125), cex.axis=0.8)
barplot(prevs,space=c(0,0,0,3,0,0, 3,0,0), names.arg=NULL, horiz=FALSE,
axes=FALSE, ylab="Percent", col=c(2,3,4), width=5, ylim=range(0,50))

xx = c(2.5, 7.5, 12.5, 32.5, 37.5, 42.5,  62.5, 67.5, 72.5)
for (i in 1:length(prevs)) {
lines(rep(xx[i], 2), c(prevs[i], prevs[i]+se[i]))
lines(c(xx[i]+1/2, xx[i]-1/2), rep(prevs[i]+se[i], 2))
}

axis(2, tick=TRUE, xaxp=c(0, 50, 5))
axis(1, at=xx+0.1, labels=names, font=1,
tck=0, tcl=0, las=1, padj=0, col=0, cex=0.1)

• Helping your field come to a consensus on just the se v. sd question would be a huge advance. They mean completely different things. – John Aug 3 '10 at 14:41
• I agree - se is usually chosen because it gives a smaller region! – csgillespie Aug 3 '10 at 15:20
• Maybe some more informative title? – user88 Aug 3 '10 at 18:36
• Just for reference, I have seen these bar charts with error bars called "Dynamite Plots" before. Here are a few references giving the exact same recommendations as everyone else pretty much has (dot charts). Tatsuki Koyama, Beware of Dynamite Poster and Drummond & Vowler, 2011. – Andy W Jan 20 '12 at 16:11
• Please add the image again if you can. Use the image uploader this time so it doesn't become a dead link. – endolith Dec 2 '13 at 22:54

Thanks for all you answers. For completeness I thought I should include what I usually do. I tend to do a combination of the suggestions given: dots, boxplots (when n is large), and se (or sd) ranges.

(Removed by moderator because the site hosting the image no longer appears to work correctly.)

From the dot plot, it is clear that data is far more spread out the "handle bar" plots suggest. In fact, there is a negative value in A3!

• That's a good answer. In addition, I'd suggest horizontally jittering the points, so they don't overlap, especially if you have more points per group than this. In ggplot2, the geom_jitter() will do that. – Harlan Oct 20 '10 at 15:00
• @Harlan: I agree. Although if I had many more points I would probably use a boxplot. – csgillespie Oct 20 '10 at 19:00
• I also like scatterplots for small data sets (nb, I use the term 'dotplot' to refer to a slightly different plot). However, for what it's worth, the barplot above is cleaner & easier to read than this one. I'm not sure that makes it better, but it's worth pointing out. – gung - Reinstate Monica Jun 6 '12 at 1:40
• @Harlan: Alternatively, make the dots transparent so that multiple dots stack up and produce a darker dot? – endolith Dec 2 '13 at 23:01
• do you have the original image to replace this dead link? – endolith Nov 10 '14 at 21:31

Frank Harrell's (most excellent) keynote entitled "Information Allergy" at useR! last month showed alternatives to these: rather than hiding the raw data via the aggregation the bars provide, the raw data is also shown as dots (or points). "Why hide the data?" was Frank's comment.

Given alpa blending, that strikes as a most sensible suggestion (and the whole talk most full of good, and important, nuggets).

• Is it available as a video? It sounds great. – Henrik Aug 3 '10 at 14:12
• I think the word is "will be eventually" -- keynotes got recorded. – Dirk Eddelbuettel Aug 3 '10 at 14:18
• this is easy in ggplot I think, i.e. had.co.nz/ggplot2/geom_jitter.html – Mike Dewar Aug 3 '10 at 14:58
• jitter is also in plain R. – user88 Aug 3 '10 at 15:14
• Just for the protocol, Frank's talk (in video) is now online: r-bloggers.com/RUG/2010/08/user-2010-conference-videos – Tal Galili Aug 17 '10 at 2:43

From a psychological perspective, I advocate plotting the data plus your uncertainty about the data. Thus, in a plot like you show, I would never bother with extending the bars all the way to zero, which only serves to minimize the eye's ability to distinguish differences in the range of the data.

Additionally, I'm frankly anti-bargraph; bar graphs map two variables to the same aesthetic attribute (x-axis location), which can cause confusion. A better approach is to avoid redundant aesthetic mapping by mapping one variable to the x-axis and another variable to another aesthetic attribute (eg. point shape or color or both).

Finally, in your plot above, you only include error bars above the value, which hinders one's ability to compare the intervals of uncertainty relative to bars above and below the value.

Here's how I would plot the data (via the ggplot2 package). Note that I add lines connecting points in the same series; some argue that this is only appropriate when the series across which the lines are connected are numeric (as seems to be in this case), however as long as there is any reasonable ordinal relationship among the levels of the x-axis variable, I think connecting lines are useful for helping the eye associate points across the x-axis. This can become particularly useful for detecting interactions, which really stand out with lines.

library(ggplot2)
a = data.frame(names,prevs,se)
a$$let = substr(a$$names,1,1)
a$$num = substr(a$$names,2,2)
ggplot(data = a)+
layer(
geom = 'point'
, mapping = aes(
x = num
, y = prevs
, colour = let
, shape = let
)
)+
layer(
geom = 'line'
, mapping = aes(
x = num
, y = prevs
, colour = let
, linetype = let
, group = let
)
)+
layer(
geom = 'errorbar'
, mapping = aes(
x = num
, ymin = prevs-se
, ymax = prevs+se
, colour = let
)
, alpha = .5
, width = .5
)


• I should add that my "plot only the data and uncertainty" recommendation should be qualified: when presenting data to an audience that has experience/expertise with the variable being plotted, plot only the data and uncertainty. When presenting data to a naieve audience and when zero is a meaningful data point, I'd first show the data extending to zero so that the audience can get oriented to the scale, then zoom in to show just the data and uncertainty. – Mike Lawrence Aug 3 '10 at 15:10
• since you've went to trouble of writing R code, could you include a jpeg image of the final plot. I find just uploading the image to img84.imageshack.us and linking to it is fairly easy. Oh thanks for the answer :) – csgillespie Aug 3 '10 at 15:26
• @csgillespie: done. – Mike Lawrence Aug 3 '10 at 15:46
• I've found that it's easier to read a plot like this with geom_ribbon() indicating the error. If you don't like producing apparent estimates for regions between 1 and 2, at least reduce the width of the error bar. – JoFrhwld Aug 3 '10 at 19:20
• @JoFrwld: I like ribbons too, though I tend to reserve them for cases where the x-axis variable it truly numeric; my version of the "don't draw lines unless the x-axis variable is numeric" rule that I profess violating in my answer above :Op – Mike Lawrence Aug 4 '10 at 1:40

I'm curious at to why you don't like these plots. I use them all the time. Without wanting to state the blooming obvious, they allow you to compare the means of different groups and see if their 95% CIs overlap (i.e., true mean likely to be different).

It's important to get a balance of simplicity and information for different purposes, I guess. But when I use these plots I am saying- "these two groups are different from each other in some important way" [or not].

Seems pretty great to me, but I'd be interested to hear counter-examples. I suppose implicit in the use of the plot is that the data do not have a bizzare distribution which renders the mean invalid or misleading.

• I've added a small section on why I dislike these plots. – csgillespie Aug 3 '10 at 15:34
• @Chris check this out about interpreting overlapping CIs pubs.amstat.org/doi/abs/10.1198/000313001317097960 Also the original question is also around the confusion of using SE or SD interchangeably while they are two different things – tosonb1 Aug 4 '10 at 1:03
• Or, for an analysis on this site, see stats.stackexchange.com/questions/18215. @tosonb1 Your link is timing out. Could you supply a reference to the paper? – whuber Aug 21 '19 at 19:43

If the data are rates: that is number of successes divided by number of trials, then a very elegant method is a funnel plot. For example, see http://qshc.bmj.com/content/11/4/390.2.full (apologies if the link requires a subscription--let me know and I'll find another).

It may be possible to adapt it to other types of data, but I haven't seen any examples.

UPDATE:

Here's a link to an example which doesn't require a subscription (and has a good explanation for how they might be used): http://understandinguncertainty.org/fertility

They can be used for non-rate data, by simply plotting mean against standard error, however they may lose some of their simplicity.

The wikipedia article is not great, as it only discusses their use in meta-analyses. I'd argue they could be useful in many other contexts.

• The data isn't necessary rates. It could be anything. – csgillespie Aug 3 '10 at 14:07
• Subscription link, unfortunately. – Matt Parker Aug 3 '10 at 14:57
• ... but here's the Wikipedia link on funnel plots: en.wikipedia.org/wiki/Funnel_plot – Matt Parker Aug 3 '10 at 14:59

I would use boxplots here; clean, meaningful, nonparametric... Or vioplot if the distribution is more interesting.

• I'm not sure boxplots or vioplots would be suitable with such a small sample size (n = 6) – csgillespie Aug 4 '10 at 12:39
• Right, I admit I haven't read the question carefully enough, so it was rather a general idea; nevertheless I think that 6 points is minimal but enough for a boxplot. I have made some experiments and they were meaningful. On the other hand, obviously boxplot does not indicate the number of observations (which is an important bit of information here), so I would rather use a combination of it and points. – user88 Aug 4 '10 at 13:55
• With 6 points - scatter plot is probably best (maybe with adding a red dot for the mean) – Tal Galili Aug 4 '10 at 17:20
• I generally use boxplots with superimposed points, I find it very "visual". Violin plots, instead, are a bit hard to understand in my opinion. – nico Aug 4 '10 at 17:30
• @csgillespie: What would indicate that bar and whisker plots are better? They are showing basically the same information as a boxplot (as you point out, the whiskers can represent various things), they just give the error only in one direction, which could be fairly confusing, if not disingenuous... Not arguing for boxplots. But beanplots/violinplots should still work, even for relatively low sample sizes, because it's just a gaussian density estimation, as I explained here. – naught101 Nov 22 '12 at 0:30

Simplifying @csgillespie's terrific code from above:

qplot(
data=a,
x=num,
y=prevs,
colour=let,
shape=let,
group=let,
ymin=prevs-se,
ymax=prevs+se,
position=position_dodge(width=0.25),
geom=c("point", "line", "errorbar")
)


I prefer geom_pointrange to errorbar and think the lines are distracting rather than helpful. Here is version that I find much cleaner than the @James or @csgillespie version:

qplot(
data=a,
x=num,
y=prevs,
colour=let,
ymin=prevs-se,
ymax=prevs+se,
position=position_dodge(width=0.25),
geom=c("pointrange"), size=I(2)
)