--- title: "Using microbiome with TreeSummarizedExperiment" author: "Leo Lahti, Sudarshan Shetty, et al." bibliography: - bibliography.bib date: "`r Sys.Date()`" output: BiocStyle::html_document: toc: true fig_caption: yes vignette: > %\VignetteIndexEntry{microbiome with TreeSummarizedExperiment} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, echo=FALSE} knitr::opts_chunk$set(fig.width=7, fig.height=5, message=FALSE, warning=FALSE) # mia and TreeSummarizedExperiment are in Suggests, so show the code but # do not evaluate it when they are missing. tse.available <- requireNamespace("mia", quietly=TRUE) && requireNamespace("TreeSummarizedExperiment", quietly=TRUE) && requireNamespace("SummarizedExperiment", quietly=TRUE) knitr::opts_chunk$set(eval=tse.available) ``` # Introduction We recommend switching from phyloseq to the `TreeSummarizedExperiment` based methods described in the [Orchestrating Microbiome Analysis (OMA)](https://bioconductor.org/books/release/OMA/) online book, which is where method development now takes place. The [mia](https://bioconductor.org/packages/mia) package provides the analysis methods for that container. To ease the transition, most functions in this package accept a `TreeSummarizedExperiment` (or any other `SummarizedExperiment`-derived object) wherever they accept a `phyloseq` object. This vignette walks through the standard operations on a `TreeSummarizedExperiment`. The companion vignette, `vignette("vignette", package="microbiome")`, gives the general introduction to the package. Almost everything is unchanged: the same function called on a `TreeSummarizedExperiment` returns the same value it would return for the equivalent `phyloseq` object. There are two things to know before starting, both covered in their own sections below: * `transform()` stores its result as a **new named assay** rather than overwriting the counts, so the result is read back with `abundances(x, assay.type=)`. * The functions that manipulate the **taxonomy table** remain phyloseq only. ```{r loading} library(microbiome) ``` ## A note on masking Attaching `mia` also attaches its dependencies, two of which export functions that share a name with a `microbiome` function. Since they are attached later, they take precedence: * `IRanges::transform()` masks `microbiome::transform()` * `Biostrings::coverage()` masks `microbiome::coverage()` Neither raises an error when called on a microbiome object, so the symptom is a silently wrong result rather than a failure. Call these two with their package prefix whenever `mia` is attached, as this vignette does throughout: ```{r masking, eval=FALSE} microbiome::transform(tse, "compositional") microbiome::coverage(tse) ``` The rest of the package is unaffected. # Getting a TreeSummarizedExperiment `mia` converts an existing `phyloseq` object. Here we use the `dietswap` data set [@OKeefe15] that ships with this package. ```{r convert} library(mia) data(dietswap) tse <- convertFromPhyloseq(dietswap) tse ``` `mia` also reads the common file formats directly, with `importBiom()`, `importMothur()`, `importQIIME2()` and others, so a conversion step is not needed for new analyses. See the [OMA book](https://bioconductor.org/books/release/OMA/) for those. # Accessors `abundances()`, `meta()` and `taxa()` are the three accessors the rest of the package is built on. They work the same way on both containers, returning the abundance matrix, the sample metadata and the feature names. ```{r accessors} a <- abundances(tse) dim(a) a[seq_len(4), seq_len(3)] head(meta(tse), 3) head(taxa(tse), 5) ``` For a `TreeSummarizedExperiment` these read the assay, `colData` and `rownames` respectively. The number of features and samples comes from the object itself. ```{r dims} c(features=nrow(tse), samples=ncol(tse)) ``` ## Choosing the assay A `SummarizedExperiment` can hold several assays at once. `abundances()` takes an `assay.type` argument to pick between them. It defaults to the `counts` assay when one is present, and otherwise to the first assay. ```{r assaynames} assayNames(tse) ``` ```{r assaytype} identical(abundances(tse), abundances(tse, assay.type="counts")) ``` Asking for an assay that does not exist is an error rather than a silent fallback: ```{r assaytype-error, error=TRUE} abundances(tse, assay.type="nonexistent") ``` # Transformations This is the one place where the result differs from the phyloseq version, and it is deliberate. For a `phyloseq` object, `transform()` overwrites the abundance table. For a `SummarizedExperiment`, transforming is *additive*: the result is stored as a new assay named after the transformation, and the original counts are left untouched. This follows the `mia::transformAssay()` convention. ```{r transform} tse <- microbiome::transform(tse, "compositional") assayNames(tse) ``` The transformed values are read back by naming the assay: ```{r transform-read} abundances(tse, assay.type="compositional")[seq_len(4), seq_len(3)] ``` The counts are still there, unchanged: ```{r transform-counts} abundances(tse)[seq_len(4), seq_len(3)] ``` Use the `name` argument to control the assay name, for instance to follow the `mia` naming convention: ```{r transform-name} tse <- microbiome::transform(tse, "compositional", name="relabundance") assayNames(tse) ``` Transformations chain, since each one adds an assay: ```{r transform-chain} tse <- microbiome::transform(tse, "clr") tse <- microbiome::transform(tse, "Z") assayNames(tse) ``` When you only need the transformed matrix and not a modified object, pass `transform` to `abundances()` directly. This never round-trips through the object and works identically for both containers: ```{r transform-abundances} clr <- abundances(tse, transform="clr") clr[seq_len(4), seq_len(3)] ``` # Alpha diversity and related indices These take the object and return a per-sample data frame or vector, as they do for phyloseq. ```{r alpha} head(alpha(tse, index=c("shannon", "gini_simpson")), 3) ``` ```{r indices} head(richness(tse), 3) head(microbiome::diversity(tse, index="shannon"), 3) head(evenness(tse, index="pielou"), 3) head(dominance(tse, index="absolute"), 3) head(rarity(tse, index="log_modulo_skewness"), 3) ``` The most abundant taxon per sample, and the overall top taxa: ```{r dominant} head(dominant(tse), 3) top_taxa(tse, n=5) ``` Divergence from a reference sample, here the median across samples: ```{r divergence} ref <- apply(abundances(tse), 1, median) head(divergence(tse, ref, method="bray"), 3) ``` Sample-wise summaries: ```{r summaries} head(readcount(tse), 3) head(microbiome::coverage(tse), 3) head(low_abundance(tse), 3) ``` # Core microbiota and prevalence ```{r prevalence} head(prevalence(tse, detection=1, sort=TRUE), 5) ``` `core_members()` lists the taxa passing a detection and prevalence threshold, and `core()` returns the object filtered down to them. ```{r core-members} core_members(tse, detection=1, prevalence=50/100) ``` ```{r core} tse.core <- core(tse, detection=1, prevalence=50/100) c(features=nrow(tse.core), samples=ncol(tse.core)) ``` Subsetting preserves the class, so the result is a `TreeSummarizedExperiment` that can be fed back into any of these functions: ```{r core-class} class(tse.core) ``` `rare()` and `rare_members()` are the complements, and `core_abundance()` gives the fraction of the community the core accounts for: ```{r rare} head(core_abundance(tse, detection=1, prevalence=50/100), 3) length(rare_members(tse, detection=1, prevalence=50/100)) ``` # Subsetting `remove_taxa()` and `remove_samples()` drop features and samples by name, again returning the same class they were given. ```{r remove} drop <- taxa(tse)[seq_len(5)] tse.sub <- remove_taxa(drop, tse) c(before=nrow(tse), after=nrow(tse.sub)) ``` ```{r remove-samples} drop.s <- colnames(tse)[seq_len(5)] tse.sub <- remove_samples(drop.s, tse) c(before=ncol(tse), after=ncol(tse.sub)) ``` # Visualization The plotting functions that go through the accessors take a `TreeSummarizedExperiment` and return a `ggplot` object as usual. ```{r plot-core, fig.cap="Core microbiota heatmap."} plot_core(tse, prevalences=seq(.1, 1, .2), detections=3) ``` ```{r plot-boxplot, fig.cap="Abundance of a single taxon across groups."} boxplot_abundance(tse, x="nationality", y=taxa(tse)[[1]]) ``` ```{r plot-alpha, fig.cap="Alpha diversity across groups."} boxplot_alpha(tse, x_var="nationality", index="shannon") ``` ```{r plot-density, fig.cap="Abundance density for a single taxon."} plot_density(tse, variable=taxa(tse)[[1]]) ``` `plot_atlas()` and `plot_tipping()` accept a `TreeSummarizedExperiment` in the same way. # Time series Given a `time` field in the sample metadata, the time series helpers work as they do for phyloseq. `time_normalize()` writes back into the object and so returns a `TreeSummarizedExperiment`. ```{r time} tse.t <- tse tse.t$time <- tse.t$timepoint length(timesplit(tse.t)) head(time_sort(tse.t)$time, 3) class(time_normalize(tse.t)) ``` `baseline()` and `collapse_replicates()` likewise accept and return the same container. # What is not supported The functions that build or manipulate the **taxonomy table** are phyloseq only, and error on a `TreeSummarizedExperiment`: * `aggregate_taxa()`, `aggregate_rare()` * `plot_composition()` * `map_levels()` * `psmelt2()`, `otu_tibble()`, `tax_tibble()`, `sample_tibble()` * `add_besthit()`, `add_refseq()` * the `read_*()` family `mia` covers this ground for `TreeSummarizedExperiment` objects, and does it better: use `mia::agglomerateByRank()` in place of `aggregate_taxa()`, `mia::meltSE()` in place of `psmelt2()`, and the [miaViz](https://bioconductor.org/packages/miaViz) package for composition plots. The [OMA book](https://bioconductor.org/books/release/OMA/) documents these. # Session info ```{r sessioninfo, eval=TRUE} sessionInfo() ``` # References