Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update transformAssay with pseudocount TRUE/FALSE option #449

Merged
merged 6 commits into from
Sep 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
.Rhistory
.RData
.Ruserdata
.DS_Store
*.Rproj
/doc/
/Meta/
Expand Down
87 changes: 53 additions & 34 deletions R/transformCounts.R
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,9 @@
#' @param name A single character value specifying the name of transformed
#' abundance table.
#'
#' @param pseudocount NULL or numeric value deciding whether pseudocount is
#' added. The numeric value specifies the value of pseudocount.
#' Recommended default choices for counts and relative abundance assay
#' \code{pseudocount = 1} and \code{pseudocount = min(assay[assay>0])}, respectively.
#' (By default: \code{pseudocount = 0})
#' @param pseudocount TRUE or FALSE, should the minimum value of \code{assay.type}
#' be added to assay values. Alternatively, a numeric value specifying the value
#' to be added. (default: \code{pseudocount = FALSE})
#'
#' @param ... additional arguments passed on to \code{vegan:decostand}:
#' \itemize{
Expand Down Expand Up @@ -133,12 +131,9 @@
#' # The target of transformation can be specified with "assay.type"
#' # Pseudocount can be added by specifying 'pseudocount'.
#'
#' # Get pseudocount; here smallest positive value
#' mat <- assay(tse, "relabundance")
#' pseudonumber <- min(mat[mat>0])
#' # Perform CLR
#' # Perform CLR with smallest positive value as pseudocount
#' tse <- transformAssay(tse, assay.type = "relabundance", method = "clr",
#' pseudocount = pseudonumber
#' pseudocount = TRUE
#' )
#'
#' head(assay(tse, "clr"))
Expand Down Expand Up @@ -193,7 +188,7 @@ setMethod("transformSamples", signature = c(x = "SummarizedExperiment"),
"rank", "rclr", "relabundance", "rrank",
"total"),
name = method,
pseudocount = 0,
pseudocount = FALSE,
...
){
.Deprecated("transformAssay")
Expand Down Expand Up @@ -229,7 +224,7 @@ setGeneric("transformAssay", signature = c("x"),
"z"),
MARGIN = "samples",
name = method,
pseudocount = 0,
pseudocount = FALSE,
...)
standardGeneric("transformAssay"))
#transformCounts wrapper with a deprecation warning
Expand All @@ -249,7 +244,7 @@ setMethod("transformAssay", signature = c(x = "SummarizedExperiment"),
"standardize", "total", "z"),
MARGIN = "samples",
name = method,
pseudocount = 0,
pseudocount = FALSE,
...){
# Input check

Expand Down Expand Up @@ -283,21 +278,22 @@ setMethod("transformAssay", signature = c(x = "SummarizedExperiment"),
call. = FALSE)
}
# Check pseudocount
if( !(is.numeric(pseudocount) && length(pseudocount) == 1 && pseudocount >= 0) ){
stop("'pseudocount' must be a non-negative single numeric value.",
if( !.is_a_bool(pseudocount) && !(is.numeric(pseudocount) && length(pseudocount) == 1 && pseudocount >= 0) ){
stop("'pseudocount' must be TRUE, FALSE or a number equal to or greater than 0.",
call. = FALSE)
}
# Input check end

# Get the method and abundance table
method <- match.arg(method)
assay <- assay(x, assay.type)

# Apply pseudocount, if it is not 0
if( pseudocount != 0 ){
assay <- .apply_pseudocount(assay, pseudocount)
}

assay <- .apply_pseudocount(assay, pseudocount)
# Store pseudocount value and set attr equal to NULL
pseudocount <- attr(assay, "pseudocount")
attr(assay, "pseudocount") <- NULL

# Calls help function that does the transformation
# Help function is different for mia and vegan transformations
if( method %in% c("log10", "log2") ){
Expand Down Expand Up @@ -327,7 +323,7 @@ setGeneric("transformFeatures", signature = c("x"),
method = c("frequency", "log", "log10", "log2", "max",
"pa", "range", "standardize", "z"),
name = method,
pseudocount = 0,
pseudocount = FALSE,
...)
standardGeneric("transformFeatures"))

Expand All @@ -339,7 +335,7 @@ setMethod("transformFeatures", signature = c(x = "SummarizedExperiment"),
method = c("frequency", "log", "log10", "log2", "max",
"pa", "range", "standardize", "z"),
name = method,
pseudocount = 0,
pseudocount = FALSE,
...){

.Deprecated("transformAssay")
Expand Down Expand Up @@ -478,14 +474,16 @@ setMethod("relAbundanceCounts", signature = c(x = "SummarizedExperiment"),
####################################.calc_log###################################
# This function applies log transformation to abundance table.
.calc_log <- function(mat, method, ...){
# If abundance table contains zeros, gives an error, because it is not
# possible to calculate log from zeros. If there is no zeros, calculates log.
if (any(mat <= 0, na.rm = TRUE)) {
stop("Abundance table contains zero or negative values and ",
method, " transformation is being applied without pseudocount.\n",
"Try to add pseudocount (default choice pseudocount = 1 for count ",
"assay; or pseudocount = min(x[x>0]) for relabundance assay).",
call. = FALSE)
# If abundance table contains zeros or negative values, gives an error, because
# it is not possible to calculate log from zeros. Otherwise, calculates log.
if ( any(mat < 0, na.rm = TRUE) ){
stop("The assay contains negative values and ", method,
" transformation is being applied without pseudocount.",
"`pseudocount` must be specified manually.", call. = FALSE)
} else if ( any(mat == 0, na.rm = TRUE) ){
stop("The assay contains zeroes and ", method,
" transformation is being applied without pseudocount.",
"`pseudocount` must be set to TRUE.", call. = FALSE)
}
# Calculate log2 or log10 abundances
if(method == "log2"){
Expand Down Expand Up @@ -551,12 +549,33 @@ setMethod("relAbundanceCounts", signature = c(x = "SummarizedExperiment"),
###############################.apply_pseudocount###############################
# This function applies pseudocount to abundance table.
.apply_pseudocount <- function(mat, pseudocount){
if( .is_a_bool(pseudocount) ){
# If pseudocount TRUE and some negative values, numerical pseudocount needed
if ( pseudocount && any(mat<0) ){
stop("The assay contains some negative values. ",
"'pseudocount' must be specified manually.", call. = FALSE)
}
# If pseudocount TRUE, set it to non-zero minimum value, else set it to zero
pseudocount <- ifelse(pseudocount, min(mat[mat>0]), 0)
# Report pseudocount if positive value
if ( pseudocount > 0 ){
message(paste("A pseudocount of", pseudocount, "was applied."))
}
}
# Give warning if pseudocount should not be added
if( all(mat>0) ){
warning("The abundance table contains only positive values. ",
"A pseudocount is not encouraged to apply.", call. = FALSE)
# Case 1: only positive values
if( pseudocount != 0 && all(mat>0) ){
warning("The assay contains only positive values. ",
"Applying a pseudocount may be unnecessary.", call. = FALSE)
}
# Case 2: some negative values
if( pseudocount != 0 && any(mat<0) ){
warning("The assay contains some negative values. ",
"Applying a pseudocount may produce meaningless data.", call. = FALSE)
}
# Add pseudocount.
# Add pseudocount
mat <- mat + pseudocount
# Set attr equal to pseudocount
attr(mat, "pseudocount") <- pseudocount
return(mat)
}
25 changes: 10 additions & 15 deletions man/transformAssay.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 24 additions & 8 deletions tests/testthat/test-5transformCounts.R
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ test_that("transformAssay", {
rowData = df)
expect_error(transformAssay(se, name = FALSE, method="relabundance"),
"'name' must be a non-empty single character value")

actual <- transformAssay(se, method="relabundance")
expect_named(assays(actual), c("counts", "relabundance"))

Expand Down Expand Up @@ -158,7 +157,8 @@ test_that("transformAssay", {
tse <- transformAssay(tse, method = "relabundance")
# Adds pseudocount
assay(tse, "test") <- assay(tse, "relabundance")+1
assay(tse, "test2") <- assay(tse, "test")
assay(tse, "test2") <- assay(tse, "test")
assay(tse, "neg_values") <- assay(tse, "counts") - 2
# First row is zeroes
assay(tse, "test2")[1, ] <- 0

Expand All @@ -181,12 +181,16 @@ test_that("transformAssay", {
# Expect error warning when zeroes
tse <- transformAssay(tse, method = "relabundance")
expect_error(transformAssay(tse, assay.type = "relabundance",
method = "clr") )
method = "clr"))

# Expect error when pseudocount TRUE but negative values present
expect_error(transformAssay(tse, method = "relabundance",
assay.type = "neg_values", pseudocount = TRUE),
"The assay contains some negative values. 'pseudocount' must be specified manually.")

# Test that CLR with counts equal to CLR with relabundance
assay(tse, "pseudo") <- assay(tse, "counts") + 1
tse <- transformAssay(
tse, assay.type = "pseudo", method = "clr", name = "clr1")
tse <- transformAssay(tse, assay.type = "pseudo", method = "clr", name = "clr1")
tse <- transformAssay(
tse, assay.type = "counts", method = "clr", name = "clr2",
pseudocount =1)
Expand All @@ -196,10 +200,22 @@ test_that("transformAssay", {
tse <- transformAssay(
tse, assay.type = "counts", method = "relabundance", pseudocount = 1,
name = "rel_pseudo1")
tse <- transformAssay(
tse, assay.type = "pseudo", method = "relabundance", name = "rel_pseudo2")
tse <- transformAssay(tse, assay.type = "pseudo", method = "relabundance", name = "rel_pseudo2")
expect_equal(assay(tse, "rel_pseudo1"), assay(tse, "rel_pseudo2"),
check.attributes = FALSE)

# Check that pseudocount = TRUE is the same as pseudocount = min
# and pseudocount = FALSE is the same as pseudocount = 0
tse <- transformAssay(tse, method = "relabundance", pseudocount = TRUE, name = "pseudo_true")
tse <- transformAssay(
tse, method = "relabundance", name = "pseudo_min",
pseudocount = min(assay(tse, "counts")[assay(tse, "counts") > 0]),
)
tse <- transformAssay(tse, method = "relabundance", pseudocount = FALSE, name = "pseudo_false")
tse <- transformAssay(tse, method = "relabundance", pseudocount = 0, name = "pseudo_zero")
expect_equal(assay(tse, "pseudo_true"), assay(tse, "pseudo_min"), check.attributes = FALSE)
expect_equal(assay(tse, "pseudo_false"), assay(tse, "pseudo_zero"), check.attributes = FALSE)
expect_false(all(assay(tse, "pseudo_true") == assay(tse, "pseudo_false")))

############################# NAMES ####################################
# Tests that samples have correct names
Expand Down Expand Up @@ -278,7 +294,7 @@ test_that("transformAssay", {
pseudocount = 4, reference = 2)

# The TreeSE version maintains the row & col number including the reference
coms <- intersect(rownames(actual), rownames(compare))
coms <- intersect(rownames(actual), rownames(compare))
expect_equal(actual[coms, -2], compare[coms, -2], check.attributes = FALSE)

# hellinger
Expand Down