Basics of Movement Analysis: Complex is Simple

Elie Gurarie, University of Maryland
August 22, 2016





Stats we are used to:

“Classical” statistics (and your intro courses) were built around controlled experiment where the Inference relies on models where the Resonse is:

  • one-dimensional
  • independent
  • identically distributed
  • often normal, second-most often binomial
  • randomly/uniformly sampled

Movement Data is ...

  • one-dimensional: Multi-Dimensional! (X,Y,Time)

    Solution: get comfortable with complex numbers.

  • independent: Highly dependent!

    Solution: get comfortable with correlated / dependent data .

  • identically distributed!: Complex and heterogeneous!

    Solution: behavioral change point analysis / segmentation, hidden markov models.

  • often normal: Circular and skewed distributions!

    Solution: Flexible Likelihood-based modelling,

  • randomly/uniformly sampled: Gappy / irregular / oddly duty-cycled data

    Solution: Continuous movement modeling.

Complex Numbers are Simple!

Complex numbers are a handy bookkeeping tool to package together two dimensions in a single quantity. They expand the one-dimensional (“Real”) number line into a second dimension (“Imaginary”).

IMHO, they are the single most efficient way to deal with 2D vectors, both in math notation and in R code.

\[ Z = X + iY \]

  • \( X \) is the real part.
  • \( Y \) is the imaginary part.

The same number can be written:

\[ Z = R \exp(i \theta) \]

  • \( R \) is the length of the vector: the modulus
  • \( \theta \) is the orientation of the vector: the argument

An example

X <- c(3,4,-2)
Y <- c(0,3,2)
Z <- X + 1i*Y
Z
[1]  3+0i  4+3i -2+2i

Alternatively:

Z <- complex(re = X, im=Y)
plot(Z, pch=19, col=1:3, asp=1)
arrows(rep(0,length(Z)), rep(0,length(Z)), Re(Z), Im(Z), lwd=2, col=1:3)

plot of chunk plotZ

Note: ALWAYS use asp=1 - “aspect ratio = 1:1” - when plotting (properly projected) movement data!

Obtaining summary statistics

Obtain lengths of vectors:

Mod(Z)
[1] 3.000000 5.000000 2.828427

Obtain orientation of vectors:

Arg(Z)
[1] 0.0000000 0.6435011 2.3561945

Note, the orientations are in radians, i.e. range from \( 0 \) to \( 2\pi \) going counter-clockwise from the \( x \)-axis. Compass directions go from 0 to 360 clockwise, so, to convert:

90-(Arg(Z)*180)/pi
[1]  90.0000  53.1301 -45.0000

Lets play with a trajectory

Quick code for a correlated random walk:

X <- cumsum(arima.sim(n=100, model=list(ar=.7)))
Y <- cumsum(arima.sim(n=100, model=list(ar=.7)))
Z <- X + 1i*Y
plot(Z, type="o", asp=1)

plot of chunk SRW

Instant summary statistics of a trajectory:

The average location

mean(Z)
[1] -11.85537+4.6554i

The step vectors:

dZ <- diff(Z)
plot(dZ, asp=1, type="n")
arrows(rep(0, length(dZ)), rep(0, length(dZ)), Re(dZ), Im(dZ), col=rgb(0,0,0,.5), lwd=2, length=0.1)

plot of chunk unnamed-chunk-7

Distribution of step lengths

S <- Mod(dZ)
summary(S)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.1294  1.0470  1.5650  1.6930  2.1750  4.1720 
hist(S, col="grey", bor="darkgrey", freq=FALSE)
lines(density(S), col=2, lwd=2)

plot of chunk unnamed-chunk-8

What about angles?

  • The absolute orientations:
Phi <- Arg(dZ)
hist(Phi, col="grey", bor="darkgrey", freq=FALSE, breaks=seq(-pi,pi,pi/3))

plot of chunk unnamed-chunk-9

  • Turning angles
Theta <- diff(Phi)
hist(Theta, col="grey", bor="darkgrey", freq=FALSE)

plot of chunk unnamed-chunk-10

QUESTION: What is a problem with this histogram?

Circular statistics

Angles are a wrapped continuous variable, i.e. \( 180^o > 0^o = 360^o < 180^o \). The best way to visualize the distribution of wrapped variables is with Rose-Diagrams. An R package that deals with circular data is circular.

require(circular)
Theta <- as.circular(Theta)
Phi <- as.circular(Phi)
rose.diag(Phi, bins=16, col="grey", prop=2, main=expression(Phi))
rose.diag(Theta, bins=16, col="grey", prop=2, main=expression(Theta))

plot of chunk unnamed-chunk-11

LAB EXERCISE

  1. Load movement data of choice!
  2. Convert the locations to a complex variable Z.
  3. Obtain a vector of time stamps T, draw a histogram of the time intervals. Then, ignore those differences.
  4. Obtain, summarize and illustrate:
  • the step lengths
  • the absolute orientation
  • the turning angles

Complex manipulations (are easy)

Addition and subtraction of vectors:

\[ Z_1 = X_1 + i Y_1; Z_2 = X_2 + i Y_2 \] \[ Z_1 + Z_2 = (X_1 + X_2) + i(Y_1 + Y_2) \]

Useful, e.g., for shifting locations:

plot(Z, asp=1, type="l", col="darkgrey", xlim=c(-2,2)*max(Mod(Z)))
lines(Z - mean(Z), col=2, lwd=2)
lines(Z + Z[length(Z)], col=3, lwd=2)

plot of chunk unnamed-chunk-12

Complex manipulations (are easy)

Multiplication of complex vectors \[ Z_1 = R_1 \exp(i \theta_1); Z_2 = R_2 \exp(i \theta_2) \] \[ Z_1 Z_2 = R_1 R_2 \exp(i (\theta_1 + \theta_2)) \]

If \( \text{Mod}(Z_2) = 1 \), multiplications rotates by \( \text{Arg}(Z_2) \)

Rot1 <- complex(mod=1, arg=pi/4)
Rot2 <- complex(mod=1, arg=-pi/4)
plot(Z, asp=1, type="l", col="darkgrey", lwd=3)
lines(Z*Rot1, col=2, lwd=2)
lines(Z*Rot2, col=3, lwd=2)

plot of chunk unnamed-chunk-13

A colorful loop:

require(gplots)
plot(Z, asp=1, type="n", xlim=c(-1,1)*max(Mod(Z)), ylim=c(-1,1)*max(Mod(Z)))
cols <- rich.colors(1000,alpha=0.1)
thetas <- seq(0,2*pi,length=100)
for(i in 1:1000)
  lines(Z*complex(mod=1, arg=thetas[i]), col=cols[i], lwd=4)

plot of chunk SillyRainbow

I know you're thinking ...



“Thanks for teaching me how to make a weird swirling rainbow thing … but why in the world would I want to shift and rotate my precious, precious data, which was just perfect the way it was?




My response: Null Sets for Pseudo Absences!

Example with Finnish Wolves

In-depth summer predation study, questions related to habitat use

  • landscape type - forest/bog/field
  • linear elements - roads/rivers/power lines, etc.

Defining Null Sets

  1. Obtain all the steps and turning angles
  2. Rotate them by the orientation of the last step (\( Arg(Z_1-Z_0) \))
  3. Add the rotated steps to the last step (\( Z_1 \))

Calculating Null Sets in R

1. Obtain all the steps and turning angles
2. Rotate them by the orientation of the last step (\( Arg(Z_1-Z_0) \))

Z <- Z[1:10]
n <- length(Z)
S <- Mod(diff(Z))
Phi <- Arg(diff(Z))
Theta <- diff(Phi)
RelSteps <- complex(mod = S[-1], arg=Theta)

Z0 <- Z[-((n-1):n)]
Z1 <- Z[-c(1,n)]
Z2 <- Z[-(1:2)]

Rotate <- complex(mod = 1, arg=Arg(Z1-Z0))
plot(c(0,RelSteps), asp=1, xlab="x", ylab="y", pch=19)
arrows(rep(0,n-2), rep(0, n-2), Re(RelSteps), Im(RelSteps), col="darkgrey")

plot of chunk unnamed-chunk-16

Note: in practice (i.e. with tons of data), it is sufficient to randomly sample some smaller number (e.g. 30) null steps at each location.

Fuzzy catterpillar plot

3. Add the rotated steps to the last step

Z.null <- matrix(0, ncol=n-2, nrow=n-2)
for(i in 1:length(Z1))
  Z.null[i,] <- Z1[i] + RelSteps * Rotate[i]

4. Make the fuzzy catterpillar plot

palette(rich.colors(10))
plot(Z, type="o", col=1:10, pch=19, asp=1)
for(i in 1:nrow(Z.null))
  segments(rep(Re(Z1[i]), n-2), rep(Im(Z1[i]), n-2), s
           Re(Z.null[i,]), Im(Z.null[i,]), col=i+1)

plot of chunk unnamed-chunk-19

Using the null-set

The use of the null set is a way to test a narrower null hypothesis that accounts for auto correlation in the data.

The places the animal COULD HAVE but DID NOT go to are pseudo-absences, against which you can fit, e.g., logistic regression models (aka Step-selection functions).

Or just be simple/lazy (like us) and compare observed locations with Chi-squared tests:

EXERCISE: Create a fuzzy-catterpillar plot!

Use (a portion) of the data you analyzed before.

# get pieces
n <- length(Z)
S <- Mod(diff(Z))
Phi <- Arg(diff(Z))
Theta <- diff(Phi)
RelSteps <- complex(mod = S[-1], arg=Theta)

# calculate null set
Z0 <- Z[-((n-1):n)]
Z1 <- Z[-c(1,n)]
Z.null <- matrix(0, ncol=n-2, nrow=n-2)
for(i in 1:length(Z1))
  Z.null[i,] <- Z1[i] + sample(max(length(RelSteps),30)) * Rotate[i]

# plot
plot(Z, type="o", col=1:10, pch=19, asp=1)
for(i in 1:nrow(Z.null))
  segments(rep(Re(Z1[i]), n-2), rep(Im(Z1[i]), n-2), 
           Re(Z.null[i,]), Im(Z.null[i,]), col=i+1)

Fuzzy Polar Bear Catterpillar!