4

How can I show the direction (heading) of observations using ggplot2? Is there a way to adjust shape=17(triangle) so that it "points" to the next time observations?

Example Code

library(ggplot2)

dat <- data.frame(id = c(1, 1, 2, 2, 3, 3), 
                  time = c(1, 2, 1, 2, 1, 2),
                  x = c(.1, .2, .3, .4, .5, .6),
                  y = c(.6, .25, .4, .33, .2, .51))


ggplot(dat, aes(x, y, color=factor(id))) + 
  geom_point(shape=17) + 
  # geom_line() +
  NULL

enter image description here

M--
  • 25,431
  • 8
  • 61
  • 93
Vedda
  • 7,066
  • 6
  • 42
  • 77

2 Answers2

3

We can use ggplot2::geom_segment after we reshape the data using dplyr and tidyr::pivot_wider:

dat <- data.frame(id = c(1, 1, 2, 2, 3, 3), 
                  time = c(1, 2, 1, 2, 1, 2),
                  x = c(.1, .2, .3, .4, .5, .6),
                  y = c(.6, .25, .4, .33, .2, .51))
library(dplyr)
library(tidyr)
library(ggplot2)
dat %>% 
  pivot_wider(names_from = time, values_from = c(x, y)) %>% 
  ggplot(aes(x=x_1, y=y_1, color=factor(id))) + 
  geom_segment(aes(xend = x_2, yend = y_2),
               arrow = arrow(length = unit(.3,"cm"))) +
  labs(x="x", y="y", color="id")

ggplot with arrows

Edit:

but I just want the arrow pointing without lines.

I'm not sure how we should handle the second point for each id (since it has not direction) but if we want to omit them from the plot we can do:

library(dplyr)
library(tidyr)
library(ggplot2)
dat %>% 
  group_by(id) %>% 
  arrange(id, time) %>% 
  mutate(x_2 = x + 0.0001 * (lead(x) - x),
         y_2 = y + 0.0001 * (lead(y) - y)) %>%
  filter(!is.na(x_2)) %>% 
  ggplot(aes(x=x, y=y, color=factor(id))) + 
  geom_segment(aes(xend = x_2, yend = y_2),
               arrow = arrow(length = unit(.3,"cm"))) +
  labs(x="x", y="y", color="id")

no lines or dots

Or if we want the arrows to point to the next measurement, independently of the color we can use the code below (now there is only the last point missing because of no direction):

library(dplyr)
library(tidyr)
library(ggplot2)
dat %>% 
  arrange(id, time) %>% 
  mutate(x_2 = x + 0.0001 * (lead(x) - x),
         y_2 = y + 0.0001 * (lead(y) - y)) %>%
  filter(!is.na(x_2)) %>% 
  ggplot(aes(x=x, y=y, color=factor(id))) + 
  geom_segment(aes(xend = x_2, yend = y_2),
               arrow = arrow(length = unit(.3,"cm"))) +
  labs(x="x", y="y", color="id")

If we want to keep the 'last' measures we could add them in another geom_point layer...

dario
  • 6,415
  • 2
  • 12
  • 26
  • 1
    Thanks. This is a good suggestion, but I just want the arrow pointing without lines. – Vedda Mar 13 '20 at 17:37
  • I just need a snap-shot in time (`t=1`) so I only need the next obs. Your arrow solution works, but wish I could remove the dot – Vedda Mar 13 '20 at 18:30
  • 1
    Do you mean the smallish dot drawn in the 'corner' of the arrows? To get rid of that just replace the coefficient `0.01` with `0.0001` or something super small ;) I edited my example to include that improvement! – dario Mar 13 '20 at 18:36
  • @Vedda Is this still not the answer you were looking for? We could adapt the arrow a bit if you want more customization – dario Mar 13 '20 at 19:05
  • Thanks! Not exactly what I was looking for but close enough. Cheers – Vedda Mar 13 '20 at 22:08
2

Combining ideas from dario's answer, How to scale a 2D vector and keep direction and Arranging arrows between points nicely in ggplot2

library(dplyr)
library(tidyr)
library(ggplot2)

dat %>% 
  pivot_wider(names_from = time, values_from = c(x, y)) %>% 
  group_by(id) %>% 
  mutate(x_v = x_2 - x_1, y_v = y_2 - y_1) %>% 
  mutate_at(vars("x_v", "y_v"), 
            list(units =~ (. / sqrt((x_v)^2 + (y_v)^2))/1000)) %>% 
  ggplot(aes(x=x_1, y=y_1, colour = factor(id))) + 
  geom_segment(aes(xend = x_1 + x_v_units, yend = y_1 + y_v_units),
              show.legend = F,
               arrow = arrow(length = unit(.3,"cm"), type="closed", angle = 20)) +
  geom_point(data = (dat %>% filter(time == 2)), aes(x, y), shape=15, size=2) +
  labs(x="x", y="y", colour="id") +
  theme_bw()

Data:

dat <- data.frame(id = c(1, 1, 2, 2, 3, 3), 
                  time = c(1, 2, 1, 2, 1, 2),
                  x = c(.1, .2, .3, .4, .5, .6),
                  y = c(.6, .25, .4, .33, .2, .51))
M--
  • 25,431
  • 8
  • 61
  • 93