diff --git a/R/table-subscript.R b/R/table-subscript.R index f297ed09..9f005df3 100644 --- a/R/table-subscript.R +++ b/R/table-subscript.R @@ -15,9 +15,11 @@ # Catch empty input e.g. x[] or x[drop = TRUE/FALSE] (when ... is empty) if (empty.ind) return(x) + input.is.data.frame <- is.data.frame(x) + # Force array class for custom QTable subscripting code input.is.not.array <- !is.array(x) - if (input.is.not.array) + if (input.is.not.array && !input.is.data.frame) x <- as.array(x) DUPLICATE.LABEL.SUFFIX <- "_@_" @@ -25,7 +27,7 @@ x.dim <- dim(x) n.dim <- length(x.dim) - if (n.dim > 0 && !is.null(dimnames(x)) && is.null(names(dimnames(x)))) + if (n.dim > 0 && !is.null(dimnames(x)) && is.null(names(dimnames(x))) && !input.is.data.frame) x <- nameDimensionAttributes(x) n.index.args <- nargs() - 1L - !missing(drop) @@ -47,8 +49,23 @@ if (!identical(as.character(called.args[[i]]), "")) evaluated.args[[i]] <- eval(called.args[[i]], parent.frame()) + if (input.is.data.frame) { + if (length(evaluated.args) == 1) { + drop = FALSE + } + evaluated.args <- mapArgsFromDataFrame(evaluated.args, FALSE) + } + + if (input.is.data.frame) { + x2 <- as.matrix(x) + mostattributes(x2) <- c(attributes(x2)[isBasicAttribute(names(attributes(x2)))], + attributes(x)[!isBasicAttribute(names(attributes(x)))]) + } + else + x2 <- x + # Update Attributes here - y <- updateTableAttributes(y, x, called.args, evaluated.args, drop = drop, + y <- updateTableAttributes(y, x2, evaluated.args, drop = drop, missing.names, DUPLICATE.LABEL.SUFFIX) y <- updateNameAttribute(y, attr(x, "name"), called.args, "[") throwWarningIfDuplicateLabels(x, evaluated.args, sep = DUPLICATE.LABEL.SUFFIX) @@ -57,7 +74,7 @@ if (missing.names) y <- unname(y) - if (input.is.not.array && is.array(y)) + if (input.is.not.array && !input.is.data.frame && is.array(y)) y <- dropTableToVector(y) y @@ -77,9 +94,12 @@ StopForUserError("exact argument should be TRUE or FALSE") called.args <- match.call(expand.dots = FALSE) empty.ind <- providedArgumentEmpty(called.args, optional.arg = "exact") + + input.is.data.frame <- is.data.frame(x) + # Force array class for custom QTable subscripting code input.is.not.array <- !is.array(x) - if (input.is.not.array) + if (input.is.not.array && !input.is.data.frame) x <- as.array(x) DUPLICATE.LABEL.SUFFIX <- "_@_" @@ -106,10 +126,14 @@ all.unit.length <- all(lengths(evaluated.args) == 1L) valid.args <- all.unit.length && (single.arg || correct.n.args) - if (!valid.args) + if (!valid.args && !input.is.data.frame) throwErrorTableDoubleIndex(input.name, x.dim) - if (n.dim > 0 && !is.null(dimnames(x)) && is.null(names(dimnames(x)))) + if (input.is.data.frame) { + evaluated.args <- mapArgsFromDataFrame(evaluated.args, TRUE) + } + + if (n.dim > 0 && !is.null(dimnames(x)) && is.null(names(dimnames(x))) && !input.is.data.frame) x <- nameDimensionAttributes(x) missing.names <- is.null(dimnames(x)) @@ -118,8 +142,16 @@ y <- NextMethod(`[[`, x) + if (input.is.data.frame) { + x2 <- as.matrix(x) + mostattributes(x2) <- c(attributes(x2)[isBasicAttribute(names(attributes(x2)))], + attributes(x)[!isBasicAttribute(names(attributes(x)))]) + } + else + x2 <- x + # Update Attributes here - y <- updateTableAttributes(y, x, called.args, evaluated.args, drop = TRUE, missing.names) + y <- updateTableAttributes(y, x2, evaluated.args, drop = TRUE, missing.names) y <- updateNameAttribute(y, attr(x, "name"), called.args, "[[") y <- removeDeduplicationSuffixFromLabels(y, DUPLICATE.LABEL.SUFFIX) if (missing.names) @@ -239,7 +271,7 @@ throwErrorOnlyNamed <- function(named.arg, function.name) { sQuote(function.name)) } -isBasicAttribute <- function(attribute.names, basic.attr = c("dim", "names", "dimnames", "class")) { +isBasicAttribute <- function(attribute.names, basic.attr = c("dim", "names", "dimnames", "class", "row.names")) { attribute.names %in% basic.attr } @@ -259,7 +291,7 @@ IsQTableAttribute <- function(attribute.names, attribute.names %in% qtable.attrs } -updateTableAttributes <- function(y, x, called.args, evaluated.args, drop = TRUE, +updateTableAttributes <- function(y, x, evaluated.args, drop = TRUE, original.missing.names = FALSE, sep = "_@_") { class(y) <- c("QTable", class(y)) y.attributes <- attributes(y) @@ -278,7 +310,7 @@ updateTableAttributes <- function(y, x, called.args, evaluated.args, drop = TRUE y <- updateNameDimensionAttr(y, x.attributes[["dim"]]) y <- updateSpanIfNecessary(y, x.attributes, evaluated.args) y <- updateIsSubscriptedAttr(y, x) - y <- updateCellText(y, x.attributes, evaluated.args) + y <- updateCellText(y, x.attributes, evaluated.args, drop) y <- keepMappedDimnames(y) y } @@ -313,7 +345,7 @@ updateIsSubscriptedAttr <- function(y, x) { y } -updateCellText <- function(y, x.attributes, evaluated.args) { +updateCellText <- function(y, x.attributes, evaluated.args, drop) { cell.text <- x.attributes$celltext if (!is.array(cell.text)) { return (y) @@ -330,8 +362,7 @@ updateCellText <- function(y, x.attributes, evaluated.args) { evaluated.args[[i]] } }) - - subscripted <- do.call(`[`, c(list(cell.text), indices)) + subscripted <- do.call(`[`, c(list(cell.text), indices, drop = drop)) if (!is.array(subscripted)) { subscripted <- array(subscripted) @@ -1035,3 +1066,24 @@ productNameOrIsNotQ <- function() { if (is.null(product.name)) return(TRUE) length(product.name) == 1L && product.name != "Q" } + +mapArgsFromDataFrame <- function(evaluated.args, is.double.bracket) { + # single bracket, single parameter, multiple elements [x] -> [TRUE, x] + if (!is.double.bracket && length(evaluated.args) == 1) { + evaluated.args <- c(TRUE, evaluated.args) + } + + # double bracket, single parameter, single element [[x]] -> [, x] + if (is.double.bracket && length(evaluated.args) == 1 && + length(evaluated.args[[1]]) == 1) { + evaluated.args <- c(TRUE, evaluated.args) + } + + # double bracket, single parameter, double element [[x]] -> [x2, x1] + if (is.double.bracket && length(evaluated.args) == 1 && + length(evaluated.args[[1]]) == 2) { + evaluated.args <- list(evaluated.args[[1]][2], evaluated.args[[1]][1]) + } + + evaluated.args +} diff --git a/tablesAsDataFrame.rds b/tablesAsDataFrame.rds new file mode 100644 index 00000000..57c1dc8d Binary files /dev/null and b/tablesAsDataFrame.rds differ diff --git a/tests/testthat/tablesAsDataFrame.rds b/tests/testthat/tablesAsDataFrame.rds new file mode 100644 index 00000000..57c1dc8d Binary files /dev/null and b/tests/testthat/tablesAsDataFrame.rds differ diff --git a/tests/testthat/test-table-subscript.R b/tests/testthat/test-table-subscript.R index 1aa17e46..a80284c3 100644 --- a/tests/testthat/test-table-subscript.R +++ b/tests/testthat/test-table-subscript.R @@ -2581,3 +2581,51 @@ test_that("celltext attribute is correctly subscripted in tables", { expect_equal(attr(t[3:10], "celltext"), structure(c("c", "d", "e", "f", "g", "h", "i", "j"), dim = c(8L))) }) +test_that("data frame is subscripted with single bracket and single parameter", { + tbls <- readRDS("tablesAsDataFrame.rds") + + t <- tbls[["PickOneWithMultiStat"]] + + vals <- as.matrix(t) + + # single bracket, double parameter + subscripted <- t[2:3, 1] + expect_equal(as.numeric(subscripted), unname(vals[2:3,1])) + expect_equal(attr(subscripted, "celltext"), array(c("!", "!"))) + expect_equal(attr(subscripted, "QStatisticsTestingInfo"), attr(t, "QStatisticsTestingInfo")[c(3,5),]) + expect_equal(attr(subscripted, "span")$rows, structure(list(row.names(vals)[2:3]), names = "", row.names = 2:3, class = "data.frame")) + # figure out why cols is missing + # expect_equal(attr(subscripted, "span")$rows, structure(list("18 to 24"), names = "", row.names = 2L, class = "data.frame")) + + # single bracket, single parameter + subscripted <- t[1] + expect_equal(as.matrix(subscripted), vals[, 1, drop = FALSE]) + expect_equal(attr(subscripted, "celltext"), array(c(NA, "!", "!", "!", "!", "!", "!", NA, "!", "!", NA), dim = c(11, 1))) + expect_equal(attr(subscripted, "QStatisticsTestingInfo"), attr(t, "QStatisticsTestingInfo")[seq(1,21,2),]) + expect_equal(attr(subscripted, "span")$rows, attr(t, "span")$rows) + # figure out why cols is missing + + # double bracket, single parameter, single element + subscripted <- t[[2]] + expect_equal(as.numeric(subscripted), unname(vals[, 2])) + expect_equal(attr(subscripted, "celltext"), array(rep(NA_character_, 11))) + expect_equal(attr(subscripted, "QStatisticsTestingInfo"), attr(t, "QStatisticsTestingInfo")[seq(2,22,2),]) + expect_equal(attr(subscripted, "span")$rows, attr(t, "span")$rows) + # figure out why cols is missing + + # double bracket, single parameter, double element + subscripted <- t[[1:2]] + expect_equal(as.numeric(subscripted), unname(vals[2, 1])) + expect_equal(attr(subscripted, "celltext"), array("!")) + expect_equal(attr(subscripted, "QStatisticsTestingInfo"), attr(t, "QStatisticsTestingInfo")[3,]) + expect_equal(attr(subscripted, "span")$rows, structure(list("18 to 24"), names = "", row.names = 2L, class = "data.frame")) + + # double bracket, double parameter + subscripted <- t[[2, 1]] + expect_equal(as.numeric(subscripted), unname(vals[2, 1])) + expect_equal(attr(subscripted, "celltext"), array("!")) + expect_equal(attr(subscripted, "QStatisticsTestingInfo"), attr(t, "QStatisticsTestingInfo")[3,]) + expect_equal(attr(subscripted, "span")$rows, structure(list("18 to 24"), names = "", row.names = 2L, class = "data.frame")) +}) + +# devtools::test_active_file("tests/testthat/test-table-subscript.R")