Code
# set working directory as project directory or one directory above,
knitr::opts_knit$set(root.dir = "..")Cape parrot vocal dialects
# set working directory as project directory or one directory above,
knitr::opts_knit$set(root.dir = "..")Source code and data found at https://github.com/maRce10/cape_parrot_dialects
Measure acoustic structure of cape parrot contact calls
Compare acoustic dissimilarity between individuals from different localities and regions
# knitr is require for creating html/pdf/word reports formatR is
# used for soft-wrapping code
# install/ load packages
sketchy::load_packages(packages = c("knitr", "formatR", "viridis",
"warbleR", github = "maRce10/PhenotypeSpace", "ggplot2", "randomForest",
"mlbench", "caret", "pbapply", "vegan", "umap", "brms", "brmsish",
"maRce10/ohun", "geosphere", "performance", "ggcorrplot", "emmeans",
"patchwork"))Warning: replacing previous import 'brms::rstudent_t' by 'ggdist::rstudent_t'
when loading 'brmsish'
Warning: replacing previous import 'brms::dstudent_t' by 'ggdist::dstudent_t'
when loading 'brmsish'
Warning: replacing previous import 'brms::qstudent_t' by 'ggdist::qstudent_t'
when loading 'brmsish'
Warning: replacing previous import 'brms::pstudent_t' by 'ggdist::pstudent_t'
when loading 'brmsish'
## Format data
dat <- read.csv("./data/raw/consolidated_sound_files_CPV_contact_calls_USEaug2026 - UPDATED_USE for analyses.csv")
warbleR_options(path = "./data/raw/consolidated_files")
st <- selection_table(whole.recs = TRUE)
st$region <- sapply(st$sound.files, function(x) dat$Regions..4.[dat$New_Name ==
x][1])
st <- st[complete.cases(st$region), ]
# make a folder for each region and save the spectrograms in the
# corresponding folder
for (i in unique(st$region)) {
dir.create(paste0("./data/processed/spectros_by_region/", i),
showWarnings = FALSE)
spectrograms(st[st$region == i, ], wl = 512, flim = c(0, 10),
dest.path = paste0("./data/processed/spectros_by_region/",
i), pal = viridis, collevels = seq(-100, 0, 5))
}
# st$sorted <- sapply(st$sound.files, function(x)
# dat$Sorted[dat$New_Name == x][1]) table(st$sorted)
# spectrograms(st, wl = 512, flim = c(0, 10), dest.path =
# './data/processed/spectrograms', pal = viridis, collevels =
# seq(-100, 0, 5)) spectrograms(st[st$sorted == 'unsorted', ],
# wl = 512, flim = c(0, 10), dest.path =
# './data/processed/unsorted_spectrograms', pal = viridis,
# collevels = seq(-100, 0, 5)) tailor_sels(st, auto.next = TRUE,
# flim = c(0, 8), collevels = seq(-100, 0, 5))sel_tab <- selection_table(path = "./data/raw/consolidated_files/",
whole.recs = TRUE)
tailored <- read.csv("./data/raw/consolidated_files/seltailor_output.csv")
tailored <- tailored[tailored$tailored == "y", ]
non_tailored <- sel_tab[!sel_tab$sound.files %in% tailored$sound.files,
]
non_tailored$tailored <- "n"
tailored$top.freq[is.na(tailored$bottom.freq)] <- non_tailored$bottom.freq <- min(tailored$bottom.freq,
na.rm = TRUE)
tailored$top.freq[is.na(tailored$top.freq)] <- non_tailored$top.freq <- max(tailored$top.freq,
na.rm = TRUE)
comm_names <- intersect(names(tailored), names(non_tailored))
all_sels <- rbind(tailored[, comm_names], non_tailored[, comm_names])
write.csv(all_sels, "./data/processed/selection_table_entire_sound_files.csv",
row.names = FALSE)sel_tab <- read.csv("./data/processed/selection_table_entire_sound_files.csv")
xcorr <- cross_correlation(X = sel_tab, path = "./data/raw/consolidated_files/",
method = 2, parallel = 1)
rownames(xcorr) <- gsub("-1$", "", rownames(xcorr))
colnames(xcorr) <- gsub("-1$", "", colnames(xcorr))
saveRDS(xcorr, "./data/processed/cross_correlation_matrix.RDS")
# less than 0.1% were undefined
sum(is.infinite(xcorr))/length(xcorr)
# convert infinite to mean xcorr
xcorr[is.infinite(xcorr)] <- mean(xcorr[!is.infinite(xcorr) & xcorr <
1])
xcorr_mds <- cmdscale(d = as.dist(xcorr), k = 2)
rownames(xcorr_mds) <- gsub("-1$", "", rownames(xcorr_mds))
saveRDS(xcorr_mds, "./data/processed/cross_correlation_MDS.RDS")
# measure acoustic features
sel_tab$bottom.freq[is.na(sel_tab$bottom.freq)] <- min(sel_tab$bottom.freq,
na.rm = TRUE)
sel_tab$top.freq <- max(sel_tab$top.freq)
acous_feat <- spectro_analysis(X = sel_tab, path = "./data/raw/consolidated_files/",
parallel = 30)
acous_feat$region <- sapply(acous_feat$sound.files, function(x) dat$Regions4[dat$New_Name ==
x][1])
acous_feat$recording <- substr(acous_feat$sound.files, 0, 4)
saveRDS(acous_feat, "./data/processed/acoustic_features.RDS")# add data from second location
dat <- read.csv("./data/raw/consolidated_sound_files_CPV_contact_calls_USEaug2026 - UPDATED_USE for analyses.csv")
names(dat)[grep("Regions..4.", names(dat))] <- "Regions4"
names(dat) <- gsub("..cluster.", ".for.cluster", names(dat))
dat <- dat[!is.na(dat$Location.for.cluster) & !is.na(dat$Longitude.for.cluster) &
!is.na(dat$Latitude.for.cluster), ]agg <- aggregate(Location.for.cluster ~ Regions4, dat, function(x) length(unique(x)))
names(agg) <- c("region", "localities")
agg$calls <- aggregate(Location.for.cluster ~ Regions4, dat, length)[,
2]
agg$localities <- aggregate(Location.for.cluster ~ Regions4, dat,
function(x) paste(unique(x), collapse = "-"))[, 2]
agg| region | localities | calls |
|---|---|---|
| central subA | Jetty River Lodge-Ntafufu Ecolodge | 687 |
| central subB | Polela Sawmill-iGxalingenwa Nature Reserve-Marutswa Forest-Salt Spring Farm-Hoha Forest | 1082 |
| northern | Amorentia | 717 |
| southern | Hogsback-Schwarzwald Forest-Alice Pecan Orchard-Stutterheim-King William’s Town-Baddaford Farm | 3801 |
agg <- aggregate(Regions4 ~ Location.for.cluster, dat, function(x) paste(unique(x),
collapse = "-"))
names(agg) <- c("localities", "region")
agg[order(agg$region), 2:1]| region | localities | |
|---|---|---|
| 7 | central subA | Jetty River Lodge |
| 10 | central subA | Ntafufu Ecolodge |
| 5 | central subB | Hoha Forest |
| 6 | central subB | iGxalingenwa Nature Reserve |
| 9 | central subB | Marutswa Forest |
| 11 | central subB | Polela Sawmill |
| 12 | central subB | Salt Spring Farm |
| 2 | northern | Amorentia |
| 1 | southern | Alice Pecan Orchard |
| 3 | southern | Baddaford Farm |
| 4 | southern | Hogsback |
| 8 | southern | King William’s Town |
| 13 | southern | Schwarzwald Forest |
| 14 | southern | Stutterheim |
To evaluate whether Cape Parrot contact call structure is better explained by region, locality, geographic distance, or some combination of these, we fitted a set of competing Bayesian mixed-effects models on pairwise acoustic dissimilarities between recordings, accounting for the non-independence inherent to pairwise distance data.
Model specifications
Six competing models were specified, each sharing the same multi-membership random-effect structure but differing in which fixed effect(s) they include, to test via leave-one-out cross-validation (LOO) whether region, locality, geographic distance, or some combination best explains acoustic dissimilarity between recordings:
\[ \text{mod\_region}: \quad \text{acoustic dissimilarity}_{ij} \sim \text{same region}_{ij} + (1 \mid \text{mm(recording}_i, \text{recording}_j)) \]
\[ \text{mod\_locality}: \quad \text{acoustic dissimilarity}_{ij} \sim \text{same locality}_{ij} + (1 \mid \text{mm(recording}_i, \text{recording}_j)) \]
\[ \text{mod\_geodist}: \quad \text{acoustic dissimilarity}_{ij} \sim \text{geographic distance}_{ij} + (1 \mid \text{mm(recording}_i, \text{recording}_j)) \]
\[ \text{mod\_region\_geo}: \quad \text{acoustic dissimilarity}_{ij} \sim \text{same region}_{ij} \times \text{geographic distance}_{ij} + (1 \mid \text{mm(recording}_i, \text{recording}_j)) \]
\[ \text{mod\_both}: \quad \text{acoustic dissimilarity}_{ij} \sim \text{same region}_{ij} + \text{same locality}_{ij} + (1 \mid \text{mm(recording}_i, \text{recording}_j)) \]
\[ \text{mod\_all}: \quad \text{acoustic dissimilarity}_{ij} \sim \text{same region}_{ij} + \text{same locality}_{ij} + \text{geographic distance}_{ij} + (1 \mid \text{mm(recording}_i, \text{recording}_j)) \]
where:
\(\text{acoustic dissimilarity}_{ij}\) is the mean pairwise cross-correlation-based dissimilarity (\(1 - r\)) between all calls of recording \(i\) and all calls of recording \(j\).
\(\text{same region}_{ij}\) and \(\text{same locality}_{ij}\) are binary predictors indicating whether recordings \(i\) and \(j\) come from the same region/locality (1) or different ones (0).
\(\text{geographic distance}_{ij}\) is the great-circle (haversine) distance between recordings \(i\) and \(j\), log-transformed (25th-percentile offset added before log to avoid zero-distance pairs at the same locality) and standardized by 2 SD (Gelman 2008), so its coefficient is on a comparable “typical full swing” scale to the binary predictors.
mm(recording\(_i\), recording\(_j\)) is a multi-membership random intercept accounting for the repeated use of the same recordings across pairwise comparisons.
To isolate whether geographic distance matters specifically within regions - as opposed to only driving the separation between regions - a seventh model was fit on the subset of same-region pairs only:
\[ \text{mod\_geodist\_within\_region}: \quad \text{acoustic dissimilarity}_{ij} \sim \text{geographic distance}_{ij} + (1 \mid \text{mm(recording}_i, \text{recording}_j)), \quad \text{same region}_{ij} = \text{same} \]
This avoids the extrapolation problem inherent to reading mod_region_geo‘s main effect: same-region and different-region pairs occupy almost non-overlapping ranges of geographic distance (regions are, by construction, spatially clustered), so any single-point comparison between the two groups’ fitted lines extrapolates at least one of them well outside its supported data range. A model fit on same-region pairs alone estimates the within-region distance slope directly, with no such extrapolation.
(1 | mm(rec1, rec2)), because each recording contributes to multiple pairwise comparisons and its pairs are not independent.phi).cmdstanr backend, with within-chain thread parallelization, 4 chains, 4 cores, and 4,000 iterations per chain.xcorr <- readRDS("./data/processed/cross_correlation_matrix.RDS")
dat$recording <- substr(dat$Old_Name, 0, 4)
## ---------------------------------------------------------------
## Region vs. locality as predictors of acoustic (dis)similarity
## brms multi-membership model, recording as the mm() grouping
## factor every pairwise combination of the 116 recordings,
## filled with mean acoustic distance across their calls
## ---------------------------------------------------------------
## 2. similarity -> dissimilarity (full matrix, keep symmetric,
## no NAs)
## -----------------------------------------------------------------------
dissim <- 1 - xcorr
## -----------------------------------------------------------------------
## 3. all pairwise combinations of the 116 recordings, filled
## with the mean call-level dissimilarity between the two
## recordings
## -----------------------------------------------------------------------
rec_ids <- dat$recording # recording id per call, same order as dissim
recordings <- sort(unique(rec_ids)) # 116 unique recordings
n_rec <- length(recordings)
pairs_idx <- t(combn(n_rec, 2)) # 6670 combinations, 116 choose 2
dist_dat <- data.frame(recording.1 = recordings[pairs_idx[, 1]], recording.2 = recordings[pairs_idx[,
2]], stringsAsFactors = FALSE)
dist_dat$mean_dissim <- sapply(seq_len(nrow(pairs_idx)), function(p) {
f1 <- dat$New_Name[dat$recording == dist_dat$recording.1[p]]
f2 <- dat$New_Name[dat$recording == dist_dat$recording.2[p]]
dists <- as.vector(dissim[rownames(dissim) %in% f1, colnames(dissim) %in%
f2])
dists <- dists[!is.infinite(dists)]
mean(dists, na.rm = TRUE)
})
## -----------------------------------------------------------------------
## 4. attach one locality / region per recording (assumes a
## recording was made at a single site - check this holds in
## your data)
## -----------------------------------------------------------------------
dist_dat$locality.1 <- sapply(dist_dat$recording.1, function(x) unique(dat$Location.for.cluster[dat$recording ==
x])[1])
dist_dat$locality.2 <- sapply(dist_dat$recording.2, function(x) unique(dat$Location.for.cluster[dat$recording ==
x])[1])
dist_dat$region.1 <- sapply(dist_dat$recording.1, function(x) unique(dat$Regions4[dat$recording ==
x])[1])
dist_dat$region.2 <- sapply(dist_dat$recording.2, function(x) unique(dat$Regions4[dat$recording ==
x])[1])
# measure geographic distance
dist_dat$geo_distance <- sapply(seq_len(nrow(dist_dat)), function(x) {
coords1 <- dat[dat$recording == dist_dat$recording.1[x], c("Longitude",
"Latitude")][1, ]
coords2 <- dat[dat$recording == dist_dat$recording.2[x], c("Longitude",
"Latitude")][1, ]
coords <- as.matrix(rbind(coords1, coords2))
dist_geo_mat <- distm(coords, fun = distHaversine)/1000
return(dist_geo_mat[1, 2])
})
geo_const <- unname(quantile(dist_dat$geo_distance, 0.25, na.rm = TRUE))
dist_dat$log_geo_distance <- log(dist_dat$geo_distance + geo_const)
## Divide by 2 SD, not 1 SD (Gelman 2008): puts this continuous
## predictor's coefficient on the same 'typical full swing'
## scale as the untouched binary same_population (0/1)
## predictor.
dist_dat$geo_distance_sc <- scale(dist_dat$log_geo_distance)[, 1]/2
dist_dat$same_locality <- factor(ifelse(dist_dat$locality.1 == dist_dat$locality.2,
"same", "different"), levels = c("different", "same"))
dist_dat$same_region <- factor(ifelse(dist_dat$region.1 == dist_dat$region.2,
"same", "different"), levels = c("different", "same"))
names(dist_dat)[names(dist_dat) %in% c("recording.1", "recording.2")] <- c("rec1",
"rec2")
# dist_dat$rec1 <- factor(dist_dat$rec1) dist_dat$rec2 <-
# factor(dist_dat$rec2)
## -----------------------------------------------------------------------
## 5. dissimilarity to open (0,1) interval for beta family
## -----------------------------------------------------------------------
rescale01 <- function(x, eps = 1e-04) (x * (1 - 2 * eps)) + eps
dist_dat$dissim_beta <- rescale01(dist_dat$mean_dissim)
## -----------------------------------------------------------------------
## 5b. check collinearity among predictors before fitting
## multi-predictor models - locality is nested within region and
## geo_distance is partly confounded with both, so this is worth
## checking explicitly
## -----------------------------------------------------------------------
saveRDS(dist_dat, "./data/processed/dist_dat.RDS")
# correlation matrix (binary predictors coded 0/1 for
# correlation purposes)
predictor_cor <- data.frame(same_region_num = as.numeric(dist_dat$same_region ==
"same"), same_locality_num = as.numeric(dist_dat$same_locality ==
"same"), geo_distance_sc = dist_dat$geo_distance_sc)
cor_mat <- cor(predictor_cor, use = "complete.obs")
kable(cor_mat, digits = 2)
saveRDS(cor_mat, "./data/processed/predictor_correlation_matrix.RDS")
# VIF (generalized VIF for factors) on the fullest fixed-effect
# specification - run once mod_all is fitted, see below
# performance::check_collinearity(mod_all)
## -----------------------------------------------------------------------
## 6. priors
## -----------------------------------------------------------------------
priors <- c(prior(normal(0, 1), class = "Intercept"), prior(normal(0,
0.5), class = "b"), prior(exponential(2), class = "sd"), prior(exponential(1),
class = "phi"))
## -----------------------------------------------------------------------
## 7. competing models: locality vs region, same mm() structure
## -----------------------------------------------------------------------
mod_region <- brm(bf(dissim_beta ~ same_region + (1 | mm(rec1, rec2))),
data = dist_dat, family = Beta(), prior = priors, chains = 4,
cores = 4, iter = 4000, warmup = 1000, backend = "cmdstanr", threads = threading(8),
control = list(adapt_delta = 0.95), seed = 123, file = "./data/processed/brms_model_region")
mod_locality <- brm(bf(dissim_beta ~ same_locality + (1 | mm(rec1,
rec2))), data = dist_dat, family = Beta(), prior = priors, backend = "cmdstanr",
threads = threading(8), chains = 4, cores = 4, iter = 4000, warmup = 1000,
control = list(adapt_delta = 0.95), seed = 123, file = "./data/processed/brms_model_locality")
mod_geodist <- brm(bf(dissim_beta ~ geo_distance_sc + (1 | mm(rec1,
rec2))), data = dist_dat, family = Beta(), prior = priors, backend = "cmdstanr",
threads = threading(8), chains = 4, cores = 4, iter = 4000, warmup = 1000,
control = list(adapt_delta = 0.95), seed = 123, file = "./data/processed/brms_model_geographic_distance")
mod_geodist_region <- brm(bf(dissim_beta ~ same_region * geo_distance_sc +
(1 | mm(rec1, rec2))), data = dist_dat, family = Beta(), prior = priors,
backend = "cmdstanr", threads = threading(8), chains = 4, cores = 4,
iter = 4000, warmup = 1000, control = list(adapt_delta = 0.95),
seed = 123, file = "./data/processed/brms_model_geographic_distance_region_interaction")
# full model with both terms - check for
# collinearity/confounding between region and locality before
# trusting this one
mod_both <- brm(bf(dissim_beta ~ same_region + same_locality + (1 |
mm(rec1, rec2))), data = dist_dat, family = Beta(), prior = priors,
backend = "cmdstanr", threads = threading(8), chains = 4, cores = 4,
iter = 4000, warmup = 1000, control = list(adapt_delta = 0.95),
seed = 123, file = "./data/processed/brms_model_both")
mod_all <- brm(bf(dissim_beta ~ same_region + same_locality + geo_distance_sc +
(1 | mm(rec1, rec2))), data = dist_dat, family = Beta(), prior = priors,
backend = "cmdstanr", threads = threading(8), chains = 4, cores = 4,
iter = 4000, warmup = 1000, control = list(adapt_delta = 0.95),
seed = 123, file = "./data/processed/brms_model_all")
# VIF on the full fixed-effect specification - run now that
# mod_all exists
performance::check_collinearity(mod_all)
## -----------------------------------------------------------------------
## 7b. within-region subset model: isolates the
## geographic-distance effect at the scale where it can actually
## be estimated without extrapolating across the near-disjoint
## distance ranges of same-region vs. different-region pairs
## (see caveat in Model specifications above)
## -----------------------------------------------------------------------
dist_dat_same_region <- dist_dat[dist_dat$same_region == "same", ]
mod_geodist_within_region <- brm(bf(dissim_beta ~ geo_distance_sc +
(1 | mm(rec1, rec2))), data = dist_dat_same_region, family = Beta(),
prior = priors, backend = "cmdstanr", threads = threading(8),
chains = 4, cores = 4, iter = 4000, warmup = 1000, control = list(adapt_delta = 0.95),
seed = 123, file = "./data/processed/brms_model_geographic_distance_within_region")
## -----------------------------------------------------------------------
## 8. model comparison
## -----------------------------------------------------------------------
mod_region <- add_criterion(mod_region, "loo")
mod_locality <- add_criterion(mod_locality, "loo")
mod_both <- add_criterion(mod_both, "loo")
mod_geodist <- add_criterion(mod_geodist, "loo")
mod_geodist_region <- add_criterion(mod_geodist_region, "loo")
mod_all <- add_criterion(mod_all, "loo")
# fit on a different (smaller, same-region-only) dataset, so
# this is NOT comparable via loo_compare() to the models above -
# reported separately
mod_geodist_within_region <- add_criterion(mod_geodist_within_region,
"loo")The following table ranks models by out-of-sample predictive fit (elpd_loo), with the top model set to elpd_diff = 0 and all others shown as the gap below it, plus a standard error (se_diff) for that gap.
mod_region <- readRDS("./data/processed/brms_model_region.rds")
mod_locality <- readRDS("./data/processed/brms_model_locality.rds")
mod_both <- readRDS("./data/processed/brms_model_both.rds")
mod_region_geo <- readRDS("./data/processed/brms_model_geographic_distance_region_interaction.rds")
mod_all <- readRDS("./data/processed/brms_model_all.rds")
mod_geodist <- readRDS("./data/processed/brms_model_geographic_distance.rds")
mod_geodist_within_region <- readRDS("./data/processed/brms_model_geographic_distance_within_region.rds")
loo_comp <- loo_compare(mod_region, mod_locality, mod_both, mod_geodist,
mod_region_geo, mod_all)
kable(as.data.frame(loo_comp), digits = 2)| elpd_diff | se_diff | elpd_loo | se_elpd_loo | p_loo | se_p_loo | looic | se_looic | |
|---|---|---|---|---|---|---|---|---|
| mod_region_geo | 0.00 | 0.00 | 11132.90 | 88.52 | 114.27 | 3.39 | -22265.80 | 177.03 |
| mod_all | -8.47 | 6.78 | 11124.43 | 88.85 | 114.67 | 3.39 | -22248.87 | 177.70 |
| mod_geodist | -17.87 | 6.19 | 11115.03 | 88.59 | 113.57 | 3.37 | -22230.06 | 177.17 |
| mod_both | -25.89 | 8.23 | 11107.01 | 91.98 | 118.83 | 3.52 | -22214.01 | 183.95 |
| mod_region | -27.08 | 7.72 | 11105.82 | 88.90 | 113.22 | 3.36 | -22211.65 | 177.80 |
| mod_locality | -77.07 | 15.11 | 11055.82 | 92.22 | 118.18 | 3.53 | -22111.65 | 184.44 |
mod_region_geo (the region x distance interaction) has the best expected out-of-sample fit of all six models - clearly ahead of mod_geodist alone (Δ = -17.87, SE 6.19, ~3 SEs), and far ahead of mod_region, mod_both, and mod_locality. mod_all (all three predictors, no interaction) is statistically indistinguishable from mod_region_geo (Δ = -8.47, SE 6.78, well under 1.5 SEs) but doesn’t isolate the interaction the way mod_region_geo does, so mod_region_geo is the more directly interpretable model for the question of whether distance matters differently within vs. between regions.
same_locality is nested within same_region (every same-locality pair is automatically a same-region pair) and geo_distance_sc is partly confounded with both (regions are, by construction, spatially clustered), so collinearity was checked before trusting coefficients from the multi-predictor models.
dist_dat <- readRDS("./data/processed/dist_dat.RDS")
pred_vars <- c("same_region", "same_locality", "geo_distance_sc")
pred_df <- dist_dat[, pred_vars]
pred_df$same_region <- as.numeric(pred_df$same_region == "same")
pred_df$same_locality <- as.numeric(pred_df$same_locality == "same")
corr_mat <- cor(pred_df, use = "pairwise.complete.obs")
cols <- mako(10, alpha = 0.8, begin = 0.2, end = 0.75)
ggcorrplot(corr_mat, type = "lower", lab = TRUE, lab_size = 4, colors = c(cols[1],
"white", cols[10]), title = "Pairwise predictor correlations")## Variance inflation factors (VIFs) were computed for the fixed
## effects to check for collinearity. VIFs are interpreted
## conventionally: values below 5 indicate low concern, values
## between 5 and 10 indicate moderate concern, and values above
## 10 indicate high concern. In this case, same_locality (VIF =
## 3.1) is low concern, same_region (VIF = 7.4) is moderate
## concern, and geo_distance_sc (VIF = 11.3) is high concern -
## region, locality, and geographic distance all partly encode
## the same underlying spatial structure, so mod_all's
## individual coefficients should not be read as independent
## effects.
vif_mod <- lm(dissim_beta ~ same_region + same_locality + geo_distance_sc,
data = dist_dat)
vif_vals <- car::vif(vif_mod)
vif_df <- data.frame(term = names(vif_vals), vif = as.numeric(vif_vals))
## conventional rule-of-thumb thresholds: <5 low concern, 5-10
## moderate, >10 high.
vif_df$severity <- cut(vif_df$vif, breaks = c(0, 5, 10, Inf), labels = c("low",
"moderate", "high"))
ggplot(vif_df, aes(x = vif, y = reorder(term, vif), color = severity)) +
geom_vline(xintercept = c(5, 10), linetype = "dashed", color = "grey60") +
geom_segment(aes(x = 1, xend = vif, yend = term), linewidth = 1) +
geom_point(size = 3) + scale_color_manual(values = c(low = cols[1],
moderate = "orange", high = cols[10])) + labs(x = "Variance Inflation Factor",
y = NULL, color = "Collinearity") + theme_minimal(base_size = 13)check_collinearity(mod_all)| Term | VIF | VIF_CI_low | VIF_CI_high | SE_factor | Tolerance | Tolerance_CI_low | Tolerance_CI_high |
|---|---|---|---|---|---|---|---|
| same_region | 7.443463 | 7.124530 | 7.779005 | 2.728271 | 0.1343461 | 0.1285511 | 0.1403601 |
| same_locality | 3.060321 | 2.943171 | 3.184535 | 1.749377 | 0.3267631 | 0.3140176 | 0.3397696 |
| geo_distance_sc | 11.321602 | 10.824196 | 11.844192 | 3.364759 | 0.0883267 | 0.0844296 | 0.0923856 |
Collinearity results
geo_distance_sc shows severe collinearity (VIF = 11.3, tolerance = 0.09) — over 91% of its variance is explained by same_region and same_locality combined.same_region also shows concerning collinearity (VIF = 7.4, tolerance = 0.13).same_locality is the least collinear of the three (VIF = 3.1, tolerance = 0.33) but still above the conventional VIF = 5 caution threshold.mod_all (which includes all three predictors together) can’t be cleanly interpreted as independent, “holding-everything-else-constant” effects — they’re each absorbing overlapping variance. mod_all remains valid for LOO/predictive comparison, but its coefficient table shouldn’t be over-interpreted term-by-term.extended_summary(mod_region_geo, highlight = TRUE, print.name = FALSE)| priors | formula | iterations | chains | thinning | warmup | diverg_transitions | rhats > 1.05 | min_bulk_ESS | min_tail_ESS | seed | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | b-normal(0, 0.5) Intercept-normal(0, 1) phi-exponential(1) sd-exponential(2) | dissim_beta ~ same_region * geo_distance_sc + (1 | mm(rec1, rec2)) | 4000 | 4 | 1 | 1000 | 0 (0%) | 0 | 194.019 | 716.161 | 123 |
| Estimate | l-95% CI | u-95% CI | Rhat | Bulk_ESS | Tail_ESS | |
|---|---|---|---|---|---|---|
| b_Intercept | -0.028 | -0.139 | 0.086 | 1.012 | 194.019 | 716.161 |
| b_same_regionsame | 0.197 | 0.113 | 0.280 | 1.001 | 1394.714 | 2366.607 |
| b_geo_distance_sc | 0.481 | 0.345 | 0.617 | 1.001 | 1344.062 | 2342.787 |
| b_same_regionsame:geo_distance_sc | -0.427 | -0.566 | -0.290 | 1.001 | 1425.763 | 2571.987 |
The interaction is the key result: geo_distance_sc (0.481, CI 0.345-0.617) is the distance slope for different-region pairs - dissimilarity rises steeply with distance. The interaction term same_regionsame:geo_distance_sc (-0.427, CI -0.566 to -0.290) nearly cancels that slope for same-region pairs, leaving an effective within-region slope close to flat (≈ 0.05). Geographic distance predicts acoustic dissimilarity strongly between regions, and barely at all within them.
Same-region and different-region pairs occupy almost non-overlapping ranges of geo_distance_sc, since regions are spatially clustered:
dist_dat <- readRDS("./data/processed/dist_dat.RDS")
kable(data.frame(same_region = c("different", "same"), min = tapply(dist_dat$geo_distance_sc,
dist_dat$same_region, min), mean = tapply(dist_dat$geo_distance_sc,
dist_dat$same_region, mean), max = tapply(dist_dat$geo_distance_sc,
dist_dat$same_region, max)), digits = 2, row.names = FALSE)| same_region | min | mean | max |
|---|---|---|---|
| different | 0.31 | 0.59 | 0.87 |
| same | -0.66 | -0.35 | 0.11 |
Because of this near-disjoint range, the model’s main effect for same_region (evaluated at geo_distance_sc = 0, i.e. the overall mean distance) extrapolates outside the real data range for at least one of the two groups, and should not be read as “same-region pairs are more/less dissimilar than different-region pairs” at some typical distance - no such shared typical distance exists in the data. The plot below restricts each group’s fitted line to its own observed distance range to avoid this:
library(ggplot2)
region_ranges <- tapply(dist_dat$geo_distance_sc, dist_dat$same_region,
range)
newdat <- do.call(rbind, lapply(names(region_ranges), function(r) {
data.frame(same_region = r, geo_distance_sc = seq(region_ranges[[r]][1],
region_ranges[[r]][2], length.out = 100))
}))
preds <- fitted(mod_region_geo, newdata = newdat, re_formula = NA)
newdat <- cbind(newdat, preds)
ggplot(newdat, aes(x = geo_distance_sc, y = Estimate, color = same_region,
fill = same_region)) + geom_ribbon(aes(ymin = Q2.5, ymax = Q97.5),
alpha = 0.3, color = NA) + geom_line(linewidth = 1) + labs(x = "geo_distance_sc",
y = "dissim_beta", color = "same_region", fill = "same_region") +
theme_minimal()Restricted to where each group actually has data: the same-region line is flat across its whole observed range (~0.53-0.55), while the different-region line rises steeply across its own range (~0.42-0.55). The two lines are not meaningfully compared at a shared distance value, since they don’t share a common range - only the difference in slopes is well supported.
To estimate the within-region distance slope directly, without extrapolating across groups, mod_geodist_within_region was fit on same-region pairs only:
extended_summary(mod_geodist_within_region, highlight = TRUE, print.name = FALSE)| priors | formula | iterations | chains | thinning | warmup | diverg_transitions | rhats > 1.05 | min_bulk_ESS | min_tail_ESS | seed | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | b-normal(0, 0.5) Intercept-normal(0, 1) phi-exponential(1) sd-exponential(2) | dissim_beta ~ geo_distance_sc + (1 | mm(rec1, rec2)) | 4000 | 4 | 1 | 1000 | 0 (0%) | 0 | 270.76 | 727.738 | 123 |
| Estimate | l-95% CI | u-95% CI | Rhat | Bulk_ESS | Tail_ESS | |
|---|---|---|---|---|---|---|
| b_Intercept | 0.166 | 0.093 | 0.239 | 1.015 | 270.76 | 727.738 |
| b_geo_distance_sc | 0.070 | 0.033 | 0.106 | 1.001 | 7078.57 | 7956.087 |
extended_summary(mod_all, highlight = TRUE, print.name = FALSE)| priors | formula | iterations | chains | thinning | warmup | diverg_transitions | rhats > 1.05 | min_bulk_ESS | min_tail_ESS | seed | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | b-normal(0, 0.5) Intercept-normal(0, 1) phi-exponential(1) sd-exponential(2) | dissim_beta ~ same_region + same_locality + geo_distance_sc + (1 | mm(rec1, rec2)) | 4000 | 4 | 1 | 1000 | 0 (0%) | 0 | 202.281 | 316.368 | 123 |
| Estimate | l-95% CI | u-95% CI | Rhat | Bulk_ESS | Tail_ESS | |
|---|---|---|---|---|---|---|
| b_Intercept | 0.152 | 0.063 | 0.237 | 1.03 | 202.281 | 316.368 |
| b_same_regionsame | 0.039 | -0.012 | 0.089 | 1 | 3412.251 | 5030.299 |
| b_same_localitysame | 0.053 | 0.028 | 0.078 | 1 | 4185.206 | 6139.623 |
| b_geo_distance_sc | 0.171 | 0.115 | 0.227 | 1.001 | 3225.264 | 4702.258 |
extended_summary(mod_both, highlight = TRUE, print.name = FALSE)| priors | formula | iterations | chains | thinning | warmup | diverg_transitions | rhats > 1.05 | min_bulk_ESS | min_tail_ESS | seed | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | b-normal(0, 1) Intercept-normal(0, 2) phi-gamma(0.01, 0.01) sd-student_t(3, 0, 2.5) | dissim_beta ~ same_region + same_locality + (1 | mm(rec1, rec2)) | 4000 | 4 | 1 | 1000 | 0 (0%) | 0 | 215.76 | 463.087 | 123 |
| Estimate | l-95% CI | u-95% CI | Rhat | Bulk_ESS | Tail_ESS | |
|---|---|---|---|---|---|---|
| b_Intercept | 0.262 | 0.193 | 0.331 | 1.041 | 215.760 | 463.087 |
| b_same_regionsame | -0.104 | -0.123 | -0.085 | 1 | 5266.276 | 7306.824 |
| b_same_localitysame | -0.008 | -0.022 | 0.007 | 1 | 10168.787 | 8880.007 |
mod_all’s same_region coefficient is no longer credible once locality and distance are both included (CI crosses zero) - its signal is carried through the interaction with distance (above), not as a distance-independent main effect, so this isn’t a contradiction of the interaction result. mod_all’s same_locality coefficient is credible but positive - the opposite sign from its (non-credible) direction in mod_both - which is a collinearity artifact rather than a real effect: same-locality pairs have geo_distance_sc close to zero by construction, so once distance enters the model this coefficient partly re-absorbs “distance ≈ 0” rather than a clean locality effect. mod_both (no distance) reproduces the original, simpler finding: same_region is credibly negative (pairs from the same region are more similar) and same_locality is not credible - locality adds nothing once region is in the model.
Note: b_Intercept shows Rhat slightly above 1.01 with reduced ESS in mod_all and mod_both (the fixed-effect and interaction coefficients are all well converged, Rhat ~1.00). Worth increasing iter or inspecting trace plots for the intercept before treating these as final.
# effect on the response scale
conditional_effects(mod_region, effects = "same_region")conditional_effects(mod_locality, effects = "same_locality")Region structures acoustic similarity, but not through a distance-independent effect - it works specifically through its interaction with geographic distance. The best-fitting model (mod_region_geo) shows a steep distance effect between regions and a much weaker one within regions.
Within a region, geographic distance has only a small effect on acoustic dissimilarity - the within-region-only model (mod_geodist_within_region) finds a credible but small positive slope (0.070, CI 0.033-0.106), and the effective within-region slope in mod_region_geo (≈0.05) is consistent with this: distance matters far less within a region than between regions, but not literally zero.
Between regions, geographic distance predicts dissimilarity strongly (0.481, CI 0.345-0.617 in mod_region_geo) - more distant regions are more acoustically distinct, an effect several times larger than the within-region one.
Locality adds nothing beyond region once distance is properly accounted for. mod_both (region + locality, no distance) shows same_locality is not credible - locality contributes nothing once region is included. mod_all (region + locality + distance together) shows both same_locality and geo_distance_sc as credible, but VIF values of 7.4 (region), 11.3 (distance), and 3.1 (locality) indicate severe collinearity among all three predictors when combined - mod_all’s individual coefficients should not be read as independent effects, since region, locality, and geographic distance all partly encode the same underlying spatial structure.
Caution on same-region vs. different-region pairs is needed at any single point. These two groups occupy near-disjoint ranges of geographic distance (regions are spatially clustered), so comparing their fitted lines at one distance value extrapolates outside the real data for at least one group. Only the difference in slopes is well supported; a difference in intercepts is not.
Practical conclusion: this argues for regional acoustic cohesion that holds fairly independently of within-region distance, alongside genuine divergence between regions that scales strongly with how far apart they are - consistent with strong differences between regions without unique, geography-independent dialects at finer scales. There are many variants within regions, but they remain more similar to each other than to variants from other regions, with only a modest additional pull from distance within the region itself.
acous_feat <- readRDS("./data/processed/acoustic_features.RDS")
acous_feat$region <- as.factor(acous_feat$region)
acous_feat$recording <- as.factor(acous_feat$recording)
## sp.ent is a proportion (0-1) - rescale to the open interval for the
## beta family, and rename without a "." (same lesson as mm() earlier:
## brms's multivariate resp identifiers are built by stripping "." and
## "_" from the column name, so "sp.ent" and "sp_ent" would collide;
## use a clean name up front to avoid ambiguity)
rescale01 <- function(x, eps = 1e-4) (x * (1 - 2 * eps)) + eps
acous_feat$spent <- rescale01(acous_feat$sp.ent)
## -----------------------------------------------------------------------
## three response equations, one family each, sharing recording as a
## correlated random intercept: (1 | p | recording) uses the same ID
## letter "p" across all three so brms estimates a correlation matrix
## between the recording-level intercepts of duration, meanpeakf, and
## spectral entropy - i.e. do "noisier"/atypical recordings shift all
## three acoustic measures together, not just one at a time
## -----------------------------------------------------------------------
bf_duration <- bf(duration ~ region + (1 | p | recording), family = lognormal())
bf_meanpeakf <- bf(meanpeakf ~ region + (1 | p | recording), family = gaussian())
bf_spent <- bf(spent ~ region + (1 | p | recording), family = Beta())
priors <- c(
prior(normal(0, 1), class = "Intercept", resp = "duration"),
prior(normal(0, 0.5), class = "b", resp = "duration"),
prior(exponential(2), class = "sd", resp = "duration"),
prior(exponential(1), class = "sigma", resp = "duration"),
prior(normal(0, 1), class = "Intercept", resp = "meanpeakf"),
prior(normal(0, 0.5), class = "b", resp = "meanpeakf"),
prior(exponential(2), class = "sd", resp = "meanpeakf"),
prior(exponential(1), class = "sigma", resp = "meanpeakf"),
prior(normal(0, 1), class = "Intercept", resp = "spent"),
prior(normal(0, 0.5), class = "b", resp = "spent"),
prior(exponential(2), class = "sd", resp = "spent")
)
mod_mv_region <- brm(
bf_duration + bf_meanpeakf + bf_spent,
data = acous_feat,
prior = priors,
backend = "cmdstanr", threads = threading(8),
chains = 4, cores = 4, iter = 4000, warmup = 1000,
control = list(adapt_delta = 0.95),
seed = 123,
file = "./data/processed/brms_model_multivariate_region"
)
extended_summary(mod_mv_region, highlight = TRUE, print.name = FALSE)<table class="table table-striped table-hover table-condensed table-responsive" style="font-size: 12px; width: auto !important; margin-left: auto; margin-right: auto;">
<thead>
<tr>
<th style="text-align:left;"> </th>
<th style="text-align:left;"> priors </th>
<th style="text-align:left;"> formula </th>
<th style="text-align:right;"> iterations </th>
<th style="text-align:right;"> chains </th>
<th style="text-align:right;"> thinning </th>
<th style="text-align:right;"> warmup </th>
<th style="text-align:left;"> diverg_transitions </th>
<th style="text-align:left;"> rhats > 1.05 </th>
<th style="text-align:right;"> min_bulk_ESS </th>
<th style="text-align:right;"> min_tail_ESS </th>
<th style="text-align:left;"> seed </th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left;"> 1 </td>
<td style="text-align:left;"> b-normal(0, 0.5)
b-normal(0, 0.5)
b-normal(0, 0.5)
Intercept-normal(0, 1)
Intercept-normal(0, 1)
Intercept-normal(0, 1)
L-lkj_corr_cholesky(1)
phi-gamma(0.01, 0.01)
sd-exponential(2)
sd-exponential(2)
sd-exponential(2)
sigma-exponential(1)
sigma-exponential(1) </td>
<td style="text-align:left;"> list(duration = list(formula = duration ~ region + (1 | p | recording), pforms = list(), pfix = list(), family = list(family = "lognormal", link = "identity", linkfun = function (mu)
link(mu, link = slink), linkinv = function (eta)
inv_link(eta, link = slink), dpars = c("mu", "sigma"), type = "real", ybounds = c(0, Inf), closed = c(FALSE, NA), ad = c("weights", "subset", "cens", "trunc", "mi", "index"), specials = "logscale", link_sigma = "log"), resp = "duration", mecor = TRUE), meanpeakf = list(formula = meanpeakf ~ region + (1 | p | recording), pforms = list(), pfix = list(), family = list(family = "gaussian", link = "identity", linkfun = function (mu)
link(mu, link = slink), linkinv = function (eta)
inv_link(eta, link = slink), dpars = c("mu", "sigma"), type = "real", ybounds = c(-Inf, Inf), closed = c(NA, NA), ad = c("weights", "subset", "se", "cens", "trunc", "mi", "index"), normalized = c("_time_hom", "_time_het", "_lagsar", "_errorsar", "_fcor"), specials = c("residuals", "rescor")), resp = "meanpeakf", mecor = TRUE), spent = list(formula = spent ~ region + (1 | p | recording), pforms = list(), pfix = list(), family = list(family = "beta", link = "logit", linkfun = function (mu)
link(mu, link = slink), linkinv = function (eta)
inv_link(eta, link = slink), dpars = c("mu", "phi"), type = "real", ybounds = c(0, 1), closed = c(FALSE, FALSE), ad = c("weights", "subset", "cens", "trunc", "mi", "index"), link_phi = "log"), resp = "spent", mecor = TRUE)) </td>
<td style="text-align:right;"> 4000 </td>
<td style="text-align:right;"> 4 </td>
<td style="text-align:right;"> 1 </td>
<td style="text-align:right;"> 1000 </td>
<td style="text-align:left;"> <span style=" ">0 (0%)</span> </td>
<td style="text-align:left;"> <span style=" ">0</span> </td>
<td style="text-align:right;"> 2576.34 </td>
<td style="text-align:right;"> 4389.989 </td>
<td style="text-align:left;"> 123 </td>
</tr>
</tbody>
</table><table class="table table-striped table-hover table-condensed table-responsive" style="font-size: 12px; width: auto !important; margin-left: auto; margin-right: auto;">
<thead>
<tr>
<th style="text-align:left;"> </th>
<th style="text-align:right;"> Estimate </th>
<th style="text-align:right;"> l-95% CI </th>
<th style="text-align:right;"> u-95% CI </th>
<th style="text-align:left;"> Rhat </th>
<th style="text-align:right;"> Bulk_ESS </th>
<th style="text-align:right;"> Tail_ESS </th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left;background-color: rgba(109, 205, 89, 77) !important;"> b_duration_Intercept </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> -0.730 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> -0.935 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> -0.528 </td>
<td style="text-align:left;background-color: rgba(109, 205, 89, 77) !important;"> <span style=" ">1.001</span> </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 2801.270 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 4658.267 </td>
</tr>
<tr>
<td style="text-align:left;background-color: rgba(109, 205, 89, 77) !important;"> b_meanpeakf_Intercept </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 0.807 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 0.341 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 1.279 </td>
<td style="text-align:left;background-color: rgba(109, 205, 89, 77) !important;"> <span style=" ">1.001</span> </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 3951.186 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 5959.060 </td>
</tr>
<tr>
<td style="text-align:left;background-color: rgba(109, 205, 89, 77) !important;"> b_spent_Intercept </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 2.324 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 2.172 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 2.472 </td>
<td style="text-align:left;background-color: rgba(109, 205, 89, 77) !important;"> <span style=" ">1.001</span> </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 3816.104 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 5879.942 </td>
</tr>
<tr>
<td style="text-align:left;background-color: rgba(109, 205, 89, 77) !important;"> b_duration_regioncentralsubB </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> -0.353 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> -0.568 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> -0.131 </td>
<td style="text-align:left;background-color: rgba(109, 205, 89, 77) !important;"> <span style=" ">1.001</span> </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 2654.846 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 4569.530 </td>
</tr>
<tr>
<td style="text-align:left;background-color: rgba(109, 205, 89, 77) !important;"> b_duration_regionnorthern </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> -0.545 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> -0.850 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> -0.233 </td>
<td style="text-align:left;background-color: rgba(109, 205, 89, 77) !important;"> <span style=" ">1</span> </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 3506.345 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 5287.439 </td>
</tr>
<tr>
<td style="text-align:left;background-color: rgba(109, 205, 89, 77) !important;"> b_duration_regionsouthern </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> -0.474 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> -0.683 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> -0.264 </td>
<td style="text-align:left;background-color: rgba(109, 205, 89, 77) !important;"> <span style=" ">1.001</span> </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 2576.340 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 4389.989 </td>
</tr>
<tr>
<td style="text-align:left;"> b_meanpeakf_regioncentralsubB </td>
<td style="text-align:right;"> 0.190 </td>
<td style="text-align:right;"> -0.304 </td>
<td style="text-align:right;"> 0.684 </td>
<td style="text-align:left;"> <span style=" ">1.001</span> </td>
<td style="text-align:right;"> 4292.430 </td>
<td style="text-align:right;"> 6213.882 </td>
</tr>
<tr>
<td style="text-align:left;"> b_meanpeakf_regionnorthern </td>
<td style="text-align:right;"> -0.456 </td>
<td style="text-align:right;"> -1.143 </td>
<td style="text-align:right;"> 0.215 </td>
<td style="text-align:left;"> <span style=" ">1</span> </td>
<td style="text-align:right;"> 6297.589 </td>
<td style="text-align:right;"> 8204.507 </td>
</tr>
<tr>
<td style="text-align:left;"> b_meanpeakf_regionsouthern </td>
<td style="text-align:right;"> 0.353 </td>
<td style="text-align:right;"> -0.122 </td>
<td style="text-align:right;"> 0.826 </td>
<td style="text-align:left;"> <span style=" ">1.001</span> </td>
<td style="text-align:right;"> 3963.790 </td>
<td style="text-align:right;"> 6101.431 </td>
</tr>
<tr>
<td style="text-align:left;background-color: rgba(109, 205, 89, 77) !important;"> b_spent_regioncentralsubB </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> -0.575 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> -0.738 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> -0.410 </td>
<td style="text-align:left;background-color: rgba(109, 205, 89, 77) !important;"> <span style=" ">1.001</span> </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 3976.678 </td>
<td style="text-align:right;background-color: rgba(109, 205, 89, 77) !important;"> 6723.934 </td>
</tr>
<tr>
<td style="text-align:left;"> b_spent_regionnorthern </td>
<td style="text-align:right;"> -0.207 </td>
<td style="text-align:right;"> -0.436 </td>
<td style="text-align:right;"> 0.020 </td>
<td style="text-align:left;"> <span style=" ">1.001</span> </td>
<td style="text-align:right;"> 4409.911 </td>
<td style="text-align:right;"> 6945.017 </td>
</tr>
<tr>
<td style="text-align:left;"> b_spent_regionsouthern </td>
<td style="text-align:right;"> -0.087 </td>
<td style="text-align:right;"> -0.239 </td>
<td style="text-align:right;"> 0.071 </td>
<td style="text-align:left;"> <span style=" ">1.001</span> </td>
<td style="text-align:right;"> 3733.371 </td>
<td style="text-align:right;"> 6100.988 </td>
</tr>
</tbody>
</table>
conditional_effects(mod_mv_region, effects = "region", resp = "duration")conditional_effects(mod_mv_region, effects = "region", resp = "meanpeakf")conditional_effects(mod_mv_region, effects = "region", resp = "spent")## all pairwise region contrasts, one response at a time
emm_duration <- emmeans(mod_mv_region, ~region, resp = "duration")
emm_meanpeakf <- emmeans(mod_mv_region, ~region, resp = "meanpeakf")
emm_spent <- emmeans(mod_mv_region, ~region, resp = "spent")
contrasts_duration <- pairs(emm_duration)
contrasts_meanpeakf <- pairs(emm_meanpeakf)
contrasts_spent <- pairs(emm_spent)
# each of these returns a full posterior for every pairwise
# difference; summary() with HPD intervals is the Bayesian
# analogue of a CI table
summary(contrasts_duration, point.est = median)| contrast | estimate | lower.HPD | upper.HPD |
|---|---|---|---|
| central subA - central subB | 0.3541985 | 0.136489 | 0.572487 |
| central subA - northern | 0.5467765 | 0.230542 | 0.845921 |
| central subA - southern | 0.4748645 | 0.272601 | 0.690762 |
| central subB - northern | 0.1903080 | -0.064800 | 0.474995 |
| central subB - southern | 0.1210120 | 0.046371 | 0.194343 |
| northern - southern | -0.0693550 | -0.339819 | 0.184583 |
summary(contrasts_meanpeakf, point.est = median)| contrast | estimate | lower.HPD | upper.HPD |
|---|---|---|---|
| central subA - central subB | -0.1889290 | -0.669702 | 0.3129130 |
| central subA - northern | 0.4546965 | -0.182350 | 1.1739400 |
| central subA - southern | -0.3538705 | -0.827519 | 0.1171150 |
| central subB - northern | 0.6443407 | -0.024983 | 1.3629080 |
| central subB - southern | -0.1617815 | -0.365023 | 0.0228440 |
| northern - southern | -0.8104185 | -1.502559 | -0.1593541 |
summary(contrasts_spent, point.est = median)| contrast | estimate | lower.HPD | upper.HPD |
|---|---|---|---|
| central subA - central subB | 0.5755130 | 0.4129400 | 0.7395750 |
| central subA - northern | 0.2073405 | -0.0233093 | 0.4310660 |
| central subA - southern | 0.0878271 | -0.0628694 | 0.2455020 |
| central subB - northern | -0.3686090 | -0.5543530 | -0.1792850 |
| central subB - southern | -0.4874686 | -0.5502953 | -0.4283280 |
| northern - southern | -0.1191689 | -0.3001820 | 0.0621004 |
## regional marginal means, one panel per response
plot_emm <- function(emm_obj, title) {
emm_df <- as.data.frame(emm_obj)
ggplot(emm_df, aes(x = region, y = emmean, color = region)) +
geom_pointrange(aes(ymin = lower.HPD, ymax = upper.HPD), linewidth = 0.8,
size = 0.6) + scale_color_viridis_d(option = "mako", begin = 0.2,
end = 0.8) + labs(title = title, x = NULL, y = "Estimated marginal mean") +
theme_minimal(base_size = 12) + theme(legend.position = "none",
axis.text.x = element_text(angle = 30, hjust = 1))
}
p_emm_duration <- plot_emm(emm_duration, "Duration")
p_emm_meanpeakf <- plot_emm(emm_meanpeakf, "Peak frequency")
p_emm_spent <- plot_emm(emm_spent, "Spectral entropy")
p_emm_duration + p_emm_meanpeakf + p_emm_spent## -----------------------------------------------------------------------
## pairwise contrasts, one panel per response - colored by
## whether the 95% HPD interval excludes zero (credible) or not
## -----------------------------------------------------------------------
plot_contrasts <- function(contrast_obj, title) {
contr_df <- as.data.frame(contrast_obj)
contr_df$credible <- contr_df$lower.HPD > 0 | contr_df$upper.HPD <
0
ggplot(contr_df, aes(x = estimate, y = reorder(contrast, estimate),
color = credible)) + geom_vline(xintercept = 0, linetype = "dashed",
color = "grey50") + geom_pointrange(aes(xmin = lower.HPD,
xmax = upper.HPD), linewidth = 0.8, size = 0.5) + scale_color_manual(values = c(`TRUE` = "#357BA2",
`FALSE` = "grey70"), labels = c(`TRUE` = "credible", `FALSE` = "not credible")) +
labs(title = title, x = "Estimated difference", y = NULL,
color = NULL) + theme_minimal(base_size = 12)
}
p_contr_duration <- plot_contrasts(contrasts_duration, "Duration: pairwise contrasts")
p_contr_meanpeakf <- plot_contrasts(contrasts_meanpeakf, "Peak frequency: pairwise contrasts")
p_contr_spent <- plot_contrasts(contrasts_spent, "Spectral entropy: pairwise contrasts")
p_contr_duration/p_contr_meanpeakf/p_contr_spentDuration: central subA is credibly longer than all three other regions (central subB: 0.354, CI 0.137-0.572; northern: 0.547, CI 0.231-0.846; southern: 0.475, CI 0.273-0.691). Among the rest, central subB is credibly longer than southern (0.121, CI 0.046-0.194), but not credibly different from northern (0.190, CI -0.065-0.475). northern vs. southern is not credible. Overall: central subA stands apart with the longest calls; the other three form a less clearly separated group.
Peak frequency: the only credible pairwise contrast is northern - southern (-0.810, CI -1.503 to -0.159) - northern has lower peak frequency than southern. This didn’t show up in the original vs.-reference table, since neither region differed credibly from central subA individually; the full pairwise comparison reveals a real difference the reference-coded coefficients masked. All other pairs (including both vs. central subA) are not credible.
Spectral entropy (log-odds scale): central subB is the outlier region here, credibly lower than every other region - vs. central subA (0.576, CI 0.413-0.740), vs. northern (-0.369, CI -0.554 to -0.179), vs. southern (-0.488, CI -0.550 to -0.428). No other pair is credible (central subA - northern and northern - southern both border zero but don’t clear it).
Putting the three features together: each region stands out on a different axis - central subA on duration (longest calls), central subB on spectral entropy (most tonal/least noisy), and the northern/southern split shows up only in peak frequency. No single region is acoustically distinct across all three features simultaneously; the regional structure is spread across different acoustic dimensions.
Practical note: the pairwise contrasts add real information beyond the original coefficient table specifically for meanpeakf - that response looked like “no regional signal” from the vs.-reference table alone, but the full 6-pair comparison shows northern and southern are genuinely different from each other, just not from the reference region.
─ Session info ───────────────────────────────────────────────────────────────
setting value
version R version 4.5.2 (2025-10-31)
os Ubuntu 22.04.4 LTS
system x86_64, linux-gnu
ui X11
language (EN)
collate en_US.UTF-8
ctype en_US.UTF-8
tz America/Costa_Rica
date 2026-08-26
pandoc 3.6.3 @ /usr/lib/rstudio/resources/app/bin/quarto/bin/tools/x86_64/ (via rmarkdown)
quarto 1.8.25 @ /usr/lib/rstudio/resources/app/bin/quarto/bin/quarto
─ Packages ───────────────────────────────────────────────────────────────────
package * version date (UTC) lib source
abind 1.4-8 2024-09-12 [1] CRAN (R 4.5.2)
ape 5.8-1 2024-12-16 [1] CRAN (R 4.5.2)
arrayhelpers 1.1-0 2020-02-04 [1] CRAN (R 4.5.2)
askpass 1.2.1 2024-10-04 [1] CRAN (R 4.5.2)
backports 1.5.1 2026-04-03 [1] CRAN (R 4.5.2)
bayesplot 1.15.0 2025-12-12 [1] CRAN (R 4.5.2)
bitops 1.0-9 2024-10-03 [1] CRAN (R 4.5.2)
boot 1.3-32 2025-08-29 [1] CRAN (R 4.5.2)
bridgesampling 1.2-1 2025-11-19 [1] CRAN (R 4.5.2)
brio 1.1.5 2024-04-24 [1] CRAN (R 4.5.2)
brms * 2.23.0 2025-09-09 [1] CRAN (R 4.5.2)
brmsish * 1.0.0 2026-03-03 [1] Github (maRce10/brmsish@81ab826)
Brobdingnag 1.2-9 2022-10-19 [1] CRAN (R 4.5.2)
cachem 1.1.0 2024-05-16 [1] CRAN (R 4.5.2)
car 3.1-5 2026-02-03 [1] CRAN (R 4.5.2)
carData 3.0-6 2026-01-30 [1] CRAN (R 4.5.2)
caret * 7.0-1 2024-12-10 [1] CRAN (R 4.5.2)
checkmate 2.3.4 2026-02-03 [1] CRAN (R 4.5.2)
class 7.3-23 2025-01-01 [1] CRAN (R 4.5.2)
classInt 0.4-11 2025-01-08 [1] CRAN (R 4.5.2)
cli 3.6.6 2026-04-09 [1] CRAN (R 4.5.2)
cluster 2.1.8.2 2026-02-05 [1] CRAN (R 4.5.2)
cmdstanr 0.9.0 2025-03-30 [1] https://stan-dev.r-universe.dev (R 4.5.2)
coda 0.19-4.1 2024-01-31 [1] CRAN (R 4.5.2)
codetools 0.2-20 2024-03-31 [1] CRAN (R 4.5.2)
cowplot 1.2.0 2025-07-07 [1] CRAN (R 4.5.2)
crayon 1.5.3 2024-06-20 [1] CRAN (R 4.5.2)
curl 7.1.0 2026-04-22 [1] CRAN (R 4.5.2)
data.table 1.18.2.1 2026-01-27 [1] CRAN (R 4.5.2)
DBI 1.3.0 2026-02-25 [1] CRAN (R 4.5.2)
deldir 2.0-4 2024-02-28 [1] CRAN (R 4.5.2)
devtools 2.4.6 2025-10-03 [1] CRAN (R 4.5.2)
digest 0.6.39 2025-11-19 [1] CRAN (R 4.5.2)
distributional 0.6.0 2026-01-14 [1] CRAN (R 4.5.2)
dplyr 1.2.1 2026-04-03 [1] CRAN (R 4.5.2)
dtw 1.23-3 2026-06-09 [1] CRAN (R 4.5.2)
e1071 1.7-17 2025-12-18 [1] CRAN (R 4.5.2)
ellipsis 0.3.2 2021-04-29 [3] CRAN (R 4.1.1)
emmeans * 2.0.4 2026-07-15 [1] CRAN (R 4.5.2)
energy 1.7-12 2024-08-24 [1] CRAN (R 4.5.2)
estimability 2.0.0 2026-06-26 [1] CRAN (R 4.5.2)
evaluate 1.0.5 2025-08-27 [1] CRAN (R 4.5.2)
farver 2.1.2 2024-05-13 [1] CRAN (R 4.5.2)
fastmap 1.2.0 2024-05-15 [1] CRAN (R 4.5.2)
fftw 1.0-9 2024-09-20 [1] CRAN (R 4.5.2)
foreach 1.5.2 2022-02-02 [3] CRAN (R 4.1.2)
formatR * 1.14 2023-01-17 [1] CRAN (R 4.5.2)
Formula 1.2-5 2023-02-24 [1] CRAN (R 4.5.2)
fs 2.1.0 2026-04-18 [1] CRAN (R 4.5.2)
future 1.70.0 2026-03-14 [1] CRAN (R 4.5.2)
future.apply 1.20.2 2026-02-20 [1] CRAN (R 4.5.2)
generics 0.1.4 2025-05-09 [1] CRAN (R 4.5.2)
geosphere * 1.6-5 2026-03-02 [1] CRAN (R 4.5.2)
ggcorrplot * 0.3.0 2026-07-24 [1] CRAN (R 4.5.2)
ggdist 3.3.3 2025-04-23 [1] CRAN (R 4.5.2)
ggplot2 * 4.0.3 2026-04-22 [1] CRAN (R 4.5.2)
globals 0.19.1 2026-03-13 [1] CRAN (R 4.5.2)
glue 1.8.1 2026-04-17 [1] CRAN (R 4.5.2)
goftest 1.2-3 2021-10-07 [3] CRAN (R 4.1.1)
gower 1.0.2 2024-12-17 [1] CRAN (R 4.5.2)
gridExtra 2.3.1 2026-06-25 [1] CRAN (R 4.5.2)
gsl 2.1-9 2025-11-10 [1] CRAN (R 4.5.2)
gtable 0.3.6 2024-10-25 [1] CRAN (R 4.5.2)
hardhat 1.4.2 2025-08-20 [1] CRAN (R 4.5.2)
htmltools 0.5.9 2025-12-04 [1] CRAN (R 4.5.2)
htmlwidgets 1.6.4 2023-12-06 [1] CRAN (R 4.5.2)
httr 1.4.8 2026-02-13 [1] CRAN (R 4.5.2)
igraph 2.3.3 2026-06-26 [1] CRAN (R 4.5.2)
inline 0.3.21 2025-01-09 [1] CRAN (R 4.5.2)
insight 1.5.2 2026-06-28 [1] CRAN (R 4.5.2)
ipred 0.9-15 2024-07-18 [1] CRAN (R 4.5.2)
iterators 1.0.14 2022-02-05 [3] CRAN (R 4.1.2)
jsonlite 2.0.0 2025-03-27 [1] CRAN (R 4.5.2)
kableExtra 1.4.0 2024-01-24 [1] CRAN (R 4.5.2)
KernSmooth 2.23-26 2025-01-01 [1] CRAN (R 4.5.2)
knitr * 1.51 2025-12-20 [1] CRAN (R 4.5.2)
labeling 0.4.3 2023-08-29 [1] CRAN (R 4.5.2)
lattice * 0.22-9 2026-02-09 [1] CRAN (R 4.5.2)
lava 1.9.0 2026-04-05 [1] CRAN (R 4.5.2)
lifecycle 1.0.5 2026-01-08 [1] CRAN (R 4.5.2)
listenv 0.10.1 2026-03-10 [1] CRAN (R 4.5.2)
loo 2.9.0 2025-12-23 [1] CRAN (R 4.5.2)
lubridate 1.9.5 2026-02-04 [1] CRAN (R 4.5.2)
magrittr 2.0.5 2026-04-04 [1] CRAN (R 4.5.2)
MASS 7.3-65 2025-02-28 [1] CRAN (R 4.5.2)
Matrix 1.7-4 2025-08-28 [1] CRAN (R 4.5.2)
matrixStats 1.5.0 2025-01-07 [1] CRAN (R 4.5.2)
memoise 2.0.1 2021-11-26 [3] CRAN (R 4.1.2)
mgcv 1.9-4 2025-11-07 [1] CRAN (R 4.5.2)
mlbench * 2.1-7 2026-02-18 [1] CRAN (R 4.5.2)
ModelMetrics 1.2.2.2 2020-03-17 [3] CRAN (R 4.0.1)
multcomp 1.4-29 2025-10-20 [1] CRAN (R 4.5.2)
mvtnorm 1.3-3 2025-01-10 [1] CRAN (R 4.5.2)
NatureSounds * 1.0.5 2025-01-17 [1] CRAN (R 4.5.2)
nicheROVER 1.1.2 2023-10-13 [1] CRAN (R 4.5.2)
nlme 3.1-168 2025-03-31 [1] CRAN (R 4.5.2)
nnet 7.3-20 2025-01-01 [1] CRAN (R 4.5.2)
ohun * 1.0.4 2025-10-22 [1] CRAN (R 4.5.2)
openssl 2.4.2 2026-06-09 [1] CRAN (R 4.5.2)
otel 0.2.0 2025-08-29 [1] CRAN (R 4.5.2)
packrat 0.9.3 2025-06-16 [1] CRAN (R 4.5.2)
parallelly 1.46.1 2026-01-08 [1] CRAN (R 4.5.2)
patchwork * 1.3.2 2025-08-25 [1] CRAN (R 4.5.2)
pbapply * 1.7-4 2025-07-20 [1] CRAN (R 4.5.2)
performance * 0.17.1 2026-06-30 [1] CRAN (R 4.5.2)
permute * 0.9-10 2026-02-06 [1] CRAN (R 4.5.2)
PhenotypeSpace * 0.1.1 2026-08-17 [1] CRAN (R 4.5.2)
pillar 1.11.1 2025-09-17 [1] CRAN (R 4.5.2)
pkgbuild 1.4.8 2025-05-26 [1] CRAN (R 4.5.2)
pkgconfig 2.0.3 2019-09-22 [3] CRAN (R 4.0.1)
pkgload 1.5.3 2026-06-15 [1] CRAN (R 4.5.2)
plyr 1.8.9 2023-10-02 [1] CRAN (R 4.5.2)
png 0.1-9 2026-03-15 [1] CRAN (R 4.5.2)
polyclip 1.10-7 2024-07-23 [1] CRAN (R 4.5.2)
posterior 1.6.1 2025-02-27 [1] CRAN (R 4.5.2)
pROC 1.19.0.1 2025-07-31 [1] CRAN (R 4.5.2)
processx 3.9.0 2026-04-22 [1] CRAN (R 4.5.2)
prodlim 2026.03.11 2026-03-11 [1] CRAN (R 4.5.2)
proxy 0.4-29 2025-12-29 [1] CRAN (R 4.5.2)
ps 1.9.3 2026-04-20 [1] CRAN (R 4.5.2)
purrr 1.2.2 2026-04-10 [1] CRAN (R 4.5.2)
QuickJSR 1.9.0 2026-01-25 [1] CRAN (R 4.5.2)
R6 2.6.1 2025-02-15 [1] CRAN (R 4.5.2)
randomForest * 4.7-1.2 2024-09-22 [1] CRAN (R 4.5.2)
raster 3.6-32 2025-03-28 [1] CRAN (R 4.5.2)
rbibutils 2.4.1 2026-01-21 [1] CRAN (R 4.5.2)
RColorBrewer 1.1-3 2022-04-03 [1] CRAN (R 4.5.2)
Rcpp * 1.1.2 2026-07-05 [1] CRAN (R 4.5.2)
RcppParallel 5.1.11-2 2026-03-05 [1] CRAN (R 4.5.2)
RCurl 1.98-1.19 2026-06-03 [1] CRAN (R 4.5.2)
Rdpack 2.6.6 2026-02-08 [1] CRAN (R 4.5.2)
recipes 1.3.1 2025-05-21 [1] CRAN (R 4.5.2)
reformulas 0.4.4 2026-02-02 [1] CRAN (R 4.5.2)
remotes 2.5.0 2024-03-17 [1] CRAN (R 4.5.2)
reshape2 1.4.5 2025-11-12 [1] CRAN (R 4.5.2)
reticulate 1.45.0 2026-02-13 [1] CRAN (R 4.5.2)
rjson 0.2.23 2024-09-16 [1] CRAN (R 4.5.2)
rlang 1.3.0 2026-07-05 [1] CRAN (R 4.5.2)
rmarkdown 2.31 2026-03-26 [1] CRAN (R 4.5.2)
rpart 4.1.24 2025-01-07 [1] CRAN (R 4.5.2)
RSpectra 0.16-2 2024-07-18 [1] CRAN (R 4.5.2)
rstan 2.32.7 2025-03-10 [1] CRAN (R 4.5.2)
rstantools 2.6.0 2026-01-10 [1] CRAN (R 4.5.2)
rstudioapi 0.18.0 2026-01-16 [1] CRAN (R 4.5.2)
S7 0.2.2 2026-04-22 [1] CRAN (R 4.5.2)
sandwich 3.1-1 2024-09-15 [1] CRAN (R 4.5.2)
scales 1.4.0 2025-04-24 [1] CRAN (R 4.5.2)
seewave * 2.2.4 2025-08-19 [1] CRAN (R 4.5.2)
sessioninfo 1.2.3 2025-02-05 [1] CRAN (R 4.5.2)
sf 1.1-2 2026-07-23 [1] CRAN (R 4.5.2)
signal 1.8-1 2024-06-26 [1] CRAN (R 4.5.2)
sketchy 1.0.7 2026-03-03 [1] CRANs (R 4.5.2)
sp 2.2-3 2026-07-19 [1] CRAN (R 4.5.2)
spatstat.data 3.1-9 2025-10-18 [1] CRAN (R 4.5.2)
spatstat.explore 3.8-2 2026-07-27 [1] CRAN (R 4.5.2)
spatstat.geom 3.8-2 2026-07-24 [1] CRAN (R 4.5.2)
spatstat.random 3.5-1 2026-07-27 [1] CRAN (R 4.5.2)
spatstat.sparse 3.2-0 2026-05-21 [1] CRAN (R 4.5.2)
spatstat.univar 3.2-0 2026-05-18 [1] CRAN (R 4.5.2)
spatstat.utils 3.2-4 2026-07-16 [1] CRAN (R 4.5.2)
StanHeaders 2.32.10 2024-07-15 [1] CRAN (R 4.5.2)
stringi 1.8.7 2025-03-27 [1] CRAN (R 4.5.2)
stringr 1.6.0 2025-11-04 [1] CRAN (R 4.5.2)
survival 3.8-6 2026-01-16 [1] CRAN (R 4.5.2)
svglite 2.2.2 2025-10-21 [1] CRAN (R 4.5.2)
svUnit 1.0.8 2025-08-26 [1] CRAN (R 4.5.2)
systemfonts 1.3.1 2025-10-01 [1] CRAN (R 4.5.2)
T4transport 0.1.8 2026-01-11 [1] CRAN (R 4.5.2)
tensor 1.5.1 2025-06-17 [1] CRAN (R 4.5.2)
tensorA 0.36.2.1 2023-12-13 [1] CRAN (R 4.5.2)
terra 1.9-11 2026-03-26 [1] CRAN (R 4.5.2)
testthat 3.3.2 2026-01-11 [1] CRAN (R 4.5.2)
textshaping 1.0.4 2025-10-10 [1] CRAN (R 4.5.2)
TH.data 1.1-5 2025-11-17 [1] CRAN (R 4.5.2)
tibble 3.3.1 2026-01-11 [1] CRAN (R 4.5.2)
tidybayes 3.0.7 2024-09-15 [1] CRAN (R 4.5.2)
tidyr 1.3.2 2025-12-19 [1] CRAN (R 4.5.2)
tidyselect 1.2.1 2024-03-11 [1] CRAN (R 4.5.2)
timechange 0.4.0 2026-01-29 [1] CRAN (R 4.5.2)
timeDate 4052.112 2026-01-28 [1] CRAN (R 4.5.2)
tuneR * 1.4.7 2024-04-17 [1] CRAN (R 4.5.2)
umap * 0.2.10.0 2023-02-01 [1] CRAN (R 4.5.2)
units 1.0-1 2026-03-11 [1] CRAN (R 4.5.2)
usethis 3.2.1 2025-09-06 [1] CRAN (R 4.5.2)
V8 8.2.0 2026-04-21 [1] CRAN (R 4.5.2)
vctrs 0.7.3 2026-04-11 [1] CRAN (R 4.5.2)
vegan * 2.7-5 2026-05-25 [1] CRAN (R 4.5.2)
viridis * 0.6.5 2024-01-29 [1] CRAN (R 4.5.2)
viridisLite * 0.4.3 2026-02-04 [1] CRAN (R 4.5.2)
warbleR * 1.1.37 2025-10-22 [1] CRAN (R 4.5.2)
withr 3.0.3 2026-06-19 [1] CRAN (R 4.5.2)
xaringanExtra 0.8.0 2024-05-19 [1] CRAN (R 4.5.2)
xfun 0.60 2026-07-09 [1] CRAN (R 4.5.2)
xml2 1.5.2 2026-01-17 [1] CRAN (R 4.5.2)
xtable 1.8-8 2026-02-22 [1] CRAN (R 4.5.2)
yaml 2.3.12 2025-12-10 [1] CRAN (R 4.5.2)
zoo 1.8-15 2025-12-15 [1] CRAN (R 4.5.2)
[1] /home/m/R/x86_64-pc-linux-gnu-library/4.5
[2] /usr/local/lib/R/site-library
[3] /usr/lib/R/site-library
[4] /usr/lib/R/library
* ── Packages attached to the search path.
──────────────────────────────────────────────────────────────────────────────