From 9529271a1659f782d8dd71e78ab0234d15fb124e Mon Sep 17 00:00:00 2001 From: Daena Rys Date: Wed, 13 Nov 2024 12:47:20 +0200 Subject: [PATCH] update Signed-off-by: Daena Rys --- NAMESPACE | 2 + R/AllGenerics.R | 12 ++ R/getShortTermChange.R | 112 ++++++++---------- R/utils.R | 1 + ...ortTermChange.Rd => addShortTermChange.Rd} | 59 +++++---- tests/testthat/test-getShortTermChange.R | 43 +------ 6 files changed, 91 insertions(+), 138 deletions(-) rename man/{getShortTermChange.Rd => addShortTermChange.Rd} (50%) diff --git a/NAMESPACE b/NAMESPACE index e576a75..d750737 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,11 +1,13 @@ # Generated by roxygen2: do not edit by hand export(addBaselineDivergence) +export(addShortTermChange) export(addStepwiseDivergence) export(getBaselineDivergence) export(getShortTermChange) export(getStepwiseDivergence) exportMethods(addBaselineDivergence) +exportMethods(addShortTermChange) exportMethods(addStepwiseDivergence) exportMethods(getBaselineDivergence) exportMethods(getShortTermChange) diff --git a/R/AllGenerics.R b/R/AllGenerics.R index 55b5d16..5b57da5 100644 --- a/R/AllGenerics.R +++ b/R/AllGenerics.R @@ -20,3 +20,15 @@ setGeneric("getStepwiseDivergence", signature = c("x"), function(x, ...) #' @export setGeneric("addStepwiseDivergence", signature = "x", function(x, ...) standardGeneric("addStepwiseDivergence")) + +#' @rdname addShortTermChange +#' @export +setGeneric("addShortTermChange", signature = "x", function(x, ...) + standardGeneric("addShortTermChange")) + +#' @rdname addShortTermChange +#' @export +setGeneric("getShortTermChange", signature = "x", function(x, ...) + standardGeneric("getShortTermChange")) + + diff --git a/R/getShortTermChange.R b/R/getShortTermChange.R index 1c87174..106896f 100644 --- a/R/getShortTermChange.R +++ b/R/getShortTermChange.R @@ -6,17 +6,16 @@ #' @param x a \code{\link{SummarizedExperiment}} object. #' @param assay.type \code{Character scalar}. Specifies the name of assay #' used in calculation. (Default: \code{"counts"}) -#' @param rarefy \code{Logical scalar}. Whether to rarefy counts. -#' (Default: \code{FALSE}) -#' @param compositional \code{Logical scalar}. Whether to transform counts. -#' (Default: \code{FALSE}) -#' @param depth \code{Integer scalar}. Specifies the depth used in rarefying. -#' (Default: \code{min(assay(x, assay.type)})) +#' @param name \code{Character scalar}. Specifies a name for storing +#' short term results. (Default: \code{"short_term_change"}) #' @param ... additional arguments. #' #' -#' @return \code{dataframe} with \code{short term change} -#' calculations. +#' @return code{getShortTermChange} returns \code{DataFrame} object containing +#' the short term change in abundance over time for a microbe. +#' \code{addShortTermChange}, on the other hand, returns a +#' \code{\link[SummarizedExperiment:SummarizedExperiment-class]{SummarizedExperiment}} +#' object with these results in its \code{metadata}. #' #' @details This approach is used by Wisnoski NI and colleagues #' \url{https://github.com/nwisnoski/ul-seedbank}. Their approach is based on @@ -26,7 +25,7 @@ #' \url{https://www.nature.com/articles/s41564-020-0685-1} can be used. #' This approach is useful for identifying short term growth behaviors of taxa. #' -#' @name getShortTermChange +#' @name addShortTermChange #' #' #' @examples @@ -37,22 +36,21 @@ #' #' short_time_labels <- c("74.5h", "173h", "438h", "434h", "390h") #' -#' # Subset samples by Time_lable and StudyIdentifier +#' # Subset samples by Time_label and StudyIdentifier #' tse <- tse[, !(tse$Time_label %in% short_time_labels)] #' tse <- tse[, (tse$StudyIdentifier == "Bioreactor A")] #' #' # Get short term change -#' getShortTermChange(tse, rarefy = TRUE, time.col = "Time.hr") +#' # Case of rarefying counts +#' tse <- transformAssay(tse, method = "relabundance") +#' getShortTermChange(tse, assay.type = relabundance, time.col = "Time.hr") +#' +#' # Case of transforming counts +#' tse <- rarefyAssay(tse, assay.type = "counts") +#' getShortTermChange(tse, assay.type = subsampled, time.col = "Time.hr") NULL -#' @rdname getShortTermChange -#' @export -setGeneric("getShortTermChange", signature = c("x"), - function( x, assay.type = "counts", rarefy = FALSE, compositional = FALSE, - depth = min(assay(x, assay.type)), ...) - standardGeneric("getShortTermChange")) - -#' @rdname getShortTermChange +#' @rdname addShortTermChange #' @export #' @importFrom dplyr arrange as_tibble summarize "%>%" #' @importFrom ggplot2 ggplot aes @@ -60,61 +58,46 @@ setGeneric("getShortTermChange", signature = c("x"), #' @importFrom mia rarefyAssay transformAssay #' @importFrom SummarizedExperiment colData setMethod("getShortTermChange", signature = c(x = "SummarizedExperiment"), - function(x, assay.type = "counts", rarefy = FALSE, compositional = FALSE, - depth = min(assay(x, assay.type)), ...){ + function(x, assay.type = "counts", ...){ ############################## Input check ############################# # Check validity of object - if(nrow(x) == 0L){ + if (nrow(x) == 0L){ stop("No data available in `x` ('x' has nrow(x) == 0L.)", call. = FALSE) } # Check assay.type .check_assay_present(assay.type, x) - if(!.is_a_bool(rarefy)){ - stop("'rarefy' must be TRUE or FALSE.", call. = FALSE) - } - if(!.is_a_bool(compositional)){ - stop("'compositional' must be TRUE or FALSE.", call. = FALSE) - } - # Ensure that the provided depth is valid - if ( !is.null(depth) && depth > min(assay(x, assay.type), na.rm = TRUE) ) { - stop("Depth cannot be greater than the minimum number of counts in your data", call. = FALSE) - - } ########################### Growth Metrics ############################ - grwt <- .calculate_growth_metrics(x, assay.type = assay.type, - rarefy = rarefy, - compositional = compositional, - depth = depth, ...) - # Clean and format growth matrics + grwt <- .calculate_growth_metrics(x, assay.type = assay.type, ...) + # Clean and format growth metrics grs.all <- .clean_growth_metrics(grwt, ...) return(grs.all) } ) + +#' @rdname addShortTermChange +#' @export +setMethod("addShortTermChange", signature = c(x = "SummarizedExperiment"), + function(x, assay.type = "counts", name = "short_term_change", ...){ + # Calculate short term change + res <- getShortTermChange(x, ...) + # Add to metadata + x <- .add_values_to_metadata(x, name, res, ...) + return(x) + } +) + +################################ HELP FUNCTIONS ################################ + # wrapper to calculate growth matrix -.calculate_growth_metrics <- function(x, assay.type, time.col = NULL, - rarefy, compositional, depth, ...) { - ############################ Data Preparation ############################## - # Initialize the filtered object based on rarefy and compositional arguments - if (rarefy == TRUE && compositional == FALSE) { - message("rarefy is set to TRUE, calculating short term change using counts") - x <- rarefyAssay(x, assay.type = assay.type, depth = depth) - assay.type <- "subsampled" - } else if (rarefy == FALSE && compositional == FALSE) { - message("rarefy is set to FALSE, compositional==FALSE, using raw counts") - x <- x - } else if (rarefy == FALSE && compositional == TRUE) { - message("rarefy is set to FALSE, compositional==TRUE, using relative abundances") - x <- transformAssay(x, method = "relabundance", assay.type = assay.type) - assay.type <- "relabundance" - } else if (rarefy == TRUE && compositional == TRUE) { - stop("Both rarefy and compositional cannot be TRUE simultaneously", call. = FALSE) - } - # Reshape data and calcualte grwoth metrics - assay_data <- meltSE(x, assay.type = assay.type, add.col = time.col) +.calculate_growth_metrics <- function(x, assay.type, time.col = NULL, ...) { + + # Reshape data and calculate growth metrics + assay_data <- meltSE(x, assay.type = assay.type, + add.col = time.col, row.name = "Feature_ID") assay_data <- assay_data %>% arrange( !!sym(time.col) ) %>% - group_by(FeatureID) %>% + group_by(Feature_ID) %>% mutate( time_lag = !!sym(time.col) - lag( !!sym(time.col) ), growth_diff =!!sym(assay.type) - lag(!!sym(assay.type)), @@ -127,19 +110,18 @@ setMethod("getShortTermChange", signature = c(x = "SummarizedExperiment"), .clean_growth_metrics <- function(grwt, time.col = NULL, ...) { # Calculate max growth maxgrs <- grwt %>% - summarize(max.growth = max(growth_diff, na.rm = TRUE)) + summarize(max_growth = max(growth_diff, na.rm = TRUE)) # Merge growth data with max growth - grs.all <- merge(grwt, maxgrs, by = "FeatureID") + grs.all <- merge(grwt, maxgrs, by = "Feature_ID") # Add 'ismax' column indicating if the growth is the maximum grs.all <- grs.all %>% - mutate(ismax = ifelse(growth_diff == max.growth, TRUE, FALSE)) + mutate(ismax = ifelse(growth_diff == max_growth, TRUE, FALSE)) # Clean and abbreviate FeatureID names - grs.all$FeatureID <- gsub("_", " ", grs.all$FeatureID) - grs.all$FeatureIDabb <- toupper(abbreviate(grs.all$FeatureID, + grs.all$Feature_IDabb <- toupper(abbreviate(grs.all$Feature_ID, minlength = 3, method = "both.sides")) # Create 'Feature.time' column combining abbreviation and time information - grs.all$Feature.time <- paste0(grs.all$FeatureIDabb, " ", + grs.all$Feature_time <- paste0(grs.all$Feature_IDabb, " ", grs.all[[time.col]], "h") return(grs.all) diff --git a/R/utils.R b/R/utils.R index 6a4d25b..1cbd6bf 100644 --- a/R/utils.R +++ b/R/utils.R @@ -166,3 +166,4 @@ .check_assay_present <- mia:::.check_assay_present .add_values_to_colData <- mia:::.add_values_to_colData .check_and_get_altExp <- mia:::.check_and_get_altExp +.add_values_to_metadata <- mia:::.add_values_to_metadata diff --git a/man/getShortTermChange.Rd b/man/addShortTermChange.Rd similarity index 50% rename from man/getShortTermChange.Rd rename to man/addShortTermChange.Rd index a7e1e23..9e08b6a 100644 --- a/man/getShortTermChange.Rd +++ b/man/addShortTermChange.Rd @@ -1,48 +1,37 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/getShortTermChange.R -\name{getShortTermChange} +% Please edit documentation in R/AllGenerics.R, R/getShortTermChange.R +\name{addShortTermChange} +\alias{addShortTermChange} \alias{getShortTermChange} \alias{getShortTermChange,SummarizedExperiment-method} +\alias{addShortTermChange,SummarizedExperiment-method} \title{Short term Changes in Abundance} \usage{ -getShortTermChange( - x, - assay.type = "counts", - rarefy = FALSE, - compositional = FALSE, - depth = min(assay(x, assay.type)), - ... -) +addShortTermChange(x, ...) -\S4method{getShortTermChange}{SummarizedExperiment}( - x, - assay.type = "counts", - rarefy = FALSE, - compositional = FALSE, - depth = min(assay(x, assay.type)), - ... -) +getShortTermChange(x, ...) + +\S4method{getShortTermChange}{SummarizedExperiment}(x, assay.type = "counts", ...) + +\S4method{addShortTermChange}{SummarizedExperiment}(x, assay.type = "counts", name = "short_term_change", ...) } \arguments{ \item{x}{a \code{\link{SummarizedExperiment}} object.} +\item{...}{additional arguments.} + \item{assay.type}{\code{Character scalar}. Specifies the name of assay used in calculation. (Default: \code{"counts"})} -\item{rarefy}{\code{Logical scalar}. Whether to rarefy counts. -(Default: \code{FALSE})} - -\item{compositional}{\code{Logical scalar}. Whether to transform counts. -(Default: \code{FALSE})} - -\item{depth}{\code{Integer scalar}. Specifies the depth used in rarefying. -(Default: \code{min(assay(x, assay.type)}))} - -\item{...}{additional arguments.} +\item{name}{\code{Character scalar}. Specifies a name for storing +short term results. (Default: \code{"short_term_change"})} } \value{ -\code{dataframe} with \code{short term change} -calculations. +code{getShortTermChange} returns \code{DataFrame} object containing +the short term change in abundance over time for a microbe. +\code{addShortTermChange}, on the other hand, returns a +\code{\link[SummarizedExperiment:SummarizedExperiment-class]{SummarizedExperiment}} +object with these results in its \code{metadata}. } \description{ Calculates short term changes in abundance of taxa @@ -65,10 +54,16 @@ tse <- minimalgut short_time_labels <- c("74.5h", "173h", "438h", "434h", "390h") -# Subset samples by Time_lable and StudyIdentifier +# Subset samples by Time_label and StudyIdentifier tse <- tse[, !(tse$Time_label \%in\% short_time_labels)] tse <- tse[, (tse$StudyIdentifier == "Bioreactor A")] # Get short term change -getShortTermChange(tse, rarefy = TRUE, time.col = "Time.hr") +# Case of rarefying counts +tse <- transformAssay(tse, method = "relabundance") +getShortTermChange(tse, assay.type = relabundance, time.col = "Time.hr") + +# Case of transforming counts +tse <- rarefyAssay(tse, assay.type = "counts") +getShortTermChange(tse, assay.type = subsampled, time.col = "Time.hr") } diff --git a/tests/testthat/test-getShortTermChange.R b/tests/testthat/test-getShortTermChange.R index bab1915..26ce8c8 100644 --- a/tests/testthat/test-getShortTermChange.R +++ b/tests/testthat/test-getShortTermChange.R @@ -7,33 +7,8 @@ test_that("getShortTermChange", { empty_se <- SummarizedExperiment() expect_error(getShortTermChange(empty_se), "No data available in `x`") - # Check if assay.type argument works - # tse_invalid <- tse - # expect_error( - # getShortTermChange(tse_invalid, assay.type = "invalid_assay"), - # "'assay.type' must be a valid name of assays(x)" - # ) - # Check that rarefy and compositional cannot both be TRUE - expect_error(getShortTermChange(tse, rarefy = TRUE, compositional = TRUE, - time.col = "Time.hr"), - "Both rarefy and compositional cannot be TRUE simultaneously") - # Check if the depth argument is greater than minimum counts - min_depth <- min(assay(tse, "counts")) - expect_error(getShortTermChange(tse, depth = min_depth + 1, - time.col = "Time.hr"), - "Depth cannot be greater than the minimum number of counts in your data") - # Check if rarefy = TRUE works - result <- getShortTermChange(tse, rarefy = TRUE, time.col = "Time.hr") - expect_true(is.data.frame(result)) - # Check if compositional = TRUE works - result <- getShortTermChange(tse, compositional = TRUE, time.col = "Time.hr") - expect_true(is.data.frame(result)) + # Should still return a dataframe - result <- getShortTermChange(tse, rarefy = TRUE, - compositional = FALSE, - time.col = "Time.hr") - expect_true(is.data.frame(result)) - short_time_labels <- c("74.5h", "173h", "438h", "434h", "390h") # Subset samples by Time_label and StudyIdentifier tse_filtered <- tse[, !(tse$Time_label %in% short_time_labels)] @@ -41,24 +16,10 @@ test_that("getShortTermChange", { expect_true(all(!(tse_filtered$Time_label %in% short_time_labels))) - result <- getShortTermChange(tse_filtered, - rarefy = TRUE, time.col = "Time.hr") - - result <- getShortTermChange(tse_filtered, - compositional = TRUE, time.col = "Time.hr") + result <- getShortTermChange(tse_filtered, time.col = "Time.hr") # Expected output is a dataframe expect_true(is.data.frame(result)) expect_true("growth_diff" %in% colnames(result)) # Test some expected properties (e.g., that growth_diff isn't all NAs) expect_false(all(is.na(result$growth_diff))) - - min_depth <- min(assay(tse_filtered, "counts")) - result <- getShortTermChange(tse_filtered, rarefy = TRUE, - depth = min_depth, time.col = "Time.hr") - expect_true(is.data.frame(result)) - expect_error(getShortTermChange(tse_filtered, - rarefy = TRUE, - depth = min_depth + 1, time.col = "Time.hr"), - "Depth cannot be greater than the minimum number of counts in your data") - })