1

I want to add multiple curves to one ggplot. With the normal plot and curve(add=TRUE) i can loop the curve function and can add multiple curves/functions.

Since i want to rewrite my code for ggplot, i'm trying to figure out how achieve this with ggplot.

I'm using the stat_function and a for loop, but ggplot is not adding the curve, just saving the last one.

Minimum reproducible example with a single line:

library(ggplot2)
p1<-ggplot()
for (i in 1:10){
  p1<- p1 + stat_function(aes(x=1:200),fun = function(x) {x+i*3}, col=i)
  print(p1)
}

What i think the code should do:

I loop multiple stat_function and adding it to my plot p1

What i want to do:

I want alle lines to be added in one plot(p1). The code as it is, just shows the last line of the loop, as if it is overwriting the plot always, but as far as i understand ggplot this should just add a line. Also an explanation why this does not work would be nice, maybe i just don't understand ggplot here

0

2 Answers 2

4

The issue is that all the stat_functions you are refer to the same i variable. And as a consequence, each function you add will overlay perfectly with the others.

The solution is to reassign the variable in a local scope, to make a local copy for each iteration:

p1 = ggplot(data.frame(x = 1 : 200)) + aes(x)

for (i in 1:10){
    p1 = local({
        j = i
        p1 + stat_function(fun = function(x) x + j * 3, col = j)
    })
}

To make matters more confusing, you don’t actually have to give the local variable a new name; you could just as easily have written i = i, and continued to use i. The reason is that this assignment will generate a new local variable i that masks the non-local variable i. I hope we can agree that writing such code is confusing, and a bad idea.

I’ve also taken the liberty of simplifying your code slightly by moving the data x out of the stat_function, and into the ggplot object directly.

However, it is altogether cleaner not to use a loop and reassignment here. Instead you can use lapply or map (from the purrr package):

p1 = ggplot(data.frame(x = 1 : 200)) +
    aes(x) +
    map(
        1 : 10,
        ~ stat_function(fun = function (x) x + .x * 3, color = .x)
    )

This is shorter, more readable (it focuses on the “what” rather than the “how”, i.e. the mechanics of the loop), and it uses a single assignment.

Sign up to request clarification or add additional context in comments.

Comments

2

Try:

library(ggplot2)
add_curve <- function(plot, i) {
  return(plot + stat_function(aes(x=1:200),fun = function(x) {x+i*3}, col=i))
}

p1 <- ggplot()


for (i in 1:10){
  p1<- add_curve(p1, i)
}

p1

Output is:

enter image description here

Or alternatively you can also define your function inside the for-loop:

for (i in 1:10){
  add_curve <- function(plot, i) {
    return(plot + stat_function(aes(x=1:200),fun = function(x) {x+i*3}, col=i))
  }
  p1<- add_curve(p1, i)
}

Or as a (somewhat obscure) Reduce + lapply one-liner (thx to @KonradRudolph):

eval(
  Reduce(function(a, b) call('+', a, b), 
         lapply(1:10, function(i) {
           bquote(stat_function(aes(x=1:200),fun = function(x) {x+.(i)*3
             }, col=.(i)))}), init = quote(ggplot())))

The idea is to build the whole ggplot() call at once and then evaluate it.

2 Comments

Rather than using parse with string pasteing, you can manipulate the unevaluated expressions — the bquote approach goes in the right direction. To combine expressions into a summation, you can use Reduce. The general approach would be: Reduce(function (a, b) call('+', a, b), lapply(1 : 10, ‹FUN›, init = quote(ggplot())), where ‹FUN› is a function that builds a stat_function call expression (similar to what you have, without the prefix +). That said, the result is still unnecessarily convoluted, since ggplot() allows adding a list of plot elements (see my answer), no need for eval
@KonradRudolph: Thx for pointing me to this. I somewhat hoped you would react to my attempt of providing an lapply solution, since I just started learning about how to tinker with expressions.. ;-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.