Introduction
State of the Field
This analysis provides a comparative benchmark of R packages designed for calculating standard and phylogenetic metrics of alpha and beta diversity. The primary objective is to evaluate their computational efficiency, with a focus on processing speed and memory allocation. Packages that rely on these foundational libraries as dependencies have been omitted from this study to isolate the performance of the core implementations.
| R Package | Classic alpha/beta | Phylogenetic alpha/beta |
|---|---|---|
| abdiv | Serial R | none |
| adiv | Serial R | Serial R |
| ampvis2 | vegan | Serial R |
| ecodist | Serial C/R | none |
| ecodive | Parallel C | Parallel C |
| entropart | Serial R | none |
| GUniFrac | none | Serial C |
| labdsv | Serial FORTRAN | none |
| parallelDist | Parallel C++ | none |
| philentropy | Serial C++ | none |
| phyloregion | vegan | Serial R |
| phyloseq | vegan | Parallel R |
| picante | vegan | Serial R |
| tabula | Serial R | none |
| vegan | Serial C | none |
Methodology
The bench R package was employed to quantify the
computational runtime and memory allocation for the diversity algorithms
within each of the 15 selected packages. All benchmarks were executed on
a host system with the following hardware and software
configuration:
CPU: 6-Core Intel i5-9600K @ 3.70GHz
RAM: 64.0 GB
OS: Windows 11 Pro (64-bit, Version 25H2, Build 26200.7171)
Furthermore, the bench::mark() function was utilized to
verify that the outputs from all benchmarked expressions were
numerically equivalent, ensuring the consistency and comparability of
the results.
Setup
Two standard datasets from the rbiom R package,
hmp50 and gems, were selected for this
evaluation. The hmp50 dataset, which includes 50 samples
and an associated phylogenetic tree, was used to benchmark the
computationally intensive phylogenetic metrics, such as UniFrac and
Faith’s PD. For the traditional diversity metrics, which are
significantly less demanding, the larger gems dataset,
comprising 1,006 samples, was employed.
To account for the heterogeneous input and output formats across the 15 R packages, necessary data transformations were performed. To ensure that the benchmarks exclusively measured the performance of the diversity calculations, these data conversion steps were executed outside of the timed code blocks whenever possible.
Click to reveal R code.
install.packages('pak')
# Tools and Datasets for Benchmarking Report
pak::pkg_install(pkg = c(
'bench', 'dplyr', 'ggplot2', 'ggrepel', 'rbiom', 'svglite' ))
# Diversity Metric Implementations
pak::pkg_install(pkg = c(
'abdiv', 'adiv', 'ecodist', 'ecodive', 'entropart', 'GUniFrac',
'kasperskytte/ampvis2', 'labdsv', 'parallelDist', 'philentropy',
'phyloregion', 'phyloseq', 'picante', 'tabula', 'vegan' ))
# Software Versions
version$version.string
#> [1] "R version 4.5.2 (2025-10-31 ucrt)"
data.frame(ver = sapply(FUN = packageDescription, fields = 'Version', c(
'bench', 'dplyr', 'ggplot2', 'ggrepel', 'rbiom',
'abdiv', 'adiv', 'ecodist', 'ecodive', 'entropart', 'GUniFrac',
'ampvis2', 'labdsv', 'parallelDist', 'philentropy',
'phyloregion', 'phyloseq', 'picante', 'tabula', 'vegan' )))
#> ver
#> bench 1.1.4
#> dplyr 1.1.4
#> ggplot2 4.0.1
#> ggrepel 0.9.6
#> rbiom 2.2.1
#> abdiv 0.2.0
#> adiv 2.2.1
#> ecodist 2.1.3
#> ecodive 2.2.0
#> entropart 1.6-16
#> GUniFrac 1.9
#> ampvis2 2.8.11
#> labdsv 2.1-2
#> parallelDist 0.2.7
#> philentropy 0.10.0
#> phyloregion 1.0.9
#> phyloseq 1.54.0
#> picante 1.8.2
#> tabula 3.3.2
#> vegan 2.7-2
library(bench)
library(ggplot2)
library(ggrepel)
library(dplyr)
library(Matrix)
(n_cpus <- ecodive::n_cpus())
#> [1] 6
# abdiv only accepts two samples at a time
pairwise <- function (f, data, ...) {
pairs <- utils::combn(nrow(data), 2)
structure(
mapply(
FUN = function (i, j) f(data[i,], data[j,], ...),
i = pairs[1,], j = pairs[2,] ),
class = 'dist',
Labels = rownames(data),
Size = nrow(data),
Diag = FALSE,
Upper = FALSE )
}
# Remove any extraneous attributes from dist objects,
# allowing them to be compared with `all.equal()`.
cleanup <- function (x) {
for (i in setdiff(names(attributes(x)), c('class', 'Labels', 'Size', 'Diag', 'Upper')))
attr(x, i) <- NULL
return (x)
}
# HMP50 dataset has 50 samples; convert to relative abundances
hmp50 <- rbiom::hmp50
hmp50_phy <- rbiom::convert_to_phyloseq(hmp50)
hmp50_mtx <- t(apply(as.matrix(hmp50), 2L, function (x) x / sum(x)))
hmp50_mtx_t <- t(hmp50_mtx)
hmp50_dgC <- as(hmp50_mtx, 'dgCMatrix')
hmp50_dgC_t <- t(hmp50_dgC)
hmp50_tree <- hmp50$tree
# GEMS dataset has 1006 samples; convert to relative abundances
gems_mtx <- t(apply(as.matrix(rbiom::gems), 2L, function (x) x / sum(x)))
gems_dgC <- as(gems_mtx, 'dgCMatrix')
gems_dgC_t <- t(gems_dgC)
## Bray-Curtis Dissimilarity
bray_curtis_res <- bench::mark(
iterations = 10,
'abdiv' = cleanup(pairwise(abdiv::bray_curtis, gems_mtx)),
'ecodist' = cleanup(ecodist::bcdist(gems_mtx)),
'ecodive' = cleanup(ecodive::bray(gems_dgC_t, norm = 'none', margin = 2)),
'labdsv' = cleanup(labdsv::dsvdis(gems_mtx, 'bray/curtis')),
'parallelDist' = cleanup(parallelDist::parallelDist(gems_mtx, 'bray')),
'philentropy' = cleanup(philentropy::distance(gems_mtx, 'sorensen', test.na = FALSE, use.row.names = TRUE, as.dist.obj = TRUE, mute.message = TRUE)),
'tabula' = cleanup(1 - pairwise(tabula::index_bray, gems_mtx)),
'vegan' = cleanup(vegan::vegdist(gems_mtx, 'bray')) )
## Jaccard Distance
jaccard_res <- bench::mark(
iterations = 10,
'abdiv' = cleanup(pairwise(abdiv::jaccard, gems_mtx)),
'ecodist' = cleanup(ecodist::distance(gems_mtx, 'jaccard')),
'ecodive' = cleanup(ecodive::jaccard(gems_dgC_t, margin = 2)),
'parallelDist' = cleanup(parallelDist::parallelDist(gems_mtx, 'binary')),
'philentropy' = cleanup(philentropy::distance(gems_mtx > 0, 'jaccard', test.na = FALSE, use.row.names = TRUE, as.dist.obj = TRUE, mute.message = TRUE)),
'phyloregion' = cleanup(phyloregion::beta_diss(gems_mtx, 'jaccard')$beta.jac),
'stats' = cleanup(stats::dist(gems_mtx, 'binary')),
'vegan' = cleanup(vegan::vegdist(gems_mtx, 'jaccard', binary = TRUE)) )
## Manhattan Distance
manhattan_res <- bench::mark(
iterations = 10,
'abdiv' = cleanup(pairwise(abdiv::manhattan, gems_mtx)),
'ecodist' = cleanup(ecodist::distance(gems_mtx, 'manhattan')),
'ecodive' = cleanup(ecodive::manhattan(gems_dgC_t, norm = 'none', margin = 2)),
'parallelDist' = cleanup(parallelDist::parallelDist(gems_mtx, 'manhattan')),
'philentropy' = cleanup(philentropy::distance(gems_mtx, 'manhattan', test.na = FALSE, use.row.names = TRUE, as.dist.obj = TRUE, mute.message = TRUE)),
'stats' = cleanup(stats::dist(gems_mtx, 'manhattan')),
'vegan' = cleanup(vegan::vegdist(gems_mtx, 'manhattan')) )
## Euclidean Distance
euclidean_res <- bench::mark(
iterations = 10,
'abdiv' = cleanup(pairwise(abdiv::euclidean, gems_mtx)),
'ecodist' = cleanup(ecodist::distance(gems_mtx, 'euclidean')),
'ecodive' = cleanup(ecodive::euclidean(gems_dgC_t, norm = 'none', margin = 2)),
'parallelDist' = cleanup(parallelDist::parallelDist(gems_mtx, 'euclidean')),
'philentropy' = cleanup(philentropy::distance(gems_mtx, 'euclidean', test.na = FALSE, use.row.names = TRUE, as.dist.obj = TRUE, mute.message = TRUE)),
'stats' = cleanup(stats::dist(gems_mtx, 'euclidean')),
'vegan' = cleanup(vegan::vegdist(gems_mtx, 'euclidean')) )
## Shannon Diversity Index
shannon_res <- bench::mark(
iterations = 10,
'abdiv' = apply(gems_mtx, 1L, abdiv::shannon),
'adiv' = adiv::speciesdiv(gems_mtx, 'Shannon')[,1],
'ecodive' = ecodive::shannon(gems_dgC_t, margin = 2),
'entropart' = apply(gems_mtx, 1L, entropart::Shannon, CheckArguments = FALSE),
'philentropy' = apply(gems_mtx, 1L, philentropy::H, unit = 'log'),
'tabula' = apply(gems_mtx, 1L, tabula::index_shannon),
'vegan' = vegan::diversity(gems_mtx, 'shannon') )
## Gini-Simpson Index
simpson_res <- bench::mark(
iterations = 10,
'abdiv' = apply(gems_mtx, 1L, abdiv::simpson),
'adiv' = adiv::speciesdiv(gems_mtx, 'GiniSimpson')[,1],
'ecodive' = ecodive::simpson(gems_dgC_t, margin = 2),
'entropart' = apply(gems_mtx, 1L, entropart::Simpson, CheckArguments = FALSE),
'tabula' = 1 - apply(gems_mtx, 1L, tabula::index_simpson),
'vegan' = vegan::diversity(gems_mtx, 'simpson') )
## Faith's Phylogenetic Diversity
faith_res <- bench::mark(
iterations = 10,
check = FALSE, # entropart has incorrect output on non-ultrametric tree
'abdiv' = apply(hmp50_mtx, 1L, abdiv::faith_pd, hmp50_tree),
'adiv' = apply(hmp50_mtx, 1L, \(x) adiv::EH(hmp50_tree, colnames(hmp50_mtx)[x > 0])),
'ecodive' = ecodive::faith(hmp50_dgC_t, hmp50_tree, margin = 2),
'entropart' = apply(hmp50_mtx, 1L, entropart::PDFD, hmp50_tree, CheckArguments = FALSE),
'phyloregion' = phyloregion::PD(hmp50_mtx, hmp50_tree),
'picante' = as.matrix(picante::pd(hmp50_mtx, hmp50_tree))[,'PD'] )
## Unweighted UniFrac
u_unifrac_res <- rbind(
local({
# cluster for phyloseq
cl <- parallel::makeCluster(n_cpus)
doParallel::registerDoParallel(cl)
on.exit(parallel::stopCluster(cl))
bench::mark(
iterations = 10,
'abdiv' = cleanup(pairwise(abdiv::unweighted_unifrac, hmp50_mtx, hmp50_tree)),
'ecodive' = cleanup(ecodive::unweighted_unifrac(hmp50_dgC_t, hmp50_tree, margin = 2)),
'GUniFrac' = cleanup(as.dist(GUniFrac::GUniFrac(hmp50_mtx, hmp50_tree, alpha=1, verbose=FALSE)[[1]][,,2])),
'phyloregion' = cleanup(phyloregion::unifrac(hmp50_dgC, hmp50_tree)),
'phyloseq' = cleanup(phyloseq::UniFrac(hmp50_phy, weighted=FALSE, normalized=FALSE, parallel=TRUE)),
'picante' = cleanup(picante::unifrac(hmp50_mtx, hmp50_tree)) )
}),
# ampvis2 conflicts with phyloseq cluster, so run separately
local({
bench::mark(
iterations = 10,
'ampvis2' = {
cleanup(ampvis2:::dist.unifrac(hmp50_mtx_t, hmp50_tree, weighted=FALSE, normalise=FALSE, num_threads=n_cpus))
doParallel::stopImplicitCluster() } )
})
)
## Weighted UniFrac
w_unifrac_res <- rbind(
local({
# cluster for phyloseq
cl <- parallel::makeCluster(n_cpus)
doParallel::registerDoParallel(cl)
on.exit(parallel::stopCluster(cl))
bench::mark(
iterations = 10,
'abdiv' = cleanup(pairwise(abdiv::weighted_unifrac, hmp50_mtx, hmp50_tree)),
'ecodive' = cleanup(ecodive::weighted_unifrac(hmp50_dgC_t, hmp50_tree, margin = 2)),
'phyloseq' = cleanup(phyloseq::UniFrac(hmp50_phy, weighted=TRUE, normalized=FALSE, parallel=TRUE)) )
}),
# ampvis2 conflicts with phyloseq cluster, so run separately
local({
bench::mark(
iterations = 10,
'ampvis2' = {
cleanup(ampvis2:::dist.unifrac(hmp50_mtx_t, hmp50_tree, weighted=TRUE, normalise=FALSE, num_threads=n_cpus))
doParallel::stopImplicitCluster() } )
})
)
## Weighted Normalized UniFrac
wn_unifrac_res <- rbind(
local({
# cluster for phyloseq
cl <- parallel::makeCluster(n_cpus)
doParallel::registerDoParallel(cl)
on.exit(parallel::stopCluster(cl))
bench::mark(
iterations = 10,
'abdiv' = cleanup(pairwise(abdiv::weighted_normalized_unifrac, hmp50_mtx, hmp50_tree)),
'ecodive' = cleanup(ecodive::normalized_unifrac(hmp50_dgC_t, hmp50_tree, margin = 2)),
'GUniFrac' = cleanup(as.dist(GUniFrac::GUniFrac(hmp50_mtx, hmp50_tree, alpha=1, verbose=FALSE)[[1]][,,1])),
'phyloseq' = cleanup(phyloseq::UniFrac(hmp50_phy, weighted=TRUE, normalized=TRUE, parallel=TRUE)) )
}),
# ampvis2 conflicts with phyloseq cluster, so run separately
local({
bench::mark(
iterations = 10,
'ampvis2' = {
cleanup(ampvis2:::dist.unifrac(hmp50_mtx_t, hmp50_tree, weighted=TRUE, normalise=TRUE, num_threads=n_cpus))
doParallel::stopImplicitCluster() } )
})
)
## Weighted Normalized UniFrac
g_unifrac_res <- rbind(
local({
# cluster for phyloseq
cl <- parallel::makeCluster(n_cpus)
doParallel::registerDoParallel(cl)
on.exit(parallel::stopCluster(cl))
bench::mark(
iterations = 10,
'abdiv' = cleanup(pairwise(abdiv::generalized_unifrac, hmp50_mtx, hmp50_tree, alpha=0.5)),
'ecodive' = cleanup(ecodive::generalized_unifrac(hmp50_dgC_t, hmp50_tree, margin=2, alpha=0.5)),
'GUniFrac' = cleanup(as.dist(GUniFrac::GUniFrac(hmp50_mtx, hmp50_tree, alpha=0.5, verbose=FALSE)[[1]][,,1])) )
})
)
## Variance Adjusted UniFrac
va_unifrac_res <- rbind(
local({
# cluster for phyloseq
cl <- parallel::makeCluster(n_cpus)
doParallel::registerDoParallel(cl)
on.exit(parallel::stopCluster(cl))
bench::mark(
iterations = 10,
'abdiv' = cleanup(pairwise(abdiv::variance_adjusted_unifrac, hmp50_mtx, hmp50_tree)),
'ecodive' = cleanup(ecodive::variance_adjusted_unifrac(hmp50_dgC_t, hmp50_tree, margin = 2)) )
})
)
## Data frame for Figure 1A
Fig1A_data <- bind_rows(
mutate(bray_curtis_res, Metric = 'bray'),
mutate(shannon_res, Metric = 'shannon'),
mutate(faith_res, Metric = 'faith') ) %>%
mutate(Package = as.character(expression)) %>%
select(Package, Metric, median, mem_alloc) %>%
arrange(Metric, median)
Fig1A_data$Title <- factor(
x = Fig1A_data$Metric,
levels = c("shannon", "bray", "faith"),
labels = c(
"**Shannon Diversity Index**<br>1006 samples",
"**Bray-Curtis Dissimilarity**<br>1006 samples",
"**Faith's Phylogenetic Diversity**<br>50 samples") )
## ggplot for Figure 1A
Fig1A <- ggplot(Fig1A_data, aes(x = median, y = mem_alloc)) +
facet_wrap('Title', nrow = 1, scales = 'free_x') +
geom_point() +
geom_label_repel(aes(
label = Package,
alpha = ifelse(Package == 'ecodive', 1, 0.6),
fontface = ifelse(Package == 'ecodive', 2, 1),
size = ifelse(Package == 'ecodive', 3.2, 3) ),
box.padding = .4,
point.padding = .5,
min.segment.length = 0 ) +
scale_alpha_identity(guide = 'none') +
scale_size_identity(guide = 'none') +
bench::scale_x_bench_time() +
scale_y_log10(labels = scales::label_bytes()) +
labs(
tag = 'A',
x = 'Median Calculation Time (log scale; n=10)',
y = 'Memory Allocated\n(log scale; n=1)' ) +
theme_bw(base_size = 12) +
theme(
strip.text = ggtext::element_markdown(hjust = 0, lineheight = 1.2),
axis.title.x = element_text(margin = margin(t = 10)))
## Data frame for Figure 1B
Fig1B_data <- bind_rows(
mutate(u_unifrac_res, `UniFrac Variant` = 'Unweighted'),
mutate(w_unifrac_res, `UniFrac Variant` = 'Weighted'),
mutate(wn_unifrac_res, `UniFrac Variant` = 'Weighted Normalized'),
mutate(g_unifrac_res, `UniFrac Variant` = 'Generalized'),
mutate(va_unifrac_res, `UniFrac Variant` = 'Variance Adjusted') ) %>%
mutate(Package = as.character(expression)) %>%
select(Package, `UniFrac Variant`, median, mem_alloc) %>%
arrange(Package)
Fig1B_data$Title <- "**UniFrac Family**<br>50 samples"
Fig1B_data$`UniFrac Variant` <- factor(
x = Fig1B_data$`UniFrac Variant`,
levels = c("Unweighted", "Weighted", "Weighted Normalized", "Generalized", "Variance Adjusted") )
## ggplot for Figure 1B
Fig1B <- ggplot(Fig1B_data, aes(x = median, y = mem_alloc)) +
facet_wrap('Title') +
geom_point(aes(shape = `UniFrac Variant`), size = 2) +
geom_label_repel(aes(
label = Package,
alpha = ifelse(Package == 'ecodive', 1, 0.6),
fontface = ifelse(Package == 'ecodive', 2, 1),
size = ifelse(Package == 'ecodive', 3.2, 3) ),
data = ~subset(., `UniFrac Variant` == 'Unweighted'),
direction = 'y',
box.padding = 0.4,
point.padding = 10,
min.segment.length = Inf ) +
scale_shape(solid = FALSE) +
scale_alpha_identity(guide = 'none') +
scale_size_identity(guide = 'none') +
bench::scale_x_bench_time() +
scale_y_log10(labels = scales::label_bytes()) +
labs(
tag = 'B',
x = 'Median Calculation Time (log scale; n=10)',
y = 'Memory Allocated\n(log scale; n=1)' ) +
theme_bw(base_size = 12) +
theme(
strip.text = ggtext::element_markdown(hjust = 0, lineheight = 1.2),
axis.title.x = element_text(margin = margin(t = 10)) )
## Combined Figure 1
Fig1 <- patchwork::wrap_plots(
Fig1A,
Fig1B,
patchwork::guide_area(),
design = "111\n223",
guides = 'collect' )Results
print(Fig1)How much faster and more memory efficient is ecodive?
plyr::ddply(Fig1A_data, as.name('Metric'), function (x) {
x[['median']] <- as.numeric(x[['median']])
x[['mem_alloc']] <- as.numeric(x[['mem_alloc']])
ecodive <- as.list(x[x[['Package']] == 'ecodive',])
x <- x[x[['Package']] != 'ecodive',]
data.frame(
speed = paste0(paste(collapse=' - ', round(range(x$median / ecodive$median))), 'x'),
memory = paste0(paste(collapse=' - ', round(range(x$mem_alloc / ecodive$mem_alloc))), 'x') )
})
#> Metric speed memory
#> 1 bray 8 - 1259x 2 - 5272x
#> 2 faith 1 - 9290x 168 - 58964x
#> 3 shannon 3 - 159x 1849 - 8261x
plyr::ddply(Fig1B_data, as.name('UniFrac Variant'), function (x) {
x[['median']] <- as.numeric(x[['median']])
x[['mem_alloc']] <- as.numeric(x[['mem_alloc']])
ecodive <- as.list(x[x[['Package']] == 'ecodive',])
x <- x[x[['Package']] != 'ecodive',]
data.frame(
speed = paste0(paste(collapse=' - ', round(range(x$median / ecodive$median))), 'x'),
memory = paste0(paste(collapse=' - ', round(range(x$mem_alloc / ecodive$mem_alloc))), 'x') )
})
#> UniFrac Variant speed memory
#> 1 Unweighted 1 - 2264x 74 - 29695x
#> 2 Weighted 45 - 2019x 421 - 175550x
#> 3 Weighted Normalized 12 - 2138x 422 - 175629x
#> 4 Generalized 10 - 1818x 673 - 151011x
#> 5 Variance Adjusted 2314 - 2314x 216462 - 216462xRaw Data for Figure 1 Panel A
print(Fig1A_data[,1:4], n = Inf)
#> # A tibble: 21 × 4
#> Package Metric median mem_alloc
#> <chr> <chr> <bch:tm> <bch:byt>
#> 1 ecodive bray 19.54ms 3.97MB
#> 2 philentropy bray 150.69ms 38.87MB
#> 3 parallelDist bray 187.98ms 7.88MB
#> 4 ecodist bray 399.37ms 29.45MB
#> 5 labdsv bray 723.36ms 68.16MB
#> 6 vegan bray 1.68s 16.45MB
#> 7 abdiv bray 13.68s 20.43GB
#> 8 tabula bray 24.59s 20.44GB
#> 9 phyloregion faith 3.1ms 145.18MB
#> 10 ecodive faith 3.14ms 425.89KB
#> 11 picante faith 83.91ms 69.86MB
#> 12 abdiv faith 624.58ms 651.74MB
#> 13 adiv faith 2.03s 562.76MB
#> 14 entropart faith 29.13s 23.95GB
#> 15 ecodive shannon 8.19ms 15.6KB
#> 16 tabula shannon 24.41ms 28.17MB
#> 17 entropart shannon 36.85ms 41.77MB
#> 18 adiv shannon 38.68ms 125.87MB
#> 19 vegan shannon 58.97ms 68.08MB
#> 20 abdiv shannon 65.92ms 95.38MB
#> 21 philentropy shannon 1.3s 52.67MBRaw Data for Figure 1 Panel B
print(Fig1B_data[,1:4], n = Inf)
#> # A tibble: 21 × 4
#> Package `UniFrac Variant` median mem_alloc
#> <chr> <fct> <bch:tm> <bch:byt>
#> 1 GUniFrac Unweighted 77.53ms 113.4MB
#> 2 GUniFrac Weighted Normalized 88.74ms 92.1MB
#> 3 GUniFrac Generalized 80.09ms 92.1MB
#> 4 abdiv Unweighted 14.5s 20.1GB
#> 5 abdiv Weighted 15.74s 20GB
#> 6 abdiv Weighted Normalized 16.17s 20GB
#> 7 abdiv Generalized 15.01s 20.2GB
#> 8 abdiv Variance Adjusted 17s 24.5GB
#> 9 ampvis2 Unweighted 3.47s 53.5MB
#> 10 ampvis2 Weighted 4.48s 49.2MB
#> 11 ampvis2 Weighted Normalized 4.34s 49.3MB
#> 12 ecodive Unweighted 6.4ms 708.2KB
#> 13 ecodive Weighted 7.8ms 119.6KB
#> 14 ecodive Weighted Normalized 7.56ms 119.6KB
#> 15 ecodive Generalized 8.26ms 140.2KB
#> 16 ecodive Variance Adjusted 7.35ms 118.5KB
#> 17 phyloregion Unweighted 6.27ms 149.5MB
#> 18 phyloseq Unweighted 297.66ms 50.9MB
#> 19 phyloseq Weighted 352.09ms 49.4MB
#> 20 phyloseq Weighted Normalized 415.88ms 49.5MB
#> 21 picante Unweighted 2.54s 1.8GB