In this appendix, we will walk the user through the process of fitting and interpreting parameters and output when conducting an integrated step-selection analysis. Step-selection functions model transitions or “steps” connecting sequential locations (\(\Delta t\) units apart) in geographical space:
\[\begin{equation} u(s,t+\Delta t) | u(s', t) = \frac{w(X(s); \beta(\Delta t))\phi(s, s'; \gamma(\Delta t))}{\int_{\tilde{s}\in G}w(X(\tilde{s}, s'); \beta(\Delta t))\phi(\tilde{s},s'; \gamma(\Delta t))d\tilde{s}} \end{equation}\]
where \(u(s, t+\Delta t) | u(s', t)\) gives the conditional probability of finding the individual at location \(s\) at time \(t+\Delta t\) given it was at location \(s'\) at time \(t\), \(w(X(s); \beta(\Delta t))\) is referred to as a step-selection function, and \(\phi(s,s'; \gamma(\Delta t))\) is a selection-independent movement kernel that describes how the animal would move in homogeneous habitat or in the absence of habitat selection (i.e., when \(w(X(s); \beta(\Delta t))=\) a constant for all \(s\)).
Integrated step-selection analyses are implemented using the following steps:
The first two steps are accomplished in the Format and Generate Random Steps and Tentative Movement Parameters sections. The third step is accomplished in the Fit iSSF section. The last step is accomplished in the Interpreting Movement Parameters section.
We will begin by preparing our data to allow us to estimate parameters in the movement kernel, \(\phi(s,s';\gamma(\Delta t))\) and the habitat-selection function, \(w(X(s); \beta(\Delta t))\). This step will involve loading our location data and covariates, translating the locations into a series of regular steps (transitions between sequential locations spaced \(\Delta t\) units apart), sampling random available steps from a tentative statistical distribution formulated in terms of step-length distribution and turn-angle distribution, and associating the habitat covariates with the steps.
First, let’s read the fisher location and spatial covariate data. These data are available as objects in the amt
package (amt_fisher
and env_covar
), and we access them easily if the package is attached. The object amt_fisher
is already of class track_xyt
from the package amt
, which we have already seen in the RSF examples (see Supplementary Appendix A). The other three objects are all of class RasterLayer
, which can be created by reading most standard raster files with the function raster::raster()
. For ease, we have already loaded them, but your own data will likely need to be read in using functions from other packages.
fisher <- amt_fisher
landuse <- amt_fisher_covar$landuse
elevation <- amt_fisher_covar$elevation
popden <- amt_fisher_covar$popden
In this example, we will work with data from a single individual, “Lupe”, so let’s subset the fisher
data and sort the locations using the timestamp of the observations, t_
.
dat <- fisher %>%
filter(name == "Lupe") %>%
arrange(t_)
The raw land-use data we loaded here has numerical codes, which we will group into different landcover types using a custom-written function, reclass_landuse()
, below. We will apply this function when preparing the data.
reclass_landuse <- function(x) {
fct_collapse(factor(x),
forest = c("30","40","50","60", "70","80", "90","100"),
grass = c("120", "140"),
wet = c("160"))
}
Most step-selection analyses assume data are collected at regular time steps (for an exception, see Munden et al., 2020). Although your device may be programmed to collect data at regular time steps, there will typically be some small amount of variation in the time between locations. If we consider that variability to be negligible, we can round it off by resampling the track.
Check the actual sampling rate:
summarize_sampling_rate(dat)
## # A tibble: 1 x 9
## min q1 median mean q3 max sd n unit
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <int> <chr>
## 1 0.400 1.97 2.03 8.93 3.98 1080. 48.1 3003 min
We can see summary statistics for the difference between locations in minutes (see the final column for the unit). These tags were programmed to take a location every two minutes, but there is some variation in the time between locations, and missed locations lead to some gaps in the data. We will therefore need to resample the track when we format the data for this analysis.
We will take advantage of amt
’s piped (%>%
) workflow here to prepare data for model fitting. This workflow will help make our code both readable and compact. Most of the functions from amt
have sensible defaults for each argument to get you started. You can easily see these defaults by checking the help file for that function (e.g., ?random_steps
) or change them as you see fit.
In this workflow, we begin with Lupe’s location data (dat
), we resample the track to get regular time steps (track_resample()
), convert from points to steps (steps_by_burst()
), generate random available steps (random_steps()
), and attach the environmental covariates to both the observed and available steps (extract_covariates()
). Finally, we’ll use dplyr::mutate()
to scale and center our covariates and add the additional movement covariates that we will need to conduct an integrated step-selection analysis.
ssf_dat <- dat %>%
track_resample(rate = minutes(2), tolerance = seconds(20)) %>%
steps_by_burst() %>%
random_steps() %>%
extract_covariates(landuse, where = "both") %>%
extract_covariates(elevation, where = "both") %>%
extract_covariates(popden, where = "both") %>%
mutate(elevation_start = scale(elevation_start),
elevation_end = scale(elevation_end),
popden_start = scale(popden_start),
popden_end = scale(popden_end),
landuseC_start = reclass_landuse(landuse_start),
landuseC_end = reclass_landuse(landuse_end),
forest_start = landuseC_start == "forest",
forest_end = landuseC_end == "forest",
cos_ta_ = cos(ta_),
log_sl_ = log(sl_)
) %>%
filter(!is.na(ta_))
Before we proceed, we make a few notes about the code above:
random_steps()
function fits a tentative gamma distribution to the observed step lengths and a tentative von Mises distribution to the observed turn angles. It then generates random available points by sampling step-lengths and turn angles from these fitted distributions and combining these random steps with the starting locations associated with each observed movement step.where = "both"
to the extract_covariates()
function, we are able to extract covariates associated with both the starting and ending locations of each movement step. It is often sensible to use covariates associated with the starting location to model the effect of habitat on the movement kernel, \(\phi(s,s';\gamma(\Delta t)\), and covariates associated with the ending location to model the habitat-selection function, \(w(X(s); \beta(\Delta t))\) (Signer, Fieberg, & Avgar, 2019).Here’s what the resulting data set looks like now. Each row is a single step with all of its associated attributes:
burst_
is a unique identifier associated with a set of consecutive steps that are spaced \(\Delta t\) units apart.x1_
and y1_
are the coordinates associated with the starting location of the step.x2_
and y2_
are the coordinates associated with the ending location of the step.sl_
and ta_
are the step-length and turn angle associated with the step.t1_
and t2_
are the timestamps associated with the start and end of the step.dt_
is the time duration associated with the step.step_id_
is a unique identifier associated with each step’s observed and random locations.case_
is an indicator variable equal to TRUE
for observed steps and FALSE
for random steps.landuse_start
, elevation_start
, popden_start
) and (landuse_end
, elevation_end
, popden_end
) are covariate values for (landuse categories, elevation, and population density) associated with the start and end of the movement step, respectively.cos_ta_
and log_sl
capture the cosine of the turn angle and the log of the step-length. These covariates will allow us to adjust/refine the parameters of our tentative step-length and turn-angle distributions after fitting our integrated step-selection model.print(ssf_dat, n = 3, width = Inf)
## # A tibble: 16,175 x 25
## burst_ x1_ x2_ y1_ y2_ sl_ direction_p ta_
## * <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 6 1783660. 1783673. 2402636. 2402664. 31.2 2.90 -1.75
## 2 6 1783660. 1783659. 2402636. 2402633. 3.36 2.90 1.48
## 3 6 1783660. 1783654. 2402636. 2402631. 6.92 2.90 0.907
## t1_ t2_ dt_ step_id_ case_ landuse_start
## * <dttm> <dttm> <drtn> <int> <lgl> <dbl>
## 1 2010-12-17 09:05:14 2010-12-17 09:07:04 1.83 mins 1 FALSE 50
## 2 2010-12-17 09:05:14 2010-12-17 09:07:04 1.83 mins 1 FALSE 50
## 3 2010-12-17 09:05:14 2010-12-17 09:07:04 1.83 mins 1 FALSE 50
## landuse_end elevation_start[,1] elevation_end[,1] popden_start[,1]
## * <dbl> <dbl> <dbl> <dbl>
## 1 50 -0.275 -0.810 -0.754
## 2 50 -0.275 -0.230 -0.754
## 3 50 -0.275 -0.230 -0.754
## popden_end[,1] landuseC_start landuseC_end forest_start forest_end cos_ta_
## * <dbl> <fct> <fct> <lgl> <lgl> <dbl>
## 1 -0.763 forest forest TRUE TRUE -0.181
## 2 -0.763 forest forest TRUE TRUE 0.0862
## 3 -0.763 forest forest TRUE TRUE 0.616
## log_sl_
## * <dbl>
## 1 3.44
## 2 1.21
## 3 1.93
## # ... with 16,172 more rows
Let’s have a closer look at what is happening under the hood when applying the random_steps()
function. This function conveniently fits tentative step-length and turn-angle distributions and then samples from these distributions to generate random steps. The default arguments lead to random_steps()
fitting a gamma distribution for the step lengths and a von Mises distribution for the turn angles.
The tentative parameters in these statistical distributions (gamma, von Mises) are stored as attributes of the resulting object. We can view the parameters of the tentative step-length distribution using:
sl_distr(ssf_dat)
## $name
## [1] "gamma"
##
## $params
## $params$shape
## [1] 1.42
##
## $params$scale
## [1] 35.9
##
##
## attr(,"class")
## [1] "gamma_distr" "sl_distr" "amt_distr" "list"
Similarly, we can access the parameters of the tentative turn-angle distribution using:
ta_distr(ssf_dat)
## $name
## [1] "vonmises"
##
## $params
## $params$kappa
## [1] 0.484
##
## $params$mu
## [1] 0
##
##
## attr(,"class")
## [1] "vonmises_distr" "ta_distr" "amt_distr" "list"
After we fit the iSSF, we will see how we can update these distributions to adjust for the influence of habitat selection when estimating parameters of the movement kernel.
In this section, we will fit a basic iSSF, demonstrating how to interpret habitat effects and movement parameters. In the next section, we will fit more complex models and show how those models differ from the basic approach presented here.
We will fit a model with the same habitat covariates introduced in the main text. Suppose we hypothesize that habitat selection at the step scale depends on human population density (popden
), elevation (elevation
), and land-use category (landuseC
). Furthermore, suppose we hypothesize that, in the absence of habitat selection, step lengths and turn angles follow a constant distribution, i.e., the selection-free movement kernel does not depend on covariates. We can fit that model as follows:
m1 <- ssf_dat %>%
fit_issf(case_ ~ popden_end + elevation_end + landuseC_end +
sl_ + log_sl_ + cos_ta_ +
strata(step_id_), model = TRUE)
Notice that our model formula includes habitat covariates (popden
, elevation
, and landuseC
) and movement covariates (sl_
, log_sl_
, and cos_ta_
), as well as an indicator for the column that indicates the stratum, i.e., the grouping that matches each observed step to its corresponding available steps (step_id_
). The extra argument, model = TRUE
, is passed along to survival::clogit()
to tell it to store the original model data in the resulting object (m1
). We will need this extra information later when we use our fitted model to generate predictions.
We begin by exploring the coefficients in the fitted model using the summary()
function:
summary(m1)
## Call:
## coxph(formula = Surv(rep(1, 16175L), case_) ~ popden_end + elevation_end +
## landuseC_end + sl_ + log_sl_ + cos_ta_ + strata(step_id_),
## data = data, model = TRUE, method = "exact")
##
## n= 16175, number of events= 1155
##
## coef exp(coef) se(coef) z Pr(>|z|)
## popden_end -0.107806 0.897802 0.106930 -1.01 0.3134
## elevation_end 0.169540 1.184760 0.059918 2.83 0.0047 **
## landuseC_endgrass -1.195003 0.302703 0.510012 -2.34 0.0191 *
## landuseC_endwet -0.047474 0.953635 0.369706 -0.13 0.8978
## sl_ 0.000784 1.000784 0.001306 0.60 0.5483
## log_sl_ 0.023037 1.023305 0.057365 0.40 0.6880
## cos_ta_ 0.008637 1.008675 0.045452 0.19 0.8493
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## exp(coef) exp(-coef) lower .95 upper .95
## popden_end 0.898 1.114 0.728 1.107
## elevation_end 1.185 0.844 1.053 1.332
## landuseC_endgrass 0.303 3.304 0.111 0.823
## landuseC_endwet 0.954 1.049 0.462 1.968
## sl_ 1.001 0.999 0.998 1.003
## log_sl_ 1.023 0.977 0.914 1.145
## cos_ta_ 1.009 0.991 0.923 1.103
##
## Concordance= 0.522 (se = 0.01 )
## Likelihood ratio test= 18.3 on 7 df, p=0.01
## Wald test = 17.8 on 7 df, p=0.01
## Score (logrank) test = 18 on 7 df, p=0.01
For now, we will just focus on the habitat-selection coefficients, i.e., popden
, elevation
, and the two landuseC
coefficients. The interpretation of the parameters is very similar to the RSF interpretation (Supplementary Appendix A) but at a local scale with habitat availability determined by the selection-free movement kernel. We can exponentiate habitat-selection parameters in a fitted step-selection model to compare the relative rates of use of two locations that differ by 1 unit of the explanatory variable but are otherwise equivalent - i.e., they should be equally accessible and have identical values for all other explanatory variables.
Calculating the relative use of location \(s_1\) versus location \(s_2\) is fairly straightforward when \(s_1\) and \(s_2\) share the same values for all but one covariate (see e.g., Supplementary Appendix A). For more complex scenarios, we have implemented a function in amt
that will calculate the log-relative intensity (referred to as the log-Relative Selection Strength, or log-RSS, by Avgar, Lele, Keim, & Boyce, 2017) by leveraging the generic predict()
function available for many ordinary model classes (e.g., lm()
or glm()
) models in R
. Regardless of whether there are interactions or polynomial terms, the function will correctly calculate log-RSS for you. If you prefer to quantify relative-use (i.e., RSS), you can simply exponentiate the results.
Here we demonstrate the use oflog_rss()
with the simple example presented in equation 8 of the main text. Suppose we have two locations with the following covariates:
elevation_end = 3
, popden_end = 1.5
, landuseC_end = "wet"
elevation_end = 2
, popden_end = 1.5
, landuseC_end = "wet"
We want to calculate the log-RSS for a step ending in \(s_1\) versus a step ending in \(s_2\), assuming \(s_1\) and \(s_2\) are equally accessible to the animal.
The function log_rss()
expects the two locations to be formatted as separate data.frame
objects. Each data.frame
must include all covariates used to fit the model. Furthermore, factor
variables should have the same levels
as the original data.
# data.frame for s1
s1 <- data.frame(
elevation_end = 3,
popden_end = 1.5,
landuseC_end = factor("wet", levels = levels(ssf_dat$landuseC_end)),
sl_ = 100,
log_sl_ = log(100),
cos_ta_ = 1)
# data.frame for s2
s2 <- data.frame(
elevation_end = 2,
popden_end = 1.5,
landuseC_end = factor("wet", levels = levels(ssf_dat$landuseC_end)),
sl_ = 100,
log_sl_ = log(100),
cos_ta_ = 1)
Now that we have specified each location as a data.frame
, we can pass them along to log_rss()
for the calculation. The function will return an object of class log_rss
, which is also more generally a list
. The list
element "df"
contains a data.frame
which contains the log-RSS calculation and could easily be used to make a plot when considering relative selection strength across a range of environmental characteristics.
lr1 <- log_rss(m1, x1 = s1, x2 = s2)
lr1$df
## elevation_end_x1 popden_end_x1 landuseC_end_x1 sl_x1 log_sl_x1 cos_ta_x1
## 1 3 1.5 wet 100 4.61 1
## log_rss
## 1 0.17
As we will see below, this function is designed to be able to consider several locations as x1
, relative to a single location in x2
. Because the [["df"]]
element of the log_rss
object is designed for plotting, it includes the value of all the covariates at location x1
by appending _x1
to the end of the predictor names. The log_rss
object also contains the original x1
and x2
as elements, along with the model formula.
We can see that the log-RSS for this example is 0.17, which is equivalent to \(\beta_{elevation}\) since we have considered the simple case in which \(s_1\) and \(s_2\) differ by only a single unit of elevation. Now that we know how the function works, we can consider more complex cases.
Suppose we are interested in making a figure showing the relative selection strength our fisher using a range of elevations. The function log_rss()
can accept a data.frame
with many rows as as its first argument, x1
, to go with a single comparison location, x2
(i.e., x2
must only have 1 row).
# Make a new data.frame for s1
s1 <- data.frame(
elevation_end = seq(from = -2, to = 2, length.out = 200),
popden_end = 1.5,
landuseC_end = factor("wet", levels = levels(ssf_dat$landuseC_end)),
sl_ = 100,
log_sl_ = log(100),
cos_ta_ = 1)
# data.frame for s2
s2 <- data.frame(
elevation_end = 0, # mean of elev, since we scaled and centered
popden_end = 1.5,
landuseC_end = factor("wet", levels = levels(ssf_dat$landuseC_end)),
sl_ = 100,
log_sl_ = log(100),
cos_ta_ = 1)
# Calculate log-RSS
lr2 <- log_rss(m1, s1, s2)
# Plot using ggplot2
ggplot(lr2$df, aes(x = elevation_end_x1, y = log_rss)) +
geom_line(size = 1) +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray30") +
xlab("Elevation (SD)") +
ylab("log-RSS vs Mean Elevation") +
theme_bw()
The line depicts the log-RSS between each value of \(s_1\) relative to \(s_2\). Note that these locations differ only in their values of elevation
, and the log-RSS is equal to 0 when the two locations are identical (i.e., when elevation
= 0). If you prefer to plot RSS to log-RSS, simply exponentiate the y-axis (and move your horizontal line for no selection to \(exp(0) = 1\)).
# Plot using ggplot2
ggplot(lr2$df, aes(x = elevation_end_x1, y = exp(log_rss))) +
geom_line(size = 1) +
geom_hline(yintercept = exp(0), linetype = "dashed", color = "gray30") +
xlab("Elevation (SD)") +
ylab("RSS vs Mean Elevation") +
theme_bw()
The RSS plots above do not give any indication of model uncertainty. One method for constructing confidence intervals associated with the RSS predictions is via non-parametric bootstrapping (Fieberg, Vitense, & Johnson, 2020). When using the argument ci="boot"
to the log_rss()
function, the function repeatedly resamples strata with replacement, refits the model, and calculates log-RSS. Quantiles of the bootstrap log-RSS statistics are used to construct, by default, a 95% confidence interval around the original log-RSS calculation. The size of the confidence interval and number of bootstrap iterations can be controlled by the user by changing the ci_level
and n_boot
arguments. Warning: this code can take some time to execute.
# Calculate log-RSS
lr2_ci_boot <- log_rss(m1, s1, s2, ci = "boot", ci_level = 0.95, n_boot = 1000)
## Generating bootstrapped confidence intervals...
## Expect this to take some time...
## ... finished bootstrapping.
# Check the header of the data.frame
head(lr2_ci_boot$df)
## elevation_end_x1 popden_end_x1 landuseC_end_x1 sl_x1 log_sl_x1 cos_ta_x1
## 1 -2.00 1.5 wet 100 4.61 1
## 2 -1.98 1.5 wet 100 4.61 1
## 3 -1.96 1.5 wet 100 4.61 1
## 4 -1.94 1.5 wet 100 4.61 1
## 5 -1.92 1.5 wet 100 4.61 1
## 6 -1.90 1.5 wet 100 4.61 1
## log_rss lwr upr
## 1 -0.339 -0.578 -0.0755
## 2 -0.336 -0.572 -0.0747
## 3 -0.332 -0.566 -0.0739
## 4 -0.329 -0.560 -0.0732
## 5 -0.325 -0.555 -0.0724
## 6 -0.322 -0.549 -0.0717
You can see that the resulting lr2_ci_boot
data.frame now has columns lwr
and upr
, indicating the lower and upper bounds of the confidence interval. We can now add this confidence envelope to our plot.
ggplot(lr2_ci_boot$df, aes(x = elevation_end_x1, y = log_rss)) +
geom_ribbon(aes(ymin = lwr, ymax = upr),
linetype = "dashed",
color = "black", fill = "gray80", alpha = 0.5) +
geom_line(size = 1) +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray30") +
xlab("Elevation (SD)") +
ylab("log-RSS vs Mean Elevation") +
theme_bw()
To avoid the computational time required to calculate bootstrap confidence intervals, you may decide that using a large-sample-based confidence interval is appropriate. We can use the standard errors from our fitted model to estimate these confidence intervals based on a normal approximation to the sampling distribution of \(\hat{\beta}\).
lr2_ci_se <- log_rss(m1, s1, s2, ci = "se", ci_level = 0.95)
# Check the header of the data.frame
head(lr2_ci_se$df)
## elevation_end_x1 popden_end_x1 landuseC_end_x1 sl_x1 log_sl_x1 cos_ta_x1
## 1 -2.00 1.5 wet 100 4.61 1
## 2 -1.98 1.5 wet 100 4.61 1
## 3 -1.96 1.5 wet 100 4.61 1
## 4 -1.94 1.5 wet 100 4.61 1
## 5 -1.92 1.5 wet 100 4.61 1
## 6 -1.90 1.5 wet 100 4.61 1
## log_rss lwr upr
## 1 -0.339 -0.574 -0.104
## 2 -0.336 -0.568 -0.103
## 3 -0.332 -0.562 -0.102
## 4 -0.329 -0.557 -0.101
## 5 -0.325 -0.551 -0.100
## 6 -0.322 -0.545 -0.099
We see, in this case, that the two confidence intervals (bootstrap, normal-based) lead to similar conclusions.
# Add "type" label to both predictions data.frames
lr2_ci_boot$df$type <- "Bootstrap"
lr2_ci_se$df$type <- "SE"
# Combine two prediction data.frames
lr2_ci_both <- rbind(lr2_ci_boot$df, lr2_ci_se$df)
# Plot
ggplot(lr2_ci_both, aes(x = elevation_end_x1, y = log_rss,
group = factor(type),
fill = factor(type))) +
geom_ribbon(aes(ymin = lwr, ymax = upr),
linetype = "dashed", color = "black", alpha = 0.25) +
geom_line(size = 1, color = "black") +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray30") +
scale_fill_manual(name = "CI Type", breaks = c("Bootstrap", "SE"),
values = c("blue", "orange")) +
xlab("Elevation (SD)") +
ylab("log-RSS vs Mean Elevation") +
theme_bw()
Previously, we viewed tentative estimates of parameters describing the distribution of step-lengths and turn-angles. When estimating these parameters, however, we did not account or adjust for the influence of habitat selection. Including movement characteristics (sl_
, log_sl_
, and cos_ta_
) in our iSSF allows us to update our estimates of these parameters, which describe selection-free movement distributions. Mathematical formulas required to perform these updates are given in Supplementary Appendix C. Here, we will see how to update the distributions using the functions from amt
.
In our model, we included parameters for the step length (sl_
) and its natural logarithm (log_sl_
). We included these parameters to update the scale and shape parameters of our tentative gamma distribution, respectively. Different terms will need to be included in the iSSF, depending on the assumed step-length distribution (for details, see Supplementary Appendix C).
For our basic model – where the movement parameters are not interacting with any covariates – amt
has a simple function to update the step-length distribution.
# Update the distribution
updated_sl <- update_sl_distr(m1)
# Check the class
class(updated_sl)
## [1] "gamma_distr" "sl_distr" "amt_distr" "list"
# Print
print(updated_sl)
## $name
## [1] "gamma"
##
## $params
## $params$shape
## [1] 1.44
##
## $params$scale
## [1] 37
##
##
## attr(,"class")
## [1] "gamma_distr" "sl_distr" "amt_distr" "list"
We can see that the updated distribution object has classes that are designed for working with step-length/turn-angle distributions in amt
. In the most general sense, it is a list
, so you can access its named components either with $name
or [["name"]]
, as you would with any other list. But it is also of class amt_distr
, a general class for working with distributions in amt
; class sl_distr
, a more specific class for working with step-length distributions; and class gamma_distr
, an even more specific class for working with gamma distributions. We can see that the object has a name ("gamma"
) and the appropriate parameters for the gamma distribution – shape ($params$shape
) and scale ($params$scale
).
We can compare the tentative and updated distributions to see how accounting for habitat-selection changes our estimates of the selection-free step-length distribution.
# data.frame for plotting
plot_sl <- data.frame(x = rep(NA, 100))
# x-axis is sequence of possible step lengths
plot_sl$x <- seq(from = 0, to = 400, length.out = 100)
# y-axis is the probability density under the given gamma distribution
# For the tentative distribution
plot_sl$tentative <- dgamma(
x = plot_sl$x,
shape = m1$sl_$params$shape,
scale = m1$sl_$params$scale)
# For the updated distribution
plot_sl$updated <- dgamma(
x = plot_sl$x,
shape = updated_sl$params$shape,
scale = updated_sl$params$scale)
# Pivot from wide data to long data
plot_sl <- plot_sl %>%
pivot_longer(cols = -x)
# Plot
ggplot(plot_sl, aes(x = x, y = value, color = factor(name))) +
geom_line(size = 1) +
xlab("Step Length (m)") +
ylab("Probability Density") +
scale_color_manual(name = "Distribution",
breaks = c("tentative", "updated"),
values = c("blue", "orange")) +
theme_bw()
In this case, our estimates do not change very much. We might have anticipated this result because the coefficients associated with sl_
and log_sl_
were not statistically significant in our fitted model (m1
).
In our model, we included cosine of the turn angle (cos_ta_
) to update the concentration parameter of our tentative von Mises distribution. Again, see Supplementary Appendix C for details of updating turn-angle distributions. Here, we will see how to use amt
to update our tentative parameters and retrieve the selection-free turn-angle distribution.
We can use the update_ta_distr
function in amt
to update our turn-angle distribution:
# Update the distribution
updated_ta <- update_ta_distr(m1)
# Check the class
class(updated_ta)
## [1] "vonmises_distr" "ta_distr" "amt_distr" "list"
# Print
print(updated_ta)
## $name
## [1] "vonmises"
##
## $params
## $params$kappa
## [1] 0.493
##
## $params$mu
## [1] 0
##
##
## attr(,"class")
## [1] "vonmises_distr" "ta_distr" "amt_distr" "list"
Again, we see that the updated distribution object has several classes associated with it; it is a list
and also of class amt_distr
. This time it is also of class ta_distr
, indicating that it is a turn-angle distribution, and more specifically it is a vonmises_distr
. We can also access the appropriate parameters for the von Mises distribution – concentration ($params$kappa
) and mean ($params$mu
– often fixed to 0 under the assumption that animals are no more likely to turn left than right).
We can again compare the tentative and updated distributions to see how accounting for habitat-selection changes our estimates of the selection-free turn-angle distribution.
# data.frame for plotting
plot_ta <- data.frame(x = rep(NA, 100))
# x-axis is sequence of possible step lengths
plot_ta$x <- seq(from = -1 * pi, to = pi, length.out = 100)
# y-axis is the probability density under the given von Mises distribution
# For the tentative distribution
plot_ta$tentative <- circular::dvonmises(
x = plot_ta$x,
mu = m1$ta_$params$mu,
kappa = m1$ta_$params$kappa)
# For the updated distribution
plot_ta$updated <- circular::dvonmises(
x = plot_ta$x,
mu = updated_ta$params$mu,
kappa = updated_ta$params$kappa)
# Pivot from wide data to long data
plot_ta <- plot_ta %>%
pivot_longer(cols = -x)
# Plot
ggplot(plot_ta, aes(x = x, y = value, color = factor(name))) +
geom_line(size = 1) +
coord_cartesian(ylim = c(0, 0.25)) +
xlab("Relative Turn Angle (radians)") +
ylab("Probability Density") +
scale_x_continuous(breaks = c(-pi, -pi/2, 0, pi/2, pi),
labels = c(expression(-pi, -pi/2, 0, pi/2, pi))) +
scale_color_manual(name = "Distribution",
breaks = c("tentative", "updated"),
values = c("blue", "orange")) +
theme_bw()
Again, we see that our estimates do not change very much, which is not surprising given that the coefficient associated with cos_ta_
was not statistically significant in our fitted model (m1
).
Suppose we are interested in how likely our fisher is to take a 50 m step versus a 100 m step. We can propose two hypothetical locations, \(s_1\) and \(s_2\), which have exactly the same habitat attributes but differ in their distance from the fisher’s starting location.
We do not want to use log_rss()
for this purpose, because the fitted movement parameters in our iSSF represent updates to the tentative step-length distribution and not the distribution itself. Instead, we want to use the updated PDF of the step-length distribution directly. We can calculate the likelihood under the selection-free step-length distribution of taking a 50 m step and the likelihood of taking a 100 m step. Then we divide for an estimate of how much more likely a 50 m step is than a 100 m step.
like50 <- dgamma(50,
shape = updated_sl$params$shape,
scale = updated_sl$params$scale)
like100 <- dgamma(100,
shape = updated_sl$params$shape,
scale = updated_sl$params$scale)
like50/like100
## [1] 2.85
Thus, we conclude Lupe is nearly 3 times more likely to take the shorter (50 m versus 100 m) step.
Suppose, now, that we hypothesize that our fisher selects for intermediate elevations and avoids locations with extremely low or high elevations. We could use splines or polynomials to capture this non-linear effect. Here, we consider adding a quadratic term for elevation in our model.
m2 <- ssf_dat %>%
fit_issf(case_ ~ popden_end + elevation_end + I(elevation_end^2) +
landuseC_end + sl_ + log_sl_ + cos_ta_ +
strata(step_id_), model = TRUE)
In our basic model, we could interpret the fitted \(\beta_{elevation}\) as capturing the log relative selection strength (or log-RSS) associated with a one unit change in elevation. With the addition of the quadratic term, the log-RSS for one unit change in elevation is no longer constant. To illustrate this point, we compare the log-RSS for a one unit change in elevation for our two models (m1
has just a linear term for elevation; m2
has a quadratic term for elevation), using two different reference values for elevation
:
elevation_end = 3
, popden_end = 1.5
, landuseC_end = "wet"
elevation_end = 2
, popden_end = 1.5
, landuseC_end = "wet"
elevation_end = -1
, popden_end = 1.5
, landuseC_end = "wet"
elevation_end = -2
, popden_end = 1.5
, landuseC_end = "wet"
We can see that \(s_1\) and \(s_2\) are separated by 1 unit of elevation, and \(s_3\) and \(s_4\) are also separated by 1 unit of elevation. Let’s compare the two sets of locations.
# data.frame for s1
s1 <- data.frame(
elevation_end = 3, # mean of elev, since we scaled and centered
popden_end = 1.5,
landuseC_end = factor("wet", levels = levels(ssf_dat$landuseC_end)),
sl_ = 100,
log_sl_ = log(100),
cos_ta_ = 1)
# data.frame for s2
s2 <- data.frame(
elevation_end = 2, # mean of elev, since we scaled and centered
popden_end = 1.5,
landuseC_end = factor("wet", levels = levels(ssf_dat$landuseC_end)),
sl_ = 100,
log_sl_ = log(100),
cos_ta_ = 1)
# data.frame for s3
s3 <- data.frame(
elevation_end = -1, # mean of elev, since we scaled and centered
popden_end = 1.5,
landuseC_end = factor("wet", levels = levels(ssf_dat$landuseC_end)),
sl_ = 100,
log_sl_ = log(100),
cos_ta_ = 1)
# data.frame for s4
s4 <- data.frame(
elevation_end = -2, # mean of elev, since we scaled and centered
popden_end = 1.5,
landuseC_end = factor("wet", levels = levels(ssf_dat$landuseC_end)),
sl_ = 100,
log_sl_ = log(100),
cos_ta_ = 1)
# Calculate log-RSS under m1
lr_m1_s1s2 <- log_rss(m1, s1, s2)
lr_m1_s3s4 <- log_rss(m1, s3, s4)
# Calculate log-RSS under m2
lr_m2_s1s2 <- log_rss(m2, s1, s2)
lr_m2_s3s4 <- log_rss(m2, s3, s4)
# Compare
data.frame(
"model" = c("linear", "quadratic"),
"logRSS_s1_s2" = round(c(lr_m1_s1s2$df$log_rss, lr_m2_s1s2$df$log_rss), 3),
"logRSS_s3_s4" = round(c(lr_m1_s3s4$df$log_rss, lr_m2_s3s4$df$log_rss), 3))
## model logRSS_s1_s2 logRSS_s3_s4
## 1 linear 0.170 0.170
## 2 quadratic 0.047 0.269
Notice that the reference value for elevation_end
(x1
) does not impact the log-RSS associated with a 1 unit change when the model is linear. If the model includes the quadratic term, however, then the reference elevation
value is important.
If we plot a range of log-RSS predictions, we can see the slope slowly leveling off as the value of elevation
increases.
# Make a new data.frame for s1
s1 <- data.frame(
elevation_end = seq(from = -2, to = 2, length.out = 100),
popden_end = 1.5,
landuseC_end = factor("wet", levels = levels(ssf_dat$landuseC_end)),
sl_ = 100,
log_sl_ = log(100),
cos_ta_ = 1)
# data.frame for s2
s2 <- data.frame(
elevation_end = 0, # mean of elev, since we scaled and centered
popden_end = 1.5,
landuseC_end = factor("wet", levels = levels(ssf_dat$landuseC_end)),
sl_ = 100,
log_sl_ = log(100),
cos_ta_ = 1)
# Calculate log-RSS
lr_m2 <- log_rss(m2, s1, s2)
# Plot using ggplot2
ggplot(lr_m2$df, aes(x = elevation_end_x1, y = log_rss)) +
geom_line(size = 1) +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray30") +
xlab("Elevation (SD)") +
ylab("log-RSS vs Mean Elevation") +
theme_bw()
Our hypothesis that Lupe selects intermediate elevations doesn’t seem to have support here (at least over the range of elevations within Lupe’s MCP), and the non-linear effect that is estimated is rather muted. Nevertheless, quadratic terms are often useful for predictors that may represent conditions (see main text; Matthiopoulos et al., 2015).
In all of our previous models, we have assumed that a single step-length distribution and a single turn-angle distribution was sufficient for modeling selection-free movements. However, considering interactions between movement properties can provide valuable insight into an animal’s motivations for moving (e.g., Dickie, McNay, Sutherland, Cody, & Avgar, 2020). In this section, we will demonstrate how to fit and interpret models that include interactions between movement characteristics and categorical or continuous explanatory variables capturing environmental characteristics.
Suppose we hypothesize that our fisher moves differently depending on the land-use class that it is in at the start of the movement step. We can fit a model where all of the movement characteristics (sl_
, log_sl_
, an dcos_ta_
) interact with landuseC
as follows:
m3 <- ssf_dat %>%
fit_issf(case_ ~ popden_end + elevation_end +
landuseC_end +
sl_ + log_sl_ + cos_ta_ +
landuseC_start:(sl_ + log_sl_ + cos_ta_) +
strata(step_id_), model = TRUE)
Under this model, Lupe’s selection-free movement kernel, \(\phi(s,s';\gamma(\Delta t,t,X(s,t)))\), depends on the value of landuseC
at the start of the movement step and Lupe’s step-selection function, \(w(X(s,t+\Delta t);\beta(\Delta t))\), depends on the value oflanduseC
at the end of the movement step.
Let’s look at how the step-length distributions vary depending on the land-use class. Earlier, we saw simple amt
functions to update step-length and turn-angle distributions from a fitted model. However, these easy-to-use functions cannot accommodate more complex models that include interactions between habitat covariates and movement characteristics. Instead, we will have to use a separate set of functions that require manual specification of the appropriate \(\beta\)’s (see ?update_distr_man
).
Before we demonstrate these functions, let’s take a look at the parameters in the fitted model. For updating the step-length distribution, pay attention to those parameters that interact with sl_
or log_sl_
.
summary(m3)
## Call:
## coxph(formula = Surv(rep(1, 16175L), case_) ~ popden_end + elevation_end +
## landuseC_end + sl_ + log_sl_ + cos_ta_ + landuseC_start:(sl_ +
## log_sl_ + cos_ta_) + strata(step_id_), data = data, model = TRUE,
## method = "exact")
##
## n= 16175, number of events= 1155
##
## coef exp(coef) se(coef) z Pr(>|z|)
## popden_end -0.107154 0.898387 0.106971 -1.00 0.3165
## elevation_end 0.173319 1.189245 0.060050 2.89 0.0039 **
## landuseC_endgrass -0.851573 0.426743 0.519500 -1.64 0.1012
## landuseC_endwet -0.242487 0.784674 0.420745 -0.58 0.5644
## sl_ 0.000659 1.000660 0.001337 0.49 0.6220
## log_sl_ 0.034202 1.034794 0.059130 0.58 0.5630
## cos_ta_ 0.020188 1.020393 0.046737 0.43 0.6658
## sl_:landuseC_startgrass -0.047730 0.953391 0.045496 -1.05 0.2941
## sl_:landuseC_startwet 0.002498 1.002501 0.007282 0.34 0.7316
## log_sl_:landuseC_startgrass 3.935494 51.187450 2.998270 1.31 0.1893
## log_sl_:landuseC_startwet -0.346018 0.707500 0.271696 -1.27 0.2028
## cos_ta_:landuseC_startgrass 0.462415 1.587904 0.758162 0.61 0.5419
## cos_ta_:landuseC_startwet -0.295292 0.744314 0.213181 -1.39 0.1660
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## exp(coef) exp(-coef) lower .95 upper .95
## popden_end 0.898 1.1131 0.728 1.11
## elevation_end 1.189 0.8409 1.057 1.34
## landuseC_endgrass 0.427 2.3433 0.154 1.18
## landuseC_endwet 0.785 1.2744 0.344 1.79
## sl_ 1.001 0.9993 0.998 1.00
## log_sl_ 1.035 0.9664 0.922 1.16
## cos_ta_ 1.020 0.9800 0.931 1.12
## sl_:landuseC_startgrass 0.953 1.0489 0.872 1.04
## sl_:landuseC_startwet 1.003 0.9975 0.988 1.02
## log_sl_:landuseC_startgrass 51.187 0.0195 0.144 18251.37
## log_sl_:landuseC_startwet 0.708 1.4134 0.415 1.21
## cos_ta_:landuseC_startgrass 1.588 0.6298 0.359 7.02
## cos_ta_:landuseC_startwet 0.744 1.3435 0.490 1.13
##
## Concordance= 0.526 (se = 0.011 )
## Likelihood ratio test= 29.2 on 13 df, p=0.006
## Wald test = 23.1 on 13 df, p=0.04
## Score (logrank) test = 26.6 on 13 df, p=0.01
Recall that one of the landuseC
categories is treated as a reference category (in this case, "forest"
). We update the parameters of the step-length distribution for the reference category with the main effect parameters (i.e., sl_
and log_sl_
). For the other categories, we need to add the appropriate interaction terms to these parameters.
# Forest step-length distribution
forest_sl <- update_gamma(
dist = m3$sl_,
beta_sl = m3$model$coefficients["sl_"],
beta_log_sl = m3$model$coefficients["log_sl_"])
# Grass step-length distribution
grass_sl <- update_gamma(
dist = m3$sl_,
beta_sl = m3$model$coefficients["sl_"] +
m3$model$coefficients["sl_:landuseC_startgrass"],
beta_log_sl = m3$model$coefficients["log_sl_"] +
m3$model$coefficients["log_sl_:landuseC_startgrass"])
# Wet step-length distribution
wet_sl <- update_gamma(
dist = m3$sl_,
beta_sl = m3$model$coefficients["sl_"] +
m3$model$coefficients["sl_:landuseC_startwet"],
beta_log_sl = m3$model$coefficients["log_sl_"] +
m3$model$coefficients["log_sl_:landuseC_startwet"])
We can follow a similar process with the turn-angle distribution.
# Forest turn-angle distribution
forest_ta <- update_vonmises(
dist = m3$ta_, beta_cos_ta = m3$model$coefficients["cos_ta_"])
# Grass turn-angle distribution
grass_ta <- update_vonmises(
dist = m3$ta_,
beta_cos_ta = m3$model$coefficients["cos_ta_"] +
m3$model$coefficients["cos_ta_:landuseC_startgrass"])
# Wet turn-angle distribution
wet_ta <- update_vonmises(
dist = m3$ta_,
beta_cos_ta = m3$model$coefficients["cos_ta_"] +
m3$model$coefficients["cos_ta_:landuseC_startwet"])
Now, we can plot the original and updated distributions for each landuseC
.
# data.frame for plotting
plot_sl <- data.frame(x = rep(NA, 100))
# x-axis is sequence of possible step lengths
plot_sl$x <- seq(from = 0, to = 400, length.out = 100)
# y-axis is the probability density under the given gamma distribution
# Forest
plot_sl$forest <- dgamma(x = plot_sl$x,
shape = forest_sl$params$shape,
scale = forest_sl$params$scale)
# Grass
plot_sl$grass <- dgamma(x = plot_sl$x,
shape = grass_sl$params$shape,
scale = grass_sl$params$scale)
# Wet
plot_sl$wet <- dgamma(x = plot_sl$x,
shape = wet_sl$params$shape,
scale = wet_sl$params$scale)
# Pivot from wide to long data
plot_sl <- plot_sl %>%
pivot_longer(cols = -x)
# Plot
p1<-ggplot(plot_sl, aes(x = x, y = value, color = factor(name))) +
geom_line(size = 1) +
scale_color_manual(name = "Land-use",
breaks = c("forest", "grass", "wet"),
values = c("forestgreen", "wheat", "blue")) +
xlab("Step Length (m)") +
ylab("Probability Density") +
theme_bw()
#data.frame for plotting
plot_ta <- data.frame(x = rep(NA, 100))
# x-axis is sequence of possible step lengths
plot_ta$x <- seq(from = -pi, to = pi, length.out = 100)
# y-axis is the probability density under the given gamma distribution
# Forest
plot_ta$forest <- circular::dvonmises(x = plot_ta$x,
kappa = forest_ta$params$kappa,
mu = 0)
# Grass
plot_ta$grass <- circular::dvonmises(x = plot_ta$x,
kappa = grass_ta$params$kappa,
mu = 0)
# Wet
plot_ta$wet <- circular::dvonmises(x = plot_ta$x,
kappa = wet_ta$params$kappa,
mu = 0)
# Pivot from wide to long data
plot_ta <- plot_ta %>%
pivot_longer(cols = -x)
# Plot
p2 <- ggplot(plot_ta, aes(x = x, y = value, color = factor(name))) +
geom_line(size = 1) +
scale_color_manual(name = "Land-use",
breaks = c("forest", "grass", "wet"),
values = c("forestgreen", "wheat", "blue")) +
scale_x_continuous(breaks = c(-pi, -pi/2, 0, pi/2, pi),
labels = c(expression(-pi, -pi/2, 0, pi/2, pi))) +
xlab("Turn Angle (radians)") +
ylab("Probability Density") +
theme_bw()
combined <- p1 + p2 & theme(legend.position = "bottom")
combined + plot_layout(guides = "collect")
We see that Lupe tends to take larger, more directed steps when in grass
and slower and more tortuous steps in wet
habitat. One possibility is that Lupe tends to travel through grass
and forage in forest
and wet
habitats.
We can follow a similar process for continuous movement predictors. Suppose we want to know if Lupe moves differently depending on the elevation she finds herself in. We can fit the following model:
m4 <- ssf_dat %>%
fit_issf(case_ ~ popden_end + landuseC_end +
elevation_end +
sl_ + log_sl_ + cos_ta_ +
elevation_start:(sl_ + log_sl_ + cos_ta_) +
strata(step_id_), model = TRUE)
Again, let’s inspect the fitted model:
summary(m4)
## Call:
## coxph(formula = Surv(rep(1, 16175L), case_) ~ popden_end + landuseC_end +
## elevation_end + sl_ + log_sl_ + cos_ta_ + elevation_start:(sl_ +
## log_sl_ + cos_ta_) + strata(step_id_), data = data, model = TRUE,
## method = "exact")
##
## n= 16175, number of events= 1155
##
## coef exp(coef) se(coef) z Pr(>|z|)
## popden_end -0.109319 0.896444 0.107207 -1.02 0.308
## landuseC_endgrass -1.199118 0.301460 0.511767 -2.34 0.019 *
## landuseC_endwet -0.015773 0.984350 0.369561 -0.04 0.966
## elevation_end 0.157844 1.170984 0.062294 2.53 0.011 *
## sl_ 0.000765 1.000765 0.001309 0.58 0.559
## log_sl_ 0.025121 1.025439 0.057641 0.44 0.663
## cos_ta_ 0.012051 1.012124 0.045574 0.26 0.791
## sl_:elevation_start 0.000828 1.000828 0.001332 0.62 0.534
## log_sl_:elevation_start -0.066514 0.935650 0.058371 -1.14 0.254
## cos_ta_:elevation_start -0.097660 0.906958 0.045985 -2.12 0.034 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## exp(coef) exp(-coef) lower .95 upper .95
## popden_end 0.896 1.116 0.727 1.106
## landuseC_endgrass 0.301 3.317 0.111 0.822
## landuseC_endwet 0.984 1.016 0.477 2.031
## elevation_end 1.171 0.854 1.036 1.323
## sl_ 1.001 0.999 0.998 1.003
## log_sl_ 1.025 0.975 0.916 1.148
## cos_ta_ 1.012 0.988 0.926 1.107
## sl_:elevation_start 1.001 0.999 0.998 1.003
## log_sl_:elevation_start 0.936 1.069 0.835 1.049
## cos_ta_:elevation_start 0.907 1.103 0.829 0.992
##
## Concordance= 0.537 (se = 0.011 )
## Likelihood ratio test= 24.6 on 10 df, p=0.006
## Wald test = 23.8 on 10 df, p=0.008
## Score (logrank) test = 24.1 on 10 df, p=0.007
We can see that the interactions between the step-length parameters and elevation
are not detectable, so we shouldn’t expect much of an effect of elevation
on the step-length distribution. However, the relationship with the turn-angle parameter (cos_ta_
) is stronger, so we might see an effect there. Let’s update our step-length and turn-angle distributions and see how they look.
Because we have included interactions between movement characteristics and a continuous covariate, elevation
, our model allows for an infinite number of selection-free movement kernels – i.e., Lupe’s selection-free movement kernel will depend on the specific value of elevation
associated with her current location. For example, the scale parameter of the gamma distribution, \(\beta_{l}\), is a function of elevation:
\[\beta_l = \beta_{sl\_} + \beta_{elevation:sl\_} \times elevation\]
To help visualize the effect of elevation
on movement, it can be helpful to plot selection-free movement kernels for a range of elevation
values. Below, we pick three values of elevation
(-2, 0, 2) which we label (“low”, “medium”, and “high”), respectively. Remember that we scaled and centered our covariates so these values correspond to the mean and \(\pm 2\) standard deviations away from the mean. We’ll create an updated gamma step-length distribution for each of these three values of elevation
:
# Low elevation step-length distribution
low_sl <- update_gamma(
dist = m4$sl_,
beta_sl = m4$model$coefficients["sl_"] +
-2 * m4$model$coefficients["sl_:elevation_start"],
beta_log_sl = m4$model$coefficients["log_sl_"] +
-2 * m4$model$coefficients["log_sl_:elevation_start"])
# Medium elevation step-length distribution
med_sl <- update_gamma(
dist = m4$sl_,
beta_sl = m4$model$coefficients["sl_"] +
0 * m4$model$coefficients["sl_:elevation_start"],
beta_log_sl = m4$model$coefficients["log_sl_"] +
0 * m4$model$coefficients["log_sl_:elevation_start"])
# Wet step-length distribution
hi_sl <- update_gamma(
dist = m4$sl_,
beta_sl = m4$model$coefficients["sl_"] +
2 * m4$model$coefficients["sl_:elevation_start"],
beta_log_sl = m4$model$coefficients["log_sl_"] +
2 * m4$model$coefficients["log_sl_:elevation_start"])
Similarly, we calculate updated turn-angle distributions for each of these 3 values of elevation
.
# low turn-angle distribution
low_ta <- update_vonmises(
dist = m4$ta_,
beta_cos_ta = m4$model$coefficients["cos_ta_"] +
-2 * m4$model$coefficients["cos_ta_:elevation_start"])
# med turn-angle distribution
med_ta <- update_vonmises(
dist = m4$ta_,
beta_cos_ta = m4$model$coefficients["cos_ta_"] +
0 * m4$model$coefficients["cos_ta_:elevation_start"])
# hi turn-angle distribution
hi_ta <- update_vonmises(
dist = m4$ta_,
beta_cos_ta = m4$model$coefficients["cos_ta_"] +
2 * m4$model$coefficients["cos_ta_:elevation_start"])
Now, let’s plot the results:
#data.frame for plotting
plot_sl <- data.frame(x = rep(NA, 100))
# x-axis is sequence of possible step lengths
plot_sl$x <- seq(from = 0, to = 400, length.out = 100)
# y-axis is the probability density under the given gamma distribution
# Forest
plot_sl$low <- dgamma(x = plot_sl$x,
shape = low_sl$params$shape,
scale = low_sl$params$scale)
# Grass
plot_sl$medium <- dgamma(x = plot_sl$x,
shape = med_sl$params$shape,
scale = med_sl$params$scale)
# Wet
plot_sl$high <- dgamma(x = plot_sl$x,
shape = hi_sl$params$shape,
scale = hi_sl$params$scale)
# Pivot from wide to long data
plot_sl <- plot_sl %>%
pivot_longer(cols = -x)
# Plot
p1 <- ggplot(plot_sl, aes(x = x, y = value, color = factor(name))) +
geom_line(size = 1) +
scale_color_manual(name = "Elevation",
breaks = c("low", "medium", "high"),
values = c("navyblue", "gray50", "firebrick")) +
xlab("Step Length (m)") +
ylab("Probability Density") +
theme_bw()
#data.frame for plotting
plot_ta <- data.frame(x = rep(NA, 100))
# x-axis is sequence of possible step lengths
plot_ta$x <- seq(from = -pi, to = pi, length.out = 100)
# y-axis is the probability density under the given gamma distribution
# low
plot_ta$low <- circular::dvonmises(x = plot_ta$x,
kappa = low_ta$params$kappa,
mu = 0)
# med
plot_ta$medium <- circular::dvonmises(x = plot_ta$x,
kappa = med_ta$params$kappa,
mu = 0)
# hi
plot_ta$high <- circular::dvonmises(x = plot_ta$x,
kappa = hi_ta$params$kappa,
mu = 0)
# Pivot from wide to long data
plot_ta <- plot_ta %>%
pivot_longer(cols = -x)
# Plot
p2 <- ggplot(plot_ta, aes(x = x, y = value, color = factor(name))) +
geom_line(size = 1) +
scale_color_manual(name = "Elevation",
breaks = c("low", "medium", "high"),
values = c("navyblue", "gray50", "firebrick")) +
scale_x_continuous(breaks = c(-pi, -pi/2, 0, pi/2, pi),
labels = c(expression(-pi, -pi/2, 0, pi/2, pi))) +
xlab("Turn Angle (radians)") +
ylab("Probability Density") +
theme_bw()
combined <- p1 + p2 & theme(legend.position = "bottom")
combined + plot_layout(guides = "collect")
The step-length distributions are very similar, which we expected based on the non-significant coefficients associated with the interactions between elevation
and (sl_
and log_sl_
) in our fitted model. We see bigger differences in the turn-angle distributions, as we expected based on the statistical significance of the elevation
and cos_ta_
interactions. Our fisher has more directed movements at low elevation and less directed movements at high elevation.
We have demonstrated how we can fit a rich set of models that capture the effects of environmental predictors on animal movement and habitat selection using the amt
package in R.
sessioninfo::session_info()
## - Session info ---------------------------------------------------------------
## setting value
## version R version 4.0.2 (2020-06-22)
## os Windows 10 x64
## system x86_64, mingw32
## ui RTerm
## language (EN)
## collate English_United States.1252
## ctype English_United States.1252
## tz America/Chicago
## date 2021-02-24
##
## - Packages -------------------------------------------------------------------
## package * version date lib source
## amt * 0.1.4 2021-01-18 [1] CRAN (R 4.0.3)
## assertthat 0.2.1 2019-03-21 [1] CRAN (R 4.0.2)
## backports 1.1.7 2020-05-13 [1] CRAN (R 4.0.0)
## bibtex 0.4.2.2 2020-01-02 [1] CRAN (R 4.0.2)
## blob 1.2.1 2020-01-20 [1] CRAN (R 4.0.2)
## boot 1.3-25 2020-04-26 [2] CRAN (R 4.0.2)
## broom * 0.7.0 2020-07-09 [1] CRAN (R 4.0.2)
## cellranger 1.1.0 2016-07-27 [1] CRAN (R 4.0.2)
## checkmate 2.0.0 2020-02-06 [1] CRAN (R 4.0.2)
## circular 0.4-93 2017-06-29 [1] CRAN (R 4.0.2)
## cli 2.0.2 2020-02-28 [1] CRAN (R 4.0.2)
## codetools 0.2-16 2018-12-24 [2] CRAN (R 4.0.2)
## colorspace 1.4-1 2019-03-18 [1] CRAN (R 4.0.2)
## crayon 1.3.4 2017-09-16 [1] CRAN (R 4.0.2)
## DBI 1.1.0 2019-12-15 [1] CRAN (R 4.0.2)
## dbplyr 1.4.4 2020-05-27 [1] CRAN (R 4.0.2)
## digest 0.6.25 2020-02-23 [1] CRAN (R 4.0.2)
## dplyr * 1.0.1 2020-07-31 [1] CRAN (R 4.0.2)
## ellipsis 0.3.1 2020-05-15 [1] CRAN (R 4.0.2)
## evaluate 0.14 2019-05-28 [1] CRAN (R 4.0.2)
## fansi 0.4.1 2020-01-08 [1] CRAN (R 4.0.2)
## farver 2.0.3 2020-01-16 [1] CRAN (R 4.0.2)
## fitdistrplus 1.1-1 2020-05-19 [1] CRAN (R 4.0.2)
## forcats * 0.5.0 2020-03-01 [1] CRAN (R 4.0.2)
## fs 1.5.0 2020-07-31 [1] CRAN (R 4.0.2)
## gbRd 0.4-11 2012-10-01 [1] CRAN (R 4.0.0)
## generics 0.0.2 2018-11-29 [1] CRAN (R 4.0.2)
## ggplot2 * 3.3.2 2020-06-19 [1] CRAN (R 4.0.2)
## glue 1.4.1 2020-05-13 [1] CRAN (R 4.0.2)
## gtable 0.3.0 2019-03-25 [1] CRAN (R 4.0.2)
## haven 2.3.1 2020-06-01 [1] CRAN (R 4.0.2)
## hms 0.5.3 2020-01-08 [1] CRAN (R 4.0.2)
## htmltools 0.5.0 2020-06-16 [1] CRAN (R 4.0.2)
## httr 1.4.2 2020-07-20 [1] CRAN (R 4.0.2)
## jsonlite 1.7.0 2020-06-25 [1] CRAN (R 4.0.2)
## knitr * 1.30 2020-09-22 [1] CRAN (R 4.0.3)
## labeling 0.3 2014-08-23 [1] CRAN (R 4.0.0)
## lattice 0.20-41 2020-04-02 [2] CRAN (R 4.0.2)
## lifecycle 0.2.0 2020-03-06 [1] CRAN (R 4.0.2)
## lubridate 1.7.9 2020-06-08 [1] CRAN (R 4.0.2)
## magrittr 1.5 2014-11-22 [1] CRAN (R 4.0.2)
## MASS 7.3-51.6 2020-04-26 [2] CRAN (R 4.0.2)
## Matrix 1.2-18 2019-11-27 [1] CRAN (R 4.0.2)
## modelr 0.1.8 2020-05-19 [1] CRAN (R 4.0.2)
## munsell 0.5.0 2018-06-12 [1] CRAN (R 4.0.2)
## mvtnorm 1.1-1 2020-06-09 [1] CRAN (R 4.0.0)
## patchwork * 1.0.1 2020-06-22 [1] CRAN (R 4.0.2)
## pillar 1.4.6 2020-07-10 [1] CRAN (R 4.0.2)
## pkgconfig 2.0.3 2019-09-22 [1] CRAN (R 4.0.2)
## purrr * 0.3.4 2020-04-17 [1] CRAN (R 4.0.2)
## R6 2.4.1 2019-11-12 [1] CRAN (R 4.0.2)
## raster 3.3-13 2020-07-17 [1] CRAN (R 4.0.2)
## Rcpp 1.0.5 2020-07-06 [1] CRAN (R 4.0.2)
## Rdpack 1.0.0 2020-07-01 [1] CRAN (R 4.0.2)
## readr * 1.3.1 2018-12-21 [1] CRAN (R 4.0.2)
## readxl 1.3.1 2019-03-13 [1] CRAN (R 4.0.2)
## reprex 0.3.0 2019-05-16 [1] CRAN (R 4.0.2)
## rlang 0.4.7 2020-07-09 [1] CRAN (R 4.0.2)
## rmarkdown 2.3 2020-06-18 [1] CRAN (R 4.0.2)
## rstudioapi 0.11 2020-02-07 [1] CRAN (R 4.0.2)
## rvest 0.3.6 2020-07-25 [1] CRAN (R 4.0.2)
## scales 1.1.1 2020-05-11 [1] CRAN (R 4.0.2)
## sessioninfo 1.1.1 2018-11-05 [1] CRAN (R 4.0.2)
## sp 1.4-2 2020-05-20 [1] CRAN (R 4.0.2)
## stringi 1.4.6 2020-02-17 [1] CRAN (R 4.0.0)
## stringr * 1.4.0 2019-02-10 [1] CRAN (R 4.0.2)
## survival 3.1-12 2020-04-10 [2] CRAN (R 4.0.2)
## tibble * 3.0.3 2020-07-10 [1] CRAN (R 4.0.2)
## tidyr * 1.1.1 2020-07-31 [1] CRAN (R 4.0.2)
## tidyselect 1.1.0 2020-05-11 [1] CRAN (R 4.0.2)
## tidyverse * 1.3.0 2019-11-21 [1] CRAN (R 4.0.2)
## utf8 1.1.4 2018-05-24 [1] CRAN (R 4.0.2)
## vctrs 0.3.2 2020-07-15 [1] CRAN (R 4.0.2)
## withr 2.2.0 2020-04-20 [1] CRAN (R 4.0.2)
## xfun 0.16 2020-07-24 [1] CRAN (R 4.0.2)
## xml2 1.3.2 2020-04-23 [1] CRAN (R 4.0.2)
## yaml 2.2.1 2020-02-01 [1] CRAN (R 4.0.2)
##
## [1] C:/Users/jfieberg/Documents/R/win-library/4.0
## [2] C:/Program Files/R/R-4.0.2/library
Avgar, T., Lele, S. R., Keim, J. L., & Boyce, M. S. (2017). Relative selection strength: Quantifying effect size in habitat-and step-selection inference. Ecology and Evolution, 7(14), 5322–5330.
Avgar, T., Potts, J. R., Lewis, M. A., & Boyce, M. S. (2016). Integrated step selection analysis: Bridging the gap between resource selection and animal movement. Methods in Ecology and Evolution, 7(5), 619–630.
Dickie, M., McNay, S. R., Sutherland, G. D., Cody, M., & Avgar, T. (2020). Corridors or risk? Movement along, and use of, linear features varies predictably among large mammal predator and prey species. Journal of Animal Ecology, 89(2), 623–634.
Fieberg, J. R., Vitense, K., & Johnson, D. H. (2020). Resampling-based methods for biologists. PeerJ, 8, e9089.
Matthiopoulos, J., Fieberg, J., Aarts, G., Beyer, H. L., Morales, J. M., & Haydon, D. T. (2015). Establishing the link between habitat selection and animal population dynamics. Ecological Monographs, 85(3), 413–436. doi:10.1890/14-2244.1
Munden, R., Borger, L., Wilson, R. P., Redcliffe, J., Brown, R., Garel, M., & Potts, J. R. (2020). Why did the animal turn? Time-varying step selection analysis for inference between observed turning points in high frequency data. bioRxiv. doi:10.1101/2020.08.13.249151
Signer, J., Fieberg, J., & Avgar, T. (2019). Animal movement tools (amt): R package for managing tracking data and conducting habitat selection analyses. Ecology and Evolution, (July 2018), 880–890. doi:10.1002/ece3.4823