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.
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
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 thescan.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).
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 i
th point.
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.
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:
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")
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).
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.
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")
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: