diff --git a/DESCRIPTION b/DESCRIPTION index ba2ee4819..d5b87261f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -118,6 +118,6 @@ LinkingTo: URL: https://microbiome.github.io/mia/, https://github.com/microbiome/mia BugReports: https://github.com/microbiome/mia/issues Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.3 VignetteBuilder: knitr +Config/roxygen2/version: 8.0.0 RdMacros: Rdpack diff --git a/NAMESPACE b/NAMESPACE index de02576c9..b98f369ed 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -60,6 +60,9 @@ export(estimateDominance) export(estimateEvenness) export(estimateFaith) export(estimateRichness) +export(exportMothur) +export(exportQIIME2) +export(exportRaw) export(filterRPCAInput) export(full_join) export(getAbundanceClass) @@ -421,6 +424,8 @@ importFrom(ape,is.binary) importFrom(ape,is.rooted) importFrom(ape,read.tree) importFrom(ape,reorder.phylo) +importFrom(ape,write.FASTA) +importFrom(ape,write.tree) importFrom(bluster,clusterRows) importFrom(decontam,isContaminant) importFrom(decontam,isNotContaminant) @@ -464,9 +469,11 @@ importFrom(stats,runif) importFrom(stats,sd) importFrom(stats,setNames) importFrom(stats,terms) +importFrom(stringr,fixed) importFrom(stringr,str_extract) importFrom(stringr,str_extract_all) importFrom(stringr,str_pad) +importFrom(stringr,str_remove) importFrom(stringr,str_replace_all) importFrom(tibble,rownames_to_column) importFrom(tibble,tibble) diff --git a/NEWS b/NEWS index 23a4d046c..350aa8a1e 100644 --- a/NEWS +++ b/NEWS @@ -190,8 +190,8 @@ Changes in version 1.19.x Changes in version 1.21.x + Fixed explained percentages on PCA axes for jointRPCA and hardened rank-deficient SVD handling in joint-RPCA (1.21.1, 2026-05-12) -* Generalise agglomerateByModule to non-binary numerical modules (1.21.2, 2026-05-31) -* Modify joint-RPCA to match with Gemelli implementation (1.21.3, 2026-06-16) ++ Generalise agglomerateByModule to non-binary numerical modules (1.21.2, 2026-05-31) ++ Modify joint-RPCA to match with Gemelli implementation (1.21.3, 2026-06-16) + Implement Striped Unifrac algorithm (1.21.4, 2026-07-29) + Added conversion functions between MAE and TreeSE (1.21.8, 2026-09-22) + Added applyByModule (1.21.11, 2026-10-02) diff --git a/R/AllGenerics.R b/R/AllGenerics.R index 6a6a29aec..c4888b705 100644 --- a/R/AllGenerics.R +++ b/R/AllGenerics.R @@ -105,6 +105,21 @@ setGeneric("convertToBIOM", signature = c("x"), setGeneric("convertToPhyloseq", signature = c("x"), function(x, ...) standardGeneric("convertToPhyloseq")) +#' @rdname export-methods +#' @export +setGeneric("exportRaw", signature = c("x"), function(x, ...) + standardGeneric("exportRaw")) + +#' @rdname export-methods +#' @export +setGeneric("exportQIIME2", signature = c("x"), function(x, ...) + standardGeneric("exportQIIME2")) + +#' @rdname export-methods +#' @export +setGeneric("exportMothur", signature = c("x"), function(x, ...) + standardGeneric("exportMothur")) + #' @rdname isContaminant #' @export setGeneric("addContaminantQC", signature = c("x"), diff --git a/R/calculateUnifrac.R b/R/calculateUnifrac.R index 9207bb370..bb9320cd5 100644 --- a/R/calculateUnifrac.R +++ b/R/calculateUnifrac.R @@ -1,6 +1,6 @@ #' @importFrom ape drop.tip .get_unifrac <- function( - x, tree, weighted = FALSE, node.label = nodeLab, nodeLab = NULL, ...){ + x, tree, new = FALSE, weighted = FALSE, node.label = nodeLab, nodeLab = NULL, ...){ # Transpose the matrix so that the orientation is the same as in other # dissimilatity methods x <- t(x) diff --git a/R/exporters.R b/R/exporters.R new file mode 100644 index 000000000..efc219116 --- /dev/null +++ b/R/exporters.R @@ -0,0 +1,368 @@ +#' Exporters to common formats for microbiome data outside of R +#' +#' @name export-methods +#' @aliases exportRaw exportQIIME2 exportMothur +#' +#' @description +#' There are a few very popular external tools for microbiome analysis, +#' including QIIME2 and mothur. However, R does not currently provide any class +#' to accommodate those data formats. When exporting data from mia to external +#' tools, the best approach is therefore to break a data container into its +#' building blocks (assays, side information, trees, etc.). +#' +#' Thanks to \code{exportRaw}, \code{exportQIIME2} and \code{exportMothur}, +#' it is now possible to export a +#' \code{\link[TreeSummarizedExperiment]{TreeSummarizedExperiment}} object as +#' raw elements or near-ready QIIME2 and mothur formats, respectively. This way, +#' migrating from mia to an external system is still a bad idea, but at least it +#' is fairly straightforward. +#' +#' @param x a \code{\link[TreeSummarizedExperiment]{TreeSummarizedExperiment}} +#' object. +#' +#' @param dpath \code{Character scalar}. String specifying the directory where +#' \code{x} should be exported. If non-existent, it is created recursively. +#' +#' @param rowdata.file \code{Character scalar}. String specifying the file where +#' rowData should be written. (Default: \code{"rowdata"}) +#' +#' @param coldata.file \code{Character scalar}. String specifying the file where +#' colData should be written. (Default: \code{"coldata"}) +#' +#' @param refseq.file \code{Character scalar}. String specifying the file where +#' referenceSeq should be written. (Default: \code{"seqs"}) +#' +#' @param assay.dir \code{Character scalar}. String specifying the directory +#' where assays should be written. (Default: \code{"assays"}) +#' +#' @param rowtree.dir \code{Character scalar}. String specifying the directory +#' where rowTrees should be written. (Default: \code{"row_trees"}) +#' +#' @param coltree.dir \code{Character scalar}. String specifying the directory +#' where colTrees should be written. (Default: \code{"col_trees"}) +#' +#' @param dimred.dir \code{Character scalar}. String specifying the directory +#' where reducedDims should be written. (Default: \code{"dim_reds"}) +#' +#' @param altexp.dir \code{Character scalar}. String specifying the directory +#' where altExps should be written. (Default: \code{"alt_exps"}) +#' +#' @param assay.type \code{Character scalar}. String specifying which assay +#' should be exported. Only for \code{exportQIIME2} and \code{exportMothur}. +#' (Default: \code{"counts"}) +#' +#' @param tree.name \code{Character scalar}. String specifying which row tree +#' should be exported. Only for \code{exportQIIME2} and \code{exportMothur}. +#' (Default: \code{"phylo"}) +#' +#' @param group.var \code{Character scalar}. String specifying one variable of +#' rowData to export as a custom grouping file. Only for \code{exportQIIME2} +#' and \code{exportMothur}. (Default: \code{NULL}) +#' +#' @param ... Unused. +#' +#' @returns Directory at \code{dpath} with components of \code{x} each stored +#' as a file in the proper format. +#' +#' @details The output directory contains the elements of \code{x}. For +#' \code{exportQIIME2} and \code{exportMothur}, data will need some more +#' processing using the target tool. For some tips, check the rbiom package +#' vignettes on converting data: +#' \url{https://cmmr.github.io/rbiom/articles/convert.html} +#' +#' @examples +#' library(TreeSummarizedExperiment) +#' +#' # Generate example TreeSE object +#' tse <- makeTSE() +#' # Give name to counts assay +#' assayNames(tse) <- "counts" +#' # Give name to taxonomy variable +#' names(rowData(tse))[1] <- "Genus" +#' +#' # Export raw TreeSE components in custom directory +#' exportRaw(tse, "out") +#' +#' # Export TreeSE components in near-ready QIIME2 format +#' exportQIIME2(tse, "qiime2_dir") +#' +#' # Export TreeSE components in near-ready mothur format +#' exportMothur(tse, "mothur_dir") +NULL + + +#' @rdname export-methods +#' @importFrom ape write.tree write.FASTA +setMethod("exportRaw", signature = c(x = "TreeSummarizedExperiment"), + function(x, dpath, rowdata.file = "rowdata", coldata.file = "coldata", + refseq.file = "seqs", assay.dir = "assays", rowtree.dir = "row_trees", + coltree.dir = "col_trees", dimred.dir = "dim_reds", + altexp.dir = "alt_exps"){ + # Create export directory + dpath <- .create_export_dir(dpath) + # Write row data + write.table(rowData(x), paste0(dpath, rowdata.file, ".tsv"), sep = "\t") + # Write col data + write.table(colData(x), paste0(dpath, coldata.file, ".tsv"), sep = "\t") + # Create directory for assays + assay.dir <- .create_slot_dir(x, assays, assay.dir, dpath) + # For each assay + for( assay_name in assayNames(x) ){ + # Write assay + write.table( + assay(x, assay_name), + paste0(assay.dir, assay_name, ".tsv"), + sep = "\t" + ) + } + # Create directory for row trees + rowtree.dir <- .create_slot_dir(x, rowTreeNames, rowtree.dir, dpath) + # For each row tree + for( tree_name in rowTreeNames(x) ){ + # Write row tree + write.tree( + rowTree(x, tree_name), paste0(rowtree.dir, tree_name, ".nwk") + ) + } + # Create directory for col trees + coltree.dir <- .create_slot_dir(x, colTreeNames, coltree.dir, dpath) + # For each col tree + for( tree_name in colTreeNames(x) ){ + # Write col tree + write.tree( + colTree(x, tree_name), paste0(coltree.dir, tree_name, ".nwk") + ) + } + # Create directory for reduced dimensions + dimred.dir <- .create_slot_dir(x, reducedDims, dimred.dir, dpath) + # For each reduced dimension + for( dimred_name in reducedDimNames(x) ){ + # Write reduced dimension + write.table( + reducedDim(x, dimred_name), + paste0(dimred.dir, dimred_name, ".tsv"), + sep = "\t" + ) + } + # Create directory for alternative experiments + altexp.dir <- .create_slot_dir(x, altExps, altexp.dir, dpath) + # For each alternative experiment + for( altexp_name in altExpNames(x) ){ + # Build path to experiment + altexp_path <- paste0(altexp.dir, altexp_name) + # If instance of SE + if( is(altExp(x, altexp_name), "SummarizedExperiment") ){ + # Apply exporter recursively + exportToRaw(altExp(x, altexp_name), altexp_path) + }else{ + # Write simple assay + write.table(altExp(x, altexp_name), altexp_path, sep = "\t") + } + } + # Write reference sequence + .write_referenceSeq(x, dpath, refseq.file) + invisible(NULL) +}) + +# Define function to create main export directory +.create_export_dir <- function(dpath){ + # Add final slash to path if absent + if( !endsWith(dpath, "/") ) dpath <- paste0(dpath, "/") + # Create export directory + if( !dir.exists(dpath) ) dir.create(dpath, recursive = TRUE) + return(dpath) +} + +# Define function to create directories for TreeSE slots +.create_slot_dir <- function(x, FUN, slot.path, main.path = ""){ + # If slot contains elements + if( length(FUN(x)) != 0L ){ + # Build path to slot + slot.path <- paste0(main.path, slot.path) + # Add final slash if absent + if( !endsWith(slot.path, "/") ) slot.path <- paste0(slot.path, "/") + # Create directory for slot + dir.create(slot.path) + } + return(slot.path) +} + + +#' @rdname export-methods +#' @importFrom ape write.tree write.FASTA +setMethod("exportQIIME2", signature = c(x = "TreeSummarizedExperiment"), + function(x, dpath, assay.type = "counts", tree.name = "phylo", + group.var = NULL){ + # Check for forbidden colnames + if( "sample-id" %in% colnames(colData(x)) ){ + warning( + "'sample-id' is a reserved name in QIIME2. colData variables with ", + "that name were made unique.", call. = FALSE + ) + } + # Create export directory + dpath <- .create_export_dir(dpath) + # Write file with custom grouping variable + .write_group_file(x, dpath, group.var, file.format = "tsv") + # Concatenate taxonomic ranks feature-wise + row_data <- .collapse_taxranks(x, ";_") + # Prepare row data using QIIME2 format + row_data <- data.frame(rownames(x), row_data, 1L, row.names = NULL) + # Name variables using QIIME2 format + colnames(row_data) <- c("Feature ID", "Taxon", "Confidence") + # Write row data + write.table( + row_data, paste0(dpath, "taxonomy.tsv"), + sep = "\t", quote = FALSE, row.names = FALSE + ) + # Convert col data to data frame + col_data <- as.data.frame(colData(x)) + # Convert factor variables to character to correctly record col types + col_data[] <- lapply( + col_data, function(col) if( is.factor(col) ) as.character(col) else col + ) + # Record col types + col_types <- apply( + col_data, 2L, function(col) switch( + type(col), character = "categorical", integer = , double = "numeric") + ) + # Add col types to first row of col data + col_data <- rbind(col_types, col_data) + # Add sample id to first var of col data + col_data <- cbind(`sample-id` = c("#q2:types", colnames(x)), col_data) + # Write col data + write.table( + col_data, paste0(dpath, "metadata.tsv"), + sep = "\t", quote = FALSE, row.names = FALSE + ) + # Add feature names as first variable of assay + sel_assay <- data.frame(rownames(x), assay(x, assay.type), row.names = NULL) + # Name first variable according to QIIME2 format + colnames(sel_assay)[1L] <- "#OTU ID" + # Write selected assay + write.table( + sel_assay, paste0(dpath, assay.type, ".tsv"), + sep = "\t", quote = FALSE, row.names = FALSE + ) + # Retrieve row tree + row_tree <- rowTree(x, tree.name) + # If row tree is present + if( !is.null(row_tree) ){ + # Write row tree + write.tree(row_tree, paste0(dpath, "tree.nwk")) + } + # Write reference sequence + .write_referenceSeq(x, dpath, "seqs") + invisible(NULL) +}) + + +#' @rdname export-methods +#' @importFrom ape write.tree write.FASTA +#' @importFrom stringr str_replace_all fixed +setMethod("exportMothur", signature = c(x = "TreeSummarizedExperiment"), + function(x, dpath, assay.type = "counts", tree.name = "phylo", + group.var = NULL){ + # Check for forbidden colnames + if( "group" %in% colnames(colData(x)) ){ + warning( + "'group' is a reserved name in Mothur. colData variables with ", + "that name were made unique.", call. = FALSE + ) + } + # Create export directory + dpath <- .create_export_dir(dpath) + # Make format compatible with Mothur + rownames(x) <- str_replace_all(fixed(rownames(x)), "-", "_") + colnames(x) <- str_replace_all(fixed(colnames(x)), "-", "_") + # Write file with custom grouping variable + .write_group_file(x, dpath, group.var, file.format = "group") + # Concatenate taxonomic ranks feature-wise + row_data <- .collapse_taxranks(x, ";") + # Make format compatible with Mothur + row_data <- str_replace_all(fixed(row_data), "-", "_") + # Retrieve selected assay + sel_assay <- assay(x, assay.type) + # Calculate assay row sums + row_sums <- rowSums(sel_assay) + # Add row sums to row data + row_data <- data.frame( + names(row_data), row_sums, row_data, row.names = NULL + ) + # Name variables of row data according to Mothur format + colnames(row_data) <- c("OTU", "Size", "Taxonomy") + # Write row data + write.table( + row_data, paste0(dpath, "taxonomy.tsv"), + sep = "\t", quote = FALSE, row.names = FALSE + ) + # Add sample ids to first variable of col data + col_data <- data.frame(group = colnames(x), colData(x), row.names = NULL) + # Write col data + write.table( + col_data, paste0(dpath, "metadata.tsv"), + sep = "\t", quote = FALSE, row.names = FALSE + ) + # Add feature names and row sums to selected assay + sel_assay <- data.frame( + rownames(sel_assay), total = row_sums, sel_assay, row.names = NULL + ) + # Name first variable according to Mothur format + colnames(sel_assay)[1L] <- "Representative_Sequence" + # Write selected assay + write.table( + sel_assay, paste0(dpath, assay.type, ".tsv"), + sep = "\t", quote = FALSE, row.names = FALSE + ) + # Retrieve row tree + row_tree <- rowTree(x, tree.name) + # If row tree is present + if( !is.null(row_tree) ){ + # Make format compatible with Mothur + row_tree$tip.label <- str_replace_all(fixed(row_tree$tip.label), "-", "_") + # Write row tree + write.tree(row_tree, paste0(dpath, "tree.nwk")) + } + # Write reference sequence + .write_referenceSeq(x, dpath, "seqs") + invisible(NULL) +}) + +.write_referenceSeq <- function(x, dpath, file){ + # If reference sequence is present + if( !is.null(referenceSeq(x)) ){ + # Write reference sequence + write.FASTA(referenceSeq(x), paste0(dpath, file, ".fna")) + } + invisible(NULL) +} + +# Define function to write file for custom grouping variable +#' @importFrom stringr str_replace_all fixed +.write_group_file <- function(x, dpath, group.var, file.format){ + if( !is.null(group.var) ){ + # Extract grouping variable from row data + group <- rowData(x)[[group.var]] + # Make format compatible with QIIME2 and Mothur + group <- str_replace_all(fixed(group), "-", "_") + # Add rownames as first variable + group <- cbind(rownames(x), group) + # Name variables according to QIIME2 format + colnames(group) <- c("Feature ID", group.var) + # Write file with custom grouping variable + write.table( + group, paste0(dpath, group.var, ".", file.format), + sep = "\t", quote = FALSE, row.names = FALSE + ) + } +} + +# Define function to concatenate taxonomic ranks feature-wise +#' @importFrom stringr str_remove +.collapse_taxranks <- function(x, sep){ + # Concatenate taxonomic ranks feature-wise + row_data <- apply(rowData(x)[taxonomyRanks(x)], 1L, paste, collapse = sep) + # Remove empty ranks + pattern <- paste0("(", sep, "|", sep, "NA)+$") + row_data <- str_remove(row_data, pattern) +} diff --git a/man/export-methods.Rd b/man/export-methods.Rd new file mode 100644 index 000000000..3b974fc5d --- /dev/null +++ b/man/export-methods.Rd @@ -0,0 +1,136 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/AllGenerics.R, R/exporters.R +\name{exportRaw} +\alias{exportRaw} +\alias{exportQIIME2} +\alias{exportMothur} +\alias{export-methods} +\alias{exportRaw,TreeSummarizedExperiment-method} +\alias{exportQIIME2,TreeSummarizedExperiment-method} +\alias{exportMothur,TreeSummarizedExperiment-method} +\title{Exporters to common formats for microbiome data outside of R} +\usage{ +exportRaw(x, ...) + +exportQIIME2(x, ...) + +exportMothur(x, ...) + +\S4method{exportRaw}{TreeSummarizedExperiment}( + x, + dpath, + rowdata.file = "rowdata", + coldata.file = "coldata", + refseq.file = "seqs", + assay.dir = "assays", + rowtree.dir = "row_trees", + coltree.dir = "col_trees", + dimred.dir = "dim_reds", + altexp.dir = "alt_exps" +) + +\S4method{exportQIIME2}{TreeSummarizedExperiment}( + x, + dpath, + assay.type = "counts", + tree.name = "phylo", + group.var = NULL +) + +\S4method{exportMothur}{TreeSummarizedExperiment}( + x, + dpath, + assay.type = "counts", + tree.name = "phylo", + group.var = NULL +) +} +\arguments{ +\item{x}{a \code{\link[TreeSummarizedExperiment]{TreeSummarizedExperiment}} +object.} + +\item{...}{Unused.} + +\item{dpath}{\code{Character scalar}. String specifying the directory where +\code{x} should be exported. If non-existent, it is created recursively.} + +\item{rowdata.file}{\code{Character scalar}. String specifying the file where +rowData should be written. (Default: \code{"rowdata"})} + +\item{coldata.file}{\code{Character scalar}. String specifying the file where +colData should be written. (Default: \code{"coldata"})} + +\item{refseq.file}{\code{Character scalar}. String specifying the file where +referenceSeq should be written. (Default: \code{"seqs"})} + +\item{assay.dir}{\code{Character scalar}. String specifying the directory +where assays should be written. (Default: \code{"assays"})} + +\item{rowtree.dir}{\code{Character scalar}. String specifying the directory +where rowTrees should be written. (Default: \code{"row_trees"})} + +\item{coltree.dir}{\code{Character scalar}. String specifying the directory +where colTrees should be written. (Default: \code{"col_trees"})} + +\item{dimred.dir}{\code{Character scalar}. String specifying the directory +where reducedDims should be written. (Default: \code{"dim_reds"})} + +\item{altexp.dir}{\code{Character scalar}. String specifying the directory +where altExps should be written. (Default: \code{"alt_exps"})} + +\item{assay.type}{\code{Character scalar}. String specifying which assay +should be exported. Only for \code{exportQIIME2} and \code{exportMothur}. +(Default: \code{"counts"})} + +\item{tree.name}{\code{Character scalar}. String specifying which row tree +should be exported. Only for \code{exportQIIME2} and \code{exportMothur}. +(Default: \code{"phylo"})} + +\item{group.var}{\code{Character scalar}. String specifying one variable of +rowData to export as a custom grouping file. Only for \code{exportQIIME2} +and \code{exportMothur}. (Default: \code{NULL})} +} +\value{ +Directory at \code{dpath} with components of \code{x} each stored +as a file in the proper format. +} +\description{ +There are a few very popular external tools for microbiome analysis, +including QIIME2 and mothur. However, R does not currently provide any class +to accommodate those data formats. When exporting data from mia to external +tools, the best approach is therefore to break a data container into its +building blocks (assays, side information, trees, etc.). + +Thanks to \code{exportRaw}, \code{exportQIIME2} and \code{exportMothur}, +it is now possible to export a +\code{\link[TreeSummarizedExperiment]{TreeSummarizedExperiment}} object as +raw elements or near-ready QIIME2 and mothur formats, respectively. This way, +migrating from mia to an external system is still a bad idea, but at least it +is fairly straightforward. +} +\details{ +The output directory contains the elements of \code{x}. For +\code{exportQIIME2} and \code{exportMothur}, data will need some more +processing using the target tool. For some tips, check the rbiom package +vignettes on converting data: +\url{https://cmmr.github.io/rbiom/articles/convert.html} +} +\examples{ +library(TreeSummarizedExperiment) + +# Generate example TreeSE object +tse <- makeTSE() +# Give name to counts assay +assayNames(tse) <- "counts" +# Give name to taxonomy variable +names(rowData(tse))[1] <- "Genus" + +# Export raw TreeSE components in custom directory +exportRaw(tse, "out") + +# Export TreeSE components in near-ready QIIME2 format +exportQIIME2(tse, "qiime2_dir") + +# Export TreeSE components in near-ready mothur format +exportMothur(tse, "mothur_dir") +} diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index ac68aa15a..fc6c99a75 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -52,7 +52,7 @@ reference: - summary - getDominant - getAbundant -- title: Data loading +- title: Import/export/convert data - contents: - importBIOM - importQIIME2 @@ -62,6 +62,9 @@ reference: - importTaxpasta - convertFromDADA2 - convertFromPhyloseq + - exportRaw + - exportMothur + - exportQIIME2 - title: Diversity - subtitle: Alpha Diversity diff --git a/src/tree.cpp b/src/tree.cpp index 480d92cfd..a7a042390 100644 --- a/src/tree.cpp +++ b/src/tree.cpp @@ -167,8 +167,7 @@ BPTree BPTree::collapse() { return this->mask(collapsemask, new_lengths); } -BPTree::~BPTree(){ -} +BPTree::~BPTree(){} void BPTree::index_and_cache(){ // Should probably do the open/close in here too diff --git a/src/tree.h b/src/tree.h index 05b116e61..0ba293bb2 100644 --- a/src/tree.h +++ b/src/tree.h @@ -19,6 +19,7 @@ #include namespace su { + class BPTree { public: /* Tracked attributes */ @@ -134,7 +135,8 @@ class BPTree { int32_t bwd(uint32_t i, int32_t d) const; int32_t enclose(uint32_t i) const; - }; +}; + } #endif /* __FAITH_TREE_H */ diff --git a/tests/testthat/test-exporters.R b/tests/testthat/test-exporters.R new file mode 100644 index 000000000..aa52a0411 --- /dev/null +++ b/tests/testthat/test-exporters.R @@ -0,0 +1,123 @@ + +test_that("exporters", { + + set.seed(123) + tse <- TreeSummarizedExperiment::makeTSE() + assayNames(tse) <- "counts" + + taxranks <- c("Family", "Genus", "Species") + rowData(tse)[taxranks] <- lapply( + taxranks, function(x) sample(letters, nrow(tse), replace = TRUE) + ) + + # Test exportRaw + dpath <- tempfile() + exportRaw(tse, dpath) + + expect_true(dir.exists(dpath)) + + row_data <- read.table(file.path(dpath, "rowdata.tsv")) + col_data <- read.table(file.path(dpath, "coldata.tsv")) + assay_tab <- read.table(file.path(dpath, "assays", "counts.tsv")) + + row_tree <- ape::read.tree(file.path(dpath, "row_trees", "phylo.nwk")) + col_tree <- ape::read.tree(file.path(dpath, "col_trees", "phylo.nwk")) + + tse2 <- TreeSummarizedExperiment::TreeSummarizedExperiment( + assays = S4Vectors::SimpleList(counts = as.matrix(assay_tab)), + rowData = row_data, + colData = col_data, + rowTree = row_tree, + colTree = col_tree + ) + + expect_equal(tse, tse2) + + # Test exportQIIME2 + dpath <- tempfile() + exportQIIME2(tse, dpath) + + expect_true(dir.exists(dpath)) + + assay_tab <- read.table( + file.path(dpath, "counts.tsv"), + sep = "\t", + header = TRUE, + comment.char = "", + check.names = FALSE + ) + + expect_equal(colnames(assay_tab), c("#OTU ID", colnames(tse))) + + row_data <- read.table( + file.path(dpath, "taxonomy.tsv"), + sep = "\t", + header = TRUE, + comment.char = "", + check.names = FALSE + ) + + expect_named(row_data, c("Feature ID", "Taxon", "Confidence")) + expect_match(row_data$Taxon, "[a-z];_[a-z];_[a-z]") + + col_data <- read.table( + file.path(dpath, "metadata.tsv"), + sep = "\t", + header = TRUE, + comment.char = "", + check.names = FALSE + ) + + expect_named(col_data, c("sample-id", colnames(colData(tse)))) + expect_identical(col_data[["sample-id"]], c("#q2:types", colnames(tse))) + expect_in(col_data[1, -1], c("categorical", "numeric")) + + # Test exportMothur + dpath <- tempfile() + + expect_warning( + exportMothur(tse, dpath), + "'group' is a reserved name in Mothur. colData variables with that name were made unique.", + fixed = TRUE + ) + + expect_true(dir.exists(dpath)) + + assay_tab <- read.table( + file.path(dpath, "counts.tsv"), + sep = "\t", + header = TRUE, + comment.char = "", + check.names = FALSE + ) + + expect_equal( + colnames(assay_tab), + c("Representative_Sequence", "total", colnames(tse)) + ) + + row_data <- read.table( + file.path(dpath, "taxonomy.tsv"), + sep = "\t", + header = TRUE, + comment.char = "", + check.names = FALSE + ) + + expect_named(row_data, c("OTU", "Size", "Taxonomy")) + expect_match(row_data$Taxonomy, "[a-z];[a-z];[a-z]") + + expect_identical(row_data$OTU, rownames(tse)) + expect_type(row_data$Size, "integer") + + col_data <- read.table( + file.path(dpath, "metadata.tsv"), + sep = "\t", + header = TRUE, + comment.char = "", + check.names = FALSE + ) + + expect_named(col_data, c("group", "ID", "group.1")) + expect_equal(colData(tse)$ID, col_data$ID) +})