Skip to content

Object permanence: Which data points describe the same object?

Thomas Lin Pedersen edited this page Dec 10, 2018 · 2 revisions

submitted by Felix Golcher

This little collection is supposed to explore the rules determining which data points are seen by gganimate as representing the same object across the animation and which are not. The code is very basic. It was not made for fancy looks but in order to understand the working of gganimate. It's what I had to understand, maybe it helps others to understand faster.

library(tidyverse)
library(gganimate)

We start with the animation of two states a and b, a consisting of one point, b consisting of two.

myps <- 10
dta <- data.frame(y =     c(1,   1.3, 0.7),
                  x =     c(1,   2,   2),
                  tstep = c("a", "b", "b"))
(gg1 <- ggplot(dta,
              aes(x,y))+geom_point(size=myps)+
    theme_void()+
    coord_equal()+
    labs(title="state: {closest_state}")+
    transition_states(states=tstep,
                      transition_length = 1,
                      state_length = 1)+
    ease_aes('linear')+
    enter_fade()+
    exit_fade())
Basis: Two state solution.

Two of the points are identified as belonging to the same object: the point on the left (state a) morphs into the upper point of state b. but why this one? What determines, what points are identified? Distance doesn't play a role as the next animation shows:

dtb <- dta
dtb$x[3] <- 1.1
dtb$y[3] <- 1

gg1 %+% dtb
The points further apart are identified and tranform into each other.

Though the lower point of state b is now much closer to the point of state a, the state a point still morphs into the upper point of state b.

There is only one option left: We conclude it should be determined by the ordering of points:

gg1 %+% dta[c(1,3,2),]
Now the two other points are morphed into each other.

If we introduce color, we see another detail: Only points of the exact same quality are identified: If the two states have different colours, no points are identified, no morphing occurs.

(gg2 <- ggplot(dta,
               aes(x,y,col=tstep))+
   geom_point(size=myps,show.legend = F)+
   theme_void()+
   coord_equal()+
   transition_states(states=tstep,
                     transition_length = 1,
                     state_length = 1)+
   labs(title="state: {closest_state}")+
   ease_aes('linear')+
   enter_fade()+
   exit_fade())
Now, no points are identified with each other.

If we color more sensibly across states, only points with the same color are identified:

dta$id <- c("A","B","A")
(gg3 <- ggplot(dta,
               aes(x,y,col=id))+
    geom_point(size=myps,show.legend = F)+
    theme_void()+
    coord_equal()+
    labs(title="state: {closest_state}")+
    transition_states(states=tstep,
                      transition_length = 1,
                      state_length = 1)+
    ease_aes('linear')+
    enter_fade()+
    exit_fade())
The two red points morph into each other.

If we add new points, ordering is again used for identifying points as describing the same object:

dtd <- rbind(dta, data.frame(x=1.5,y=1,tstep="b",id="A"))
gg3 %+% dtd
plot of chunk c3

If you want to avoid identification of object across states without using color, the states can be assigned to different groups:

ggplot(dta,
       aes(x,y,group=tstep))+geom_point(size=myps)+
  theme_void()+
  coord_equal()+
  transition_states(states=tstep,
                    transition_length = 1,
                    state_length = 1)+
  ease_aes('linear')+
  enter_fade()+
  exit_fade()
Do not identify points over different states.

In summary: data points which match in all aesthetics, including group, describe the same object. If ambiguities remain, the ordering of the data is used for matching up objects.

Our last animation in this series repeats the first. But now we add a fourth data point, which is now connected with the orphaned data point. There are two objects on the move:

dtg <- rbind(dta, data.frame(x=1,y=1.1,tstep="a",id=NA))
gg1 %+% dtg
plot of chunk figg

If you want to identify data points across different qualities, you can explicitly identify them by using transition_components.

dtc <- data.frame(x =    c(1,   2,   2,    1),
                  y =    c(1.4, 1,   1.4,  1),
                  id =   c("a", "a", "b",  "b"),
                  time = c(1,   2,   2.1,  3.1),
                  element = paste0("e",1:4))
(gg4 <- ggplot(dtc, aes(x,y,label=element,col=element, group = id)) +
    geom_point(size=20, show.legend = F) +
    geom_text(size=10, show.legend = F, col="black") +
    theme_void()+
    coord_equal()+
    labs(title="Time {round(frame_time,2)}")+
    transition_components(time=time,
                          enter_length = .2,
                          exit_length = .2,
                          range=c(0,6))+
    enter_fade())
plot of chunk tc1

If there are more elements with a specific ID at some time than at others, the old rules apply and ordering comes into play again: The Element e5, added at the end of the data frame, appears from nowhere.

dte <- rbind(dtc, data.frame(x=2,y=1.2,id="a",time=2.0,element="e5"))
gg4 %+% dte
plot of chunk tc2

If we pull it to the top of the data frame, e1 now morphs into the new element and e2, now further down in the data, appears only later:

gg4 %+% dte[c(5,1:4),]
plot of chunk tc3

There are transitions which do not seem to allow movement of objects at all, as the following examples show.

homo <- data.frame(from=c(-2.3e5, -2e5, -7e5, -3e5),
                   to=c(-3e4, 0, -3e5, -2e5),
                   spec=c("Neanderthalensis","Sapiens","Heidelbergensis","(archaic) Sapiens"),
                   x=c(1,1,1,1),
                   y=c(1,2,2,2))
ggplot(homo, aes(x,y,label=spec))+
  geom_text(size=10)+
  theme_void()+
  coord_equal()+
  xlim(c(-2,4))+
  ylim(c(.5,2.5))+
  transition_events(start=from,
                    end=to,
                    exit_length = 1e4)+
  labs(title="{-round(frame_time/1000)*1000} BCE")+
  exit_fade()+
  ease_aes('linear')
plot of chunk te1
dtf <- data.frame(x=rnorm(100),
                  y=rnorm(100))
ggplot(dtf, aes(x,y))+
  geom_point()+
  transition_filter(transition_length = 1,
                    filter_length = 1,
                    x>y,x<y,x>-y,x< -y)
plot of chunk tf1