Cultural evolution analysis

LBH song type prevalence and degradation

Author
Published

October 30, 2024

Source code and data found at

1 Next steps

  • Measure phylogenetic signal at the lek level to compare it to the across-lek values
  • Measure change in overlap of acoustic space of leks overtime as a measure of cultural convergence
  • Modulation index must be extracted from contours (not from spectro_analysis())

2 Purpose

  • Depict the effect of cultural evolution on song structure variation in long-billed hermits

3 Load packages

Code
# 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", "rprojroot",
    github = "maRce10/warbleR", github = "maRce10/baRulho", "readxl",
    "Rraven", "ggplot2", "brms", "viridis", "corrplot", "DT", "ape",
    "phangorn", "ggtree", "phytools", "ratematrix", "geomorph", "evolvability",
    "geiger", "ggdist", "ggtree", "gghalves", "pbapply", "mvMORPH",
    "corrplot", "caret", "rptR", "lme4", "MASS"))

4 Custom functions

Code
# get phylogenetic signal froma brms model object
phylo_sig_brms <- function(model) {
    hyp <-
        "sd_phylo__Intercept^2 / (sd_phylo__Intercept^2 + sigma^2) = 0"
    hyp_test <- hypothesis(model, hyp, class = NULL)
    return(hyp_test$hypothesis[1, c("Estimate", "CI.Lower", "CI.Upper", "Post.Prob")])
}

# match data and tree tip labels
match_data_tree <- function(data, tree, index) {
    
    if (!is.null(dim(data))){
    data <- data[index %in% tree$tip.label, ]
    rownames(data) <- index[index %in% tree$tip.label]
    } else {
    data <- data[index %in% tree$tip.label]
    names(data) <- index[index %in% tree$tip.label]
    }
    
    # prun tree
    tree <- drop.tip(tree, tip = tree$tip.label[!tree$tip.label %in% index])
    
    # return
    out <- list(data = data, tree = tree)
    return(out)
}

# compare evolutionary rates between features
CompareRates.multTrait <-  function(phy,x,TraitCov=T,ms.err=NULL,ms.cov=NULL){
  #Compares LLik of R-matrix vs. LLik of R-matrix with constrained diagonal
  #TraitCov = TRUE assumes covariation among traits (default)
  #ms.err allows the incorporation of within-species measurement error. Input is a matrix of species (rows) by within-species 
  #variation for each trait (columns).
  #ms.cov allows the incorporation of within-species covariation between traits. Input is a matrix of species (rows) by within-species 
  #covariation for each pair of traits (columns). These must be provided in a specific order, beginning with covariation between trait 1 
  #and the rest, then trait 2 and the rest, etc. For instance, for 4 traits, the columns are: cov_12, cov_13, cov_14, cov_23, cov_24 cov_34. 
  #Some calculations adapted from 'evol.vcv' in phytools (Revell, 2012)

  x<-as.matrix(x)
  N<-nrow(x)
  p<-ncol(x)
  C<-vcv.phylo(phy)
  C<-C[rownames(x),rownames(x)]
  if (is.matrix(ms.err)){    
    ms.err<-as.matrix(ms.err[rownames(x),])}
  if (is.matrix(ms.cov)){    
    ms.cov<-as.matrix(ms.cov[rownames(x),])}
  
  #Cholesky decomposition function for diagonal-constrained VCV
  build.chol<-function(b){
    c.mat<-matrix(0,nrow=p,ncol=p)
    c.mat[lower.tri(c.mat)] <- b[-1]  
    c.mat[p,p]<-exp(b[1])
    c.mat[1,1]<-sqrt(sum((c.mat[p,])^2))
    if(p>2){
      for (i in 2:(p-1)){
        c.mat[i,i]<-ifelse( (c.mat[1,1]^2-sum((c.mat[i,])^2) )>0,
                            sqrt(c.mat[1,1]^2-sum((c.mat[i,])^2)), 0)
      }}
    return(c.mat) 
  }
  
  #Fit Rate matrix for all traits: follows code of L. Revell (evol.vcv)
  a.obs<-colSums(solve(C))%*%x/sum(solve(C))   
  D<-matrix(0,N*p,p)
  for(i in 1:(N*p)) for(j in 1:p) if((j-1)*N<i&&i<=j*N) D[i,j]=1.0
  y<-as.matrix(as.vector(x))
  one<-matrix(1,N,1)
  R.obs<-t(x-one%*%a.obs)%*%solve(C)%*%(x-one%*%a.obs)/N
  if (TraitCov==F)    #for TraitCov = F
  { R.obs<-diag(diag(R.obs),p)  }
  #Calculate observed likelihood with or without measurement error
  LLik.obs<-ifelse(is.matrix(ms.err)==TRUE, 
                   -t(y-D%*%t(a.obs))%*%ginv((kronecker(R.obs,C)+ diag(as.vector(ms.err))))%*%(y-D%*%t(a.obs))/2-N*p*log(2*pi)/2-  
                     determinant((kronecker(R.obs,C)+ diag(as.vector(ms.err))))$modulus[1]/2 , 
                   -t(y-D%*%t(a.obs))%*%ginv(kronecker(R.obs,C))%*%(y-D%*%t(a.obs))/2-N*p*log(2*pi)/2-  
                     determinant(kronecker(R.obs,C))$modulus[1]/2
  ) 
  
  #Fit common rate for all traits; search over parameter space   
  sigma.mn<-mean(diag(R.obs))   #reasonable start value for diagonal
  
  #Within-species measurement error matrix
  if(is.matrix(ms.err)){m.e<-diag(as.vector(ms.err))}
  
  #Within-species measurement error and trait covariation matrix
  if (is.matrix(ms.err) && is.matrix(ms.cov)){  
    within.spp<-cbind(ms.err,ms.cov)
    rc.label<-NULL
    for (i in 1:p){ rc.label<-rbind(rc.label,c(i,i)) }
    for (i in 1:p){
      for (j in 2:p){ if (i!=j && i<j){rc.label<-rbind(rc.label,c(i,j))} }}
    m.e<-NULL
    for (i in 1:p){
      tmp<-NULL
      for (j in 1:p){
        for (k in 1:nrow(rc.label)){
          if(setequal(c(i,j),rc.label[k,])==T) {tmp<-cbind(tmp,diag(within.spp[,k]))}
        }
      }
      m.e<-rbind(m.e,tmp)
    }
  }
  
  #likelihood optimizer for no trait covariation
  lik.covF<-function(sigma){  
    R<-matrix(0,nrow=p,ncol=p)
    diag(R)<-sigma
    LLik<-ifelse(is.matrix(ms.err)==TRUE, 
                 -t(y-D%*%t(a.obs))%*%ginv((kronecker(R,C)+ m.e))%*%(y-D%*%t(a.obs))/2-N*p*log(2*pi)/2-  
                   determinant((kronecker(R,C)+ m.e))$modulus[1]/2 , 
                 -t(y-D%*%t(a.obs))%*%ginv(kronecker(R,C))%*%(y-D%*%t(a.obs))/2-N*p*log(2*pi)/2-  
                   determinant(kronecker(R,C))$modulus[1]/2
    ) 
    if (LLik == -Inf) { LLikk <- -1e+10  }
    return(-LLik)
  }
  
  #likelihood optimizer with trait covariation
  lik.covT<-function(sigma){  
    low.chol<-build.chol(sigma)
    R<-low.chol%*%t(low.chol)
    
    LLik<-ifelse(is.matrix(ms.err)==TRUE, 
                 -t(y-D%*%t(a.obs))%*%ginv((kronecker(R,C)+ m.e))%*%(y-D%*%t(a.obs))/2-N*p*log(2*pi)/2-  
                   determinant((kronecker(R,C)+ m.e))$modulus[1]/2 , 
                 -t(y-D%*%t(a.obs))%*%ginv(kronecker(R,C))%*%(y-D%*%t(a.obs))/2-N*p*log(2*pi)/2-  
                   determinant(kronecker(R,C))$modulus[1]/2
    ) 
    if (LLik == -Inf)  {LLikk <- -1e+10  }
    return(-LLik)
  }
  
  ##Optimize for no trait covariation (methods: "Nelder-Mead"  or "SANN")
  if (TraitCov==F)    
  { model1<-optim(sigma.mn,fn=lik.covF,method="L-BFGS-B",lower=c(0.0))}
  ##Optimize with trait covariation
  R.offd<-rep(0,(p*(p-1)/2))
  if (TraitCov==T)  
  {model1<-optim(par=c(sigma.mn,R.offd),fn=lik.covT,method="L-BFGS-B")}
  
  #### Assemble R.constrained
  if (TraitCov==F){R.constr<-diag(model1$par,p)}
  if (TraitCov==T){  
    chol.mat<-build.chol(model1$par)
    R.constr<-chol.mat%*%t(chol.mat)}
  
  if(model1$convergence==0)
    message<-"Optimization has converged."
  else
    message<-"Optim may not have converged.  Consider changing start value or lower/upper limits."
  LRT<- (-2*((-model1$value-LLik.obs)))
  LRT.prob<-pchisq(LRT, (p-1),lower.tail=FALSE) #df = Nvar-1
  AIC.obs<- -2*LLik.obs+2*p+2*p #(2p twice: 1x for rates, 1x for anc. states)
  AIC.common<- -2*(-model1$value)+2+2*p #(2*1: for 1 rate 2p for anc. states)
  return(list(Robs=R.obs, Rconstrained=R.constr,Lobs=LLik.obs,Lconstrained=(-model1$value),LRTest=LRT,Prob=LRT.prob,
              AICc.obs=AIC.obs,AICc.constrained=AIC.common,optimmessage=message))   
}

cols_corr <- colorRampPalette(c(mako(3, direction = 1, begin = 0.2,
end = 0.5), "#BEBEBE1A", "white", "#BEBEBE1A",mako(3, direction = 1,
begin = 0.7, end = 0.9)))(30)

5 Plot tree

Code
tree <- readRDS("./data/processed/single_combined_map_tree_prank_all_uexp_old.RDS")


# #Highlight and label clade
ult_tree <- chronopl(tree, lambda = 0.5)

groupInfo <- sapply(strsplit(ult_tree$tip.label, split = "-"), "[[",
    1)

g <- split(ult_tree$tip.label, groupInfo)

p <- ggtree(ult_tree, layout = "fan")

clades <- sapply(g, function(n) MRCA(p, n))

p <- groupClade(p, clades, group_name = "subtree") + aes(color = subtree) +
    scale_color_viridis_d(option = "G", begin = 0.1, end = 0.9, guide = "none")

for (i in seq_along(clades)) p <- p + geom_cladelabel(clades[i], names(clades)[i],
    barsize = 2, offset.text = 0.2, fontsize = 3, hjust = 0.5, color = mako(5,
        begin = 0.1, end = 0.9)[i])

p

6 Select sound data

Code
# edited file open
est <- readRDS("~/Dropbox/Projects/lbh_songtype_prevalence_and_degradation/data/raw/EST all sels aug 2019.RDS")

est$lek.song.type.id <- paste(est$lek.song.type, seq_len(nrow(est)),
    sep = "-")
est$lek.songtype.year <- paste(est$lek.song.type, est$year, sep = "-")

# remove weird selection
est <- est[grep("310.CCL.2013.3.3.7.38|43.CCL.2011.6.16.8.19", est$sound.files,
    invert = TRUE), ]

# choose from those with SNR >3 best examples from different
# years when possible
out <- lapply(tree_sty, function(x) {

    Y <- as.data.frame(est[est$lek.songtype.year == x, ])

    # year_buffer <- c(-1, 1, -2, 2) z <- 0 while(nrow(Y) == 0 &
    # z < 4){ z <- z + 1 year <- as.numeric(strsplit(x,
    # '-')[[1]][3]) e <- paste(paste(strsplit(x, '-')[[1]][-3],
    # collapse = '-'), year - year_buffer[z], sep = '-') Y <-
    # as.data.frame(est[est$lek.songtype.year == e, ]) }
    # Y$lek.songtype.year.target <- if (nrow(Y) > 0) x else
    # vector() sort by SNR
    Y <- Y[order(Y$SNR, decreasing = TRUE), ]

    # remove bad ones (below -40 SNR)
    Y <- Y[Y$SNR > -40, ]

    # keep highest 3 SNR
    if (nrow(Y) > 4)
        Y <- Y[1:4, ]

    # if(nrow(Y) > 0) Y <- rename_est_waves(Y, new.selec =
    # 1:nrow(Y), new.sound.files = paste(x,
    # seq_len(length(attr(Y, 'wave.objects'))), sep = '_'))

    return(Y)

})

sub_dat <- do.call(rbind, out)

sub_est <- fix_extended_selection_table(sub_dat, est)

sub_est <- resample_est(sub_est, samp.rate = 44.1, bit.depth = 16,
    parallel = 20)

saveRDS(sub_est, "./data/raw/extended_selection_table_all_lek_songtype_year_high_snr.RDS")

7 Frequency contours

Code
contours <- read.csv("/home/m/Dropbox/Projects/lbh_songtype_prevalence_and_degradation/data/processed/seltailor_output.csv")

contours$maxdom_cont <- apply(contours[, grep("ff", colnames(contours))],
    1, max)
contours$mindom_cont <- apply(contours[, grep("ff", colnames(contours))],
    1, min)
contours$meandom_cont <- apply(contours[, grep("ff", colnames(contours))],
    1, mean)

# cummulative sum of absolute change between consecutive values
# of ff
contours$meandom_cont <- apply(contours[, grep("ff", colnames(contours))],
    1, mean)

cum_contours <- contours[, grep("ff", colnames(contours))][, -1] -
    contours[, grep("ff", colnames(contours))][, -sum(grepl("ff",
        colnames(contours)))]

contours$modulation <- apply(cum_contours, 1, function(x) sum(abs(x)))

contours$lek.songtype.year <- sapply(strsplit(contours$song.type,
    "-"), function(x) paste(x[2], x[3], x[4], sep = "-"))

source("~/Dropbox/R_package_testing/warbleR/R/freq_DTW.R")
dtw_dist <- freq_DTW(ts.df = contours[, c("sound.files", "selec",
    grep("ff", colnames(contours), value = TRUE))], parallel = 20)

dtw_mds <- cmdscale(dtw_dist, k = 2)

contours$contour.sim <- dtw_mds[, 1]

saveRDS(contours, "./data/processed/contours_dtw.RDS")

8 Measure acoustic structure

Code
sub_est <- readRDS("./data/raw/extended_selection_table_all_lek_songtype_year_high_snr.RDS")

contours <- readRDS("./data/processed/contours_dtw.RDS")

source("~/Dropbox/R_package_testing/warbleR/R/spectro_analysis.R")
source("~/Dropbox/R_package_testing/warbleR/R/internal_functions.R")
spec_features <- spectro_analysis(sub_est, wl = 200, ovlp = 90, parallel = 20)
spec_features$song.type.year <- sub_est$lek.songtype.year
spec_features$org.sound.files <- sub_est$sound.files
spec_features$SNR <- sub_est$SNR

# fix modulation index spec_features$modindx <-
# spec_features$modindx * spec_features$dfrange

spec_features$modinx <- sapply(spec_features$song.type.year, function(x) {
    y <- contours[contours$lek.songtype.year == x, "modulation"]
    if (length(y) > 0)
        return(y) else return(NA)
})

spec_features$mindom <- sapply(spec_features$song.type.year, function(x) {
    y <- contours[contours$lek.songtype.year == x, "mindom_cont"]
    if (length(y) > 0)
        return(y) else return(NA)
})

spec_features$maxdom <- sapply(spec_features$song.type.year, function(x) {
    y <- contours[contours$lek.songtype.year == x, "maxdom_cont"]
    if (length(y) > 0)
        return(y) else return(NA)
})


spec_features$meandom <- sapply(spec_features$song.type.year, function(x) {
    y <- contours[contours$lek.songtype.year == x, "meandom_cont"]
    if (length(y) > 0)
        return(y) else return(NA)
})

spec_features$dfrange <- spec_features$maxdom - spec_features$mindom

spec_features$contour.sim <- sapply(spec_features$song.type.year,
    function(x) {
        y <- contours[contours$lek.songtype.year == x, "contour.sim"]
        if (length(y) > 0)
            return(y) else return(NA)
    })

saveRDS(spec_features, "./data/processed/spectro_features_all_lek_songtype_year_high_snr.RDS")
Code
dat <- read.csv("/home/m/Dropbox/Projects/LBH cult-evo/Song selections aug 2019 SAME.FREQ.RANGE AND METADATA.csv")


cs <- check_sels(dat, path = "/media/m/Expansion/Projects/Ongoing/Phaethornis_longirostris/Sound recordings/Manual recordings/Recs/",
    parallel = 20)

est_lbh <- selection_table(dat, path = "/media/m/Expansion/Projects/Ongoing/Phaethornis_longirostris/Sound recordings/Manual recordings/Recs/",
    parallel = 20, extended = TRUE)

saveRDS(est_lbh, "./data/processed/extended_selection_table_Song selections aug 2019 SAME.FREQ.RANGE AND METADATA.RDS")
Code
est_lbh <- readRDS("./data/processed/extended_selection_table_Song selections aug 2019 SAME.FREQ.RANGE AND METADATA.RDS")

est_lbh <- resample_est(est_lbh, samp.rate = 44.1, bit.depth = 16,
    parallel = 20)

source("~/Dropbox/R_package_testing/warbleR/R/spectro_analysis.R")
source("~/Dropbox/R_package_testing/warbleR/R/internal_functions.R")
sp_feat_lbh_all <- spectro_analysis(est_lbh, wl = 200, ovlp = 90,
    parallel = 20)

sp_feat_lbh_all$bird.id <- est_lbh$bird.id
sp_feat_lbh_all$lek.song.type <- est_lbh$lek.song.type
sp_feat_lbh_all$year <- est_lbh$year
sp_feat_lbh_all$lek <- est_lbh$lek

# fix modulation index sp_feat_lbh_all$modindx <-
# sp_feat_lbh_all$modindx * sp_feat_lbh_all$dfrange

sp_feat_lbh_all$SNR <- sig2noise(est_lbh, parallel = 20, mar = 0.1)$SNR

sp_feat_lbh_all$song.type.year <- paste(sp_feat_lbh_all$lek.song.type,
    sp_feat_lbh_all$year, sep = "-")

sp_feat_lbh_all$modinx <- sapply(sp_feat_lbh_all$song.type.year, function(x) {
    y <- contours[contours$lek.songtype.year == x, "modulation"]
    if (length(y) > 0)
        return(y) else return(NA)
})

sp_feat_lbh_all$mindom <- sapply(sp_feat_lbh_all$song.type.year, function(x) {
    y <- contours[contours$lek.songtype.year == x, "mindom_cont"]
    if (length(y) > 0)
        return(y) else return(NA)
})

sp_feat_lbh_all$maxdom <- sapply(sp_feat_lbh_all$song.type.year, function(x) {
    y <- contours[contours$lek.songtype.year == x, "maxdom_cont"]
    if (length(y) > 0)
        return(y) else return(NA)
})


sp_feat_lbh_all$meandom <- sapply(sp_feat_lbh_all$song.type.year,
    function(x) {
        y <- contours[contours$lek.songtype.year == x, "meandom_cont"]
        if (length(y) > 0)
            return(y) else return(NA)
    })

sp_feat_lbh_all$dfrange <- sp_feat_lbh_all$maxdom - sp_feat_lbh_all$mindom

sp_feat_lbh_all$contour.sim <- sapply(sp_feat_lbh_all$song.type.year,
    function(x) {
        y <- contours[contours$lek.songtype.year == x, "contour.sim"]
        if (length(y) > 0)
            return(y) else return(NA)
    })

saveRDS(sp_feat_lbh_all, "./data/processed/spectro_features_ALL_Song selections aug 2019 SAME.FREQ.RANGE AND METADATA.RDS")


cap_data <- readxl::read_excel("/home/m/Dropbox/LBH data/Additional data files/LBH captures data.xlsx")

cap_data <- cap_data[cap_data$`Bird ID` %in% est_lbh$bird.id, ]

# get average morphology per bird
mean_cap_data <- aggregate(cbind(Weight, `Flattened wing length`,
    `Central rectriz`, `Exposed culmen`, `Bill base height`, `Bill base width`) ~
    `Bird ID`, cap_data, mean)

sp_feat_lbh <- sp_feat_lbh_all[sp_feat_lbh_all$bird.id %in% mean_cap_data$`Bird ID`,
    ]

saveRDS(sp_feat_lbh, "./data/processed/spectro_features_Song selections aug 2019 SAME.FREQ.RANGE AND METADATA.RDS")

9 Repeatability

Code
sp_feat_lbh <- readRDS("./data/processed/spectro_features_Song selections aug 2019 SAME.FREQ.RANGE AND METADATA.RDS")

cap_data <- readxl::read_excel("/home/m/Dropbox/LBH data/Additional data files/LBH captures data.xlsx")

# cap_data <- cap_data[cap_data$`Bird ID` %in%
# sp_feat_lbh$bird.id, ]

morph_features <- c("Weight", "Flattened wing length", "Unflattened wing length",
    "Central rectriz", "Exposed culmen", "Total culmen", "Bill base height",
    "Bill base width", "Mean tarsus length")

cap_data$bird.id.year <- paste(cap_data$`Bird ID`, substr(cap_data$Day,
    0, 4), sep = "-")
Code
rep_list <- lapply(morph_features, function(x) {
    dat <- cap_data[cap_data$`Bird ID` != 0, c(x, "bird.id.year")]

    # keep only those repeated
    tab <- table(dat$bird.id.year)

    dat <- dat[dat$bird.id.year %in% names(tab)[tab > 1], ]
    names(dat) <- c("trait", "bird.id.year")

    # dat$trait <- scale(dat$trait)
    repitab <- rpt(trait ~ 1 + (1 | bird.id.year), grname = "bird.id.year",
        data = dat, datatype = "Gaussian", npermut = 0, nboot = 100,
        ncores = 10, parallel = TRUE)

    out <- data.frame(feature = x, rep = repitab$R[[1]], low_ci = repitab$CI_emp[[1]],
        hi_ci = repitab$CI_emp[[2]], n.id = length(unique(dat$bird.id.year)))

    return(out)
})

rep_morph <- do.call(rbind, rep_list)

# order factor levels by repeatability
rep_morph$feature <- factor(rep_morph$feature, levels = rep_morph$feature[order(-rep_morph$rep)])

saveRDS(rep_morph, "./data/processed/repeatability_morph_features.RDS")
Code
rep_morph <- readRDS("./data/processed/repeatability_morph_features.RDS")

# ggplot2 graph with morphological feature reaptability with 95%
# CI
ggplot(rep_morph, aes(x = feature, y = rep, ymin = low_ci, ymax = hi_ci)) +
    geom_point(size = 3, col = cols_corr[3]) + geom_errorbar(width = 0.05,
    col = cols_corr[3]) + theme_classic(base_size = 18) + theme(axis.text.x = element_text(angle = 45,
    vjust = 1, hjust = 1)) + geom_hline(yintercept = 0.4, linetype = "dashed") +
    labs(x = "Morphological feature", y = "Repeatability")

Code
sel_morph_features <- c("Weight", "Flattened wing length", "Central rectriz",
    "Total culmen", "Bill base height", "Bill base width")
Code
# get average morphology per bird
mean_cap_data <- aggregate(cbind(Weight, `Flattened wing length`,
    `Central rectriz`, `Total culmen`, `Bill base height`, `Bill base width`) ~
    `Bird ID`, cap_data[cap_data$`Bird ID` != 0, ], mean)

cormat <- cor(mean_cap_data[, sel_morph_features], use = "pairwise.complete.obs")

corrplot.mixed(cormat, tl.cex = 0.7, upper.col = cols_corr, lower.col = cols_corr,
    order = "hclust", lower = "number", upper = "ellipse", tl.col = "black")

10 PCA morphology

Code
pca <- prcomp(mean_cap_data[, sel_morph_features], scale. = TRUE)

# plot rotation values by PC
pca_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$variable <- factor(pca_rot_stck$variable, levels = c("Weight",
    "Flattened wing length", "Central rectriz", "Total culmen", "Bill base height",
    "Bill base width"))

ggplot(pca_rot_stck, aes(x = variable, y = rotation, fill = Sign)) +
    geom_col() + coord_flip() + scale_fill_viridis_d(alpha = 0.7,
    begin = 0.2, end = 0.8) + facet_wrap(~ind_var) + theme_classic()

Code
# add to data
mean_cap_data$pc1.size <- pca$x[, 1]

cap_data$pc1.size <- predict(pca, cap_data[, sel_morph_features])[,
    1]

11 Check feature correlations

Code
# read spectral features
spec_features <- readRDS("./data/processed/spectro_features_all_lek_songtype_year_high_snr.RDS")

# correlation matrix across features

features <- c("meandom", "mindom", "maxdom", "dfrange", "sp.ent",
    "modindx", "duration", "meanpeakf", "skew", "kurt", "time.ent",
    "time.IQR", "peakt", "contour.sim")

cormat <- cor(spec_features[, features], use = "pairwise.complete.obs")

highly_correlated <- findCorrelation(cormat, cutoff = 0.9)

features[highly_correlated]
[1] "duration" "kurt"    
Code
cols_corr <- colorRampPalette(c(viridis(3, direction = 1, begin = 0.2,
    end = 0.5), "#BEBEBE1A", "white", "#BEBEBE1A", viridis(3, direction = 1,
    begin = 0.7, end = 0.9)))(30)

corrplot.mixed(cormat, tl.cex = 0.7, upper.col = cols_corr, lower.col = cols_corr,
    order = "hclust", lower = "number", upper = "ellipse", tl.col = "black")

11.1 Removing colinear features

Remove meandom as it represents (and is highly correlated to) peakf

Get residuals for time.ent, skew, and time.IQR vs duration to remove correlation with duration

Code
# fix time entropy
mod_resid_time.ent <- lm(time.ent ~ duration, data = spec_features)
spec_features$time.ent <- resid(mod_resid_time.ent)

mod_resid_skew <- lm(skew ~ kurt, data = spec_features)
spec_features$skew <- resid(mod_resid_skew)
#
mod_resid_timeIQR <- lm(time.IQR ~ duration, data = spec_features)
spec_features$time.IQR <- resid(mod_resid_timeIQR)

cormat <- cor(spec_features[, features], use = "pairwise.complete.obs")
highly_correlated <- findCorrelation(cormat, cutoff = 0.9)

features[highly_correlated]
character(0)
Code
# rewrite selected features features <- c('mindom', 'maxdom',
# 'dfrange', 'sp.ent', 'modindx', 'duration', 'meanpeakf',
# 'skew', 'kurt', 'time.ent', 'time.IQR', 'peakt')

cormat <- cor(spec_features[, features], use = "pairwise.complete.obs")

corrplot.mixed(cormat, tl.cex = 0.7, upper.col = cols_corr, lower.col = cols_corr,
    order = "hclust", lower = "number", upper = "ellipse", tl.col = "black")

11.2 Set which features are restrited by physiology/anatomy

Code
restriction <- c(1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0)
features_df <- data.frame(features, restriction)

features_df$restriction <- factor(restriction, levels = c(0, 1), labels = c("unrestricted",
    "restricted"))

features_df
features restriction
meandom restricted
mindom restricted
maxdom restricted
dfrange restricted
sp.ent restricted
modindx unrestricted
duration restricted
meanpeakf restricted
skew unrestricted
kurt unrestricted
time.ent unrestricted
time.IQR unrestricted
peakt unrestricted
contour.sim unrestricted

12 PCA acoustic features

Code
pca.song <- prcomp(sp_feat_lbh[complete.cases(sp_feat_lbh), features],
    scale. = TRUE)

# plot rotation values by PC
pca_rot <- as.data.frame(pca.song$rotation[, 1:4])
pca_var <- round(summary(pca.song)$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$variable <- factor(pca_rot_stck$variable, levels = features)

ggplot(pca_rot_stck, aes(x = variable, y = rotation, fill = Sign)) +
    geom_col() + coord_flip() + scale_fill_viridis_d(alpha = 0.7,
    begin = 0.2, end = 0.8) + facet_wrap(~ind_var) + theme_classic()

Code
# add to data
sp_feat_lbh$pc1.song <- predict(pca.song, sp_feat_lbh)[, 1]
features <- c(features, "pc1.song")

13 Check covaration between morphological and acoustic structure

Code
sp_feat_lbh$bird.id.year <- paste(sp_feat_lbh$bird.id, sp_feat_lbh$year,
    sep = "-")

morph_acoust_dat <- merge(cap_data[, c("bird.id.year", sel_morph_features,
    "pc1.size")], sp_feat_lbh[sp_feat_lbh$SNR > 1, c("bird.id.year",
    features)], by = "bird.id.year")

# correlation matrix for each acoustic feature and morphology
cors <- lapply(features, function(x) {
    cors_list <- lapply(c(sel_morph_features, "pc1.size"), function(y) {
        ct <- cor.test(morph_acoust_dat[, x], morph_acoust_dat[, y],
            use = "pairwise.complete.obs")

        out <- data.frame(x, y, cor = ct$estimate[[1]], p = ct$p.value)
        # cor(morph_acoust_dat[, c(x, sel_morph_features,
        # 'pc1.size')], use = 'pairwise.complete.obs')[-1, 1]
        return(out)
    })
    do.call(rbind, cors_list)
})

cors <- do.call(rbind, cors)
cors$signif <- ifelse(cors$p < 0.05, "p < 0.05", "p >= 0.05")


agg_cor <- aggregate(cor ~ x, cors, mean)

# order levels by correlation
cors$x <- factor(cors$x, levels = agg_cor$x[order(agg_cor$cor)])

feat_color <- features_df$restriction[match(levels(cors$x), features_df$features)]


feat_color <- ifelse(feat_color == "restricted", "gray40", "black")
feat_color[is.na(feat_color)] <- "purple4"

ggplot(cors, aes(x = x, y = y, fill = cor)) + geom_tile() + coord_equal() +
    scale_fill_gradient2(low = viridis(10)[3], high = viridis(10)[7],
        guide = "none") + geom_text(aes(label = round(cor, 3), color = signif),
    size = 3) + scale_color_manual(values = c("black", "gray")) +
    labs(x = "", y = "", color = "P value") + theme_classic(base_size = 10) +
    theme(axis.text.x = element_text(size = 11, angle = 30, vjust = 0.8,
        hjust = 0.8, color = feat_color))

Code
ggplot(cors, aes(x = x, y = y, fill = cor)) + geom_tile() + coord_equal() +
    scale_fill_gradient2(low = viridis(10)[3], high = viridis(10)[7],
        guide = "none") + geom_text(aes(label = round(cor, 3), color = signif),
    size = 3) + scale_color_manual(values = c("black", "gray")) +
    labs(x = "", y = "", color = "P value") + theme_classic(base_size = 10) +
    theme(axis.text.x = element_text(size = 11, angle = 30, vjust = 0.8,
        hjust = 0.8, color = feat_color))

Code
# scatterplot pc1 size vs pc1 song
ggplot(morph_acoust_dat, aes(x = pc1.size, y = pc1.song)) + geom_point(color = "gray49",
    alpha = 0.5) + geom_smooth(method = "lm", color = "orange", se = FALSE) +
    theme_classic() + labs(x = "Body size (PC1: 32%)", y = "Acoustic structure (PC1: 28%)")

14 Partition acoustic structure variance

For PC1:

Code
sp_feat_lbh_all <- readRDS("./data/processed/spectro_features_ALL_Song selections aug 2019 SAME.FREQ.RANGE AND METADATA.RDS")

sp_feat_lbh_all$pc1.song <- predict(pca.song, sp_feat_lbh_all)[, 1]
sub_sp_lbh <- sp_feat_lbh_all[sp_feat_lbh_all$SNR > 1, ]

# remove single lek sites
sub_sp_lbh <- sub_sp_lbh[!sub_sp_lbh$lek %in% c("DAR", "VER", "SIR",
    "TIS"), ]

sub_sp_lbh$site <- "la selva"
sub_sp_lbh$site[sub_sp_lbh$lek %in% c("HC1", "HC2")] <- "hitoy cerere"
sub_sp_lbh$site[sub_sp_lbh$lek %in% c("BR1", "BR2")] <- "las brisas"
sub_sp_lbh$site[sub_sp_lbh$lek %in% c("TR1", "TR2")] <- "tirimbina"

sub_sp_lbh$lek.year <- paste(sub_sp_lbh$lek, sub_sp_lbh$year, sep = "-")

agg <- aggregate(lek.song.type ~ lek.year, sub_sp_lbh, function(x) length(unique(x)))

sub_sp_lbh$lek.year <- paste(sub_sp_lbh$lek, sub_sp_lbh$year, sep = "-")

# X <- sub_sp_lbh[!sub_sp_lbh$lek.year %in%
# agg$lek.year[agg$lek.song.type == 1], ]

model_site <- lmer(pc1.song ~ (1 | site/lek/lek.song.type) + (1 |
    year), data = sub_sp_lbh)

part_var <- function(model) {
    var_components <- as.data.frame(VarCorr(model))
    total_variance <- sum(var_components$vcov)
    var_components$proportion <- var_components$vcov/total_variance

    return(var_components)
}

var_components_site <- part_var(model_site)

var_components_site$level <- factor(c("Song type", "Year", "Lek",
    "Site", "Residual"), levels = c("Song type", "Lek", "Site", "Year",
    "Residual"))

# sample sizes
var_components_site$n <- sapply(c("lek.song.type", "year", "lek",
    "site", "Residual"), function(x) length(unique(sub_sp_lbh[, names(sub_sp_lbh) ==
    x])))


var_components_site$prop <- as.character(round(var_components_site$proportion,
    2))

# var_components_site$prop[var_components_site$level == 'Site']
# <- '< 0.01'

var_components_site$prop <- paste(var_components_site$prop, " (n = ",
    var_components_site$n, ")", sep = "")

var_components_site$proportion[var_components_site$proportion < 0.005] <- 0.005

# barplot of variance by factor
ggplot(var_components_site[var_components_site$level != "Residual",
    ], aes(x = level, y = proportion, fill = grp)) + geom_bar(stat = "identity") +
    theme_classic(base_size = 14) + labs(x = "Level", y = "Explained variance",
    fill = "Factor") + theme(axis.text.x = element_text(angle = 45,
    vjust = 1, hjust = 1)) + geom_text(aes(label = prop), vjust = -0.5,
    size = 3) + scale_fill_viridis_d(option = "G", begin = 0.1, end = 0.9,
    guide = "none")

Code
sub_sp_lbh <- sub_sp_lbh[sub_sp_lbh$bird.id != "0", ]


model_indiv <- lmer(pc1.song ~ (1 | lek/lek.song.type/bird.id) + (1 |
    year), data = sub_sp_lbh)

var_components_indiv <- part_var(model_indiv)

var_components_indiv$level <- factor(c("Individual", "Song type",
    "Lek", "Year", "Residual"), levels = c("Individual", "Song type",
    "Lek", "Year", "Residual"))

# sample sizes
var_components_indiv$n <- sapply(c("bird.id", "lek.song.type", "lek",
    "year", "Residual"), function(x) length(unique(sub_sp_lbh[, names(sub_sp_lbh) ==
    x])))


var_components_indiv$prop <- as.character(round(var_components_indiv$proportion,
    2))

var_components_indiv$prop[var_components_indiv$prop == "0"] <- "< 0.01"

var_components_indiv$prop <- paste(var_components_indiv$prop, " (n = ",
    var_components_indiv$n, ")", sep = "")

# barplot of variance by factor
ggplot(var_components_indiv[var_components_indiv$level != "Residual",
    ], aes(x = level, y = proportion, fill = grp)) + geom_bar(stat = "identity") +
    theme_classic(base_size = 14) + labs(x = "Level", y = "Explained variance",
    fill = "Factor") + theme(axis.text.x = element_text(angle = 45,
    vjust = 1, hjust = 1)) + geom_text(aes(label = prop), vjust = -0.5,
    size = 3) + scale_fill_viridis_d(option = "G", begin = 0.1, end = 0.9,
    guide = "none")

Code
var_components <- rbind(var_components_indiv, var_components_site[var_components_site$grp ==
    "site", ])

var_components$level <- factor(c("Individual", "Song type", "Lek",
    "Year", "Residual", "Site"), levels = c("Individual", "Song type",
    "Lek", "Year", "Residual", "Site"))

var_components$proportion <- var_components$proportion/sum(var_components$proportion)

var_components$prop <- paste(round(var_components$proportion, 2),
    " (n = ", var_components$n, ")", sep = "")

ggplot(var_components[var_components$level != "Residual", ], aes(x = level,
    y = proportion, fill = grp)) + geom_bar(stat = "identity") + theme_classic(base_size = 14) +
    labs(x = "Level", y = "Explained variance", fill = "Factor") +
    theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) +
    geom_text(aes(label = prop), vjust = -0.5, size = 3) + scale_fill_viridis_d(option = "G",
    begin = 0.1, end = 0.9, guide = "none")

For all acoustic features:

Code
var_part_list <- lapply(features, function(x) {
    form <- paste(x, " ~ (1 | lek / lek.song.type / bird.id) + (1 | year)")
    model <- lmer(as.formula(form), data = sub_sp_lbh)
    var_components <- part_var(model)
    out <- data.frame(feature = x, level = c("Individual", "Song type",
        "Lek", "Year", "Residual"), prop = var_components$proportion)
    return(out)
})

var_part_feat <- do.call(rbind, var_part_list)

var_part_feat$level <- factor(var_part_feat$level, levels = c("Individual",
    "Song type", "Lek", "Year", "Residual"))


ggplot(var_part_feat[var_part_feat$level != "Residual", ], aes(x = feature,
    y = level, fill = prop)) + geom_tile() + coord_equal() + scale_fill_gradient2(low = viridis(10)[3],
    high = viridis(10)[7], guide = "none") + geom_text(aes(label = round(prop,
    2)), size = 3) + labs(x = "", y = "") + theme_classic(base_size = 10) +
    theme(axis.text.x = element_text(size = 11, angle = 30, vjust = 0.8,
        hjust = 0.8))

15 Phylogenetic analyses

15.1 Phylogenetic signal

Frequentist:

Code
spec_features$pc1.song <- predict(pca.song, spec_features)[, 1]
spec_features <- spec_features[, c("sound.files", "selec", "song.type.year",
    "org.sound.files", "SNR", features)]


mean_spec_features <- aggregate(x = . ~ song.type.year, spec_features[,
    c("song.type.year", features)], mean)

# add standard error
se_spec_features <- aggregate(x = . ~ song.type.year, spec_features[,
    c("song.type.year", features)], function(x) sd(x)/sqrt(length(x)))

# fill with NAs
for (i in features) se_spec_features[is.na(se_spec_features[, i]),
    i] <- mean(se_spec_features[, i], na.rm = TRUE)


mean_spec_features_matrix <- as.matrix(mean_spec_features[, 2:ncol(mean_spec_features)])

rownames(mean_spec_features_matrix) <- mean_spec_features$song.type.year
Code
out <- lapply(features, function(x) {

    trait <- mean_spec_features[, x]
    names(trait) <- mean_spec_features$song.type.year

    # prune tree and data
    matched <- match_data_tree(mean_spec_features[, x], tree, index = mean_spec_features$song.type.year)

    ps <- phylosig(tree = matched$tree, x = scale(matched$data), method = "lambda",
        test = TRUE)

    out_df <- data.frame(feature = x, as.data.frame(ps[1:4]))

    return(out_df)
})

phylosig_df <- do.call(rbind, out)

saveRDS(phylosig_df, "./data/processed/phylogenetic_signal_by_feature_consensus_tree.RDS")
Code
# read data
phylosig_df <- readRDS("./data/processed/phylogenetic_signal_by_feature_consensus_tree.RDS")

phylosig_df
feature lambda logL logL0 P
meandom 0.8670102 -127.1958 -179.4439 0.0000000
mindom 0.3355681 -196.3323 -197.9173 0.0750054
maxdom 0.7515800 -162.7333 -187.4023 0.0000000
dfrange 0.3842097 -173.4693 -185.0820 0.0000014
sp.ent 0.8453137 -157.2099 -184.3594 0.0000000
modindx 0.7362833 -174.1197 -197.5493 0.0000000
duration 0.8042682 -151.4915 -192.3074 0.0000000
meanpeakf 0.6889964 -179.9816 -204.6171 0.0000000
skew 0.8072986 -186.9270 -195.0952 0.0000530
kurt 0.9177309 -200.2520 -214.1811 0.0000001
time.ent 0.8068974 -165.3402 -189.6666 0.0000000
time.IQR 0.1891207 -181.9732 -185.0312 0.0133962
peakt 0.4827029 -184.8977 -189.6817 0.0019801
contour.sim 0.7242524 -135.0508 -180.7365 0.0000000
pc1.song 0.7595308 -133.9647 -179.5253 0.0000000
Code
ggplot(phylosig_df, aes(x = feature, y = lambda)) + geom_point(size = 4) +
    # facet_wrap(~ feature, ncol = 4, scales = 'free_y') +
    # scale_color_viridis_d(option = 'G', end = 0.8, begin =
    # 0.2, alpha = 0.7) +
theme_classic(base_size = 14) + theme(axis.text.x = element_text(angle = 90,
    vjust = 0.5, hjust = 1)) + labs(x = "Evolutionary model", y = "Phylogenetic signal (lambda)",
    fill = "Evolutionary\nmodel")

Bayesian (not used):

Code
features <- c("meandom", "mindom", "maxdom", "dfrange", "sp.ent",
    "modindx", "duration", "skew", "kurt", "time.ent", "time.IQR",
    "meanpeakf")

spec_features <- spec_features[, c("sound.files", "selec", "song.type.year",
    "org.sound.files", "SNR", features)]


out <- pblapply(features, cl = length(features), function(x) {

    tree <- trees[[1]]

    # prune tree and data
    trait <- mean_spec_features[mean_spec_features$song.type.year %in%
        tree$tip.label, c(x, "song.type.year")]
    tree <- drop.tip(tree, tip = tree$tip.label[!tree$tip.label %in%
        trait$song.type.year])
    names(trait) <- c(x, "phylo")

    A <- ape::vcv.phylo(tree)

    form <- paste(x, " ~ 1 + (1|gr(phylo, cov = A))")

    mod <- brm(formula = form, data = trait, family = gaussian(),
        data2 = list(A = A), backend = "cmdstanr", sample_prior = TRUE,
        chains = 1, iter = 1000)

    ps <- phylo_sig_brms(mod)

    out_df <- data.frame(feature = x, as.data.frame(ps))

    return(out_df)
})


phylosig_brms_df <- do.call(rbind, out)

phylosig_brms_df[order(phylosig_brms_df$Estimate), ]

15.2 Phenograms by acoustic feature

Code
sub_features <- c(Modulation = "modindx", `Max dominant` = "maxdom",
    `Frequency range` = "dfrange", `Mean frequency` = "meandom", `Time IQR` = "time.IQR",
    Kurtosis = "kurt")


cols <- viridis(10)[c(3, 7)]

par(mfcol = c(3, 2))

for (i in seq_along(sub_features)) {

    feat <- mean_spec_features[, sub_features[i]]

    feat <- scale(feat)

    names(feat) <- mean_spec_features$song.type.year

    phenogram(tree, x = feat, spread.labels = F, offset = 0, ftype = "off",
        colors = ifelse(i < 4, cols[1], cols[2]), ylim = c(-2.4, 2.4))

    title(names(sub_features)[i], cex.main = 2)
}

15.3 Multivariate phylogenetic signal

Code
# prune tree and data
matched <- match_data_tree(mean_spec_features_matrix, tree, index = mean_spec_features$song.type.year)

signal <- mvgls(Y ~ 1, data = list(Y = matched$data), matched$tree,
    model = "lambda", penalty = "RidgeArch")

summary(signal)

Call:
mvgls(formula = Y ~ 1, data = list(Y = matched$data), tree = matched$tree, 
    model = "lambda", penalty = "RidgeArch")


Generalized least squares fit by penalized REML 
        GIC   logLik
  -2438.203 1350.432

Parameter estimate(s):
lambda: 0.7416 

Regularization parameter (gamma): 0 


Covariance matrix of size: 15 by 15 
for 130 observations 

Coefficients (truncated):
            meandom mindom maxdom dfrange sp.ent modindx duration meanpeakf
(Intercept)   4.309  2.282  7.157   4.875 0.9157   12.27    0.147     5.704
               skew  kurt
(Intercept) -0.1068 14.85
Use "coef" to display all the coefficients

15.4 Modes of evolution

15.4.1 Single traits

Code
models <- c(brownian_motion = "BM", Ornstein_Uhlenbeck = "OU", early_burst = "EB",
    rate_trend = "rate_trend", puntctuational = "kappa", time_dependent = "delta",
    mean_trend = "mean_trend", white = "white")

out <- pblapply(features, cl = 10, function(x) {

    # prune tree and data
    matched <- match_data_tree(mean_spec_features[, x], tree, index = mean_spec_features$song.type.year)

    mods <- lapply(models, function(y) {
        fc <- fitContinuous(phy = matched$tree, dat = scale(matched$data),
            model = y)

        out <- data.frame(feature = x, model = y, AICc = fc$opt$aicc,
            sigma2 = fc$opt$sigsq, alpha = ifelse(length(fc$opt$alpha) >
                0, fc$opt$alpha, NA))

        return(out)
    })

    out_df <- do.call(rbind, mods)

    out_df$delta_AIC <- out_df$AICc - min(out_df$AICc)

    return(out_df)
})

evo_mods <- do.call(rbind, out)

rownames(evo_mods) <- seq_len(nrow(evo_mods))

write.csv(evo_mods, "./data/processed/evolutionary_models_by_feature_consensus_tree.csv",
    row.names = FALSE)
Code
evo_mods <- read.csv("./data/processed/evolutionary_models_by_feature_consensus_tree.csv")

restricted <- c("meandom", "mindom", "maxdom", "dfrange", "sp.ent",
    "meanpeakf", "duration")

evo_mods$restriction <- ifelse(evo_mods$feature %in% restricted, "restricted",
    "unrestricted")

# order features by restriction (first restricted second
# unrestricted)
evo_mods$feature <- factor(evo_mods$feature, levels = c(restricted,
    setdiff(unique(evo_mods$feature), restricted)))


# flip y axis
ggplot()

Code
## ggplot showing the delta AICc by model and feature with
## facets get the mean with apoint and the range + - the sd
ggplot(evo_mods, aes(x = model, y = delta_AIC, color = restriction)) +
    # geom_errorbar(aes(ymin=neg_delta-sd, ymax=neg_delta+sd),
    # width=.3, linewidth = 2) +
geom_point(size = 4) + facet_wrap(~feature, scales = "free_y") + scale_color_viridis_d(option = "G",
    end = 0.8, begin = 0.2, alpha = 0.6, guide = "none") + theme_classic(base_size = 14) +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) +
    labs(x = "Evolutionary model", y = "Delta AICc", fill = "Evolutionary\nmodel") +
    scale_y_reverse()

Code
sub_evo_mods <- evo_mods[evo_mods$feature %in% c("duration", "dfrange",
    "sp.ent", "meanpeakf", "modindx", "skew", "kurt", "time.ent"),
    ]
sub_evo_mods <- sub_evo_mods[sub_evo_mods$model != "kappa", ]

ggplot(sub_evo_mods, aes(x = model, y = delta_AIC, color = restriction)) +
    geom_point(size = 4) + facet_wrap(~feature, ncol = 4, scales = "free_y") +
    scale_color_viridis_d(option = "G", end = 0.8, begin = 0.2, alpha = 0.7) +
    theme_classic(base_size = 14) + theme(axis.text.x = element_text(angle = 90,
    vjust = 0.5, hjust = 1)) + labs(x = "Evolutionary model", y = "Delta AICc",
    fill = "Evolutionary\nmodel", color = "") + scale_y_reverse()

Code
ggplot(sub_evo_mods, aes(x = model, y = delta_AIC, fill = feature,
    color = feature, shape = restriction)) + geom_jitter(size = 2,
    width = 0.1) + scale_shape_manual(values = c(21, 22)) + scale_fill_viridis_d(option = "G",
    end = 0.8, begin = 0.2, alpha = 0.7) + scale_color_viridis_d(option = "G",
    end = 0.8, begin = 0.2, alpha = 0.7) + theme_classic(base_size = 20) +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) +
    labs(x = "Evolutionary model", y = "Delta AICc", fill = "", color = "",
        shape = "") + scale_y_reverse()

Code
ggplot(sub_evo_mods[sub_evo_mods$model %in% c("BM", "delta", "OU",
    "rate_trend"), ], aes(x = model, y = delta_AIC, fill = feature,
    color = feature, shape = restriction)) + geom_jitter(size = 2,
    width = 0.1) + scale_shape_manual(values = c(21, 22)) + scale_fill_viridis_d(option = "G",
    end = 0.8, begin = 0.2, alpha = 0.7) + scale_color_viridis_d(option = "G",
    end = 0.8, begin = 0.2, alpha = 0.7) + theme_classic(base_size = 20) +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) +
    labs(x = "Evolutionary model", y = "Delta AICc", fill = "", color = "",
        shape = "") + scale_y_reverse()

Code
ggplot(sub_evo_mods[sub_evo_mods$model %in% c("BM", "delta", "OU",
    "rate_trend"), ], aes(x = model, y = delta_AIC, fill = feature,
    color = feature)) + geom_jitter(size = 2, width = 0.1) + scale_fill_viridis_d(option = "G",
    end = 0.8, begin = 0.2, alpha = 0.7) + scale_color_viridis_d(option = "G",
    end = 0.8, begin = 0.2, alpha = 0.7) + theme_classic(base_size = 20) +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) +
    labs(x = "Evolutionary model", y = "Delta AICc", fill = "", color = "") +
    scale_y_reverse()

Code
### Multivariate trait ev

matched <- match_data_tree(mean_spec_features[, features[features !=
    "pc1.song"]], tree, index = mean_spec_features$song.type.year)

combs <- combn(names(matched$data), 2)

comp_rates_list <- lapply(seq_len(ncol(combs)), function(i) {
    # comp_rates_list <- lapply(c(1:3, 14), function(i) {

    W <- (matched$data[, combs[, i]])
    x <- lapply(W, function(x) log(x + min(abs(x)) + 1))
    x <- do.call(cbind, x)
    rownames(x) <- rownames(W)

    print(i)
    cr <- try(CompareRates.multTrait(phy = matched$tree, x = x), silent = TRUE)

    if (!is(cr, "try-error")) {
        out <- data.frame(feat1 = combs[1, i], feat2 = combs[2, i],
            rate1 = cr$Robs[1, 1], rate2 = cr$Robs[2, 2], p = cr$Prob)
    } else {
        out <- data.frame(feat1 = combs[1, i], feat2 = combs[2, i],
            rate1 = NA, rate2 = NA, p = NA)
    }

    return(out)
})

comp_rates <- do.call(rbind, comp_rates_list)


cr <- CompareRates.multTrait(phy = matched$tree, x = as.matrix(matched$data)[,
    ])



mvBM(tree, data)

mv_ou1 <- mvOU(tree = matched$tree, data = as.matrix(matched$data),
    model = "OU1")
mv_oum <- mvOU(tree = matched$tree, data = as.matrix(matched$data),
    model = "OUM")
mv_bm <- mvBM(tree = matched$tree, data = as.matrix(matched$data),
    model = "OUM")


ou_tree <- transf.branch.lengths(phy = matched$tree, model = "OUrandomRoot",
    parameters = list(alpha = 0.2073415))$tree


# Simulated dataset
set.seed(14)
# Generating a random tree
tree2 <- pbtree(n = 50)

# Setting the regime states of tip species
sta <- as.vector(c(rep("Forest", 20), rep("Savannah", 30)))
names(sta) <- tree$tip.label

# Making the simmap tree with mapped states
tree3 <- make.simmap(tree2, sta, model = "ER", nsim = 1)
# col<-c('blue','orange'); names(col)<-c('Forest','Savannah')

# Plot of the phylogeny for illustration
# plotSimmap(tree,col,fsize=0.6,node.numbers=FALSE,lwd=3,
# pts=FALSE)

# Simulate the traits
alpha <- matrix(c(2, 0.5, 0.5, 1), 2)
sigma <- matrix(c(0.1, 0.05, 0.05, 0.1), 2)
theta <- c(2, 3, 1, 1.3)
data <- mvSIM(tree3, param = list(sigma = sigma, alpha = alpha, ntraits = 2,
    theta = theta, names_traits = c("head.size", "mouth.size")), model = "OUM",
    nsim = 1)

eem_mv <- estimate.evolutionary.model(phyltree = tree2, mData = data)

a <- OUwie(tree2, data, model = "OU1")


tre2 = transf.branch.lengths(phy = tree2, model = "OUrandomRoot",
    parameters = list(alpha = 0.2073415))
cr <- CompareRates.multTrait(phy = tree2, x = data)
cr2 <- CompareRates.multTrait(phy = tre2$tree, x = data)

library(phytools)
alpha <- 0.5
tree_ou <- ouTree(tree, alpha)
plot(tree_ou)


eem_mv <- estimate.evolutionary.model(phyltree = matched$tree, mData = as.matrix(matched$data[,
    1:2]))

16 Evolutionary rate from fitContinuous

Code
# get sigma2 for the lowest delta AIC for each model
sigma2 <- evo_mods[evo_mods$delta_AIC == 0, ]

ggplot(sigma2, aes(x = feature, y = sigma2, color = restriction)) +
    # geom_errorbar(aes(ymin=neg_delta-sd, ymax=neg_delta+sd),
    # width=.4, linewidth = 2) +
geom_point(size = 4) + # facet_wrap(~ feature, ncol = 4, scales = 'free_y') + geom_point(size
geom_point(size = 4) + # facet_wrap(~ feature, ncol = 4, scales = 'free_y') + =
geom_point(size = 4) + # facet_wrap(~ feature, ncol = 4, scales = 'free_y') + 4)
geom_point(size = 4) + # facet_wrap(~ feature, ncol = 4, scales = 'free_y') + +
geom_point(size = 4) + # facet_wrap(~ feature, ncol = 4, scales = 'free_y') + #
geom_point(size = 4) + # facet_wrap(~ feature, ncol = 4, scales = 'free_y') + facet_wrap(~
geom_point(size = 4) + # facet_wrap(~ feature, ncol = 4, scales = 'free_y') + feature,
geom_point(size = 4) + # facet_wrap(~ feature, ncol = 4, scales = 'free_y') + ncol
geom_point(size = 4) + # facet_wrap(~ feature, ncol = 4, scales = 'free_y') + =
geom_point(size = 4) + # facet_wrap(~ feature, ncol = 4, scales = 'free_y') + 4,
geom_point(size = 4) + # facet_wrap(~ feature, ncol = 4, scales = 'free_y') + scales
geom_point(size = 4) + # facet_wrap(~ feature, ncol = 4, scales = 'free_y') + =
geom_point(size = 4) + # facet_wrap(~ feature, ncol = 4, scales = 'free_y') + 'free_y')
geom_point(size = 4) + # facet_wrap(~ feature, ncol = 4, scales = 'free_y') + +
scale_color_viridis_d(option = "G", end = 0.8, begin = 0.2, alpha = 0.7) +
    theme_classic(base_size = 20) + theme(axis.text.x = element_text(angle = 90,
    vjust = 0.5, hjust = 1)) + labs(x = "Evolutionary model", y = "Evolutionary rate\n(sigma^2)",
    color = "")

Code
sigma2$cor.morph <- sapply(as.character(sigma2$feature), function(z) cors$cor[cors$x ==
    z & cors$y == "pc1.size"])

# scatter plot of cor.morph vs sigma2
ggplot(sigma2, aes(x = abs(cor.morph), y = sigma2, color = restriction)) +
    geom_point(size = 4) + scale_color_viridis_d(option = "G", end = 0.8,
    begin = 0.2, alpha = 0.7) + theme_classic(base_size = 20) + theme(axis.text.x = element_text(angle = 90,
    vjust = 0.5, hjust = 1)) + labs(x = "Absoute correlation\nbody size (PC1)",
    y = "Evolutionary rate\n(sigma^2)", color = "")

16.1 Coevolution

Code
path <- "./data/processed/posterior_ratematrix_consensus"

matched <- match_data_tree(mean_spec_features_matrix, tree, index = rownames(mean_spec_features_matrix))

## Run MCMC chains.
handle_1 <- ratematrixMCMC(data = matched$data, phy = matched$tree,
    gen = 30000, dir = path)
handle_2 <- ratematrixMCMC(data = matched$data, phy = matched$tree,
    gen = 30000, dir = path)
handle_3 <- ratematrixMCMC(data = matched$data, phy = matched$tree,
    gen = 30000, dir = path)

# read handles
posteriors <- pblapply(list(handle_1, handle_2, handle_3), cl = 1,
    function(x) {
        readMCMC(x, burn = 0.2, thin = 1)
    })

saveRDS(posteriors, "./data/processed/posteriors_ratematrix_consensus.RDS")
Code
# read posteriors
posteriors <- readRDS("./data/processed/posteriors_ratematrix_consensus.RDS")
merged_posteriors <- mergePosterior(posteriors)


mean_cov <- merged_posteriors$matrix[[1]]
for (i in 2:length(merged_posteriors$matrix)) mean_cov <- mean_cov +
    merged_posteriors$matrix[[i]]

mean_cov <- mean_cov/length(merged_posteriors$matrix)

# range(mean_cov)

mean_corr <- cov2cor(mean_cov)
colnames(mean_corr) <- names(posteriors[[1]]$root)


cp <- corrplot.mixed(mean_corr, tl.cex = 0.7, upper.col = cols_corr,
    lower.col = cols_corr, order = "hclust", lower = "number", upper = "ellipse",
    tl.col = "black")

16.2 Compare evolutionary rates of acoustic features

Code
# get confidence intervals
feat_grp <- features_df$restriction
names(feat_grp) <- features_df$features

matched <- match_data_tree(mean_spec_features_matrix[, colnames(mean_spec_features_matrix) !=
    "pc1.song"], tree, index = rownames(mean_spec_features_matrix))

jck_EMR <- compare.multi.evol.rates(A = scale(matched$data), gp = feat_grp,
    Subset = TRUE, phy = matched$tree, iter = 10000, print.progress = FALSE)

jck_EMR

Call:


Observed Rate Ratio: 1.19565

P-value: 0.0101

Effect Size: 2.48711

Based on 10001 random permutations

The rate for group unrestricted is 0.186899209203047  

The rate for group restricted is 0.156315560419451 
Code
# evo_rates <- jck_EMR names(evo_rates) <- c('Unrestricted',
# 'Restricted') evo_rates <- stack(evo_rates)
# write.csv(evo_rates,
# './data/processed/evolutionary_rates.csv', row.names = FALSE)
Code
evo_rates <- read.csv("./data/processed/evolutionary_rates.csv")

cols <- viridis(10)

# raincoud plot:
fill_color <- adjustcolor(cols[[6]], 0.4)

ggplot(evo_rates, aes(y = values, x = ind)) +
  # add half-violin from {ggdist} package
  stat_halfeye(
    fill = fill_color,
    alpha = 0.5,
    # custom bandwidth
    adjust = .5,
    # adjust height
    width = .6,
    .width = 0,
    # move geom to the cright
    justification = -.2,
    point_colour = NA
  ) +
  geom_boxplot(fill = fill_color,
    width = .15,
    # remove outliers
    outlier.shape = NA # `outlier.shape = NA` works as well
  ) +
  # add justified jitter from the {gghalves} package
  geom_half_point(
    color = fill_color,
    # draw jitter on the left
    side = "l",
    # control range of jitter
    range_scale = .4,
    # add some transparency
    alpha = .5,
  ) +
   # ylim(c(-30, 310)) +
  labs(x = "Acoustic features", y = "Evolutionary rate") + theme(axis.text.x = element_text(angle = 15, hjust = 1)) + 
    theme_classic(base_size = 20)

16.3 Evolutionary integration

Code
feat_grp <- features_df$restriction
names(feat_grp) <- features_df$features

out <- warbleR:::pblapply_wrblr_int(trees, cl = 20, function(tree) {

    matched <- match_data_tree(mean_spec_features_matrix, tree, index = rownames(mean_spec_features_matrix))

    IT <- phylo.integration(A = matched$data, partition.gp = feat_grp,
        phy = matched$tree, iter = 10000, print.progress = FALSE)

    out <- data.frame(effect_size = IT$Z, IT$P.value)

    return(out)
})

phylo_intr <- do.call(rbind, out)


saveRDS(phylo_intr, "./data/processed/phylogenetic_integration.RDS")
Code
phylo_intr <- readRDS("./data/processed/phylogenetic_integration.RDS")

# make a ggplot2 density plot overlaid with a histogram of the
# effect size in phylo_intr
ggplot(phylo_intr, aes(x = effect_size)) + geom_density(fill = "#3B2F5E99",
    alpha = 0.5) + geom_histogram(aes(y = ..density..), fill = "#54C9AD99",
    alpha = 0.5, bins = 20) + theme_classic(base_size = 20) + labs(x = "Modularity effect size",
    y = "Density") + geom_vline(xintercept = 0, color = "red", linetype = "dashed")

17 Find modules of traits

Code
matched <- match_data_tree(mean_spec_features_matrix, tree, index = rownames(mean_spec_features_matrix))


# Correlation matrix of traits based on PCA loadings
cor_matrix <- cor(matched$data)
cor_matrix <- mean_corr
# Create a network from the correlation matrix
threshold <- 0.6  # Set a threshold for correlation strength
adj_matrix <- abs(cor_matrix) > threshold  # Adjacency matrix

# Build the graph
trait_network <- graph_from_adjacency_matrix(adj_matrix, mode = "undirected",
    diag = FALSE)

# Plot the network change color of nodes by restricted column in
# features_df
plot(trait_network, vertex.label = colnames(matched$data), main = "Trait Network from PCA Loadings",
    vertex.color = ifelse(features_df$restriction == "restricted",
        "red", "blue"))

# Detect modules using Louvain method
communities <- cluster_louvain(trait_network)
communities <- cluster_walktrap(trait_network)
# Assign module membership to traits

V(trait_network)$color <- communities$membership  # Color nodes by module

# Plot the network with detected modules
plot(trait_network, vertex.label = colnames(matched$data), vertex.color = V(trait_network)$color,
    main = "Detected Trait Modules")
Code
phylo_intr <- readRDS("./data/processed/phylogenetic_integration.RDS")

quantile(phylo_intr$effect_size, c(0.025, 0.975))

sum(phylo_intr$effect_size > 0)/length(phylo_intr$effect_size)

17.1 Degradation and evolutionary rate of acoustic features

17.1.1 PCA

Code
# Taking only numeric data for the pca
degrad_df <- read.csv("./data/processed/degradation_measures_SUR_ALL.csv")

degrad_params <- c("signal.to.noise.ratio", "tail.to.signal.ratio",
    "excess.attenuation", "envelope.correlation", "blur.ratio", "cross.correlation",
    "spectrum.blur.ratio", "spectrum.correlation")

degrad_df <- degrad_df[complete.cases(degrad_df[, degrad_params]),
    ]

sapply(degrad_df[, degrad_params], function(x) any(is.infinite(x)))

degrad_df <- degrad_df[!is.infinite(degrad_df$excess.attenuation),
    ]

pca_degrad <- prcomp(degrad_df[, degrad_params], scale = T)

pca_var <- round(summary(pca_degrad)$importance[2, ] * 100)

degrad_df$pc1 <- pca_degrad$x[, 1]

degrad_params <- c(degrad_params, "pc1")
# plot rotation values by PC
pca_rot <- as.data.frame(pca_degrad$rotation[, 1:4])

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]), "%)")

ggplot(pca_rot_stck, aes(x = variable, y = rotation, fill = Sign)) +
    geom_col() + coord_flip() + scale_fill_viridis_d(alpha = 0.7,
    begin = 0.2, end = 0.8) + facet_wrap(~ind_var) + theme_classic()

17.2 Estimate evolutinary rate association to degradation

Code
mean_spec_features$pc1 <- sapply(mean_spec_features$song.type.year,
    function(x) mean(degrad_df$pc1[degrad_df$song.type.year == x]))



# Standardize the tree to unit length
ultra_tree_standardized <- multi2di(ultra_tree)
ultra_tree_standardized <- compute.brlen(ultra_tree_standardized,
    method = "cladewise")

mods <- warbleR:::pblapply_wrblr_int(2:ncol(mean_spec_features), cl = 10,
    function(x) {
        # brownian motion
        bm_mod <- rate_gls(x = mean_spec_features$pc1, y = mean_spec_features[,
            x], species = mean_spec_features$song.type.year, ultra_tree_standardized,
            model = "predictor_BM", silent = TRUE, maxiter = 1000)

        bootout_bm <- rate_gls_boot(bm_mod, n = 100, silent = TRUE,
            maxiter = 1000)

        bootout_bm <- cbind(model = "BM", feature = names(mean_spec_features)[x],
            parameter = rownames(bootout_bm$summary), as.data.frame(bootout_bm$summary))

        # geometric brownian motion
        gbm_mod <- rate_gls(x = mean_spec_features$pc1 + 1, y = mean_spec_features[,
            x], species = mean_spec_features$song.type.year, ultra_tree_standardized,
            model = "predictor_gBM", silent = TRUE, maxiter = 1000)

        bootout_gbm <- rate_gls_boot(gbm_mod, n = 100, silent = TRUE,
            maxiter = 1000)

        bootout_gbm <- cbind(model = "gBM", feature = names(mean_spec_features)[x],
            parameter = rownames(bootout_gbm$summary), as.data.frame(bootout_gbm$summary))

        re_mod <- rate_gls(x = mean_spec_features$pc1, y = mean_spec_features[,
            x], species = mean_spec_features$song.type.year, ultra_tree_standardized,
            model = "recent_evol", silent = TRUE, maxiter = 1000)

        bootout_re <- rate_gls_boot(re_mod, n = 100, silent = TRUE,
            maxiter = 1000)

        bootout_re <- cbind(model = "recent_evol", feature = names(mean_spec_features)[x],
            parameter = rownames(bootout_re$summary), as.data.frame(bootout_re$summary))

        bootout <- rbind(bootout_bm, bootout_gbm, bootout_re)

        return(bootout)
    })

gls_res <- do.call(rbind, mods)

write.csv(gls_res, "./data/processed/evolutionary_rate_by_degradation.csv",
    row.names = FALSE)
Code
gls_res <- read.csv("./data/processed/evolutionary_rate_by_degradation.csv")

gls_res <- gls_res[gls_res$parameter == "b" | gls_res$parameter ==
    "Rsquared", c("model", "feature", "parameter", "Estimate", "SE",
    "X2.5.", "X97.5.")]

gls_res_kbl <- kableExtra::kbl(gls_res, row.names = FALSE, escape = FALSE,
    format = "html", digits = 3)

gls_res_kbl <- kableExtra::row_spec(kable_input = gls_res_kbl, row = which(gls_res$`97.5%` *
    gls_res$`2.5%` > 0 & gls_res$parameter == "b"), background = grDevices::adjustcolor("#6DCD59FF",
    alpha.f = 0.3))

gls_res_kbl
Code
df <- data.frame(feature = c("FreqMax", "RangoFreq", "IndxMod", "FreqPico",
    "Duración", "Skew", "FreqMin", "EntTiempo", "EntSP", "Kurt",
    "TiempoIQR", "FreqProm"), value = c(0, 0, 0, 0, 0.16, 0.24, 0.25,
    0.26, 0.37, 0.42, 0.44, 0.66))
ggplot(df, aes(x = value, fill = feature)) + geom_histogram(binwidth = 0.1) +
    labs(title = "Histogram of Values by Feature", x = "Value", y = "Frequency") +
    scale_fill_viridis(discrete = TRUE) + theme_minimal()
hist(df$value)
mean(df$value)
install.packages("goffda")
library(goffda)
test <- r_ou(12, t = seq(0, 1, len = 50), mu = 0, alpha = 1, sigma = 1,
    x0 = rnorm(12, mean = mu, sd = sigma/sqrt(2 * alpha)))
test <- plot(r_ou(n = 12, t = seq(0, 1, len = 50), alpha = 2, sigma = 4,
    x0 = 1:12), col = viridis(12), lwd = )

Takeaways

 


 

Session information

R version 4.4.1 (2024-06-14)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 22.04.4 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.10.0 
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.10.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=es_CR.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=es_CR.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=es_CR.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=es_CR.UTF-8 LC_IDENTIFICATION=C       

time zone: America/Costa_Rica
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] MASS_7.3-61        lme4_1.1-35.5      rptR_0.9.22        caret_6.0-94      
 [5] lattice_0.22-6     mvMORPH_1.2.1      subplex_1.9        corpcor_1.6.10    
 [9] pbapply_1.7-2      gghalves_0.1.4     ggdist_3.3.2       geiger_2.0.11     
[13] evolvability_2.0.0 geomorph_4.0.8     Matrix_1.7-0       rgl_1.3.1         
[17] RRPP_2.0.3         ratematrix_1.2.4   phytools_2.3-0     maps_3.4.2        
[21] ggtree_3.12.0      phangorn_2.11.1    ape_5.8            DT_0.33           
[25] corrplot_0.92      viridis_0.6.5      viridisLite_0.4.2  brms_2.22.0       
[29] Rcpp_1.0.13        ggplot2_3.5.1      Rraven_1.0.13      readxl_1.4.3      
[33] baRulho_2.1.2      ohun_1.0.2         warbleR_1.1.32     NatureSounds_1.0.4
[37] seewave_2.2.3      tuneR_1.4.7        rprojroot_2.0.4    formatR_1.14      
[41] knitr_1.48        

loaded via a namespace (and not attached):
  [1] fs_1.6.4                matrixStats_1.4.1       bitops_1.0-9           
  [4] sf_1.0-17               lubridate_1.9.3         doParallel_1.0.17      
  [7] numDeriv_2016.8-1.1     tools_4.4.1             backports_1.5.0        
 [10] utf8_1.2.4              R6_2.5.1                lazyeval_0.2.2         
 [13] mgcv_1.9-1              withr_3.0.1             Brobdingnag_1.2-9      
 [16] gridExtra_2.3           cli_3.6.3               sandwich_3.1-0         
 [19] labeling_0.4.3          mvtnorm_1.3-1           readr_2.1.5            
 [22] proxy_0.4-27            dtw_1.23-1              yulab.utils_0.1.4      
 [25] DEoptim_2.2-8           parallelly_1.38.0       rstudioapi_0.16.0      
 [28] phylolm_2.6.5           optimParallel_1.0-2     generics_0.1.3         
 [31] gridGraphics_0.5-1      combinat_0.0-8          dplyr_1.1.4            
 [34] distributional_0.5.0    loo_2.8.0               fansi_1.0.6            
 [37] abind_1.4-8             lifecycle_1.0.4         scatterplot3d_0.3-44   
 [40] multcomp_1.4-25         yaml_2.3.10             clusterGeneration_1.3.8
 [43] recipes_1.0.10          grid_4.4.1              crayon_1.5.3           
 [46] pillar_1.9.0            rjson_0.2.23            boot_1.3-30            
 [49] estimability_1.5.1      fftw_1.0-9              future.apply_1.11.2    
 [52] codetools_0.2-20        fastmatch_1.1-4         glue_1.8.0             
 [55] packrat_0.9.2           ggfun_0.1.5             data.table_1.15.4      
 [58] remotes_2.5.0           vctrs_0.6.5             png_0.1-8              
 [61] treeio_1.26.0           spam_2.11-0             testthat_3.2.1.1       
 [64] cellranger_1.1.0        gtable_0.3.5            cachem_1.1.0           
 [67] gower_1.0.1             xfun_0.48               prodlim_2024.06.25     
 [70] coda_0.19-4.1           survival_3.7-0          timeDate_4032.109      
 [73] signal_1.8-1            iterators_1.0.14        sketchy_1.0.3          
 [76] pbmcapply_1.5.1         hardhat_1.4.0           units_0.8-5            
 [79] lava_1.8.0              TH.data_1.1-2           ipred_0.9-14           
 [82] nlme_3.1-165            tensorA_0.36.2.1        Deriv_4.1.6            
 [85] KernSmooth_2.23-24      rpart_4.1.23            colorspace_2.1-1       
 [88] DBI_1.2.3               nnet_7.3-19             mnormt_2.1.1           
 [91] tidyselect_1.2.1        emmeans_1.10.3          compiler_4.4.1         
 [94] expm_0.999-9            posterior_1.6.0         checkmate_2.3.2        
 [97] scales_1.3.0            classInt_0.4-10         quadprog_1.5-8         
[100] stringr_1.5.1           digest_0.6.37           Sim.DiffProc_4.9       
[103] minqa_1.2.7             rmarkdown_2.28          htmltools_0.5.8.1      
[106] pkgconfig_2.0.3         jpeg_0.1-10             base64enc_0.1-3        
[109] fastmap_1.2.0           rlang_1.1.4             htmlwidgets_1.6.4      
[112] farver_2.1.2            zoo_1.8-12              jsonlite_1.8.9         
[115] ModelMetrics_1.2.2.2    RCurl_1.98-1.16         magrittr_2.0.3         
[118] ggplotify_0.1.2         dotCall64_1.2           bayesplot_1.11.1       
[121] patchwork_1.3.0         munsell_0.5.1           stringi_1.8.4          
[124] pROC_1.18.5             brio_1.1.5              plyr_1.8.9             
[127] parallel_4.4.1          listenv_0.9.1           splines_4.4.1          
[130] hms_1.1.3               igraph_2.0.3            reshape2_1.4.4         
[133] stats4_4.4.1            rstantools_2.4.0        evaluate_1.0.1         
[136] RcppParallel_5.1.9      deSolve_1.40            nloptr_2.1.1           
[139] tzdb_0.4.0              foreach_1.5.2           tidyr_1.3.1            
[142] purrr_1.0.2             future_1.34.0           xtable_1.8-4           
[145] glassoFast_1.0.1        e1071_1.7-16            tidytree_0.4.6         
[148] class_7.3-22            tibble_3.2.1            aplot_0.2.3            
[151] memoise_2.0.1           timechange_0.3.0        globals_0.16.3         
[154] bridgesampling_1.1-2    xaringanExtra_0.8.0