Code
# options to customize chunk outputs
knitr::opts_chunk$set(
tidy = TRUE, # soft-wrap code in output
tidy.opts = list(width.cutoff = 65) # width cutoff for soft-wrapping code
)Vocal convergence and social behavior in budgies
# options to customize chunk outputs
knitr::opts_chunk$set(
tidy = TRUE, # soft-wrap code in output
tidy.opts = list(width.cutoff = 65) # width cutoff for soft-wrapping code
)Figure out the best way to quantify convergence
Quantify vocal convergence
# 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("PhenotypeSpace", "warbleR", "T4transport", "ggplot2",
"viridis", "pbapply", "energy", "beepr")
# install/ load packages
sketchy::load_packages(packages = packages)Loading required package: PhenotypeSpace
Loading required package: warbleR
Loading required package: tuneR
Loading required package: seewave
Loading required package: NatureSounds
Loading required package: knitr
Loading required package: T4transport
** ------------------------------------------------------- **
** T4transport || Computational Optimal Transport in R
**
** Maintainer : Kisung You (kisung.you@outlook.com)
** Version : 0.1.8 (2026)
** Website : https://www.kisungyou.com/T4transport/
**
** Please share any bugs or suggestions to the maintainer.
** ------------------------------------------------------- **
Loading required package: ggplot2
Loading required package: viridis
Loading required package: viridisLite
Loading required package: pbapply
Loading required package: energy
Loading required package: beepr
Attaching package: 'beepr'
The following object is masked from 'package:seewave':
beep
est <- readRDS("./data/raw/extended_selection_table.RDS")
mfs <- mfcc_stats(X = est, parallel = 5, bp = c(0.5, 8))
saveRDS(mfs, "./data/processed/mfccs.RDS")mfs <- readRDS("./data/processed/mfccs.RDS")
# run PCA on acoustic variables
pca <- prcomp(mfs[, c(-1, -2)], center = TRUE, scale. = TRUE)
saveRDS(pca, "./data/processed/pca.RDS")
# plot rotation values by PC
summ_pca <- summary(pca)
summ_pca$importance[2, ]
# select the PCs explaining at least 90%
pcs <- colnames(summ_pca$importance)[1:min(which(summ_pca$importance[3,
] > 0.9))]
pca_dat <- cbind(mfs[, c(1, 2)], as.data.frame(pca$x)[pcs])
pca_dat$record.block <- sapply(pca_dat$sound.files, function(x) unique(est$record.block[est$sound.files ==
x]))
pca_dat$record.group <- sapply(pca_dat$sound.files, function(x) unique(est$record.group[est$sound.files ==
x]))
pca_dat$date <- sapply(pca_dat$sound.files, function(x) unique(est$date[est$sound.files ==
x]))
pca_dat$treatment <- sapply(pca_dat$sound.files, function(x) unique(est$treatment[est$sound.files ==
x]))
pca_dat$round <- sapply(pca_dat$sound.files, function(x) unique(est$round[est$sound.files ==
x]))
pca_dat$ID <- sapply(pca_dat$sound.files, function(x) unique(est$ID[est$sound.files ==
x]))
saveRDS(pca_dat, "./data/processed/pca_dat.RDS")The first 69 PCs explain 90% of the variance in the data. The following graph shows the cumulative proportion of variance explained by each PC. The dashed red line indicates the 90% threshold.
pca <- readRDS("./data/processed/pca.RDS")
# plot rotation values by PC
summ_pca <- summary(pca)
summ_pca_df <- as.data.frame(t(summ_pca$importance))
summ_pca_df$PC <- as.character(1:nrow(summ_pca_df))
summ_pca_df$PC <- factor(summ_pca_df$PC, levels = summ_pca_df$PC)
summ_pca_df <- summ_pca_df[summ_pca_df$PC %in% 1:80, ]
## ggplot barplot for cummulative proportion
ggplot(data = summ_pca_df, aes(x = PC, y = `Cumulative Proportion`)) +
geom_bar(stat = "identity", fill = "steelblue") + geom_hline(yintercept = 0.9,
linetype = "dashed", color = "red") + labs(x = "Principal Component",
y = "Cumulative Proportion of Variance Explained") + theme_classic()pca_dat <- readRDS("./data/processed/pca_dat.RDS")
tab <- table(pca_dat$ID[pca_dat$record.block != 1], pca_dat$record.block[pca_dat$record.block !=
1])
counts <- as.data.frame(tab)
names(counts) <- c("ID", "block", "count")
counts$group <- sapply(counts$ID, function(x) unique(pca_dat$record.group[pca_dat$ID ==
x]))
cutoff <- 200
subcounts <- counts[counts$count >= cutoff, ]
sum(c(table(subcounts$group, subcounts$block)) > 1)[1] 22
subcounts <- as.data.frame(table(subcounts$group, subcounts$block))
names(subcounts) <- c("group", "block", "Freq")
sel_groups <- subcounts[subcounts$Freq > 1, ]source("~/Dropbox/R_package_testing/PhenotypeSpace/R/space_similarity.R")
source("~/Dropbox/R_package_testing/PhenotypeSpace/R/rarefact_space_similarity.R")
source("~/Dropbox/R_package_testing/PhenotypeSpace/R/internal_functions.R")
sample_cutoffs <- seq(5, 200, by = 1)
distance_methods <- c("Wasserstein", "Euclidean", "energy")
out <- lapply(seq_len(nrow(sel_groups)), function(x) {
subpca_dat <- pca_dat[pca_dat$record.group == sel_groups$group[x] &
pca_dat$record.block == sel_groups$block[x], ]
# only those above cutoff
id_count <- table(subpca_dat$ID)
subpca_dat <- subpca_dat[subpca_dat$ID %in% names(id_count)[id_count >
cutoff], ]
best_dists <- list()
for (dm in distance_methods) {
best_dists[[length(best_dists) + 1]] <- space_similarity(formula = as.formula(paste("ID ~",
paste(grep("PC", names(subpca_dat), value = TRUE), collapse = " + "))),
data = subpca_dat, method = "distance", distance.method = dm,
pb = FALSE)
}
names(best_dists) <- distance_methods
dists <- lapply(sample_cutoffs, function(y) {
sel_ID <- subpca_dat$ID[1]
X1 <- subpca_dat[subpca_dat$ID %in% sel_ID, ]
X2 <- subpca_dat[!subpca_dat$ID %in% sel_ID, ]
X1 <- X1[sample(seq_len(nrow(X1)), size = y, replace = FALSE),
]
W <- rbind(X1, X2)
out_dfs <- list()
for (dm in distance_methods) {
# default
dists <- space_similarity(formula = as.formula(paste("ID ~",
paste(grep("PC", names(subpca_dat), value = TRUE),
collapse = " + "))), data = W, method = "distance",
distance.method = dm, pb = FALSE)
dists$full.distance <- best_dists[[dm]]$distance
dists <- dists[dists$group.1 == sel_ID | dists$group.2 ==
sel_ID, ]
dists$method <- dm
# rarefacted
dists_rare <- rarefact_space_similarity(formula = as.formula(paste("ID ~",
paste(grep("PC", names(subpca_dat), value = TRUE),
collapse = " + "))), data = W, method = "distance",
distance.method = dm, pb = FALSE, n = 30)
dists_rare$full.distance <- best_dists[[dm]]$distance
dists_rare <- dists_rare[dists_rare$group.1 == sel_ID |
dists_rare$group.2 == sel_ID, ]
dists_rare$distance <- dists_rare$mean.distance
dists_rare$method <- paste0(dm, "_rare")
out_df <- rbind(dists, dists_rare[, names(dists)])
out_df$min.sample <- y
out_df$group <- sel_groups$group[x]
out_df$block <- sel_groups$block[x]
out_dfs[[length(out_dfs) + 1]] <- out_df
}
dists_df <- do.call(rbind, out_dfs)
return(dists_df)
})
dists_df <- do.call(rbind, dists)
return(dists_df)
})
results <- do.call(rbind, out)
saveRDS(results, "./data/processed/simulated_low_sample_scenarios.RDS")
beep(3)results <- readRDS("./data/processed/simulated_low_sample_scenarios.RDS")
results$prop.error <- abs(results$full.distance - results$distance)/results$full.distance
# ggplot with lines
agg <- aggregate(prop.error ~ min.sample + method, data = results,
FUN = mean)
agg$sd <- aggregate(prop.error ~ min.sample + method, data = results,
FUN = sd)$prop.error
# plot
ggplot(agg, aes(x = min.sample, y = prop.error, color = method)) +
geom_line() + geom_ribbon(aes(ymin = prop.error - sd, ymax = prop.error +
sd), alpha = 0.2) + labs(x = "Minimum sample size", y = "Mean proportional error") +
theme_minimal()# add column rarefaction
agg$rare <- grepl("rare", agg$method)
agg$method <- gsub("_rare", "", agg$method)
agg$method <- factor(agg$method, levels = c("Wasserstein", "energy",
"Euclidean"))
ggplot(agg, aes(x = min.sample, y = prop.error, color = rare)) + geom_line() +
geom_ribbon(aes(ymin = prop.error - sd, ymax = prop.error + sd),
alpha = 0.2) + labs(x = "Minimum sample size", y = "Mean proportional error") +
theme_minimal() + facet_wrap(~method)Zoom in in the y axis:
ggplot(agg, aes(x = min.sample, y = prop.error, color = rare)) + geom_line() +
geom_ribbon(aes(ymin = prop.error - sd, ymax = prop.error + sd),
alpha = 0.2) + labs(x = "Minimum sample size", y = "Mean proportional error") +
theme_minimal() + facet_wrap(~method) + ylim(c(0, 0.25))Warning: Removed 31 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 159 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Similarity metrics can still be useful if the keep the relative difference between distances even if the absolute distance is not accurate. To check this we can calculate the correlation between the full data set distances and the down sampled distances.
The following graph shows the correlation between the full data set distances and the down sampled distances for each method (columns) and minimum sample size (rows). The dashed line represents the 1:1 line, where the inferred distance equals the true distance.
# order levels
results$method <- factor(results$method, levels = c("Wasserstein",
"energy", "Euclidean", "Wasserstein_rare", "energy_rare", "Euclidean_rare"))
# do scatterplot distance vs full
ggplot(results[results$min.sample %in% c(5, 10, 20, 30), ], aes(y = full.distance,
x = distance, color = method)) + geom_point(alpha = 0.5) + scale_color_viridis_d(option = "G",
guide = "none", end = 0.8) + geom_abline(slope = 1, intercept = 0,
linetype = "dashed") + labs(y = "True distance", x = "Inferred distance") +
theme_minimal() + facet_wrap(~min.sample + method, scales = "free",
ncol = 6)The following graph shows the pearson correlation (y axis) between the full data set distances and the subsampled data for each method (columns) and minimum sample size (x axis)
# get pearson correlation for each min.sample
cors_list <- lapply(split(results, f = interaction(results$min.sample,
results$method)), function(x) {
cor(x$full.distance, x$distance, method = "pearson")
})
# put in data frame
cors_df <- as.data.frame(do.call(rbind, cors_list))
cors_df$min.sample <- sapply(strsplit(rownames(cors_df), "\\."), function(x) x[1])
cors_df$method <- sapply(strsplit(rownames(cors_df), "\\."), function(x) x[2])
cors_df$rare <- grepl("rare", cors_df$method)
cors_df$method <- gsub("_rare", "", cors_df$method)
cors_df$correlation <- as.numeric(cors_df$V1)
cors_df$min.sample <- as.numeric(cors_df$min.sample)
ggplot(cors_df, aes(x = min.sample, y = correlation, color = rare)) +
scale_color_viridis_d(option = "G", end = 0.8, alpha = 0.6) +
geom_line() + labs(x = "Minimum sample size", y = "Pearson correlation",
color = "Rarefaction") + theme_minimal() + facet_wrap(~method,
ncol = 6)Rarefaction is not that great ???
A minimum sample size between 30-50 seems to be a good compromise between accuracy and sample size
─ Session info ───────────────────────────────────────────────────────────────
setting value
version R version 4.3.2 (2023-10-31)
os Ubuntu 22.04.2 LTS
system x86_64, linux-gnu
ui X11
language es_ES:en
collate en_US.UTF-8
ctype en_US.UTF-8
tz America/Costa_Rica
date 2026-07-09
pandoc 3.6.3 @ /usr/lib/rstudio/resources/app/bin/quarto/bin/tools/x86_64/ (via rmarkdown)
quarto 1.4.549 @ /usr/local/bin/quarto
─ Packages ───────────────────────────────────────────────────────────────────
package * version date (UTC) lib source
abind 1.4-8 2024-09-12 [1] CRAN (R 4.3.2)
audio 0.1-12 2025-12-15 [1] CRAN (R 4.3.2)
beepr * 2.0 2024-07-06 [1] CRAN (R 4.3.2)
bitops 1.0-9 2024-10-03 [1] CRAN (R 4.3.2)
boot 1.3-32 2025-08-29 [1] CRAN (R 4.3.2)
brio 1.1.5 2024-04-24 [1] CRAN (R 4.3.2)
cachem 1.1.0 2024-05-16 [1] CRAN (R 4.3.2)
class 7.3-23 2025-01-01 [1] CRAN (R 4.3.2)
classInt 0.4-11 2025-01-08 [1] CRAN (R 4.3.2)
cli 3.6.6 2026-04-09 [1] CRAN (R 4.3.2)
cluster 2.1.8.2 2026-02-05 [1] CRAN (R 4.3.2)
codetools 0.2-20 2024-03-31 [1] CRAN (R 4.3.2)
crayon 1.5.3 2024-06-20 [1] CRAN (R 4.3.2)
curl 7.1.0 2026-04-22 [1] CRAN (R 4.3.2)
DBI 1.3.0 2026-02-25 [1] CRAN (R 4.3.2)
deldir 2.0-4 2024-02-28 [1] CRAN (R 4.3.2)
devtools 2.4.6 2025-10-03 [1] CRAN (R 4.3.2)
digest 0.6.39 2025-11-19 [1] CRAN (R 4.3.2)
dplyr 1.2.1 2026-04-03 [1] CRAN (R 4.3.2)
dtw 1.23-3 2026-06-09 [1] CRAN (R 4.3.2)
e1071 1.7-17 2025-12-18 [1] CRAN (R 4.3.2)
ellipsis 0.3.3 2026-04-04 [1] CRAN (R 4.3.2)
energy * 1.7-12 2024-08-24 [1] CRAN (R 4.3.2)
evaluate 1.0.5 2025-08-27 [1] CRAN (R 4.3.2)
farver 2.1.2 2024-05-13 [1] CRAN (R 4.3.2)
fastmap 1.2.0 2024-05-15 [1] CRAN (R 4.3.2)
fftw 1.0-9 2024-09-20 [1] CRAN (R 4.3.2)
formatR 1.14 2023-01-17 [1] CRAN (R 4.3.2)
fs 2.0.1 2026-03-24 [1] CRAN (R 4.3.2)
generics 0.1.4 2025-05-09 [1] CRAN (R 4.3.2)
ggplot2 * 4.0.3 2026-04-22 [1] CRAN (R 4.3.2)
glue 1.8.1 2026-04-17 [1] CRAN (R 4.3.2)
goftest 1.2-3 2021-10-07 [3] CRAN (R 4.1.1)
gridExtra 2.3.1 2026-06-25 [1] CRAN (R 4.3.2)
gsl 2.1-8 2023-01-24 [1] CRAN (R 4.3.2)
gtable 0.3.6 2024-10-25 [1] CRAN (R 4.3.2)
htmltools 0.5.9 2025-12-04 [1] CRAN (R 4.3.2)
htmlwidgets 1.6.4 2023-12-06 [1] CRAN (R 4.3.2)
httr 1.4.8 2026-02-13 [1] CRAN (R 4.3.2)
jsonlite 2.0.0 2025-03-27 [1] CRAN (R 4.3.2)
KernSmooth 2.23-26 2025-01-01 [1] CRAN (R 4.3.2)
knitr * 1.51 2025-12-20 [1] CRAN (R 4.3.2)
labeling 0.4.3 2023-08-29 [1] CRAN (R 4.3.2)
lattice 0.22-9 2026-02-09 [1] CRAN (R 4.3.2)
lifecycle 1.0.5 2026-01-08 [1] CRAN (R 4.3.2)
magrittr 2.0.5 2026-04-04 [1] CRAN (R 4.3.2)
MASS 7.3-55 2022-01-13 [4] CRAN (R 4.1.2)
Matrix 1.6-5 2024-01-11 [1] CRAN (R 4.3.2)
memoise 2.0.1 2021-11-26 [3] CRAN (R 4.1.2)
mgcv 1.8-39 2022-02-24 [4] CRAN (R 4.1.2)
NatureSounds * 1.0.5 2025-01-17 [1] CRAN (R 4.3.2)
nlme 3.1-169 2026-03-27 [1] CRAN (R 4.3.2)
otel 0.2.0 2025-08-29 [1] CRAN (R 4.3.2)
packrat 0.9.3 2025-06-16 [1] CRAN (R 4.3.2)
pbapply * 1.7-4 2025-07-20 [1] CRAN (R 4.3.2)
permute 0.9-10 2026-02-06 [1] CRAN (R 4.3.2)
PhenotypeSpace * 0.1.0 2024-10-04 [1] CRAN (R 4.3.2)
pillar 1.11.1 2025-09-17 [1] CRAN (R 4.3.2)
pkgbuild 1.4.8 2025-05-26 [1] CRAN (R 4.3.2)
pkgconfig 2.0.3 2019-09-22 [3] CRAN (R 4.0.1)
pkgload 1.5.3 2026-06-15 [1] CRAN (R 4.3.2)
polyclip 1.10-7 2024-07-23 [1] CRAN (R 4.3.2)
proxy 0.4-29 2025-12-29 [1] CRAN (R 4.3.2)
purrr 1.2.2 2026-04-10 [1] CRAN (R 4.3.2)
R6 2.6.1 2025-02-15 [1] CRAN (R 4.3.2)
raster 3.6-32 2025-03-28 [1] CRAN (R 4.3.2)
rbibutils 2.4.1 2026-01-21 [1] CRAN (R 4.3.2)
RColorBrewer 1.1-3 2022-04-03 [1] CRAN (R 4.3.2)
Rcpp 1.1.1-1.1 2026-04-24 [1] CRAN (R 4.3.2)
RCurl 1.98-1.19 2026-06-03 [1] CRAN (R 4.3.2)
Rdpack 2.6.6 2026-02-08 [1] CRAN (R 4.3.2)
remotes 2.5.0 2024-03-17 [1] CRAN (R 4.3.2)
rjson 0.2.23 2024-09-16 [1] CRAN (R 4.3.2)
rlang 1.2.0 2026-04-06 [1] CRAN (R 4.3.2)
rmarkdown 2.31 2026-03-26 [1] CRAN (R 4.3.2)
rstudioapi 0.19.0 2026-06-11 [1] CRAN (R 4.3.2)
S7 0.2.2 2026-04-22 [1] CRAN (R 4.3.2)
scales 1.4.0 2025-04-24 [1] CRAN (R 4.3.2)
seewave * 2.2.4 2025-08-19 [1] CRAN (R 4.3.2)
sessioninfo 1.2.4 2026-06-04 [1] CRAN (R 4.3.2)
sf 1.0-22 2025-11-10 [1] CRAN (R 4.3.2)
signal 1.8-1 2024-06-26 [1] CRAN (R 4.3.2)
sketchy 1.0.6 2025-12-04 [1] local (/home/marce/Dropbox/R_package_testing/sketchy)
sp 2.2-1 2026-02-13 [1] CRAN (R 4.3.2)
spatstat.data 3.1-9 2025-10-18 [1] CRAN (R 4.3.2)
spatstat.explore 3.8-1 2026-05-24 [1] CRAN (R 4.3.2)
spatstat.geom 3.8-1 2026-05-23 [1] CRAN (R 4.3.2)
spatstat.random 3.5-0 2026-05-24 [1] CRAN (R 4.3.2)
spatstat.sparse 3.2-0 2026-05-21 [1] CRAN (R 4.3.2)
spatstat.univar 3.2-0 2026-05-18 [1] CRAN (R 4.3.2)
spatstat.utils 3.2-3 2026-05-10 [1] CRAN (R 4.3.2)
stringi 1.8.7 2025-03-27 [1] CRAN (R 4.3.2)
stringr 1.6.0 2025-11-04 [1] CRAN (R 4.3.2)
T4transport * 0.1.8 2026-01-11 [1] CRAN (R 4.3.2)
tensor 1.5.1 2025-06-17 [1] CRAN (R 4.3.2)
terra 1.8-80 2025-11-05 [1] CRAN (R 4.3.2)
testthat 3.3.2 2026-01-11 [1] CRAN (R 4.3.2)
tibble 3.3.1 2026-01-11 [1] CRAN (R 4.3.2)
tidyselect 1.2.1 2024-03-11 [1] CRAN (R 4.3.2)
tuneR * 1.4.7 2024-04-17 [1] CRAN (R 4.3.2)
units 1.0-1 2026-03-11 [1] CRAN (R 4.3.2)
usethis 3.2.1 2025-09-06 [1] CRAN (R 4.3.2)
vctrs 0.7.3 2026-04-11 [1] CRAN (R 4.3.2)
vegan 2.7-5 2026-05-25 [1] CRAN (R 4.3.2)
viridis * 0.6.5 2024-01-29 [1] CRAN (R 4.3.2)
viridisLite * 0.4.3 2026-02-04 [1] CRAN (R 4.3.2)
warbleR * 1.1.37 2025-10-22 [1] CRAN (R 4.3.2)
withr 3.0.3 2026-06-19 [1] CRAN (R 4.3.2)
xaringanExtra 0.8.0 2024-05-19 [1] CRAN (R 4.3.2)
xfun 0.59 2026-06-19 [1] CRAN (R 4.3.2)
yaml 2.3.12 2025-12-10 [1] CRAN (R 4.3.2)
[1] /home/marce/R/x86_64-pc-linux-gnu-library/4.3
[2] /usr/local/lib/R/site-library
[3] /usr/lib/R/site-library
[4] /usr/lib/R/library
* ── Packages attached to the search path.
──────────────────────────────────────────────────────────────────────────────