4

I simply want to draw multiple arrows on a scatterplot using ggplot2. In this (dummy) example, an arrow is drawn but it moves as i is incremented and only one arrow is drawn. Why does that happen?

library(ggplot2)
a <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
b <- data.frame(x1=c(2,3),y1=c(10,10),x2=c(3,4),y2=c(15,15))
for (i in 1:nrow(b)) {
        a <- a + geom_segment(arrow=arrow(), 
          mapping = aes(x=b[i,1],y=b[i,2],xend=b[i,3],yend=b[i,4]))
         plot(a)
 }

Thanks.

0

2 Answers 2

3

This isn't strange behavior, this is exactly how aes() is supposed to work. It delays evaluation of the parameters until the plotting actually runs. This is problematic if you include expressions to variable outside your data.frame (like i) and functions (like [,]). These are only evaulated when you actually "draw" the plot.

If you want to force evaulation of your parameters, you can use aes_. This will work for you

for (i in 1:nrow(b)) {
  a <- a + geom_segment(arrow=arrow(), 
    mapping = aes_(x=b[i,1],y=b[i,2],xend=b[i,3],yend=b[i,4]))
 }
plot(a)

Now within the loop the parameters for x= and y=, etc are evaluated in the environment and their value are "frozen" in the layer.

Of course, it would be better not to build layers in loops and just procide a proper data object as pointed out in @eipi10's answer.

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

1 Comment

I think I've heard of the function aes_ before but I couldn't understand it then. Now I get a primer and will check it more. Thanks.
2

As @Roland explains in the comment thread to this answer, only one arrow is plotted, because geom_segment(arrow=arrow(), mapping = aes(x=b[i,1],y=b[i,2],xend=b[i,3],yend=b[i,4])) is evaluated only when a is plotted. But i only has one value each time a is plotted. During the first time through the loop, i=1 and during the second time i=2. After the loop i also still equals 2. Thus, only one arrow is plotted each time. If, after the loop, you run i=1:2 then you'll get both arrows. On the other hand, if you change i to anything other than 1 and/or 2, you won't get any arrows plotted.

In any case, you can get both arrows without a loop as follows:

ggplot(mtcars, aes(wt, mpg)) + 
  geom_point() +
  geom_segment(data=b, arrow=arrow(), aes(x=x1,y=y1,xend=x2,yend=y2))

Question regarding @Roland's first comment: Shouldn't the object a be updated each time through the loop by adding the new geom_segment? For example, if I start with the OP's original a, then after one iteration of the loop,

a = a + geom_segment(arrow=arrow(), aes(x=b[1,1],y=b[1,2],xend=b[1,3],yend=b[1,4])) 

Then, after two iterations of the loop,

a = a + geom_segment(arrow=arrow(), aes(x=b[1,1],y=b[1,2],xend=b[1,3],yend=b[1,4])) + 
        geom_segment(arrow=arrow(), aes(x=b[2,1],y=b[2,2],xend=b[2,3],yend=b[2,4])) 

where in each case a means the value of a before the start of the loop. Shouldn't those underlying changes to the object a occur regardless of the when or if a is evaluated?

7 Comments

Because evaluation happens during plotting and after the loop i == 2.
@Roland, I have a question regarding your comment, but I've added it to my answer so the code will be easier to read.
Look at the internals of the ggplot2 object. x = b[i, 1] and it is only evaluated during plotting.
Does it work that way because the b data frame was never passed into the ggplot environment using the data argument, but is instead drawn from the external environment each time?
Interesting. So if after the loop, you change i to anything other than 1 or 2, then evaluate a, you don't get any arrows. On the other hand, if after the loop you run i = 1:2, and then evaluate a, you get both arrows. Well, I learned something new. Thanks!
|

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.