How to calculate SD by group in R, without losing columns still needed for plotting in ggplot2?

Viewed 63

I have a dataset of 'scenario's (27x) where A, B en C have been certain input values into a model, and value is the outcome of a variable.

Now I want to make a grouped barplot with ggplot (value on y, with factor B on x, fill by A. I want to make errorbars based on the variation caused by factor C.

My dataset is (simplified) approximatly in this format:

data <- data.frame(matrix(ncol=0, nrow=27))
data$value <- runif(27, min=10, max=60)
data$A <- factor((rep(1:9, each=3)))
data$B <- factor((rep(1:3, each=9)))
data$C <- factor(rep(rep(1:3),9))

Looks like:

     value A B C
1 27.76710 1 1 1
2 34.71762 1 1 2
3 20.72895 1 1 3
4 34.83710 2 1 1
5 31.44144 2 1 2
6 13.11038 2 1 3
etc

The ggplot would be

ggplot(data, aes(fill=A, y=value, x=B)) + 
  geom_bar(stat="identity",position=position_dodge())+
  geom_errorbar(aes(ymin=?????, ymax=????), width=.2,
                position=position_dodge(.9))

So I am struggling with ymin and ymax. It could be value+sd or -sd, but I don't have a sd calculated yet.

My approach now is using summarize from dplyr by group A. This gives me:

data %>% 
group_by(A) %>% 
summarise(mean=mean(value), sd = sd(value))

  A      mean    sd
  <fct> <dbl> <dbl>
1 1      27.7  6.99
2 2      26.5 11.7 
3 3      33.7 21.9 
4 4      27.7  6.99
etc

This is fine, however, now I lost all my other columns (in this case I still need B for my ggplot). How can I still calculate a mean and sd and keep all my other columns?

Or are there other ways to get the effect I need? (I could re-add the column B by hand but I'd like to know if there are other ways also for the future and for occasions B is not easily re-made)

1 Answers

You have three rows of data for each combination of A and B, so your current code is actually overplotting three bars at each x-axis position. You can see this by adding transparency to the bars.

ggplot(data, aes(fill=A, y=value, x=B)) + 
  geom_bar(stat="identity", position=position_dodge(), alpha=0.3)

enter image description here

It looks like you're actually trying to do the following (but let me know if I've misunderstood):

pd = position_dodge(0.92)

data %>% 
  group_by(A,B) %>% 
  summarise(mean=mean(value), sd=sd(value)) %>% 
  ggplot(aes(fill=A, x=B)) + 
  geom_col(aes(y=mean), position=pd)+
  geom_errorbar(aes(ymin=mean-sd, ymax=mean+sd), position=pd, width=0.2)

enter image description here

Facetting is another option:

data %>% 
  group_by(A,B) %>% 
  summarise(mean=mean(value), sd=sd(value)) %>% 
  ggplot(aes(x=A)) + 
    geom_col(aes(y=mean), fill=hcl(240,100,65)) +
    geom_errorbar(aes(ymin=mean-sd, ymax=mean+sd), width=0.2) +
    facet_grid(. ~ B, labeller=label_both, space="free_x", scales="free_x")

enter image description here

But do you really need bars?

data %>% 
  group_by(A,B) %>% 
  summarise(mean=mean(value), sd=sd(value)) %>% 
  ggplot(aes(x=A)) + 
  geom_pointrange(aes(y=mean, ymin=mean-sd, ymax=mean+sd), shape=21, fill="red", 
                  fatten=6, stroke=0.3) +
  facet_grid(. ~ B, labeller=label_both, space="free_x", scales="free_x")

We can also do this calculation within ggplot, using stat_summary:

data %>% 
  ggplot(aes(x=A, y=value)) + 
  stat_summary(fun.data=mean_sdl, fun.args=list(mult=1), geom="pointrange", 
               shape=21, fill="red", fatten=6, stroke=0.3) +
  facet_grid(. ~ B, labeller=label_both, space="free_x", scales="free_x")

Either way, the plot looks like this:

enter image description here

Related