Bootstrap Resampling for Ranking Differentially Abundant Taxa


Microbiome studies often seek to identify individual features (i.e., OTUs/ASVs, species, pathways, etc.) associated some condition (i.e., exposure, experimental treatment, etc.) of interest. This problem can be approached in many different ways, but most commonly, one-at-a-time (OaaT) feature screening is undertaken. With OaaT screening, differences in species abundance according to some condition are obtained for each species individually, and the resultant associations ranked according to some measure. This measure is often the false discovery rate corrected p-value and the approach is referred to as differential abundance (DA) testing. In previous posts, I have highlighted some popular workflows to do this and touched on some of the difficulties.

While I often employ this type of approaches in my own work, I have limited confidence one can accurately identify the “correct set” of features from the typically long list of highly correlated and compositionally constrained features without some type of rigorous validation (which is not without its own difficulties as features may be expected to differ over time, geography, etc.). Rarely is such validation undertaken in the published literature, resulting in a possibly high proportion of findings that will not be reproduced in future studies.

Given access to validation samples is often difficult to come by if not pre-planned, and even if planned difficult to implement in practice due to resource and budgetary constraints, I have been thinking more about approaches that may help quantify the uncertainty in our ranking of the “most differently abundant features”. As usual, Professor Frank Harrell has some wisdom to share on this topic in Chapter 20 of his text Biostatistics for Biomedical Research titled Challenges of Analyzing High-Dimensional Data. He discusses several issues faced when attempting to perform feature selection with high-dimensional data including screening, multiplicity corrections, and ranking and recommendations bootstrap resampling as an approach to better reflect the difficulty of this task. He writes:

Feature discovery is really a ranking and selection problem. But ranking and selection methods are almost never used. An example bootstrap analysis on simulated data is presented later. This involves sampling with replacement from the combined (X, Y) dataset and recomputing for each bootstrap repetition the p association measures for the p candidate Xs. The p association measures are ranked. The ranking of each feature across bootstrap resamples is tracked, and a 0.95 confidence interval for the rank is derived by computing the 0.025 and 0.975 quantiles of the estimated ranks.

This approach puts OaaT in an honest context that fully admits the true difficulty of the task, including the high cost of the false negative rate (low power). Suppose that features are ranked so that a low ranking means weak estimated association and a high ranking means strong association. If one were to consider features to have “passed the screen” if their lower 0.95 confidence limit for their rank exceeds a threshold, and only dismisses features from further consideration if the upper confidence limit for their rank falls below a threshold, there would correctly be a large middle ground where features are not declared either winners or losers, and the researcher would be able to only make conclusions that are supported by the data.


This sounds like great advice. So in this post, I work through how one might implement this process in the hopes it might of use to others and future me. I found the results quite informative and look forward to applying the results to additional datasets. Below I:

  • access publicly available data using the curatedMetagenomicData package
  • convert the data into a phyloseq object for statistical analysis
  • perform DA testing using the popular Maaslin2 package with default settings
  • bootstrap the phyloseq object by sampling with replacement many times
  • fit the Maaslin2 model to each bootstrap resample and obtaining a ranking for each feature
  • present the 95% confidence interval for the ranks together with the Maaslin2 results


Load libraries

First, we load the required libraries.

library(curatedMetagenomicData); packageVersion("curatedMetagenomicData")  
## [1] '3.0.10'
library(tidyverse); packageVersion("tidyverse")                                                 
## [1] '1.3.1'
library(phyloseq); packageVersion("phyloseq")                            
## [1] '1.36.0'
library(Maaslin2); packageVersion("Maaslin2")                              
## [1] '1.6.0'
library(future.apply); packageVersion("future.apply")                     
## [1] '1.8.1'
library(Hmisc); packageVersion("Hmisc")                                    
## [1] '4.6.0'


Obtain example data

Then we pull down an example dataset using the curatedMetagenomicData package (this package is fantastic BTW and has been used in previous posts). This time we will select a study by Yachida et al. examining differences in the stool microbiomes of colorectal cancer patients and healthy controls via whole (meta)genome shotgun sequencing.

In the code chunks below, I identify the sample IDs of interest from the complete sampleMetadata object, use those IDs to pull down the processed taxonomic profiles as generated from MetaPhlAn 3, and then do a bit of data wrangling to convert the data into a phyloseq object for downstream analysis. I could have used the mia package for this last step, and likely will in future examples, to save some work and highlight some of its functionality (another awesome package).

meta_df <- curatedMetagenomicData::sampleMetadata

mydata <- meta_df %>%
  dplyr::filter(study_name == "YachidaS_2019") %>%
  dplyr::select(sample_id, subject_id, body_site, disease, age, gender, BMI) %>%
  dplyr::mutate(case_status = ifelse(disease == "CRC", "CRC", 
                              ifelse(disease == "healthy", "Control", "Other"))) %>%
  dplyr::filter(case_status != "Other") %>%
  na.omit(.)

head(mydata)
##      sample_id subject_id body_site disease age gender      BMI case_status
## 1 SAMD00115014  sub_10021     stool     CRC  57   male 26.88095         CRC
## 2 SAMD00115015  sub_10023     stool healthy  65   male 26.56250     Control
## 3 SAMD00115025  sub_10025     stool healthy  40   male 25.00000     Control
## 4 SAMD00115027  sub_10029     stool healthy  67 female 20.17325     Control
## 5 SAMD00114971  sub_10031     stool healthy  77   male 24.46460     Control
## 6 SAMD00114972  sub_10033     stool     CRC  54 female 24.74745         CRC
mydata %>%
  dplyr::count(case_status)
##   case_status   n
## 1     Control 245
## 2         CRC 258


#building ps object
keep_id <- mydata$sample_id

crc_se_sp <- sampleMetadata %>%
  dplyr::filter(sample_id %in% keep_id) %>%
  curatedMetagenomicData::returnSamples("relative_abundance", counts = TRUE)

crc_sp_df <- as.data.frame(as.matrix(assay(crc_se_sp)))

crc_sp_df <- crc_sp_df %>%
  rownames_to_column(var = "Species")

otu_tab <- crc_sp_df %>%
  tidyr::separate(Species, into = c("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species"), sep = "\\|") %>%
  dplyr::select(-Kingdom, -Phylum, -Class, -Order, -Family, -Genus) %>%
  dplyr::mutate(Species = gsub("s__", "", Species)) %>%
  tibble::column_to_rownames(var = "Species")

tax_tab <- crc_sp_df %>%
  dplyr::select(Species) %>%
  tidyr::separate(Species, into = c("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species"), sep = "\\|") %>%
  dplyr::mutate(Kingdom = gsub("k__", "", Kingdom),
                Phylum = gsub("p__", "", Phylum),
                Class = gsub("c__", "", Class),
                Order = gsub("o__", "", Order),
                Family = gsub("f__", "", Family),
                Genus = gsub("g__", "", Genus),
                Species = gsub("s__", "", Species)) %>%
  dplyr::mutate(spec_row = Species) %>%
  tibble::column_to_rownames(var = "spec_row")

rownames(mydata) <- NULL
mydata <- mydata %>%
  tibble::column_to_rownames(var = "sample_id")

(ps <- phyloseq(sample_data(mydata),
                otu_table(otu_tab, taxa_are_rows = TRUE),
                tax_table(as.matrix(tax_tab))))
## phyloseq-class experiment-level object
## otu_table()   OTU Table:         [ 717 taxa and 503 samples ]
## sample_data() Sample Data:       [ 503 samples by 7 sample variables ]
## tax_table()   Taxonomy Table:    [ 717 taxa by 7 taxonomic ranks ]
head(sample_data(ps))
##              subject_id body_site disease age gender      BMI case_status
## SAMD00115014  sub_10021     stool     CRC  57   male 26.88095         CRC
## SAMD00115015  sub_10023     stool healthy  65   male 26.56250     Control
## SAMD00115025  sub_10025     stool healthy  40   male 25.00000     Control
## SAMD00115027  sub_10029     stool healthy  67 female 20.17325     Control
## SAMD00114971  sub_10031     stool healthy  77   male 24.46460     Control
## SAMD00114972  sub_10033     stool     CRC  54 female 24.74745         CRC
table(sample_data(ps)$case_status)
## 
## Control     CRC 
##     245     258
rm(list= ls()[!(ls() %in% c("ps"))])


So you can see that we end up with a phyloseq object with 717 taxa from 503 samples of which n=258 samples are from participants with colorectal cancer and n=245 from otherwise healthy controls. We also have metadata on their age, sex, and BMI that we will include as model covariates (recognizing that this adjustment set is likely insufficient to fully account for confounding bias).


Performing DA testing with Maaslin2

Now that we have the data formatted for analysis, we can perform the DA testing using the popular Maaslin2 package with the default settings (as recommended here).

The second code chunk just cleans up the output a bit and recalculates the FDR p-value (q-value) given we are only interested in the results for case status.

m1 <- Maaslin2(
  input_data = data.frame(otu_table(ps)),
  input_metadata = data.frame(sample_data(ps)),
  output = "/Users/olljt2/desktop/maaslin2_output",
  min_abundance = 0.0,
  min_prevalence = 0.3,
  normalization = "TSS",
  transform = "LOG",
  analysis_method = "LM",
  fixed_effects = c("age", "gender", "BMI", "case_status"),
  correction = "BH",
  standardize = FALSE,
  cores = 1,
  plot_heatmap = FALSE,
  plot_scatter = FALSE
)
mas_res <- m1$results %>%
  dplyr::filter(metadata == "case_status") %>%
  dplyr::select(-qval, -name, -metadata, -N, -N.not.zero) %>%
  dplyr::arrange(pval) %>%
  dplyr::mutate(qval = p.adjust(pval, method = "fdr"))

head(mas_res)
##                         feature value       coef    stderr         pval
## 1       Collinsella_aerofaciens   CRC  1.7494259 0.4378328 7.426711e-05
## 2              Roseburia_faecis   CRC -1.7557381 0.4537057 1.234242e-04
## 3         Holdemania_filiformis   CRC  1.1438006 0.3149170 3.102965e-04
## 4  Bacteroides_cellulosilyticus   CRC  1.6620121 0.4732434 4.852776e-04
## 5 Streptococcus_anginosus_group   CRC  0.8570658 0.2608184 1.087633e-03
## 6        Mogibacterium_diversum   CRC  0.9275163 0.2956485 1.806511e-03
##          qval
## 1 0.006603197
## 2 0.006603197
## 3 0.011067240
## 4 0.012981175
## 5 0.023275341
## 6 0.026898454


So based on these results, and assuming ranking these species on the FDR p-value is the goal, one might declare Collinsella aerofaciens the winner and celebrate victory! We also see several other species with low FDR p-values (go ahead and print the whole list to see the results for all taxa).

Most of the time we stop here and report the results. However, these results alone do not give us a good idea as to whether we might expect to achieve a similar ranking if we were to perform the same analysis in some new set of patient samples drawn from the “same source population”.

Bootstrap resampling can help give us some idea of the possible uncertainty in these rankings by treating the sample as if it were the population of interest and repeatedly sampling from it.


Bootstrap resampling and ranking

The first function below takes an existing phyloseq object as input, samples the data with replacement, and returns a single bootstrap resample as output. Combined with the replicate function, we can use this function to generate many bootstrap resamples (here n=500), and combine them all in a single list that we can then iterate over.

The second function fits the default Maaslin2 algorithm and ranks the results on the FDR p-values and effect sizes (i.e., coefficient values). We can then use the lapply function to apply the fucntion to the list of ps objects.

We then combine the results across the 500 resampled ps objects into a single data.frame so that we can operate on it to pull out the information of interest.

This will take a bit of time to run (~10 min on my laptop). I used the future package to allow the boostraping to operate in parallel. I did not for the model fitting.

ps_boot <- function(ps_object = ps){
  ps_ids <- sample_names(ps_object)
  boot_ids <- sample(ps_ids, size = length(ps_ids), replace = TRUE)
  boot_df <- data.frame(seq_id = boot_ids)
  
  
  meta_df <- data.frame(sample_data(ps_object)) %>%
    rownames_to_column(var = "seq_id")
  
  join_df <- left_join(boot_df, meta_df)
  
  
  otu_df <- data.frame(t(otu_table(ps_object))) %>%
    rownames_to_column(var = "seq_id")
  
  join_otu <- left_join(boot_df, otu_df)
  
  
  join_df <- join_df %>%
    mutate(boot_id = paste("S", row_number(), sep = "")) %>%
    dplyr::select(-seq_id) %>%
    column_to_rownames(var = "boot_id")
  
  join_otu <- join_otu %>%
    mutate(boot_id = paste("S", row_number(), sep = "")) %>%
    dplyr::select(-seq_id) %>%
    column_to_rownames(var = "boot_id") %>%
    t() %>%
    data.frame()
  
  boot_ps <- phyloseq(sample_data(join_df),
                      otu_table(join_otu, taxa_are_rows = TRUE))
  
  return(boot_ps)
  
}

get_ranks <- function(ps_object){
  m <- Maaslin2(
    input_data = data.frame(otu_table(ps_object)),
    input_metadata = data.frame(sample_data(ps_object)),
    output = "/Users/olljt2/desktop/maaslin2_output",
    min_abundance = 0.0,
    min_prevalence = 0.3,
    normalization = "TSS",
    transform = "LOG",
    analysis_method = "LM",
    fixed_effects = c("age", "gender", "BMI", "case_status"),
    correction = "BH",
    standardize = FALSE,
    cores = 1,
    plot_heatmap = FALSE,
    plot_scatter = FALSE)

  mas_out <- m$results %>%
    dplyr::filter(metadata == "case_status") %>%
    dplyr::select(-qval, -name, -metadata, -N, -N.not.zero) %>%
    dplyr::arrange(pval) %>%
    dplyr::mutate(qval = p.adjust(pval, method = "fdr"),
                  p_rank = rank(pval)) %>%
    dplyr::arrange(coef) %>%
    dplyr::mutate(coef_rank = rank(coef))
  
  return(mas_out)
}

availableCores()
plan(multisession)

boot_ps_list <- future_replicate(n = 500, expr = ps_boot(), simplify = FALSE, future.seed = 243)
boot_ranks <- lapply(boot_ps_list, get_ranks)
boot_ranks_df <- bind_rows(boot_ranks, .id = "column_label")
saveRDS(boot_ranks_df, "/users/olljt2/desktop/boot_ranks_df.rds")
head(boot_ranks_df)
##   column_label                   feature value      coef    stderr         pval
## 1            1 Lachnospira_pectinoschiza   CRC -2.680995 0.5284147 5.518097e-07
## 2            1          Roseburia_faecis   CRC -2.531921 0.4476999 2.623861e-08
## 3            1       Eubacterium_eligens   CRC -2.320282 0.4878747 2.591016e-06
## 4            1         Bacteroides_dorei   CRC -1.873865 0.5887274 1.549179e-03
## 5            1    Roseburia_intestinalis   CRC -1.869300 0.4933628 1.698289e-04
## 6            1  Streptococcus_salivarius   CRC -1.441700 0.3523627 4.998012e-05
##           qval p_rank coef_rank
## 1 2.924592e-05      2         1
## 2 2.781293e-06      1         2
## 3 9.154924e-05      3         3
## 4 1.172950e-02     14         4
## 5 2.250233e-03      8         5
## 6 9.447558e-04      5         6

So we can see that we now have a data.frame that contains the rankings of interest and an indicator for the resample from which the results were obtained.


Merge rank info

Here we compute the 95% CI for the ranks, as well as the highest and lowest rank for each species and merge it to the Maaslin2 results.

ranks_df <- boot_ranks_df %>% 
  group_by(feature) %>% 
  summarise(lcl_p_rank = round(quantile(p_rank, prob = (0.025)), 0),
            ucl_p_rank = round(quantile(p_rank, prob = (0.975)), 0),
            min_p_rank = min(p_rank),
            max_p_rank = max(p_rank),
            p_rank_95_ci = paste("(", lcl_p_rank, "; ", ucl_p_rank, ")", sep = "")) %>%
  dplyr::select(feature, p_rank_95_ci, min_p_rank, max_p_rank) %>%
  dplyr::ungroup()

mas_res_ranks <- dplyr::left_join(mas_res, ranks_df)
## Joining, by = "feature"


Examine the rankings

head(mas_res_ranks)
##                         feature value       coef    stderr         pval
## 1       Collinsella_aerofaciens   CRC  1.7494259 0.4378328 7.426711e-05
## 2              Roseburia_faecis   CRC -1.7557381 0.4537057 1.234242e-04
## 3         Holdemania_filiformis   CRC  1.1438006 0.3149170 3.102965e-04
## 4  Bacteroides_cellulosilyticus   CRC  1.6620121 0.4732434 4.852776e-04
## 5 Streptococcus_anginosus_group   CRC  0.8570658 0.2608184 1.087633e-03
## 6        Mogibacterium_diversum   CRC  0.9275163 0.2956485 1.806511e-03
##          qval p_rank_95_ci min_p_rank max_p_rank
## 1 0.006603197      (1; 34)          1         77
## 2 0.006603197      (1; 36)          1         69
## 3 0.011067240      (1; 42)          1         82
## 4 0.012981175      (1; 46)          1         70
## 5 0.023275341      (1; 53)          1        105
## 6 0.026898454      (1; 61)          1         96
c_aerofaciens <- boot_ranks_df %>%
  dplyr::filter(feature == "Collinsella_aerofaciens") %>%
  dplyr::select(p_rank)

Hmisc::describe(c_aerofaciens)
## c_aerofaciens 
## 
##  1  Variables      500  Observations
## --------------------------------------------------------------------------------
## p_rank 
##        n  missing distinct     Info     Mean      Gmd      .05      .10 
##      500        0       42     0.99     8.28    8.886     1.00     1.00 
##      .25      .50      .75      .90      .95 
##     2.00     5.00    11.00    20.00    26.05 
## 
## lowest :  1  2  3  4  5, highest: 50 54 55 75 77
## --------------------------------------------------------------------------------


We can see that there is quite a bit of variability in the 95% CI for the ranks of these top species including Collinsella aerofaciens.

From this, we might update our thinking as to whether this species may be expected to have the lowest FDR ranking if we tried this again in a new set of patient samples. It perhaps seems more plausible that it might be expected to fall somewhere around the top 30 or so. This is rather different, I think, than assuming that this is the “best hit” as based on these criteria and modeling approach etc. So depending where we set our thresholds for having “passed the screen”, we might have more or less enthusiasm for this species (or this DA testing approach in general). The ranks of these top species are quite similar (i.e., a lot of overlap), which is to be expected, since it is hard to pick true “winners”.

Go ahead and look at the distribution of rankings for all species. You will see that there is A LOT of overlap as you move further down the list highlighting the difficulty of the task we are trying to perform.

Let’s also plot the rankings for the top few species since I find this visualization often helpful. You may also want plot a few closer to the middle of the distribution just to see how disturbingly wide the variability can be (i.e., how the results could change from sample-to-sample).

ggplot(data = dplyr::filter(boot_ranks_df, feature == "Collinsella_aerofaciens"), aes(x = p_rank)) +
  geom_histogram(bins = 100, fill = "white", color = "dodgerblue") +
  labs(x = "\nRank", y = "Count\n")

my_taxa <- c("Collinsella_aerofaciens", "Roseburia_faecis", "Holdemania_filiformis",   
             "Bacteroides_cellulosilyticus", "Streptococcus_anginosus_group", "Mogibacterium_diversum")

ggplot(data = dplyr::filter(boot_ranks_df, feature %in% my_taxa), aes(x = p_rank, color = feature)) +
  geom_histogram(bins = 100) +
  labs(x = "\nRank", y = "Count\n") +
  facet_wrap(~feature) +
  theme(legend.position = "none")


Other quantities from the bootstrap

We can also obtain many other quantities via bootstrapping. Here is just an example of how one might obtain the bootstrap SE (not that we need to here per se).

boot_ranks_df %>%
  dplyr::filter(feature %in% my_taxa) %>%
  dplyr::group_by(feature) %>%
  dplyr::summarise(boot_se = sd(coef)) %>%
  dplyr::arrange(feature)
## # A tibble: 6 × 2
##   feature                       boot_se
##   <chr>                           <dbl>
## 1 Bacteroides_cellulosilyticus    0.452
## 2 Collinsella_aerofaciens         0.453
## 3 Holdemania_filiformis           0.335
## 4 Mogibacterium_diversum          0.284
## 5 Roseburia_faecis                0.442
## 6 Streptococcus_anginosus_group   0.263
head(mas_res) %>%
  arrange(feature)
##                         feature value       coef    stderr         pval
## 1  Bacteroides_cellulosilyticus   CRC  1.6620121 0.4732434 4.852776e-04
## 2       Collinsella_aerofaciens   CRC  1.7494259 0.4378328 7.426711e-05
## 3         Holdemania_filiformis   CRC  1.1438006 0.3149170 3.102965e-04
## 4        Mogibacterium_diversum   CRC  0.9275163 0.2956485 1.806511e-03
## 5              Roseburia_faecis   CRC -1.7557381 0.4537057 1.234242e-04
## 6 Streptococcus_anginosus_group   CRC  0.8570658 0.2608184 1.087633e-03
##          qval
## 1 0.012981175
## 2 0.006603197
## 3 0.011067240
## 4 0.026898454
## 5 0.006603197
## 6 0.023275341


The main point of this post was just to share some thoughts on how bootstrapping and ranking may help us to think about the difficulty of feature selection in high-dimensions when trying to ascertain microbiome features that differ most according to some condition of interest. Bootstrap ranking is not a panacea, and the rankings will correlate with the FDR p-values, but considering the distribution of ranks might help us to think more about the limits of what we hope to learn from a single, often under-powered, study.

In the next post, I will show an example of how we can apply a meta-analysis approach to microbiome data when we do have access to several studies focused on the same condition. You may be surprised to see how the results from a single study can differ from those obtained from pooled estimates.

All the good ideas here on bootstrap ranking come from Frank. Any errors in logic or programming are my own.


Session Info

sessionInfo()
## R version 4.1.1 (2021-08-10)
## Platform: x86_64-apple-darwin17.0 (64-bit)
## Running under: macOS Catalina 10.15.7
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRlapack.dylib
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## attached base packages:
## [1] parallel  stats4    stats     graphics  grDevices utils     datasets 
## [8] methods   base     
## 
## other attached packages:
##  [1] Hmisc_4.6-0                    Formula_1.2-4                 
##  [3] survival_3.2-11                lattice_0.20-44               
##  [5] future.apply_1.8.1             future_1.22.1                 
##  [7] Maaslin2_1.6.0                 phyloseq_1.36.0               
##  [9] forcats_0.5.1                  stringr_1.4.0                 
## [11] dplyr_1.0.7                    purrr_0.3.4                   
## [13] readr_2.0.2                    tidyr_1.1.4                   
## [15] tibble_3.1.5                   ggplot2_3.3.5                 
## [17] tidyverse_1.3.1                curatedMetagenomicData_3.0.10 
## [19] TreeSummarizedExperiment_2.0.3 Biostrings_2.60.2             
## [21] XVector_0.32.0                 SingleCellExperiment_1.14.1   
## [23] SummarizedExperiment_1.22.0    Biobase_2.52.0                
## [25] GenomicRanges_1.44.0           GenomeInfoDb_1.28.4           
## [27] IRanges_2.26.0                 S4Vectors_0.30.2              
## [29] BiocGenerics_0.38.0            MatrixGenerics_1.4.3          
## [31] matrixStats_0.61.0            
## 
## loaded via a namespace (and not attached):
##   [1] utf8_1.2.2                    tidyselect_1.1.1             
##   [3] RSQLite_2.2.8                 AnnotationDbi_1.54.1         
##   [5] htmlwidgets_1.5.4             grid_4.1.1                   
##   [7] BiocParallel_1.26.2           munsell_0.5.0                
##   [9] codetools_0.2-18              withr_2.4.2                  
##  [11] colorspace_2.0-2              filelock_1.0.2               
##  [13] highr_0.9                     knitr_1.36                   
##  [15] rstudioapi_0.13               robustbase_0.93-9            
##  [17] listenv_0.8.0                 labeling_0.4.2               
##  [19] optparse_1.7.1                GenomeInfoDbData_1.2.6       
##  [21] lpsymphony_1.20.0             bit64_4.0.5                  
##  [23] farver_2.1.0                  rhdf5_2.36.0                 
##  [25] parallelly_1.28.1             vctrs_0.3.8                  
##  [27] treeio_1.16.2                 generics_0.1.1               
##  [29] xfun_0.29                     BiocFileCache_2.0.0          
##  [31] R6_2.5.1                      bitops_1.0-7                 
##  [33] rhdf5filters_1.4.0            cachem_1.0.6                 
##  [35] DelayedArray_0.18.0           assertthat_0.2.1             
##  [37] promises_1.2.0.1              scales_1.1.1                 
##  [39] nnet_7.3-16                   gtable_0.3.0                 
##  [41] globals_0.14.0                rlang_0.4.12                 
##  [43] splines_4.1.1                 lazyeval_0.2.2               
##  [45] broom_0.7.9                   checkmate_2.0.0              
##  [47] BiocManager_1.30.16           yaml_2.2.1                   
##  [49] reshape2_1.4.4                modelr_0.1.8                 
##  [51] backports_1.2.1               httpuv_1.6.3                 
##  [53] tools_4.1.1                   bookdown_0.24                
##  [55] logging_0.10-108              ellipsis_0.3.2               
##  [57] jquerylib_0.1.4               biomformat_1.20.0            
##  [59] RColorBrewer_1.1-2            Rcpp_1.0.7                   
##  [61] hash_2.2.6.1                  plyr_1.8.6                   
##  [63] base64enc_0.1-3               zlibbioc_1.38.0              
##  [65] RCurl_1.98-1.5                rpart_4.1-15                 
##  [67] pbapply_1.5-0                 haven_2.4.3                  
##  [69] cluster_2.1.2                 fs_1.5.0                     
##  [71] magrittr_2.0.1                data.table_1.14.2            
##  [73] blogdown_1.7                  reprex_2.0.1                 
##  [75] mvtnorm_1.1-3                 hms_1.1.1                    
##  [77] mime_0.12                     evaluate_0.14                
##  [79] xtable_1.8-4                  jpeg_0.1-9                   
##  [81] readxl_1.3.1                  gridExtra_2.3                
##  [83] compiler_4.1.1                crayon_1.4.1                 
##  [85] htmltools_0.5.2               mgcv_1.8-36                  
##  [87] pcaPP_1.9-74                  later_1.3.0                  
##  [89] tzdb_0.1.2                    lubridate_1.8.0              
##  [91] DBI_1.1.1                     ExperimentHub_2.0.0          
##  [93] biglm_0.9-2.1                 dbplyr_2.1.1                 
##  [95] MASS_7.3-54                   rappdirs_0.3.3               
##  [97] Matrix_1.3-4                  ade4_1.7-18                  
##  [99] getopt_1.20.3                 permute_0.9-5                
## [101] cli_3.1.0                     igraph_1.2.7                 
## [103] pkgconfig_2.0.3               foreign_0.8-81               
## [105] xml2_1.3.2                    foreach_1.5.1                
## [107] bslib_0.3.1                   multtest_2.48.0              
## [109] rvest_1.0.2                   digest_0.6.28                
## [111] vegan_2.5-7                   rmarkdown_2.11               
## [113] cellranger_1.1.0              tidytree_0.3.5               
## [115] htmlTable_2.3.0               curl_4.3.2                   
## [117] shiny_1.7.1                   lifecycle_1.0.1              
## [119] nlme_3.1-152                  jsonlite_1.7.2               
## [121] Rhdf5lib_1.14.2               fansi_0.5.0                  
## [123] pillar_1.6.4                  KEGGREST_1.32.0              
## [125] fastmap_1.1.0                 httr_1.4.2                   
## [127] DEoptimR_1.0-9                interactiveDisplayBase_1.30.0
## [129] glue_1.4.2                    png_0.1-7                    
## [131] iterators_1.0.13              BiocVersion_3.13.1           
## [133] bit_4.0.4                     stringi_1.7.5                
## [135] sass_0.4.0                    blob_1.2.2                   
## [137] AnnotationHub_3.0.2           latticeExtra_0.6-29          
## [139] memoise_2.0.0                 ape_5.5
Associate Professor of Pediatrics

Related