whitetiger2016
whitetiger2016

Reputation: 41

Strange behaviour of ggplot2

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.

Upvotes: 4

Views: 423

Answers (2)

MrFlick
MrFlick

Reputation: 206167

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.

Upvotes: 3

eipi10
eipi10

Reputation: 93761

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?

Upvotes: 2

Related Questions