2

I am creating a function that automatically plots a number of curves based on user input. However, when I loop through the ggplot, all the plots look the same when they should be different. My data looks something like this:

PseudoA PseudoB
0.546 0.646
0.234 -0.757
... ...

Let's imagine the user wants to plot 5 curves, I tried using the following loop:

    p<-list()
    for(i in 1:5){
      eq<-function(x){c + ((1-c)*(1/(1+2.71828^(-1.7*(df$PseudoA[i]*(x-df$PseudoB[i]))))))}

      p[[i]]<-ggplot()+geom_function(fun=eq)+
                     scale_x_continuous(limits=c(-4,4),breaks=waiver(), labels=c("0.9","0.7","0.5","0.3","0.1"))

    }
    library(gridExtra)
    grid.arrange(grobs=c(p[1:5]), ncol=2, rnow=3)

But it plots the same curve five times, as you can see here, even though when I plot them separately outside of the loop they're all very different: enter image description here

Keep in mind that I don't know how many plots the user is going to want, whether 5, 10 or 20, hence the loop. Any help would be appreciated!

Diego
  • 31
  • 2
  • 1
    [This is your issue](https://stackoverflow.com/a/69037565/903061), ggplot's lazy evaluation, but I don't think the `!!` solution will work in your case since you are not using `aes()`. It's also possible wrapping the ggplot object in `force()` would work. Could you make your example reproducible so that we can test solutions? I imported the 2 rows of data you shared and changed your code to `for(i in 1:2)` since there are only two rows, but I'm still getting warnings about `c` being non-numeric and empty plots. What is `c`? And maybe a couple more rows of data would help? – Gregor Thomas May 18 '23 at 19:31

1 Answers1

2

As Gregor Thomas points out, this is caused by delayed evaluation. The function eq in each loop is stored with the unevaluated symbol i inside it. When you call grid.arrange, the plots each calculate the function with the value of i that is in the global environment. Since the value of i is 5 at the end of your loop, all five plots will represent the last row of your data.

There are a few ways round this, but the cleanest is to use a mapping function so that you don't get iterator variables and temporary functions polluting your workspace:

library(ggplot2)
library(gridExtra)

p <- Map(function(A, B) {
  ggplot() +
    geom_function(fun = function(x) {
      c + ((1 - c) * (1 / (1 + 2.71828^(-1.7*(A * (x-B))))))
      }) +
    scale_x_continuous(limits=c(-4,4), labels=c("0.9","0.7","0.5","0.3","0.1"))
  }, df$PseudoA, df$PseudoB)


grid.arrange(grobs=c(p[1:5]), ncol = 2, rnow = 3)

enter image description here


Data used

The example was not fully reproducible, so I used the following sample variables:

set.seed(1)

c <- 0.5

df <- data.frame(PseudoA = runif(5, -1, 1),
                 PseudoB = runif(5, -1, 1))

df
#>      PseudoA    PseudoB
#> 1 -0.4689827  0.7967794
#> 2 -0.2557522  0.8893505
#> 3  0.1457067  0.3215956
#> 4  0.8164156  0.2582281
#> 5 -0.5966361 -0.8764275

Note that it's probably not a good idea to call a variable c, since this clashes with the commonly used function c()

Created on 2023-05-18 with reprex v2.0.2

Allan Cameron
  • 147,086
  • 7
  • 49
  • 87