Is there a more elegant way to colour ggplot bars with mean of data?

Viewed 90

There must be a less clunky way to summarise mean values in a bar chart. I expected fill to return a mean for all of the counts. See the MWE including the work around.

# Some data
Machine <- c( "A", "B", "B", "B", "B", "C","D", "E", "F", "G", "H", "I", "J", "K", "G","H", "G")
Efficiency <- c(93.3, 95.0, 95.0, 99.1, 84.1, 95.8, 91.2, 82.9, 73.1, 93.7, 86.6, 68.1, 78.5, 68.5, 86.6, 90.0, 97.3)
gt <- data.frame(Machine, Efficiency)

ggplot(gt, aes(x=Machine, fill = Efficiency)) + geom_bar()
# This is not what I expected so...

library(dplyr)
gt2 <- gt %>% group_by(Machine) %>% mutate(mean(Efficiency))
names(gt2) <- c("Machine", "Efficiency", "Mean_Efficiency")

ggplot(gt2, aes(x=Machine, fill = Mean_Efficiency)) +
    geom_bar(stat = "count") 
# This is what I want (and mistakenly expected)

Is there a more elegant way to colour the bars according to the mean efficiency?

3 Answers

The reason that you first attempt does not give the expected result is that ggplot2 cannot know that it is the mean of Efficiency that you want to use for filling. It could just as well be the sum or the median and ggplot2 is right in not trying to guess something it cannot know.

So, the best solution is to precompute the mean. Your plot relies on two summaries, however: one is the mean that you precompute and the other is the counts which you let ggplot2 (or, more specifically, stat_count() which is used by geom_bar()) do for you. If find this mix of methods for the summaries a bit confusing and would prefer to precompute both summaries as follows:

gt2 <- gt %>%
        group_by(Machine) %>%
        summarize(count = n(),
                  mean_efficiency = mean(Efficiency)) 

You can then use geom_col() instead of geom_bar() since the former takes an explicit y-aesthetic in stead of plotting the counts:

ggplot(gt2, aes(x = Machine, y = count, fill = mean_efficiency)) +
  geom_col()

enter image description here

Note that this solution is not more correct than yours. To me it is clearer than your solution, but of course this is also a matter of taste. It's up to you which you prefer.

No pre-computation is needed if you call ave from within aes.

ggplot(gt, aes(x=Machine, fill = ave(Efficiency,Machine))) + geom_bar()

That is, directly set the fill aesthetic to be the mean of Efficiency over Machine. For me this is a much more natural and readable approach, but as others have said it is a matter of taste.

If you want to set the legend title, use (eg) +labs(fill="Mean efficiency")

enter image description here

Your approach with computing the means before plotting was absolutely right. fillwon't do that for you. However your code could be more concise without always setting names. (;

library(ggplot2)
library(dplyr)

# Some data
gt <- data.frame(
  Machine = c( "A", "B", "B", "B", "B", "C","D", "E", "F", "G", "H", "I", "J", "K", "G","H", "G"),
  Efficiency = c(93.3, 95.0, 95.0, 99.1, 84.1, 95.8, 91.2, 82.9, 73.1, 93.7, 86.6, 68.1, 78.5, 68.5, 86.6, 90.0, 97.3)
)
# aggregate(Efficiency~Machine, data=gt, FUN = mean)
gt <- gt %>% 
  group_by(Machine) %>% 
  mutate(Mean_Efficiency = mean(Efficiency)) %>% 
  ungroup() 

ggplot(gt, aes(x = Machine, fill = Mean_Efficiency)) + 
  geom_bar()
Related