library(tidyverse)
library(sf)
library(mapview)
library(move)
library(adehabitatLT)
Movement data
As mentioned this morning, movement data may come in a variety of scales and resolutions, requiring careful consideration before analysis.
Here we are going to focus on GPS relocation data as it is one of the most common forms of tracking data and provides a (mostly) consistent record of an animals movement path.
Justr as there are multiple semi-cooperative packages in R for working with spatial data generaly, there are many packages in R used to handle movement data, and an ever growing number built for specific analyses. Unfortunately there is no strict consensus yet on which class movement data objects should be in across packages, and researchers often have to be comfortable converting from one to the other depending on the task at hand and the format required by each package. Although there are most definitely more R packages created for dealing with animal movement data, we are going to focus today on the two most prominent move
and adehabitatLT
.
To begin we are going to introduce Movebank and Move
& MoveStack
objects with the library move
. Later we will introduce the adehabitatLT
package and demonstrate how easy it is to pull out primary stepwise characteristics like step length, turning angle, and net displacement from our trajectories. These primary path characteristics will form the basis for many more complex movement analyses including our simulations later next week.
Movebank
Though often researchers in your position will have data of your own, there has been an effort to share movement data from other collections through the Movebank database. Movebank is a free online infrastructure created to help researchers manage, share, analyze, and archive animal movement data. move
is an R package that contains functions to access movement data stored in movebank.org as well as tools to visualize and statistically analyze animal movement data. move
is addressing movement ecology questions that complement existing efforts such as adeHabitat
and other packages which we will introduce later.
There are 3 ways to load movement data into R as a move
object: 1. Using the movebank API 2. Directly loading movebank-formatted .csvs 3. Converting data from a traditional dataframe
Here let’s consider some Albatross data, from movebank study #2911040: To load data directly from the movebank API like I will here, you would need your own movebank username and password, here I have stored my password in my local environment as the variable pass
.
loginStored <- movebankLogin(username="dpseidel", password=pass)
#search for studies -
searchMovebankStudies(x="albatross", login=loginStored)
#get study ID -
getMovebankStudy(2911040, login=loginStored)$citation
#check for animal IDs -
getMovebankAnimals(2911040, login=loginStored)
#get the all data - getMovebankData()
#get only specific animal by specifying individual_id
albatross <- getMovebank("event", study_id = 2911040, login=loginStored)
write_tsv(albatross, "Study2911040")
# getMovebank("event", login, study_id,...): returns the sensor measurements from a study.
# See also getMovebankData, getMovebankNonLocationData.
To save us the effort of getting you all Movebank logins at this point, I have provided the tsv. We can use this to demonstrate the 3rd way of loading in movement data.
study <- read_tsv("data_files/Study2911040", comment="##",
col_types = "Tnncc")
study <- as.data.frame(study)
albatross <- move(x=study$location_long,
y=study$location_lat,
time=study$timestamp,
data=study,
animal = as.factor(study$individual_id),
sensor = as.factor(study$tag_id),
proj=CRS("+proj=longlat"))
## Warning in .local(x, y, time, data, proj, ...): There were NA locations
## detected and omitted. Currently they are not stored in unusedrecords
head(albatross)
## timestamp location_lat location_long
## 1 2008-05-31 13:30:02 -1.372641 -89.74021
## 2 2008-05-31 15:00:44 -1.372894 -89.74015
## 3 2008-05-31 16:30:39 -1.372881 -89.74014
## 4 2008-05-31 18:00:49 -1.372891 -89.74016
## 5 2008-05-31 19:30:18 -1.372912 -89.74013
## 6 2008-05-31 21:00:20 -1.372904 -89.74017
class(albatross)
## [1] "MoveStack"
## attr(,"package")
## [1] "move"
We’ve created our own MoveStack. We can plot it using the move::plot command and if we wish, mapview()
which converts it to a sf
object on the fly.
plot(albatross, type="l") # all of them
If we are curious to compare behaviors across individuals, we can split the moveStack, into separte move objects with the command split
ids <- split(albatross)
This allows us to plot and manipulate the individuals separately:
plot(ids[[1]], type='l')
plot(ids[[28]], type='l')
par(mfrow=c(2,2))
plot(ids[[1]], type='l', main= names(ids[1]))
plot(ids[[2]], type='l', main= names(ids[2]))
plot(ids[[3]], type='l', main= names(ids[3]))
plot(ids[[4]], type='l', main= names(ids[4]))
par(mfrow=c(1,1))
And of course, just as before, we can manipulate, and clean up, this data easily when converted to an sf object:
albatross_sf <-
study %>% na.omit() %>%
st_as_sf(coords = c("location_long", "location_lat"), crs=4326)
For instance, in this state, it’s simple to look at how many records we have per individual:
albatross_sf %>%
group_by(individual_id) %>%
tally()
## Simple feature collection with 28 features and 2 fields
## geometry type: MULTIPOINT
## dimension: XY
## bbox: xmin: -91.3732 ymin: -12.79464 xmax: -77.51874 ymax: 0.1821983
## epsg (SRID): 4326
## proj4string: +proj=longlat +datum=WGS84 +no_defs
## # A tibble: 28 x 3
## individual_id n geometry
## <fctr> <int> <simple_feature>
## 1 2911059 757 <MULTIPOINT (...>
## 2 2911060 1113 <MULTIPOINT (...>
## 3 2911061 182 <MULTIPOINT (...>
## 4 2911062 573 <MULTIPOINT (...>
## 5 2911063 757 <MULTIPOINT (...>
## 6 2911064 633 <MULTIPOINT (...>
## 7 2911065 977 <MULTIPOINT (...>
## 8 2911066 647 <MULTIPOINT (...>
## 9 2911067 1503 <MULTIPOINT (...>
## 10 2911074 1326 <MULTIPOINT (...>
## # ... with 18 more rows
# something we could do on the movestack by calling...
table(albatross@trackId)
##
## X2911059 X2911060 X2911061 X2911062 X2911063 X2911064 X2911065 X2911066
## 757 1113 182 573 757 633 977 647
## X2911067 X2911074 X2911075 X2911076 X2911077 X2911078 X2911079 X2911080
## 1503 1326 32 53 189 46 67 48
## X2911084 X2911085 X2911086 X2911087 X2911088 X2911089 X2911090 X2911091
## 22 243 140 96 58 4 150 1338
## X2911092 X2911093 X2911094 X2911095
## 974 1552 2339 209
Or map the paths of a particular pair:
albatross_sf %>%
filter(individual_id %in% c(2911059,2911062)) %>%
mapview(zcol="individual_id")
and easily convert it back to a movestack
as needed.
as(albatross_sf, 'Spatial') %>% as(., "MoveStack")
## class : MoveStack
## features : 16028
## extent : -91.3732, -77.51874, -12.79464, 0.1821983 (xmin, xmax, ymin, ymax)
## coord. ref. : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0
## variables : 3
## names : timestamp, individual_id, tag_id
## min values : 2008-05-31 13:29:31, 2911059, 2911107
## max values : 2008-11-06 18:00:55, 2911095, 2911134
## timestamps : NA ... NA Time difference of NA secs (start ... end, duration)
## sensors :
## indiv. data :
## individuals : NA
## date created: 2017-11-07 22:24:21
There is lots more the move
package has to offer but for now, we are going to move onto ltraj objects and the primary path characteristics they make so easy to extract. We will come back to the move
package tomorrow when we discuss methods for estimating home ranges.
Ltraj objects
The class ltraj is intended to store trajectories of animals. ltraj
objects function neatly as lists, similar in some ways to the list of Move objects that was created when we split out albatross MoveStack. A key difference between Move and ltraj objects is not only in the structure of data but also the attributes calculated. Ltraj ojects automatically calculate common path characteristics like step length, relative turning angle, absolute turning angle, and net squared displacement from which much of subsequent movement modelling builds off of. Additionally from these primary path characteristics it’s simple to calculate secondary statistics like sinuosity, residence time, or directional persistence.
ltrajs
can be created from a traditional data frames, spatial dataframes, or directly from move objects using the command as.ltraj
and, in the case of the dataframes, specifying the coordinates.
# from move object
alba_ltraj <- as(albatross, 'ltraj')
## Warning in asMethod(object): Converting a long lat projected object while
## the ltraj does not deal with long lat projected data
alba_ltraj
##
## *********** List of class ltraj ***********
##
## Type of the traject: Type II (time recorded)
## * Time zone unspecified: dates printed in user time zone *
## Irregular traject. Variable time lag between two locs
##
## Characteristics of the bursts:
## id burst nb.reloc NAs date.begin date.end
## 1 X2911059 X2911059 757 0 2008-05-31 13:30:02 2008-07-17 21:00:44
## 2 X2911060 X2911060 1113 0 2008-05-31 14:59:31 2008-08-09 18:00:18
## 3 X2911061 X2911061 182 0 2008-06-23 17:58:02 2008-07-11 10:32:29
## 4 X2911062 X2911062 573 0 2008-05-31 13:29:55 2008-07-06 12:00:26
## 5 X2911063 X2911063 757 0 2008-05-31 14:59:30 2008-07-17 22:31:01
## 6 X2911064 X2911064 633 0 2008-05-31 14:59:55 2008-07-11 12:00:43
## 7 X2911065 X2911065 977 0 2008-05-31 13:29:31 2008-08-03 10:30:44
## 8 X2911066 X2911066 647 0 2008-06-23 19:28:30 2008-08-03 10:30:43
## 9 X2911067 X2911067 1503 0 2008-06-23 17:58:01 2008-09-28 13:31:13
## 10 X2911074 X2911074 1326 0 2008-06-23 17:58:44 2008-09-15 13:30:17
## 11 X2911075 X2911075 32 0 2008-06-23 17:57:56 2008-06-25 15:00:14
## 12 X2911076 X2911076 53 0 2008-06-23 16:27:20 2008-06-26 21:00:31
## 13 X2911077 X2911077 189 0 2008-06-23 19:28:50 2008-07-05 12:00:47
## 14 X2911078 X2911078 46 0 2008-06-23 16:28:30 2008-06-26 12:00:55
## 15 X2911079 X2911079 67 0 2008-06-23 17:58:19 2008-06-27 19:30:44
## 16 X2911080 X2911080 48 0 2008-06-23 16:28:31 2008-06-26 13:30:43
## 17 X2911084 X2911084 22 0 2008-05-31 16:29:31 2008-06-01 22:30:50
## 18 X2911085 X2911085 243 0 2008-05-31 16:29:31 2008-06-15 22:30:55
## 19 X2911086 X2911086 140 0 2008-05-31 16:29:56 2008-06-09 12:00:50
## 20 X2911087 X2911087 96 0 2008-05-31 16:30:27 2008-06-06 15:00:22
## 21 X2911088 X2911088 58 0 2008-05-31 18:00:01 2008-06-04 10:30:49
## 22 X2911089 X2911089 4 0 2008-05-31 13:29:55 2008-05-31 16:30:55
## 23 X2911090 X2911090 150 0 2008-06-11 11:59:02 2008-06-20 18:00:59
## 24 X2911091 X2911091 1338 0 2008-07-10 23:57:31 2008-10-03 15:00:56
## 25 X2911092 X2911092 974 0 2008-07-10 22:28:00 2008-09-09 22:30:57
## 26 X2911093 X2911093 1552 0 2008-06-19 02:58:55 2008-10-13 12:00:56
## 27 X2911094 X2911094 2339 0 2008-06-12 13:28:49 2008-11-06 18:00:55
## 28 X2911095 X2911095 209 0 2008-07-11 16:27:25 2008-07-24 21:01:28
##
##
## infolocs provided. The following variables are available:
## [1] "sensor" "timestamp" "location_lat" "location_long"
Note all the information the this class gives us up front. Not only can we see the number of relocations from each of our animals we can see exactly the duration of each “burst” and that this is an “irregular” “Type2” trajectory. Just what exactly does that mean?
Side note: types of trajectory
The adehabitat packages, and ltraj objects, distinguish between 2 types of trajectories: 1. Trajectories of type I are characterized by the fact that the time is not precisely known or not taken into account for the relocations of the trajectory (i.e. sampling of tracks in snow) 2. Trajectories of type II are characterized by the fact that the time is known for each relocation. This type of trajectory is in turn be divided into two subtypes: – regular trajectories: these trajectories are characterized by a constant time lag between successive relocations; – irregular trajectories: these trajectories are characterized by a variable time lag between successive relocations
It’s worth emphasizing that functions in adehabitatLT
are mainly designed to deal with type I or type II regular trajectories. Irregular trajectories are harder to analyze as their descriptive parameters may not be compared when computed on different time lags.
Side note: Making an irregular trajectory regular.
From here on out we are going to work with some ibex data already formatted as ltrajs and provided by the adehabitatLT package. First we want to load both the regular trajectory data “ibex” and the raw irregular trajectory “ibexraw” just to get a sense of the difference.
data(ibex)
data(ibexraw)
ibex
##
## *********** List of class ltraj ***********
##
## Type of the traject: Type II (time recorded)
## * Time zone: Europe/Paris *
## Regular traject. Time lag between two locs: 14400 seconds
##
## Characteristics of the bursts:
## id burst nb.reloc NAs date.begin date.end
## 1 A153 A153 84 13 2003-06-01 00:00:00 2003-06-14 20:00:00
## 2 A160 A160 81 22 2003-06-01 08:00:00 2003-06-14 16:00:00
## 3 A286 A286 83 15 2003-06-01 00:00:00 2003-06-14 16:00:00
## 4 A289 A289 84 26 2003-06-01 00:00:00 2003-06-14 20:00:00
ibexraw
##
## *********** List of class ltraj ***********
##
## Type of the traject: Type II (time recorded)
## * Time zone: Europe/Paris *
## Irregular traject. Variable time lag between two locs
##
## Characteristics of the bursts:
## id burst nb.reloc NAs date.begin date.end
## 1 A153 A153 71 0 2003-06-01 00:00:56 2003-06-14 20:01:33
## 2 A160 A160 59 0 2003-06-01 08:01:35 2003-06-14 16:02:20
## 3 A286 A286 68 0 2003-06-01 00:02:45 2003-06-14 16:01:41
## 4 A289 A289 58 0 2003-06-01 00:01:31 2003-06-14 20:02:32
From a simple histogram on the first individual of raw data, we can see there are some big time gaps.
hist(ibexraw[1], "dt", freq = TRUE)
Looking at this rawibex data, we can see that the median dt
is:
median(ibexraw[[1]]$dt)
## [1] 14411
From that we can expect that the unit was set to take a fix every 14400 seconds (4 hrs). To regularize a trajectory like this one that is not too irregular - we need to round our dt’s and insert missing values where needed. Keep in mind, not all trajectories can or should be regularized - we will touch on more advanced methods for dealling with these next week.
In this case, we need to make our first timestamp exact and set the missing values accordingly:
ibex153_raw <- ibexraw[1]
ibex153_raw[[1]]$date <- lubridate::force_tz(ibex153_raw[[1]]$date, "GMT") # hack to simplify timezones.
## The reference date: the hour should be exact (i.e. minutes=0):
ref <- strptime("00:00:00", "%H:%M:%S", tz="GMT")
ibex153_raw %>%
setNA(., ref, 4, units = "hour") %>% # set the missing values
sett0(., ref, 4, units = "hour") -> ibex153 # round the fixes to 4 hours.
is.regular(ibex153)
## [1] TRUE
Now our trajectory is regular and we can trust and compare all those primary path characteristics we care about!
Primary Path Characteristics
If we dig into any one of our ltraj list objects, we’ll see a dataframe containing certain basic characteristics from our paths. Specifically:
- Step Length (i.e
dist
): the distance between successive relocations is often used in animal movement analysis (e.g. Root and Kareiva 1984, Marsh and Jones 1988);
ibex[[1]]$dist
## [1] 41.048752 NA NA NA NA
## [6] 244.663034 5.099020 1937.807524 2206.399782 957.133742
## [11] NA NA NA NA 995.301462
## [16] 1663.136795 1079.757380 NA NA NA
## [21] NA NA 860.847257 257.637342 3377.519208
## [26] 4012.722393 554.379834 363.258861 1209.845031 1840.706658
## [31] 2828.386289 3005.796068 109.123783 380.276215 141.509717
## [36] 179.805450 2196.165067 2880.844494 338.285382 644.772828
## [41] 2012.489255 1410.323722 805.497362 1069.415728 466.274597
## [46] 759.697966 1050.824914 829.743334 1075.119063 1666.965207
## [51] 875.938925 1042.054221 897.661963 NA NA
## [56] 3122.510048 709.078275 1192.982816 1562.585358 1634.619528
## [61] 2756.335430 2098.842776 NA NA 22.671568
## [66] 6.082763 682.820621 1169.884182 2330.009657 NA
## [71] NA NA NA 284.787640 1195.557192
## [76] NA NA 231.397926 67.623960 280.401498
## [81] 317.000000 235.187160 762.115477 NA
hist(ibex[[1]]$dist)
- Turning Angle (
rel.angle
v.abs.angle
)
Your relative turning angle measures the change of direction between the step built by relocations i − 1 and i and the step built by relocations i and i + 1 (what we generally refer to as “turning angle”). It is often used together with the parameter dist to fit movement models (e.g. Root and Kareiva 1984, Marsh and Jones 1988). For comparison the absolute angle is the angle between the x direction and the step built by relocations i and i + 1.
ibex[[1]]$abs.angle
## [1] -2.54683340 NA NA NA NA
## [6] -1.49715922 -1.76819189 1.27385296 0.62446442 0.66021846
## [11] NA NA NA NA 2.01197581
## [16] 2.36129649 1.26603301 NA NA NA
## [21] NA NA -1.23582097 1.45799595 -1.09735030
## [26] 2.06688126 1.90338502 0.43779130 -0.19463985 1.54308603
## [31] -1.17709308 1.74365693 -2.29135046 0.35169169 -1.01219701
## [36] 0.73818917 0.43676470 -2.80480316 2.47562342 1.40564765
## [41] -2.85080218 0.10869966 1.11991871 -1.09167526 1.14168393
## [46] 2.19823773 -1.08246238 -0.73595045 2.07563628 -2.12551673
## [51] 1.27540298 -1.99194377 -0.09035732 NA NA
## [56] 2.17931844 -0.39674382 -1.26601127 1.43405806 2.71926871
## [61] 0.47435627 -2.22068195 NA NA 2.29377568
## [66] -1.40564765 1.21476819 -2.45123215 -0.46844604 NA
## [71] NA NA NA -2.62767297 1.62184073
## [76] NA NA -2.13122798 0.90067429 3.08807239
## [81] 2.90273480 3.00510745 0.19276358 NA
ibex[[1]]$rel.angle
## [1] NA NA NA NA NA
## [6] NA -0.27103267 3.04204484 -0.64938853 0.03575404
## [11] NA NA NA NA NA
## [16] 0.34932068 -1.09526348 NA NA NA
## [21] NA NA NA 2.69381692 -2.55534625
## [26] -3.11895374 -0.16349624 -1.46559372 -0.63243116 1.73772588
## [31] -2.72017911 2.92075001 2.24817791 2.64304215 -1.36388870
## [36] 1.75038618 -0.30142447 3.04161744 -1.00275873 -1.06997577
## [41] 2.02673548 2.95950184 1.01121904 -2.21159396 2.23335918
## [46] 1.05655381 3.00248520 0.34651193 2.81158672 2.08203230
## [51] -2.88226560 3.01583856 1.90158645 NA NA
## [56] NA -2.57606226 -0.86926745 2.70006934 1.28521065
## [61] -2.24491244 -2.69503822 NA NA NA
## [66] 2.58376198 2.62041584 2.61718496 1.98278612 NA
## [71] NA NA NA NA -2.03367161
## [76] NA NA NA 3.03190227 2.18739810
## [81] -0.18533758 0.10237265 -2.81234387 NA
hist(ibex[[1]]$rel.angle)
- Net Squared Displacement, i.e R2n,the squared distance between the first relocation of the trajectory and the current relocation is often used to test some movements models (e.g. the correlated random walk, see the seminal paper of Kareiva and Shigesada, 1983).
ibex[[1]]$R2n
## [1] 0 1685 NA 1225649 NA 1175380 716456
## [8] 707850 7707508 22105508 31493309 NA NA NA
## [15] 34046082 40262557 48411817 64601465 NA NA 69518160
## [22] NA 73715652 62073850 66127105 38097497 72914036 81193601
## [29] 85244200 87619817 123526720 83596313 135212089 132860745 138624349
## [36] 136645444 140455498 183407993 133989776 136704325 152060121 128744480
## [43] 138756605 158040557 139548573 150733337 161919316 143966953 137174165
## [50] 153560628 115910825 135535354 112497445 117329650 NA 85816936
## [57] 123531865 121096973 100520641 133665220 140549533 187570994 137323381
## [64] NA 135575348 135869474 135740125 152038697 129897332 121375673
## [71] NA 135658601 NA 143118593 138149945 165926018 NA
## [78] 140839250 135599701 137063840 135622345 135539108 135047525 142675065
hist(ibex[[1]]$R2n)
Note these distributions plotted above as they often form the basis for accurate simulations of animal movemnt paths.
Some Secondary Characteristics
From this set of characteristics we can easily pull out secondary path characteristics like:
- Velocity:
ibex[[1]]$dist/ibex[[1]]$dt * 3.6 #convert meters per sec to kmph
## [1] 0.010262188 NA NA NA NA
## [6] 0.061165758 0.001274755 0.484451881 0.551599946 0.239283435
## [11] NA NA NA NA 0.248825365
## [16] 0.415784199 0.269939345 NA NA NA
## [21] NA NA 0.215211814 0.064409336 0.844379802
## [26] 1.003180598 0.138594958 0.090814715 0.302461258 0.460176664
## [31] 0.707096572 0.751449017 0.027280946 0.095069054 0.035377429
## [36] 0.044951363 0.549041267 0.720211124 0.084571346 0.161193207
## [41] 0.503122314 0.352580930 0.201374340 0.267353932 0.116568649
## [46] 0.189924492 0.262706229 0.207435833 0.268779766 0.416741302
## [51] 0.218984731 0.260513555 0.224415491 NA NA
## [56] 0.780627512 0.177269569 0.298245704 0.390646339 0.408654882
## [61] 0.689083857 0.524710694 NA NA 0.005667892
## [66] 0.001520691 0.170705155 0.292471046 0.582502414 NA
## [71] NA NA NA 0.071196910 0.298889298
## [76] NA NA 0.057849481 0.016905990 0.070100374
## [81] 0.079250000 0.058796790 0.190528869 NA
- First Passage Time
# Calculate fpt for radii of a kilometer to 5 kilometers
ibex_fpt <- fpt(ibex153, radii = seq(1000, 5000, 1000), units="hours")
# plot variation over time
plot(ibex_fpt, scale=1000)
# plot mean fpt across scales
meanfpt(fpt(ibex153, radii = seq(1000, 5000, 1000), units="hours"))
These are just a few of the metrics you might want to derive from your paths. Additional secondary characteristics include straightness, sinuosity, directional persistence, persistence velocity, residence time, time to return, and more. We will encounter many of these on later days of this workshop.
In addition to the derivation of these primary and secondary path cahracteristics, adehabitatLT is especially useful for path segementation and some simple movement models. We will get more into these analyses at a later date. If interested, I highly recommend consulting the packages very thourough documentation.