Skip to content
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
25 changes: 23 additions & 2 deletions R/safe_t_test.r
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
#' observations) would otherwise stop execution.
#'
#' @details
#' Inputs `x` and `y` must be numeric vectors containing only finite,
#' non-missing values. In particular, `NA`, `NaN`, `Inf`, and `-Inf` values are
#' not allowed and will trigger an error before [stats::t.test()] is evaluated.
#'
#' When [stats::t.test()] succeeds, the resulting `"htest"` object is returned
#' unchanged except that `data.name` is reconstructed from the original
#' expressions supplied to `x` and `y`.
Expand All @@ -32,6 +36,10 @@
#' }
#'
#' @inheritParams stats::t.test
#' @param x (`numeric`)\cr A numeric vector containing only finite, non-missing
#' values.
#' @param y (`numeric`)\cr An optional numeric vector containing only finite,
#' non-missing values.
#'
#' @return
#' An object of class `"htest"`.
Expand Down Expand Up @@ -71,6 +79,19 @@ safe_t_test <- function(
x_expr <- substitute(x)
y_expr <- substitute(y)
alternative <- match.arg(alternative)

checkmate::assert_numeric(x, any.missing = FALSE, finite = TRUE)
if (paired) {
checkmate::assert_numeric(y, finite = TRUE, any.missing = FALSE, len = length(x))
} else {
checkmate::assert_numeric(y, finite = TRUE, any.missing = FALSE, null.ok = TRUE)
}
checkmate::assert_choice(alternative, choices = c("two.sided", "less", "greater"))
checkmate::assert_number(mu, finite = TRUE)
checkmate::assert_flag(paired)
checkmate::assert_flag(var.equal)
checkmate::assert_number(conf.level, lower = 0, upper = 1, finite = TRUE)

tryCatch(
{
res <- stats::t.test(
Expand Down Expand Up @@ -99,12 +120,12 @@ safe_t_test <- function(
}

if (is.null(y)) {
estimate <- c(mean(x, na.rm = TRUE))
estimate <- mean(x)
dname <- deparse1(x_expr)
method <- ifelse(paired, "Paired t-test", "One Sample t-test")
names(estimate) <- ifelse(paired, "mean difference", "mean of x")
} else {
estimate <- c(mean(x, na.rm = TRUE), mean(y, na.rm = TRUE))
estimate <- c(mean(x), mean(y))
dname <- paste(deparse1(x_expr), "and", deparse1(y_expr))
method <- paste(if (!var.equal) "Welch", "Two Sample t-test")
names(estimate) <- c("mean of x", "mean of y")
Expand Down
164 changes: 66 additions & 98 deletions tests/testthat/test-safe_t_test.r
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
test_that("safe_t_test() preserves the argument order of t.test()", {
x <- c(41, 40, 13, 71, 3, 96)
y <- c(1, 50, 43, 76, 94, 77)

result <- expect_silent(
safe_t_test(x, y, "greater", 4, TRUE, TRUE, 0.5)
)
expected <- t.test(x, y, "greater", 4, TRUE, TRUE, 0.5)
expect_identical(result, expected)
})

test_that("safe_t_test() is identical to t.test() for regular data (x only)", {
# Intentionally named xx (not x) because we want to check whether
# safe_t_test() preserves the variable name
Expand Down Expand Up @@ -54,15 +65,9 @@ test_that("safe_t_test() is identical to t.test() for regular data", {
expect_identical(result_cl, expected_cl)
})

test_that("safe_t_test() preserves the argument order of t.test()", {
x <- c(41, 40, 13, 71, 3, 96)
y <- c(1, 50, 43, 76, 94, 77)

result <- expect_silent(
safe_t_test(x, y, "greater", 4, TRUE, TRUE, 0.5)
)
expected <- t.test(x, y, "greater", 4, TRUE, TRUE, 0.5)
expect_identical(result, expected)
test_that("safe_t_test() does not fail for nearly constant data", {
x <- c(1.709999999999999964473, rep(1.710000000000000186517, 4))
expect_silent(safe_t_test(x, x))
})

test_that("safe_t_test() returns NaN for constant data (paired)", {
Expand All @@ -83,19 +88,15 @@ test_that("safe_t_test() returns NaN for constant data (paired, var.equal)", {
expect_identical(result, expected)
})

test_that("safe_t_test() warns for constant paired samples of different lengths", {
x <- c(2, NA, NA, 2, 2, NA, 2)
y <- c(NA, 2, 2, 2, 2, NA)
expect_warning(
safe_t_test(x, y, paired = TRUE, conf.level = 0.5)
test_that("safe_t_test() fails for constant paired samples of different lengths", {
x <- rep(2, 6)
y <- c(x, 2)
expect_error(
safe_t_test(x, y, paired = TRUE, conf.level = 0.5),
"length"
)
})

test_that("safe_t_test() does not fail for nearly constant data", {
x <- c(1.709999999999999964473, rep(1.710000000000000186517, 4))
expect_silent(safe_t_test(x, x))
})

test_that("safe_t_test() returns NA for constant data (x only)", {
x <- rep(2, 5)
result <- expect_silent(
Expand Down Expand Up @@ -144,66 +145,6 @@ test_that("safe_t_test() returns NA for constant data", {
expect_identical(result, expected)
})

test_that("safe_t_test() returns NA for constant data (x only, many NAs)", {
x <- c(2, NA, NA, 2, 2, NA, 2)
result <- expect_silent(
safe_t_test(x, conf.level = 0.5)
)

expected <- list(
statistic = setNames(NA_real_, "t"),
parameter = setNames(NA_real_, "df"),
p.value = NA_real_,
conf.int = c(NA_real_, NA_real_),
estimate = setNames(2, "mean of x"),
null.value = setNames(0, "mean"),
stderr = NA_real_,
alternative = "two.sided",
method = "One Sample t-test (failed: data are essentially constant)",
data.name = "x"
)
attr(expected$conf.int, "conf.level") <- 0.5
class(expected) <- "htest"

expect_identical(result, expected)
})

test_that("safe_t_test() returns NA for constant data (many NAs)", {
x <- c(2, NA, NA, 2, 2, NA, 2)
y <- c(NA, 2, 2, 2, 2, NA)
result <- expect_silent(
safe_t_test(x, x, conf.level = 0.5)
)

expected <- list(
statistic = setNames(NA_real_, "t"),
parameter = setNames(NA_real_, "df"),
p.value = NA_real_,
conf.int = c(NA_real_, NA_real_),
estimate = setNames(c(2, 2), c("mean of x", "mean of y")),
null.value = setNames(0, "difference in means"),
stderr = NA_real_,
alternative = "two.sided",
method = "Welch Two Sample t-test (failed: data are essentially constant)",
data.name = "x and x"
)
attr(expected$conf.int, "conf.level") <- 0.5
class(expected) <- "htest"

expect_identical(result, expected)
})

test_that("safe_t_test() returns NaN for constant data (many NAs, paired)", {
x <- c(2, NA, NA, 2, 2, 2)
y <- c(NA, 2, 2, 2, 2, NA)
result <- expect_silent(
safe_t_test(x, y, paired = TRUE, conf.level = 0.5)
)

expected <- t.test(x, y, paired = TRUE, conf.level = 0.5)
expect_identical(result, expected)
})

test_that("safe_t_test() returns NA for constant data (var.equal)", {
x <- rep(2, 5)
result <- expect_silent(
Expand Down Expand Up @@ -303,27 +244,54 @@ test_that("safe_t_test() returns NA for not enough observations", {
expect_identical(result, expected)
})

test_that("safe_t_test() returns NA for not enough observations (NA)", {
test_that("safe_t_test() fails for NA", {
x <- c(1, 3, 5, 20, 31, NA, 23)
y <- c(11, 16, 19, 28, 7, 20, 21, 1)

expect_error(safe_t_test(x), "missing")
expect_error(safe_t_test(x, y), "missing")
expect_error(safe_t_test(x, y, paired = TRUE), "missing")
})

test_that("safe_t_test() fails for NA (1 non-NA obs.)", {
x <- c(1, NA, NA)
y <- c(2, NA, NA)
result <- expect_silent(
safe_t_test(x, y, var.equal = TRUE, conf.level = 0.5)
)

expected <- list(
statistic = setNames(NA_real_, "t"),
parameter = setNames(NA_real_, "df"),
p.value = NA_real_,
conf.int = c(NA_real_, NA_real_),
estimate = setNames(c(1, 2), c("mean of x", "mean of y")),
null.value = setNames(0, "difference in means"),
stderr = NA_real_,
alternative = "two.sided",
method = " Two Sample t-test (failed: not enough observations)",
data.name = "x and y"
)
attr(expected$conf.int, "conf.level") <- 0.5
class(expected) <- "htest"
expect_error(safe_t_test(x), "missing")
expect_error(safe_t_test(x, y), "missing")
expect_error(safe_t_test(x, y, paired = TRUE), "missing")
})

expect_identical(result, expected)
test_that("safe_t_test() fails for NA (constant data)", {
x <- c(2, NA, NA, 2, 2, NA, 2)
y <- c(NA, 2, 2, 2, 2, NA)

expect_error(safe_t_test(x), "missing")
expect_error(safe_t_test(x, y), "missing")
expect_error(safe_t_test(x, y, paired = TRUE), "missing")
})

test_that("safe_t_test() fails for NaN", {
x <- c(1, 3, 5, NaN, 31, 34, 23)
y <- c(11, 16, 32, 28, 7, 20, 21, 1)

expect_error(safe_t_test(x), "missing")
expect_error(safe_t_test(x, y), "missing")
expect_error(safe_t_test(x, y, paired = TRUE), "missing")
})

test_that("safe_t_test() fails for Inf", {
x <- c(1, 3, 5, 20, 31, Inf, 23)
y <- c(11, 16, 19, 28, 7, 20, 21, 1)

expect_error(safe_t_test(x), "finite")
expect_error(safe_t_test(x, y), "finite")
expect_error(safe_t_test(x, y, paired = TRUE), "finite")
})

test_that("safe_t_test() fails for paired data of different lengths", {
x <- c(1, 3, 5, 20, 31, 23)
y <- c(11, 16, 19, 28, 7, 20, 21, 1)

expect_error(safe_t_test(x, y, paired = TRUE), "length")
})