diff --git a/R/freqplots.R b/R/freqplots.R index 04c3b24..d4479fb 100644 --- a/R/freqplots.R +++ b/R/freqplots.R @@ -148,25 +148,15 @@ freqplot <- function(type = "barchart", orient = "vertical", scale = "freq", # Non-standard evaluation - otype <- deparse(substitute(type, env = environment())) - type <- tryCatch({if (typeof(type) %in% c("character", "NULL")) type else otype}, - error = function(cond) {otype}) + type <- resolve_arg(type) - oorient <- deparse(substitute(orient, env = environment())) - orient <- tryCatch({if (typeof(orient) %in% c("character", "NULL")) orient else oorient}, - error = function(cond) {oorient}) + orient <- resolve_arg(orient) - oscale <- deparse(substitute(scale, env = environment())) - scale <- tryCatch({if (typeof(scale) %in% c("character", "NULL")) scale else oscale}, - error = function(cond) {oscale}) + scale <- resolve_arg(scale) - otwoway <- deparse(substitute(twoway, env = environment())) - twoway <- tryCatch({if (typeof(twoway) %in% c("character", "NULL")) twoway else otwoway}, - error = function(cond) {otwoway}) + twoway <- resolve_arg(twoway) - ogroupby <- deparse(substitute(groupby, env = environment())) - groupby <- tryCatch({if (typeof(groupby) %in% c("character", "NULL")) groupby else ogroupby}, - error = function(cond) {ogroupby}) + groupby <- resolve_arg(groupby) if (length(type) > 1) { stop("Parmeter 'type' is invalid. Value must be a single character string.") diff --git a/R/proc_freq.R b/R/proc_freq.R index 0fee9ae..9c68a5c 100644 --- a/R/proc_freq.R +++ b/R/proc_freq.R @@ -517,31 +517,12 @@ proc_freq <- function(data, where = NULL ) { - # Deal with single value unquoted parameter values - oby <- deparse(substitute(by, env = environment())) - by <- tryCatch({if (typeof(by) %in% c("character", "NULL")) by else oby}, - error = function(cond) {oby}) - - otables <- deparse(substitute(tables, env = environment())) - tables <- tryCatch({if (typeof(tables) %in% c("character", "NULL")) tables else otables}, - error = function(cond) {otables}) - - owgt <- deparse(substitute(weight, env = environment())) - weight <- tryCatch({if (typeof(weight) %in% c("character", "NULL")) weight else owgt}, - error = function(cond) {owgt}) - - oopt <- deparse(substitute(options, env = environment())) - options <- tryCatch({if (typeof(options) %in% c("character", "NULL")) options else oopt}, - error = function(cond) {oopt}) - - oout <- deparse(substitute(output, env = environment())) - output <- tryCatch({if (typeof(output) %in% c("character", "NULL")) output else oout}, - error = function(cond) {oout}) - - rout <- deparse(substitute(order, env = environment())) - order <- tryCatch({if (typeof(order) %in% c("character", "NULL")) order else rout}, - error = function(cond) {rout}) - + by <- resolve_arg(by) + tables <- resolve_arg(tables) + weight <- resolve_arg(weight) + options <- resolve_arg(options) + output <- resolve_arg(output) + order <- resolve_arg(order) # Parameter checks @@ -649,7 +630,7 @@ proc_freq <- function(data, # Deal with where expression if (!is.null(where)) { - data <- subset(data, eval(where)) + data <- subset_data(data, where) } rptflg <- FALSE diff --git a/R/proc_means.R b/R/proc_means.R index ef49741..05d69e5 100644 --- a/R/proc_means.R +++ b/R/proc_means.R @@ -366,34 +366,20 @@ proc_means <- function(data, # Deal with single value unquoted parameter values - oby <- deparse(substitute(by, env = environment())) - by <- tryCatch({if (typeof(by) %in% c("character", "NULL")) by else oby}, - error = function(cond) {oby}) + by <- resolve_arg(by) # Deal with single value unquoted parameter values - oclass <- deparse(substitute(class, env = environment())) - class <- tryCatch({if (typeof(class) %in% c("character", "NULL")) class else oclass}, - error = function(cond) {oclass}) + class <- resolve_arg(class) - ovar <- deparse(substitute(var, env = environment())) - var <- tryCatch({if (typeof(var) %in% c("character", "NULL")) var else ovar}, - error = function(cond) {ovar}) + var <- resolve_arg(var) - oweight <- deparse(substitute(weight, env = environment())) - weight <- tryCatch({if (typeof(weight) %in% c("character", "NULL")) weight else oweight}, - error = function(cond) {oweight}) + weight <- resolve_arg(weight) - ostats <- deparse(substitute(stats, env = environment())) - stats <- tryCatch({if (typeof(stats) %in% c("character", "NULL")) stats else ostats}, - error = function(cond) {ostats}) + stats <- resolve_arg(stats) - oopt <- deparse(substitute(options, env = environment())) - options <- tryCatch({if (typeof(options) %in% c("integer", "double", "character", "NULL")) options else oopt}, - error = function(cond) {oopt}) + options <- resolve_arg(options, type = c("character", "double", "integer", "NULL")) - oout <- deparse(substitute(output, env = environment())) - output <- tryCatch({if (typeof(output) %in% c("character", "NULL")) output else oout}, - error = function(cond) {oout}) + output <- resolve_arg(output) # Parameter checks @@ -521,7 +507,7 @@ proc_means <- function(data, # Deal with where expression if (!is.null(where)) { - data <- subset(data, eval(where)) + data <- subset_data(data, where) } # Get report if requested diff --git a/R/proc_reg.R b/R/proc_reg.R index 472bc4b..e977e62 100644 --- a/R/proc_reg.R +++ b/R/proc_reg.R @@ -443,26 +443,16 @@ proc_reg <- function(data, missing <- FALSE # Deal with single value unquoted parameter values - oweight <- deparse(substitute(weight, env = environment())) - weight <- tryCatch({if (typeof(weight) %in% c("character", "NULL")) weight else oweight}, - error = function(cond) {oweight}) + weight <- resolve_arg(weight) # Deal with single value unquoted parameter values - oby <- deparse(substitute(by, env = environment())) - by <- tryCatch({if (typeof(by) %in% c("character", "NULL")) by else oby}, - error = function(cond) {oby}) + by <- resolve_arg(by) - ostats <- deparse(substitute(stats, env = environment())) - stats <- tryCatch({if (typeof(stats) %in% c("character", "NULL")) stats else ostats}, - error = function(cond) {ostats}) + stats <- resolve_arg(stats) - oopt <- deparse(substitute(options, env = environment())) - options <- tryCatch({if (typeof(options) %in% c("integer", "double", "character", "NULL")) options else oopt}, - error = function(cond) {oopt}) + options <- resolve_arg(options, type = c("integer", "double", "character", "NULL")) - oout <- deparse(substitute(output, env = environment())) - output <- tryCatch({if (typeof(output) %in% c("character", "NULL")) output else oout}, - error = function(cond) {oout}) + output <- resolve_arg(output) # Parameter checks @@ -596,7 +586,7 @@ proc_reg <- function(data, # Where subset if (!is.null(where)) { - data <- subset(data, eval(where)) + data <- subset_data(data, where) } # Get report if requested diff --git a/R/proc_sort.R b/R/proc_sort.R index 7ca5e2d..34a5d82 100644 --- a/R/proc_sort.R +++ b/R/proc_sort.R @@ -141,32 +141,20 @@ proc_sort <- function(data, by = NULL, keep = NULL, order = "ascending", options = NULL, as.character = FALSE, na.sort = NULL, where = NULL) { - # Deal with single value unquoted parameter values - oby <- deparse(substitute(by, env = environment())) - by <- tryCatch({if (typeof(by) %in% c("character", "NULL")) by else oby}, - error = function(cond) {oby}) + by <- resolve_arg(by) # Deal with single value unquoted parameter values - okeep <- deparse(substitute(keep, env = environment())) - keep <- tryCatch({if (typeof(keep) %in% c("character", "NULL")) keep else okeep}, - error = function(cond) {okeep}) - + keep <- resolve_arg(keep) # Deal with single value unquoted parameter values - oorder <- deparse(substitute(order, env = environment())) - order <- tryCatch({if (typeof(order) %in% c("character", "NULL")) order else oorder}, - error = function(cond) {oorder}) + order <- resolve_arg(order) # Deal with single value unquoted option values - oopt <- deparse(substitute(options, env = environment())) - options <- tryCatch({if (typeof(options) %in% c("integer", "double", "character", "NULL")) options else oopt}, - error = function(cond) {oopt}) + options <- resolve_arg(options, type = c("character", "double", "integer", "NULL")) # Deal with single value unquoted option values - onasort <- deparse(substitute(na.sort, env = environment())) - na.sort <- tryCatch({if (typeof(na.sort) %in% c("character", "NULL")) na.sort else onasort}, - error = function(cond) {onasort}) + na.sort <- resolve_arg(na.sort) # Force to lower case if (!is.null(na.sort)) { @@ -174,7 +162,6 @@ proc_sort <- function(data, by = NULL, keep = NULL, order = "ascending", } # Parameter checks - if (!"data.frame" %in% class(data)) { stop("Input data is not a data frame.") } @@ -185,7 +172,7 @@ proc_sort <- function(data, by = NULL, keep = NULL, order = "ascending", # Deal with where expression if (!is.null(where)) { - data <- subset(data, eval(where)) + data <- subset_data(data, where) } if (!is.null(order)) { diff --git a/R/proc_transpose.R b/R/proc_transpose.R index 083b695..94f80fa 100644 --- a/R/proc_transpose.R +++ b/R/proc_transpose.R @@ -172,53 +172,31 @@ proc_transpose <- function(data, } # Deal with single value unquoted parameter values - oby <- deparse(substitute(by, env = environment())) - by <- tryCatch({if (typeof(by) %in% c("character", "NULL")) by else oby}, - error = function(cond) {oby}) + by <- resolve_arg(by) # Deal with single value unquoted parameter values - ovar <- deparse(substitute(var, env = environment())) - var <- tryCatch({if (typeof(var) %in% c("character", "NULL")) var else ovar}, - error = function(cond) {ovar}) + var <- resolve_arg(var) # Deal with single value unquoted parameter values - oid <- deparse(substitute(id, env = environment())) - id <- tryCatch({if (typeof(id) %in% c("character", "NULL")) id else oid}, - error = function(cond) {oid}) + id <- resolve_arg(id) # Deal with single value unquoted parameter values - oidlabel <- deparse(substitute(idlabel, env = environment())) - idlabel <- tryCatch({if (typeof(idlabel) %in% c("character", "NULL")) idlabel else oidlabel}, - error = function(cond) {oidlabel}) + idlabel <- resolve_arg(idlabel) # Deal with single value unquoted parameter values - ocopy <- deparse(substitute(copy, env = environment())) - copy <- tryCatch({if (typeof(copy) %in% c("character", "NULL")) copy else ocopy}, - error = function(cond) {ocopy}) + copy <- resolve_arg(copy) - oname <- deparse(substitute(name, env = environment())) - name <- tryCatch({if (typeof(name) %in% c("character", "NULL")) name else oname}, - error = function(cond) {oname}) + name <- resolve_arg(name) - onamelabel <- deparse(substitute(namelabel, env = environment())) - namelabel <- tryCatch({if (typeof(namelabel) %in% c("character", "NULL")) namelabel else onamelabel}, - error = function(cond) {onamelabel}) + namelabel <- resolve_arg(namelabel) - oprefix <- deparse(substitute(prefix, env = environment())) - prefix <- tryCatch({if (typeof(prefix) %in% c("character", "NULL")) prefix else oprefix}, - error = function(cond) {oprefix}) + prefix <- resolve_arg(prefix) - osuffix <- deparse(substitute(suffix, env = environment())) - suffix <- tryCatch({if (typeof(suffix) %in% c("character", "NULL")) suffix else osuffix}, - error = function(cond) {osuffix}) + suffix <- resolve_arg(suffix) - odelimiter <- deparse(substitute(delimiter, env = environment())) - delimiter <- tryCatch({if (typeof(delimiter) %in% c("character", "NULL")) delimiter else odelimiter}, - error = function(cond) {odelimiter}) + delimiter <- resolve_arg(delimiter) - ooptions <- deparse(substitute(options, env = environment())) - options <- tryCatch({if (typeof(options) %in% c("character", "NULL")) options else ooptions}, - error = function(cond) {ooptions}) + options <- resolve_arg(options) # Parameter checks @@ -497,8 +475,7 @@ proc_transpose <- function(data, # Where if (!is.null(where)) { - res <- subset(res, eval(where)) - + res <- subset_data(res, where) } if ("noname" %in% options) { diff --git a/R/proc_ttest.R b/R/proc_ttest.R index 01abf92..ed6fd94 100644 --- a/R/proc_ttest.R +++ b/R/proc_ttest.R @@ -424,34 +424,20 @@ proc_ttest <- function(data, # Deal with single value unquoted parameter values - oby <- deparse(substitute(by, env = environment())) - by <- tryCatch({if (typeof(by) %in% c("character", "NULL")) by else oby}, - error = function(cond) {oby}) + by <- resolve_arg(by) # Deal with single value unquoted parameter values - oclass <- deparse(substitute(class, env = environment())) - class <- tryCatch({if (typeof(class) %in% c("character", "NULL")) class else oclass}, - error = function(cond) {oclass}) + class <- resolve_arg(class) - ovar <- deparse(substitute(var, env = environment())) - var <- tryCatch({if (typeof(var) %in% c("character", "NULL")) var else ovar}, - error = function(cond) {ovar}) + var <- resolve_arg(var) - oopt <- deparse(substitute(options, env = environment())) - options <- tryCatch({if (typeof(options) %in% c("integer", "double", "character", "NULL")) options else oopt}, - error = function(cond) {oopt}) + options <- resolve_arg(options, type = c("character", "double", "integer", "NULL")) - oout <- deparse(substitute(output, env = environment())) - output <- tryCatch({if (typeof(output) %in% c("character", "NULL")) output else oout}, - error = function(cond) {oout}) + output <- resolve_arg(output) - opaired <- deparse(substitute(paired, env = environment())) - paired <- tryCatch({if (typeof(paired) %in% c("character", "NULL")) paired else opaired}, - error = function(cond) {opaired}) + paired <- resolve_arg(paired) - rout <- deparse(substitute(order, env = environment())) - order <- tryCatch({if (typeof(order) %in% c("character", "NULL")) order else rout}, - error = function(cond) {rout}) + order <- resolve_arg(order) # Parameter checks if (!"data.frame" %in% class(data)) { @@ -600,7 +586,7 @@ proc_ttest <- function(data, # Where subset if (!is.null(where)) { - data <- subset(data, eval(where)) + data <- subset_data(data, where) } # Deal with order diff --git a/R/regplots.R b/R/regplots.R index 4c424df..b8847c4 100644 --- a/R/regplots.R +++ b/R/regplots.R @@ -260,17 +260,11 @@ regplot <- function(type = c("diagnostics", "residuals", "fitplot"), panel = TRU # Non-standard evaluation - otype <- deparse(substitute(type, env = environment())) - type <- tryCatch({if (typeof(type) %in% c("character", "NULL")) type else otype}, - error = function(cond) {otype}) + type <- resolve_arg(type) - ostats <- deparse(substitute(stats, env = environment())) - stats <- tryCatch({if (typeof(stats) %in% c("character", "NULL")) stats else ostats}, - error = function(cond) {ostats}) + stats <- resolve_arg(stats) - oid <- deparse(substitute(id, env = environment())) - id <- tryCatch({if (typeof(id) %in% c("character", "NULL")) id else oid}, - error = function(cond) {oid}) + id <- resolve_arg(id) # Parameter Checks diff --git a/R/ttestplots.R b/R/ttestplots.R index 29732da..ef86983 100644 --- a/R/ttestplots.R +++ b/R/ttestplots.R @@ -170,9 +170,7 @@ ttestplot <- function(type = "default", panel = TRUE, showh0 = FALSE, label = TRUE, id = NULL) { # Non-standard evaluation - otype <- deparse(substitute(type, env = environment())) - type <- tryCatch({if (typeof(type) %in% c("character", "NULL")) type else otype}, - error = function(cond) {otype}) + type <- resolve_arg(type) # agreement, boxplot, histogram, interval, profiles, qqplot, summary diff --git a/R/utilities.R b/R/utilities.R index 101dfc2..1180aee 100644 --- a/R/utilities.R +++ b/R/utilities.R @@ -1517,5 +1517,73 @@ force_width <- function(str, wdth) { return(ret) } +# Sassy-r NSE +#' @noRd +resolve_arg <- function(arg, type = c("character", "NULL")) { + + call <- match.call() + if (!"arg" %in% names(call)) { + stop("Argument 'arg' is missing.", call. = FALSE) + } + + if (!is.character(type)) { + stop("'type' must be a character vector.", call. = FALSE) + } + + valid_modes <- c("character", "double", "integer", "NULL") + + if (!all(type %in% valid_modes)) { + stop("'type' must contain valid mode values.", call. = FALSE) + } + + expr <- eval.parent(substitute(substitute(arg))) + expr_str <- paste(deparse(expr, width.cutoff = 500L), collapse = " ") + + value <- tryCatch(arg, error = function(e) e) + + res <- if (!inherits(value, "error") && typeof(value) %in% type) value else expr_str + + return(res) +} + +# Subset data +#' @noRd +subset_data <- function(data, where = NULL) { + + if (!is.data.frame(data)) { + stop("'data' must be a data.frame.", call. = FALSE) + } + + if (is.null(where)) { + return(data) + } + + if (!is.expression(where)) { + stop("'where' must be an expression.", call. = FALSE) + } + + if (length(where) != 1L) { + stop("'where' must be an expression of length 1.", call. = FALSE) + } + + rows <- tryCatch( + eval(where, envir = data), + error = function(e) + stop("Error evaluating 'where': ", e$message, call. = FALSE) + ) + + if (!is.logical(rows)) { + stop("'where' must evaluate to a logical vector.", call. = FALSE) + } + + if (length(rows) != nrow(data)) { + stop("'where' must evaluate to a logical vector with length equal to nrow(data).", call. = FALSE) + } + + res <- copy.attributes(data, data[rows, , drop = FALSE]) + + return(res) +} + diff --git a/tests/testthat/test-utilities.R b/tests/testthat/test-utilities.R index 963c0df..70eddba 100644 --- a/tests/testthat/test-utilities.R +++ b/tests/testthat/test-utilities.R @@ -888,3 +888,347 @@ test_that("utils29: get_line_count() works as expected.", { }) + +test_that("resolve_arg1: errors when arg is missing in the resolve_arg call itself", { + caller <- function() { + resolve_arg() + } + + expect_error( + caller(), + "Argument 'arg' is missing.", + fixed = TRUE + ) +}) + +test_that("resolve_arg2: errors when type is not a character vector", { + caller <- function(x) { + resolve_arg(x, type = 1) + } + + expect_error( + caller("A"), + "'type' must be a character vector.", + fixed = TRUE + ) +}) + +test_that("resolve_arg3: errors when type contains an invalid mode value", { + caller <- function(x) { + resolve_arg(x, type = c("character", "logical")) + } + + expect_error( + caller("A"), + "'type' must contain valid mode values.", + fixed = TRUE + ) +}) + +test_that("resolve_arg4: errors when type consists entirely of invalid mode values", { + caller <- function(x) { + resolve_arg(x, type = c("logical", "list")) + } + + expect_error( + caller("A"), + "'type' must contain valid mode values.", + fixed = TRUE + ) +}) + +test_that("resolve_arg5: returns character input unchanged when character is allowed", { + caller <- function(x) { + resolve_arg(x) + } + + expect_identical(caller("USUBJID"), "USUBJID") + expect_identical(caller(c("A", "B")), c("A", "B")) +}) + +test_that("resolve_arg6: returns NULL unchanged when NULL is allowed", { + caller <- function(x = NULL) { + resolve_arg(x) + } + + expect_null(caller()) + expect_null(caller(NULL)) +}) + +test_that("resolve_arg7: captures unquoted name as character string", { + caller <- function(x) { + resolve_arg(x) + } + + expect_identical(caller(USUBJID), "USUBJID") +}) + +test_that("resolve_arg8: captures unevaluated expression as a single string", { + caller <- function(x) { + resolve_arg(x) + } + + expect_identical(caller(AGE > 18), "AGE > 18") + expect_identical(caller(A + B * C), "A + B * C") +}) + +test_that("resolve_arg9: returns evaluated double when double is allowed", { + caller <- function(x) { + resolve_arg(x, type = "double") + } + + expect_identical(caller(1.5), 1.5) +}) + +test_that("resolve_arg10: returns evaluated integer when integer is allowed", { + caller <- function(x) { + resolve_arg(x, type = "integer") + } + + expect_identical(caller(1L), 1L) +}) + +test_that("resolve_arg11: returns expression string when value type is not allowed", { + caller <- function(x) { + resolve_arg(x, type = "character") + } + + expect_identical(caller(1.5), "1.5") + expect_identical(caller(1L), "1L") +}) + +test_that("resolve_arg12: returns evaluated value when multiple allowed types include actual type", { + caller <- function(x) { + resolve_arg(x, type = c("character", "double")) + } + + expect_identical(caller("AVAL"), "AVAL") + expect_identical(caller(2.5), 2.5) +}) + +test_that("resolve_arg13: falls back to expression string when evaluation errors", { + caller <- function(x) { + resolve_arg(x) + } + + expect_identical(caller(not_an_object), "not_an_object") + expect_identical(caller(a + b), "a + b") +}) + +test_that("resolve_arg14: captures complex function call expression when result type is not allowed", { + caller <- function(x) { + resolve_arg(x, type = "character") + } + + expect_identical(caller(mean(c(1, 2, 3))), "mean(c(1, 2, 3))") +}) + +test_that("resolve_arg15: returns evaluated character result of an expression when character is allowed", { + caller <- function(x) { + resolve_arg(x, type = "character") + } + + expect_identical(caller(paste0("A", "B")), "AB") +}) + +test_that("resolve_arg16: returns expression string for NULL when NULL is not allowed", { + caller <- function(x = NULL) { + resolve_arg(x, type = "character") + } + + expect_identical(caller(), "NULL") + expect_identical(caller(NULL), "NULL") +}) + +library(testthat) + +test_that("subset_data1: errors when data is not a data.frame", { + expect_error( + subset_data(data = 1:5), + "'data' must be a data.frame.", + fixed = TRUE + ) +}) + +test_that("subset_data2: returns data unchanged when where is NULL", { + df <- data.frame( + USUBJID = c("01", "02", "03"), + AGE = c(10, 20, 30) + ) + + res <- subset_data(df, where = NULL) + + rownames(res) <- NULL + rownames(df) <- NULL + + expect_identical(res, df) +}) + +test_that("subset_data3: errors when where is not an expression", { + df <- data.frame( + USUBJID = c("01", "02", "03"), + AGE = c(10, 20, 30) + ) + + expect_error( + subset_data(df, where = quote(AGE > 18)), + "'where' must be an expression.", + fixed = TRUE + ) +}) + +test_that("subset_data4: errors when where expression has length greater than 1", { + df <- data.frame( + USUBJID = c("01", "02", "03"), + AGE = c(10, 20, 30) + ) + + where <- expression(AGE > 18, AGE < 30) + + expect_error( + subset_data(df, where = where), + "'where' must be an expression of length 1.", + fixed = TRUE + ) +}) + +test_that("subset_data5: errors when where references a non-existent column", { + df <- data.frame( + USUBJID = c("01", "02", "03"), + AGE = c(10, 20, 30) + ) + + where <- expression(TRT == "A") + + expect_error( + subset_data(df, where = where), + "Error evaluating 'where'", + fixed = TRUE + ) +}) + +test_that("subset_data6: errors when where evaluates to non-logical vector", { + df <- data.frame( + USUBJID = c("01", "02", "03"), + AGE = c(10, 20, 30) + ) + + where <- expression(AGE) + + expect_error( + subset_data(df, where = where), + "'where' must evaluate to a logical vector.", + fixed = TRUE + ) +}) + +test_that("subset_data7: errors when where returns logical vector of incorrect length", { + df <- data.frame( + USUBJID = c("01", "02", "03"), + AGE = c(10, 20, 30) + ) + + where <- expression(c(TRUE, FALSE)) + + expect_error( + subset_data(df, where = where), + "'where' must evaluate to a logical vector with length equal to nrow(data).", + fixed = TRUE + ) +}) + +test_that("subset_data8: subsets rows correctly when where evaluates to logical vector", { + df <- data.frame( + USUBJID = c("01", "02", "03"), + AGE = c(10, 20, 30) + ) + + where <- expression(AGE > 18) + + res <- subset_data(df, where = where) + + expected <- data.frame( + USUBJID = c("02", "03"), + AGE = c(20, 30) + ) + + rownames(res) <- NULL + rownames(expected) <- NULL + + expect_identical(res, expected) +}) + +test_that("subset_data9: returns empty data.frame when condition is all FALSE", { + df <- data.frame( + USUBJID = c("01", "02", "03"), + AGE = c(10, 20, 30) + ) + + where <- expression(AGE > 100) + + res <- subset_data(df, where = where) + expected <- df[0, , drop = FALSE] + + rownames(res) <- NULL + rownames(expected) <- NULL + + expect_identical(res, expected) +}) + +test_that("subset_data10: returns full dataset when condition is all TRUE", { + df <- data.frame( + USUBJID = c("01", "02", "03"), + AGE = c(10, 20, 30) + ) + + where <- expression(AGE >= 10) + + res <- subset_data(df, where = where) + + rownames(res) <- NULL + rownames(df) <- NULL + + expect_identical(res, df) +}) + +test_that("subset_data11: preserves data.frame structure when single row returned", { + df <- data.frame( + USUBJID = c("01", "02", "03"), + AGE = c(10, 20, 30) + ) + + where <- expression(USUBJID == "02") + + res <- subset_data(df, where = where) + + rownames(res) <- NULL + + expect_true(is.data.frame(res)) + expect_identical(nrow(res), 1L) + expect_identical(ncol(res), 2L) + expect_identical(res$USUBJID, "02") + expect_identical(res$AGE, 20) +}) + +test_that("subset_data12: handles compound logical expressions correctly", { + df <- data.frame( + USUBJID = c("01", "02", "03", "04"), + AGE = c(10, 20, 30, 40), + SEX = c("F", "M", "F", "M") + ) + + where <- expression(AGE >= 20 & SEX == "M") + + res <- subset_data(df, where = where) + + expected <- data.frame( + USUBJID = c("02", "04"), + AGE = c(20, 40), + SEX = c("M", "M") + ) + + rownames(res) <- NULL + rownames(expected) <- NULL + + expect_identical(res, expected) +})