pca <-readRDS("./data/processed/pca_acoustic_features_2026.RDS")# summary(pca)# plot rotation values by PCpca_rot <-as.data.frame(pca$rotation[, 1:4])pca_var <-round(summary(pca)$importance[2, ] *100)pca_rot_stck <-stack(pca_rot)pca_rot_stck$variable <-rownames(pca_rot)pca_rot_stck$values[pca_rot_stck$ind =="PC1"] <- pca_rot_stck$values[pca_rot_stck$ind =="PC1"]pca_rot_stck$Sign <-ifelse(pca_rot_stck$values >0, "Positive", "Negative")pca_rot_stck$rotation <-abs(pca_rot_stck$values)pca_rot_stck$ind_var <-paste0(pca_rot_stck$ind, " (", sapply(pca_rot_stck$ind,function(x) pca_var[names(pca_var) == x]), "%)")pca_rot_stck$top_vars <-ave(abs(pca_rot_stck$values), pca_rot_stck$ind,FUN =function(x) {# Order decreasing ord <-order(x, decreasing =TRUE) x_sorted <- x[ord]# Cumulative proportion cumprop <-cumsum(x_sorted) /sum(x_sorted) selected_sorted <- cumprop <=0.50# variables with 50% of contribution selected_sorted[which(cumprop >=0.50)[1]] <-TRUE# Return logical vector in original order selected <-logical(length(x)) selected[ord] <- selected_sorted selected })# Create facet-specific variablepca_rot_stck$var_facet <-paste(pca_rot_stck$ind, pca_rot_stck$variable, sep ="_")# Reorder within each ind by rotation (largest at top after coord_flip)pca_rot_stck <-do.call(rbind, lapply(split(pca_rot_stck, pca_rot_stck$ind), function(df) { df$var_facet <-factor( df$var_facet,levels = df$var_facet[order(df$rotation)] ) df}))# add which variables are stablestable_df <-get_stable_loadings(pca, pcs =4, B =1000, cum_threshold =0.5, freq_threshold =0.5, seed =123)pca_rot_stck <-merge( pca_rot_stck, stable_df,by =c("variable", "ind"),all.x =TRUE)pca_rot_stck$top_vars <-ifelse(pca_rot_stck$stable, 0.6, 1)# Build colored labels per row# Colored labelspca_rot_stck$label_col <-ifelse( pca_rot_stck$stable,paste0("<span style='color:black;'>", pca_rot_stck$variable, "</span>"),paste0("<span style='color:gray50;'>", pca_rot_stck$variable, "</span>"))# Named vector for labelslabel_vec <-setNames(pca_rot_stck$label_col, pca_rot_stck$var_facet)# absolute CIpca_rot_stck$ci_low_plot <-abs(pca_rot_stck$ci_lower)pca_rot_stck$ci_high_plot <-abs(pca_rot_stck$ci_upper)# Plotggplot(pca_rot_stck, aes(x = var_facet, y = rotation, fill = Sign, alpha =as.factor(top_vars))) +geom_col() +geom_errorbar(aes(ymin = ci_low_plot,ymax = ci_high_plot),width =0.1,linewidth =0.2) +coord_flip() +scale_alpha_manual(values = pca_rot_stck$top_vars, guide =NULL) +scale_x_discrete(labels = label_vec, name ="Variable") +scale_fill_viridis_d(alpha =0.7, begin =0.2, end =0.8) +facet_wrap(~ind_var, scales ="free_y") +theme_classic() +theme(axis.text.y =element_markdown() )
Bar plots show variable loadings for the first four principal components. Loadings are computed from a PCA performed on standardized acoustic variables (mean-centered and scaled to unit variance). Bars represent the mean absolute loading estimated from 1,000 bootstrap resamples of the original dataset. For each of 1,000 bootstrap iterations, we resampled observations (rows of the original data matrix) with replacement to generate a dataset of equal size to the original. Horizontal whiskers indicate 95% bootstrap confidence intervals (2.5–97.5 percentiles of the bootstrap distribution). Variables are ordered within each component by their contribution (squared loading). Bar color indicates the sign of the original loading (green = positive, purple = negative). Transparency highlights whether a variable consistently contributes to the component: variables are considered stable when (i) their bootstrap confidence interval does not overlap zero and (ii) they are selected among the set of variables jointly explaining at least 50% of the component variance in ≥50% of bootstrap replicates. This procedure allows assessment of both the magnitude and robustness of variable contributions to each principal component.
7.2 Predictions
Code
sim <-data.frame(guild =rep(c("Cerrado", "Abierto"), each =100),duration =c(rnorm(100, mean =0.03, sd =0.01), rnorm(100, mean =0.05, sd =0.01)))ggplot(sim, aes(x = guild, y = duration)) +geom_boxplot(fill =mako(10, alpha =0.3)[4]) +labs(x ="Ambiente", y ="Duración") +# remove y axis tick labelstheme_classic(base_size =30) +theme(axis.text.y=element_blank(),axis.ticks.y=element_blank() )
Code
sim2 <-data.frame(guild =rep(c("Cerrado", "Abierto"), each =100),rate =c(rnorm(100, mean =0.05, sd =0.01), rnorm(100, mean =0.032, sd =0.01)))ggplot(sim2, aes(x = guild, y = rate)) +geom_boxplot(fill =mako(10, alpha =0.3)[4]) +labs(x ="Ambiente", y ="Tasa de llamados") +# remove y axis tick labelstheme_classic(base_size =30) +theme(axis.text.y=element_blank(),axis.ticks.y=element_blank() )
Code
sim3 <-data.frame(guild =rep(c("Cerrado", "Abierto"), each =100),freq =c(rnorm(100, mean =0.05, sd =0.01), rnorm(100, mean =0.032, sd =0.01)))ggplot(sim3, aes(x = guild, y = freq)) +geom_boxplot(fill =mako(10, alpha =0.3)[4]) +labs(x ="Ambiente", y ="Frecuencia (kHz)") +# remove y axis tick labelstheme_classic(base_size =30) +theme(axis.text.y=element_blank(),axis.ticks.y=element_blank() )
7.3 Macroevolution
7.3.1 By guild
Code
est_bats <-readRDS("./data/processed/acoustic_analysis_data_2026.RDS")iter <-5000chains <-2priors <-c(# Population-level effects (including intercept)# For log-link Gamma, intercept is on log scaleset_prior("normal(1, 2)", class ="Intercept"), # log-scale: exp(1) ≈ 2.7 mean durationset_prior("normal(0, 1)", class ="b"),# Random effect standard deviationsset_prior("exponential(1)", class ="sd", group ="indiv"),set_prior("exponential(1)", class ="sd", group ="sp"),# Gamma shape parameter (nu or shape)# Constrained to be positiveset_prior("exponential(1)", class ="shape"))dur_mod <-brm(formula = duration ~ guild + (1| indiv) + (1| sp) +ar(p =2, time = orig.start, gr = orig.sf) + (1| treatment),family =Gamma(),data = est_bats,prior = priors,chains = chains,iter = iter,file ="./data/processed/duration_macro_model.RDS" )priors <-c(# Population-level (fixed) effectsset_prior("normal(0, 1)", class ="b"),# Intercept - centered around plausible frequency value# Adjust mean based on your data (e.g., kHz for bat calls)set_prior("normal(0, 10)", class ="Intercept"),# Random effect standard deviationsset_prior("exponential(1)", class ="sd", group ="indiv"),set_prior("exponential(1)", class ="sd", group ="sp"),# Residual standard deviationset_prior("exponential(1)", class ="sigma"))freq_mod <-brm(formula = freq ~ guild + (1| indiv) + (1| sp) +ar(p =2, time = orig.start, gr = orig.sf) + (1| treatment),family =gaussian(),data = est_bats,prior = priors,chains = chains,iter = iter,file ="./data/processed/freq_macro_model.RDS" )priors <-c(# Population-level effects (including intercept)# For log-link Gamma, intercept is on log scaleset_prior("normal(1, 2)", class ="Intercept"), # log-scale: exp(1) ≈ 2.7 mean durationset_prior("normal(0, 1)", class ="b"),# Random effect standard deviationsset_prior("exponential(1)", class ="sd", group ="indiv"),set_prior("exponential(1)", class ="sd", group ="sp"),# Gamma shape parameter (nu or shape)# Constrained to be positiveset_prior("exponential(1)", class ="shape"))rate_mod <-brm(formula = rate ~ guild + (1| indiv) + (1| sp) +ar(p =2, time = orig.start, gr = orig.sf) + (1| treatment),family =Gamma(),data = est_bats,prior = priors,chains = chains,iter = iter *2,file ="./data/processed/rate_macro_model.RDS" )priors <-c(# Population-level effects# PCA scores usually range ~ -3 to 3set_prior("normal(0, 1)", class ="b"),# Intercept - centered at 0 (mean of PCA scores)set_prior("normal(0, 1)", class ="Intercept"),# Random effect standard deviations# Expect smaller variation than total variance (SD=1)set_prior("exponential(2)", class ="sd", group ="indiv"),set_prior("exponential(2)", class ="sd", group ="sp"),# Residual standard deviation# Should be less than 1 (total SD of PCA scores)set_prior("exponential(2)", class ="sigma"))pc1_mod <-brm(formula = pc1 ~ guild + (1| indiv) + (1| sp) +ar(p =2, time = orig.start, gr = orig.sf) + (1| treatment),data = est_bats,prior = priors,chains = chains,iter = iter,file ="./data/processed/pc1_macro_model.RDS" )
est_bats <-readRDS("./data/processed/acoustic_analysis_data_2026.RDS")iter <-5000chains <-2priors <-c(# Population-level effects (including intercept)# For log-link Gamma, intercept is on log scaleset_prior("normal(1, 2)", class ="Intercept"), # log-scale: exp(1) ≈ 2.7 mean durationset_prior("normal(0, 1)", class ="b"),# Random effect standard deviationsset_prior("exponential(1)", class ="sd", group ="indiv"),# Gamma shape parameter (nu or shape)# Constrained to be positiveset_prior("exponential(1)", class ="shape"))dur_sp_mod <-brm(formula = duration ~ sp + (1| indiv) + (1| treatment),family =Gamma(),data = est_bats,prior = priors,chains = chains,iter = iter,file ="./data/processed/duration_sp_macro_model.RDS" )priors <-c(# Population-level (fixed) effectsset_prior("normal(0, 1)", class ="b"),# Intercept - centered around plausible frequency value# Adjust mean based on your data (e.g., kHz for bat calls)set_prior("normal(0, 10)", class ="Intercept"),# Random effect standard deviationsset_prior("exponential(1)", class ="sd", group ="indiv"),# Residual standard deviationset_prior("exponential(1)", class ="sigma"))freq_mod <-brm(formula = freq ~ sp + (1| indiv) + (1| treatment) +ar(p =2, time = orig.start, gr = orig.sf),family =gaussian(),data = est_bats,prior = priors,chains = chains,iter = iter,file ="./data/processed/freq_sp_macro_model.RDS" )priors <-c(# Population-level effects (including intercept)# For log-link Gamma, intercept is on log scaleset_prior("normal(1, 2)", class ="Intercept"), # log-scale: exp(1) ≈ 2.7 mean durationset_prior("normal(0, 1)", class ="b"),# Random effect standard deviationsset_prior("exponential(1)", class ="sd", group ="indiv"),# Gamma shape parameter (nu or shape)# Constrained to be positiveset_prior("exponential(1)", class ="shape"))rate_mod <-brm(formula = rate ~ sp + (1| indiv) + (1| treatment) +ar(p =2, time = orig.start, gr = orig.sf),family =Gamma(),data = est_bats,prior = priors,chains = chains,iter = iter *2,file ="./data/processed/rate_sp_macro_model.RDS" )priors <-c(# Population-level effects# PCA scores usually range ~ -3 to 3set_prior("normal(0, 1)", class ="b"),# Intercept - centered at 0 (mean of PCA scores)set_prior("normal(0, 1)", class ="Intercept"),# Random effect standard deviations# Expect smaller variation than total variance (SD=1)set_prior("exponential(2)", class ="sd", group ="indiv"),# Residual standard deviation# Should be less than 1 (total SD of PCA scores)set_prior("exponential(2)", class ="sigma"))pc1_mod <-brm(formula = pc1 ~ sp + (1| indiv) ++ (1| treatment) +ar(p =2, time = orig.start, gr = orig.sf),data = est_bats,prior = priors,chains = chains,iter = iter,file ="./data/processed/pc1_sp_macro_model.RDS" )
mod <-readRDS("./data/processed/pc1_sp_macro_model.RDS")# contrastscontrasts <-contrasts(fit = mod,predictor ="sp",n.posterior =2000,level.sep =" VS ",html.table =FALSE,plot =TRUE,highlight =TRUE,fill = fill_color)
7.4 Plasticity
7.4.1 Only habitat
Code
priors <-c(# Population-level effects (including intercept)# For log-link Gamma, intercept is on log scaleset_prior("normal(1, 2)", class ="Intercept"), # log-scale: exp(1) ≈ 2.7 mean durationset_prior("normal(0, 1)", class ="b"),# Random effect standard deviationsset_prior("exponential(1)", class ="sd", group ="indiv"),set_prior("exponential(1)", class ="sd", group ="sp"),# Gamma shape parameter (nu or shape)# Constrained to be positiveset_prior("exponential(1)", class ="shape"))dur_plast_mod <-brm(formula = duration ~ treatment + (1| indiv) + (1| sp),family =Gamma(),data = est_bats,prior = priors,chains = chains,iter = iter *2,file ="./data/processed/duration_plasticity_model.RDS")priors <-c(# Population-level (fixed) effectsset_prior("normal(0, 1)", class ="b"),# Intercept - centered around plausible frequency value# Adjust mean based on your data (e.g., kHz for bat calls)set_prior("normal(0, 10)", class ="Intercept"),# Random effect standard deviationsset_prior("exponential(1)", class ="sd", group ="indiv"),set_prior("exponential(1)", class ="sd", group ="sp"),# Residual standard deviationset_prior("exponential(1)", class ="sigma"))freq_plast_mod <-brm(formula = freq ~ treatment + (1| indiv) + (1| sp) +ar(p =2, time = orig.start, gr = orig.sf),family =gaussian(),data = est_bats,prior = priors,chains = chains,iter = iter,file ="./data/processed/freq_plasticity_model.RDS" )priors <-c(# Population-level effects (including intercept)# For log-link Gamma, intercept is on log scaleset_prior("normal(1, 2)", class ="Intercept"), # log-scale: exp(1) ≈ 2.7 mean durationset_prior("normal(0, 1)", class ="b"),# Random effect standard deviationsset_prior("exponential(1)", class ="sd", group ="indiv"),set_prior("exponential(1)", class ="sd", group ="sp"),# Gamma shape parameter (nu or shape)# Constrained to be positiveset_prior("exponential(1)", class ="shape"))rate_plast_mod <-brm(formula = rate ~ treatment + (1| indiv) + (1| sp) +ar(p =2, time = orig.start, gr = orig.sf),family =Gamma(),data = est_bats,prior = priors,chains = chains,iter = iter,file ="./data/processed/rate_plasticity_model.RDS" )priors <-c(# Population-level effects# PCA scores usually range ~ -3 to 3set_prior("normal(0, 1)", class ="b"),# Intercept - centered at 0 (mean of PCA scores)set_prior("normal(0, 1)", class ="Intercept"),# Random effect standard deviations# Expect smaller variation than total variance (SD=1)set_prior("exponential(2)", class ="sd", group ="indiv"),set_prior("exponential(2)", class ="sd", group ="sp"),# Residual standard deviation# Should be less than 1 (total SD of PCA scores)set_prior("exponential(2)", class ="sigma"))pc1_plast_mod <-brm(formula = pc1 ~ treatment + (1| indiv) + (1| sp) +ar(p =2, time = orig.start, gr = orig.sf),data = est_bats,prior = priors,chains = chains,iter = iter,file ="./data/processed/pc1_plasticity_model.RDS" )
mod <-readRDS("./data/processed/pc1_plasticity_model.RDS")# contrastscontrasts <-contrasts(fit = mod,predictor ="treatment",n.posterior =2000,level.sep =" VS ",html.table =FALSE,plot =TRUE,highlight =TRUE,fill = fill_color)
7.4.2 Habitat by guild
Code
iter <-10000chains <-4priors <-c(# Population-level effects (including intercept)# For log-link Gamma, intercept is on log scaleset_prior("normal(1, 2)", class ="Intercept"), # log-scale: exp(1) ≈ 2.7 mean durationset_prior("normal(0, 1)", class ="b"),# Random effect standard deviationsset_prior("exponential(1)", class ="sd", group ="indiv"),set_prior("exponential(1)", class ="sd", group ="sp"),# Gamma shape parameter (nu or shape)# Constrained to be positiveset_prior("exponential(1)", class ="shape"))dur_plast_mod <-brm(formula = duration ~ treatment * guild + (1| indiv) + (1| sp) +ar(p =2, time = orig.start, gr = orig.sf),family =Gamma(),data = est_bats,prior = priors,chains = chains,iter = iter,file ="./data/processed/duration_plasticity_interaction_model.RDS")priors <-c(# Population-level (fixed) effectsset_prior("normal(0, 1)", class ="b"),# Intercept - centered around plausible frequency value# Adjust mean based on your data (e.g., kHz for bat calls)set_prior("normal(0, 10)", class ="Intercept"),# Random effect standard deviationsset_prior("exponential(1)", class ="sd", group ="indiv"),set_prior("exponential(1)", class ="sd", group ="sp"),# Residual standard deviationset_prior("exponential(1)", class ="sigma"))freq_plast_mod <-brm(formula = freq ~ treatment * guild + (1| indiv) + (1| sp) +ar(p =2, time = orig.start, gr = orig.sf),family =gaussian(),data = est_bats,prior = priors,chains = chains,iter = iter,file ="./data/processed/freq_plasticity_interaction_model.RDS" )priors <-c(# Population-level effects (including intercept)# For log-link Gamma, intercept is on log scaleset_prior("normal(1, 2)", class ="Intercept"), # log-scale: exp(1) ≈ 2.7 mean durationset_prior("normal(0, 1)", class ="b"),# Random effect standard deviationsset_prior("exponential(1)", class ="sd", group ="indiv"),set_prior("exponential(1)", class ="sd", group ="sp"),# Gamma shape parameter (nu or shape)# Constrained to be positiveset_prior("exponential(1)", class ="shape"))rate_plast_mod <-brm(formula = rate ~ treatment * guild + (1| indiv) + (1| sp) +ar(p =2, time = orig.start, gr = orig.sf),family =Gamma(),data = est_bats,prior = priors,chains = chains,iter = iter,file ="./data/processed/rate_plasticity_interaction_model.RDS" )priors <-c(# Population-level effects# PCA scores usually range ~ -3 to 3set_prior("normal(0, 1)", class ="b"),# Intercept - centered at 0 (mean of PCA scores)set_prior("normal(0, 1)", class ="Intercept"),# Random effect standard deviations# Expect smaller variation than total variance (SD=1)set_prior("exponential(2)", class ="sd", group ="indiv"),set_prior("exponential(2)", class ="sd", group ="sp"),# Residual standard deviation# Should be less than 1 (total SD of PCA scores)set_prior("exponential(2)", class ="sigma"))pc1_plast_mod <-brm(formula = pc1 ~ treatment * guild + (1| indiv) + (1| sp) +ar(p =2, time = orig.start, gr = orig.sf),data = est_bats,prior = priors,chains = chains,iter = iter,file ="./data/processed/pc1_plasticity_interaction_model.RDS" )