library(tidyverse)
library(lubridate)
library(sf)
library(ctmm)
past_ids_vec <- c(
"AG082", "AG083", "AG088", "AG089", "AG096", "AG432",
"st2010-1048_2", "st2010-1052", "st2010-1053"
)
present_ids_vec <- c(
"171604 - adWBV05", "171605 - Matira", "171606 - Ilkeliani",
"171607 - Baldrick", "171608 - adWBV06", "171609 - Charlie",
"171612 - Olarro", "171613 - Tipilikwani"
)
Fixes are flagged as erroneous based on visual inspection of individual trajectories (speed > 100 km/h or step > 50 km with turning angle > 150°). Individual st2010-1054 is excluded entirely due to insufficient data quality. Timestamps for st2010-1048_2 recorded as 2010 are corrected to 2011.
remove_timestamps <- tribble(
~individual.local.identifier, ~timestamp,
# st2010-1048_2
"st2010-1048_2", "2010-11-17 09:00:00",
"st2010-1048_2", "2010-12-07 07:00:00",
# st2010-1052
"st2010-1052", "2010-06-12 17:00:00",
"st2010-1052", "2010-06-13 05:00:00",
# st2010-1057
"st2010-1057", "2011-01-14 09:00:00",
# adWBV05
"171604 - adWBV05", "2017-09-14 06:00:00",
"171604 - adWBV05", "2018-02-11 06:00:00",
# Matira
"171605 - Matira", "2018-11-03 06:00:00",
# Ilkeliani
"171606 - Ilkeliani", "2019-04-22 06:00:00",
"171606 - Ilkeliani", "2019-08-17 06:00:00",
# Baldrick
"171607 - Baldrick", "2018-07-05 06:00:00",
"171607 - Baldrick", "2020-01-14 06:00:00",
# adWBV06
"171608 - adWBV06", "2017-12-27 06:00:00",
# Charlie
"171609 - Charlie", "2018-09-08 06:00:00",
"171609 - Charlie", "2019-11-02 06:00:00",
# Olarro
"171612 - Olarro", "2020-03-18 06:00:00",
# Tipilikwani
"171613 - Tipilikwani", "2019-06-22 06:00:00",
"171613 - Tipilikwani", "2021-02-14 06:00:00"
) %>%
mutate(timestamp = as.POSIXct(timestamp, tz = "UTC"))
# Load merged raw GPS (all individuals, 2h-resampled by Movebank)
wbv_raw <- read.csv(
"/Users/oli/University/Zoology/5th_Year_MSc/MSC_Thesis/Thesis_R/WBV_resampled_2h.csv"
) %>%
mutate(timestamp = as.POSIXct(timestamp, tz = "UTC"))
cat("Raw rows:", nrow(wbv_raw), "\n")
## Raw rows: 12506
cat("Unique individuals:", n_distinct(wbv_raw$individual.local.identifier), "\n")
## Unique individuals: 19
# Exclude st2010-1054 entirely; apply timestamp removal list
wbv_clean <- wbv_raw %>%
filter(individual.local.identifier != "st2010-1054") %>%
filter(individual.local.identifier %in% c(past_ids_vec, present_ids_vec)) %>%
anti_join(remove_timestamps,
by = c("individual.local.identifier", "timestamp")) %>%
# Correct year error: st2010-1048_2 fixes recorded as 2010 should be 2011
mutate(
timestamp = if_else(
individual.local.identifier == "st2010-1048_2" &
year(timestamp) == 2010,
timestamp + years(1),
timestamp
)
) %>%
# Derived columns
mutate(
month = month(timestamp),
season = if_else(month %in% c(11, 12, 1, 2, 3, 4), "Wet", "Dry"),
period = if_else(individual.local.identifier %in% past_ids_vec,
"Past", "Present"),
year = year(timestamp)
) %>%
arrange(individual.local.identifier, timestamp)
cat("Cleaned rows:", nrow(wbv_clean), "\n")
## Cleaned rows: 12323
cat("Individuals:", n_distinct(wbv_clean$individual.local.identifier), "\n")
## Individuals: 17
saveRDS(wbv_clean, "WBV_resampled_2h.rds")
# Summary table
wbv_clean %>%
group_by(individual.local.identifier, period) %>%
summarise(
n_fixes = n(),
start = min(timestamp),
end = max(timestamp),
duration_months = as.numeric(difftime(max(timestamp), min(timestamp),
units = "days")) / 30.44,
.groups = "drop"
) %>%
arrange(period, individual.local.identifier) %>%
print(n = Inf)
## # A tibble: 17 × 6
## individual.local.ide…¹ period n_fixes start end
## <chr> <chr> <int> <dttm> <dttm>
## 1 AG082 Past 362 2009-07-06 00:00:00 2010-07-11 00:00:00
## 2 AG083 Past 294 2009-07-06 00:00:00 2010-04-26 00:00:00
## 3 AG088 Past 287 2009-07-09 00:00:00 2010-05-04 00:00:00
## 4 AG089 Past 339 2009-07-09 00:00:00 2010-06-18 00:00:00
## 5 AG096 Past 316 2009-08-07 00:00:00 2010-06-18 00:00:00
## 6 AG432 Past 214 2010-06-16 00:00:00 2011-01-15 00:00:00
## 7 st2010-1048_2 Past 260 2011-01-01 00:00:00 2011-12-31 00:00:00
## 8 st2010-1052 Past 263 2010-08-09 00:00:00 2011-05-03 00:00:00
## 9 st2010-1053 Past 281 2010-08-10 00:00:00 2011-05-20 00:00:00
## 10 171604 - adWBV05 Prese… 1697 2018-04-15 00:00:00 2022-12-12 00:00:00
## 11 171605 - Matira Prese… 498 2017-11-19 00:00:00 2019-03-31 00:00:00
## 12 171606 - Ilkeliani Prese… 1043 2018-06-18 00:00:00 2021-04-25 00:00:00
## 13 171607 - Baldrick Prese… 1713 2018-01-12 00:00:00 2022-10-04 00:00:00
## 14 171608 - adWBV06 Prese… 1909 2018-04-15 00:00:00 2023-07-10 00:00:00
## 15 171609 - Charlie Prese… 529 2018-04-16 00:00:00 2019-09-26 00:00:00
## 16 171612 - Olarro Prese… 1212 2018-06-18 00:00:00 2022-01-18 00:00:00
## 17 171613 - Tipilikwani Prese… 1106 2018-06-18 00:00:00 2021-06-28 00:00:00
## # ℹ abbreviated name: ¹individual.local.identifier
## # ℹ 1 more variable: duration_months <dbl>
Resident wildebeest only (migrant == 0). Fixes with speed > 100 km/h between consecutive locations are removed as GPS errors.
wb_raw <- read.csv(
"/Users/oli/University/Zoology/5th_Year_MSc/MSC_Thesis/Thesis_R/Wildebeest_V1.csv",
stringsAsFactors = FALSE
) %>%
mutate(datetime = as.POSIXct(datetime, format = "%d/%m/%Y %H:%M", tz = "UTC"))
cat("Raw wildebeest rows:", nrow(wb_raw), "\n")
## Raw wildebeest rows: 891068
wb <- wb_raw %>%
filter(
migrant == 0,
year %in% c(2010, 2011, 2017:2023),
!is.na(UTM_X), !is.na(UTM_Y), !is.na(datetime)
) %>%
mutate(
period = if_else(year %in% 2010:2011, "Past", "Present"),
month = month(datetime),
season = if_else(month %in% c(11, 12, 1, 2, 3, 4), "Wet", "Dry")
) %>%
arrange(AID, datetime)
# Remove individuals with < 100 fixes
keep_ids <- wb %>%
count(AID) %>%
filter(n >= 100) %>%
pull(AID)
wb <- wb %>% filter(AID %in% keep_ids)
cat("After fix-count filter:", nrow(wb), "fixes,",
n_distinct(wb$AID), "individuals\n")
## After fix-count filter: 176858 fixes, 33 individuals
# Speed-based outlier removal
wb <- wb %>%
group_by(AID) %>%
arrange(datetime, .by_group = TRUE) %>%
mutate(
step_km = sqrt((UTM_X - lag(UTM_X))^2 + (UTM_Y - lag(UTM_Y))^2) / 1000,
dt_h = as.numeric(difftime(datetime, lag(datetime), units = "hours")),
speed_kmh = step_km / dt_h
) %>%
filter(is.na(speed_kmh) | speed_kmh <= 100) %>%
ungroup()
cat("After outlier removal:", nrow(wb), "fixes\n")
## After outlier removal: 176858 fixes
WB_clean <- wb %>%
select(AID, datetime, UTM_X, UTM_Y, period, year, month, season, sex)
saveRDS(WB_clean, "WB_clean.rds")
cat("Saved: WB_clean.rds\n")
## Saved: WB_clean.rds
WB_clean %>%
group_by(period) %>%
summarise(n_fixes = n(), n_individuals = n_distinct(AID), .groups = "drop")
## # A tibble: 2 × 3
## period n_fixes n_individuals
## <chr> <int> <int>
## 1 Past 94243 15
## 2 Present 82615 18
No migratory wildebeest were tracked during the Past vulture period (2010–2011). We use data from 2002–2004 as a proxy for the migratory distribution, retaining individuals with ≥ 100 fixes.
wb_mig_past <- wb_raw %>%
filter(
migrant == 1,
year %in% 2002:2004,
!is.na(UTM_X), !is.na(UTM_Y), !is.na(datetime)
) %>%
mutate(
month = month(datetime),
season = if_else(month %in% c(11, 12, 1, 2, 3, 4), "Wet", "Dry")
) %>%
arrange(AID, datetime)
keep_mig <- wb_mig_past %>% count(AID) %>% filter(n >= 100) %>% pull(AID)
wb_mig_past <- wb_mig_past %>% filter(AID %in% keep_mig)
cat("Migratory wildebeest proxy (2002–2004):",
nrow(wb_mig_past), "fixes,",
n_distinct(wb_mig_past$AID), "individuals\n")
## Migratory wildebeest proxy (2002–2004): 26866 fixes, 13 individuals
saveRDS(wb_mig_past, "WB_mig_past_proxy.rds")
cat("Saved: WB_mig_past_proxy.rds\n")
## Saved: WB_mig_past_proxy.rds