Report on the use of passive acoustic monitoring for analysis of bird trends in Jasper National Park
Abstract
Since 2007, Jasper National Park has conducted passive acoustic monitoring as part of its ecological integrity monitoring program. The 17 years of data were analyzed to identify trends and extract insights that inform ongoing monitoring and strengthen future species monitoring practices. The analysis assessed whether species and guild abundances shifted by ±2.5% in the alpine and montane ecoregions. Data were managed and processed in WildTrax, combining and harmonizing legacy datasets from multiple methodologies. Sampling locations were tested for independence, and trend analyses quantified changes in species counts over time across guilds and ecoregions.
This report is dynamically generated, meaning its results may evolve with the addition of new data or further analyses. For the most recent updates, refer to the publication date and feel free to reach out to the authors.
Land Acknowledgement
We respectfully acknowledge that Jasper National Park is located in Treaty 6 and 8 as well as the traditional lands of the Anishinabe, Aseniwuche Winewak, Dene-zaa, Nêhiyawak, Secwépemc, Stoney Nakoda, Mountain Métis and Métis. We acknowledge the past, present, and future generations of these nations who continue to steward the land.
Introduction
Human activities have been identified as key pressures and contributors to the global decline in forest wildlife (Allan et al. (2017)). The repercussions of habitat fragmentation (Fahrig (2003)) and loss (Hanski (2011)), climate change (Mantyka-pringle, Martin, and Rhodes (2012), Sattar et al. (2021), Abrahms et al. (2023)), and increased access to sensitive areas exert direct and indirect pressures on forest biodiversity, particularly in managed regions in Canada (Lemieux et al. (2011)). Climate change and increasing wildfire activity in Alberta’s Rocky Mountain Natural Region have significantly impacted montane bird monitoring; in 2023, two of the park’s three long-term montane monitoring transects were affected by wildfires, while the third is currently undergoing wildfire risk reduction measures. Since 2007, Jasper National Park initiated a program incorporating passive acoustic monitoring of the Park’s vocalizing wildlife. ARUs are compact environmental sensors that are designed to passively record the environment (Shonfield and Bayne (2017)), capturing vocalizing species like birds and amphibians, which is growing in use across the globe (Sugai et al. (2018)). This technology enables resource managers to conduct prolonged surveys with minimal human interference, but also to create a permanent, archiveable recording of the soundscape. The subsequent data collected by these units contribute valuable information to ecological integrity metrics such as species richness, diversity, occupancy, and trends of species and human activities in National Parks over time. This data can aid in decision-making and management within the Park. Given the rapid and ease of accumulating data from these units, maintaining a high standard of data integrity is paramount to ensure future data interoperability and sharing.
This project aims to analyze Jasper’s passive acoustic monitoring data from 2007 to 2024, assessing trends in species and guild abundance while accounting for the clustering of survey points within transects. Separate analyses will be conducted for montane and sub-alpine and alpine ecoregions to align with Ecological Integrity reporting requirements, determining time-to-first-detection usability in analysis. To enhance accessibility and reproducibility, the findings will be presented in this online report with fully documented code, allowing future updates as data collection methods become standardized. Additionally, recommendations will be developed to refine data transcription priorities, improve annual reporting methods, and evaluate species guild classifications for long-term monitoring. The objectives of this report are to:
- Describe the data management and processing procedures for the acoustic data collected from 2007 to 2024;
- Comparing data processing methods on count of species and individuals heard on recordings;
- Report on transects in montane/sub-alpine and alpine ecoregions, including time-to-first-detection where possible, with key metrics such as the percentage of bird species and guilds declining by ≥2.5% in both montane/sub-alpine and alpine regions.
- Recommendations for prioritizing previous years’ data for re-transcription to 1SPT, determine the best approach for annual reporting, e.g. baseline comparisons or 10-year trend assessments, adjust methods for evaluating species trends against thresholds, and review guilds and traits used in assessments
- Facilitate data publication to the public, resource managers, academic institutions, and any other relevant agencies
Methods
Data collection
Songbird data was collected using autonomous recording units (ARUs), deployed by field staff to capture one 10-minute recordings per point count annually. Surveys were scheduled consistently each breeding season in June and early July, starting at dawn. Technicians walked transects containing ten points, each spaced at least 300 m apart to prevent duplicate detections and ensure independence of locations. At each location, the ARU is set up, and technicians move 10–20 m away to minimize disturbance, allowing at least 11 minutes of recording for voice notes and activation and de-activation of the units.
Code
datatable(locs_summary,
options = list(
searching = TRUE,
paging = TRUE,
pageLength = 10
|>
)) formatStyle(columns = colnames(locs_summary),
backgroundColor = styleEqual(c("NA"), "lightgray"))
Data management, processing and quality control
Recordings were clipped and organized to only include the 10-minute count. Before adopting WildTrax in 2021, processing analysts excluded the initial 20 seconds to 1.5 minutes of recordings to reduce human impact on detection probability, then logged the first detection time per species. Recordings are now uploaded as clean 10-minute files with the voice note and observer notes removed. In WildTrax, individuals were counted by users scanning both the spectrogram and listening to the audio output. Tags were then drawn to encompass the signal within the methods indicated in each project (see Table 2 and Figure 2). Transcribers also had site photos available (see Figure 3) to optimize their species identification by having habitat context while processing.
Code
<- tibble(
transcription_table Years = c("2007-2020", "2021-2022", "2023-2024", "2007-2011"),
`Transcription Method` = c(
"0-3.33, 3.33-6.66, 6.66-10 min",
"1 SPT - Species per task or recording (Every new individual is tagged within the 10 minute recording).",
"1 SPT - Species per task or recording (Every new individual is tagged within the 10 minute recording).",
"1 SPT - Species per task or recording (Every new individual is tagged within the 10 minute recording). Re-transcription."
),`Bin Method` = c(
"Abundance re-starts for each 3.33-minute bin",
"Time of first detection over 10 minute period",
"Time of first detection over 10 minute period",
"Time of first detection over 10 minute period. Re-transcription."
),`Method Details` = c(
"No cap on abundance; abundance re-starts for each bin, no total abundance for the 10-min recording",
"Time of first detection over 10 minute period",
"Time of first detection over 10 minute period",
"Time of first detection over 10 minute period. Re-transcription."
),`Max # of Individuals` = c(
"No cap",
"Maximum of 3 individuals per 10-minute recording",
"No cap",
"No cap. Re-transcription."
)
)
transcription_table## # A tibble: 4 × 5
## Years `Transcription Method` `Bin Method` `Method Details`
## <chr> <chr> <chr> <chr>
## 1 2007-2020 0-3.33, 3.33-6.66, 6.66-10 min Abundance r… No cap on abund…
## 2 2021-2022 1 SPT - Species per task or recording… Time of fir… Time of first d…
## 3 2023-2024 1 SPT - Species per task or recording… Time of fir… Time of first d…
## 4 2007-2011 1 SPT - Species per task or recording… Time of fir… Time of first d…
## # ℹ 1 more variable: `Max # of Individuals` <chr>
# Render the datatable
datatable(transcription_table,
options = list(
searching = TRUE,
paging = TRUE,
pageLength = 10
|>
)) formatStyle(columns = colnames(transcription_table),
backgroundColor = styleEqual(c("NA"), "lightgray"))


Analyses
For the purpose of these analyses abundance was defined as the count of individuals detected during point counts, rather than as a density x area relationship. All analyses took place in R 4.5.1 ‘Great Square Root’.
Location correlation
To assess spatial autocorrelation among bird survey locations across years, we examined the spatial relationship between survey points based on total species abundance per location and year in order to determine whether spatial clustering or spatial dependence exists in the species abundance data. Given that survey points are typically 300 meters apart, we used the knearneigh()
function to identify the nearest neighbours within this threshold. This function calculates the closest neighboring survey points for each location. We then constructed a spatial weight matrix using the knn2nb()
function and calculated Moran’s I statistic using moran.test
to determine whether spatial autocorrelation exists in the abundance of species across survey locations. A significant Moran’s I suggests that survey locations are not independent and that spatial autocorrelation should be accounted for in further analyses. We also conducted further analyses grouping locations from each transect (CAVELL, ELYSIUM, FIDDLE, MAJESTIC, OPAL, POBOKTAN, PYRAMID, SULPHUR, SUNWAPTA, TEKARRA, VALLEY5, WATCHTOWER, WILCOX).
Community analysis
A redundancy analysis (RDA) was conducted using the vegan
package (Oksanen et al. (2025)) to quantify the variation in community composition explained by ecoregions and to visualize species–ecoregion relationships with ordination plots (C. Radhakrishna Rao (1964)). Species‑level observations were aggregated into a species‑by‑location matrix, with abundances recorded as the maximum count of each species at each location. Ecoregions were categorized into Alpine and Montane with the Montaine ecoregions that can be further sub-divided into Upper Subalpine, Lower Subalpine, and Montane proper ecozones for all subsequent analyses. Differences in composition between Alpine and Montane groups were tested using permutational multivariate analysis of variance (PERMANOVA; Anderson (2001)) on Bray–Curtis dissimilarities with 999 permutations under a reduced model.
Trend analysis
We analyzed bird abundance trends from 2007 to 2024 across species, ecoregions, and functional guilds. This multi-level approach allowed us to assess patterns of change while considering ecological and functional groupings. Observations were first filtered to include only data from the EI monitoring sites containing ecoregional classification. Species were classified into functional guilds using an ecological trait database (see Table 5). Spatial autocorrelation was accounted for when considered sites grouped <100 meters from one another across years based on the results of the Moran’s I test. Given transcription methods varied over time (see Table 2), which may possibly affect data consistency, from both a species detectability and methodological standpoint, we used generalized linear mixed models (GLMM) using the lme4
with package (Bates, Kliegl, et al. (2015)) to examine time-to-first detection of each individual and the differences it posed across years to account for detection probability differences, using location as random effect (1 | location
). 20 species (AMPI, AMRO, BRSP, CHSP, DEJU, GCKI, HETH, FOSP, OCWA, RCKI, SWTH, VATH, WAVI, WCSP, WTSP, GCSP, YRWA, RBNU, LISP, MOCH) that were the most abundant and represented different guilds were used for the analyses. Count data were modeled with a Poisson distribution or a negative binomial distribution when overdispersion was detected, with year
and method
(i.e., processing methodology) as fixed effects and location
as a random effect.
To evaluate trend significance, we applied the Mann–Kendall test (Mann (1945a); Hamed and Rao (1998)), a non-parametric method sensitive to monotonic trends (Suthar, Biggs, and Anderson (2025)), and estimated trend magnitude using Sen’s Slope (Pranab Kumar Sen (1968); Dawood (2017)) via the mmkh()
function in the modifiedmk
package. Because these tests do not account for detection probability, we complemented them with single-visit abundance models (Sólymos, Lele, and Bayne (2012)) via the svabu
function implemented in the detect
package. svabu
models estimate abundance while explicitly incorporating detection probability, providing more reliable inferences about true abundance trends. It also avoids the need to assume a closed population statistically and computationally efficient estimators. For each species, we fitted separate models for each ecoregion with year, julian date and time of day included as covariate in the abundance component (e.g., svabu(individual ~ year | year + julian + hour)
). Year-specific mean abundance (λ̂) was then estimated across sites, and the resulting λ̂ series was analyzed using the Mann–Kendall test to assess temporal trends in detection-corrected abundance.
Functional diversity was quantified using Rao’s Q (C. R. Rao (1982); Laliberté and Legendre (2010)) via the dbFD() function in the FD package (Laliberté, Legendre, and Shipley (2014)). To assess temporal changes in guild-level trait diversity, we applied linear, mixed-effects, and segmented models. We also summarized raw species richness—the number of unique species—per location and plotted its change over time (Figure 10), complemented by annual estimates of Shannon’s diversity index, which integrates both species richness and evenness, to capture broader shifts in community structure (Figure 11).
Code
<- list()
all_lambda <- list()
trend_summary
<- tot_a_filtered |>
tot_abundance filter(!detection_time > 180) |>
mutate(julian = yday(recording_date_time),
hour = hour(recording_date_time)) |>
filter(species_code %in% mdm)
for (sp in unique(tot_abundance$species_code)) {
for (ec in unique(tot_abundance$ecoregion)) {
<- tot_abundance %>%
simp filter(species_code == sp, ecoregion == ec)
if (nrow(simp) < 5) next
<- svabu(individual_order ~ year | year + julian, data = simp)
fit
<- mean(simp$julian, na.rm = TRUE)
mean_julian
<- expand.grid(year = unique(simp$year), julian = mean_julian)
pred_grid $lambda <- predict(fit, newdata = pred_grid, type = "response")
pred_grid
<- pred_grid %>%
lambda_year group_by(year) %>%
summarise(lambda_hat = mean(lambda)) %>%
mutate(species_code = sp, ecoregion = ec)
paste(sp, ec, sep = "_")]] <- lambda_year
all_lambda[[
# Mann–Kendall test on λ̂
<- try(mmkh(lambda_year$lambda_hat), silent = TRUE)
mk
if (!inherits(mk, "try-error")) {
paste(sp, ec, sep = "_")]] <- tibble(
trend_summary[[species_code = sp,
ecoregion = ec,
tau = mk[["Tau"]],
p_value = mk[["new P-value"]],
sen_slope = mk[["Sen's slope"]]
)
}
}
}
<- bind_rows(all_lambda)
lambda_all <- bind_rows(trend_summary)
trend_all
<- trend_all %>%
trend_alls mutate(
perc_change = sen_slope / mean(lambda_all$lambda_hat[lambda_all$species_code == species_code & lambda_all$ecoregion == ecoregion]) * 100,
trend_flag = case_when(
>= 2.5 ~ "Increase > 2.5%",
perc_change <= -2.5 ~ "Decrease > 2.5%",
perc_change TRUE ~ "Stable"
) )
Code
<- read_csv("jasper_guilds.csv")
guilds
<- janp_main |>
guild_activity filter(data_type %in% c("legacy", "single_visit_3_max", "single_visit_0_max")) |>
filter(!(data_type == "single_visit_0_max" & year < 2023)) |>
wt_tidy_species(remove = c("mammal", "amphibian", "abiotic", "insect", "unknown"), zerofill = T) |>
::select(location, recording_date_time, species_common_name, species_code, individual_count) |>
dplyrmutate(julian = lubridate::yday(recording_date_time),
month = month(recording_date_time),
year = factor(year(recording_date_time))) |>
inner_join(guilds, by = "species_code") |>
group_by(species_code) |>
add_tally() |>
ungroup() |>
group_by(julian, species_code) |>
add_tally() |>
ungroup() |>
arrange(species_code) |>
mutate(recording_date_time = as.POSIXct(recording_date_time)) |>
mutate(species_code = factor(species_code, levels = sort(unique(species_code)))) |>
inner_join(locs_summary, by = c("location" = "Location")) |>
mutate(ecoregion = case_when(ecoregion %in% c("Alpine") ~ "Alpine",
%in% c("Upper Subalpine", "Lower Subalpine", "Montane") ~ "Montane"))
ecoregion
datatable(guilds,
options = list(
searching = TRUE,
paging = TRUE,
pageLength = 10
))
Code
<- janp_main |>
shannon_d wt_tidy_species(remove = c("mammal","amphibian","abiotic","insect","unknown"), zerofill = F) |>
inner_join(wt_get_species() |> dplyr::select(species_code, species_class, species_order), by = "species_code") |>
::select(location, recording_date_time, species_code, species_common_name, individual_order, individual_count) |>
dplyrdistinct() |>
group_by(location, recording_date_time, species_code, species_common_name) |>
summarise(count = max(individual_order)) |>
ungroup() |>
pivot_wider(names_from = species_code, values_from = count, values_fill = 0) |>
pivot_longer(cols = -(location:species_common_name), names_to = "species", values_to = "count") |>
group_by(location, year = year(recording_date_time), species) |>
summarise(total_count = sum(count)) |>
ungroup() |>
group_by(location, year) |>
summarise(shannon_index = diversity(total_count, index = "shannon")) |>
ungroup() |>
ggplot(aes(x = factor(year), y = shannon_index, fill = factor(year))) +
geom_boxplot() +
geom_point(alpha = 0.6, colour = "grey") +
labs(x = "Year",
y = "Shannon diversity index per location") +
theme_bw() +
guides(fill = guide_legend(title = "Year")) +
scale_fill_viridis_d(alpha = 0.8, option = "cividis")
Results
Some of these analyses are still a work-in-progress. Check back soon for updates and additional details.
Ecoregions
Figure 4 shows the relationship between species and ecoregion. The PERMANOVA test was performed using Bray-Curtis dissimilarity to assess whether community composition significantly differed between ecoregion groups. The analysis revealed a significant difference in community composition between alpine and montane groups. The ecoregion grouping explained approximately 27.85% of the variation in community composition, while residual variation accounted for 72.15%. These findings indicate a substantial divergence in species composition between ecoregion groups and helps to justify subsequent analyses looking at trend differences between these areas.

Species richness and functional diversity
Activity patterns across nesting, dietary and migratory guilds are illustrated in ?@fig-spp-nesting onwards. A notable pattern emerges across all four guilds of the proportion of tags detected being detected later in the alpine ecoregion compared to the montane ecoregion. Species richness per location is at Figure 10 and Shannon’s diversity index over years at Figure 11. Overall, both richness and diversity were stable across years. Rao’s Q averaged between about 8.2 and 10.5 across survey locations, with a clear upward tendency over time (Figure 9). The non‑parametric Mann–Kendall test gave a Kendall’s τ of 0.32 (p ≈ 0.07), indicating a positive but marginally non‑significant monotonic increase in functional diversity. A simple linear regression of mean Rao’s Q against year yielded a slope of 0.051 units per year (p ≈ 0.06), again suggesting an upward trend that narrowly misses the conventional 0.05 significance threshold. When we account for repeated measures at each location via a mixed‑effects model (random intercepts by location), the year effect becomes highly significant (slope = 0.058 Rao’s Q units per year; t ≈ 4.3), demonstrating that functional diversity has increases when location‑level variation is considered. Finally, breakpoint analysis identifies a shift around 2009, suggesting that functional diversity was relatively low and stable from 2007–2009, then rose to more variable but generally higher values from 2010 onward.





Code
<- janp_main |>
spp_rich_location filter(data_type %in% c("legacy","single_visit_3_max","single_visit_0_max")) |>
filter(!(data_type == "single_visit_0_max" & year < 2023)) |>
wt_tidy_species(remove = c("mammal","amphibian","abiotic","insect","unknown"), zerofill = F) |>
::select(location, year, species_code) |>
dplyrdistinct() |>
group_by(location, year) |>
summarise(species_count = n_distinct(species_code)) |>
ungroup() |>
inner_join(locs_summary, by = c("location" = "Location")) |>
mutate(ecoregion = case_when(ecoregion %in% c("Alpine") ~ "Alpine",
%in% c("Upper Subalpine","Lower Subalpine","Montane") ~ "Montane"))
ecoregion
|>
spp_rich_location ggplot(aes(x=as.factor(year), y=species_count, fill=year)) +
geom_boxplot() +
geom_point(alpha = 0.7, colour = "grey") +
geom_smooth(method = "lm") +
theme_bw() +
facet_wrap(~ecoregion) +
scale_fill_viridis_c(option = "cividis") +
xlab('Year') + ylab('Species richness per location') +
coord_flip()

Code
shannon_d

Abundance trends
Code
# Plot abundance trends
ggplot(lambda_all, aes(x = year, y = lambda_hat, color = ecoregion)) +
geom_line(size = 1.1) +
facet_wrap(~species_code, scales = "free_y", ncol = 6) +
ylim(0, 3) +
labs(
x = "Year",
y = expression("Estimated Abundance (" * hat(lambda) * ")"),
title = "Detection-Corrected Abundance Trends by Ecoregion"
+
) theme_bw() +
scale_colour_viridis_d(option = "cividis") +
theme(strip.text = element_text(size = 8))

Code
%>%
lambda_all group_by(species_code) %>%
summarise(
mk_test = list(mmkh(lambda_hat))) |>
ungroup() |>
unnest_wider(mk_test)
Code
<- lambda_all %>%
mean_lambda group_by(species_code, ecoregion) %>%
summarise(mean_lambda = mean(lambda_hat), .groups = "drop")
<- trend_all %>%
trend_alls left_join(mean_lambda, by = c("species_code", "ecoregion")) %>%
mutate(
perc_change = sen_slope / mean_lambda * 100,
trend_flag = case_when(
>= 2.5 ~ "Increase > 2.5%",
perc_change <= -2.5 ~ "Decrease > 2.5%",
perc_change TRUE ~ "Stable"
)
)
<- lambda_all %>%
lambda_all_flagged left_join(trend_alls %>% dplyr::select(species_code, ecoregion, trend_flag),
by = c("species_code", "ecoregion")) |>
arrange(species_code, year)
# Plot
ggplot(lambda_all_flagged, aes(x = year, y = lambda_hat, color = trend_flag)) +
geom_point(data = tot_a_filtered %>% filter(species_code %in% mdm),
aes(x = year, y = individual_order), alpha = 0.3, color = "gray") +
geom_smooth(method = "lm", se = TRUE, size = 1.1) + # straight line with confidence interval
facet_wrap(~species_code, scales = "free_y") +
scale_color_manual(values = c("Increase > 2.5%" = "blue",
"Decrease > 2.5%" = "red",
"Stable" = "black")) +
labs(x = "Year", y = expression("Estimated Abundance ("*lambda*")"),
color = "Trend Category",
title = "Detection-Corrected Abundance Trends with ±2.5% Threshold") +
theme_bw() +
theme(legend.position = "bottom")
