Statistical analysis

Bellbird mimicry

Author
Published

May 21, 2026

Code
# options to customize chunk outputs
knitr::opts_chunk$set(
  message = FALSE
 )

Purpose

  • Evaluate similarity of vocalizations of a mimic bellbird and its model species using birdNET embeddings
    • Are focal belbird vocalizations more similar to sympatric than to allopatric species?
    • Are mimic vocalizations (resembling sympatric species) more common in the focal bellbird than in other bellbirds?

Analysis flowchart

flowchart LR
  A[Read birdNET output] --> B(Format data) 
  B --> C(Graphs)
  C --> D(Statistical analysis)
  D --> E(Model summary) 

style A fill:#382A5433
style B fill:#395D9C33
style C fill:#3497A933
style D fill:#60CEAC33

Load packages and custom functions

Code
# knitr is require for creating html/pdf/word reports
# formatR is used for soft-wrapping code

# install knitr package if not installed
if (!requireNamespace("sketchy", quietly = TRUE)) {
  install.packages("sketchy")
}


packages <- c(
  "knitr",
  "Rtsne",
  "caret",
  "ggplot2",
  "viridis",
  "doParallel",
  "brms",
  "brmsish",
  "cowplot",
  "Rraven",
  "kableExtra"
)

# install/ load packages
sketchy::load_packages(packages = packages)


options(knitr.kable.NA = '')

print <- function(x, row.names = FALSE) {
    kb <- kable(x, row.names = row.names, digits = 4, "html")
    kb <- kable_styling(kb,
                        bootstrap_options = c("striped", "hover", "condensed", "responsive"))
    scroll_box(kb, width = "100%")
}

common_names <- c(
  "Procnias tricarunculatus" = "Three-wattled Bellbird",
  "Psarocolius guatimozinus" = "Chestnut-headed Oropendola",
  "Psarocolius montezuma" = "Montezuma Oropendola",
  "Pteroglossus erythropygius" = "Pale-mandibled Aracari",
  "Pteroglossus torquatus" = "Collared Aracari",
  "Ramphastos brevis" = "Choco Toucan",
  "Ramphastos sulfuratus" = "Keel-billed Toucan",
  "nocall" = "Unclassified"
)

# to create several posterior predictive check plots out of a brms fit
custom_ppc <- function(fit, group = NULL, ndraws = 30) {
  by_group  <- if (!is.null(group)) {
    TRUE
  } else
    FALSE
  
  if (by_group)
    by_group  <-  if (any(names(fit$data) == group)) {
      TRUE
    } else
      FALSE
  
  if (by_group)
    by_group <-
      if (is.character(fit$data[, group]) |
          is.factor(fit$data[, group])) {
        TRUE
      } else
        FALSE
  
  
  if (by_group) {
    ppc_dens <- pp_check(fit,
                         ndraws = ndraws,
                         type = 'dens_overlay_grouped',
                         group = group)
    
    pp_mean <- pp_check(
      fit,
      type = "stat_grouped",
      stat = "mean",
      group = group,
      ndraws = ndraws
    )  + theme_classic()
    
  } else {
    ppc_dens <- pp_check(fit, ndraws = ndraws, type = 'dens_overlay')
    
    pp_mean <- pp_check(fit,
                        type = "stat",
                        stat = "mean",
                        ndraws = ndraws) + theme_classic()
    
  }
  
  pp_plot_list <-
    list(ppc_dens, pp_mean)
  
  pp_plot_list <-
    lapply(pp_plot_list, function(x)
      x   + scale_fill_viridis_d(
        begin = 0.3,
        end = 0.8,
        alpha = 0.5,
        option = "mako"
      ) + theme_classic())
  
  
  ppc_plot <- plot_grid(plotlist = pp_plot_list, ncol = 2)
 
   ppc_plot
}

1 Sympatric and allopatric species

1.1 BirdNET model

Code
mod_eval_symp_allo <- read.csv("./data/processed/birdnet_models/sympatric_and_allopatric/sympatric_and_allopatric_evaluation.csv")

names(mod_eval_symp_allo) <- gsub("..", " ", names(mod_eval_symp_allo), fixed = TRUE)

print(mod_eval_symp_allo)
Class Precision 0.5. Recall 0.5. F1.Score 0.5. Precision opt. Recall opt. F1.Score opt. AUPRC AUROC Optimal.Threshold True.Positives False.Positives True.Negatives False.Negatives Samples Percentage
OVERALL (Macro-avg) 0.8908 0.9627 0.9232 0.9180 0.9504 0.9312 0.9630 0.9968
Procnias tricarunculatus 0.9712 0.9854 0.9783 1.0000 0.9708 0.9852 0.9990 0.9998 0.80 133 0 884 4 137 13.42
Psarocolius guatimozinus 0.8909 0.9608 0.9245 0.9423 0.9608 0.9515 0.9867 0.9992 0.55 49 3 967 2 51 5.00
Psarocolius montezuma 0.9014 0.9846 0.9412 0.9538 0.9538 0.9538 0.9873 0.9991 0.75 62 3 953 3 65 6.37
Pteroglossus erythropygius 0.8943 0.9486 0.9206 0.9336 0.9206 0.9271 0.9844 0.9959 0.70 197 14 793 17 214 20.96
Pteroglossus torquatus 0.6216 0.8679 0.7244 0.6216 0.8679 0.7244 0.7844 0.9842 0.50 46 28 940 7 53 5.19
Ramphastos brevis 0.9840 0.9946 0.9892 0.9840 0.9946 0.9892 0.9995 0.9999 0.50 184 3 833 1 185 18.12
Ramphastos sulfuratus 0.9722 0.9968 0.9844 0.9904 0.9842 0.9873 0.9994 0.9997 0.80 311 3 702 5 316 30.95

Keeping only true positives (confidence >= optimal threshold) for the classification of vocalizations of sympatric and allopatric species

Code
# read data
sympatric_allopatric_class <- imp_raven(
  path = "./data/processed/birdnet_classification_outputs/sympatrics_and_allopactrics/sympatrics_and_allopatrics_model/",
  all.data = TRUE,
  warbler.format = TRUE,
  pb = FALSE
)

# filter low confidence classifications
sympatric_allopatric_class$true.positive <- sapply(seq_len(nrow(sympatric_allopatric_class)), function(x){
    sympatric_allopatric_class$Confidence[x] >= mod_eval_symp_allo$Optimal.Threshold[mod_eval_symp_allo$Class == sympatric_allopatric_class$`Common Name`[x]]
    })

sympatric_allopatric_class <- sympatric_allopatric_class[sympatric_allopatric_class$true.positive, ]

# rename column
sympatric_allopatric_class$predicted_sp <- sympatric_allopatric_class$`Common Name`

sympatric_allopatric_class$`Common Name` <- NULL

# read metadata
est <- readRDS(
  "./data/processed/extended_selection_table_sympatric_allopatric_and_mimic_sounds.RDS"
)

# add metadata
sympatric_allopatric_class$sound.files <- basename(gsub("\\\\", "/", sympatric_allopatric_class$sound.files))


sympatric_allopatric_class$vocalizing_sp <- sapply(sympatric_allopatric_class$sound.files, function(x) {
  est$source[paste0(est$sound.files, ".wav") == x][1]
})


# save confusion matrix
conf_mat <- confusionMatrix(
  data = as.factor(sympatric_allopatric_class$predicted_sp),
  reference = as.factor(sympatric_allopatric_class$vocalizing_sp)
)

conf_df <- as.data.frame(conf_mat$table)

conf_df$total <- sapply(conf_df$Reference, function(x) {
  sum(sympatric_allopatric_class$vocalizing_sp == x)
})

conf_df$Proportion <- conf_df$Freq / conf_df$total

conf_df$pred_common_name <- common_names[as.character(conf_df$Prediction)]
conf_df$ref_common_name <- common_names[as.character(conf_df$Reference)]

# sort levels by genus
conf_df$pred_common_name <- factor(
  conf_df$pred_common_name,
  levels = c(
    "Three-wattled Bellbird",
    "Chestnut-headed Oropendola",
    "Montezuma Oropendola",
    "Collared Aracari",
    "Pale-mandibled Aracari",
    "Keel-billed Toucan",
    "Choco Toucan"
  )
)

conf_df$ref_common_name <- factor(
  conf_df$ref_common_name,
  levels = c(
    "Three-wattled Bellbird",
    "Chestnut-headed Oropendola",
    "Montezuma Oropendola",
    "Collared Aracari",
    "Pale-mandibled Aracari",
    "Keel-billed Toucan",
    "Choco Toucan"
  )
)

ggplot(conf_df, aes(x = ref_common_name, y = pred_common_name, fill = Proportion)) +
  geom_tile() +
  theme_bw() +
  coord_equal() +
  scale_fill_gradient2(low = mako(10)[3], high = mako(10)[7]) +
  labs(x = "Reference species", y = "Predicted species") +
  geom_text(aes(label = round(Proportion, 2)), color = "black") +
  theme_classic() +
  theme(
    axis.text.x = element_text(
      color = "black",
      size = 11,
      angle = 30,
      hjust = 1
    )
  )

1.1.1 Embeddings

Code
embs <- read.csv("./data/processed/embeddings/embeddings_sympatric_allopatric_spp.csv")

# Split into separate columns
emb_matrix <- do.call(rbind, strsplit(embs$embedding, ","))
colnames(emb_matrix) <- paste0("emb", 1:ncol(emb_matrix))
emb_matrix <- apply(emb_matrix, 2, as.numeric)

# Combine with the first 3 columns
embs <- as.data.frame(cbind(embs[, 1:3], emb_matrix))

# add metadata
embs$file_path <- gsub("Nueva-grabaci\xf3n", "Nueva-grabación", embs$file_path, useBytes = TRUE)
embs$sound.files <- basename(gsub("\\\\", "/", embs$file_path, useBytes = TRUE))

embs$vocalizing_sp <- sapply(embs$sound.files, function(x) {
  est$source[paste0(est$sound.files, ".wav") == x][1]
})

set.seed(123)
tsne_out <- Rtsne(as.matrix(embs[, grep("emb", names(embs))]))

tsne_all <- data.frame(vocalizing_sp = embs$vocalizing_sp, tsne1 = tsne_out$Y[,1], tsne2 = tsne_out$Y[,2])

saveRDS(tsne_all, "./data/processed/tsne_dims_model_and_sister_species.rds")
Code
# colors and shapes by species for scatterplots
cols <- c(adjustcolor("tomato", alpha.f = 0.4), mako(4, alpha = 0.4, begin = 0.2, end = 0.8))
cols2 <- c(adjustcolor("tomato", alpha.f = 0.9), mako(4, alpha = 0.9, begin = 0.2, end = 0.8))

cols <- c(cols[1:2], rep(cols[3:5], each = 2))
cols2 <- c(cols2[1:2], rep(cols2[3:5], each = 2))

shapes <- c(16, 16, 16, 1, 17, 2, 15, 0)

names(shapes) <- names(cols) <- names(cols2) <- c(
  "mimic_bellbird",
  "Three-wattled Bellbird",
  "Montezuma Oropendola",
  "Chestnut-headed Oropendola",
  "Collared Aracari",
  "Pale-mandibled Aracari",
  "Keel-billed Toucan",
  "Choco Toucan"
)

tsne_all <- readRDS("./data/processed/tsne_dims_model_and_sister_species.rds")

tsne_only_model_species <- tsne_all[tsne_all$vocalizing_sp != "mimic_bellbird", ]

tsne_only_model_species$common_name <- common_names[as.character(tsne_only_model_species$vocalizing_sp)] 

tsne_only_model_species$common_name <- factor(
  tsne_only_model_species$common_name,
  levels = c(
    "Three-wattled Bellbird",
    "Chestnut-headed Oropendola",
    "Montezuma Oropendola",
    "Collared Aracari",
    "Pale-mandibled Aracari",
    "Keel-billed Toucan",
    "Choco Toucan"
  )
)

ggplot(tsne_only_model_species, aes(x = tsne1, y = tsne2, color = common_name, shape = common_name)) +
  geom_point(size = 2) +
  theme_minimal() +
  labs(x = "t-SNE 1", y = "t-SNE 2", color = "", shape = "") +
    scale_color_manual(values = cols[names(cols) %in% unique(tsne_only_model_species$common_name)]) +
    scale_shape_manual(values = shapes[names(shapes) %in% unique(tsne_only_model_species$common_name)]) +
    theme_classic()

1.1.2 Focal bellbird classification

Code
# read data
mimic_class <- imp_raven(
  path = "./data/processed/birdnet_classification_outputs/mimic_bellbird/sympatrics_and_allopatrics_model/",
  all.data = TRUE,
  warbler.format = TRUE,
  pb = FALSE
)

# filter low confidence classifications
mimic_class$true.positive <- sapply(seq_len(nrow(mimic_class)), function(x){
    mimic_class$Confidence[x] >= mod_eval_symp_allo$Optimal.Threshold[mod_eval_symp_allo$Class == mimic_class$`Common Name`[x]]
    })

mimic_class <- mimic_class[mimic_class$true.positive, ]

# add metadata
mimic_class$sound.files <- basename(gsub(
  "\\\\",
  "/",
  mimic_class$sound.files
))

# rename column
mimic_class$predicted_sp <- mimic_class$`Common Name`
mimic_class$vocalizing_sp <- "Procnias tricarunculatus"

mimic_class$`Common Name` <- NULL

prop_mimic_class <- table(mimic_class$predicted_sp)

prop_mimic_class <- data.frame(prop_mimic_class)
names(prop_mimic_class) <- c("Prediction", "Freq")
prop_mimic_class$total <- sum(prop_mimic_class$Freq)
prop_mimic_class$Proportion <- prop_mimic_class$Freq / prop_mimic_class$total
prop_mimic_class$Reference <- "Focal\nbellbird"

others <- conf_df[conf_df$Reference == "Procnias tricarunculatus", ]
others$Reference <- "Other\nbellbirds"

# proportions observed in adult bellbird
prop_class <- rbind(
  prop_mimic_class,
  others[, c("Prediction", "Freq", "total", "Proportion", "Reference")]
)

prop_class$genus <- sapply(
  strsplit(as.character(prop_class$Prediction), " "),
  "[[",
  1
)

prop_class$type <- ifelse(
  grepl("guati|eryth|brevis", prop_class$Prediction),
  "Allopatric",
  "Sympatric"
)

prop_class$type[prop_class$genus == "Procnias"] <- "Own\nspecies"

prop_class$type <- factor(
  prop_class$type,
  levels = c("Allopatric", "Sympatric", "Own\nspecies")
)

prop_class$genus <- factor(
  prop_class$genus,
  levels = c("Procnias", "Pteroglossus", "Psarocolius", "Ramphastos")
)

prop_class$Reference <- factor(
  prop_class$Reference,
  levels = c("Other\nbellbirds", "Focal\nbellbird")
)


genus_labels <- c(
  "Procnias" = "Three-wattled bellbird",
  "Psarocolius" = "Oropendolas",
  "Pteroglossus" = "Aracaris",
  "Ramphastos" = "Toucans"
)

prop_class$common_name <- common_names[as.character(prop_class$Prediction)]

prop_class$common_name <- gsub("-", "\n", prop_class$common_name)


prop_class$sp_label <- sapply(
  strsplit(as.character(prop_class$common_name), " "),
  "[[",
  1
)

prop_class$sp_label[prop_class$sp_label == "Three\nwattled"] <- ""


prop_class$sp_label_y <- ave(
  prop_class$Proportion,
  prop_class$genus,
  FUN = function(x) {
    x + log10(max(x) + 0.999) * 0.44
  }
)


bar_cols <- c("gray", mako(10, alpha = 0.6, begin = 0.2, end = 0.8)[c(2, 8)])

# dummy rows controlling y-range of each facet
dummy_df <- data.frame(
  genus = c("Procnias", "Psarocolius", "Pteroglossus", "Ramphastos"),
  Reference = "Focal\nbellbird",
  Proportion = c(1.18, 0.0024, 0.17, 0.50)
)

dummy_df$genus <- factor(
  dummy_df$genus,
  levels = c("Procnias", "Pteroglossus", "Psarocolius", "Ramphastos")
)


ggplot(prop_class, aes(x = Reference, y = Proportion)) +

  # invisible layer used only to expand scales
  geom_blank(data = dummy_df) +

  geom_bar(
    aes(group = interaction(Reference, type), fill = type),
    position = position_dodge(),
    stat = "identity"
  ) +

  geom_text(
    aes(
      group = interaction(Reference, type),
      label = paste0(round(Proportion * 100, 1), "%")
    ),
    position = position_dodge(width = 0.9),
    vjust = -0.5,
    size = 3
  ) +

  facet_wrap(
    . ~ genus,
    scales = "free_y",
    labeller = labeller(genus = genus_labels)
  ) +

  ylab("Percentage of vocalizations predicted as") +
  xlab("") +

  scale_y_continuous(
    labels = scales::percent,
    expand = expansion(mult = c(0, 0))
  ) +

  scale_fill_manual(
    name = "",
    values = c(
      "Own\nspecies" = bar_cols[1],
      "Allopatric" = bar_cols[2],
      "Sympatric" = bar_cols[3]
    ),
    breaks = c("Allopatric", "Sympatric")
  ) +

  theme_classic() +
  theme(
    plot.title = element_text(hjust = 0.5),
    legend.position = c(0.74, 0.78),
    legend.justification = c(1, 0),
    legend.background = element_rect(fill = "white", color = NA),
    panel.grid.major.x = element_blank()
  ) +

  geom_text(
    aes(
      group = interaction(Reference, type),
      label = sp_label,
      y = sp_label_y
    ),
    position = position_dodge(width = 0.9),
    vjust = 0.5,
    size = 2.5,
    angle = 90,
    hjust = 0
  )

1.1.3 Statistical analysis

Evaluate whether the proportion of vocalizations predicted as sympatric (model) species vs allopatric species is significantly higher in the focal bellbird than in other bellbirds

Code
mimic_other_bellbirds <- sympatric_allopatric_class[sympatric_allopatric_class$predicted_sp != "Procnias tricarunculatus", ]

mimic_focal <- mimic_class[mimic_class$predicted_sp != "Procnias tricarunculatus", ]

mimic_focal$vocalizing_type <- "focal"
mimic_other_bellbirds$vocalizing_type <- "others"

mimic_bellbirds <- rbind(
  mimic_focal[, c("predicted_sp", "vocalizing_type")],
  mimic_other_bellbirds[, c("predicted_sp", "vocalizing_type")]
)

mimic_bellbirds$vocalizing_type <- factor(
  mimic_bellbirds$vocalizing_type,
  levels = c("others", "focal")
)

mimic_bellbirds$mimic_type <- 
ifelse(
  grepl("guati|eryth|brevis", mimic_bellbirds$predicted_sp),
  0,
  1
)

mimic_bellbirds$genus <- sapply(
  strsplit(as.character(mimic_bellbirds$predicted_sp), " "),
  "[[",
  1
)

cores <- 4
chains <- 4
iter <- 4000

priors <- c(
  prior(normal(0, 1.5), class = "Intercept"),
  prior(normal(0, 0.7), class = "b"),
  prior(exponential(1), class = "sd")
)

only_prior_fit  <- brm(
  mimic_type ~ vocalizing_type + (1 | genus),
  data = mimic_bellbirds,
  family = bernoulli(),
  prior = priors,
  sample_prior = "only",
  chains = chains,
  iter = iter,
  cores = cores,
          file = "./data/processed/mimicry_prob_by_simpatry_model_only_priors.RDS", 
  file_refit = "on_change"
)


mod1 <- brm(
  mimic_type ~ vocalizing_type  + (1 | genus),
  data = mimic_bellbirds,
  family = bernoulli(),
  cores = cores,
    prior = priors,
        chains = chains,
        iter = iter,
        file = "./data/processed/mimicry_prob_by_simpatry_model.RDS", 
  file_refit = "on_change"
)

1.1.3.1 Prior check

Priors

Code
prior_summary(mod1)
prior class coef group resp dpar nlpar lb ub tag source
normal(0, 0.7) b user
b vocalizing_typefocal default
normal(0, 1.5) Intercept user
exponential(1) sd 0 user
sd genus default
sd Intercept genus default

Prior predictive checks

Code
custom_ppc(only_prior_fit, group = "vocalizing_type", ndraws = 100)

1.1.3.2 Fit summary

Code
fill_color <- mako(10)[8]

extended_summary(
  mod1,
  n.posterior = 1000,
  fill = fill_color,
  trace.palette = mako,
  remove.intercepts = FALSE,
  highlight = TRUE,
  print.name = FALSE
)
formula family priors iterations chains thinning warmup diverg_transitions rhats > 1.05 min_bulk_ESS min_tail_ESS seed
1 mimic_type ~ vocalizing_type + (1 | genus) bernoulli (logit) b-normal(0, 0.7) Intercept-normal(0, 1.5) sd-exponential(1) 4000 4 1 2000 12 (0.0015%) 0 1519.44 1939.21 1537639093
Estimate l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
b_Intercept -0.274 -1.405 0.846 1.003 1519.44 1939.21
b_vocalizing_typefocal 2.966 2.641 3.307 1.003 3701.09 3564.68

Posterior predictive check

Code
custom_ppc(mod1, group = "vocalizing_type", ndraws = 100)

Code
post <- as_draws_df(mod1)

# Odds ratio: focal vs other birds
focal_vs_others <- exp(post$b_vocalizing_typefocal)

# Predicted probability of sympatric mimicry in focal birds
p_focal <- posterior_epred(
  mod1,
  newdata = data.frame(
    vocalizing_type = "focal",
    genus = unique(mimic_bellbirds$genus)[1]
  ),
  re_formula = NA
)

p_focal <- as.numeric(p_focal)

# Predicted probability of sympatric mimicry in other birds
p_others <- posterior_epred(
  mod1,
  newdata = data.frame(
    vocalizing_type = "others",
    genus = unique(mimic_bellbirds$genus)[1]
  ),
  re_formula = NA
)

p_others <- as.numeric(p_others)

# Within-group sympatric vs allopatric odds
focal_ratio <- p_focal / (1 - p_focal)
other_ratio <- p_others / (1 - p_others)

1.1.3.3 Summary

  • Focal bellbird vocalizations had 19.32 times greater odds [95% UI: 14.03–27.3] of being classified as the sympatric rather than the allopatric model species compared with vocalizations from other bellbirds (predicted probability of sympatric classification for focal birds = 0.94; for other bellbirds = 0.43).

  • Within focal bellbird vocalizations, the odds of classification as the sympatric model species were 14.9 times greater [95% UI: 4.56–47.88] than the odds of classification as closely related allopatric species (predicted probability of sympatric classification for focal birds = 0.94; predicted probability of allopatric classification for focal birds = 0.06).

  • For vocalizations from other bellbirds, the odds of classification as sympatric and allopatric species were similar (odds = 0.77 [95% UI: 0.25–2.33]; predicted probability of sympatric classification for other bellbirds = 0.43; predicted probability of allopatric classification for other bellbirds = 0.57).

2 Only sympatric species

2.1 BirdNET model

Code
mod_eval_symp <- read.csv("./data/processed/birdnet_models/sympatric/sympatric_evaluation.csv")

names(mod_eval_symp) <- gsub("..", " ", names(mod_eval_symp), fixed = TRUE)

print(mod_eval_symp)
Class Precision 0.5. Recall 0.5. F1.Score 0.5. Precision opt. Recall opt. F1.Score opt. AUPRC AUROC Optimal.Threshold True.Positives False.Positives True.Negatives False.Negatives Samples Percentage
OVERALL (Macro-avg) 0.9884 0.9908 0.9896 0.9939 0.9890 0.9914 0.9994 0.9998
Procnias tricarunculatus 0.9783 0.9854 0.9818 1.0000 0.9781 0.9889 0.9994 0.9998 0.60 134 0 434 3 137 23.99
Psarocolius montezuma 0.9848 1.0000 0.9924 0.9848 1.0000 0.9924 0.9985 0.9998 0.15 65 1 505 0 65 11.38
Pteroglossus torquatus 1.0000 0.9811 0.9905 1.0000 0.9811 0.9905 0.9997 1.0000 0.15 52 0 518 1 53 9.28
Ramphastos sulfuratus 0.9906 0.9968 0.9937 0.9906 0.9968 0.9937 0.9998 0.9998 0.50 315 3 252 1 316 55.34
Code
# read data
sympatric_class <- imp_raven(
  path = "./data/processed/birdnet_classification_outputs/sympatrics/sympatrics_model/",
  all.data = TRUE,
  warbler.format = TRUE,
  pb = FALSE
)

sympatric_class$true.positive <- sapply(seq_len(nrow(sympatric_class)), function(x){
    sympatric_class$Confidence[x] >= mod_eval_symp$Optimal.Threshold[mod_eval_symp$Class == sympatric_class$`Common Name`[x]]
    })
    

sympatric_class <- sympatric_class[sympatric_class$true.positive,]

# rename column
sympatric_class$predicted_sp <- sympatric_class$`Common Name`

sympatric_class$`Common Name` <- NULL

# read metadata
est <- readRDS(
  "./data/processed/extended_selection_table_sympatric_allopatric_and_mimic_sounds.RDS"
)

# add metadata
sympatric_class$sound.files <- basename(gsub("\\\\", "/", sympatric_class$sound.files))


sympatric_class$vocalizing_sp <- sapply(sympatric_class$sound.files, function(x) {
  est$source[paste0(est$sound.files, ".wav") == x][1]
})


sympatric_class <- sympatric_class[sympatric_class$vocalizing_sp %in% unique(sympatric_class$predicted_sp),]

# save confusion matrix
conf_mat <- confusionMatrix(
  data = as.factor(sympatric_class$predicted_sp),
  reference = as.factor(sympatric_class$vocalizing_sp)
)

conf_df <- as.data.frame(conf_mat$table)

conf_df$total <- sapply(conf_df$Reference, function(x) {
  sum(sympatric_class$vocalizing_sp == x)
})

conf_df$Proportion <- conf_df$Freq / conf_df$total

conf_df$pred_common_name <- common_names[as.character(conf_df$Prediction)]
conf_df$ref_common_name <- common_names[as.character(conf_df$Reference)]

# sort levels by genus
conf_df$pred_common_name <- factor(
  conf_df$pred_common_name,
  levels = c(
    "Three-wattled Bellbird",
    "Montezuma Oropendola",
    "Collared Aracari",
    "Keel-billed Toucan"
  )
)

conf_df$ref_common_name <- factor(
  conf_df$ref_common_name,
  levels = c(
    "Three-wattled Bellbird",
    "Montezuma Oropendola",
    "Collared Aracari",
    "Keel-billed Toucan"
  )
)

ggplot(conf_df, aes(x = ref_common_name, y = pred_common_name, fill = Proportion)) +
  geom_tile() +
  theme_bw() +
  coord_equal() +
  scale_fill_gradient2(low = mako(10)[3], high = mako(10)[7]) +
  labs(x = "Reference species", y = "Predicted species") +
  geom_text(aes(label = round(Proportion, 2)), color = "black") +
  theme_classic() +
  theme(
    axis.text.x = element_text(
      color = "black",
      size = 11,
      angle = 30,
      hjust = 1
    )
  )

Code
# read data
sympatric_focal <- imp_raven(
  path = "./data/processed/birdnet_classification_outputs/mimic_bellbird/sympatric_model/",
  all.data = TRUE,
  warbler.format = TRUE,
  pb = FALSE
)



# filter out low confidence calls
sympatric_focal$true.positive <- sapply(seq_len(nrow(sympatric_focal)), function(x){
    if (sympatric_focal$`Common Name`[x] == "nocall")
        return(NA)
        
    sympatric_focal$Confidence[x] >= mod_eval_symp$Optimal.Threshold[mod_eval_symp$Class == sympatric_focal$`Common Name`[x]]
    })
    


# remove nocalls
sympatric_focal_nocalls <- sympatric_focal

sympatric_focal <- sympatric_focal[sympatric_focal$true.positive | is.na(sympatric_focal$true.positive), ]


sympatric_focal$Reference <- "Focal\nbellbird"

# rename column
sympatric_focal$predicted_sp <- sympatric_focal$`Common Name`

sympatric_focal$`Common Name` <- NULL

# get other bellbirds from previous data
sympatric_others <- sympatric_class[sympatric_class$vocalizing_sp == "Procnias tricarunculatus", ]

sympatric_others$vocalizing_sp <- NULL

sympatric_others$Reference <- "Other\nbellbirds"

sympatric <- rbind(
  sympatric_focal,
  sympatric_others
)

# # add metadata
# sympatric$sound.files <- basename(gsub(
#   "\\\\",
#   "/",
#   sympatric$sound.files
# ))
# 
# # rename column
# sympatric$predicted_sp <- sympatric$`Common Name`
# sympatric$`Common Name` <- NULL

prop_sympatric <- table(sympatric$predicted_sp, sympatric$Reference)

prop_sympatric <- data.frame(prop_sympatric)
names(prop_sympatric) <- c("Prediction", "Vocalizing", "Freq")

# add Psarocolius
# prop_sympatric2 <- prop_sympatric[3:4, ]
# prop_sympatric2$Prediction <- "Psarocolius montezuma"
# prop_sympatric2$Freq <- 0

# prop_sympatric <- rbind(prop_sympatric, prop_sympatric2)

prop_sympatric$total <- sapply(prop_sympatric$Vocalizing, function(x) sum(prop_sympatric$Freq[prop_sympatric$Vocalizing == x]))

prop_sympatric$Proportion <- prop_sympatric$Freq / prop_sympatric$total

prop_sympatric$common_name <- common_names[as.character(
  prop_sympatric$Prediction
)]

# order levels
prop_sympatric$common_name <- factor(
  prop_sympatric$common_name,
  levels = c(
    "Three-wattled Bellbird",
    "Collared Aracari",
    "Keel-billed Toucan",
    "Montezuma Oropendola"
  )
)


# ggplot2 bar plot single panel colored by references
ggplot(prop_sympatric[prop_sympatric$Prediction != "nocall",], aes(x = common_name, y = Proportion, fill = Vocalizing)) +
  geom_bar(stat = "identity", position = position_dodge(width = 0.9)) +

  geom_text(
    aes(label = paste0(round(Proportion * 100, 1), "%")),
    position = position_dodge(width = 0.9),
    vjust = -0.4,
    size = 4
  ) +

  labs(y = "Percentage of vocalizations predicted as", x = "", fill = "") +

  scale_y_continuous(
    labels = scales::percent,
    expand = expansion(mult = c(0, 0.16))
  ) +

  scale_fill_manual(values = mako(10, alpha = 0.6)[c(3, 8)]) +

  theme_classic() +
  theme(
    legend.position = c(0.1, 0.87),
    plot.title = element_text(hjust = 0.5),
    panel.grid.major.x = element_blank(),
    axis.text.x = element_blank(),
    axis.ticks.x = element_blank()
  ) +
  facet_wrap(. ~ common_name, nrow = 2, scales = "free")

2.1.1 Statistical analysis

Code
sympatric$prediction_type <- ifelse(
  grepl("rocnias", sympatric$predicted_sp),
  0,
  1
)

sympatric$vocalizing_type <- ifelse(
  grepl("ocal", sympatric$Reference),
  "focal",
  "others"
)

# order levels
sympatric$vocalizing_type <- factor(
  sympatric$vocalizing_type,
  levels = c("others", "focal")
)

cores <- 4
chains <- 4
iter <- 4000

priors <- c(
  prior(normal(-3, 1), class = "Intercept"),
  prior(normal(0, 2), class = "b")
)

only_prior_fit2  <- brm(
  prediction_type ~ vocalizing_type + predicted_sp,
  data = sympatric[sympatric$predicted_sp != "nocall",],
  family = bernoulli(),
  prior = priors,
sample_prior = "only",
  chains = chains,
  iter = iter,
  cores = cores,
  file = "./data/processed/mimicry_simpatric_only_model_only_priors.RDS",
  file_refit = "on_change"
)


mod2 <- brm(
  prediction_type ~ vocalizing_type,
  data = sympatric[sympatric$predicted_sp != "nocall",],
  family = bernoulli(),
  prior = priors,
  chains = chains,
  iter = iter,
  cores = cores,
  control = list(
  adapt_delta = 0.999,
  max_treedepth = 15
),
  file = "./data/processed/mimicry_simpatric_only_model.RDS",
  file_refit = "on_change"
)

2.1.1.1 Prior check

Priors

Code
prior_summary(mod2)
prior class coef group resp dpar nlpar lb ub tag source
normal(0, 2) b user
b vocalizing_typefocal default
normal(-3, 1) Intercept user

Prior predictive checks

Code
custom_ppc(only_prior_fit2, group = "vocalizing_type", ndraws = 100)

2.1.2 Fit summary

Code
fill_color <- mako(10)[8]

extended_summary(
  mod2,
  n.posterior = 1000,
  fill = fill_color,
  trace.palette = mako,
  remove.intercepts = FALSE,
  highlight = TRUE,
  print.name = FALSE
)
formula family priors iterations chains thinning warmup diverg_transitions rhats > 1.05 min_bulk_ESS min_tail_ESS seed
1 prediction_type ~ vocalizing_type bernoulli (logit) b-normal(0, 2) Intercept-normal(-3, 1) 4000 4 1 2000 0 (0%) 0 1390.19 1412.9 549624481
Estimate l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
b_Intercept -4.814 -5.603 -4.128 1.003 1390.19 1423.82
b_vocalizing_typefocal 4.988 4.287 5.786 1.003 1422.15 1412.90

Posterior predictive check

Code
custom_ppc(mod1, group = "vocalizing_type", ndraws = 100)

Code
# Odds ratio: focal vs other bellbirds
post2 <- as_draws_df(mod2)

# Odds ratio: focal vs other bellbirds
focal_vs_others <- exp(post2$b_vocalizing_typefocal)

# Predicted probability of sympatric classification for focal birds
p_focal <- posterior_epred(
  mod2,
  newdata = data.frame(vocalizing_type = "focal")
)

p_focal <- as.numeric(p_focal)

# Predicted probability of sympatric classification for other bellbirds
p_others <- posterior_epred(
  mod2,
  newdata = data.frame(vocalizing_type = "others")
)

p_others <- as.numeric(p_others)

2.1.2.1 Summary

  • Focal bellbird vocalizations had 143.81 times greater odds [95% UI: 72.75–325.59] of being classified as a sympatric model species compared with vocalizations from other bellbirds (predicted probability of sympatric classification for focal birds = 0.54; for other bellbirds = 0.01).

3 Mimicry through time

Code
mimic_data <- est[est$source == "mimetic_bellbird", ]

rec_info <- read.table("./data/raw/Recording_dates_.csv", head = TRUE, sep = ";")

rec_info$date <- gsub("\\/", "-", rec_info$date)
rec_info$date <- gsub("out", "ago", rec_info$date)
rec_info$date <- gsub("set", "sep", rec_info$date)

rec_info$org.date <- rec_info$date
rec_info$date <- as.Date(rec_info$date, format = "%d-%b-%y")


mimic_data$orig.sound.files <- attr(mimic_data, "check.res")$orig.sound.files


mimic_data$date <- sapply(mimic_data$orig.sound.files, function(x) as.character(rec_info$date[rec_info$sound.files ==
    x][1]))

mimic_data$date <- as.Date(mimic_data$date)

sympatric_focal$fixed.sound.files <- gsub("Nueva-grabaci\xf3n", "Nueva-grabación", sympatric_focal$sound.files, useBytes = TRUE)

sympatric_focal$fixed.sound.files <- basename(gsub("\\\\", "/", sympatric_focal$fixed.sound.files, useBytes = TRUE))

mimic_data$sound.files <- gsub("Nueva-grabaci\xf3n", "Nueva-grabación", mimic_data$sound.files, useBytes = TRUE)


mimic_data$sound.files <- paste0(mimic_data$sound.files, ".wav")

mimic_data$pred_mimic <- sapply(mimic_data$sound.files, function(x) {
  sympatric_focal$predicted_sp[sympatric_focal$fixed.sound.files == x][1]
})

mimic_data <- mimic_data[mimic_data$pred_mimic != "babbling",]

mimic_data$pred_mimic <- ifelse(mimic_data$pred_mimic == "babbling" | is.na(mimic_data$pred_mimic), "nocall", mimic_data$pred_mimic)

agg_pred <- aggregate(orig.sound.files ~ date + pred_mimic, mimic_data, length)


out <- lapply(unique(agg_pred$date), function(x) {

    X <- agg_pred[agg_pred$date == x, ]
    X$prop <- X$orig.sound.files/sum(X$orig.sound.files)

    if (length(unique(X$pred_mimic)) < 4) {

        new_df <- data.frame(date = X$date[1], pred_mimic = setdiff(c("Procnias tricarunculatus",
            "Pteroglossus torquatus", "Ramphastos sulfuratus", "Psarocolius montezuma", "nocall"), X$pred_mimic),
            orig.sound.files = 0, prop = 0)
  
        X <- rbind(new_df, X)
    }
    
      X$total <- sum(X$orig.sound.files)
    return(X)
})

prop_pred <- do.call(rbind, out)

prop_pred$date.fact <- as.factor(as.character(prop_pred$date))

count_date <- aggregate(orig.sound.files ~ date, prop_pred, sum)

prop_pred$pred_common_name <- common_names[as.character(prop_pred$pred_mimic)]


ggplot(prop_pred[prop_pred$date %in% count_date$date[count_date$orig.sound.files >=
    10], ], aes(x = date.fact, y = prop, fill = pred_common_name)) + geom_bar(stat = "identity") +
    scale_fill_viridis_d(alpha = 0.7, end = 0.8, option = "G") + theme_classic() + theme(axis.text.x = element_text(angle = 45,
    vjust = 0.5)) + labs(x = "Date", y = "Proportion of vocalizations\npredicted as")

3.1 Statistical analysis

We modeled the probability of producing original sounds as a function of Julian day while allowing both intercepts and temporal slopes to vary among mimic categories (pred_mimic). Rather than fitting a full fixed interaction between date and mimic category, we used a hierarchical varying-slope structure to account for heterogeneity among mimic groups while enabling partial pooling across categories. This approach reduces overparameterization and yields more stable estimates, particularly for mimic groups with limited sample sizes, while still allowing different groups to exhibit distinct temporal trajectories.

Code
# weakly informative priors
priors <- c(
# Weakly informative logistic intercept
prior(normal(0, 2.5), class = "Intercept"),
# Temporal slope
prior(normal(0, 1), class = "b"),
# Random effect standard deviations
prior(exponential(1), class = "sd")
)

dat <- prop_pred[prop_pred$pred_mimic != "Psarocolius montezuma",]

dat$julian_day <- as.numeric(format(as.Date(dat$date), "%j"))


dat$pred_common_name <- common_names[as.character(dat$pred_mimic)]


dat$pred_common_name <- factor(dat$pred_common_name, levels = c( "Three-wattled Bellbird", "Collared Aracari",     "Keel-billed Toucan", "Unclassified"))


mod2 <- brm(
  orig.sound.files | trials(total) ~ 
    scale(julian_day) +
    (1 + scale(julian_day) | pred_common_name),
  family = binomial,
  data = dat,
  prior = priors,
  chains = 4,
  cores = 4,
  iter = 4000,
  control = list(adapt_delta = 0.95), file_refit = "on_change",
  file = "data/processed/brms_model_prop_pred_mimicry.rds"
)

# mod2 <- add_criterion(
#       mod2,
#       criterion = "loo")


only_prior_mod2 <- brm(
  orig.sound.files | trials(total) ~ 
    scale(julian_day) +
    (1 + scale(julian_day) | pred_common_name),
  family = binomial,
  data = dat,
  prior = priors,
  sample_prior = "only",
  chains = 4,
  cores = 4,
  iter = 4000,
  control = list(adapt_delta = 0.95), file_refit = "on_change",
  file = "data/processed/brms_model_prop_pred_mimicry_only_prior.rds"
)

3.1.1 Prior check

Priors

Code
prior_summary(mod2)
prior class coef group resp dpar nlpar lb ub tag source
normal(0, 1) b user
b scalejulian_day default
normal(0, 2.5) Intercept user
lkj_corr_cholesky(1) L default
L pred_common_name default
exponential(1) sd 0 user
sd pred_common_name default
sd Intercept pred_common_name default
sd scalejulian_day pred_common_name default

Prior predictive checks

Code
custom_ppc(only_prior_mod2, group = "vocalizing_type", ndraws = 100)

3.1.2 Fit summary

Code
fill_color <- mako(10)[8]

extended_summary(
  mod2,
  n.posterior = 1000,
  fill = fill_color,
  trace.palette = mako,
  remove.intercepts = FALSE,
  highlight = TRUE,
  print.name = FALSE
)
formula family priors iterations chains thinning warmup diverg_transitions rhats > 1.05 min_bulk_ESS min_tail_ESS seed
1 orig.sound.files | trials(total) ~ scale(julian_day) + (1 + scale(julian_day) | pred_common_name) binomial (logit) b-normal(0, 1) Intercept-normal(0, 2.5) L-lkj_corr_cholesky(1) sd-exponential(1) 4000 4 1 2000 2 (0.00025%) 0 2549.73 2900.21 1896986874
Estimate l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
b_Intercept -1.204 -2.185 -0.153 1.001 2549.73 2900.21
b_scalejulian_day -0.046 -0.967 0.909 1.001 3452.85 3753.83

Posterior predictive check

Code
custom_ppc(mod2, group = "vocalizing_type", ndraws = 100)

Code
# Extract posterior draws as base R data.frame
post <- as.data.frame(mod2)

# Get species levels
species_levels <- unique(mod2$data$pred_common_name)

# Global slope
beta_global <- post[, "b_scalejulian_day"]
# Find all random slope columns automatically
rand_cols <- grep("^r_pred_common_name\\[.*,scalejulian_day\\]$",
names(post),
value = TRUE)
# Extract species names from column names
species_levels <- sub("r_pred_common_name\\[(.*),scalejulian_day\\]", "\\1", rand_cols)


# Storage matrix
results <- matrix(NA, nrow = length(species_levels), ncol = 3)
colnames(results) <- c("mean_slope",
                       "l95",
                       "u95")

rownames(results) <- species_levels


# Loop through species
for (i in seq_along(rand_cols)) {

  slope_draws <- beta_global + post[, rand_cols[i]]

  results[i, "mean_slope"]    <- mean(slope_draws)
  results[i, "l95"]           <- quantile(slope_draws, 0.025)
  results[i, "u95"]           <- quantile(slope_draws, 0.975)
}

# Print results
print(round(results, 4), row.names = TRUE)
mean_slope l95 u95
Three-wattled.Bellbird 0.9856 0.8240 1.1513
Collared.Aracari -0.0238 -0.1982 0.1574
Keel-billed.Toucan -0.8677 -1.0282 -0.7091
Unclassified -0.3365 -0.5525 -0.1239
Code
# dat <- prop_pred[prop_pred$pred_mimic != "Psarocolius montezuma",]
# 
# dat$julian_day <- as.numeric(format(as.Date(dat$date), "%j"))


# Create prediction grid
newdata <- expand.grid(
  julian_day = seq(min(dat$julian_day),
                   max(dat$julian_day),
                   length.out = 100),
  pred_common_name = unique(dat$pred_common_name)
)

# IMPORTANT: match scaling used in model
newdata$scalejulian_day <- scale(dat$julian_day)[1]  # dummy to get attributes
newdata$scalejulian_day <- (newdata$julian_day - mean(dat$julian_day)) /
                           sd(dat$julian_day)

newdata$total <- round(mean(dat$total), 0)  # use mean total for predictions

# Get posterior expected probabilities
epred <- posterior_epred(
  mod2,
  newdata = newdata,
  re_formula = NULL
)

# Posterior mean prediction
newdata$fit <- colMeans(epred)

# Convert julian day back to date for plotting
origin_year <- format(as.Date(dat$date[1]), "%Y")
newdata$date <- as.Date(newdata$julian_day - 1,
                        origin = paste0(origin_year, "-01-01"))

newdata$fit_prop <- newdata$fit / 100

# include only those for which CI did not include 0
results_df <- as.data.frame(results)
sig_spp <- rownames(results_df)[results_df$l95 * results_df$u95 > 0]

newdata <- newdata[newdata$pred_common_name %in% c("Three-wattled Bellbird", "Collared Aracari",     "Keel-billed Toucan", "Unclassified"), ]


# Plot
ggplot(
  dat[dat$prop > 0, ],
  aes(
    x = as.Date(date),
    y = prop,
    fill = pred_common_name,
    col = pred_common_name,
    size = orig.sound.files
  )
) +
  geom_point(pch = 21) +
  geom_line(
    data = newdata,
    aes(x = date, y = fit_prop, color = pred_common_name),
    size = 1.2
  ) +
  scale_fill_viridis_d(alpha = 0.6, end = 0.8) +
  scale_color_viridis_d(alpha = 0.8, end = 0.8) +
  theme_classic() +
  theme(axis.text.x = element_text(angle = 45, vjust = 0.5)) +
  labs(
    x = "Date",
    y = "Proportion of vocalizations",
    fill = "Predicted species",
    color = "Predicted species",
    size = "Number of recorded\nvocalizations"
  )
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.

3.1.3 Summary

  • The proportion of vocalizations of its own species increased true time while the proportion for Killed-billed Toucan and unclassified calls decreased
  • Proportions for Collared Araraci did not show a definitive pattern
─ Session info ───────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.5.2 (2025-10-31)
 os       Ubuntu 22.04.5 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-05-21
 pandoc   3.2 @ /usr/lib/rstudio/resources/app/bin/quarto/bin/tools/x86_64/ (via rmarkdown)
 quarto   1.7.31 @ /usr/local/bin/quarto

─ Packages ───────────────────────────────────────────────────────────────────
 package        * version    date (UTC) lib source
 abind            1.4-8      2024-09-12 [1] CRAN (R 4.5.0)
 ape              5.8-1      2024-12-16 [1] CRAN (R 4.5.0)
 arrayhelpers     1.1-0      2020-02-04 [1] CRAN (R 4.5.0)
 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 [3] CRAN (R 4.5.0)
 bridgesampling   1.2-1      2025-11-19 [1] CRAN (R 4.5.0)
 brio             1.1.5      2024-04-24 [1] CRAN (R 4.5.0)
 brms           * 2.23.0     2025-09-09 [1] CRAN (R 4.5.0)
 brmsish        * 1.0.0      2026-01-06 [1] CRANs (R 4.5.2)
 Brobdingnag      1.2-9      2022-10-19 [1] CRAN (R 4.5.0)
 cachem           1.1.0      2024-05-16 [1] CRAN (R 4.5.0)
 caret          * 7.0-1      2024-12-10 [1] CRAN (R 4.5.0)
 checkmate        2.3.4      2026-02-03 [1] CRAN (R 4.5.2)
 class            7.3-23     2025-01-01 [4] CRAN (R 4.4.2)
 cli              3.6.6      2026-04-09 [1] CRAN (R 4.5.2)
 coda             0.19-4.1   2024-01-31 [1] CRAN (R 4.5.0)
 codetools        0.2-20     2024-03-31 [4] CRAN (R 4.5.0)
 cowplot        * 1.2.0      2025-07-07 [1] CRAN (R 4.5.0)
 crayon           1.5.3      2024-06-20 [1] CRAN (R 4.5.0)
 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)
 devtools         2.4.5      2022-10-11 [1] CRAN (R 4.5.0)
 digest           0.6.39     2025-11-19 [1] CRAN (R 4.5.0)
 distributional   0.5.0      2024-09-17 [1] CRAN (R 4.5.0)
 doParallel     * 1.0.17     2022-02-07 [1] CRAN (R 4.5.0)
 dplyr            1.1.4      2023-11-17 [1] CRAN (R 4.5.0)
 dtw              1.23-1     2022-09-19 [1] CRAN (R 4.5.0)
 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          1.11.1     2025-05-04 [3] CRAN (R 4.5.0)
 estimability     1.5.1      2024-05-12 [3] CRAN (R 4.5.0)
 evaluate         1.0.5      2025-08-27 [1] CRAN (R 4.5.0)
 farver           2.1.2      2024-05-13 [1] CRAN (R 4.5.0)
 fastmap          1.2.0      2024-05-15 [1] CRAN (R 4.5.0)
 fftw             1.0-9      2024-09-20 [3] CRAN (R 4.5.0)
 foreach        * 1.5.2      2022-02-02 [3] CRAN (R 4.1.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.0)
 ggdist           3.3.3      2025-04-23 [1] CRAN (R 4.5.0)
 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)
 gower            1.0.2      2024-12-17 [3] CRAN (R 4.5.0)
 gridExtra        2.3        2017-09-09 [1] CRAN (R 4.5.0)
 gtable           0.3.6      2024-10-25 [1] CRAN (R 4.5.0)
 hardhat          1.4.1      2025-01-31 [1] CRAN (R 4.5.0)
 htmltools        0.5.9      2025-12-04 [1] CRAN (R 4.5.0)
 htmlwidgets      1.6.4      2023-12-06 [1] RSPM (R 4.5.0)
 httpuv           1.6.16     2025-04-16 [1] RSPM (R 4.5.0)
 httr             1.4.7      2023-08-15 [1] CRAN (R 4.5.0)
 inline           0.3.21     2025-01-09 [1] CRAN (R 4.5.0)
 ipred            0.9-15     2024-07-18 [3] CRAN (R 4.5.0)
 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.0)
 kableExtra     * 1.4.0      2024-01-24 [1] CRAN (R 4.5.0)
 knitr          * 1.51       2025-12-20 [1] CRAN (R 4.5.2)
 labeling         0.4.3      2023-08-29 [1] CRAN (R 4.5.0)
 later            1.4.2      2025-04-08 [1] RSPM (R 4.5.0)
 lattice        * 0.22-9     2026-02-09 [4] 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 [4] CRAN (R 4.4.3)
 Matrix           1.7-4      2025-08-28 [4] CRAN (R 4.5.1)
 matrixStats      1.5.0      2025-01-07 [1] CRAN (R 4.5.0)
 memoise          2.0.1      2021-11-26 [1] CRAN (R 4.5.0)
 mime             0.13       2025-03-17 [1] CRAN (R 4.5.0)
 miniUI           0.1.2      2025-04-17 [3] CRAN (R 4.5.0)
 ModelMetrics     1.2.2.2    2020-03-17 [3] CRAN (R 4.0.1)
 multcomp         1.4-28     2025-01-29 [3] CRAN (R 4.5.0)
 mvtnorm          1.3-3      2025-01-10 [1] CRAN (R 4.5.0)
 NatureSounds     1.0.5      2025-01-17 [1] CRAN (R 4.5.0)
 nlme             3.1-168    2025-03-31 [4] CRAN (R 4.4.3)
 nnet             7.3-20     2025-01-01 [4] CRAN (R 4.4.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.47.0     2026-04-17 [1] CRAN (R 4.5.2)
 pbapply          1.7-4      2025-07-20 [1] CRAN (R 4.5.0)
 pillar           1.11.1     2025-09-17 [1] CRAN (R 4.5.0)
 pkgbuild         1.4.8      2025-05-26 [1] CRAN (R 4.5.0)
 pkgconfig        2.0.3      2019-09-22 [1] CRAN (R 4.5.0)
 pkgload          1.4.1      2025-09-23 [1] CRAN (R 4.5.0)
 plyr             1.8.9      2023-10-02 [1] CRAN (R 4.5.0)
 posterior        1.6.1      2025-02-27 [1] CRAN (R 4.5.0)
 pROC             1.18.0     2021-09-03 [3] CRAN (R 4.1.1)
 prodlim          2026.03.11 2026-03-11 [1] CRAN (R 4.5.2)
 profvis          0.4.0      2024-09-20 [1] CRAN (R 4.5.0)
 promises         1.3.3      2025-05-29 [1] RSPM (R 4.5.0)
 proxy            0.4-29     2025-12-29 [1] CRAN (R 4.5.2)
 purrr            1.2.0      2025-11-04 [1] CRAN (R 4.5.0)
 QuickJSR         1.8.1      2025-09-20 [1] CRAN (R 4.5.0)
 R6               2.6.1      2025-02-15 [1] CRAN (R 4.5.0)
 RColorBrewer     1.1-3      2022-04-03 [1] CRAN (R 4.5.0)
 Rcpp           * 1.1.1-1.1  2026-04-24 [1] CRAN (R 4.5.2)
 RcppParallel     5.1.11-1   2025-08-27 [1] CRAN (R 4.5.0)
 RCurl            1.98-1.17  2025-03-22 [1] CRAN (R 4.5.0)
 recipes          1.3.0      2025-04-17 [1] CRAN (R 4.5.0)
 remotes          2.5.0      2024-03-17 [1] CRAN (R 4.5.0)
 reshape2         1.4.5      2025-11-12 [1] CRAN (R 4.5.0)
 rjson            0.2.23     2024-09-16 [1] CRAN (R 4.5.0)
 rlang            1.2.0      2026-04-06 [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 [4] CRAN (R 4.4.2)
 Rraven         * 1.0.16     2017-11-17 [1] CRAN (R 4.5.0)
 rstan            2.32.7     2025-03-10 [1] CRAN (R 4.5.0)
 rstantools       2.5.0      2025-09-01 [1] CRAN (R 4.5.0)
 rstudioapi       0.17.1     2024-10-22 [1] CRAN (R 4.5.0)
 Rtsne          * 0.17       2023-12-07 [1] CRAN (R 4.5.0)
 S7               0.2.2      2026-04-22 [1] CRAN (R 4.5.2)
 sandwich         3.1-1      2024-09-15 [3] CRAN (R 4.5.0)
 scales           1.4.0      2025-04-24 [1] CRAN (R 4.5.0)
 seewave          2.2.4      2025-08-19 [1] CRAN (R 4.5.0)
 sessioninfo      1.2.3      2025-02-05 [3] CRAN (R 4.5.0)
 shiny            1.10.0     2024-12-14 [1] CRAN (R 4.5.0)
 signal           1.8-1      2024-06-26 [1] CRAN (R 4.5.0)
 sketchy          1.0.7      2026-01-28 [1] CRANs (R 4.5.2)
 StanHeaders      2.32.10    2024-07-15 [1] CRAN (R 4.5.0)
 stringi          1.8.7      2025-03-27 [1] CRAN (R 4.5.0)
 stringr          1.6.0      2025-11-04 [1] CRAN (R 4.5.0)
 survival         3.8-6      2026-01-16 [4] CRAN (R 4.5.2)
 svglite          2.2.2      2025-10-21 [1] CRAN (R 4.5.0)
 svUnit           1.0.8      2025-08-26 [1] CRAN (R 4.5.0)
 systemfonts      1.3.1      2025-10-01 [1] CRAN (R 4.5.0)
 tensorA          0.36.2.1   2023-12-13 [1] CRAN (R 4.5.0)
 testthat         3.2.3      2025-01-13 [1] CRAN (R 4.5.0)
 textshaping      1.0.4      2025-10-10 [1] CRAN (R 4.5.0)
 TH.data          1.1-3      2025-01-17 [3] CRAN (R 4.5.0)
 tibble           3.3.0      2025-06-08 [1] RSPM (R 4.5.0)
 tidybayes        3.0.7      2024-09-15 [1] CRAN (R 4.5.0)
 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.0)
 timechange       0.4.0      2026-01-29 [1] CRAN (R 4.5.2)
 timeDate         4041.110   2024-09-22 [3] CRAN (R 4.5.0)
 tuneR            1.4.7      2024-04-17 [1] CRAN (R 4.5.0)
 urlchecker       1.0.1      2021-11-30 [1] CRAN (R 4.5.0)
 usethis          3.1.0      2024-11-26 [3] CRAN (R 4.5.0)
 V8               6.0.4      2025-06-04 [1] RSPM (R 4.5.0)
 vctrs            0.7.3      2026-04-11 [1] CRAN (R 4.5.2)
 viridis        * 0.6.5      2024-01-29 [1] CRAN (R 4.5.0)
 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.0)
 withr            3.0.2      2024-10-28 [1] CRAN (R 4.5.0)
 xaringanExtra    0.8.0      2024-05-19 [1] CRAN (R 4.5.0)
 xfun             0.57       2026-03-20 [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-14     2025-04-10 [3] CRAN (R 4.5.0)

 [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.

──────────────────────────────────────────────────────────────────────────────