0

I would like to create multiple plots using for loop setup. However my code does not work. Could anyone give me some guidance on this?

for i in 1:4 {
paste0("p_carb_",i) <- ggplot(mtcars%>% filter(carb==4), aes(x = wt, y = mpg, color = disp)) 
+  geom_point()
}
user438383
  • 5,716
  • 8
  • 28
  • 43
Stataq
  • 2,237
  • 6
  • 14
  • 2
    "Does not work" isn't very precise, but I suspect you are a victim of [lazy evaluation](https://adv-r.hadley.nz/functions.html#lazy-evaluation). The solution is simple: use one of the `apply` family of functions (which force evaluation) rather than a `for` loop, which does not. More detail [here](https://stackoverflow.com/questions/39799886/r-assigning-ggplot-objects-to-list-in-loop). Also, `paste0("p_carb_",i)` creates a character string, which is not valid as the target of an assignment. – Limey Jan 25 '22 at 15:42
  • Is it a way to make `paste0("p_carb_",i)` a usable target to store the plots? – Stataq Jan 25 '22 at 15:44
  • 2
    Objects are usually stored in lists. So initialize a list prior to a loop, e.g. plotlist <- list(). Then inside the loop set plotlist(i) = ggplot... But agree with Limey, better to use `lapply`. Moreover, the ggplot function on the left hand side contains no `i` terms, so all iterations will return the same result. – SteveM Jan 25 '22 at 15:48

1 Answers1

3

Perhaps this?

library(ggplot2)
library(dplyr)
ggs <- lapply(sort(unique(mtcars$carb)), function(crb) {
  ggplot(filter(mtcars, carb == crb), aes(x = wt, y = mpg, color = disp)) +
    geom_point()
})

This produces six plots, which when the first two are viewed side-by-side (calling ggs[[1]] and then ggs[[2]]), we see

enter image description here

An alternative might be to facet the data, as in

ggplot(mtcars, aes(x = wt, y = mpg, color = disp)) +
  facet_wrap(~ carb) +
  geom_point()

enter image description here

But the literal translation of your paste(..) <- ... code into something syntactically correct, we'd use an anti-pattern in R: assign:

for (crb in sort(unique(mtcars$carb))) {
  gg <- ggplot(filter(mtcars, carb == crb), aes(x = wt, y = mpg, color = disp)) +
    geom_point()
  assign(paste0("carb_", crb), gg)
}

Again, this is not the preferred/best-practices way of doing things. It is generally considered much better to keep like-things in a list for uniform/consistent processing of them.


Multiple IDs ... two ways:

  1. Nested lapply:

    carbs <- sort(unique(mtcars$carb))
    ggs <- lapply(carbs, function(crb) {
      gears <- subset(mtcars, carb == crb)$gear
      lapply(gears, function(gr) {
        ggplot(dplyr::filter(mtcars, carb == crb, gear == gr), aes(x = wt, y = mpg, color = disp)) +
          geom_point()
      })
    })
    

    Where ggs[[1]] is a list of lists. ggs[[1]][[1]] will be one plot.

  2. split list, one-deep:

    carbsgears <- split(mtcars, mtcars[,c("carb", "gear")], drop = TRUE)
    ggs <- lapply(carbsgears, function(dat) {
      ggplot(dat, aes(x = wt, y = mpg, color = disp)) + geom_point()
    })
    

    Here, ggs is a list only one-deep. The names are just concatenated strings of the two fields, so since we have mtcars$carb with values c(1,2,3,4,6,8) and mtcars$gear with values c(3,4,5), removing combinations without data we have names:

    names(ggs)
    #  [1] "1.3" "2.3" "3.3" "4.3" "1.4" "2.4" "4.4" "2.5" "4.5" "6.5" "8.5"
    

    where "1.3" is carb == 1 and gear == 3. When column names have dots in them, this might become ambiguous.

r2evans
  • 141,215
  • 6
  • 77
  • 149
  • 1
    Your answer opens a whole new world to me :D – Stataq Jan 25 '22 at 16:03
  • What if we have more complicate for loop setup, for example we have two part need to be loop `for (i in ID) { for (a in ID2) { ... ` , how can we set it up using `lapply`? I can't not think of a real sample data. Hopefully I explain it right. – Stataq Jan 25 '22 at 16:26
  • 1
    Perhaps stick with faceting (again): `ggplot(mtcars, aes(x = wt, y = mpg, color = disp)) + facet_grid(vs ~ carb) + geom_point()` (where you might instead choose `facet_grid(ID ~ ID2)`). – r2evans Jan 25 '22 at 16:30
  • 2
    You can also nest calls to `lapply`. That would handle OP's use case above. The choice between many separate plots and a single faceted plot is multi-dimensional. There is no absolute answer. The better option will change on a case-by-case basis and will depend on the data, the message you want to convey, the audience ... and personal preference. – Limey Jan 25 '22 at 16:43
  • Thanks @Limey, well said. – r2evans Jan 25 '22 at 16:52
  • @Limey Is it possible to give an example with nest calls to lapply? – Stataq Jan 25 '22 at 16:53
  • Yes. I will do so. But not until tomorrow. – Limey Jan 25 '22 at 16:54
  • @Limey Thanks. Looking forward :D – Stataq Jan 25 '22 at 16:57
  • 1
    Not to steal your thunder @Limey, but ... Stataq, see my edit, particularly with the `split` option. – r2evans Jan 25 '22 at 17:01
  • 1
    @r2evans: I'm always happy when someone volunteers to do my work for me. Especially when it's someone as knowledgeable as yourself. – Limey Jan 25 '22 at 17:58
  • 1
    @Stataq, does that edit make sense? – r2evans Jan 25 '22 at 18:34
  • @r2evans It is wonderful! Learned a lot from your answer. :D – Stataq Jan 25 '22 at 19:26