Elie Gurarie, University of Maryland
August 22, 2016
“Classical” statistics (and your intro courses) were built around controlled experiment where the Inference relies on models where the Resonse 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 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 \]
The same number can be written:
\[ Z = R \exp(i \theta) \]
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)
Note: ALWAYS use
asp=1
- “aspect ratio = 1:1” - when plotting (properly projected) movement data!
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
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)
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)
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)
Phi <- Arg(dZ)
hist(Phi, col="grey", bor="darkgrey", freq=FALSE, breaks=seq(-pi,pi,pi/3))
Theta <- diff(Phi)
hist(Theta, col="grey", bor="darkgrey", freq=FALSE)
QUESTION: What is a problem with this histogram?
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))
- Load movement data of choice!
- Convert the locations to a complex variable Z.
- Obtain a vector of time stamps T, draw a histogram of the time intervals. Then, ignore those differences.
- Obtain, summarize and illustrate:
- the step lengths
- the absolute orientation
- the turning angles
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)
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)
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)
“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!
In-depth summer predation study, questions related to habitat use
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")
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.
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)
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:
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!