1 Introduction

Visualizing tracks is a first step in identifying possible patterns or errors, as well as potential explanations for them. You should try different kinds of plots depending on your data. The kinds of figures you make should also be tuned to your goals. For your own exploratory purposes, there’s no need to get too picky about formatting and esthetics and there’s a higher premium on simplicity of code. For presentation and publication purposes, it might be worth getting more fussy.

Here are some examples you can later modify and improve to better serve your research question or interests.

For this lab we will work on a dataset consisting of movement data from four individuals of short-toed eagle (Circaetus gallicus). This is a migratory species, moving between southern Europe and Northern Africa.

2 Load the data

Load list with movement data. It is a list of data frames, one for each individual. We have - presumably - already processed the data, i.e. (a) formatted the dates, (b) removed obvious errors and (c) obtained a correctly km scaled projection.

load("Eagles.rda")

A comment on lists: Remember - a list has a length:

length(Eagles)
## [1] 4

Each element has a name:

names(Eagles)
## [1] "44787g" "80422g" "80423g" "80424g"

each element is accessed using double square brackets `[[]]:

head(Eagles[[1]])
##       ID       Date Time Latitude Longitude Speed Course Altitude
## 1 44787g 2009-07-08    8       NA        NA     0    164       15
## 2 44787g 2009-07-08    9 38.36033  -1.01383     0    122      698
## 3 44787g 2009-07-08   10 38.36033  -1.01400     0    200      700
## 4 44787g 2009-07-08   11 38.36033  -1.01383     0    192        0
## 5 44787g 2009-07-08   12 38.36033  -1.01400     0     73      700
## 6 44787g 2009-07-08   13 38.36033  -1.01383     0    190      699
##              DateTime        X        Y
## 1 2009-07-08 00:00:08       NA       NA
## 2 2009-07-08 00:00:09 149.2613 4253.427
## 3 2009-07-08 00:00:10 149.2464 4253.428
## 4 2009-07-08 00:00:11 149.2613 4253.427
## 5 2009-07-08 00:00:12 149.2464 4253.428
## 6 2009-07-08 00:00:13 149.2613 4253.427

3 Scanning track

We will start by exploring one individual. A basic XY-plot of one eagle:

e <- Eagles[[2]]
with(e, plot(X,Y,asp=1, type="o"))

Note the very important asp=1 argument, that controls the aspect ratio.

What about time dimension? Here’s a way you can plot x and y coordinates as a function of time.

par(mar = c(0,4,0,0), oma = c(4,0,5,0), xpd=NA)
layout(rbind(c(1,2), c(1,3)))
with(e, {
     plot(X, Y, asp = 1, type="o")
     plot(DateTime, X, type="o", xaxt="n", xlab="") 
     plot(DateTime, Y, type="o")
     title(names(Eagles[1]), outer=TRUE)
})

A few tricks here: 1. the layout() command helps “lay out” our three figures in an arrangement that echoes the matrix shape: 1 | 2 1 | 3 2. The par() commands control all graphical parameters, but here, in particular, the margins (mar) and the outer margins (oma). See ?par for more guidance.

This is a useful function, so let’s keep it around.

scan.track <- function(x,y,time, ...){
  par(mar = c(0,4,0,0), oma = c(4,0,5,0), xpd=NA)
  layout(rbind(c(1,2), c(1,3)))
     plot(x, y, asp = 1, type="o", ...)
     plot(time, x, type="o", xaxt="n", xlab="", ...)
     plot(time, y, type="o", ...)
}

R trick: the ... in the function definition allows us to pass any arguments we want to the functions inside the scan.track function. We’re drawing curves - this way we can add colors, control width, etc., all from the “outside”.

Let’s test it:

with(Eagles[[4]], 
  scan.track(X,Y,DateTime,pch=19, col=rgb(1,0,0,.5), cex=0.5))

Now that we have a function, we might want to generate this plot for all individuals and save the output as a multi-page pdf. The following loop should do just that:

pdf("EagleScans.pdf", width = 6, height = 3)
for(i in 1:length(Eagles)){
  e <- Eagles[[i]]
  scan.track(e$X, e$Y, e$DateTime, pch=19, col=rgb(0,0,0,.5))
  title(names(Eagles)[i], outer=TRUE)
}
dev.off()  

This will generate a pdf with all the eagles (labeled).

4 Pseudo-animating a track

This is, in my opinion, the best way to animate a track, essentially by flipping through a multi-page pdf.

e <- Eagles[[1]]
pdf("myanimation.pdf")
for(i in seq(1, nrow(e), 10)){
  map("world", xlim = c(-20,10), ylim = c(10,45), fill=TRUE, col="grey", bor=NA)
  with(e, {
       lines(Longitude[1:i], Latitude[1:i], col="blue", cex=0.5, lwd=2)
       points(Longitude[i], Latitude[i], pch=19, col="darkblue")
       title(paste(ID[1], DateTime[i]))
  })
  box()
}
dev.off()

Basically, you loop through the data and plot only up to the ith point.

5 Mapping

5.1 Base graphics mapping

Recall base mapping. We can use the world map from the package ‘mapdata’ or from ‘maptools’ for a plot background.

require(mapdata) # also loads maps
## Loading required package: mapdata
## Loading required package: maps
## 
##  # ATTENTION: maps v3.0 has an updated 'world' map.        #
##  # Many country borders and names have changed since 1990. #
##  # Type '?world' or 'news(package="maps")'. See README_v3. #
require(maptools)
## Loading required package: maptools
## Loading required package: sp
## Checking rgeos availability: FALSE
##      Note: when rgeos is not available, polygon geometry     computations in maptools depend on gpclib,
##      which has a restricted licence. It is disabled by default;
##      to enable gpclib, type gpclibPermit()
# using the maps function

lonlim = c(-20,10)
latlim = c(10,45)

par(mfrow=c(1,2))
#with mapdata
map("world", xlim = lonlim, ylim = latlim, fill=TRUE, col="lightgrey", bor="grey")
with(e, lines(Longitude, Latitude, pch=19, type="o", col=rgb(0,0,1,.3), cex=0.5))
title("`world` in `maps`")
box()

# using the wrld_simpl data in maptools
data(wrld_simpl)
plot(wrld_simpl, ylim=latlim, xlim=lonlim, col="lightgray", border="gray", axes=F) 
with(e, lines(Longitude, Latitude, pch=19, type="o", col=rgb(0,0,1,.3), cex=0.5))
title("`wrld_simpl` in `maptools`")
box()

They are quite similar, but world_simpl is an updated dataset on country boundaries.

We can plot the movement track for all eagles on the map using sapply().

require(scales) #this package includes some useful plotting functions 
## Loading required package: scales
ids <- names(Eagles)
map("world", xlim = c(-20,10), ylim = c(10,45), fill=TRUE, col="lightgrey", bor="grey")
lapply(Eagles, function(e)
       with(e, lines(Longitude, Latitude, pch=19, type="o", 
                     col=alpha(match(ID[1], ids), 0.5), cex=0.5)))
## $`44787g`
## NULL
## 
## $`80422g`
## NULL
## 
## $`80423g`
## NULL
## 
## $`80424g`
## NULL
legend("bottomright", legend=ids, col=1:length(ids), pch=19, cex=0.8, bty="n")
box()

The biggest trick here is the awkward col = match(ID[1], ids) command, which is saying: match the ID of this eagle (e$ID[1] = 80422g) to the list of IDs (ids =44787g, 80422g, 80423g, 80424g). Note the legend command.

5.2 ggplots and ggmaps

A lot of the akwardness of the above code is made simpler using ggplots, which is a whole different way to think about graphics which you’ll have to learn about yourself. For some things, it can be very efficient. To work with them, you have to have your data as a data frame, which is thankfully easy to do:

require(plyr)
Eagles.df <- ldply(Eagles)

Here’s a ggplot with no maps:

require(ggplot2)
ggplot(Eagles.df, aes(x=Longitude, y=Latitude, col=ID)) + geom_path() + geom_point()
## Warning: Removed 3 rows containing missing values (geom_path).
## Warning: Removed 498 rows containing missing values (geom_point).

The best feature of ggplot is the automatic labeling.

There is a great package called ggmap with allows us to use google and other map sources to improve our plots. We will use google maps, available through the package ‘ggmap’. There are several steps here:

  1. Obtain a “basemap” - which is downloaded from Google, and require a range
require(ggmap)
latrange <- c(5,50)
longrange <- c(-20,20)
basemap <- get_map(location = c(longrange[1], latrange[1], longrange[2], latrange[2]), maptype = "satellite", source="google")
## Warning: bounding box given to google - spatial extent only approximate.

Note: if you are using the ‘move’ package (data is a Move object), you can use the bbox() command to automatically obtain the bounding box of the data range code as follows:

require(move)
require(sp)
basemap <- get_map(bbox(extent(eagles.move)*1.1), source="google", maptype="satellite")
  1. Now that we’ve downloaded the basemap, we can draw it with:
ggmap(basemap)

… and add ggplot syntax to add the tracks of the eagles.

ggmap(basemap) + geom_point(data = Eagles.df, mapping = aes(x = Longitude, y = Latitude, col=ID), alpha = 0.5, size=0.5) + geom_path(data = Eagles.df, mapping = aes(x = Longitude, y = Latitude, col=ID), alpha = 0.5) + coord_map() + labs(x = "Longitude", y = "Latitude") +
scale_color_manual(values = rainbow(length(unique(Eagles.df$ID)))) 
## Warning: Removed 1247 rows containing missing values (geom_point).
## Warning: Removed 745 rows containing missing values (geom_path).

5.3 Google Earth kml files

The kml file syntax is a native syntax for adding locations (points, paths, etc.) to the Google Earth program, which is a wonderful way to visualize what is happening. Three is a function in maptools which allows you to output your movement data as a kml. The steps below are a bit complicated, but essentially:

require(maptools)

for(id in names(Eagles)){
  e <- Eagles[[id]][c("Longitude", "Latitude")] 
  e <- na.omit(e) # can't have NA's
  e.lines <- Lines(Line(e), ID = id)
  kmlLine(e.lines, kmlfile = paste0(id,".kml"),
          description = id, kmlname = id, 
          col=match(id, names(Eagles)))
}

Here’s a screen grab:

This will generate 4 files, one for each eagle, that can be opend in Google Earth. Note that the color was generated with that matching trick from before.

6 Annotated data

Often times there are additional covariates (sometimes referred to as “annotations”) that can be added to a visualization. The eagles, for example, also have altitude. GGplots in general are good at layering information. Below a ggplot of one eagle with color reflecting altitude:

ggplot(Eagles[[3]], aes(x = Longitude, y = Latitude, color=Altitude)) + 
  geom_path() + geom_point()
## Warning: Removed 2 rows containing missing values (geom_path).
## Warning: Removed 429 rows containing missing values (geom_point).

Here’s one with all eagles, and size of point reflecting altitude (harder to tune)

ggplot(Eagles.df, aes(x = Longitude, y = Latitude, size=Altitude, col = ID)) + 
  geom_path(lwd = 1) + geom_point()
## Warning: Removed 3 rows containing missing values (geom_path).
## Warning: Removed 498 rows containing missing values (geom_point).

And a ggmap version (using the basemap from earlier code):

ggmap(basemap) + 
geom_path(data = e, mapping = aes(x = Longitude, y = Latitude), size=0.6) +
geom_point(data = e, mapping = aes(x = Longitude, y = Latitude, colour=Altitude), alpha = 0.8, size=2) + 
coord_map() + labs(x = "Longitude", y = "Latitude")

7 Three-dimensional representations

What about 3D data? You can also plot annotated data in 3D. Here’s an example based on Dodge (2013).

The code below is very sparsely documented involves using the (rather funky) interactive 3d plotting package rgl.

require(fields)
require(rgl)

e<-na.omit(e) #no NAs allowed
colorpalette <- colorRampPalette(c("blue3","cyan3","aquamarine3","yellow","orange","red")) 
mycol=colorpalette(length(temp))

Open a 3d plotting environment (from the funky rgl package) and plot the track:

open3d()
plot3d(e$X, e$Y, e$Altitude,  xlab = "X (km)", ylab = "Y (km)", zlab = "Altitude",
       ticktype="simple", type="l", aspect=c(1,2,1/2), col="darkblue")

Add and x-y projection:

plot3d(e$X, e$Y, 0, ticktype="simple", col="gray", type="l", add=T, axes=FALSE)

Add color to the 3d track line based on a (randomly simulated) behavior variable:

palette(topo.colors(5))
e$behavior <- sample(1:5, size = nrow(e), prob = c(5,4,2,3,1), replace = TRUE)
plot3d(e$X, e$Y, e$Altitude, col=e$behavior, size=4, add=TRUE)

The end result will look something like this: