We applied Hidden Markov Models (HMMs; McClintock et al., 2020) to analyze the movement patterns of black capuchins in plantation landscapes and in INP. This approach allowed us to identify and compare discrete movement states between these contrasting landscapes and between contrasting environments within plantation landscapes (pine stands vs forest fragments). Within plantation landscapes we evaluated if movement states showed seasonal variation between native forest patches and pine stands associated to the seasonal bark-stripping behavior of capuchins (Mikich and Liebsch, 2014), expecting shorter steps and more convoluted movement patterns associated with this activity. The dataset consisted of GPS locations recorded at 30-minute intervals throughout the activity period (6:30 AM–8:30 PM) for four groups in plantations and four groups in INP. Data spanned multiple years (2008, 2009, 2010, 2022, and 2023) covering the months of March to December, with June excluded due to inconsistent data collection. We classified movement, based on step length (distance between consecutive GPS locations) and turning angle (change in direction), into two behavioral states: Resident (short distances, sharp turns) and Transit (long distances, small turns). Step length represents movement magnitude (speed), while turning angle indicates directional persistence (Langrock et al., 2012). Resident behavior, linked to resting, social interactions, or slow foraging, had an initial mean step length of 20 m (SD = 20 m) and turning angle concentration (k) of 0.1. Transit behavior, associated with direct travel between resources, had a mean step length of 300 m (SD = 300 m) and k = 0.5. We employed the Viterbi algorithm to decode the most probable sequence of behavioral states for each individual (Joo et al., 2013). We evaluated model convergence by verifying stable parameter estimates across multiple runs with varying initial values, ensuring that the global maximum of the likelihood function was identified (McClintock and Michelot, 2018). These analyses were conducted using the R package ‘momentuHMM’ (McClintock and Michelot, 2018).
library(sf)
library(sp)
library(amt)
library(tidyverse)
library(momentuHMM)
library(ggplot2)
library(conflicted)
library(patchwork)
library(flextable)
library(gridExtra)
library(grid)
library(showtext)
font_add("Calibri", "C:/Windows/Fonts/calibri.ttf")
font_add("Calibri Bold", "C:/Windows/Fonts/calibrib.ttf")
font_add("Calibri Italic", "C:/Windows/Fonts/calibrii.ttf")
conflict_prefer("filter", "dplyr")
conflict_prefer("select", "dplyr")
showtext_auto()hmm <- bind_rows(lapply(c('tilo_hmm.RDS', 'toxo_hmm.RDS', 'jasy_hmm.RDS', 'col_palomo.RDS'), readRDS))Extracting the information from Timber company Shape file
shp <-
st_read("UNION.shp") %>%
mutate(tipouso_grouped = ifelse(
tipouso == "BNSM",
"BNSM",
"plantations")) %>%
mutate(sensibilidad = case_when(
tipouso == "PITA" & apl >= 2012 & apl <= 2017 ~ "sensible",
TRUE ~ "insensible"))
crs_shp <- st_crs(shp)
shp2 <-
st_read("UNION.shp") %>%
mutate(tipouso_grouped = ifelse(
tipouso == "BNSM",
"BNSM",
"plantations")) %>%
mutate(ages =case_when(
tipouso_grouped == "plantations" & apl >= 2018 ~ "young",
tipouso_grouped == "plantations" & apl >= 2013 & apl <= 2017 ~ "medium",
tipouso_grouped == "plantations" & apl <= 2012 ~ "old",
TRUE ~ NA_character_
))
shp <- shp %>%
mutate(tipouso_grouped = ifelse(tipouso == "BNSM", "BNSM", "plantations"))These will allow me to calculate minimum distantes to different landscape features
shp_bnsm <- shp %>% filter(tipouso == "BNSM")
shp_medium <- shp2 %>% filter(ages == 'medium')
shp_old<- shp2 %>% filter(ages == 'old')
shp_young <- shp2 %>% filter(ages == 'young')hmm <-
bind_rows(lapply(c(
'tilo_hmm.RDS',
'toxo_hmm.RDS',
'jasy_hmm.RDS',
'col_palomo.RDS'),
readRDS)) %>%
rename(lon = x_, lat = y_, t=t_) %>%
select(lat, lon, t, id)
dist_sensible <- hmm_sf2[,"dist_sensible"] %>%
st_drop_geometry() %>%
pull(dist_sensible) %>%
as.numeric()
dist_bnsm <- hmm_sf2[,"dist_bnsm"] %>%
st_drop_geometry() %>%
pull(dist_bnsm) %>%
as.numeric()
dist_old <- hmm_sf2[,"dist_old"] %>%
st_drop_geometry() %>%
pull(dist_old) %>%
as.numeric()
dist_young <- hmm_sf2[,"dist_young"] %>%
st_drop_geometry() %>%
pull(dist_young) %>%
as.numeric()
hmm <- hmm %>%
mutate(
X = X,
Y = Y,
dist_bnsm = dist_bnsm,
dist_sensible = dist_sensible,
dist_young = dist_young,
dist_old = dist_old)hmm_sf <- hmm %>%
st_as_sf(coords = c('lon', 'lat'),
crs = 4326)
if (st_crs(hmm_sf) != st_crs(shp)) {
shp <- st_transform(shp, st_crs(hmm_sf))
}
hmm_sf_joined <- st_join(hmm_sf, shp[, c("tipouso", "apl")])
hmm_sf_joined <- hmm_sf_joined %>%
mutate(tipouso_grouped = ifelse(tipouso == "BNSM", "BNSM", "plantations"))
hmm_sf_joined <- hmm_sf_joined %>%
mutate(
tipouso_grouped = case_when(
tipouso %in% c("BNSM", "FCAM") ~ "BNSM",
TRUE ~ "plantations"
),
apl_grouped = case_when(
tipouso_grouped == "plantations" & apl >= 2017 ~ "young",
tipouso_grouped == "plantations" & apl >= 2013 & apl <= 2016 ~ "medium",
tipouso_grouped == "plantations" & apl <= 2012 ~ "old",
TRUE ~ NA_character_
))
hmm <- hmm_sf_joined %>%
st_drop_geometry() %>%
bind_cols(st_coordinates(hmm_sf_joined) %>%
as.data.frame() %>%
rename(lon = X, lat = Y)) Explorative plots
hmm_sf <- hmm %>%
st_as_sf(coords = c('lon', 'lat'),
crs = 4326)
datos_filtrados <- hmm_sf %>%
mutate(
dist_sensible = as.numeric(dist_sensible),
dist_bnsm = as.numeric(dist_bnsm)) %>%
pivot_longer(
cols = c(dist_sensible, dist_bnsm),
names_to = "metric",
values_to = "distancia")Constrasting distances to plantations and native forest fragments
p_col<-
datos_filtrados %>% filter (id == 'col', metric == 'dist_sensible') %>%
ggplot() +
geom_sf(aes(color = distancia),
size = 3) +
scale_color_gradientn(
colors = c("gold", "darkorange", "orangered", "red1", "firebrick3", "darkred"),
name = "Distance (m)") +
labs(
x = "Longitude",
y = "Latitud") +
theme_minimal() +
theme(
panel.background = element_rect(fill = "white", color = NA),
plot.background = element_rect(fill = "white", color = NA),
legend.position = "none",
text = element_text(family = "Calibri", size = 34),
axis.text.x = element_text(angle = 45, hjust = 1),
plot.margin = margin(0, 0, 0, 0))
p_tilo<-
datos_filtrados %>% filter (id == 'tilo', metric == 'dist_sensible') %>%
ggplot() +
geom_sf(aes(color = distancia),
size = 3) +
scale_color_gradientn(
colors = c("gold", "darkorange", "orangered", "red1", "firebrick3", "darkred"),
name = "Distance (m)") +
labs(
x = "Longitude",
y = " ") +
theme_minimal() +
theme(
panel.background = element_rect(fill = "white", color = NA),
plot.background = element_rect(fill = "white", color = NA),
legend.position = "right",
text = element_text(family = "Calibri", size = 34),
axis.text.x = element_text(angle = 45, hjust = 1),
plot.margin = margin(0, 0, 0, 0))
p_jasy<-
datos_filtrados %>% filter (id == 'jasy', metric == 'dist_sensible') %>%
ggplot() +
geom_sf(aes(color = distancia),
size = 3) +
scale_color_gradientn(
colors = c("gold", "darkorange", "orangered", "red1", "firebrick3", "darkred"),
name = "Distance (m)") +
labs(
x = "Longitude",
y = " ") +
theme_minimal() +
theme(
panel.background = element_rect(fill = "white", color = NA),
plot.background = element_rect(fill = "white", color = NA),
legend.position = "none",
text = element_text(family = "Calibri", size = 34),
axis.text.x = element_text(angle = 45, hjust = 1),
plot.margin = margin(0, 0, 0, 0))
p_toxo<-
datos_filtrados %>% filter (id == 'toxo', metric == 'dist_sensible') %>%
ggplot() +
geom_sf(aes(color = distancia),
size = 3) +
scale_color_gradientn(
colors = c("gold", "darkorange", "orangered", "red1", "firebrick3", "darkred"),
name = "Distance (m)") +
labs(
x = "Longitude",
y = "Latitud") +
theme_minimal() +
theme(
panel.background = element_rect(fill = "white", color = NA),
plot.background = element_rect(fill = "white", color = NA),
legend.position = "none",
text = element_text(family = "Calibri", size = 34),
axis.text.x = element_text(angle = 45, hjust = 1),
plot.margin = margin(0, 0, 0, 0))Native Forest Fragments
p_col_nf<-
datos_filtrados %>% filter (id == 'col', metric == 'dist_bnsm') %>%
ggplot() +
geom_sf(aes(color = distancia),
size = 3) +
scale_color_gradientn(
colors = c("darkolivegreen","darkolivegreen3", "darkolivegreen2", "darkolivegreen1", "palegreen1"),
name = "Distance (m)"
) +
labs(
x = "Longitude",
y = "Latitud") +
theme_minimal() +
theme(
panel.background = element_rect(fill = "white", color = NA),
plot.background = element_rect(fill = "white", color = NA),
legend.position = "none",
text = element_text(family = "Calibri", size = 34),
axis.text.x = element_text(angle = 45, hjust = 1),
plot.margin = margin(0, 0, 0, 0))
p_tilo_nf<-
datos_filtrados %>% filter (id == 'tilo', metric == 'dist_bnsm') %>%
ggplot() +
geom_sf(aes(color = distancia),
size =3) +
scale_color_gradientn(
colors = c("darkolivegreen","darkolivegreen3", "darkolivegreen2", "darkolivegreen1", "palegreen1"),
name = "Distance (m)"
) +
labs(
x = "Longitude",
y = " ") +
theme_minimal() +
theme(
panel.background = element_rect(fill = "white", color = NA),
plot.background = element_rect(fill = "white", color = NA),
legend.position = "right",
text = element_text(family = "Calibri", size = 34),
axis.text.x = element_text(angle = 45, hjust = 1),
plot.margin = margin(0, 0, 0, 0))
p_jasy_nf<-
datos_filtrados %>% filter (id == 'jasy', metric == 'dist_bnsm') %>%
ggplot() +
geom_sf(aes(color = distancia),
size = 3) +
scale_color_gradientn(
colors = c("darkolivegreen","darkolivegreen3", "darkolivegreen2", "darkolivegreen1", "palegreen1"),
name = "Distance (m)"
) +
labs(
x = "Longitude",
y = " ") +
theme_minimal() +
theme(
panel.background = element_rect(fill = "white", color = NA),
plot.background = element_rect(fill = "white", color = NA),
legend.position = "none",
text = element_text(family = "Calibri", size = 34),
axis.text.x = element_text(angle = 45, hjust = 1),
plot.margin = margin(0, 3, 0, 0))
p_toxo_nf<-
datos_filtrados %>% filter (id == 'toxo', metric == 'dist_bnsm') %>%
ggplot() +
geom_sf(aes(color = distancia),
size = 3) +
scale_color_gradientn(
colors = c("darkolivegreen","darkolivegreen3", "darkolivegreen2", "darkolivegreen1", "palegreen1"),
name = "Distance(m)"
) +
labs(
x = "Longitude",
y = "Latitud") +
theme_minimal() +
theme(
panel.background = element_rect(fill = "white", color = NA),
plot.background = element_rect(fill = "white", color = NA),
legend.position = "none",
text = element_text(family = "Calibri", size = 34),
axis.text.x = element_text(angle = 45, hjust = 1),
plot.margin = margin(0, 0, 0, 0))p1 <- ((p_col_nf | p_tilo_nf) / (p_toxo_nf | p_jasy_nf)) +
plot_layout(guides = "collect") &
theme(plot.background = element_rect(fill = "white", color = NA))p2 <- ((p_col | p_tilo) / (p_toxo | p_jasy)) +
plot_layout(guides = "collect") &
theme(plot.background = element_rect(fill = "white", color = NA))p1 + plot_annotation(title = "Distances to Native Forest")& theme(
plot.title = element_text(size = 40, face = "bold"))p2 +plot_annotation(
title = "Distance to Medium-aged Pine Stands") & theme(
plot.title = element_text(size = 40, face = "bold"))There are some zeros, so I continue with the ZeroMass included.
I had to remove NAs, likely due to some issues when distances were calculated from the shape file. It was about 30 points, nothing serious. I also scaled both distances because many Warnings were triggered due to the unusual distribution of distances… many zeros and some values of 1500 or more. I keep the columns with the variables in their original scale.
stepMean0_2 <- c(m1 = 20, m2 = 300)
stepSD0_2 <- c(sd1 = 20, sd2 = 300)
zeromass_2 <- c(zm1 = 0.05, zm2 =0.05)
angleCon0_2 <- c(rho1 = 0.1, rho2 = 0.5)
stateNames_2 <- c("resident","transit")
dist_2 <- list(step = "gamma", angle = "wrpcauchy")
Par0_2 <- list(step=c(stepMean0_2, stepSD0_2), angle = c(angleCon0_2))Full-year models include data from March to December, that is low and high bark-stripping seasons. ‘Sens’ stands for medium-aged pine stands
# Model 1: Null model
hmm2_null <-
fitHMM(
data = hmm_prep,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ 1)
# Model 2: Medium-aged distance
hmm2_sens <-
fitHMM(
data = hmm_prep,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_sensible)
# Model 4: Old distance
hmm2_old <-
fitHMM(
data = hmm_prep,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_old)
# Model 5: Young distance
hmm2_young <-
fitHMM(
data = hmm_prep,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_young)
# Model 6: Medium-aged + BN+SM distance
hmm2_sens_bnsm <-
fitHMM(
data = hmm_prep,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_sensible + dist_bnsm)
# Model 7: Medium-aged + Old distance
hmm2_old_sens <-
fitHMM(
data = hmm_prep,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_sensible + dist_old)
# Model 8: Medium-aged + Young distance
hmm2_young_sens <-
fitHMM(
data = hmm_prep,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_sensible + dist_young)
# Model 9: Full model
hmm2_full <-
fitHMM(
data = hmm_prep,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_sensible + dist_bnsm + dist_old + dist_young)Failed fitting of the model including Distance to young and medium-aged stands, optimization algorithm nlm supplied a non-finite value. Model specification leads to numerical instability, possibly due to collinearity between covariates. Consequently, this model is not feasible to run as currently defined, and thus excluded.
aic_year <- AIC(hmm2_null, hmm2_sens, hmm2_bnsm, hmm2_old, hmm2_young,
hmm2_sens_bnsm, hmm2_old_sens, hmm2_full)
aic_year_df <- as.data.frame(aic_year) %>%
mutate(Model = c("Forest Fragment", "Young", "Medium", "Null",
"Old", "Old Pine + Medium", "Medium + Forest Fragment", "Full Model"),
deltaAIC = AIC - min(AIC)) %>%
arrange(AIC) %>%
select(Model, AIC, deltaAIC)
aic_year_table <- flextable(aic_year_df) %>%
colformat_num(j = c("AIC", "deltaAIC"), digits = 3) %>%
theme_tron_legacy() %>%
autofit()
aic_year_tableModel | AIC | deltaAIC |
|---|---|---|
Forest Fragment | 81,785.64 | 0.0000 |
Young | 81,935.88 | 150.2406 |
Medium | 81,936.67 | 151.0253 |
Null | 81,937.87 | 152.2320 |
Old | 81,938.75 | 153.1074 |
Old Pine + Medium | 82,294.64 | 508.9998 |
Medium + Forest Fragment | 83,084.88 | 1,299.2429 |
Full Model | 83,096.46 | 1,310.8182 |
## hmm_2states
## 1 2
## 0.74465 0.25535
## Value of the maximum log-likelihood: -40881.82
##
##
## step parameters:
## ----------------
## resident transit
## mean 76.10556 229.8218
## sd 55.17141 148.0904
##
## angle parameters:
## -----------------
## resident transit
## mean 0.0000000 0.0000000
## concentration 0.1464638 0.5154257
##
## Regression coeffs for the transition probabilities:
## ---------------------------------------------------
## 1 -> 2 2 -> 1
## (Intercept) -2.23279017 -0.47837146
## dist_bnsm 0.01445949 -0.01344355
##
## Transition probability matrix (based on mean covariate values):
## ---------------------------------------------------------------
## resident transit
## resident 0.8687853 0.1312147
## transit 0.3107032 0.6892968
##
## Initial distribution:
## ---------------------
## resident transit
## 0.8068209 0.1931791
Only fitting HMM for data collected during low bark-stripping month, see Figure B.2 panel A for more details
hmm2_low_null <-
fitHMM(
data = low,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ 1)
hmm2_low_full <-
fitHMM(
data = low,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_bnsm + dist_sensible + dist_young + dist_old)
hmm2_low_sens <-
fitHMM(
data = low,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_sensible)
hmm2_low_bnsm <-
fitHMM(
data = low,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_bnsm)
hmm2_low_sens_bnsm <-
fitHMM(
data = low,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_sensible+ dist_bnsm)
hmm2_low_young <-
fitHMM(
data = low,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_young)
hmm2_low_old<-
fitHMM(
data = low,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_old)
hmm2_low_young_sens_bnsm <-
fitHMM(
data = low,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_sensible + dist_young + dist_bnsm)
hmm2_low_old_sens_bnsm<-
fitHMM(
data = low,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_sensible + dist_old + dist_bnsm)Failed fitting of the model including Distance to native forest, old and medium-aged stands, optimization algorithm nlm supplied a non-finite value. Model specification leads to numerical instability, possibly due to collinearity between covariates. Consequently, this model is not feasible to run as currently defined, and thus excluded.
aic_low <-AIC(hmm2_low_null, hmm2_low_sens, hmm2_low_sens_bnsm, hmm2_low_old ,hmm2_low_young, hmm2_low_young_sens_bnsm, hmm2_low_old_sens_bnsm)
aic_low_df <- as.data.frame(aic_low) %>%
mutate(Model = c("Medium + Forest Fragment", "Medium + Young + Forest Fragment", "Medium", "Null",
"Young","Old", "Old + Medium + Forest Fragment"),
deltaAIC = AIC - min(AIC)) %>%
arrange(AIC) %>%
select(Model, AIC, deltaAIC)
aic_low_table <- flextable(aic_low_df) %>%
colformat_num(j = c("AIC", "deltaAIC"), digits = 3) %>%
theme_tron_legacy() %>%
autofit()
aic_low_tableModel | AIC | deltaAIC |
|---|---|---|
Medium + Forest Fragment | 28,484.91 | 0.000000 |
Medium + Young + Forest Fragment | 28,489.30 | 4.387503 |
Medium | 28,492.08 | 7.171087 |
Null | 28,512.33 | 27.425731 |
Young | 28,512.34 | 27.434699 |
Old | 28,513.07 | 28.163874 |
Old + Medium + Forest Fragment | 28,514.87 | 29.966900 |
## hmm_2states_low
## 1 2
## 0.5347187 0.4652813
## Value of the maximum log-likelihood: -14231.65
##
##
## step parameters:
## ----------------
## resident transit
## mean 50.08417 148.76993
## sd 33.12291 98.58646
##
## angle parameters:
## -----------------
## resident transit
## mean 0.0000000 0.0000000
## concentration 0.1063557 0.4054252
##
## Regression coeffs for the transition probabilities:
## ---------------------------------------------------
## 1 -> 2 2 -> 1
## (Intercept) -1.309465231 -0.9496168657
## dist_sensible -0.000700878 -0.0003953991
## dist_bnsm 0.022186113 -0.0370060607
##
## Transition probability matrix (based on mean covariate values):
## ---------------------------------------------------------------
## resident transit
## resident 0.7718309 0.2281691
## transit 0.1805468 0.8194532
##
## Initial distribution:
## ---------------------
## resident transit
## 0.4583366 0.5416634
pr_low <- pseudoRes(hmm2_low_sens_bnsm)
qqnorm(pr_low$stepRes,
pch = 21,
col = "midnightblue",
cex = 2.2,
main = "Q-Q Plot of Step Residuals",
xlab = "Theoretical Quantiles",
ylab = "Sample Quantiles",
cex.main = 2.2,
cex.lab = 1.8,
cex.axis = 1.6) qqnorm(pr_low$angleRes,
pch = 21,
col = "midnightblue",
cex = 2.2,
main = "Q-Q Plot of Turning angles Residuals",
xlab = "Theoretical Quantiles",
ylab = "Sample Quantiles",
cex.main = 2.2,
cex.lab = 1.8,
cex.axis = 1.6) plotStationary(
hmm2_low_sens_bnsm,
plotCI = TRUE,
col = c("#0072B2", "#d33420"),
cex.axis = 2,
cex.lab = 2,
cex.legend = 1.8,
cex.main = 2,
lwd =2,
legend.pos = "topright")Figure 3. Predicted stationary probabilities for the movement states (Resident and Transit) of black capuchin monkeys (Sapajus nigritus) in pine plantations, derived from two-state Hidden Markov Models. Each line represents the mean probability of a state, and the shaded ribbons indicate 95% confidence intervals. The probabilities are shown as a function of the minimum distance to young, medium-aged pine stands and native forest fragments, during low bark-stripping months (March to May).
hmm2_high_null <-
fitHMM(
data = high,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ 1)
hmm2_high_sens_bnsm <-
fitHMM(
data = high,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_sensible + dist_bnsm)
hmm2_high_sens <-
fitHMM(
data = high,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_sensible)
hmm2_high_full <-
fitHMM(
data = high,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_sensible + dist_bnsm + dist_old + dist_young)
hmm2_high_young <-
fitHMM(
data = high,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_young)
hmm2_high_old<-
fitHMM(
data = high,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_old)
hmm2_high_young_sens_bnsm <-
fitHMM(
data = high,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_sensible + dist_bnsm + dist_young)
hmm2_high_old_sens_bnsm<-
fitHMM(
data = high,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ dist_sensible + dist_bnsm + dist_old)aic_high <-AIC(hmm2_high_null, hmm2_high_sens_bnsm, hmm2_high_sens, hmm2_high_old, hmm2_high_young,
hmm2_high_young_sens_bnsm, hmm2_high_old_sens_bnsm)
aic_high_df <- as.data.frame(aic_high) %>%
mutate(Model = c("Medium + Young + Forest Fragment", "Medium + Old + Forest Fragment", "Medium + Forest Fragment", "Medium", "Null","Young", "Old"),
deltaAIC = AIC - min(AIC)) %>%
arrange(AIC) %>%
select(Model, AIC, deltaAIC)
aic_high_table <- flextable(aic_high_df) %>%
colformat_num(j = c("AIC", "deltaAIC"), digits = 3) %>%
theme_tron_legacy() %>%
autofit()
aic_high_tableModel | AIC | deltaAIC |
|---|---|---|
Medium + Young + Forest Fragment | 53,133.34 | 0.00000 |
Medium + Old + Forest Fragment | 53,150.00 | 16.66230 |
Medium + Forest Fragment | 53,151.07 | 17.73671 |
Medium | 53,260.34 | 127.00547 |
Null | 53,266.35 | 133.01297 |
Young | 53,267.04 | 133.70222 |
Old | 53,271.10 | 137.76008 |
## Value of the maximum log-likelihood: -26551.67
##
##
## step parameters:
## ----------------
## resident transit
## mean 87.30877 281.0000
## sd 62.79430 169.3918
##
## angle parameters:
## -----------------
## resident transit
## mean 0.0000000 0.0000000
## concentration 0.1625826 0.5663035
##
## Regression coeffs for the transition probabilities:
## ---------------------------------------------------
## 1 -> 2 2 -> 1
## (Intercept) -2.5168271847 -0.3306755996
## dist_sensible 0.0008183783 -0.0007597368
## dist_bnsm 0.0090843200 -0.0194060404
## dist_young -0.0002480793 0.0024984486
##
## Transition probability matrix (based on mean covariate values):
## ---------------------------------------------------------------
## resident transit
## resident 0.8839104 0.1160896
## transit 0.4393721 0.5606279
##
## Initial distribution:
## ---------------------
## resident transit
## 0.5030794 0.4969206
pr_high <- pseudoRes(hmm2_high_young_sens_bnsm)
qqnorm(pr_high$stepRes,
pch = 21,
col = "midnightblue",
cex = 2.2,
main = "Q-Q Plot of Step Residuals",
xlab = "Theoretical Quantiles",
ylab = "Sample Quantiles",
cex.main = 2.2,
cex.lab = 1.8,
cex.axis = 1.6) qqnorm(pr_high$angleRes,
pch = 21,
col = "midnightblue",
cex = 2.2,
main = "Q-Q Plot of Turning angles Residuals",
xlab = "Theoretical Quantiles",
ylab = "Sample Quantiles",
cex.main = 2.2,
cex.lab = 1.8,
cex.axis = 1.6) plotStationary(
hmm2_high_young_sens_bnsm,
plotCI = TRUE,
col = c("#0072B2", "#d33420"),
cex.axis = 2,
cex.lab = 2,
cex.legend = 1.8,
cex.main = 2,
lwd =2,
legend.pos = "topright")
Figure 3. Predicted stationary probabilities for the
movement states (Resident and Transit) of black
capuchin monkeys (Sapajus nigritus) in pine plantations,
derived from two-state Hidden Markov Models. Each line represents the
mean probability of a state, and the shaded ribbons indicate 95%
confidence intervals. The probabilities are shown as a function of the
minimum distance to young, medium-aged pine stands and native forest
fragments, during high bark-stripping months (July to October).
low <- as.data.frame(low) %>%
mutate(state2 = as.factor(hmm_2states_low))
high <- as.data.frame(high) %>%
mutate(state2 = as.factor(hmm_2states_high))
color <- c("Native Forest Fragment" = "palegreen4", "Plantation Stand" = "saddlebrown")
state_proportions_low <- low %>%
group_by(tipouso_grouped, state2) %>%
summarise(count = n(), .groups = "drop") %>%
group_by(tipouso_grouped) %>%
mutate(proportion = count / sum(count)) %>%
arrange(tipouso_grouped, desc(proportion))
state_proportions_low <- state_proportions_low %>%
mutate(tipouso_grouped = recode(tipouso_grouped,
"BNSM" = "Native Forest Fragment",
"plantations" = "Plantation Stand"))
state_proportions_low <- state_proportions_low %>%
mutate(state2 = recode(state2,
"1" = "Resident",
"2" = "Transit")) %>%
mutate(season = "Low Bark-stripping")
# High
state_proportions_high <- high %>%
group_by(tipouso_grouped, state2) %>%
summarise(count = n(), .groups = "drop") %>%
group_by(tipouso_grouped) %>%
mutate(proportion = count / sum(count)) %>%
arrange(tipouso_grouped, desc(proportion))
state_proportions_high <- state_proportions_high %>%
mutate(tipouso_grouped = recode(tipouso_grouped,
"BNSM" = "Native Forest Fragment",
"plantations" = "Plantation Stand"))
state_proportions_high <- state_proportions_high %>%
mutate(state2 = recode(state2,
"1" = "Resident",
"2" = "Transit"))%>%
mutate(season = "High Bark-stripping")
divided_state_props <- rbind (state_proportions_low, state_proportions_high) %>%
mutate(season = factor(season, levels = c("Low Bark-stripping", "High Bark-stripping")))
ggplot(divided_state_props, aes(x = state2, y = proportion, fill = tipouso_grouped)) +
geom_bar(stat = "identity", position = "dodge", color = "black", size = 1.1) +
scale_fill_manual(values = color,
name = "Habitat Type",
labels = c("Native Forest Fragment", "Plantation Stand")) +
facet_wrap(~season) +
theme_bw() +
theme(text = element_text(family = "Calibri", size = 30),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
legend.position = "bottom",
legend.title = element_text(size = 24),
legend.text = element_text(size = 24),
legend.key.size = unit(1.1, "cm"),
axis.text.x = element_text(size = 22, color = "black"),
axis.text.y = element_text(size = 22, color = "black"),
axis.title.x = element_blank(),
plot.margin = margin(1, 1, 1, 1, "cm"))hmm_prep_nf <- hmm_prep %>% filter (tipouso_grouped == "BNSM")
hmm_prep_pita <- hmm_prep %>% filter (tipouso_grouped == "plantations")
sl_plant_nf <- ggplot() +
geom_histogram(
data = subset(hmm_prep_nf, state2 == "Resident"),
aes(x = step, fill = state2),
bins = 95,
alpha = 0.7,
color = "#013c5d",
size=0.3,
show.legend = FALSE) +
geom_histogram(
data = subset(hmm_prep_nf, state2 == "Transit"),
aes(x = step, fill = state2),
bins = 95,
alpha = 0.9,
color = "#6b170d",
size=0.3,
show.legend = FALSE) +
scale_fill_manual(
values = c(
"Transit" = "#d33420",
"Resident" = "#0072B2")) +
labs(
x = "Step Length (m)",
y = "Count") +
scale_x_continuous(limits = c(0, 600)) +
theme_minimal() +
theme(
text = element_text(family = "Calibri", size = 38, color = 'black'),
panel.background = element_rect(fill = "white", color = NA),
plot.background = element_rect(fill = "white", color = NA),
axis.text = element_text(color = "black"),
legend.text = element_text(size = 35),
legend.title = element_text(size = 35),
axis.title.x = element_text(vjust = 0.5, size =42),
axis.title.y = element_text(vjust = 2, size =42),
strip.text = element_text(size = 35),
legend.position = "none",
plot.margin = unit(c(1.5, 1.5, 1.5, 1.5), "cm"))
roset_nf <- ggplot() +
geom_histogram(
data = subset(hmm_prep_nf, state2 == "Resident"),
aes(x = angle, fill = state2),
bins = 45,
alpha = 0.7,
color = "#013c5d",
size=0.3,
show.legend = FALSE) +
geom_histogram(
data = subset(hmm_prep_nf, state2 == "Transit"),
aes(x = angle, fill = state2),
bins = 45,
alpha = 0.9,
color = "#6b170d",
size=0.3,
show.legend = FALSE) +
scale_fill_manual(
values = c(
"Transit" = "#d33420",
"Resident" = "#0072B2")) +
coord_polar(start = -pi) +
labs(
x = "Turning angle (radians)",
y = "Count") +
scale_x_continuous(
breaks = c(-pi/2, 0, pi/2, pi),
labels = c(
expression(-frac(pi, 2)),
"0",
expression(frac(pi, 2)),
expression(pi))) +
theme_minimal() +
theme(
text = element_text(family = "Calibri", size = 42),
panel.background = element_rect(fill = "white", color = NA),
plot.background = element_rect(fill = "white", color = NA),
panel.grid = element_line(color = "gray80"),
axis.text.x = element_text(size = 35, color = "black", hjust = 10),
axis.text.y = element_text(size = 35, color = "black"),
axis.title.x = element_text(size = 42, margin = margin(t = 35)),
axis.title.y = element_text(size = 42, margin = margin(r = 20)),
legend.position = "none",
plot.margin = unit(c(1.5, 1.5, 1.5, 1.5), "cm"))
sl_plant_pita <- ggplot() +
geom_histogram(
data = subset(hmm_prep_pita, state2 == "Resident"),
aes(x = step, fill = state2),
bins = 95,
alpha = 0.7,
color = "#013c5d",
size=0.3,
show.legend = FALSE) +
geom_histogram(
data = subset(hmm_prep_pita, state2 == "Transit"),
aes(x = step, fill = state2),
bins = 95,
alpha = 0.9,
color = "#6b170d",
size=0.3,
show.legend = FALSE) +
scale_fill_manual(
values = c(
"Transit" = "#d33420",
"Resident" = "#0072B2")) +
labs(
x = "Step Length (m)",
y = "Count") +
scale_x_continuous(limits = c(0, 600)) +
theme_minimal() +
theme(
text = element_text(family = "Calibri", size = 38, color = 'black'),
panel.background = element_rect(fill = "white", color = NA),
plot.background = element_rect(fill = "white", color = NA),
axis.text = element_text(color = "black"),
legend.text = element_text(size = 35),
legend.title = element_text(size = 35),
axis.title.x = element_text(vjust = 0.5, size =42),
axis.title.y = element_text(vjust = 2, size =42),
strip.text = element_text(size = 35),
legend.position = "none",
plot.margin = unit(c(1.5, 1.5, 1.5, 1.5), "cm"))
roset_pita <- ggplot() +
geom_histogram(
data = subset(hmm_prep_pita, state2 == "Transit"),
aes(x = angle, fill = state2),
bins = 45,
alpha = 0.9,
color = "#6b170d",
size=0.3,
show.legend = FALSE) +
geom_histogram(
data = subset(hmm_prep_pita, state2 == "Resident"),
aes(x = angle, fill = state2),
bins = 45,
alpha = 0.7,
color = "#013c5d",
size=0.3,
show.legend = FALSE) +
scale_fill_manual(
values = c(
"Transit" = "#d33420",
"Resident" = "#0072B2")) +
coord_polar(start = -pi) +
labs(
x = "Turning angle (radians)",
y = "Count") +
scale_x_continuous(
breaks = c(-pi/2, 0, pi/2, pi),
labels = c(
expression(-frac(pi, 2)),
"0",
expression(frac(pi, 2)),
expression(pi))) +
theme_minimal() +
theme(
text = element_text(family = "Calibri", size = 42),
panel.background = element_rect(fill = "white", color = NA),
plot.background = element_rect(fill = "white", color = NA),
panel.grid = element_line(color = "gray80"),
axis.text.x = element_text(size = 35, color = "black", hjust = 10),
axis.text.y = element_text(size = 35, color = "black"),
axis.title.x = element_text(size = 42, margin = margin(t = 35)),
axis.title.y = element_text(size = 42, margin = margin(r = 20)),
legend.position = "none",
plot.margin = unit(c(1.5, 1.5, 1.5, 1.5), "cm"))
combined_plot_habitats <- sl_plant_nf + roset_nf + sl_plant_pita + roset_pita +
plot_layout(guides = "collect")
combined_plot_habitats <- combined_plot_habitats &
theme(
legend.position = "bottom",
legend.title = element_text(size = 34, face = "bold"),
legend.text = element_text(size = 32),
legend.key.size = unit(1.5, "cm"),
legend.spacing.y = unit(0.9, "cm"))
combined_plot_habitats
Figure 2. Distributions of step lengths and turning
angles for two behavioral movement states of black capuchin monkeys in
pine plantations of northeastern Argentina. The Resident state (blue) is characterized
by short step lengths and more random turning angles, and was more
common in native forest fragments (upper panels). The Transit state
(red) is
characterized by longer step lengths and more linear movements, and was
more common in pine stands (lower panels).
hmm_inp <-
hmm_inp %>% filter(!is.na(lon) &
!is.na(lat))
hmm_sf_inp <-
hmm_inp %>%
st_as_sf(
coords = c(
'lon',
'lat'),
crs = 4326) %>%
st_transform(crs=32721)
hmm_sf_inp_new_coords <-
hmm_sf_inp %>%
st_coordinates()
X <- hmm_sf_inp_new_coords[,"X"]
Y <- hmm_sf_inp_new_coords[,"Y"]
hmm_utm_inp <- cbind(
hmm_inp,
X,
Y)
hmm_prep_inp <-
prepData(
hmm_utm_inp, type = "UTM",
coordNames = c("X","Y"))
hmm_prep_inp <-
hmm_prep_inp %>% filter(!is.na(step) &
!is.na(angle)) stepMean0_2 <- c(m1 = 20, m2 = 300)
stepSD0_2 <- c(sd1 = 20, sd2 = 300)
zeromass_2 <- c(zm1 = 0.05, zm2 =0.05)
angleCon0_2 <- c(rho1 = 0.1, rho2 = 0.5)
stateNames_2 <- c("resident","transit")
dist_2 <- list(step = "gamma", angle = "wrpcauchy")
Par0_2 <- list(step=c(stepMean0_2, stepSD0_2), angle = c(angleCon0_2))hmm_fit_inp <-
fitHMM(
data = hmm_prep_inp,
nbStates = 2, dist = dist_2,
Par0 = Par0_2, stateNames = stateNames_2,
formula = ~ 1)
pr <- pseudoRes(hmm_fit_inp)
qqnorm(pr$stepRes,
pch = 21,
col = "midnightblue",
cex = 2.2,
main = "Q-Q Plot of Step Residuals",
xlab = "Theoretical Quantiles",
ylab = "Sample Quantiles",
cex.main = 2.2,
cex.lab = 1.8,
cex.axis = 1.6) qqnorm(pr$angleRes,
pch = 21,
col = "midnightblue",
cex = 2.2,
main = "Q-Q Plot of Turning angles Residuals",
xlab = "Theoretical Quantiles",
ylab = "Sample Quantiles",
cex.main = 2.2,
cex.lab = 1.8,
cex.axis = 1.6) ## Value of the maximum log-likelihood: -19765.78
##
##
## step parameters:
## ----------------
## resident transit
## mean 36.41865 97.31534
## sd 22.75771 59.20351
##
## angle parameters:
## -----------------
## resident transit
## mean 0.000000 0.0000000
## concentration 0.238198 0.3410758
##
## Regression coeffs for the transition probabilities:
## ---------------------------------------------------
## 1 -> 2 2 -> 1
## (Intercept) -3.388103 -1.813249
##
## Transition probability matrix:
## ------------------------------
## resident transit
## resident 0.9673306 0.03266935
## transit 0.1402459 0.85975415
##
## Initial distribution:
## ---------------------
## resident transit
## 0.6928904 0.3071096
hmm_states_2_inp <- viterbi(hmm_fit_inp)
hmm_states_df_inp <- hmm_prep_inp %>%
mutate(state = hmm_states_2_inp)
prop.table(table(hmm_states_2_inp))## hmm_states_2_inp
## 1 2
## 0.92508834 0.07491166
hmm_states_df_inp$state <- factor(
hmm_states_df_inp$state,
levels = c(2, 1), # Resident first, Transit second
labels = c("Transit", "Resident"))
sl_pni <- ggplot() +
geom_histogram(
data = subset(hmm_states_df_inp, state == "Resident"),
aes(x = step, fill = state),
bins = 95,
alpha = 0.7,
color = "#013c5d",
size=0.3,
show.legend = FALSE) +
geom_histogram(
data = subset(hmm_states_df_inp, state == "Transit"),
aes(x = step, fill = state),
bins = 95,
alpha = 0.9,
color = "#6b170d",
size=0.3,
show.legend = FALSE) +
scale_fill_manual(
values = c(
"Transit" = "#d33420",
"Resident" = "#0072B2")) +
labs(
x = "Step Length (m)",
y = "Count") +
scale_x_continuous(limits = c(0, 400)) +
theme_minimal() +
theme(
text = element_text(family = "Calibri", size = 38, color = 'black'),
panel.background = element_rect(fill = "white", color = NA),
plot.background = element_rect(fill = "white", color = NA),
axis.text = element_text(color = "black"),
legend.text = element_text(size = 35),
legend.title = element_text(size = 35),
axis.title.x = element_text(vjust = 0.5, size =42),
axis.title.y = element_text(vjust = 2, size =42),
strip.text = element_text(size = 35),
legend.position = "none",
plot.margin = unit(c(1.5, 1.5, 1.5, 1.5), "cm"))
roset_pni <- ggplot() +
geom_histogram(
data = subset(hmm_states_df_inp, state == "Resident"),
aes(x = angle, fill = state),
bins = 45,
alpha = 0.7,
color = "#013c5d",
size=0.3,
show.legend = FALSE) +
geom_histogram(
data = subset(hmm_states_df_inp, state == "Transit"),
aes(x = angle, fill = state),
bins = 45,
alpha = 0.9,
color = "#6b170d",
size=0.3,
show.legend = FALSE) +
scale_fill_manual(
values = c(
"Transit" = "#d33420",
"Resident" = "#0072B2")) +
coord_polar(start = -pi) +
# Labels
labs(
x = "Turning angle (radians)",
y = "Count") +
scale_x_continuous(
breaks = c(-pi/2, 0, pi/2, pi),
labels = c(
expression(-frac(pi, 2)),
"0",
expression(frac(pi, 2)),
expression(pi))) +
theme_minimal() +
theme(
text = element_text(family = "Calibri", size = 42),
panel.background = element_rect(fill = "white", color = NA),
plot.background = element_rect(fill = "white", color = NA),
panel.grid = element_line(color = "gray80"),
axis.text.x = element_text(size = 35, color = "black", hjust = 10),
axis.text.y = element_text(size = 35, color = "black"),
axis.title.x = element_text(size = 42, margin = margin(t = 35)),
axis.title.y = element_text(size = 42, margin = margin(r = 20)),
legend.position = "none",
plot.margin = unit(c(1.5, 1.5, 1.5, 1.5), "cm"))
combined_plot_inp <- sl_pni + roset_pni +
plot_layout(guides = "collect")
combined_plot_inp <- combined_plot_inp +
theme(
plot.title = element_text(
size = 39,
hjust = 1,
margin = margin(b = 50)),
legend.position = "bottom",
legend.title = element_text(size = 34, face = "bold"),
legend.text = element_text(size = 32),
legend.key.size = unit(1.5, "cm"),
legend.spacing.y = unit(0.9, "cm"))
combined_plot_inp
HMM Iguazu National Park. Distributions of step lengths
and turning angles for two behavioral movement states of black capuchin
monkeys in Iguazu National Park, northeastern Argentina. The Resident
state (blue) is
characterized by short step lengths and more random turning angles. The
Transit state (red) is characterized by
longer step lengths and more linear movements.