can't manage to use facet in ggplot2 inside a for loop

961 views Asked by At

I have a dataframe with 2 factors, called dat.avg

> head(dat.avg)
  metal     site    season   Average      STDV
1    Al     Acre spring 19 4034.7345  222.4645
2    Al     Acre    summer 5860.1790 2940.4691
3    Al Ashqelon    autumn 2148.6943  403.5688
4    Al Ashqelon spring 19 3336.1576  871.4381
5    Al Ashqelon spring 20 2170.9057  589.2840
6    Al Ashqelon    summer  918.3047  413.6208

> str(dat.avg)
'data.frame':   351 obs. of  5 variables:
 $ metal  : chr  "Al" "Al" "Al" "Al" ...
 $ site   : Factor w/ 7 levels "Acre","Ashqelon",..: 1 1 2 2 2 2 2 3 3 3 ...
 $ season : Factor w/ 5 levels "autumn","spring 19",..: 2 4 1 2 3 4 5 1 3 5 ...
 $ Average: num  4035 5860 2149 3336 2171 ...
 $ STDV   : num  222 2940 404 871 589 ...

I want to produce a loop that will make a bar plot for each metal using ggplot, where X axis is grouped by sites, and in each site all the seasons are nested (same for all metals so no need to change). Y axis is the Average, scale is different for each metal so its 'scale = free' . I managed to do it for every metal separately, but for some reason, no matter what I tried, face isn't working. This is my code, if someone can point put what am I doing wrong it will be amazing.

> metal.vec
 [1] "Al" "As" "Cd" "Co" "Cr" "Cu" "Fe" "Mg" "Mn" "Ni" "Pb" "Se" "V"  "Zn"

for (j in 1:length(metal.vec)) { 
  temp.dat.avg  <- dat.avg %>%
    filter(site == dat.avg$site,
           metal == metal.vec[j],
           Average == dat.avg$Average,
           season == dat.avg$season)
  metal.bar  <- ggplot(temp.dat.avg, aes(x = site, y = Average, fill = season)) +
  geom_bar(stat='identity',color="black", position=position_dodge())+
  theme_minimal() + 
  #facet_wrap(~ metal.vec[j])+
  #facet_grid(vars(metal.vec[j]), scales = "free", space = "free") + 
  geom_errorbar(aes(ymin=Average-STDV, ymax=Average+STDV), width=.2,
                  position=position_dodge(.9)) +
  ggtitle(metal.vec[j], "Average and SD Concentration")+
  ylab("Average Metal Concentration in dry weight (µg/g)")
  print(metal.bar + scale_fill_brewer(palette="Spectral"))
}

I've put both the faceting code with "#" just so you can see #facet_wrap(~ metal.vec[j])+ #facet_grid(vars(metal.vec[j]), scales = "free", space = "free") neither one have worked for me

My idea is to produce this graph:this kind of bar plot

with facet on metal that will look something like that : this kind of faceting

I'll appreciate any help! thank a lot!

2

There are 2 answers

0
Emanuel V On

The comments are generally correct. Ultimately, you should only need facet_wrap(~metal).

You should look at dplyr's group_by and summarise prior to plotting. Use group_by to group by site, season, and metal, then create a new column, say GroupAverage, using summarise, eg:

dat.avg %>%
    group_by(site,season,metal) %>%
    summarise(GroupAverage=mean(Average))

Then you can get rid of the exterior loop and have a single call to ggplot with the facet_wrap layer.

Again, generally, if you start using explicit loops with R, you're probably doing something inefficiently.

Good luck.

0
zhiwei li On

If you use facet() to plot multiple plots then you do not need to use a loop.

I am not sure if I understand well what you want.

Considering you do not post a reproducible code and data, I make a fake data.

library(tidyverse)
set.seed(20201011)

#FAKE DATA
metal <- c("Al", "As", "Cd", "Co")
site <- paste('City', LETTERS[1:5], sep = '_')
season <- c('spring', 'summer', 'autumn', 'winter')

frames <- expand.grid(metal = metal,
                      site = site,
                      season = season)
dat.avg <- frames %>% cbind(Average = runif(nrow(frames), min = 1, max = 10),
                            STDV = runif(nrow(frames), min = 1, max = 5)) 

The dat.avg like this:

> head(dat.avg,5)
  metal   site season  Average     STDV
1    Al City_A spring 9.430763 1.781402
2    As City_A spring 3.924159 2.714745
3    Cd City_A spring 1.598158 2.950350
4    Co City_A spring 7.171125 1.415245
5    Al City_B spring 2.257886 2.482321

First, we creat the basic plot that have nothing but facet by metal.

p1 <- ggplot(dat.avg, aes(x = season, y = Average, fill= site))+ facet_grid(metal ~.)
p1

enter image description here

Second, add bar plot base on p1

p2 <- p1 + geom_bar(position = position_dodge(),  stat="identity", color = 'black') 
p2

enter image description here

Third, add error bar base on p2.

p3 <- p2 + geom_errorbar(aes(ymin=Average-STDV, ymax=Average+STDV), position = position_dodge(0.9), width = .3) 
p3

enter image description here

Fourth, if you want the theme like this kind of faceting, try this:

p4 <- p3 + 
     labs(
    title = 'Variation in Metals Conentration along Israeli Mediterranean Coastline, by Season and Site',
    subtitle  = 'North to South',
    caption = 'Field Smapling 2019-2020, \n *Outliers omited for better visualization'
     ) +
     theme(
    plot.title = element_text(color = 'Darkblue'),
    plot.subtitle = element_text(color = 'blue'),
    plot.caption = element_text(color = 'green', face = 'bold')
      ) + 
     guides(fill = F)
p4

enter image description here

Finally, if you want the color schemes like: this kind of bar plot, try this:

p5 <- p4 + scale_fill_manual(values = c('#D7191C', '#FDAE61', '#FFFFBF', '#ABDDA4', '#2B83BA'))
p5

enter image description here

Hope these codes could help you.