| 1 |
#' Compare two rtables |
|
| 2 |
#' |
|
| 3 |
#' Prints a matrix where `.` means cell matches, `X` means cell does |
|
| 4 |
#' not match, `+` cell (row) is missing, and `-` cell (row) |
|
| 5 |
#' should not be there. If `structure` is set to `TRUE`, `C` indicates |
|
| 6 |
#' column-structure mismatch, `R` indicates row-structure mismatch, and |
|
| 7 |
#' `S` indicates mismatch in both row and column structure. |
|
| 8 |
#' |
|
| 9 |
#' @param object (`VTableTree`)\cr `rtable` to test. |
|
| 10 |
#' @param expected (`VTableTree`)\cr expected `rtable`. |
|
| 11 |
#' @param tol (`numeric(1)`)\cr tolerance. |
|
| 12 |
#' @param comp.attr (`flag`)\cr whether to compare cell formats. Other attributes are |
|
| 13 |
#' silently ignored. |
|
| 14 |
#' @param structure (`flag`)\cr whether structures (in the form of column and row |
|
| 15 |
#' paths to cells) should be compared. Currently defaults to `FALSE`, but this is |
|
| 16 |
#' subject to change in future versions. |
|
| 17 |
#' |
|
| 18 |
#' @note In its current form, `compare_rtables` does not take structure into |
|
| 19 |
#' account, only row and cell position. |
|
| 20 |
#' |
|
| 21 |
#' @return A matrix of class `rtables_diff` representing the differences |
|
| 22 |
#' between `object` and `expected` as described above. |
|
| 23 |
#' |
|
| 24 |
#' @examples |
|
| 25 |
#' t1 <- rtable(header = c("A", "B"), format = "xx", rrow("row 1", 1, 2))
|
|
| 26 |
#' t2 <- rtable(header = c("A", "B", "C"), format = "xx", rrow("row 1", 1, 2, 3))
|
|
| 27 |
#' |
|
| 28 |
#' compare_rtables(object = t1, expected = t2) |
|
| 29 |
#' |
|
| 30 |
#' if (interactive()) {
|
|
| 31 |
#' Viewer(t1, t2) |
|
| 32 |
#' } |
|
| 33 |
#' |
|
| 34 |
#' expected <- rtable( |
|
| 35 |
#' header = c("ARM A\nN=100", "ARM B\nN=200"),
|
|
| 36 |
#' format = "xx", |
|
| 37 |
#' rrow("row 1", 10, 15),
|
|
| 38 |
#' rrow(), |
|
| 39 |
#' rrow("section title"),
|
|
| 40 |
#' rrow("row colspan", rcell(c(.345543, .4432423), colspan = 2, format = "(xx.xx, xx.xx)"))
|
|
| 41 |
#' ) |
|
| 42 |
#' |
|
| 43 |
#' expected |
|
| 44 |
#' |
|
| 45 |
#' object <- rtable( |
|
| 46 |
#' header = c("ARM A\nN=100", "ARM B\nN=200"),
|
|
| 47 |
#' format = "xx", |
|
| 48 |
#' rrow("row 1", 10, 15),
|
|
| 49 |
#' rrow("section title"),
|
|
| 50 |
#' rrow("row colspan", rcell(c(.345543, .4432423), colspan = 2, format = "(xx.xx, xx.xx)"))
|
|
| 51 |
#' ) |
|
| 52 |
#' |
|
| 53 |
#' compare_rtables(object, expected, comp.attr = FALSE) |
|
| 54 |
#' |
|
| 55 |
#' object <- rtable( |
|
| 56 |
#' header = c("ARM A\nN=100", "ARM B\nN=200"),
|
|
| 57 |
#' format = "xx", |
|
| 58 |
#' rrow("row 1", 10, 15),
|
|
| 59 |
#' rrow(), |
|
| 60 |
#' rrow("section title")
|
|
| 61 |
#' ) |
|
| 62 |
#' |
|
| 63 |
#' compare_rtables(object, expected) |
|
| 64 |
#' |
|
| 65 |
#' object <- rtable( |
|
| 66 |
#' header = c("ARM A\nN=100", "ARM B\nN=200"),
|
|
| 67 |
#' format = "xx", |
|
| 68 |
#' rrow("row 1", 14, 15.03),
|
|
| 69 |
#' rrow(), |
|
| 70 |
#' rrow("section title"),
|
|
| 71 |
#' rrow("row colspan", rcell(c(.345543, .4432423), colspan = 2, format = "(xx.xx, xx.xx)"))
|
|
| 72 |
#' ) |
|
| 73 |
#' |
|
| 74 |
#' compare_rtables(object, expected) |
|
| 75 |
#' |
|
| 76 |
#' object <- rtable( |
|
| 77 |
#' header = c("ARM A\nN=100", "ARM B\nN=200"),
|
|
| 78 |
#' format = "xx", |
|
| 79 |
#' rrow("row 1", 10, 15),
|
|
| 80 |
#' rrow(), |
|
| 81 |
#' rrow("section title"),
|
|
| 82 |
#' rrow("row colspan", rcell(c(.345543, .4432423), colspan = 2, format = "(xx.x, xx.x)"))
|
|
| 83 |
#' ) |
|
| 84 |
#' |
|
| 85 |
#' compare_rtables(object, expected) |
|
| 86 |
#' |
|
| 87 |
#' @export |
|
| 88 |
compare_rtables <- function(object, expected, tol = 0.1, comp.attr = TRUE, |
|
| 89 |
structure = FALSE) {
|
|
| 90 |
# if (identical(object, expected)) return(invisible(TRUE)) |
|
| 91 | ||
| 92 | 12x |
if (!is(object, "VTableTree")) {
|
| 93 | ! |
stop( |
| 94 | ! |
"argument object is expected to be of class TableTree or ", |
| 95 | ! |
"ElementaryTable" |
| 96 |
) |
|
| 97 |
} |
|
| 98 | 12x |
if (!is(expected, "VTableTree")) {
|
| 99 | ! |
stop( |
| 100 | ! |
"argument expected is expected to be of class TableTree or ", |
| 101 | ! |
"ElementaryTable" |
| 102 |
) |
|
| 103 |
} |
|
| 104 | 12x |
dim_out <- apply(rbind(dim(object), dim(expected)), 2, max) |
| 105 | ||
| 106 | 12x |
X <- matrix(rep(".", dim_out[1] * dim_out[2]), ncol = dim_out[2])
|
| 107 | 12x |
row.names(X) <- as.character(1:dim_out[1]) |
| 108 | 12x |
colnames(X) <- as.character(1:dim_out[2]) |
| 109 | ||
| 110 | 12x |
if (!identical(names(object), names(expected))) {
|
| 111 | 7x |
attr(X, "info") <- "column names are not the same" |
| 112 |
} |
|
| 113 | ||
| 114 | 12x |
if (!comp.attr) {
|
| 115 | ! |
attr(X, "info") <- c( |
| 116 | ! |
attr(X, "info"), |
| 117 | ! |
"cell attributes have not been compared" |
| 118 |
) |
|
| 119 |
} |
|
| 120 | 12x |
if (!identical(row.names(object), row.names(expected))) {
|
| 121 | 2x |
attr(X, "info") <- c(attr(X, "info"), "row labels are not the same") |
| 122 |
} |
|
| 123 | ||
| 124 | 12x |
nro <- nrow(object) |
| 125 | 12x |
nre <- nrow(expected) |
| 126 | 12x |
nco <- ncol(object) |
| 127 | 12x |
nce <- ncol(expected) |
| 128 | ||
| 129 | 12x |
if (nco < nce) {
|
| 130 | 2x |
X[, seq(nco + 1, nce)] <- "-" |
| 131 | 10x |
} else if (nce < nco) {
|
| 132 | 3x |
X[, seq(nce + 1, nco)] <- "+" |
| 133 |
} |
|
| 134 | 12x |
if (nro < nre) {
|
| 135 | 1x |
X[seq(nro + 1, nre), ] <- "-" |
| 136 | 11x |
} else if (nre < nro) {
|
| 137 | ! |
X[seq(nre + 1, nro), ] <- "+" |
| 138 |
} |
|
| 139 | ||
| 140 | 12x |
orig_object <- object # nolint |
| 141 | 12x |
orig_expected <- expected # nolint |
| 142 | 12x |
if (nro != nre || nco != nce) {
|
| 143 | 5x |
object <- object[1:min(nro, nre), 1:min(nco, nce), drop = FALSE] |
| 144 | 5x |
expected <- expected[1:min(nro, nre), 1:min(nco, nce), drop = FALSE] |
| 145 | 5x |
inner <- compare_rtables(object, expected, tol = tol, comp.attr = comp.attr, structure = structure) |
| 146 | 5x |
X[seq_len(nrow(object)), seq_len(ncol(object))] <- inner |
| 147 | 5x |
class(X) <- c("rtables_diff", class(X))
|
| 148 | 5x |
return(X) |
| 149 |
} |
|
| 150 | ||
| 151 |
## from here dimensions match! |
|
| 152 | ||
| 153 | 7x |
orows <- cell_values(object, omit_labrows = FALSE) |
| 154 | 7x |
erows <- cell_values(expected, omit_labrows = FALSE) |
| 155 | 7x |
if (nrow(object) == 1) {
|
| 156 | ! |
orows <- list(orows) |
| 157 | ! |
erows <- list(erows) |
| 158 |
} |
|
| 159 | 7x |
res <- mapply(compare_rrows, |
| 160 | 7x |
row1 = orows, row2 = erows, tol = tol, ncol = ncol(object), |
| 161 | 7x |
USE.NAMES = FALSE, SIMPLIFY = FALSE |
| 162 |
) |
|
| 163 | 7x |
X <- do.call(rbind, res) |
| 164 | 7x |
rpo <- row_paths(object) |
| 165 | 7x |
rpe <- row_paths(expected) |
| 166 | ||
| 167 | 7x |
if (comp.attr) {
|
| 168 | 7x |
ofmts <- value_formats(object) |
| 169 | 7x |
efmts <- value_formats(expected) |
| 170 |
## dim(ofmts) <- NULL |
|
| 171 |
## dim(efmts) <- NULL |
|
| 172 | ||
| 173 | 7x |
fmt_mismatch <- which(!mapply(identical, x = ofmts, y = efmts)) ## inherently ignores dim |
| 174 | ||
| 175 | ||
| 176 |
## note the single index here!!!, no comma!!!! |
|
| 177 | 7x |
X[fmt_mismatch] <- "X" |
| 178 |
} |
|
| 179 | ||
| 180 | ||
| 181 | 7x |
if (structure) {
|
| 182 | 1x |
rp_mismatches <- !mapply(identical, x = rpo, y = rpe) |
| 183 | 1x |
cpo <- col_paths(object) |
| 184 | 1x |
cpe <- col_paths(expected) |
| 185 | 1x |
cp_mismatches <- !mapply(identical, x = cpo, y = cpe) |
| 186 | ||
| 187 | 1x |
if (any(rp_mismatches)) { # P for (row or column) path do not match
|
| 188 | ! |
X[rp_mismatches, ] <- "R" |
| 189 |
} |
|
| 190 | 1x |
if (any(cp_mismatches)) {
|
| 191 | 1x |
crep <- rep("C", nrow(X))
|
| 192 | 1x |
if (any(rp_mismatches)) {
|
| 193 | ! |
crep[rp_mismatches] <- "P" |
| 194 |
} |
|
| 195 | 1x |
X[, cp_mismatches] <- rep(crep, sum(cp_mismatches)) |
| 196 |
} |
|
| 197 |
} |
|
| 198 | 7x |
class(X) <- c("rtables_diff", class(X))
|
| 199 | 7x |
X |
| 200 |
} |
|
| 201 | ||
| 202 |
## for (i in 1:dim(X)[1]) {
|
|
| 203 |
## for (j in 1:dim(X)[2]) {
|
|
| 204 | ||
| 205 |
## is_equivalent <- TRUE |
|
| 206 |
## if (i <= nro && i <= nre && j <= nco && j <= nce) {
|
|
| 207 |
## x <- object[i,j, drop = TRUE] |
|
| 208 |
## y <- expected[i,j, drop = TRUE] |
|
| 209 | ||
| 210 |
## attr_x <- attributes(x) |
|
| 211 |
## attr_y <- attributes(y) |
|
| 212 | ||
| 213 |
## attr_x_sorted <- if (is.null(attr_x)) NULL else attr_x[order(names(attr_x))] |
|
| 214 |
## attr_y_sorted <- if (is.null(attr_y)) NULL else attr_y[order(names(attr_y))] |
|
| 215 | ||
| 216 |
## if (comp.attr && !identical(attr_x_sorted, attr_y_sorted)) {
|
|
| 217 |
## is_equivalent <- FALSE |
|
| 218 |
## } else if (is.numeric(x) && is.numeric(y)) {
|
|
| 219 |
## if (any(abs(na.omit(x - y)) > tol)) {
|
|
| 220 |
## is_equivalent <- FALSE |
|
| 221 |
## } |
|
| 222 |
## } else {
|
|
| 223 |
## if (!identical(x, y)) {
|
|
| 224 |
## is_equivalent <- FALSE |
|
| 225 |
## } |
|
| 226 |
## } |
|
| 227 | ||
| 228 |
## if (!is_equivalent) {
|
|
| 229 |
## X[i,j] <- "X" |
|
| 230 |
## } |
|
| 231 |
## } else if (i > nro || j > nco) {
|
|
| 232 |
## ## missing in object |
|
| 233 |
## X[i, j] <- "+" |
|
| 234 |
## } else {
|
|
| 235 |
## ## too many elements |
|
| 236 |
## X[i, j] <- "-" |
|
| 237 |
## } |
|
| 238 |
## } |
|
| 239 |
## } |
|
| 240 |
## class(X) <- c("rtable_diff", class(X))
|
|
| 241 |
## X |
|
| 242 |
## } |
|
| 243 | ||
| 244 |
compare_value <- function(x, y, tol) {
|
|
| 245 | 359x |
if (identical(x, y) || (is.numeric(x) && is.numeric(y) && max(abs(x - y)) <= tol)) {
|
| 246 |
"." |
|
| 247 |
} else {
|
|
| 248 | 72x |
"X" |
| 249 |
} |
|
| 250 |
} |
|
| 251 | ||
| 252 |
compare_rrows <- function(row1, row2, tol, ncol) {
|
|
| 253 | 173x |
if (length(row1) == ncol && length(row2) == ncol) {
|
| 254 | 115x |
mapply(compare_value, x = row1, y = row2, tol = tol, USE.NAMES = FALSE) |
| 255 | 58x |
} else if (length(row1) == 0 && length(row2) == 0) {
|
| 256 | 44x |
rep(".", ncol)
|
| 257 |
} else {
|
|
| 258 | 14x |
rep("X", ncol)
|
| 259 |
} |
|
| 260 |
} |
|
| 261 | ||
| 262 |
## #' @export |
|
| 263 |
## print.rtable_diff <- function(x, ...) {
|
|
| 264 |
## print.default(unclass(x), quote = FALSE, ...) |
|
| 265 |
## } |
| 1 |
#' Internal generics and methods |
|
| 2 |
#' |
|
| 3 |
#' These are internal methods that are documented only to satisfy `R CMD check`. End users should pay no |
|
| 4 |
#' attention to this documentation. |
|
| 5 |
#' |
|
| 6 |
#' @param x (`ANY`)\cr the object. |
|
| 7 |
#' @param obj (`ANY`)\cr the object. |
|
| 8 |
#' |
|
| 9 |
#' @name internal_methods |
|
| 10 |
#' @rdname int_methods |
|
| 11 |
#' @aliases int_methods |
|
| 12 |
NULL |
|
| 13 | ||
| 14 |
#' @return The number of rows (`nrow`), columns (`ncol`), or both (`dim`) of the object. |
|
| 15 |
#' |
|
| 16 |
#' @rdname dimensions |
|
| 17 |
#' @exportMethod nrow |
|
| 18 |
setMethod( |
|
| 19 |
"nrow", "VTableTree", |
|
| 20 | 2379x |
function(x) length(collect_leaves(x, TRUE, TRUE)) |
| 21 |
) |
|
| 22 | ||
| 23 |
#' @rdname int_methods |
|
| 24 |
#' @exportMethod nrow |
|
| 25 |
setMethod( |
|
| 26 |
"nrow", "TableRow", |
|
| 27 | 945x |
function(x) 1L |
| 28 |
) |
|
| 29 | ||
| 30 |
#' Table dimensions |
|
| 31 |
#' |
|
| 32 |
#' @param x (`TableTree` or `ElementaryTable`)\cr a table object. |
|
| 33 |
#' |
|
| 34 |
#' @examples |
|
| 35 |
#' lyt <- basic_table() %>% |
|
| 36 |
#' split_cols_by("ARM") %>%
|
|
| 37 |
#' analyze(c("SEX", "AGE"))
|
|
| 38 |
#' |
|
| 39 |
#' tbl <- build_table(lyt, ex_adsl) |
|
| 40 |
#' |
|
| 41 |
#' dim(tbl) |
|
| 42 |
#' nrow(tbl) |
|
| 43 |
#' ncol(tbl) |
|
| 44 |
#' |
|
| 45 |
#' NROW(tbl) |
|
| 46 |
#' NCOL(tbl) |
|
| 47 |
#' |
|
| 48 |
#' @rdname dimensions |
|
| 49 |
#' @exportMethod ncol |
|
| 50 |
setMethod( |
|
| 51 |
"ncol", "VTableNodeInfo", |
|
| 52 |
function(x) {
|
|
| 53 | 25796x |
ncol(col_info(x)) |
| 54 |
} |
|
| 55 |
) |
|
| 56 | ||
| 57 |
#' @rdname int_methods |
|
| 58 |
#' @exportMethod ncol |
|
| 59 |
setMethod( |
|
| 60 |
"ncol", "TableRow", |
|
| 61 |
function(x) {
|
|
| 62 | 75154x |
if (!no_colinfo(x)) {
|
| 63 | 74110x |
ncol(col_info(x)) |
| 64 |
} else {
|
|
| 65 | 1044x |
length(spanned_values(x)) |
| 66 |
} |
|
| 67 |
} |
|
| 68 |
) |
|
| 69 | ||
| 70 |
#' @rdname int_methods |
|
| 71 |
#' @exportMethod ncol |
|
| 72 |
setMethod( |
|
| 73 |
"ncol", "LabelRow", |
|
| 74 |
function(x) {
|
|
| 75 | 22936x |
ncol(col_info(x)) |
| 76 |
} |
|
| 77 |
) |
|
| 78 | ||
| 79 |
#' @rdname int_methods |
|
| 80 |
#' @exportMethod ncol |
|
| 81 |
setMethod( |
|
| 82 |
"ncol", "InstantiatedColumnInfo", |
|
| 83 |
function(x) {
|
|
| 84 | 125152x |
length(col_exprs(x)) |
| 85 |
} |
|
| 86 |
) |
|
| 87 | ||
| 88 |
#' @rdname dimensions |
|
| 89 |
#' @exportMethod dim |
|
| 90 |
setMethod( |
|
| 91 |
"dim", "VTableNodeInfo", |
|
| 92 | 21548x |
function(x) c(nrow(x), ncol(x)) |
| 93 |
) |
|
| 94 | ||
| 95 |
#' Retrieve or set the direct children of a tree-style object |
|
| 96 |
#' |
|
| 97 |
#' @param x (`TableTree` or `ElementaryTable`)\cr an object with a tree structure. |
|
| 98 |
#' @param value (`list`)\cr new list of children. |
|
| 99 |
#' |
|
| 100 |
#' @return A list of direct children of `x`. |
|
| 101 |
#' |
|
| 102 |
#' @export |
|
| 103 |
#' @rdname tree_children |
|
| 104 | 272116x |
setGeneric("tree_children", function(x) standardGeneric("tree_children"))
|
| 105 | ||
| 106 |
#' @exportMethod tree_children |
|
| 107 |
#' @rdname int_methods |
|
| 108 |
setMethod( |
|
| 109 |
"tree_children", c(x = "VTree"), |
|
| 110 | ! |
function(x) x@children |
| 111 |
) |
|
| 112 | ||
| 113 |
#' @exportMethod tree_children |
|
| 114 |
#' @rdname int_methods |
|
| 115 |
setMethod( |
|
| 116 |
"tree_children", c(x = "VTableTree"), |
|
| 117 | 75045x |
function(x) x@children |
| 118 |
) |
|
| 119 | ||
| 120 |
## this includes VLeaf but also allows for general methods |
|
| 121 |
## needed for table_inset being carried around by rows and |
|
| 122 |
## such. |
|
| 123 |
#' @exportMethod tree_children |
|
| 124 |
#' @rdname int_methods |
|
| 125 |
setMethod( |
|
| 126 |
"tree_children", c(x = "ANY"), ## "VLeaf"), |
|
| 127 | 14431x |
function(x) list() |
| 128 |
) |
|
| 129 | ||
| 130 |
#' @export |
|
| 131 |
#' @rdname tree_children |
|
| 132 | 63220x |
setGeneric("tree_children<-", function(x, value) standardGeneric("tree_children<-"))
|
| 133 | ||
| 134 |
#' @exportMethod tree_children<- |
|
| 135 |
#' @rdname int_methods |
|
| 136 |
setMethod( |
|
| 137 |
"tree_children<-", c(x = "VTree"), |
|
| 138 |
function(x, value) {
|
|
| 139 | ! |
x@children <- value |
| 140 | ! |
x |
| 141 |
} |
|
| 142 |
) |
|
| 143 | ||
| 144 |
#' @exportMethod tree_children<- |
|
| 145 |
#' @rdname int_methods |
|
| 146 |
setMethod( |
|
| 147 |
"tree_children<-", c(x = "VTableTree"), |
|
| 148 |
function(x, value) {
|
|
| 149 | 57284x |
x@children <- value |
| 150 | 57284x |
x |
| 151 |
} |
|
| 152 |
) |
|
| 153 | ||
| 154 |
#' Retrieve or set content table from a `TableTree` |
|
| 155 |
#' |
|
| 156 |
#' Returns the content table of `obj` if it is a `TableTree` object, or `NULL` otherwise. |
|
| 157 |
#' |
|
| 158 |
#' @param obj (`TableTree`)\cr the table object. |
|
| 159 |
#' |
|
| 160 |
#' @return the `ElementaryTable` containing the (top level) *content rows* of `obj` (or `NULL` if `obj` is not |
|
| 161 |
#' a formal table object). |
|
| 162 |
#' |
|
| 163 |
#' @export |
|
| 164 |
#' @rdname content_table |
|
| 165 | 100073x |
setGeneric("content_table", function(obj) standardGeneric("content_table"))
|
| 166 | ||
| 167 |
#' @exportMethod content_table |
|
| 168 |
#' @rdname int_methods |
|
| 169 |
setMethod( |
|
| 170 |
"content_table", "TableTree", |
|
| 171 | 63874x |
function(obj) obj@content |
| 172 |
) |
|
| 173 | ||
| 174 |
#' @exportMethod content_table |
|
| 175 |
#' @rdname int_methods |
|
| 176 |
setMethod( |
|
| 177 |
"content_table", "ANY", |
|
| 178 | 12925x |
function(obj) NULL |
| 179 |
) |
|
| 180 | ||
| 181 |
#' @param value (`ElementaryTable`)\cr the new content table for `obj`. |
|
| 182 |
#' |
|
| 183 |
#' @export |
|
| 184 |
#' @rdname content_table |
|
| 185 | 6856x |
setGeneric("content_table<-", function(obj, value) standardGeneric("content_table<-"))
|
| 186 | ||
| 187 |
#' @exportMethod "content_table<-" |
|
| 188 |
#' @rdname int_methods |
|
| 189 |
setMethod( |
|
| 190 |
"content_table<-", c("TableTree", "ElementaryTable"),
|
|
| 191 |
function(obj, value) {
|
|
| 192 | 6856x |
obj@content <- value |
| 193 | 6856x |
obj |
| 194 |
} |
|
| 195 |
) |
|
| 196 | ||
| 197 |
#' @param for_analyze (`flag`) whether split is an analyze split. |
|
| 198 |
#' @rdname int_methods |
|
| 199 | 1313x |
setGeneric("next_rpos", function(obj, nested = TRUE, for_analyze = FALSE) standardGeneric("next_rpos"))
|
| 200 | ||
| 201 |
#' @rdname int_methods |
|
| 202 |
setMethod( |
|
| 203 |
"next_rpos", "PreDataTableLayouts", |
|
| 204 |
function(obj, nested, for_analyze = FALSE) next_rpos(rlayout(obj), nested, for_analyze = for_analyze) |
|
| 205 |
) |
|
| 206 | ||
| 207 |
.check_if_nest <- function(obj, nested, for_analyze) {
|
|
| 208 | 311x |
if (!nested) {
|
| 209 | 24x |
FALSE |
| 210 |
} else {
|
|
| 211 |
## can always nest analyze splits (almost? what about colvars noncolvars mixing? prolly ok?) |
|
| 212 | 287x |
for_analyze || |
| 213 |
## If its not an analyze split it can't go under an analyze split |
|
| 214 | 287x |
!(is(last_rowsplit(obj), "VAnalyzeSplit") || |
| 215 | 287x |
is(last_rowsplit(obj), "AnalyzeMultiVars")) ## should this be CompoundSplit? # nolint |
| 216 |
} |
|
| 217 |
} |
|
| 218 | ||
| 219 |
#' @rdname int_methods |
|
| 220 |
setMethod( |
|
| 221 |
"next_rpos", "PreDataRowLayout", |
|
| 222 |
function(obj, nested, for_analyze) {
|
|
| 223 | 656x |
l <- length(obj) |
| 224 | 656x |
if (length(obj[[l]]) > 0L && !.check_if_nest(obj, nested, for_analyze)) {
|
| 225 | 39x |
l <- l + 1L |
| 226 |
} |
|
| 227 | 656x |
l |
| 228 |
} |
|
| 229 |
) |
|
| 230 | ||
| 231 |
#' @rdname int_methods |
|
| 232 | 1x |
setMethod("next_rpos", "ANY", function(obj, nested) 1L)
|
| 233 | ||
| 234 |
#' @rdname int_methods |
|
| 235 | 671x |
setGeneric("next_cpos", function(obj, nested = TRUE) standardGeneric("next_cpos"))
|
| 236 | ||
| 237 |
#' @rdname int_methods |
|
| 238 |
setMethod( |
|
| 239 |
"next_cpos", "PreDataTableLayouts", |
|
| 240 |
function(obj, nested) next_cpos(clayout(obj), nested) |
|
| 241 |
) |
|
| 242 | ||
| 243 |
#' @rdname int_methods |
|
| 244 |
setMethod( |
|
| 245 |
"next_cpos", "PreDataColLayout", |
|
| 246 |
function(obj, nested) {
|
|
| 247 | 335x |
if (nested || length(obj[[length(obj)]]) == 0) {
|
| 248 | 326x |
length(obj) |
| 249 |
} else {
|
|
| 250 | 9x |
length(obj) + 1L |
| 251 |
} |
|
| 252 |
} |
|
| 253 |
) |
|
| 254 | ||
| 255 |
#' @rdname int_methods |
|
| 256 |
setMethod("next_cpos", "ANY", function(obj, nested) 1L)
|
|
| 257 | ||
| 258 |
#' @rdname int_methods |
|
| 259 | 3070x |
setGeneric("last_rowsplit", function(obj) standardGeneric("last_rowsplit"))
|
| 260 | ||
| 261 |
#' @rdname int_methods |
|
| 262 |
setMethod( |
|
| 263 |
"last_rowsplit", "NULL", |
|
| 264 | ! |
function(obj) NULL |
| 265 |
) |
|
| 266 | ||
| 267 |
#' @rdname int_methods |
|
| 268 |
setMethod( |
|
| 269 |
"last_rowsplit", "SplitVector", |
|
| 270 |
function(obj) {
|
|
| 271 | 1206x |
if (length(obj) == 0) {
|
| 272 | 256x |
NULL |
| 273 |
} else {
|
|
| 274 | 950x |
obj[[length(obj)]] |
| 275 |
} |
|
| 276 |
} |
|
| 277 |
) |
|
| 278 | ||
| 279 |
#' @rdname int_methods |
|
| 280 |
setMethod( |
|
| 281 |
"last_rowsplit", "PreDataRowLayout", |
|
| 282 |
function(obj) {
|
|
| 283 | 1206x |
if (length(obj) == 0) {
|
| 284 | ! |
NULL |
| 285 |
} else {
|
|
| 286 | 1206x |
last_rowsplit(obj[[length(obj)]]) |
| 287 |
} |
|
| 288 |
} |
|
| 289 |
) |
|
| 290 | ||
| 291 |
#' @rdname int_methods |
|
| 292 |
setMethod( |
|
| 293 |
"last_rowsplit", "PreDataTableLayouts", |
|
| 294 | 656x |
function(obj) last_rowsplit(rlayout(obj)) |
| 295 |
) |
|
| 296 | ||
| 297 |
# rlayout ---- |
|
| 298 |
## TODO maybe export these? |
|
| 299 | ||
| 300 |
#' @rdname int_methods |
|
| 301 | 4374x |
setGeneric("rlayout", function(obj) standardGeneric("rlayout"))
|
| 302 | ||
| 303 |
#' @rdname int_methods |
|
| 304 |
setMethod( |
|
| 305 |
"rlayout", "PreDataTableLayouts", |
|
| 306 | 4374x |
function(obj) obj@row_layout |
| 307 |
) |
|
| 308 | ||
| 309 |
#' @rdname int_methods |
|
| 310 | ! |
setMethod("rlayout", "ANY", function(obj) PreDataRowLayout())
|
| 311 | ||
| 312 |
#' @rdname int_methods |
|
| 313 | 1942x |
setGeneric("rlayout<-", function(object, value) standardGeneric("rlayout<-"))
|
| 314 | ||
| 315 |
#' @rdname int_methods |
|
| 316 |
setMethod( |
|
| 317 |
"rlayout<-", "PreDataTableLayouts", |
|
| 318 |
function(object, value) {
|
|
| 319 | 1942x |
object@row_layout <- value |
| 320 | 1942x |
object |
| 321 |
} |
|
| 322 |
) |
|
| 323 | ||
| 324 |
#' @rdname int_methods |
|
| 325 | 68384x |
setGeneric("tree_pos", function(obj) standardGeneric("tree_pos"))
|
| 326 | ||
| 327 |
## setMethod("tree_pos", "VNodeInfo",
|
|
| 328 |
## function(obj) obj@pos_in_tree) |
|
| 329 | ||
| 330 |
#' @rdname int_methods |
|
| 331 |
setMethod( |
|
| 332 |
"tree_pos", "VLayoutNode", |
|
| 333 | ! |
function(obj) obj@pos_in_tree |
| 334 |
) |
|
| 335 | ||
| 336 |
#' @rdname int_methods |
|
| 337 | 1510x |
setGeneric("pos_subset", function(obj) standardGeneric("pos_subset"))
|
| 338 | ||
| 339 |
#' @rdname int_methods |
|
| 340 |
setMethod( |
|
| 341 |
"pos_subset", "TreePos", |
|
| 342 | 1510x |
function(obj) obj@subset |
| 343 |
) |
|
| 344 | ||
| 345 |
#' @rdname int_methods |
|
| 346 | 101x |
setGeneric("tree_pos<-", function(obj, value) standardGeneric("tree_pos<-"))
|
| 347 | ||
| 348 |
#' @rdname int_methods |
|
| 349 |
setMethod( |
|
| 350 |
"tree_pos<-", "VLayoutNode", |
|
| 351 |
function(obj, value) {
|
|
| 352 | 101x |
obj@pos_in_tree <- value |
| 353 | 101x |
obj |
| 354 |
} |
|
| 355 |
) |
|
| 356 | ||
| 357 |
## setMethod("pos_subset", "VNodeInfo",
|
|
| 358 |
## function(obj) pos_subset(tree_pos(obj))) |
|
| 359 | ||
| 360 |
#' @rdname int_methods |
|
| 361 |
setMethod( |
|
| 362 |
"pos_subset", "VLayoutNode", |
|
| 363 | ! |
function(obj) pos_subset(tree_pos(obj)) |
| 364 |
) |
|
| 365 | ||
| 366 |
#' @rdname int_methods |
|
| 367 | 55508x |
setGeneric("pos_splits", function(obj) standardGeneric("pos_splits"))
|
| 368 | ||
| 369 |
#' @rdname int_methods |
|
| 370 |
setMethod( |
|
| 371 |
"pos_splits", "TreePos", |
|
| 372 | 55508x |
function(obj) obj@splits |
| 373 |
) |
|
| 374 | ||
| 375 |
## setMethod("pos_splits", "VNodeInfo",
|
|
| 376 |
## function(obj) pos_splits(tree_pos(obj))) |
|
| 377 | ||
| 378 |
#' @rdname int_methods |
|
| 379 |
setMethod( |
|
| 380 |
"pos_splits", "VLayoutNode", |
|
| 381 | ! |
function(obj) pos_splits(tree_pos(obj)) |
| 382 |
) |
|
| 383 | ||
| 384 |
#' @rdname int_methods |
|
| 385 | 101x |
setGeneric("pos_splits<-", function(obj, value) standardGeneric("pos_splits<-"))
|
| 386 | ||
| 387 |
#' @rdname int_methods |
|
| 388 |
setMethod( |
|
| 389 |
"pos_splits<-", "TreePos", |
|
| 390 |
function(obj, value) {
|
|
| 391 | 101x |
obj@splits <- value |
| 392 | 101x |
obj |
| 393 |
} |
|
| 394 |
) |
|
| 395 | ||
| 396 |
#' @rdname int_methods |
|
| 397 |
setMethod( |
|
| 398 |
"pos_splits<-", "VLayoutNode", |
|
| 399 |
function(obj, value) {
|
|
| 400 | ! |
pos <- tree_pos(obj) |
| 401 | ! |
pos_splits(pos) <- value |
| 402 | ! |
tree_pos(obj) <- pos |
| 403 | ! |
obj |
| 404 | ! |
obj |
| 405 |
} |
|
| 406 |
) |
|
| 407 | ||
| 408 | ||
| 409 | ||
| 410 | ||
| 411 |
#' @rdname int_methods |
|
| 412 | 62345x |
setGeneric("pos_splvals", function(obj) standardGeneric("pos_splvals"))
|
| 413 | ||
| 414 |
#' @rdname int_methods |
|
| 415 |
setMethod( |
|
| 416 |
"pos_splvals", "TreePos", |
|
| 417 | 62345x |
function(obj) obj@s_values |
| 418 |
) |
|
| 419 | ||
| 420 |
## setMethod("pos_splvals", "VNodeInfo",
|
|
| 421 |
## function(obj) pos_splvals(tree_pos(obj))) |
|
| 422 | ||
| 423 |
#' @rdname int_methods |
|
| 424 |
setMethod( |
|
| 425 |
"pos_splvals", "VLayoutNode", |
|
| 426 | ! |
function(obj) pos_splvals(tree_pos(obj)) |
| 427 |
) |
|
| 428 | ||
| 429 |
#' @rdname int_methods |
|
| 430 | 101x |
setGeneric("pos_splvals<-", function(obj, value) standardGeneric("pos_splvals<-"))
|
| 431 | ||
| 432 |
#' @rdname int_methods |
|
| 433 |
setMethod( |
|
| 434 |
"pos_splvals<-", "TreePos", |
|
| 435 |
function(obj, value) {
|
|
| 436 | 101x |
obj@s_values <- value |
| 437 | 101x |
obj |
| 438 |
} |
|
| 439 |
) |
|
| 440 | ||
| 441 |
## setMethod("pos_splvals", "VNodeInfo",
|
|
| 442 |
## function(obj) pos_splvals(tree_pos(obj))) |
|
| 443 | ||
| 444 |
#' @rdname int_methods |
|
| 445 |
setMethod( |
|
| 446 |
"pos_splvals<-", "VLayoutNode", |
|
| 447 |
function(obj, value) {
|
|
| 448 | ! |
pos <- tree_pos(obj) |
| 449 | ! |
pos_splvals(pos) <- value |
| 450 | ! |
tree_pos(obj) <- pos |
| 451 | ! |
obj |
| 452 |
} |
|
| 453 |
) |
|
| 454 | ||
| 455 | ||
| 456 |
#' @rdname int_methods |
|
| 457 | 1510x |
setGeneric("pos_splval_labels", function(obj) standardGeneric("pos_splval_labels"))
|
| 458 | ||
| 459 |
#' @rdname int_methods |
|
| 460 |
setMethod( |
|
| 461 |
"pos_splval_labels", "TreePos", |
|
| 462 | 1510x |
function(obj) obj@sval_labels |
| 463 |
) |
|
| 464 |
## no longer used |
|
| 465 | ||
| 466 |
## setMethod("pos_splval_labels", "VNodeInfo",
|
|
| 467 |
## function(obj) pos_splval_labels(tree_pos(obj))) |
|
| 468 |
## #' @rdname int_methods |
|
| 469 |
## setMethod("pos_splval_labels", "VLayoutNode",
|
|
| 470 |
## function(obj) pos_splval_labels(tree_pos(obj))) |
|
| 471 | ||
| 472 |
#' @rdname int_methods |
|
| 473 | 16855x |
setGeneric("spl_payload", function(obj) standardGeneric("spl_payload"))
|
| 474 | ||
| 475 |
#' @rdname int_methods |
|
| 476 | 16855x |
setMethod("spl_payload", "Split", function(obj) obj@payload)
|
| 477 | ||
| 478 |
#' @rdname int_methods |
|
| 479 | 3x |
setGeneric("spl_payload<-", function(obj, value) standardGeneric("spl_payload<-"))
|
| 480 | ||
| 481 |
#' @rdname int_methods |
|
| 482 |
setMethod("spl_payload<-", "Split", function(obj, value) {
|
|
| 483 | 3x |
obj@payload <- value |
| 484 | 3x |
obj |
| 485 |
}) |
|
| 486 | ||
| 487 |
#' @rdname int_methods |
|
| 488 | 778x |
setGeneric("spl_label_var", function(obj) standardGeneric("spl_label_var"))
|
| 489 | ||
| 490 |
#' @rdname int_methods |
|
| 491 | 775x |
setMethod("spl_label_var", "VarLevelSplit", function(obj) obj@value_label_var)
|
| 492 | ||
| 493 |
## TODO revisit. do we want to do this? used in vars_in_layout, but only |
|
| 494 |
## for convenience. |
|
| 495 |
#' @rdname int_methods |
|
| 496 | 3x |
setMethod("spl_label_var", "Split", function(obj) NULL)
|
| 497 | ||
| 498 |
### name related things |
|
| 499 |
# #' @inherit formatters::formatter_methods |
|
| 500 |
#' Methods for generics in the `formatters` package |
|
| 501 |
#' |
|
| 502 |
#' See the `formatters` documentation for descriptions of these generics. |
|
| 503 |
#' |
|
| 504 |
#' @inheritParams gen_args |
|
| 505 |
#' |
|
| 506 |
#' @return |
|
| 507 |
#' * Accessor functions return the current value of the component being accessed of `obj` |
|
| 508 |
#' * Setter functions return a modified copy of `obj` with the new value. |
|
| 509 |
#' |
|
| 510 |
#' @rdname formatters_methods |
|
| 511 |
#' @aliases formatters_methods |
|
| 512 |
#' @exportMethod obj_name |
|
| 513 |
setMethod( |
|
| 514 |
"obj_name", "VNodeInfo", |
|
| 515 | 70522x |
function(obj) obj@name |
| 516 |
) |
|
| 517 | ||
| 518 |
#' @rdname formatters_methods |
|
| 519 |
#' @exportMethod obj_name |
|
| 520 |
setMethod( |
|
| 521 |
"obj_name", "Split", |
|
| 522 | 120957x |
function(obj) obj@name |
| 523 |
) |
|
| 524 | ||
| 525 |
#' @rdname formatters_methods |
|
| 526 |
#' @exportMethod obj_name<- |
|
| 527 |
setMethod( |
|
| 528 |
"obj_name<-", "VNodeInfo", |
|
| 529 |
function(obj, value) {
|
|
| 530 | 54x |
obj@name <- value |
| 531 | 54x |
obj |
| 532 |
} |
|
| 533 |
) |
|
| 534 | ||
| 535 |
#' @rdname formatters_methods |
|
| 536 |
#' @exportMethod obj_name<- |
|
| 537 |
setMethod( |
|
| 538 |
"obj_name<-", "Split", |
|
| 539 |
function(obj, value) {
|
|
| 540 | 3x |
obj@name <- value |
| 541 | 3x |
obj |
| 542 |
} |
|
| 543 |
) |
|
| 544 | ||
| 545 |
### Label related things |
|
| 546 |
#' @rdname formatters_methods |
|
| 547 |
#' @exportMethod obj_label |
|
| 548 | 2461x |
setMethod("obj_label", "Split", function(obj) obj@split_label)
|
| 549 | ||
| 550 |
#' @rdname formatters_methods |
|
| 551 |
#' @exportMethod obj_label |
|
| 552 | 47458x |
setMethod("obj_label", "TableRow", function(obj) obj@label)
|
| 553 | ||
| 554 |
## XXX Do we want a convenience for VTableTree that |
|
| 555 |
## grabs the label from the LabelRow or will |
|
| 556 |
## that just muddy the waters? |
|
| 557 |
#' @rdname formatters_methods |
|
| 558 |
#' @exportMethod obj_label |
|
| 559 |
setMethod( |
|
| 560 |
"obj_label", "VTableTree", |
|
| 561 | 348x |
function(obj) obj_label(tt_labelrow(obj)) |
| 562 |
) |
|
| 563 | ||
| 564 |
#' @rdname formatters_methods |
|
| 565 |
#' @exportMethod obj_label |
|
| 566 | ! |
setMethod("obj_label", "ValueWrapper", function(obj) obj@label)
|
| 567 | ||
| 568 |
#' @rdname formatters_methods |
|
| 569 |
#' @exportMethod obj_label<- |
|
| 570 |
setMethod( |
|
| 571 |
"obj_label<-", "Split", |
|
| 572 |
function(obj, value) {
|
|
| 573 | 1x |
obj@split_label <- value |
| 574 | 1x |
obj |
| 575 |
} |
|
| 576 |
) |
|
| 577 | ||
| 578 |
#' @rdname formatters_methods |
|
| 579 |
#' @exportMethod obj_label<- |
|
| 580 |
setMethod( |
|
| 581 |
"obj_label<-", "TableRow", |
|
| 582 |
function(obj, value) {
|
|
| 583 | 32x |
obj@label <- value |
| 584 | 32x |
obj |
| 585 |
} |
|
| 586 |
) |
|
| 587 | ||
| 588 |
#' @rdname formatters_methods |
|
| 589 |
#' @exportMethod obj_label<- |
|
| 590 |
setMethod( |
|
| 591 |
"obj_label<-", "ValueWrapper", |
|
| 592 |
function(obj, value) {
|
|
| 593 | ! |
obj@label <- value |
| 594 | ! |
obj |
| 595 |
} |
|
| 596 |
) |
|
| 597 | ||
| 598 |
#' @rdname formatters_methods |
|
| 599 |
#' @exportMethod obj_label<- |
|
| 600 |
setMethod( |
|
| 601 |
"obj_label<-", "VTableTree", |
|
| 602 |
function(obj, value) {
|
|
| 603 | 11x |
lr <- tt_labelrow(obj) |
| 604 | 11x |
obj_label(lr) <- value |
| 605 | 11x |
if (!is.na(value) && nzchar(value)) {
|
| 606 | 10x |
labelrow_visible(lr) <- TRUE |
| 607 | 1x |
} else if (is.na(value)) {
|
| 608 | 1x |
labelrow_visible(lr) <- FALSE |
| 609 |
} |
|
| 610 | 11x |
tt_labelrow(obj) <- lr |
| 611 | 11x |
obj |
| 612 |
} |
|
| 613 |
) |
|
| 614 | ||
| 615 |
### Label rows. |
|
| 616 |
#' @rdname int_methods |
|
| 617 | 144524x |
setGeneric("tt_labelrow", function(obj) standardGeneric("tt_labelrow"))
|
| 618 | ||
| 619 |
#' @rdname int_methods |
|
| 620 |
setMethod( |
|
| 621 |
"tt_labelrow", "VTableTree", |
|
| 622 | 50078x |
function(obj) obj@labelrow |
| 623 |
) |
|
| 624 | ||
| 625 |
#' @rdname int_methods |
|
| 626 | 4343x |
setGeneric("tt_labelrow<-", function(obj, value) standardGeneric("tt_labelrow<-"))
|
| 627 | ||
| 628 |
#' @rdname int_methods |
|
| 629 |
setMethod( |
|
| 630 |
"tt_labelrow<-", c("VTableTree", "LabelRow"),
|
|
| 631 |
function(obj, value) {
|
|
| 632 | 4343x |
if (no_colinfo(value)) {
|
| 633 | 1x |
col_info(value) <- col_info(obj) |
| 634 |
} |
|
| 635 | 4343x |
obj@labelrow <- value |
| 636 | 4343x |
obj |
| 637 |
} |
|
| 638 |
) |
|
| 639 | ||
| 640 |
#' @rdname int_methods |
|
| 641 | 221979x |
setGeneric("labelrow_visible", function(obj) standardGeneric("labelrow_visible"))
|
| 642 | ||
| 643 |
#' @rdname int_methods |
|
| 644 |
setMethod( |
|
| 645 |
"labelrow_visible", "VTableTree", |
|
| 646 |
function(obj) {
|
|
| 647 | 31672x |
labelrow_visible(tt_labelrow(obj)) |
| 648 |
} |
|
| 649 |
) |
|
| 650 | ||
| 651 |
#' @rdname int_methods |
|
| 652 |
setMethod( |
|
| 653 |
"labelrow_visible", "LabelRow", |
|
| 654 | 121136x |
function(obj) obj@visible |
| 655 |
) |
|
| 656 | ||
| 657 |
#' @rdname int_methods |
|
| 658 |
setMethod( |
|
| 659 |
"labelrow_visible", "VAnalyzeSplit", |
|
| 660 | 1619x |
function(obj) .labelkids_helper(obj@var_label_position) |
| 661 |
) |
|
| 662 | ||
| 663 |
#' @rdname int_methods |
|
| 664 | 3318x |
setGeneric("labelrow_visible<-", function(obj, value) standardGeneric("labelrow_visible<-"))
|
| 665 | ||
| 666 |
#' @rdname int_methods |
|
| 667 |
setMethod( |
|
| 668 |
"labelrow_visible<-", "VTableTree", |
|
| 669 |
function(obj, value) {
|
|
| 670 | 1502x |
lr <- tt_labelrow(obj) |
| 671 | 1502x |
labelrow_visible(lr) <- value |
| 672 | 1502x |
tt_labelrow(obj) <- lr |
| 673 | 1502x |
obj |
| 674 |
} |
|
| 675 |
) |
|
| 676 | ||
| 677 |
#' @rdname int_methods |
|
| 678 |
setMethod( |
|
| 679 |
"labelrow_visible<-", "LabelRow", |
|
| 680 |
function(obj, value) {
|
|
| 681 | 1513x |
obj@visible <- value |
| 682 | 1513x |
obj |
| 683 |
} |
|
| 684 |
) |
|
| 685 | ||
| 686 |
#' @rdname int_methods |
|
| 687 |
setMethod( |
|
| 688 |
"labelrow_visible<-", "VAnalyzeSplit", |
|
| 689 |
function(obj, value) {
|
|
| 690 | 303x |
obj@var_label_position <- value |
| 691 | 303x |
obj |
| 692 |
} |
|
| 693 |
) |
|
| 694 | ||
| 695 |
## TRUE is always, FALSE is never, NA is only when no |
|
| 696 |
## content function (or rows in an instantiated table) is present |
|
| 697 |
#' @rdname int_methods |
|
| 698 | 1749x |
setGeneric("label_kids", function(spl) standardGeneric("label_kids"))
|
| 699 | ||
| 700 |
#' @rdname int_methods |
|
| 701 | 1749x |
setMethod("label_kids", "Split", function(spl) spl@label_children)
|
| 702 | ||
| 703 |
#' @rdname int_methods |
|
| 704 | 3x |
setGeneric("label_kids<-", function(spl, value) standardGeneric("label_kids<-"))
|
| 705 | ||
| 706 |
#' @rdname int_methods |
|
| 707 |
setMethod("label_kids<-", c("Split", "character"), function(spl, value) {
|
|
| 708 | 1x |
label_kids(spl) <- .labelkids_helper(value) |
| 709 | 1x |
spl |
| 710 |
}) |
|
| 711 | ||
| 712 |
#' @rdname int_methods |
|
| 713 |
setMethod("label_kids<-", c("Split", "logical"), function(spl, value) {
|
|
| 714 | 2x |
spl@label_children <- value |
| 715 | 2x |
spl |
| 716 |
}) |
|
| 717 | ||
| 718 |
#' @rdname int_methods |
|
| 719 | 464x |
setGeneric("vis_label", function(spl) standardGeneric("vis_label"))
|
| 720 | ||
| 721 |
#' @rdname int_methods |
|
| 722 |
setMethod("vis_label", "Split", function(spl) {
|
|
| 723 | 464x |
.labelkids_helper(label_position(spl)) |
| 724 |
}) |
|
| 725 | ||
| 726 |
## #' @rdname int_methods |
|
| 727 |
## setGeneric("vis_label<-", function(spl, value) standardGeneric("vis_label<-"))
|
|
| 728 |
## #' @rdname int_methods |
|
| 729 |
## setMethod("vis_label<-", "Split", function(spl, value) {
|
|
| 730 |
## stop("defunct")
|
|
| 731 |
## if(is.na(value)) |
|
| 732 |
## stop("split label visibility must be TRUE or FALSE, got NA")
|
|
| 733 |
## # spl@split_label_visible <- value |
|
| 734 |
## spl |
|
| 735 |
## }) |
|
| 736 | ||
| 737 |
#' @rdname int_methods |
|
| 738 | 1204x |
setGeneric("label_position", function(spl) standardGeneric("label_position"))
|
| 739 | ||
| 740 |
#' @rdname int_methods |
|
| 741 | 826x |
setMethod("label_position", "Split", function(spl) spl@split_label_position)
|
| 742 | ||
| 743 |
#' @rdname int_methods |
|
| 744 | 378x |
setMethod("label_position", "VAnalyzeSplit", function(spl) spl@var_label_position) ## split_label_position)
|
| 745 | ||
| 746 |
#' @rdname int_methods |
|
| 747 | 58x |
setGeneric("label_position<-", function(spl, value) standardGeneric("label_position<-"))
|
| 748 | ||
| 749 |
#' @rdname int_methods |
|
| 750 |
setMethod("label_position<-", "Split", function(spl, value) {
|
|
| 751 | 58x |
value <- match.arg(value, valid_lbl_pos) |
| 752 | 58x |
spl@split_label_position <- value |
| 753 | 58x |
spl |
| 754 |
}) |
|
| 755 | ||
| 756 |
### Function accessors (summary, tabulation and split) ---- |
|
| 757 | ||
| 758 |
#' @rdname int_methods |
|
| 759 | 3762x |
setGeneric("content_fun", function(obj) standardGeneric("content_fun"))
|
| 760 | ||
| 761 |
#' @rdname int_methods |
|
| 762 | 3710x |
setMethod("content_fun", "Split", function(obj) obj@content_fun)
|
| 763 | ||
| 764 |
#' @rdname int_methods |
|
| 765 | 125x |
setGeneric("content_fun<-", function(object, value) standardGeneric("content_fun<-"))
|
| 766 | ||
| 767 |
#' @rdname int_methods |
|
| 768 |
setMethod("content_fun<-", "Split", function(object, value) {
|
|
| 769 | 125x |
object@content_fun <- value |
| 770 | 125x |
object |
| 771 |
}) |
|
| 772 | ||
| 773 |
#' @rdname int_methods |
|
| 774 | 1987x |
setGeneric("analysis_fun", function(obj) standardGeneric("analysis_fun"))
|
| 775 | ||
| 776 |
#' @rdname int_methods |
|
| 777 | 1888x |
setMethod("analysis_fun", "AnalyzeVarSplit", function(obj) obj@analysis_fun)
|
| 778 | ||
| 779 |
#' @rdname int_methods |
|
| 780 | 99x |
setMethod("analysis_fun", "AnalyzeColVarSplit", function(obj) obj@analysis_fun)
|
| 781 | ||
| 782 |
## not used and probably not needed |
|
| 783 |
## #' @rdname int_methods |
|
| 784 |
## setGeneric("analysis_fun<-", function(object, value) standardGeneric("analysis_fun<-"))
|
|
| 785 | ||
| 786 |
## #' @rdname int_methods |
|
| 787 |
## setMethod("analysis_fun<-", "AnalyzeVarSplit", function(object, value) {
|
|
| 788 |
## object@analysis_fun <- value |
|
| 789 |
## object |
|
| 790 |
## }) |
|
| 791 |
## #' @rdname int_methods |
|
| 792 |
## setMethod("analysis_fun<-", "AnalyzeColVarSplit", function(object, value) {
|
|
| 793 |
## if(is(value, "function")) |
|
| 794 |
## value <- list(value) |
|
| 795 |
## object@analysis_fun <- value |
|
| 796 |
## object |
|
| 797 |
## }) |
|
| 798 | ||
| 799 |
#' @rdname int_methods |
|
| 800 | 1224x |
setGeneric("split_fun", function(obj) standardGeneric("split_fun"))
|
| 801 | ||
| 802 |
#' @rdname int_methods |
|
| 803 | 1013x |
setMethod("split_fun", "CustomizableSplit", function(obj) obj@split_fun)
|
| 804 | ||
| 805 |
## Only that type of split currently has the slot |
|
| 806 |
## this should probably change? for now define |
|
| 807 |
## an accessor that just returns NULL |
|
| 808 |
#' @rdname int_methods |
|
| 809 | 159x |
setMethod("split_fun", "Split", function(obj) NULL)
|
| 810 | ||
| 811 |
#' @rdname int_methods |
|
| 812 | 13x |
setGeneric("split_fun<-", function(obj, value) standardGeneric("split_fun<-"))
|
| 813 | ||
| 814 |
#' @rdname int_methods |
|
| 815 |
setMethod("split_fun<-", "CustomizableSplit", function(obj, value) {
|
|
| 816 | 13x |
obj@split_fun <- value |
| 817 | 13x |
obj |
| 818 |
}) |
|
| 819 | ||
| 820 |
# nocov start |
|
| 821 |
## Only that type of split currently has the slot |
|
| 822 |
## this should probably change? for now define |
|
| 823 |
## an accessor that just returns NULL |
|
| 824 |
#' @rdname int_methods |
|
| 825 |
setMethod( |
|
| 826 |
"split_fun<-", "Split", |
|
| 827 |
function(obj, value) {
|
|
| 828 |
stop( |
|
| 829 |
"Attempted to set a custom split function on a non-customizable split.", |
|
| 830 |
"This should not happen, please contact the maintainers." |
|
| 831 |
) |
|
| 832 |
} |
|
| 833 |
) |
|
| 834 |
# nocov end |
|
| 835 | ||
| 836 |
## Content specification related accessors ---- |
|
| 837 | ||
| 838 |
#' @rdname int_methods |
|
| 839 | 509x |
setGeneric("content_extra_args", function(obj) standardGeneric("content_extra_args"))
|
| 840 | ||
| 841 |
#' @rdname int_methods |
|
| 842 | 509x |
setMethod("content_extra_args", "Split", function(obj) obj@content_extra_args)
|
| 843 | ||
| 844 |
#' @rdname int_methods |
|
| 845 | 125x |
setGeneric("content_extra_args<-", function(object, value) standardGeneric("content_extra_args<-"))
|
| 846 | ||
| 847 |
#' @rdname int_methods |
|
| 848 |
setMethod("content_extra_args<-", "Split", function(object, value) {
|
|
| 849 | 125x |
object@content_extra_args <- value |
| 850 | 125x |
object |
| 851 |
}) |
|
| 852 | ||
| 853 |
#' @rdname int_methods |
|
| 854 | 2112x |
setGeneric("content_var", function(obj) standardGeneric("content_var"))
|
| 855 | ||
| 856 |
#' @rdname int_methods |
|
| 857 | 2112x |
setMethod("content_var", "Split", function(obj) obj@content_var)
|
| 858 | ||
| 859 |
#' @rdname int_methods |
|
| 860 | 125x |
setGeneric("content_var<-", function(object, value) standardGeneric("content_var<-"))
|
| 861 | ||
| 862 |
#' @rdname int_methods |
|
| 863 |
setMethod("content_var<-", "Split", function(object, value) {
|
|
| 864 | 125x |
object@content_var <- value |
| 865 | 125x |
object |
| 866 |
}) |
|
| 867 | ||
| 868 |
### Miscellaneous accessors ---- |
|
| 869 | ||
| 870 |
#' @rdname int_methods |
|
| 871 | 1308x |
setGeneric("avar_inclNAs", function(obj) standardGeneric("avar_inclNAs"))
|
| 872 | ||
| 873 |
#' @rdname int_methods |
|
| 874 |
setMethod( |
|
| 875 |
"avar_inclNAs", "VAnalyzeSplit", |
|
| 876 | 1308x |
function(obj) obj@include_NAs |
| 877 |
) |
|
| 878 | ||
| 879 |
#' @rdname int_methods |
|
| 880 | ! |
setGeneric("avar_inclNAs<-", function(obj, value) standardGeneric("avar_inclNAs<-"))
|
| 881 | ||
| 882 |
#' @rdname int_methods |
|
| 883 |
setMethod( |
|
| 884 |
"avar_inclNAs<-", "VAnalyzeSplit", |
|
| 885 |
function(obj, value) {
|
|
| 886 | ! |
obj@include_NAs <- value |
| 887 |
} |
|
| 888 |
) |
|
| 889 | ||
| 890 |
#' @rdname int_methods |
|
| 891 | 921x |
setGeneric("spl_labelvar", function(obj) standardGeneric("spl_labelvar"))
|
| 892 | ||
| 893 |
#' @rdname int_methods |
|
| 894 | 921x |
setMethod("spl_labelvar", "VarLevelSplit", function(obj) obj@value_label_var)
|
| 895 | ||
| 896 |
#' @rdname int_methods |
|
| 897 | 3106x |
setGeneric("spl_child_order", function(obj) standardGeneric("spl_child_order"))
|
| 898 | ||
| 899 |
#' @rdname int_methods |
|
| 900 | 2773x |
setMethod("spl_child_order", "VarLevelSplit", function(obj) obj@value_order)
|
| 901 | ||
| 902 |
#' @rdname int_methods |
|
| 903 |
setGeneric( |
|
| 904 |
"spl_child_order<-", |
|
| 905 | 708x |
function(obj, value) standardGeneric("spl_child_order<-")
|
| 906 |
) |
|
| 907 | ||
| 908 |
#' @rdname int_methods |
|
| 909 |
setMethod( |
|
| 910 |
"spl_child_order<-", "VarLevelSplit", |
|
| 911 |
function(obj, value) {
|
|
| 912 | 708x |
obj@value_order <- value |
| 913 | 708x |
obj |
| 914 |
} |
|
| 915 |
) |
|
| 916 | ||
| 917 |
#' @rdname int_methods |
|
| 918 |
setMethod( |
|
| 919 |
"spl_child_order", |
|
| 920 |
"ManualSplit", |
|
| 921 | 52x |
function(obj) obj@levels |
| 922 |
) |
|
| 923 | ||
| 924 |
#' @rdname int_methods |
|
| 925 |
setMethod( |
|
| 926 |
"spl_child_order", |
|
| 927 |
"MultiVarSplit", |
|
| 928 | 100x |
function(obj) spl_varnames(obj) |
| 929 |
) |
|
| 930 | ||
| 931 |
#' @rdname int_methods |
|
| 932 |
setMethod( |
|
| 933 |
"spl_child_order", |
|
| 934 |
"AllSplit", |
|
| 935 | 137x |
function(obj) character() |
| 936 |
) |
|
| 937 | ||
| 938 |
#' @rdname int_methods |
|
| 939 |
setMethod( |
|
| 940 |
"spl_child_order", |
|
| 941 |
"VarStaticCutSplit", |
|
| 942 | 44x |
function(obj) spl_cutlabels(obj) |
| 943 |
) |
|
| 944 | ||
| 945 |
#' @rdname int_methods |
|
| 946 | 1112x |
setGeneric("root_spl", function(obj) standardGeneric("root_spl"))
|
| 947 | ||
| 948 |
#' @rdname int_methods |
|
| 949 |
setMethod( |
|
| 950 |
"root_spl", "PreDataAxisLayout", |
|
| 951 | 1112x |
function(obj) obj@root_split |
| 952 |
) |
|
| 953 | ||
| 954 |
#' @rdname int_methods |
|
| 955 | 9x |
setGeneric("root_spl<-", function(obj, value) standardGeneric("root_spl<-"))
|
| 956 | ||
| 957 |
#' @rdname int_methods |
|
| 958 |
setMethod( |
|
| 959 |
"root_spl<-", "PreDataAxisLayout", |
|
| 960 |
function(obj, value) {
|
|
| 961 | 9x |
obj@root_split <- value |
| 962 | 9x |
obj |
| 963 |
} |
|
| 964 |
) |
|
| 965 | ||
| 966 |
#' Row attribute accessors |
|
| 967 |
#' |
|
| 968 |
#' @inheritParams gen_args |
|
| 969 |
#' |
|
| 970 |
#' @return Various return values depending on the accessor called. |
|
| 971 |
#' |
|
| 972 |
#' @export |
|
| 973 |
#' @rdname row_accessors |
|
| 974 | 72x |
setGeneric("obj_avar", function(obj) standardGeneric("obj_avar"))
|
| 975 | ||
| 976 |
#' @rdname row_accessors |
|
| 977 |
#' @exportMethod obj_avar |
|
| 978 | 55x |
setMethod("obj_avar", "TableRow", function(obj) obj@var_analyzed)
|
| 979 | ||
| 980 |
#' @rdname row_accessors |
|
| 981 |
#' @exportMethod obj_avar |
|
| 982 | 17x |
setMethod("obj_avar", "ElementaryTable", function(obj) obj@var_analyzed)
|
| 983 | ||
| 984 |
#' @export |
|
| 985 |
#' @rdname row_accessors |
|
| 986 | 77121x |
setGeneric("row_cells", function(obj) standardGeneric("row_cells"))
|
| 987 | ||
| 988 |
#' @rdname row_accessors |
|
| 989 |
#' @exportMethod row_cells |
|
| 990 | 8335x |
setMethod("row_cells", "TableRow", function(obj) obj@leaf_value)
|
| 991 | ||
| 992 |
#' @rdname row_accessors |
|
| 993 | 4141x |
setGeneric("row_cells<-", function(obj, value) standardGeneric("row_cells<-"))
|
| 994 | ||
| 995 |
#' @rdname row_accessors |
|
| 996 |
#' @exportMethod row_cells |
|
| 997 |
setMethod("row_cells<-", "TableRow", function(obj, value) {
|
|
| 998 | 4141x |
obj@leaf_value <- value |
| 999 | 4141x |
obj |
| 1000 |
}) |
|
| 1001 | ||
| 1002 |
#' @export |
|
| 1003 |
#' @rdname row_accessors |
|
| 1004 | 2636x |
setGeneric("row_values", function(obj) standardGeneric("row_values"))
|
| 1005 | ||
| 1006 |
#' @rdname row_accessors |
|
| 1007 |
#' @exportMethod row_values |
|
| 1008 | 554x |
setMethod("row_values", "TableRow", function(obj) rawvalues(obj@leaf_value))
|
| 1009 | ||
| 1010 | ||
| 1011 |
#' @rdname row_accessors |
|
| 1012 |
#' @exportMethod row_values<- |
|
| 1013 | 1282x |
setGeneric("row_values<-", function(obj, value) standardGeneric("row_values<-"))
|
| 1014 | ||
| 1015 |
#' @rdname row_accessors |
|
| 1016 |
#' @exportMethod row_values<- |
|
| 1017 |
setMethod( |
|
| 1018 |
"row_values<-", "TableRow", |
|
| 1019 |
function(obj, value) {
|
|
| 1020 | 1282x |
obj@leaf_value <- lapply(value, rcell) |
| 1021 | 1282x |
obj |
| 1022 |
} |
|
| 1023 |
) |
|
| 1024 | ||
| 1025 |
#' @rdname row_accessors |
|
| 1026 |
#' @exportMethod row_values<- |
|
| 1027 |
setMethod( |
|
| 1028 |
"row_values<-", "LabelRow", |
|
| 1029 |
function(obj, value) {
|
|
| 1030 | ! |
stop("LabelRows cannot have row values.")
|
| 1031 |
} |
|
| 1032 |
) |
|
| 1033 | ||
| 1034 |
#' @rdname int_methods |
|
| 1035 | 1045x |
setGeneric("spanned_values", function(obj) standardGeneric("spanned_values"))
|
| 1036 | ||
| 1037 |
#' @rdname int_methods |
|
| 1038 |
setMethod( |
|
| 1039 |
"spanned_values", "TableRow", |
|
| 1040 |
function(obj) {
|
|
| 1041 | 1045x |
rawvalues(spanned_cells(obj)) |
| 1042 |
} |
|
| 1043 |
) |
|
| 1044 | ||
| 1045 |
#' @rdname int_methods |
|
| 1046 |
setMethod( |
|
| 1047 |
"spanned_values", "LabelRow", |
|
| 1048 |
function(obj) {
|
|
| 1049 | ! |
rep(list(NULL), ncol(obj)) |
| 1050 |
} |
|
| 1051 |
) |
|
| 1052 | ||
| 1053 |
#' @rdname int_methods |
|
| 1054 | 1045x |
setGeneric("spanned_cells", function(obj) standardGeneric("spanned_cells"))
|
| 1055 | ||
| 1056 |
#' @rdname int_methods |
|
| 1057 |
setMethod( |
|
| 1058 |
"spanned_cells", "TableRow", |
|
| 1059 |
function(obj) {
|
|
| 1060 | 1045x |
sp <- row_cspans(obj) |
| 1061 | 1045x |
rvals <- row_cells(obj) |
| 1062 | 1045x |
unlist( |
| 1063 | 1045x |
mapply(function(v, s) rep(list(v), times = s), |
| 1064 | 1045x |
v = rvals, s = sp |
| 1065 |
), |
|
| 1066 | 1045x |
recursive = FALSE |
| 1067 |
) |
|
| 1068 |
} |
|
| 1069 |
) |
|
| 1070 | ||
| 1071 |
#' @rdname int_methods |
|
| 1072 |
setMethod( |
|
| 1073 |
"spanned_cells", "LabelRow", |
|
| 1074 |
function(obj) {
|
|
| 1075 | ! |
rep(list(NULL), ncol(obj)) |
| 1076 |
} |
|
| 1077 |
) |
|
| 1078 | ||
| 1079 |
#' @rdname int_methods |
|
| 1080 | 3x |
setGeneric("spanned_values<-", function(obj, value) standardGeneric("spanned_values<-"))
|
| 1081 | ||
| 1082 |
#' @rdname int_methods |
|
| 1083 |
setMethod( |
|
| 1084 |
"spanned_values<-", "TableRow", |
|
| 1085 |
function(obj, value) {
|
|
| 1086 | 2x |
sp <- row_cspans(obj) |
| 1087 |
## this is 3 times too clever!!! |
|
| 1088 | 2x |
valindices <- unlist(lapply(sp, function(x) c(TRUE, rep(FALSE, x - 1)))) |
| 1089 | ||
| 1090 | 2x |
splvec <- cumsum(valindices) |
| 1091 | 2x |
lapply( |
| 1092 | 2x |
split(value, splvec), |
| 1093 | 2x |
function(v) {
|
| 1094 | 3x |
if (length(unique(v)) > 1) {
|
| 1095 | 1x |
stop( |
| 1096 | 1x |
"Got more than one unique value within a span, ", |
| 1097 | 1x |
"new spanned values do not appear to match the ", |
| 1098 | 1x |
"existing spanning pattern of the row (",
|
| 1099 | 1x |
paste(sp, collapse = " "), ")" |
| 1100 |
) |
|
| 1101 |
} |
|
| 1102 |
} |
|
| 1103 |
) |
|
| 1104 | 1x |
rvals <- value[valindices] |
| 1105 | ||
| 1106 |
## rvals = lapply(split(value, splvec), |
|
| 1107 |
## function(v) {
|
|
| 1108 |
## if(length(v) == 1) |
|
| 1109 |
## return(v) |
|
| 1110 |
## stopifnot(length(unique(v)) == 1L) |
|
| 1111 |
## rcell(unique(v), colspan<- length(v)) |
|
| 1112 |
## }) |
|
| 1113 |
## if(any(splvec > 1)) |
|
| 1114 |
## rvals <- lapply(rvals, function(x) x[[1]]) |
|
| 1115 | 1x |
row_values(obj) <- rvals |
| 1116 | 1x |
obj |
| 1117 |
} |
|
| 1118 |
) |
|
| 1119 | ||
| 1120 |
#' @rdname int_methods |
|
| 1121 |
setMethod( |
|
| 1122 |
"spanned_values<-", "LabelRow", |
|
| 1123 |
function(obj, value) {
|
|
| 1124 | 1x |
if (!is.null(value)) {
|
| 1125 | 1x |
stop("Label rows can't have non-null cell values, got", value)
|
| 1126 |
} |
|
| 1127 | ! |
obj |
| 1128 |
} |
|
| 1129 |
) |
|
| 1130 | ||
| 1131 |
### Format manipulation |
|
| 1132 |
### obj_format<- is not recursive |
|
| 1133 |
## TODO export these? |
|
| 1134 |
#' @rdname formatters_methods |
|
| 1135 |
#' @export |
|
| 1136 | 7076x |
setMethod("obj_format", "VTableNodeInfo", function(obj) obj@format)
|
| 1137 | ||
| 1138 |
#' @rdname formatters_methods |
|
| 1139 |
#' @export |
|
| 1140 | 115875x |
setMethod("obj_format", "CellValue", function(obj) attr(obj, "format", exact = TRUE))
|
| 1141 | ||
| 1142 |
#' @rdname formatters_methods |
|
| 1143 |
#' @export |
|
| 1144 | 2666x |
setMethod("obj_format", "Split", function(obj) obj@split_format)
|
| 1145 | ||
| 1146 |
#' @rdname formatters_methods |
|
| 1147 |
#' @export |
|
| 1148 |
setMethod("obj_format<-", "VTableNodeInfo", function(obj, value) {
|
|
| 1149 | 1755x |
obj@format <- value |
| 1150 | 1755x |
obj |
| 1151 |
}) |
|
| 1152 | ||
| 1153 |
#' @rdname formatters_methods |
|
| 1154 |
#' @export |
|
| 1155 |
setMethod("obj_format<-", "Split", function(obj, value) {
|
|
| 1156 | 1x |
obj@split_format <- value |
| 1157 | 1x |
obj |
| 1158 |
}) |
|
| 1159 | ||
| 1160 |
#' @rdname formatters_methods |
|
| 1161 |
#' @export |
|
| 1162 |
setMethod("obj_format<-", "CellValue", function(obj, value) {
|
|
| 1163 | 1257x |
attr(obj, "format") <- value |
| 1164 | 1257x |
obj |
| 1165 |
}) |
|
| 1166 | ||
| 1167 |
#' @rdname int_methods |
|
| 1168 |
#' @export |
|
| 1169 |
setMethod("obj_na_str<-", "CellValue", function(obj, value) {
|
|
| 1170 | 4415x |
attr(obj, "format_na_str") <- value |
| 1171 | 4415x |
obj |
| 1172 |
}) |
|
| 1173 | ||
| 1174 |
#' @rdname int_methods |
|
| 1175 |
#' @export |
|
| 1176 |
setMethod("obj_na_str<-", "VTableNodeInfo", function(obj, value) {
|
|
| 1177 | 26x |
obj@na_str <- value |
| 1178 | 26x |
obj |
| 1179 |
}) |
|
| 1180 | ||
| 1181 |
#' @rdname int_methods |
|
| 1182 |
#' @export |
|
| 1183 |
setMethod("obj_na_str<-", "Split", function(obj, value) {
|
|
| 1184 | ! |
obj@split_na_str <- value |
| 1185 | ! |
obj |
| 1186 |
}) |
|
| 1187 | ||
| 1188 |
#' @rdname int_methods |
|
| 1189 |
#' @export |
|
| 1190 | 31233x |
setMethod("obj_na_str", "VTableNodeInfo", function(obj) obj@na_str)
|
| 1191 | ||
| 1192 |
#' @rdname formatters_methods |
|
| 1193 |
#' @export |
|
| 1194 | 1359x |
setMethod("obj_na_str", "Split", function(obj) obj@split_na_str)
|
| 1195 | ||
| 1196 |
.no_na_str <- function(x) {
|
|
| 1197 | 16729x |
if (!is.character(x)) {
|
| 1198 | 6486x |
x <- obj_na_str(x) |
| 1199 |
} |
|
| 1200 | 16729x |
length(x) == 0 || all(is.na(x)) |
| 1201 |
} |
|
| 1202 | ||
| 1203 |
#' @rdname int_methods |
|
| 1204 |
setGeneric("set_format_recursive", function(obj, format, na_str, override = FALSE) {
|
|
| 1205 | 10236x |
standardGeneric("set_format_recursive")
|
| 1206 |
}) |
|
| 1207 | ||
| 1208 |
#' @param override (`flag`)\cr whether to override attribute. |
|
| 1209 |
#' |
|
| 1210 |
#' @rdname int_methods |
|
| 1211 |
setMethod( |
|
| 1212 |
"set_format_recursive", "TableRow", |
|
| 1213 |
function(obj, format, na_str, override = FALSE) {
|
|
| 1214 | 1128x |
if (is.null(format) && .no_na_str(na_str)) {
|
| 1215 | 564x |
return(obj) |
| 1216 |
} |
|
| 1217 | ||
| 1218 | 564x |
if ((is.null(obj_format(obj)) && !is.null(format)) || override) {
|
| 1219 | 564x |
obj_format(obj) <- format |
| 1220 |
} |
|
| 1221 | 564x |
if ((.no_na_str(obj) && !.no_na_str(na_str)) || override) {
|
| 1222 | ! |
obj_na_str(obj) <- na_str |
| 1223 |
} |
|
| 1224 | 564x |
lcells <- row_cells(obj) |
| 1225 | 564x |
lvals <- lapply(lcells, function(x) {
|
| 1226 | 2100x |
if (!is.null(x) && (override || is.null(obj_format(x)))) {
|
| 1227 | 89x |
obj_format(x) <- obj_format(obj) |
| 1228 |
} |
|
| 1229 | 2100x |
if (!is.null(x) && (override || .no_na_str(x))) {
|
| 1230 | 2100x |
obj_na_str(x) <- obj_na_str(obj) |
| 1231 |
} |
|
| 1232 | 2100x |
x |
| 1233 |
}) |
|
| 1234 | 564x |
row_values(obj) <- lvals |
| 1235 | 564x |
obj |
| 1236 |
} |
|
| 1237 |
) |
|
| 1238 | ||
| 1239 |
#' @rdname int_methods |
|
| 1240 |
setMethod( |
|
| 1241 |
"set_format_recursive", "LabelRow", |
|
| 1242 | 11x |
function(obj, format, override = FALSE) obj |
| 1243 |
) |
|
| 1244 | ||
| 1245 |
setMethod( |
|
| 1246 |
"set_format_recursive", "VTableTree", |
|
| 1247 |
function(obj, format, na_str, override = FALSE) {
|
|
| 1248 | 1933x |
force(format) |
| 1249 | 1933x |
if (is.null(format) && .no_na_str(na_str)) {
|
| 1250 | 1926x |
return(obj) |
| 1251 |
} |
|
| 1252 | ||
| 1253 | 7x |
if ((is.null(obj_format(obj)) && !is.null(format)) || override) {
|
| 1254 | 7x |
obj_format(obj) <- format |
| 1255 |
} |
|
| 1256 | 7x |
if ((.no_na_str(obj) && !.no_na_str(na_str)) || override) {
|
| 1257 | ! |
obj_na_str(obj) <- na_str |
| 1258 |
} |
|
| 1259 | ||
| 1260 | 7x |
kids <- tree_children(obj) |
| 1261 | 7x |
kids <- lapply(kids, function(x, format2, na_str2, oride) {
|
| 1262 | 33x |
set_format_recursive(x, |
| 1263 | 33x |
format = format2, na_str = na_str2, override = oride |
| 1264 |
) |
|
| 1265 |
}, |
|
| 1266 | 7x |
format2 = obj_format(obj), na_str2 = obj_na_str(obj), oride = override |
| 1267 |
) |
|
| 1268 | 7x |
tree_children(obj) <- kids |
| 1269 | 7x |
obj |
| 1270 |
} |
|
| 1271 |
) |
|
| 1272 | ||
| 1273 |
#' @rdname int_methods |
|
| 1274 | 2104x |
setGeneric("content_format", function(obj) standardGeneric("content_format"))
|
| 1275 | ||
| 1276 |
#' @rdname int_methods |
|
| 1277 | 2104x |
setMethod("content_format", "Split", function(obj) obj@content_format)
|
| 1278 | ||
| 1279 |
#' @rdname int_methods |
|
| 1280 | 125x |
setGeneric("content_format<-", function(obj, value) standardGeneric("content_format<-"))
|
| 1281 | ||
| 1282 |
#' @rdname int_methods |
|
| 1283 |
setMethod("content_format<-", "Split", function(obj, value) {
|
|
| 1284 | 125x |
obj@content_format <- value |
| 1285 | 125x |
obj |
| 1286 |
}) |
|
| 1287 | ||
| 1288 |
#' @rdname int_methods |
|
| 1289 | 2104x |
setGeneric("content_na_str", function(obj) standardGeneric("content_na_str"))
|
| 1290 | ||
| 1291 |
#' @rdname int_methods |
|
| 1292 | 2104x |
setMethod("content_na_str", "Split", function(obj) obj@content_na_str)
|
| 1293 | ||
| 1294 |
#' @rdname int_methods |
|
| 1295 | ! |
setGeneric("content_na_str<-", function(obj, value) standardGeneric("content_na_str<-"))
|
| 1296 | ||
| 1297 |
#' @rdname int_methods |
|
| 1298 |
setMethod("content_na_str<-", "Split", function(obj, value) {
|
|
| 1299 | ! |
obj@content_na_str <- value |
| 1300 | ! |
obj |
| 1301 |
}) |
|
| 1302 | ||
| 1303 |
#' Value formats |
|
| 1304 |
#' |
|
| 1305 |
#' Returns a matrix of formats for the cells in a table. |
|
| 1306 |
#' |
|
| 1307 |
#' @param obj (`VTableTree` or `TableRow`)\cr a table or row object. |
|
| 1308 |
#' @param default (`string`, `function`, or `list`)\cr default format. |
|
| 1309 |
#' |
|
| 1310 |
#' @return Matrix (storage mode list) containing the effective format for each cell position in the table |
|
| 1311 |
#' (including 'virtual' cells implied by label rows, whose formats are always `NULL`). |
|
| 1312 |
#' |
|
| 1313 |
#' @seealso [table_shell()] and [table_shell_str()] for information on the table format structure. |
|
| 1314 |
#' |
|
| 1315 |
#' @examples |
|
| 1316 |
#' lyt <- basic_table() %>% |
|
| 1317 |
#' split_rows_by("RACE", split_fun = keep_split_levels(c("ASIAN", "WHITE"))) %>%
|
|
| 1318 |
#' analyze("AGE")
|
|
| 1319 |
#' |
|
| 1320 |
#' tbl <- build_table(lyt, DM) |
|
| 1321 |
#' value_formats(tbl) |
|
| 1322 |
#' |
|
| 1323 |
#' @export |
|
| 1324 | 1123x |
setGeneric("value_formats", function(obj, default = obj_format(obj)) standardGeneric("value_formats"))
|
| 1325 | ||
| 1326 |
#' @rdname value_formats |
|
| 1327 |
setMethod( |
|
| 1328 |
"value_formats", "ANY", |
|
| 1329 |
function(obj, default) {
|
|
| 1330 | 762x |
obj_format(obj) %||% default |
| 1331 |
} |
|
| 1332 |
) |
|
| 1333 | ||
| 1334 |
#' @rdname value_formats |
|
| 1335 |
setMethod( |
|
| 1336 |
"value_formats", "TableRow", |
|
| 1337 |
function(obj, default) {
|
|
| 1338 | 245x |
if (!is.null(obj_format(obj))) {
|
| 1339 | 215x |
default <- obj_format(obj) |
| 1340 |
} |
|
| 1341 | 245x |
formats <- lapply(row_cells(obj), function(x) value_formats(x) %||% default) |
| 1342 | 245x |
formats |
| 1343 |
} |
|
| 1344 |
) |
|
| 1345 | ||
| 1346 |
#' @rdname value_formats |
|
| 1347 |
setMethod( |
|
| 1348 |
"value_formats", "LabelRow", |
|
| 1349 |
function(obj, default) {
|
|
| 1350 | 102x |
rep(list(NULL), ncol(obj)) |
| 1351 |
} |
|
| 1352 |
) |
|
| 1353 | ||
| 1354 |
#' @rdname value_formats |
|
| 1355 |
setMethod( |
|
| 1356 |
"value_formats", "VTableTree", |
|
| 1357 |
function(obj, default) {
|
|
| 1358 | 14x |
if (!is.null(obj_format(obj))) {
|
| 1359 | ! |
default <- obj_format(obj) |
| 1360 |
} |
|
| 1361 | 14x |
rws <- collect_leaves(obj, TRUE, TRUE) |
| 1362 | 14x |
formatrws <- lapply(rws, value_formats, default = default) |
| 1363 | 14x |
mat <- do.call(rbind, formatrws) |
| 1364 | 14x |
row.names(mat) <- row.names(obj) |
| 1365 | 14x |
mat |
| 1366 |
} |
|
| 1367 |
) |
|
| 1368 | ||
| 1369 |
### Collect all leaves of a current tree |
|
| 1370 |
### This is a workhorse function in various |
|
| 1371 |
### places |
|
| 1372 |
### NB this is written generally enought o |
|
| 1373 |
### be used on all tree-based structures in the |
|
| 1374 |
### framework. |
|
| 1375 | ||
| 1376 |
#' Collect leaves of a `TableTree` |
|
| 1377 |
#' |
|
| 1378 |
#' @inheritParams gen_args |
|
| 1379 |
#' @param incl.cont (`flag`)\cr whether to include rows from content tables within the tree. Defaults to `TRUE`. |
|
| 1380 |
#' @param add.labrows (`flag`)\cr whether to include label rows. Defaults to `FALSE`. |
|
| 1381 |
#' |
|
| 1382 |
#' @return A list of `TableRow` objects for all rows in the table. |
|
| 1383 |
#' |
|
| 1384 |
#' @export |
|
| 1385 |
setGeneric("collect_leaves",
|
|
| 1386 |
function(tt, incl.cont = TRUE, add.labrows = FALSE) {
|
|
| 1387 | 121182x |
standardGeneric("collect_leaves")
|
| 1388 |
}, |
|
| 1389 |
signature = "tt" |
|
| 1390 |
) |
|
| 1391 | ||
| 1392 |
#' @inheritParams collect_leaves |
|
| 1393 |
#' |
|
| 1394 |
#' @rdname int_methods |
|
| 1395 |
#' @exportMethod collect_leaves |
|
| 1396 |
setMethod( |
|
| 1397 |
"collect_leaves", "TableTree", |
|
| 1398 |
function(tt, incl.cont = TRUE, add.labrows = FALSE) {
|
|
| 1399 | 26129x |
ret <- c( |
| 1400 | 26129x |
if (add.labrows && labelrow_visible(tt)) {
|
| 1401 | 10551x |
tt_labelrow(tt) |
| 1402 |
}, |
|
| 1403 | 26129x |
if (incl.cont) {
|
| 1404 | 26129x |
tree_children(content_table(tt)) |
| 1405 |
}, |
|
| 1406 | 26129x |
lapply(tree_children(tt), |
| 1407 | 26129x |
collect_leaves, |
| 1408 | 26129x |
incl.cont = incl.cont, add.labrows = add.labrows |
| 1409 |
) |
|
| 1410 |
) |
|
| 1411 | 26129x |
unlist(ret, recursive = TRUE) |
| 1412 |
} |
|
| 1413 |
) |
|
| 1414 | ||
| 1415 |
#' @rdname int_methods |
|
| 1416 |
#' @exportMethod collect_leaves |
|
| 1417 |
setMethod( |
|
| 1418 |
"collect_leaves", "ElementaryTable", |
|
| 1419 |
function(tt, incl.cont = TRUE, add.labrows = FALSE) {
|
|
| 1420 | 61502x |
ret <- tree_children(tt) |
| 1421 | 61502x |
if (add.labrows && labelrow_visible(tt)) {
|
| 1422 | 12227x |
ret <- c(tt_labelrow(tt), ret) |
| 1423 |
} |
|
| 1424 | 61502x |
ret |
| 1425 |
} |
|
| 1426 |
) |
|
| 1427 | ||
| 1428 |
#' @rdname int_methods |
|
| 1429 |
#' @exportMethod collect_leaves |
|
| 1430 |
setMethod( |
|
| 1431 |
"collect_leaves", "VTree", |
|
| 1432 |
function(tt, incl.cont, add.labrows) {
|
|
| 1433 | ! |
ret <- lapply( |
| 1434 | ! |
tree_children(tt), |
| 1435 | ! |
collect_leaves |
| 1436 |
) |
|
| 1437 | ! |
unlist(ret, recursive = TRUE) |
| 1438 |
} |
|
| 1439 |
) |
|
| 1440 | ||
| 1441 |
#' @rdname int_methods |
|
| 1442 |
#' @exportMethod collect_leaves |
|
| 1443 |
setMethod( |
|
| 1444 |
"collect_leaves", "VLeaf", |
|
| 1445 |
function(tt, incl.cont, add.labrows) {
|
|
| 1446 | 686x |
list(tt) |
| 1447 |
} |
|
| 1448 |
) |
|
| 1449 | ||
| 1450 |
#' @rdname int_methods |
|
| 1451 |
#' @exportMethod collect_leaves |
|
| 1452 |
setMethod( |
|
| 1453 |
"collect_leaves", "NULL", |
|
| 1454 |
function(tt, incl.cont, add.labrows) {
|
|
| 1455 | ! |
list() |
| 1456 |
} |
|
| 1457 |
) |
|
| 1458 | ||
| 1459 |
#' @rdname int_methods |
|
| 1460 |
#' @exportMethod collect_leaves |
|
| 1461 |
setMethod( |
|
| 1462 |
"collect_leaves", "ANY", |
|
| 1463 |
function(tt, incl.cont, add.labrows) {
|
|
| 1464 | ! |
stop("class ", class(tt), " does not inherit from VTree or VLeaf")
|
| 1465 |
} |
|
| 1466 |
) |
|
| 1467 | ||
| 1468 |
n_leaves <- function(tt, ...) {
|
|
| 1469 | 407x |
length(collect_leaves(tt, ...)) |
| 1470 |
} |
|
| 1471 | ||
| 1472 |
### Spanning information ---- |
|
| 1473 | ||
| 1474 |
#' @rdname int_methods |
|
| 1475 | 59625x |
setGeneric("row_cspans", function(obj) standardGeneric("row_cspans"))
|
| 1476 | ||
| 1477 |
#' @rdname int_methods |
|
| 1478 | 5128x |
setMethod("row_cspans", "TableRow", function(obj) obj@colspans)
|
| 1479 | ||
| 1480 |
#' @rdname int_methods |
|
| 1481 |
setMethod( |
|
| 1482 |
"row_cspans", "LabelRow", |
|
| 1483 | 1649x |
function(obj) rep(1L, ncol(obj)) |
| 1484 |
) |
|
| 1485 | ||
| 1486 |
#' @rdname int_methods |
|
| 1487 | 4077x |
setGeneric("row_cspans<-", function(obj, value) standardGeneric("row_cspans<-"))
|
| 1488 | ||
| 1489 |
#' @rdname int_methods |
|
| 1490 |
setMethod("row_cspans<-", "TableRow", function(obj, value) {
|
|
| 1491 | 4077x |
obj@colspans <- value |
| 1492 | 4077x |
obj |
| 1493 |
}) |
|
| 1494 | ||
| 1495 |
#' @rdname int_methods |
|
| 1496 |
setMethod("row_cspans<-", "LabelRow", function(obj, value) {
|
|
| 1497 |
stop("attempted to set colspans for LabelRow") # nocov
|
|
| 1498 |
}) |
|
| 1499 | ||
| 1500 |
## XXX TODO colapse with above? |
|
| 1501 |
#' @rdname int_methods |
|
| 1502 | 51023x |
setGeneric("cell_cspan", function(obj) standardGeneric("cell_cspan"))
|
| 1503 | ||
| 1504 |
#' @rdname int_methods |
|
| 1505 |
setMethod( |
|
| 1506 |
"cell_cspan", "CellValue", |
|
| 1507 | 51023x |
function(obj) attr(obj, "colspan", exact = TRUE) |
| 1508 |
) ## obj@colspan) |
|
| 1509 | ||
| 1510 |
#' @rdname int_methods |
|
| 1511 |
setGeneric( |
|
| 1512 |
"cell_cspan<-", |
|
| 1513 | 7067x |
function(obj, value) standardGeneric("cell_cspan<-")
|
| 1514 |
) |
|
| 1515 | ||
| 1516 |
#' @rdname int_methods |
|
| 1517 |
setMethod("cell_cspan<-", "CellValue", function(obj, value) {
|
|
| 1518 |
## obj@colspan <- value |
|
| 1519 | 7067x |
attr(obj, "colspan") <- value |
| 1520 | 7067x |
obj |
| 1521 |
}) |
|
| 1522 | ||
| 1523 |
#' @rdname int_methods |
|
| 1524 | 27931x |
setGeneric("cell_align", function(obj) standardGeneric("cell_align"))
|
| 1525 | ||
| 1526 |
#' @rdname int_methods |
|
| 1527 |
setMethod( |
|
| 1528 |
"cell_align", "CellValue", |
|
| 1529 | 27931x |
function(obj) attr(obj, "align", exact = TRUE) %||% "center" |
| 1530 |
) ## obj@colspan) |
|
| 1531 | ||
| 1532 |
#' @rdname int_methods |
|
| 1533 |
setGeneric( |
|
| 1534 |
"cell_align<-", |
|
| 1535 | 56x |
function(obj, value) standardGeneric("cell_align<-")
|
| 1536 |
) |
|
| 1537 | ||
| 1538 |
#' @rdname int_methods |
|
| 1539 |
setMethod("cell_align<-", "CellValue", function(obj, value) {
|
|
| 1540 |
## obj@colspan <- value |
|
| 1541 | 56x |
if (is.null(value)) {
|
| 1542 | ! |
value <- "center" |
| 1543 |
} else {
|
|
| 1544 | 56x |
value <- tolower(value) |
| 1545 |
} |
|
| 1546 | 56x |
check_aligns(value) |
| 1547 | 56x |
attr(obj, "align") <- value |
| 1548 | 56x |
obj |
| 1549 |
}) |
|
| 1550 | ||
| 1551 |
### Level (indent) in tree structure ---- |
|
| 1552 | ||
| 1553 |
#' @rdname int_methods |
|
| 1554 | 36x |
setGeneric("tt_level", function(obj) standardGeneric("tt_level"))
|
| 1555 | ||
| 1556 |
## this will hit everything via inheritence |
|
| 1557 |
#' @rdname int_methods |
|
| 1558 | 36x |
setMethod("tt_level", "VNodeInfo", function(obj) obj@level)
|
| 1559 | ||
| 1560 |
#' @rdname int_methods |
|
| 1561 | 2x |
setGeneric("tt_level<-", function(obj, value) standardGeneric("tt_level<-"))
|
| 1562 | ||
| 1563 |
## this will hit everyhing via inheritence |
|
| 1564 |
#' @rdname int_methods |
|
| 1565 |
setMethod("tt_level<-", "VNodeInfo", function(obj, value) {
|
|
| 1566 | 1x |
obj@level <- as.integer(value) |
| 1567 | 1x |
obj |
| 1568 |
}) |
|
| 1569 | ||
| 1570 |
#' @rdname int_methods |
|
| 1571 |
setMethod( |
|
| 1572 |
"tt_level<-", "VTableTree", |
|
| 1573 |
function(obj, value) {
|
|
| 1574 | 1x |
obj@level <- as.integer(value) |
| 1575 | 1x |
tree_children(obj) <- lapply(tree_children(obj), |
| 1576 | 1x |
`tt_level<-`, |
| 1577 | 1x |
value = as.integer(value) + 1L |
| 1578 |
) |
|
| 1579 | 1x |
obj |
| 1580 |
} |
|
| 1581 |
) |
|
| 1582 | ||
| 1583 |
#' @rdname int_methods |
|
| 1584 |
#' @export |
|
| 1585 | 62780x |
setGeneric("indent_mod", function(obj) standardGeneric("indent_mod"))
|
| 1586 | ||
| 1587 |
#' @rdname int_methods |
|
| 1588 |
setMethod( |
|
| 1589 |
"indent_mod", "Split", |
|
| 1590 | 3397x |
function(obj) obj@indent_modifier |
| 1591 |
) |
|
| 1592 | ||
| 1593 |
#' @rdname int_methods |
|
| 1594 |
setMethod( |
|
| 1595 |
"indent_mod", "VTableNodeInfo", |
|
| 1596 | 29447x |
function(obj) obj@indent_modifier |
| 1597 |
) |
|
| 1598 | ||
| 1599 |
#' @rdname int_methods |
|
| 1600 |
setMethod( |
|
| 1601 |
"indent_mod", "ANY", |
|
| 1602 | 26197x |
function(obj) attr(obj, "indent_mod", exact = TRUE) %||% 0L |
| 1603 |
) |
|
| 1604 | ||
| 1605 |
#' @rdname int_methods |
|
| 1606 |
setMethod( |
|
| 1607 |
"indent_mod", "RowsVerticalSection", |
|
| 1608 |
## function(obj) setNames(obj@indent_mods,names(obj))) |
|
| 1609 |
function(obj) {
|
|
| 1610 | 1838x |
val <- attr(obj, "indent_mods", exact = TRUE) %||% |
| 1611 | 1838x |
vapply(obj, indent_mod, 1L) ## rep(0L, length(obj)) |
| 1612 | 1838x |
setNames(val, names(obj)) |
| 1613 |
} |
|
| 1614 |
) |
|
| 1615 | ||
| 1616 |
#' @examples |
|
| 1617 |
#' lyt <- basic_table() %>% |
|
| 1618 |
#' split_rows_by("RACE", split_fun = keep_split_levels(c("ASIAN", "WHITE"))) %>%
|
|
| 1619 |
#' analyze("AGE")
|
|
| 1620 |
#' |
|
| 1621 |
#' tbl <- build_table(lyt, DM) |
|
| 1622 |
#' indent_mod(tbl) |
|
| 1623 |
#' indent_mod(tbl) <- 1L |
|
| 1624 |
#' tbl |
|
| 1625 |
#' |
|
| 1626 |
#' @rdname int_methods |
|
| 1627 |
#' @export |
|
| 1628 | 1672x |
setGeneric("indent_mod<-", function(obj, value) standardGeneric("indent_mod<-"))
|
| 1629 | ||
| 1630 |
#' @rdname int_methods |
|
| 1631 |
setMethod( |
|
| 1632 |
"indent_mod<-", "Split", |
|
| 1633 |
function(obj, value) {
|
|
| 1634 | 1x |
obj@indent_modifier <- as.integer(value) |
| 1635 | 1x |
obj |
| 1636 |
} |
|
| 1637 |
) |
|
| 1638 | ||
| 1639 |
#' @rdname int_methods |
|
| 1640 |
setMethod( |
|
| 1641 |
"indent_mod<-", "VTableNodeInfo", |
|
| 1642 |
function(obj, value) {
|
|
| 1643 | 1668x |
obj@indent_modifier <- as.integer(value) |
| 1644 | 1668x |
obj |
| 1645 |
} |
|
| 1646 |
) |
|
| 1647 | ||
| 1648 |
#' @rdname int_methods |
|
| 1649 |
setMethod( |
|
| 1650 |
"indent_mod<-", "CellValue", |
|
| 1651 |
function(obj, value) {
|
|
| 1652 | 2x |
attr(obj, "indent_mod") <- as.integer(value) |
| 1653 | 2x |
obj |
| 1654 |
} |
|
| 1655 |
) |
|
| 1656 | ||
| 1657 |
#' @rdname int_methods |
|
| 1658 |
setMethod( |
|
| 1659 |
"indent_mod<-", "RowsVerticalSection", |
|
| 1660 |
function(obj, value) {
|
|
| 1661 | 1x |
if (length(value) != 1 && length(value) != length(obj)) {
|
| 1662 | ! |
stop( |
| 1663 | ! |
"When setting indent mods on a RowsVerticalSection the value ", |
| 1664 | ! |
"must have length 1 or the number of rows" |
| 1665 |
) |
|
| 1666 |
} |
|
| 1667 | 1x |
attr(obj, "indent_mods") <- as.integer(value) |
| 1668 | 1x |
obj |
| 1669 | ||
| 1670 |
## obj@indent_mods <- value |
|
| 1671 |
## obj |
|
| 1672 |
} |
|
| 1673 |
) |
|
| 1674 | ||
| 1675 |
#' @rdname int_methods |
|
| 1676 |
setGeneric( |
|
| 1677 |
"content_indent_mod", |
|
| 1678 | 1366x |
function(obj) standardGeneric("content_indent_mod")
|
| 1679 |
) |
|
| 1680 | ||
| 1681 |
#' @rdname int_methods |
|
| 1682 |
setMethod( |
|
| 1683 |
"content_indent_mod", "Split", |
|
| 1684 | 1366x |
function(obj) obj@content_indent_modifier |
| 1685 |
) |
|
| 1686 | ||
| 1687 |
#' @rdname int_methods |
|
| 1688 |
setMethod( |
|
| 1689 |
"content_indent_mod", "VTableNodeInfo", |
|
| 1690 | ! |
function(obj) obj@content_indent_modifier |
| 1691 |
) |
|
| 1692 | ||
| 1693 |
#' @rdname int_methods |
|
| 1694 |
setGeneric( |
|
| 1695 |
"content_indent_mod<-", |
|
| 1696 | 125x |
function(obj, value) standardGeneric("content_indent_mod<-")
|
| 1697 |
) |
|
| 1698 | ||
| 1699 |
#' @rdname int_methods |
|
| 1700 |
setMethod( |
|
| 1701 |
"content_indent_mod<-", "Split", |
|
| 1702 |
function(obj, value) {
|
|
| 1703 | 125x |
obj@content_indent_modifier <- as.integer(value) |
| 1704 | 125x |
obj |
| 1705 |
} |
|
| 1706 |
) |
|
| 1707 | ||
| 1708 |
#' @rdname int_methods |
|
| 1709 |
setMethod( |
|
| 1710 |
"content_indent_mod<-", "VTableNodeInfo", |
|
| 1711 |
function(obj, value) {
|
|
| 1712 | ! |
obj@content_indent_modifier <- as.integer(value) |
| 1713 | ! |
obj |
| 1714 |
} |
|
| 1715 |
) |
|
| 1716 | ||
| 1717 |
## TODO export these? |
|
| 1718 |
#' @rdname int_methods |
|
| 1719 |
#' @export |
|
| 1720 | 182781x |
setGeneric("rawvalues", function(obj) standardGeneric("rawvalues"))
|
| 1721 | ||
| 1722 |
#' @rdname int_methods |
|
| 1723 | ! |
setMethod("rawvalues", "ValueWrapper", function(obj) obj@value)
|
| 1724 | ||
| 1725 |
#' @rdname int_methods |
|
| 1726 | 72x |
setMethod("rawvalues", "LevelComboSplitValue", function(obj) obj@combolevels)
|
| 1727 | ||
| 1728 |
#' @rdname int_methods |
|
| 1729 | 3917x |
setMethod("rawvalues", "list", function(obj) lapply(obj, rawvalues))
|
| 1730 | ||
| 1731 |
#' @rdname int_methods |
|
| 1732 | 4761x |
setMethod("rawvalues", "ANY", function(obj) obj)
|
| 1733 | ||
| 1734 |
#' @rdname int_methods |
|
| 1735 | 93201x |
setMethod("rawvalues", "CellValue", function(obj) obj[[1]])
|
| 1736 | ||
| 1737 |
#' @rdname int_methods |
|
| 1738 |
setMethod( |
|
| 1739 |
"rawvalues", "TreePos", |
|
| 1740 | 236x |
function(obj) rawvalues(pos_splvals(obj)) |
| 1741 |
) |
|
| 1742 | ||
| 1743 |
#' @rdname int_methods |
|
| 1744 |
setMethod( |
|
| 1745 |
"rawvalues", "RowsVerticalSection", |
|
| 1746 | 2x |
function(obj) unlist(obj, recursive = FALSE) |
| 1747 |
) |
|
| 1748 | ||
| 1749 |
#' @rdname int_methods |
|
| 1750 |
#' @export |
|
| 1751 | 90638x |
setGeneric("value_names", function(obj) standardGeneric("value_names"))
|
| 1752 | ||
| 1753 |
#' @rdname int_methods |
|
| 1754 |
setMethod( |
|
| 1755 |
"value_names", "ANY", |
|
| 1756 | 41x |
function(obj) as.character(rawvalues(obj)) |
| 1757 |
) |
|
| 1758 | ||
| 1759 |
#' @rdname int_methods |
|
| 1760 |
setMethod( |
|
| 1761 |
"value_names", "TreePos", |
|
| 1762 | 1506x |
function(obj) value_names(pos_splvals(obj)) |
| 1763 |
) |
|
| 1764 | ||
| 1765 |
#' @rdname int_methods |
|
| 1766 |
setMethod( |
|
| 1767 |
"value_names", "list", |
|
| 1768 | 7296x |
function(obj) lapply(obj, value_names) |
| 1769 |
) |
|
| 1770 | ||
| 1771 |
#' @rdname int_methods |
|
| 1772 |
setMethod( |
|
| 1773 |
"value_names", "ValueWrapper", |
|
| 1774 | ! |
function(obj) rawvalues(obj) |
| 1775 |
) |
|
| 1776 | ||
| 1777 |
#' @rdname int_methods |
|
| 1778 |
setMethod( |
|
| 1779 |
"value_names", "LevelComboSplitValue", |
|
| 1780 | 1663x |
function(obj) obj@value |
| 1781 |
) ## obj@comboname) |
|
| 1782 | ||
| 1783 |
#' @rdname int_methods |
|
| 1784 |
setMethod( |
|
| 1785 |
"value_names", "RowsVerticalSection", |
|
| 1786 | 3652x |
function(obj) attr(obj, "row_names", exact = TRUE) |
| 1787 |
) ## obj@row_names) |
|
| 1788 | ||
| 1789 |
## not sure if I need these anywhere |
|
| 1790 |
## XXX |
|
| 1791 |
#' @rdname int_methods |
|
| 1792 | 5984x |
setGeneric("value_labels", function(obj) standardGeneric("value_labels"))
|
| 1793 | ||
| 1794 |
#' @rdname int_methods |
|
| 1795 | ! |
setMethod("value_labels", "ANY", function(obj) as.character(obj_label(obj)))
|
| 1796 | ||
| 1797 |
#' @rdname int_methods |
|
| 1798 |
setMethod( |
|
| 1799 |
"value_labels", "TreePos", |
|
| 1800 | ! |
function(obj) sapply(pos_splvals(obj), obj_label) |
| 1801 |
) |
|
| 1802 | ||
| 1803 |
#' @rdname int_methods |
|
| 1804 |
setMethod("value_labels", "list", function(obj) {
|
|
| 1805 | 4095x |
ret <- lapply(obj, obj_label) |
| 1806 | 4095x |
if (!is.null(names(obj))) {
|
| 1807 | 545x |
inds <- vapply(ret, function(x) length(x) == 0, NA) |
| 1808 | 545x |
ret[inds] <- names(obj)[inds] |
| 1809 |
} |
|
| 1810 | 4095x |
ret |
| 1811 |
}) |
|
| 1812 | ||
| 1813 |
#' @rdname int_methods |
|
| 1814 |
setMethod( |
|
| 1815 |
"value_labels", |
|
| 1816 |
"RowsVerticalSection", |
|
| 1817 | 1839x |
function(obj) setNames(attr(obj, "row_labels", exact = TRUE), value_names(obj)) |
| 1818 |
) |
|
| 1819 | ||
| 1820 |
#' @rdname int_methods |
|
| 1821 | ! |
setMethod("value_labels", "ValueWrapper", function(obj) obj_label(obj))
|
| 1822 | ||
| 1823 |
#' @rdname int_methods |
|
| 1824 |
setMethod( |
|
| 1825 |
"value_labels", "LevelComboSplitValue", |
|
| 1826 | ! |
function(obj) obj_label(obj) |
| 1827 |
) |
|
| 1828 | ||
| 1829 |
#' @rdname int_methods |
|
| 1830 | 50x |
setMethod("value_labels", "MultiVarSplit", function(obj) obj@var_labels)
|
| 1831 | ||
| 1832 |
#' @rdname int_methods |
|
| 1833 | 6050x |
setGeneric("value_expr", function(obj) standardGeneric("value_expr"))
|
| 1834 |
#' @rdname int_methods |
|
| 1835 | 118x |
setMethod("value_expr", "ValueWrapper", function(obj) obj@subset_expression)
|
| 1836 |
#' @rdname int_methods |
|
| 1837 | ! |
setMethod("value_expr", "ANY", function(obj) NULL)
|
| 1838 |
## no setters for now, we'll see about that. |
|
| 1839 | ||
| 1840 |
#' @rdname int_methods |
|
| 1841 | 6x |
setGeneric("spl_varlabels", function(obj) standardGeneric("spl_varlabels"))
|
| 1842 | ||
| 1843 |
#' @rdname int_methods |
|
| 1844 | 6x |
setMethod("spl_varlabels", "MultiVarSplit", function(obj) obj@var_labels)
|
| 1845 | ||
| 1846 |
#' @rdname int_methods |
|
| 1847 |
setGeneric( |
|
| 1848 |
"spl_varlabels<-", |
|
| 1849 | 2x |
function(object, value) standardGeneric("spl_varlabels<-")
|
| 1850 |
) |
|
| 1851 | ||
| 1852 |
#' @rdname int_methods |
|
| 1853 |
setMethod("spl_varlabels<-", "MultiVarSplit", function(object, value) {
|
|
| 1854 | 2x |
object@var_labels <- value |
| 1855 | 2x |
object |
| 1856 |
}) |
|
| 1857 | ||
| 1858 |
## These two are similar enough we could probably combine |
|
| 1859 |
## them but conceptually they are pretty different |
|
| 1860 |
## split_exargs is a list of extra arguments that apply |
|
| 1861 |
## to *all the chidlren*, |
|
| 1862 |
## while splv_extra is for *child-specific* extra arguments, |
|
| 1863 |
## associated with specific values of the split |
|
| 1864 |
#' @rdname int_methods |
|
| 1865 | 3995x |
setGeneric("splv_extra", function(obj) standardGeneric("splv_extra"))
|
| 1866 | ||
| 1867 |
#' @rdname int_methods |
|
| 1868 |
setMethod( |
|
| 1869 |
"splv_extra", "SplitValue", |
|
| 1870 | 3995x |
function(obj) obj@extra |
| 1871 |
) |
|
| 1872 | ||
| 1873 |
#' @rdname int_methods |
|
| 1874 |
setGeneric( |
|
| 1875 |
"splv_extra<-", |
|
| 1876 | 2217x |
function(obj, value) standardGeneric("splv_extra<-")
|
| 1877 |
) |
|
| 1878 |
#' @rdname int_methods |
|
| 1879 |
setMethod( |
|
| 1880 |
"splv_extra<-", "SplitValue", |
|
| 1881 |
function(obj, value) {
|
|
| 1882 | 2217x |
obj@extra <- value |
| 1883 | 2217x |
obj |
| 1884 |
} |
|
| 1885 |
) |
|
| 1886 | ||
| 1887 |
#' @rdname int_methods |
|
| 1888 | 2495x |
setGeneric("split_exargs", function(obj) standardGeneric("split_exargs"))
|
| 1889 | ||
| 1890 |
#' @rdname int_methods |
|
| 1891 |
setMethod( |
|
| 1892 |
"split_exargs", "Split", |
|
| 1893 | 2443x |
function(obj) obj@extra_args |
| 1894 |
) |
|
| 1895 | ||
| 1896 |
#' @rdname int_methods |
|
| 1897 |
setGeneric( |
|
| 1898 |
"split_exargs<-", |
|
| 1899 | 1x |
function(obj, value) standardGeneric("split_exargs<-")
|
| 1900 |
) |
|
| 1901 | ||
| 1902 |
#' @rdname int_methods |
|
| 1903 |
setMethod( |
|
| 1904 |
"split_exargs<-", "Split", |
|
| 1905 |
function(obj, value) {
|
|
| 1906 | 1x |
obj@extra_args <- value |
| 1907 | 1x |
obj |
| 1908 |
} |
|
| 1909 |
) |
|
| 1910 | ||
| 1911 | ! |
is_labrow <- function(obj) is(obj, "LabelRow") |
| 1912 | ||
| 1913 |
spl_ref_group <- function(obj) {
|
|
| 1914 | 17x |
stopifnot(is(obj, "VarLevWBaselineSplit")) |
| 1915 | 17x |
obj@ref_group_value |
| 1916 |
} |
|
| 1917 | ||
| 1918 |
### column info |
|
| 1919 | ||
| 1920 |
#' Column information/structure accessors |
|
| 1921 |
#' |
|
| 1922 |
#' @inheritParams gen_args |
|
| 1923 |
#' @param df (`data.frame` or `NULL`)\cr data to use if the column information is being |
|
| 1924 |
#' generated from a pre-data layout object. |
|
| 1925 |
#' @param path (`character` or `NULL`)\cr `col_counts` accessor and setter only. |
|
| 1926 |
#' Path (in column structure). |
|
| 1927 |
#' @param rtpos (`TreePos`)\cr root position. |
|
| 1928 |
#' |
|
| 1929 |
#' @return A `LayoutColTree` object. |
|
| 1930 |
#' |
|
| 1931 |
#' @rdname col_accessors |
|
| 1932 |
#' @export |
|
| 1933 | 4433x |
setGeneric("clayout", function(obj) standardGeneric("clayout"))
|
| 1934 | ||
| 1935 |
#' @rdname col_accessors |
|
| 1936 |
#' @exportMethod clayout |
|
| 1937 |
setMethod( |
|
| 1938 |
"clayout", "VTableNodeInfo", |
|
| 1939 | 33x |
function(obj) coltree(col_info(obj)) |
| 1940 |
) |
|
| 1941 | ||
| 1942 |
#' @rdname col_accessors |
|
| 1943 |
#' @exportMethod clayout |
|
| 1944 |
setMethod( |
|
| 1945 |
"clayout", "PreDataTableLayouts", |
|
| 1946 | 4400x |
function(obj) obj@col_layout |
| 1947 |
) |
|
| 1948 | ||
| 1949 |
## useful convenience for the cascading methods in colby_constructors |
|
| 1950 |
#' @rdname col_accessors |
|
| 1951 |
#' @exportMethod clayout |
|
| 1952 | ! |
setMethod("clayout", "ANY", function(obj) PreDataColLayout())
|
| 1953 | ||
| 1954 |
#' @rdname col_accessors |
|
| 1955 |
#' @export |
|
| 1956 | 1619x |
setGeneric("clayout<-", function(object, value) standardGeneric("clayout<-"))
|
| 1957 | ||
| 1958 |
#' @rdname col_accessors |
|
| 1959 |
#' @exportMethod clayout<- |
|
| 1960 |
setMethod( |
|
| 1961 |
"clayout<-", "PreDataTableLayouts", |
|
| 1962 |
function(object, value) {
|
|
| 1963 | 1619x |
object@col_layout <- value |
| 1964 | 1619x |
object |
| 1965 |
} |
|
| 1966 |
) |
|
| 1967 | ||
| 1968 |
#' @rdname col_accessors |
|
| 1969 |
#' @export |
|
| 1970 | 291312x |
setGeneric("col_info", function(obj) standardGeneric("col_info"))
|
| 1971 | ||
| 1972 |
#' @rdname col_accessors |
|
| 1973 |
#' @exportMethod col_info |
|
| 1974 |
setMethod( |
|
| 1975 |
"col_info", "VTableNodeInfo", |
|
| 1976 | 257110x |
function(obj) obj@col_info |
| 1977 |
) |
|
| 1978 | ||
| 1979 |
### XXX I've made this recursive. Do we ALWAYS want it to be? |
|
| 1980 |
### |
|
| 1981 |
### I think we do. |
|
| 1982 |
#' @rdname col_accessors |
|
| 1983 |
#' @export |
|
| 1984 | 73228x |
setGeneric("col_info<-", function(obj, value) standardGeneric("col_info<-"))
|
| 1985 | ||
| 1986 |
#' @return Returns various information about columns, depending on the accessor used. |
|
| 1987 |
#' |
|
| 1988 |
#' @exportMethod col_info<- |
|
| 1989 |
#' @rdname col_accessors |
|
| 1990 |
setMethod( |
|
| 1991 |
"col_info<-", "TableRow", |
|
| 1992 |
function(obj, value) {
|
|
| 1993 | 43385x |
obj@col_info <- value |
| 1994 | 43385x |
obj |
| 1995 |
} |
|
| 1996 |
) |
|
| 1997 | ||
| 1998 |
.set_cinfo_kids <- function(obj) {
|
|
| 1999 | 22909x |
kids <- lapply( |
| 2000 | 22909x |
tree_children(obj), |
| 2001 | 22909x |
function(x) {
|
| 2002 | 53521x |
col_info(x) <- col_info(obj) |
| 2003 | 53521x |
x |
| 2004 |
} |
|
| 2005 |
) |
|
| 2006 | 22909x |
tree_children(obj) <- kids |
| 2007 | 22909x |
obj |
| 2008 |
} |
|
| 2009 | ||
| 2010 |
#' @rdname col_accessors |
|
| 2011 |
#' @exportMethod col_info<- |
|
| 2012 |
setMethod( |
|
| 2013 |
"col_info<-", "ElementaryTable", |
|
| 2014 |
function(obj, value) {
|
|
| 2015 | 14827x |
obj@col_info <- value |
| 2016 | 14827x |
.set_cinfo_kids(obj) |
| 2017 |
} |
|
| 2018 |
) |
|
| 2019 | ||
| 2020 |
#' @rdname col_accessors |
|
| 2021 |
#' @exportMethod col_info<- |
|
| 2022 |
setMethod( |
|
| 2023 |
"col_info<-", "TableTree", |
|
| 2024 |
function(obj, value) {
|
|
| 2025 | 8082x |
obj@col_info <- value |
| 2026 | 8082x |
if (nrow(content_table(obj))) {
|
| 2027 | 2147x |
ct <- content_table(obj) |
| 2028 | 2147x |
col_info(ct) <- value |
| 2029 | 2147x |
content_table(obj) <- ct |
| 2030 |
} |
|
| 2031 | 8082x |
.set_cinfo_kids(obj) |
| 2032 |
} |
|
| 2033 |
) |
|
| 2034 | ||
| 2035 |
#' @rdname col_accessors |
|
| 2036 |
#' @param ccount_format (`FormatSpec`)\cr The format to be used by default for column |
|
| 2037 |
#' counts throughout this column tree (i.e. if not overridden by a more specific format |
|
| 2038 |
#' specification). |
|
| 2039 |
#' @export |
|
| 2040 |
setGeneric( |
|
| 2041 |
"coltree", |
|
| 2042 | 13577x |
function(obj, df = NULL, rtpos = TreePos(), alt_counts_df = df, ccount_format = "(N=xx)") standardGeneric("coltree")
|
| 2043 |
) |
|
| 2044 | ||
| 2045 |
#' @rdname col_accessors |
|
| 2046 |
#' @exportMethod coltree |
|
| 2047 |
setMethod( |
|
| 2048 |
"coltree", "InstantiatedColumnInfo", |
|
| 2049 |
function(obj, df = NULL, rtpos = TreePos(), alt_counts_df = df, ccount_format) {
|
|
| 2050 | 9071x |
if (!is.null(df)) {
|
| 2051 | ! |
warning("Ignoring df argument and retrieving already-computed LayoutColTree")
|
| 2052 |
} |
|
| 2053 | 9071x |
obj@tree_layout |
| 2054 |
} |
|
| 2055 |
) |
|
| 2056 | ||
| 2057 |
#' @rdname col_accessors |
|
| 2058 |
#' @export coltree |
|
| 2059 |
setMethod( |
|
| 2060 |
"coltree", "PreDataTableLayouts", |
|
| 2061 |
function(obj, df, rtpos, alt_counts_df = df, ccount_format) {
|
|
| 2062 | 1x |
coltree(clayout(obj), df, rtpos, alt_counts_df = alt_counts_df, ccount_format = ccount_format) |
| 2063 |
} |
|
| 2064 |
) |
|
| 2065 | ||
| 2066 |
#' @rdname col_accessors |
|
| 2067 |
#' @export coltree |
|
| 2068 |
setMethod( |
|
| 2069 |
"coltree", "PreDataColLayout", |
|
| 2070 |
function(obj, df, rtpos, alt_counts_df = df, ccount_format) {
|
|
| 2071 | 368x |
obj <- set_def_child_ord(obj, df) |
| 2072 | 368x |
kids <- lapply( |
| 2073 | 368x |
obj, |
| 2074 | 368x |
function(x) {
|
| 2075 | 377x |
splitvec_to_coltree( |
| 2076 | 377x |
df = df, |
| 2077 | 377x |
splvec = x, |
| 2078 | 377x |
pos = rtpos, |
| 2079 | 377x |
alt_counts_df = alt_counts_df, |
| 2080 | 377x |
global_cc_format = ccount_format |
| 2081 |
) |
|
| 2082 |
} |
|
| 2083 |
) |
|
| 2084 | 361x |
if (length(kids) == 1) {
|
| 2085 | 353x |
res <- kids[[1]] |
| 2086 |
} else {
|
|
| 2087 | 8x |
res <- LayoutColTree( |
| 2088 | 8x |
lev = 0L, |
| 2089 | 8x |
kids = kids, |
| 2090 | 8x |
tpos = rtpos, |
| 2091 | 8x |
spl = RootSplit(), |
| 2092 | 8x |
colcount = NROW(alt_counts_df), |
| 2093 | 8x |
colcount_format = ccount_format |
| 2094 |
) |
|
| 2095 |
} |
|
| 2096 | 361x |
disp_ccounts(res) <- disp_ccounts(obj) |
| 2097 | 361x |
res |
| 2098 |
} |
|
| 2099 |
) |
|
| 2100 | ||
| 2101 |
#' @rdname col_accessors |
|
| 2102 |
#' @export coltree |
|
| 2103 |
setMethod( |
|
| 2104 |
"coltree", "LayoutColTree", |
|
| 2105 |
function(obj, df, rtpos, alt_counts_df, ccount_format) obj |
|
| 2106 |
) |
|
| 2107 | ||
| 2108 |
#' @rdname col_accessors |
|
| 2109 |
#' @export coltree |
|
| 2110 |
setMethod( |
|
| 2111 |
"coltree", "VTableTree", |
|
| 2112 |
function(obj, df, rtpos, alt_counts_df, ccount_format) coltree(col_info(obj)) |
|
| 2113 |
) |
|
| 2114 | ||
| 2115 |
#' @rdname col_accessors |
|
| 2116 |
#' @export coltree |
|
| 2117 |
setMethod( |
|
| 2118 |
"coltree", "TableRow", |
|
| 2119 |
function(obj, df, rtpos, alt_counts_df, ccount_format) coltree(col_info(obj)) |
|
| 2120 |
) |
|
| 2121 | ||
| 2122 | 1029x |
setGeneric("coltree<-", function(obj, value) standardGeneric("coltree<-"))
|
| 2123 |
setMethod( |
|
| 2124 |
"coltree<-", c("InstantiatedColumnInfo", "LayoutColTree"),
|
|
| 2125 |
function(obj, value) {
|
|
| 2126 | 551x |
obj@tree_layout <- value |
| 2127 | 551x |
obj |
| 2128 |
} |
|
| 2129 |
) |
|
| 2130 | ||
| 2131 |
setMethod( |
|
| 2132 |
"coltree<-", c("VTableTree", "LayoutColTree"),
|
|
| 2133 |
function(obj, value) {
|
|
| 2134 | 478x |
cinfo <- col_info(obj) |
| 2135 | 478x |
coltree(cinfo) <- value |
| 2136 | 478x |
col_info(obj) <- cinfo |
| 2137 | 478x |
obj |
| 2138 |
} |
|
| 2139 |
) |
|
| 2140 | ||
| 2141 |
#' @rdname col_accessors |
|
| 2142 |
#' @export |
|
| 2143 | 132120x |
setGeneric("col_exprs", function(obj, df = NULL) standardGeneric("col_exprs"))
|
| 2144 | ||
| 2145 |
#' @rdname col_accessors |
|
| 2146 |
#' @export col_exprs |
|
| 2147 |
setMethod( |
|
| 2148 |
"col_exprs", "PreDataTableLayouts", |
|
| 2149 | 1x |
function(obj, df = NULL) col_exprs(clayout(obj), df) |
| 2150 |
) |
|
| 2151 | ||
| 2152 |
#' @rdname col_accessors |
|
| 2153 |
#' @export col_exprs |
|
| 2154 |
setMethod( |
|
| 2155 |
"col_exprs", "PreDataColLayout", |
|
| 2156 |
function(obj, df = NULL) {
|
|
| 2157 | 1x |
if (is.null(df)) {
|
| 2158 | ! |
stop("can't determine col_exprs without data")
|
| 2159 |
} |
|
| 2160 | 1x |
ct <- coltree(obj, df = df) |
| 2161 | 1x |
make_col_subsets(ct, df = df) |
| 2162 |
} |
|
| 2163 |
) |
|
| 2164 | ||
| 2165 |
#' @rdname col_accessors |
|
| 2166 |
#' @export col_exprs |
|
| 2167 |
setMethod( |
|
| 2168 |
"col_exprs", "InstantiatedColumnInfo", |
|
| 2169 |
function(obj, df = NULL) {
|
|
| 2170 | 132118x |
if (!is.null(df)) {
|
| 2171 | ! |
warning("Ignoring df method when extracted precomputed column subsetting expressions.")
|
| 2172 |
} |
|
| 2173 | 132118x |
obj@subset_exprs |
| 2174 |
} |
|
| 2175 |
) |
|
| 2176 | ||
| 2177 |
#' @rdname int_methods |
|
| 2178 | 2896x |
setGeneric("col_extra_args", function(obj, df = NULL) standardGeneric("col_extra_args"))
|
| 2179 | ||
| 2180 |
#' @rdname int_methods |
|
| 2181 |
setMethod( |
|
| 2182 |
"col_extra_args", "InstantiatedColumnInfo", |
|
| 2183 |
function(obj, df) {
|
|
| 2184 | 2535x |
if (!is.null(df)) {
|
| 2185 | ! |
warning("Ignorning df when retrieving already-computed column extra arguments.")
|
| 2186 |
} |
|
| 2187 | 2535x |
obj@cextra_args |
| 2188 |
} |
|
| 2189 |
) |
|
| 2190 | ||
| 2191 |
#' @rdname int_methods |
|
| 2192 |
setMethod( |
|
| 2193 |
"col_extra_args", "PreDataTableLayouts", |
|
| 2194 |
function(obj, df) col_extra_args(clayout(obj), df) |
|
| 2195 |
) |
|
| 2196 | ||
| 2197 |
#' @rdname int_methods |
|
| 2198 |
setMethod( |
|
| 2199 |
"col_extra_args", "PreDataColLayout", |
|
| 2200 |
function(obj, df) {
|
|
| 2201 | ! |
col_extra_args(coltree(obj, df), NULL) |
| 2202 |
} |
|
| 2203 |
) |
|
| 2204 | ||
| 2205 |
#' @rdname int_methods |
|
| 2206 |
setMethod( |
|
| 2207 |
"col_extra_args", "LayoutColTree", |
|
| 2208 |
function(obj, df) {
|
|
| 2209 | 361x |
if (!is.null(df)) {
|
| 2210 | ! |
warning("Ignoring df argument and returning already calculated extra arguments")
|
| 2211 |
} |
|
| 2212 | 361x |
get_col_extras(obj) |
| 2213 |
} |
|
| 2214 |
) |
|
| 2215 | ||
| 2216 |
#' @rdname int_methods |
|
| 2217 |
setMethod( |
|
| 2218 |
"col_extra_args", "LayoutColLeaf", |
|
| 2219 |
function(obj, df) {
|
|
| 2220 | ! |
if (!is.null(df)) {
|
| 2221 | ! |
warning("Ignoring df argument and returning already calculated extra arguments")
|
| 2222 |
} |
|
| 2223 | ||
| 2224 | ! |
get_pos_extra(pos = tree_pos(obj)) |
| 2225 |
} |
|
| 2226 |
) |
|
| 2227 | ||
| 2228 |
#' @seealso [facet_colcount()] |
|
| 2229 |
#' @export |
|
| 2230 |
#' @rdname col_accessors |
|
| 2231 | 2269x |
setGeneric("col_counts", function(obj, path = NULL) standardGeneric("col_counts"))
|
| 2232 | ||
| 2233 |
#' @export |
|
| 2234 |
#' @rdname col_accessors |
|
| 2235 |
setMethod( |
|
| 2236 |
"col_counts", "InstantiatedColumnInfo", |
|
| 2237 |
function(obj, path = NULL) {
|
|
| 2238 | 2250x |
if (is.null(path)) {
|
| 2239 | 2249x |
lfs <- collect_leaves(coltree(obj)) |
| 2240 | 2249x |
ret <- vapply(lfs, facet_colcount, 1L, path = NULL) |
| 2241 |
} else {
|
|
| 2242 | 1x |
ret <- facet_colcount(obj, path) |
| 2243 |
} |
|
| 2244 |
## required for strict backwards compatibility, |
|
| 2245 |
## even though its undesirable behavior. |
|
| 2246 | 2250x |
unname(ret) |
| 2247 |
} |
|
| 2248 |
) |
|
| 2249 | ||
| 2250 |
#' @export |
|
| 2251 |
#' @rdname col_accessors |
|
| 2252 |
setMethod( |
|
| 2253 |
"col_counts", "VTableNodeInfo", |
|
| 2254 | 19x |
function(obj, path = NULL) col_counts(col_info(obj), path = path) |
| 2255 |
) |
|
| 2256 | ||
| 2257 |
#' @export |
|
| 2258 |
#' @rdname col_accessors |
|
| 2259 | 14x |
setGeneric("col_counts<-", function(obj, path = NULL, value) standardGeneric("col_counts<-"))
|
| 2260 | ||
| 2261 |
#' @export |
|
| 2262 |
#' @rdname col_accessors |
|
| 2263 |
setMethod( |
|
| 2264 |
"col_counts<-", "InstantiatedColumnInfo", |
|
| 2265 |
function(obj, path = NULL, value) {
|
|
| 2266 |
## obj@counts[.path_to_pos(path, obj, cols = TRUE)] <- value |
|
| 2267 |
## obj |
|
| 2268 | 9x |
if (!is.null(path)) {
|
| 2269 | 1x |
all_paths <- list(path) |
| 2270 |
} else {
|
|
| 2271 | 8x |
all_paths <- make_col_df(obj, visible_only = TRUE)$path |
| 2272 |
} |
|
| 2273 | 9x |
if (length(value) != length(all_paths)) {
|
| 2274 | ! |
stop( |
| 2275 | ! |
"Got ", length(value), " values for ", |
| 2276 | ! |
length(all_paths), " column paths", |
| 2277 | ! |
if (is.null(path)) " (from path = NULL)", |
| 2278 |
"." |
|
| 2279 |
) |
|
| 2280 |
} |
|
| 2281 | 9x |
ctree <- coltree(obj) |
| 2282 | 9x |
for (i in seq_along(all_paths)) {
|
| 2283 | 73x |
facet_colcount(ctree, all_paths[[i]]) <- value[i] |
| 2284 |
} |
|
| 2285 | 9x |
coltree(obj) <- ctree |
| 2286 | 9x |
obj |
| 2287 |
} |
|
| 2288 |
) |
|
| 2289 | ||
| 2290 |
#' @export |
|
| 2291 |
#' @rdname col_accessors |
|
| 2292 |
setMethod( |
|
| 2293 |
"col_counts<-", "VTableNodeInfo", |
|
| 2294 |
function(obj, path = NULL, value) {
|
|
| 2295 | 5x |
cinfo <- col_info(obj) |
| 2296 | 5x |
col_counts(cinfo, path = path) <- value |
| 2297 | 5x |
col_info(obj) <- cinfo |
| 2298 | 5x |
obj |
| 2299 |
} |
|
| 2300 |
) |
|
| 2301 | ||
| 2302 |
#' @export |
|
| 2303 |
#' @rdname col_accessors |
|
| 2304 | 1819x |
setGeneric("col_total", function(obj) standardGeneric("col_total"))
|
| 2305 | ||
| 2306 |
#' @export |
|
| 2307 |
#' @rdname col_accessors |
|
| 2308 |
setMethod( |
|
| 2309 |
"col_total", "InstantiatedColumnInfo", |
|
| 2310 | 1818x |
function(obj) obj@total_count |
| 2311 |
) |
|
| 2312 | ||
| 2313 |
#' @export |
|
| 2314 |
#' @rdname col_accessors |
|
| 2315 |
setMethod( |
|
| 2316 |
"col_total", "VTableNodeInfo", |
|
| 2317 | 1x |
function(obj) col_total(col_info(obj)) |
| 2318 |
) |
|
| 2319 | ||
| 2320 |
#' @export |
|
| 2321 |
#' @rdname col_accessors |
|
| 2322 | 2x |
setGeneric("col_total<-", function(obj, value) standardGeneric("col_total<-"))
|
| 2323 | ||
| 2324 |
#' @export |
|
| 2325 |
#' @rdname col_accessors |
|
| 2326 |
setMethod( |
|
| 2327 |
"col_total<-", "InstantiatedColumnInfo", |
|
| 2328 |
function(obj, value) {
|
|
| 2329 |
## all methods funnel to this one so ensure integer-ness here. |
|
| 2330 | 1x |
obj@total_count <- as.integer(value) |
| 2331 | 1x |
obj |
| 2332 |
} |
|
| 2333 |
) |
|
| 2334 | ||
| 2335 |
#' @export |
|
| 2336 |
#' @rdname col_accessors |
|
| 2337 |
setMethod( |
|
| 2338 |
"col_total<-", "VTableNodeInfo", |
|
| 2339 |
function(obj, value) {
|
|
| 2340 | 1x |
cinfo <- col_info(obj) |
| 2341 | 1x |
col_total(cinfo) <- value |
| 2342 | 1x |
col_info(obj) <- cinfo |
| 2343 | 1x |
obj |
| 2344 |
} |
|
| 2345 |
) |
|
| 2346 | ||
| 2347 |
#' @rdname int_methods |
|
| 2348 | 20697x |
setGeneric("disp_ccounts", function(obj) standardGeneric("disp_ccounts"))
|
| 2349 | ||
| 2350 |
#' @rdname int_methods |
|
| 2351 |
setMethod( |
|
| 2352 |
"disp_ccounts", "VTableTree", |
|
| 2353 | 371x |
function(obj) disp_ccounts(col_info(obj)) |
| 2354 |
) |
|
| 2355 | ||
| 2356 |
#' @rdname int_methods |
|
| 2357 |
setMethod( |
|
| 2358 |
"disp_ccounts", "InstantiatedColumnInfo", |
|
| 2359 | 690x |
function(obj) obj@display_columncounts |
| 2360 |
) |
|
| 2361 | ||
| 2362 |
#' @rdname int_methods |
|
| 2363 |
setMethod( |
|
| 2364 |
"disp_ccounts", "PreDataTableLayouts", |
|
| 2365 | 1050x |
function(obj) disp_ccounts(clayout(obj)) |
| 2366 |
) |
|
| 2367 | ||
| 2368 |
#' @rdname int_methods |
|
| 2369 |
setMethod( |
|
| 2370 |
"disp_ccounts", "PreDataColLayout", |
|
| 2371 | 1411x |
function(obj) obj@display_columncounts |
| 2372 |
) |
|
| 2373 | ||
| 2374 |
#' @rdname int_methods |
|
| 2375 |
setMethod( |
|
| 2376 |
"disp_ccounts", "LayoutColTree", |
|
| 2377 | 757x |
function(obj) obj@display_columncounts |
| 2378 |
) |
|
| 2379 | ||
| 2380 |
#' @rdname int_methods |
|
| 2381 |
setMethod( |
|
| 2382 |
"disp_ccounts", "LayoutColLeaf", |
|
| 2383 | 14914x |
function(obj) obj@display_columncounts |
| 2384 |
) |
|
| 2385 | ||
| 2386 |
#' @rdname int_methods |
|
| 2387 |
setMethod( |
|
| 2388 |
"disp_ccounts", "Split", |
|
| 2389 | 1357x |
function(obj) obj@child_show_colcounts |
| 2390 |
) |
|
| 2391 | ||
| 2392 |
#' @rdname int_methods |
|
| 2393 | 2420x |
setGeneric("disp_ccounts<-", function(obj, value) standardGeneric("disp_ccounts<-"))
|
| 2394 | ||
| 2395 |
#' @rdname int_methods |
|
| 2396 |
setMethod( |
|
| 2397 |
"disp_ccounts<-", "VTableTree", |
|
| 2398 |
function(obj, value) {
|
|
| 2399 | 1x |
cinfo <- col_info(obj) |
| 2400 | 1x |
disp_ccounts(cinfo) <- value |
| 2401 | 1x |
col_info(obj) <- cinfo |
| 2402 | 1x |
obj |
| 2403 |
} |
|
| 2404 |
) |
|
| 2405 | ||
| 2406 |
#' @rdname int_methods |
|
| 2407 |
setMethod( |
|
| 2408 |
"disp_ccounts<-", "InstantiatedColumnInfo", |
|
| 2409 |
function(obj, value) {
|
|
| 2410 | 2x |
obj@display_columncounts <- value |
| 2411 | 2x |
obj |
| 2412 |
} |
|
| 2413 |
) |
|
| 2414 | ||
| 2415 |
#' @rdname int_methods |
|
| 2416 |
setMethod( |
|
| 2417 |
"disp_ccounts<-", "PreDataColLayout", |
|
| 2418 |
function(obj, value) {
|
|
| 2419 | 366x |
obj@display_columncounts <- value |
| 2420 | 366x |
obj |
| 2421 |
} |
|
| 2422 |
) |
|
| 2423 | ||
| 2424 |
#' @rdname int_methods |
|
| 2425 |
setMethod( |
|
| 2426 |
"disp_ccounts<-", "LayoutColTree", |
|
| 2427 |
function(obj, value) {
|
|
| 2428 | 362x |
obj@display_columncounts <- value |
| 2429 | 362x |
obj |
| 2430 |
} |
|
| 2431 |
) |
|
| 2432 | ||
| 2433 |
#' @rdname int_methods |
|
| 2434 |
setMethod( |
|
| 2435 |
"disp_ccounts<-", "LayoutColLeaf", |
|
| 2436 |
function(obj, value) {
|
|
| 2437 | 1323x |
obj@display_columncounts <- value |
| 2438 | 1323x |
obj |
| 2439 |
} |
|
| 2440 |
) |
|
| 2441 | ||
| 2442 |
#' @rdname int_methods |
|
| 2443 |
setMethod( |
|
| 2444 |
"disp_ccounts<-", "PreDataTableLayouts", |
|
| 2445 |
function(obj, value) {
|
|
| 2446 | 366x |
clyt <- clayout(obj) |
| 2447 | 366x |
disp_ccounts(clyt) <- value |
| 2448 | 366x |
clayout(obj) <- clyt |
| 2449 | 366x |
obj |
| 2450 |
} |
|
| 2451 |
) |
|
| 2452 | ||
| 2453 | ||
| 2454 |
## this is a horrible hack but when we have non-nested siblings at the top level |
|
| 2455 |
## the beginning of the "path <-> position" relationship breaks down. |
|
| 2456 |
## we probably *should* have e.g., c("root", "top_level_splname_1",
|
|
| 2457 |
## "top_level_splname_1, "top_level_splname_1_value", ...) |
|
| 2458 |
## but its pretty clear why no one will be happy with that, I think |
|
| 2459 |
## so we punt on the problem for now with an explicit workaround |
|
| 2460 |
## |
|
| 2461 |
## those first non-nested siblings currently have (incorrect) |
|
| 2462 |
## empty tree_pos elements so we just look at the obj_name |
|
| 2463 | ||
| 2464 |
pos_singleton_path <- function(obj) {
|
|
| 2465 | 6478x |
pos <- tree_pos(obj) |
| 2466 | 6478x |
splvals <- pos_splvals(pos) |
| 2467 | 6478x |
length(splvals) == 0 || |
| 2468 | 6478x |
(length(splvals) == 1 && is.na(unlist(value_names(splvals)))) |
| 2469 |
} |
|
| 2470 | ||
| 2471 |
## close to a duplicate of tt_at_path, but... not quite :( |
|
| 2472 |
#' @rdname int_methods |
|
| 2473 |
coltree_at_path <- function(obj, path, ...) {
|
|
| 2474 | 3186x |
if (length(path) == 0) {
|
| 2475 | 684x |
return(obj) |
| 2476 |
} |
|
| 2477 | 2502x |
stopifnot( |
| 2478 | 2502x |
is(path, "character"), |
| 2479 | 2502x |
length(path) > 0 |
| 2480 |
) |
|
| 2481 | 2502x |
if (any(grepl("@content", path, fixed = TRUE))) {
|
| 2482 | ! |
stop("@content token is not valid for column paths.")
|
| 2483 |
} |
|
| 2484 | ||
| 2485 | 2502x |
cur <- obj |
| 2486 | 2502x |
curpath <- pos_to_path(tree_pos(obj)) # path |
| 2487 | 2502x |
num_consume_path <- 2 |
| 2488 | 2502x |
while (!identical(curpath, path) && !is(cur, "LayoutColLeaf")) { # length(curpath) > 0) {
|
| 2489 | 4398x |
kids <- tree_children(cur) |
| 2490 | 4398x |
kidmatch <- find_kid_path_match(kids, path) |
| 2491 | 4398x |
if (length(kidmatch) == 0) {
|
| 2492 | ! |
stop( |
| 2493 | ! |
"unable to match full path: ", paste(path, sep = "->"), |
| 2494 | ! |
"\n path of last match: ", paste(curpath, sep = "->") |
| 2495 |
) |
|
| 2496 |
} |
|
| 2497 | 4398x |
cur <- kids[[kidmatch]] |
| 2498 | 4398x |
curpath <- pos_to_path(tree_pos(cur)) |
| 2499 |
} |
|
| 2500 | 2502x |
cur |
| 2501 |
} |
|
| 2502 | ||
| 2503 |
find_kid_path_match <- function(kids, path) {
|
|
| 2504 | 8792x |
if (length(kids) == 0) {
|
| 2505 | ! |
return(integer()) |
| 2506 |
} |
|
| 2507 | 8792x |
kidpaths <- lapply(kids, function(k) pos_to_path(tree_pos(k))) |
| 2508 | ||
| 2509 | 8792x |
matches <- vapply(kidpaths, function(kpth) identical(path[seq_along(kpth)], kpth), NA) |
| 2510 | 8792x |
firstkidpos <- tree_pos(kids[[1]]) |
| 2511 | 8792x |
if (all(matches) && pos_singleton_path(kids[[1]])) {
|
| 2512 | 756x |
kidpaths <- lapply(seq_along(kidpaths), function(i) c(kidpaths[[i]], obj_name(kids[[i]]))) |
| 2513 | 756x |
matches <- vapply(kidpaths, function(kpth) identical(path[seq_along(kpth)], kpth), NA) |
| 2514 |
} |
|
| 2515 | 8792x |
which(matches) |
| 2516 |
} |
|
| 2517 | ||
| 2518 | ||
| 2519 |
## almost a duplicate of recursive_replace, but I spent a bunch |
|
| 2520 |
## of time ramming my head against the different way pathing happens |
|
| 2521 |
## in column space (unfortunately) before giving up building |
|
| 2522 |
## coltree_at_path around recursive_replace, so here we are. |
|
| 2523 | ||
| 2524 |
ct_recursive_replace <- function(ctree, path, value, pos = 1) {
|
|
| 2525 | 6894x |
pos <- tree_pos(ctree) |
| 2526 | 6894x |
curpth <- pos_to_path(pos) |
| 2527 | 6894x |
if (identical(path, curpth)) {
|
| 2528 | 2500x |
return(value) |
| 2529 | 4394x |
} else if (is(ctree, "LayoutColLeaf")) {
|
| 2530 | ! |
stop( |
| 2531 | ! |
"unable to match full path: ", paste(path, sep = "->"), |
| 2532 | ! |
"\n path at leaf: ", paste(curpth, sep = "->") |
| 2533 |
) |
|
| 2534 |
} |
|
| 2535 | 4394x |
kids <- tree_children(ctree) |
| 2536 | 4394x |
kids_singl <- pos_singleton_path(kids[[1]]) |
| 2537 | 4394x |
kidind <- find_kid_path_match(kids, path) |
| 2538 | ||
| 2539 | 4394x |
if (length(kidind) == 0) {
|
| 2540 | ! |
stop("Path appears invalid for this tree at step ", path[1])
|
| 2541 | 4394x |
} else if (length(kidind) > 1) {
|
| 2542 | ! |
stop( |
| 2543 | ! |
"singleton step (root, cbind_root, etc) in path appears to have matched multiple children. ", |
| 2544 | ! |
"This shouldn't happen, please contact the maintainers." |
| 2545 |
) |
|
| 2546 |
} |
|
| 2547 | ||
| 2548 | 4394x |
kids[[kidind]] <- ct_recursive_replace( |
| 2549 | 4394x |
kids[[kidind]], |
| 2550 | 4394x |
path, value |
| 2551 |
) |
|
| 2552 | 4394x |
tree_children(ctree) <- kids |
| 2553 | 4394x |
ctree |
| 2554 |
} |
|
| 2555 | ||
| 2556 |
`coltree_at_path<-` <- function(obj, path, value) {
|
|
| 2557 | 2500x |
obj <- ct_recursive_replace(obj, path, value) |
| 2558 | 2500x |
obj |
| 2559 |
} |
|
| 2560 | ||
| 2561 |
#' Set visibility of column counts for a group of sibling facets |
|
| 2562 |
#' |
|
| 2563 |
#' @inheritParams gen_args |
|
| 2564 |
#' @param path (`character`)\cr the path *to the parent of the |
|
| 2565 |
#' desired siblings*. The last element in the path should |
|
| 2566 |
#' be a split name. |
|
| 2567 |
#' @return obj, modified with the desired column count. |
|
| 2568 |
#' display behavior |
|
| 2569 |
#' |
|
| 2570 |
#' @seealso [colcount_visible()] |
|
| 2571 |
#' |
|
| 2572 |
#' @export |
|
| 2573 |
`facet_colcounts_visible<-` <- function(obj, path, value) {
|
|
| 2574 | 1x |
coldf <- make_col_df(obj, visible_only = FALSE) |
| 2575 | 1x |
allpaths <- coldf$path |
| 2576 | 1x |
lenpath <- length(path) |
| 2577 | 1x |
match_paths <- vapply(allpaths, function(path_i) {
|
| 2578 | 10x |
(length(path_i) == lenpath + 1) && |
| 2579 | 10x |
(all(head(path_i, -1) == path)) |
| 2580 | 1x |
}, TRUE) |
| 2581 | 1x |
for (curpath in allpaths[match_paths]) {
|
| 2582 | 2x |
colcount_visible(obj, curpath) <- value |
| 2583 |
} |
|
| 2584 | 1x |
obj |
| 2585 |
} |
|
| 2586 | ||
| 2587 |
#' Get or set column count for a facet in column space |
|
| 2588 |
#' |
|
| 2589 |
#' @inheritParams gen_args |
|
| 2590 |
#' @param path character. This path must end on a |
|
| 2591 |
#' split value, e.g., the level of a categorical variable |
|
| 2592 |
#' that was split on in column space, but it need not |
|
| 2593 |
#' be the path to an individual column. |
|
| 2594 |
#' |
|
| 2595 |
#' @return for `facet_colcount` the current count associated |
|
| 2596 |
#' with that facet in column space, for `facet_colcount<-`, |
|
| 2597 |
#' `obj` modified with the new column count for the specified |
|
| 2598 |
#' facet. |
|
| 2599 |
#' |
|
| 2600 |
#' @note Updating a lower-level (more specific) |
|
| 2601 |
#' column count manually **will not** update the |
|
| 2602 |
#' counts for its parent facets. This cannot be made |
|
| 2603 |
#' automatic because the rtables framework does not |
|
| 2604 |
#' require sibling facets to be mutually exclusive |
|
| 2605 |
#' (e.g., total "arm", faceting into cumulative |
|
| 2606 |
#' quantiles, etc) and thus the count of a parent facet |
|
| 2607 |
#' will not always be simply the sum of the counts for |
|
| 2608 |
#' all of its children. |
|
| 2609 |
#' |
|
| 2610 |
#' @seealso [col_counts()] |
|
| 2611 |
#' |
|
| 2612 |
#' @examples |
|
| 2613 |
#' lyt <- basic_table() %>% |
|
| 2614 |
#' split_cols_by("ARM", show_colcounts = TRUE) %>%
|
|
| 2615 |
#' split_cols_by("SEX",
|
|
| 2616 |
#' split_fun = keep_split_levels(c("F", "M")),
|
|
| 2617 |
#' show_colcounts = TRUE |
|
| 2618 |
#' ) %>% |
|
| 2619 |
#' split_cols_by("STRATA1", show_colcounts = TRUE) %>%
|
|
| 2620 |
#' analyze("AGE")
|
|
| 2621 |
#' |
|
| 2622 |
#' tbl <- build_table(lyt, ex_adsl) |
|
| 2623 |
#' |
|
| 2624 |
#' facet_colcount(tbl, c("ARM", "A: Drug X"))
|
|
| 2625 |
#' facet_colcount(tbl, c("ARM", "A: Drug X", "SEX", "F"))
|
|
| 2626 |
#' facet_colcount(tbl, c("ARM", "A: Drug X", "SEX", "F", "STRATA1", "A"))
|
|
| 2627 |
#' |
|
| 2628 |
#' ## modify specific count after table creation |
|
| 2629 |
#' facet_colcount(tbl, c("ARM", "A: Drug X", "SEX", "F", "STRATA1", "A")) <- 25
|
|
| 2630 |
#' |
|
| 2631 |
#' ## show black space for certain counts by assign NA |
|
| 2632 |
#' |
|
| 2633 |
#' facet_colcount(tbl, c("ARM", "A: Drug X", "SEX", "F", "STRATA1", "C")) <- NA
|
|
| 2634 |
#' |
|
| 2635 |
#' @export |
|
| 2636 |
setGeneric( |
|
| 2637 |
"facet_colcount", |
|
| 2638 | 22835x |
function(obj, path) standardGeneric("facet_colcount")
|
| 2639 |
) |
|
| 2640 | ||
| 2641 |
#' @rdname facet_colcount |
|
| 2642 |
#' @export |
|
| 2643 |
setMethod( |
|
| 2644 |
"facet_colcount", "LayoutColTree", |
|
| 2645 |
function(obj, path = NULL) {
|
|
| 2646 |
## if(length(path) == 0L) |
|
| 2647 |
## stop("face_colcount requires a non-null path") #nocov
|
|
| 2648 | 685x |
subtree <- coltree_at_path(obj, path) |
| 2649 | 685x |
subtree@column_count |
| 2650 |
} |
|
| 2651 |
) |
|
| 2652 | ||
| 2653 |
#' @rdname facet_colcount |
|
| 2654 |
#' @export |
|
| 2655 |
setMethod( |
|
| 2656 |
"facet_colcount", "LayoutColLeaf", |
|
| 2657 |
function(obj, path = NULL) {
|
|
| 2658 |
## not sure if we should check for null here as above |
|
| 2659 | 22149x |
obj@column_count |
| 2660 |
} |
|
| 2661 |
) |
|
| 2662 | ||
| 2663 |
#' @rdname facet_colcount |
|
| 2664 |
#' @export |
|
| 2665 |
setMethod( |
|
| 2666 |
"facet_colcount", "VTableTree", |
|
| 2667 | ! |
function(obj, path) facet_colcount(coltree(obj), path = path) |
| 2668 |
) |
|
| 2669 | ||
| 2670 |
#' @rdname facet_colcount |
|
| 2671 |
#' @export |
|
| 2672 |
setMethod( |
|
| 2673 |
"facet_colcount", "InstantiatedColumnInfo", |
|
| 2674 | 1x |
function(obj, path) facet_colcount(coltree(obj), path = path) |
| 2675 |
) |
|
| 2676 | ||
| 2677 |
#' @rdname facet_colcount |
|
| 2678 |
#' @export |
|
| 2679 |
setGeneric( |
|
| 2680 |
"facet_colcount<-", |
|
| 2681 | 1178x |
function(obj, path, value) standardGeneric("facet_colcount<-")
|
| 2682 |
) |
|
| 2683 | ||
| 2684 |
#' @rdname facet_colcount |
|
| 2685 |
#' @export |
|
| 2686 |
setMethod( |
|
| 2687 |
"facet_colcount<-", "LayoutColTree", |
|
| 2688 |
function(obj, path, value) {
|
|
| 2689 | 1176x |
ct <- coltree_at_path(obj, path) |
| 2690 | 1176x |
ct@column_count <- as.integer(value) |
| 2691 | 1176x |
coltree_at_path(obj, path) <- ct |
| 2692 | 1176x |
obj |
| 2693 |
} |
|
| 2694 |
) |
|
| 2695 | ||
| 2696 |
#' @rdname facet_colcount |
|
| 2697 |
#' @export |
|
| 2698 |
setMethod( |
|
| 2699 |
"facet_colcount<-", "LayoutColLeaf", |
|
| 2700 |
function(obj, path, value) {
|
|
| 2701 | ! |
obj@column_count <- as.integer(value) |
| 2702 | ! |
obj |
| 2703 |
} |
|
| 2704 |
) |
|
| 2705 | ||
| 2706 |
#' @rdname facet_colcount |
|
| 2707 |
#' @export |
|
| 2708 |
setMethod( |
|
| 2709 |
"facet_colcount<-", "VTableTree", |
|
| 2710 |
function(obj, path, value) {
|
|
| 2711 | 1x |
cinfo <- col_info(obj) |
| 2712 | 1x |
facet_colcount(cinfo, path) <- value |
| 2713 | 1x |
col_info(obj) <- cinfo |
| 2714 | 1x |
obj |
| 2715 |
} |
|
| 2716 |
) |
|
| 2717 | ||
| 2718 |
#' @rdname facet_colcount |
|
| 2719 |
#' @export |
|
| 2720 |
setMethod( |
|
| 2721 |
"facet_colcount<-", "InstantiatedColumnInfo", |
|
| 2722 |
function(obj, path, value) {
|
|
| 2723 | 1x |
ct <- coltree(obj) |
| 2724 | 1x |
facet_colcount(ct, path) <- value |
| 2725 | 1x |
coltree(obj) <- ct |
| 2726 | 1x |
obj |
| 2727 |
} |
|
| 2728 |
) |
|
| 2729 | ||
| 2730 |
#' Value and Visibility of specific column counts by path |
|
| 2731 |
#' |
|
| 2732 |
#' @inheritParams gen_args |
|
| 2733 |
#' |
|
| 2734 |
#' @return for `colcount_visible` a logical scalar |
|
| 2735 |
#' indicating whether the specified position in |
|
| 2736 |
#' the column hierarchy is set to display its column count; |
|
| 2737 |
#' for `colcount_visible<-`, `obj` updated with |
|
| 2738 |
#' the specified count displaying behavior set. |
|
| 2739 |
#' |
|
| 2740 |
#' @note Users generally should not call `colcount_visible` |
|
| 2741 |
#' directly, as setting sibling facets to have differing |
|
| 2742 |
#' column count visibility will result in an error when |
|
| 2743 |
#' printing or paginating the table. |
|
| 2744 |
#' |
|
| 2745 |
#' @export |
|
| 2746 | 2x |
setGeneric("colcount_visible", function(obj, path) standardGeneric("colcount_visible"))
|
| 2747 | ||
| 2748 |
#' @rdname colcount_visible |
|
| 2749 |
#' @export |
|
| 2750 |
setMethod( |
|
| 2751 |
"colcount_visible", "VTableTree", |
|
| 2752 | 1x |
function(obj, path) colcount_visible(coltree(obj), path) |
| 2753 |
) |
|
| 2754 | ||
| 2755 |
#' @rdname colcount_visible |
|
| 2756 |
#' @export |
|
| 2757 |
setMethod( |
|
| 2758 |
"colcount_visible", "InstantiatedColumnInfo", |
|
| 2759 | ! |
function(obj, path) colcount_visible(coltree(obj), path) |
| 2760 |
) |
|
| 2761 | ||
| 2762 |
#' @rdname colcount_visible |
|
| 2763 |
#' @export |
|
| 2764 |
setMethod( |
|
| 2765 |
"colcount_visible", "LayoutColTree", |
|
| 2766 |
function(obj, path) {
|
|
| 2767 | 1x |
subtree <- coltree_at_path(obj, path) |
| 2768 | 1x |
disp_ccounts(subtree) |
| 2769 |
} |
|
| 2770 |
) |
|
| 2771 | ||
| 2772 |
#' @rdname colcount_visible |
|
| 2773 |
#' @export |
|
| 2774 | 1348x |
setGeneric("colcount_visible<-", function(obj, path, value) standardGeneric("colcount_visible<-"))
|
| 2775 | ||
| 2776 |
#' @rdname colcount_visible |
|
| 2777 |
#' @export |
|
| 2778 |
setMethod( |
|
| 2779 |
"colcount_visible<-", "VTableTree", |
|
| 2780 |
function(obj, path, value) {
|
|
| 2781 | 3x |
ctree <- coltree(obj) |
| 2782 | 3x |
colcount_visible(ctree, path) <- value |
| 2783 | 3x |
coltree(obj) <- ctree |
| 2784 | 3x |
obj |
| 2785 |
} |
|
| 2786 |
) |
|
| 2787 | ||
| 2788 |
#' @rdname colcount_visible |
|
| 2789 |
#' @export |
|
| 2790 |
setMethod( |
|
| 2791 |
"colcount_visible<-", "InstantiatedColumnInfo", |
|
| 2792 |
function(obj, path, value) {
|
|
| 2793 | 21x |
ctree <- coltree(obj) |
| 2794 | 21x |
colcount_visible(ctree, path) <- value |
| 2795 | 21x |
coltree(obj) <- ctree |
| 2796 | 21x |
obj |
| 2797 |
} |
|
| 2798 |
) |
|
| 2799 | ||
| 2800 | ||
| 2801 |
#' @rdname colcount_visible |
|
| 2802 |
#' @export |
|
| 2803 |
setMethod( |
|
| 2804 |
"colcount_visible<-", "LayoutColTree", |
|
| 2805 |
function(obj, path, value) {
|
|
| 2806 | 1324x |
subtree <- coltree_at_path(obj, path) |
| 2807 | 1324x |
disp_ccounts(subtree) <- value |
| 2808 | 1324x |
coltree_at_path(obj, path) <- subtree |
| 2809 | 1324x |
obj |
| 2810 |
} |
|
| 2811 |
) |
|
| 2812 | ||
| 2813 |
#' @rdname int_methods |
|
| 2814 |
#' @export |
|
| 2815 | 17428x |
setGeneric("colcount_format", function(obj) standardGeneric("colcount_format"))
|
| 2816 | ||
| 2817 |
#' @rdname int_methods |
|
| 2818 |
#' @export |
|
| 2819 |
setMethod( |
|
| 2820 |
"colcount_format", "InstantiatedColumnInfo", |
|
| 2821 | 696x |
function(obj) obj@columncount_format |
| 2822 |
) |
|
| 2823 | ||
| 2824 |
#' @rdname int_methods |
|
| 2825 |
#' @export |
|
| 2826 |
setMethod( |
|
| 2827 |
"colcount_format", "VTableNodeInfo", |
|
| 2828 | 381x |
function(obj) colcount_format(col_info(obj)) |
| 2829 |
) |
|
| 2830 | ||
| 2831 |
#' @rdname int_methods |
|
| 2832 |
#' @export |
|
| 2833 |
setMethod( |
|
| 2834 |
"colcount_format", "PreDataColLayout", |
|
| 2835 | 366x |
function(obj) obj@columncount_format |
| 2836 |
) |
|
| 2837 | ||
| 2838 |
#' @rdname int_methods |
|
| 2839 |
#' @export |
|
| 2840 |
setMethod( |
|
| 2841 |
"colcount_format", "PreDataTableLayouts", |
|
| 2842 | 366x |
function(obj) colcount_format(clayout(obj)) |
| 2843 |
) |
|
| 2844 | ||
| 2845 |
#' @rdname int_methods |
|
| 2846 |
#' @export |
|
| 2847 |
setMethod( |
|
| 2848 |
"colcount_format", "Split", |
|
| 2849 | 1357x |
function(obj) obj@child_colcount_format |
| 2850 |
) |
|
| 2851 | ||
| 2852 |
#' @rdname int_methods |
|
| 2853 |
#' @export |
|
| 2854 |
setMethod( |
|
| 2855 |
"colcount_format", "LayoutColTree", |
|
| 2856 | 684x |
function(obj) obj@columncount_format |
| 2857 |
) |
|
| 2858 | ||
| 2859 |
#' @rdname int_methods |
|
| 2860 |
#' @export |
|
| 2861 |
setMethod( |
|
| 2862 |
"colcount_format", "LayoutColLeaf", |
|
| 2863 | 13431x |
function(obj) obj@columncount_format |
| 2864 |
) |
|
| 2865 | ||
| 2866 | ||
| 2867 | ||
| 2868 |
#' @rdname int_methods |
|
| 2869 |
#' @export |
|
| 2870 |
setGeneric( |
|
| 2871 |
"colcount_format<-", |
|
| 2872 | 734x |
function(obj, value) standardGeneric("colcount_format<-")
|
| 2873 |
) |
|
| 2874 | ||
| 2875 |
#' @export |
|
| 2876 |
#' @rdname int_methods |
|
| 2877 |
setMethod( |
|
| 2878 |
"colcount_format<-", "InstantiatedColumnInfo", |
|
| 2879 |
function(obj, value) {
|
|
| 2880 | 1x |
obj@columncount_format <- value |
| 2881 | 1x |
obj |
| 2882 |
} |
|
| 2883 |
) |
|
| 2884 | ||
| 2885 |
#' @rdname int_methods |
|
| 2886 |
#' @export |
|
| 2887 |
setMethod( |
|
| 2888 |
"colcount_format<-", "VTableNodeInfo", |
|
| 2889 |
function(obj, value) {
|
|
| 2890 | 1x |
cinfo <- col_info(obj) |
| 2891 | 1x |
colcount_format(cinfo) <- value |
| 2892 | 1x |
col_info(obj) <- cinfo |
| 2893 | 1x |
obj |
| 2894 |
} |
|
| 2895 |
) |
|
| 2896 | ||
| 2897 |
#' @rdname int_methods |
|
| 2898 |
#' @export |
|
| 2899 |
setMethod( |
|
| 2900 |
"colcount_format<-", "PreDataColLayout", |
|
| 2901 |
function(obj, value) {
|
|
| 2902 | 366x |
obj@columncount_format <- value |
| 2903 | 366x |
obj |
| 2904 |
} |
|
| 2905 |
) |
|
| 2906 | ||
| 2907 |
#' @rdname int_methods |
|
| 2908 |
#' @export |
|
| 2909 |
setMethod( |
|
| 2910 |
"colcount_format<-", "PreDataTableLayouts", |
|
| 2911 |
function(obj, value) {
|
|
| 2912 | 366x |
clyt <- clayout(obj) |
| 2913 | 366x |
colcount_format(clyt) <- value |
| 2914 | 366x |
clayout(obj) <- clyt |
| 2915 | 366x |
obj |
| 2916 |
} |
|
| 2917 |
) |
|
| 2918 | ||
| 2919 |
## It'd probably be better if this had the full set of methods as above |
|
| 2920 |
## but its not currently modelled in the class and probably isn't needed |
|
| 2921 |
## super much |
|
| 2922 |
#' @rdname int_methods |
|
| 2923 |
#' @export |
|
| 2924 | 688x |
setGeneric("colcount_na_str", function(obj) standardGeneric("colcount_na_str"))
|
| 2925 | ||
| 2926 |
#' @rdname int_methods |
|
| 2927 |
#' @export |
|
| 2928 |
setMethod( |
|
| 2929 |
"colcount_na_str", "InstantiatedColumnInfo", |
|
| 2930 | 347x |
function(obj) obj@columncount_na_str |
| 2931 |
) |
|
| 2932 | ||
| 2933 |
#' @rdname int_methods |
|
| 2934 |
#' @export |
|
| 2935 |
setMethod( |
|
| 2936 |
"colcount_na_str", "VTableNodeInfo", |
|
| 2937 | 341x |
function(obj) colcount_na_str(col_info(obj)) |
| 2938 |
) |
|
| 2939 | ||
| 2940 |
#' @rdname int_methods |
|
| 2941 |
#' @export |
|
| 2942 |
setGeneric( |
|
| 2943 |
"colcount_na_str<-", |
|
| 2944 | 4x |
function(obj, value) standardGeneric("colcount_na_str<-")
|
| 2945 |
) |
|
| 2946 | ||
| 2947 |
#' @export |
|
| 2948 |
#' @rdname int_methods |
|
| 2949 |
setMethod( |
|
| 2950 |
"colcount_na_str<-", "InstantiatedColumnInfo", |
|
| 2951 |
function(obj, value) {
|
|
| 2952 | 2x |
obj@columncount_na_str <- value |
| 2953 | 2x |
obj |
| 2954 |
} |
|
| 2955 |
) |
|
| 2956 | ||
| 2957 |
#' @rdname int_methods |
|
| 2958 |
#' @export |
|
| 2959 |
setMethod( |
|
| 2960 |
"colcount_na_str<-", "VTableNodeInfo", |
|
| 2961 |
function(obj, value) {
|
|
| 2962 | 2x |
cinfo <- col_info(obj) |
| 2963 | 2x |
colcount_na_str(cinfo) <- value |
| 2964 | 2x |
col_info(obj) <- cinfo |
| 2965 | 2x |
obj |
| 2966 |
} |
|
| 2967 |
) |
|
| 2968 | ||
| 2969 |
#' Exported for use in `tern` |
|
| 2970 |
#' |
|
| 2971 |
#' Does the `table`/`row`/`InstantiatedColumnInfo` object contain no column structure information? |
|
| 2972 |
#' |
|
| 2973 |
#' @inheritParams gen_args |
|
| 2974 |
#' |
|
| 2975 |
#' @return `TRUE` if the object has no/empty instantiated column information, `FALSE` otherwise. |
|
| 2976 |
#' |
|
| 2977 |
#' @rdname no_info |
|
| 2978 |
#' @export |
|
| 2979 | 197154x |
setGeneric("no_colinfo", function(obj) standardGeneric("no_colinfo"))
|
| 2980 | ||
| 2981 |
#' @exportMethod no_colinfo |
|
| 2982 |
#' @rdname no_info |
|
| 2983 |
setMethod( |
|
| 2984 |
"no_colinfo", "VTableNodeInfo", |
|
| 2985 | 83944x |
function(obj) no_colinfo(col_info(obj)) |
| 2986 |
) |
|
| 2987 | ||
| 2988 |
#' @exportMethod no_colinfo |
|
| 2989 |
#' @rdname no_info |
|
| 2990 |
setMethod( |
|
| 2991 |
"no_colinfo", "InstantiatedColumnInfo", |
|
| 2992 | 101999x |
function(obj) length(obj@subset_exprs) == 0 |
| 2993 |
) ## identical(obj, EmptyColInfo)) |
|
| 2994 | ||
| 2995 |
#' Names of a `TableTree` |
|
| 2996 |
#' |
|
| 2997 |
#' @param x (`TableTree`)\cr the object. |
|
| 2998 |
#' |
|
| 2999 |
#' @details |
|
| 3000 |
#' For `TableTree`s with more than one level of splitting in columns, the names are defined to be the top-level |
|
| 3001 |
#' split values repped out across the columns that they span. |
|
| 3002 |
#' |
|
| 3003 |
#' @return The column names of `x`, as defined in the details above. |
|
| 3004 |
#' |
|
| 3005 |
#' @exportMethod names |
|
| 3006 |
#' @rdname names |
|
| 3007 |
setMethod( |
|
| 3008 |
"names", "VTableNodeInfo", |
|
| 3009 | 182x |
function(x) names(col_info(x)) |
| 3010 |
) |
|
| 3011 | ||
| 3012 |
#' @rdname names |
|
| 3013 |
#' @exportMethod names |
|
| 3014 |
setMethod( |
|
| 3015 |
"names", "InstantiatedColumnInfo", |
|
| 3016 | 272x |
function(x) names(coltree(x)) |
| 3017 |
) |
|
| 3018 | ||
| 3019 |
#' @rdname names |
|
| 3020 |
#' @exportMethod names |
|
| 3021 |
setMethod( |
|
| 3022 |
"names", "LayoutColTree", |
|
| 3023 |
function(x) {
|
|
| 3024 | 380x |
unname(unlist(lapply( |
| 3025 | 380x |
tree_children(x), |
| 3026 | 380x |
function(obj) {
|
| 3027 | 407x |
nm <- obj_name(obj) |
| 3028 | 407x |
rep(nm, n_leaves(obj)) |
| 3029 |
} |
|
| 3030 |
))) |
|
| 3031 |
} |
|
| 3032 |
) |
|
| 3033 | ||
| 3034 |
#' @rdname names |
|
| 3035 |
#' @exportMethod row.names |
|
| 3036 |
setMethod( |
|
| 3037 |
"row.names", "VTableTree", |
|
| 3038 |
function(x) {
|
|
| 3039 | 104x |
unname(sapply(collect_leaves(x, add.labrows = TRUE), |
| 3040 | 104x |
obj_label, |
| 3041 | 104x |
USE.NAMES = FALSE |
| 3042 | 104x |
)) ## XXXX this should probably be obj_name??? |
| 3043 |
} |
|
| 3044 |
) |
|
| 3045 | ||
| 3046 |
#' Convert to a vector |
|
| 3047 |
#' |
|
| 3048 |
#' Convert an `rtables` framework object into a vector, if possible. This is unlikely to be useful in |
|
| 3049 |
#' realistic scenarios. |
|
| 3050 |
#' |
|
| 3051 |
#' @param x (`ANY`)\cr the object to be converted to a vector. |
|
| 3052 |
#' @param mode (`string`)\cr passed on to [as.vector()]. |
|
| 3053 |
#' |
|
| 3054 |
#' @return A vector of the chosen mode (or an error is raised if more than one row was present). |
|
| 3055 |
#' |
|
| 3056 |
#' @note This only works for a table with a single row or a row object. |
|
| 3057 |
#' |
|
| 3058 |
#' @name asvec |
|
| 3059 |
#' @aliases as.vector,VTableTree-method |
|
| 3060 |
#' @exportMethod as.vector |
|
| 3061 |
setMethod("as.vector", "VTableTree", function(x, mode) {
|
|
| 3062 | 12x |
stopifnot(nrow(x) == 1L) |
| 3063 | 12x |
if (nrow(content_table(x)) == 1L) {
|
| 3064 | ! |
tab <- content_table(x) |
| 3065 |
} else {
|
|
| 3066 | 12x |
tab <- x |
| 3067 |
} |
|
| 3068 | 12x |
as.vector(tree_children(tab)[[1]], mode = mode) |
| 3069 |
}) |
|
| 3070 | ||
| 3071 |
#' @inheritParams asvec |
|
| 3072 |
#' |
|
| 3073 |
#' @rdname int_methods |
|
| 3074 |
#' @exportMethod as.vector |
|
| 3075 |
setMethod("as.vector", "TableRow", function(x, mode) as.vector(unlist(row_values(x)), mode = mode))
|
|
| 3076 | ||
| 3077 |
#' @rdname int_methods |
|
| 3078 |
#' @exportMethod as.vector |
|
| 3079 |
setMethod("as.vector", "ElementaryTable", function(x, mode) {
|
|
| 3080 | 2x |
stopifnot(nrow(x) == 1L) |
| 3081 | 2x |
as.vector(tree_children(x)[[1]], mode = mode) |
| 3082 |
}) |
|
| 3083 | ||
| 3084 |
## cuts ---- |
|
| 3085 | ||
| 3086 |
#' @rdname int_methods |
|
| 3087 | 220x |
setGeneric("spl_cuts", function(obj) standardGeneric("spl_cuts"))
|
| 3088 | ||
| 3089 |
#' @rdname int_methods |
|
| 3090 |
setMethod( |
|
| 3091 |
"spl_cuts", "VarStaticCutSplit", |
|
| 3092 | 220x |
function(obj) obj@cuts |
| 3093 |
) |
|
| 3094 | ||
| 3095 |
#' @rdname int_methods |
|
| 3096 | 264x |
setGeneric("spl_cutlabels", function(obj) standardGeneric("spl_cutlabels"))
|
| 3097 | ||
| 3098 |
#' @rdname int_methods |
|
| 3099 |
setMethod( |
|
| 3100 |
"spl_cutlabels", "VarStaticCutSplit", |
|
| 3101 | 264x |
function(obj) obj@cut_labels |
| 3102 |
) |
|
| 3103 | ||
| 3104 |
#' @rdname int_methods |
|
| 3105 | 5x |
setGeneric("spl_cutfun", function(obj) standardGeneric("spl_cutfun"))
|
| 3106 | ||
| 3107 |
#' @rdname int_methods |
|
| 3108 |
setMethod( |
|
| 3109 |
"spl_cutfun", "VarDynCutSplit", |
|
| 3110 | 5x |
function(obj) obj@cut_fun |
| 3111 |
) |
|
| 3112 | ||
| 3113 |
#' @rdname int_methods |
|
| 3114 | 5x |
setGeneric("spl_cutlabelfun", function(obj) standardGeneric("spl_cutlabelfun"))
|
| 3115 | ||
| 3116 |
#' @rdname int_methods |
|
| 3117 |
setMethod( |
|
| 3118 |
"spl_cutlabelfun", "VarDynCutSplit", |
|
| 3119 | 5x |
function(obj) obj@cut_label_fun |
| 3120 |
) |
|
| 3121 | ||
| 3122 |
#' @rdname int_methods |
|
| 3123 | 5x |
setGeneric("spl_is_cmlcuts", function(obj) standardGeneric("spl_is_cmlcuts"))
|
| 3124 | ||
| 3125 |
#' @rdname int_methods |
|
| 3126 |
setMethod( |
|
| 3127 |
"spl_is_cmlcuts", "VarDynCutSplit", |
|
| 3128 | 5x |
function(obj) obj@cumulative_cuts |
| 3129 |
) |
|
| 3130 | ||
| 3131 |
#' @rdname int_methods |
|
| 3132 |
setGeneric( |
|
| 3133 |
"spl_varnames", |
|
| 3134 | 206x |
function(obj) standardGeneric("spl_varnames")
|
| 3135 |
) |
|
| 3136 | ||
| 3137 |
#' @rdname int_methods |
|
| 3138 |
setMethod( |
|
| 3139 |
"spl_varnames", "MultiVarSplit", |
|
| 3140 | 206x |
function(obj) obj@var_names |
| 3141 |
) |
|
| 3142 | ||
| 3143 |
#' @rdname int_methods |
|
| 3144 |
setGeneric( |
|
| 3145 |
"spl_varnames<-", |
|
| 3146 | 2x |
function(object, value) standardGeneric("spl_varnames<-")
|
| 3147 |
) |
|
| 3148 | ||
| 3149 |
#' @rdname int_methods |
|
| 3150 |
setMethod( |
|
| 3151 |
"spl_varnames<-", "MultiVarSplit", |
|
| 3152 |
function(object, value) {
|
|
| 3153 | 2x |
oldvnms <- spl_varnames(object) |
| 3154 | 2x |
oldvlbls <- spl_varlabels(object) |
| 3155 | 2x |
object@var_names <- value |
| 3156 | 2x |
if (identical(oldvnms, oldvlbls)) {
|
| 3157 | 1x |
spl_varlabels(object) <- value |
| 3158 |
} |
|
| 3159 | 2x |
object |
| 3160 |
} |
|
| 3161 |
) |
|
| 3162 | ||
| 3163 |
#' Top left material |
|
| 3164 |
#' |
|
| 3165 |
#' A `TableTree` object can have *top left material* which is a sequence of strings which are printed in the |
|
| 3166 |
#' area of the table between the column header display and the label of the first row. These functions access |
|
| 3167 |
#' and modify that material. |
|
| 3168 |
#' |
|
| 3169 |
#' @inheritParams gen_args |
|
| 3170 |
#' |
|
| 3171 |
#' @return A character vector representing the top-left material of `obj` (or `obj` after modification, in the |
|
| 3172 |
#' case of the setter). |
|
| 3173 |
#' |
|
| 3174 |
#' @export |
|
| 3175 |
#' @rdname top_left |
|
| 3176 | 7220x |
setGeneric("top_left", function(obj) standardGeneric("top_left"))
|
| 3177 | ||
| 3178 |
#' @export |
|
| 3179 |
#' @rdname top_left |
|
| 3180 | 3109x |
setMethod("top_left", "VTableTree", function(obj) top_left(col_info(obj)))
|
| 3181 | ||
| 3182 |
#' @export |
|
| 3183 |
#' @rdname top_left |
|
| 3184 | 3744x |
setMethod("top_left", "InstantiatedColumnInfo", function(obj) obj@top_left)
|
| 3185 | ||
| 3186 |
#' @export |
|
| 3187 |
#' @rdname top_left |
|
| 3188 | 367x |
setMethod("top_left", "PreDataTableLayouts", function(obj) obj@top_left)
|
| 3189 | ||
| 3190 |
#' @export |
|
| 3191 |
#' @rdname top_left |
|
| 3192 | 6159x |
setGeneric("top_left<-", function(obj, value) standardGeneric("top_left<-"))
|
| 3193 | ||
| 3194 |
#' @export |
|
| 3195 |
#' @rdname top_left |
|
| 3196 |
setMethod("top_left<-", "VTableTree", function(obj, value) {
|
|
| 3197 | 3079x |
cinfo <- col_info(obj) |
| 3198 | 3079x |
top_left(cinfo) <- value |
| 3199 | 3079x |
col_info(obj) <- cinfo |
| 3200 | 3079x |
obj |
| 3201 |
}) |
|
| 3202 | ||
| 3203 |
#' @export |
|
| 3204 |
#' @rdname top_left |
|
| 3205 |
setMethod("top_left<-", "InstantiatedColumnInfo", function(obj, value) {
|
|
| 3206 | 3079x |
obj@top_left <- value |
| 3207 | 3079x |
obj |
| 3208 |
}) |
|
| 3209 | ||
| 3210 |
#' @export |
|
| 3211 |
#' @rdname top_left |
|
| 3212 |
setMethod("top_left<-", "PreDataTableLayouts", function(obj, value) {
|
|
| 3213 | 1x |
obj@top_left <- value |
| 3214 | 1x |
obj |
| 3215 |
}) |
|
| 3216 | ||
| 3217 |
vil_collapse <- function(x) {
|
|
| 3218 | 14x |
x <- unlist(x) |
| 3219 | 14x |
x <- x[!is.na(x)] |
| 3220 | 14x |
x <- unique(x) |
| 3221 | 14x |
x[nzchar(x)] |
| 3222 |
} |
|
| 3223 | ||
| 3224 |
#' List variables required by a pre-data table layout |
|
| 3225 |
#' |
|
| 3226 |
#' @param lyt (`PreDataTableLayouts`)\cr the layout (or a component thereof). |
|
| 3227 |
#' |
|
| 3228 |
#' @details |
|
| 3229 |
#' This will walk the layout declaration and return a vector of the names of the unique variables that are used |
|
| 3230 |
#' in any of the following ways: |
|
| 3231 |
#' |
|
| 3232 |
#' * Variable being split on (directly or via cuts) |
|
| 3233 |
#' * Element of a Multi-variable column split |
|
| 3234 |
#' * Content variable |
|
| 3235 |
#' * Value-label variable |
|
| 3236 |
#' |
|
| 3237 |
#' @return A character vector containing the unique variables explicitly used in the layout (see the notes below). |
|
| 3238 |
#' |
|
| 3239 |
#' @note |
|
| 3240 |
#' * This function will not detect dependencies implicit in analysis or summary functions which accept `x` |
|
| 3241 |
#' or `df` and then rely on the existence of particular variables not being split on/analyzed. |
|
| 3242 |
#' * The order these variable names appear within the return vector is undefined and should not be relied upon. |
|
| 3243 |
#' |
|
| 3244 |
#' @examples |
|
| 3245 |
#' lyt <- basic_table() %>% |
|
| 3246 |
#' split_cols_by("ARM") %>%
|
|
| 3247 |
#' split_cols_by("SEX") %>%
|
|
| 3248 |
#' summarize_row_groups(label_fstr = "Overall (N)") %>% |
|
| 3249 |
#' split_rows_by("RACE",
|
|
| 3250 |
#' split_label = "Ethnicity", labels_var = "ethn_lab", |
|
| 3251 |
#' split_fun = drop_split_levels |
|
| 3252 |
#' ) %>% |
|
| 3253 |
#' summarize_row_groups("RACE", label_fstr = "%s (n)") %>%
|
|
| 3254 |
#' analyze("AGE", var_labels = "Age", afun = mean, format = "xx.xx")
|
|
| 3255 |
#' |
|
| 3256 |
#' vars_in_layout(lyt) |
|
| 3257 |
#' |
|
| 3258 |
#' @export |
|
| 3259 |
#' @rdname vil |
|
| 3260 | 15x |
setGeneric("vars_in_layout", function(lyt) standardGeneric("vars_in_layout"))
|
| 3261 | ||
| 3262 |
#' @rdname vil |
|
| 3263 |
setMethod( |
|
| 3264 |
"vars_in_layout", "PreDataTableLayouts", |
|
| 3265 |
function(lyt) {
|
|
| 3266 | 1x |
vil_collapse(c( |
| 3267 | 1x |
vars_in_layout(clayout(lyt)), |
| 3268 | 1x |
vars_in_layout(rlayout(lyt)) |
| 3269 |
)) |
|
| 3270 |
} |
|
| 3271 |
) |
|
| 3272 | ||
| 3273 |
#' @rdname vil |
|
| 3274 |
setMethod( |
|
| 3275 |
"vars_in_layout", "PreDataAxisLayout", |
|
| 3276 |
function(lyt) {
|
|
| 3277 | 2x |
vil_collapse(lapply(lyt, vars_in_layout)) |
| 3278 |
} |
|
| 3279 |
) |
|
| 3280 | ||
| 3281 |
#' @rdname vil |
|
| 3282 |
setMethod( |
|
| 3283 |
"vars_in_layout", "SplitVector", |
|
| 3284 |
function(lyt) {
|
|
| 3285 | 3x |
vil_collapse(lapply(lyt, vars_in_layout)) |
| 3286 |
} |
|
| 3287 |
) |
|
| 3288 | ||
| 3289 |
#' @rdname vil |
|
| 3290 |
setMethod( |
|
| 3291 |
"vars_in_layout", "Split", |
|
| 3292 |
function(lyt) {
|
|
| 3293 | 7x |
vil_collapse(c( |
| 3294 | 7x |
spl_payload(lyt), |
| 3295 |
## for an AllSplit/RootSplit |
|
| 3296 |
## doesn't have to be same as payload |
|
| 3297 | 7x |
content_var(lyt), |
| 3298 | 7x |
spl_label_var(lyt) |
| 3299 |
)) |
|
| 3300 |
} |
|
| 3301 |
) |
|
| 3302 | ||
| 3303 |
#' @rdname vil |
|
| 3304 |
setMethod( |
|
| 3305 |
"vars_in_layout", "CompoundSplit", |
|
| 3306 | 1x |
function(lyt) vil_collapse(lapply(spl_payload(lyt), vars_in_layout)) |
| 3307 |
) |
|
| 3308 | ||
| 3309 |
#' @rdname vil |
|
| 3310 |
setMethod( |
|
| 3311 |
"vars_in_layout", "ManualSplit", |
|
| 3312 | 1x |
function(lyt) character() |
| 3313 |
) |
|
| 3314 | ||
| 3315 |
## Titles and footers ---- |
|
| 3316 | ||
| 3317 |
# ##' Titles and Footers |
|
| 3318 |
# ##' |
|
| 3319 |
# ##' Get or set the titles and footers on an object |
|
| 3320 |
# ##' |
|
| 3321 |
# ##' @inheritParams gen_args |
|
| 3322 |
# ##' |
|
| 3323 |
# ##' @rdname title_footer |
|
| 3324 |
# ##' @export |
|
| 3325 |
#' @rdname formatters_methods |
|
| 3326 |
#' @export |
|
| 3327 |
setMethod( |
|
| 3328 |
"main_title", "VTitleFooter", |
|
| 3329 | 3761x |
function(obj) obj@main_title |
| 3330 |
) |
|
| 3331 | ||
| 3332 |
##' @rdname formatters_methods |
|
| 3333 |
##' @export |
|
| 3334 |
setMethod( |
|
| 3335 |
"main_title<-", "VTitleFooter", |
|
| 3336 |
function(obj, value) {
|
|
| 3337 | 3313x |
stopifnot(length(value) == 1) |
| 3338 | 3313x |
obj@main_title <- value |
| 3339 | 3313x |
obj |
| 3340 |
} |
|
| 3341 |
) |
|
| 3342 | ||
| 3343 |
# Getters for TableRow is here for convenience for binding (no need of setters) |
|
| 3344 |
#' @rdname formatters_methods |
|
| 3345 |
#' @export |
|
| 3346 |
setMethod( |
|
| 3347 |
"main_title", "TableRow", |
|
| 3348 | 6x |
function(obj) "" |
| 3349 |
) |
|
| 3350 | ||
| 3351 |
#' @rdname formatters_methods |
|
| 3352 |
#' @export |
|
| 3353 |
setMethod( |
|
| 3354 |
"subtitles", "VTitleFooter", |
|
| 3355 | 3751x |
function(obj) obj@subtitles |
| 3356 |
) |
|
| 3357 | ||
| 3358 |
#' @rdname formatters_methods |
|
| 3359 |
#' @export |
|
| 3360 |
setMethod( |
|
| 3361 |
"subtitles<-", "VTitleFooter", |
|
| 3362 |
function(obj, value) {
|
|
| 3363 | 3308x |
obj@subtitles <- value |
| 3364 | 3308x |
obj |
| 3365 |
} |
|
| 3366 |
) |
|
| 3367 | ||
| 3368 |
#' @rdname formatters_methods |
|
| 3369 |
#' @export |
|
| 3370 |
setMethod( |
|
| 3371 |
"subtitles", "TableRow", # Only getter: see main_title for TableRow |
|
| 3372 | 6x |
function(obj) character() |
| 3373 |
) |
|
| 3374 | ||
| 3375 |
#' @rdname formatters_methods |
|
| 3376 |
#' @export |
|
| 3377 |
setMethod( |
|
| 3378 |
"main_footer", "VTitleFooter", |
|
| 3379 | 3770x |
function(obj) obj@main_footer |
| 3380 |
) |
|
| 3381 | ||
| 3382 |
#' @rdname formatters_methods |
|
| 3383 |
#' @export |
|
| 3384 |
setMethod( |
|
| 3385 |
"main_footer<-", "VTitleFooter", |
|
| 3386 |
function(obj, value) {
|
|
| 3387 | 3314x |
obj@main_footer <- value |
| 3388 | 3314x |
obj |
| 3389 |
} |
|
| 3390 |
) |
|
| 3391 | ||
| 3392 |
#' @rdname formatters_methods |
|
| 3393 |
#' @export |
|
| 3394 |
setMethod( |
|
| 3395 |
"main_footer", "TableRow", # Only getter: see main_title for TableRow |
|
| 3396 | 6x |
function(obj) character() |
| 3397 |
) |
|
| 3398 | ||
| 3399 |
#' @rdname formatters_methods |
|
| 3400 |
#' @export |
|
| 3401 |
setMethod( |
|
| 3402 |
"prov_footer", "VTitleFooter", |
|
| 3403 | 3751x |
function(obj) obj@provenance_footer |
| 3404 |
) |
|
| 3405 | ||
| 3406 |
#' @rdname formatters_methods |
|
| 3407 |
#' @export |
|
| 3408 |
setMethod( |
|
| 3409 |
"prov_footer<-", "VTitleFooter", |
|
| 3410 |
function(obj, value) {
|
|
| 3411 | 3308x |
obj@provenance_footer <- value |
| 3412 | 3308x |
obj |
| 3413 |
} |
|
| 3414 |
) |
|
| 3415 | ||
| 3416 |
#' @rdname formatters_methods |
|
| 3417 |
#' @export |
|
| 3418 |
setMethod( |
|
| 3419 |
"prov_footer", "TableRow", # Only getter: see main_title for TableRow |
|
| 3420 | 6x |
function(obj) character() |
| 3421 |
) |
|
| 3422 | ||
| 3423 |
make_ref_value <- function(value) {
|
|
| 3424 | 3534x |
if (is(value, "RefFootnote")) {
|
| 3425 | ! |
value <- list(value) |
| 3426 | 3534x |
} else if (!is.list(value) || any(!sapply(value, is, "RefFootnote"))) {
|
| 3427 | 10x |
value <- lapply(value, RefFootnote) |
| 3428 |
} |
|
| 3429 | 3534x |
value |
| 3430 |
} |
|
| 3431 | ||
| 3432 |
#' Referential footnote accessors |
|
| 3433 |
#' |
|
| 3434 |
#' Access and set the referential footnotes aspects of a built table. |
|
| 3435 |
#' |
|
| 3436 |
#' @inheritParams gen_args |
|
| 3437 |
#' |
|
| 3438 |
#' @export |
|
| 3439 |
#' @rdname ref_fnotes |
|
| 3440 | 59462x |
setGeneric("row_footnotes", function(obj) standardGeneric("row_footnotes"))
|
| 3441 | ||
| 3442 |
#' @export |
|
| 3443 |
#' @rdname int_methods |
|
| 3444 |
setMethod( |
|
| 3445 |
"row_footnotes", "TableRow", |
|
| 3446 | 57173x |
function(obj) obj@row_footnotes |
| 3447 |
) |
|
| 3448 | ||
| 3449 |
#' @export |
|
| 3450 |
#' @rdname int_methods |
|
| 3451 |
setMethod( |
|
| 3452 |
"row_footnotes", "RowsVerticalSection", |
|
| 3453 | 1813x |
function(obj) attr(obj, "row_footnotes", exact = TRUE) %||% list() |
| 3454 |
) |
|
| 3455 | ||
| 3456 |
#' @export |
|
| 3457 |
#' @rdname ref_fnotes |
|
| 3458 | 65x |
setGeneric("row_footnotes<-", function(obj, value) standardGeneric("row_footnotes<-"))
|
| 3459 | ||
| 3460 |
#' @export |
|
| 3461 |
#' @rdname int_methods |
|
| 3462 |
setMethod( |
|
| 3463 |
"row_footnotes<-", "TableRow", |
|
| 3464 |
function(obj, value) {
|
|
| 3465 | 65x |
obj@row_footnotes <- make_ref_value(value) |
| 3466 | 65x |
obj |
| 3467 |
} |
|
| 3468 |
) |
|
| 3469 | ||
| 3470 |
#' @export |
|
| 3471 |
#' @rdname int_methods |
|
| 3472 |
setMethod( |
|
| 3473 |
"row_footnotes", "VTableTree", |
|
| 3474 |
function(obj) {
|
|
| 3475 | 476x |
rws <- collect_leaves(obj, TRUE, TRUE) |
| 3476 | 476x |
cells <- lapply(rws, row_footnotes) |
| 3477 | 476x |
cells |
| 3478 |
} |
|
| 3479 |
) |
|
| 3480 | ||
| 3481 |
#' @export |
|
| 3482 |
#' @rdname ref_fnotes |
|
| 3483 | 222351x |
setGeneric("cell_footnotes", function(obj) standardGeneric("cell_footnotes"))
|
| 3484 | ||
| 3485 |
#' @export |
|
| 3486 |
#' @rdname int_methods |
|
| 3487 |
setMethod( |
|
| 3488 |
"cell_footnotes", "CellValue", |
|
| 3489 | 176655x |
function(obj) attr(obj, "footnotes", exact = TRUE) %||% list() |
| 3490 |
) |
|
| 3491 | ||
| 3492 |
#' @export |
|
| 3493 |
#' @rdname int_methods |
|
| 3494 |
setMethod( |
|
| 3495 |
"cell_footnotes", "TableRow", |
|
| 3496 |
function(obj) {
|
|
| 3497 | 40465x |
ret <- lapply(row_cells(obj), cell_footnotes) |
| 3498 | 40465x |
if (length(ret) != ncol(obj)) {
|
| 3499 | 151x |
ret <- rep(ret, row_cspans(obj)) |
| 3500 |
} |
|
| 3501 | 40465x |
ret |
| 3502 |
} |
|
| 3503 |
) |
|
| 3504 | ||
| 3505 |
#' @export |
|
| 3506 |
#' @rdname int_methods |
|
| 3507 |
setMethod( |
|
| 3508 |
"cell_footnotes", "LabelRow", |
|
| 3509 |
function(obj) {
|
|
| 3510 | 4755x |
rep(list(list()), ncol(obj)) |
| 3511 |
} |
|
| 3512 |
) |
|
| 3513 | ||
| 3514 |
#' @export |
|
| 3515 |
#' @rdname int_methods |
|
| 3516 |
setMethod( |
|
| 3517 |
"cell_footnotes", "VTableTree", |
|
| 3518 |
function(obj) {
|
|
| 3519 | 476x |
rws <- collect_leaves(obj, TRUE, TRUE) |
| 3520 | 476x |
cells <- lapply(rws, cell_footnotes) |
| 3521 | 476x |
do.call(rbind, cells) |
| 3522 |
} |
|
| 3523 |
) |
|
| 3524 | ||
| 3525 |
#' @export |
|
| 3526 |
#' @rdname ref_fnotes |
|
| 3527 | 625x |
setGeneric("cell_footnotes<-", function(obj, value) standardGeneric("cell_footnotes<-"))
|
| 3528 | ||
| 3529 |
#' @export |
|
| 3530 |
#' @rdname int_methods |
|
| 3531 |
setMethod( |
|
| 3532 |
"cell_footnotes<-", "CellValue", |
|
| 3533 |
function(obj, value) {
|
|
| 3534 | 565x |
attr(obj, "footnotes") <- make_ref_value(value) |
| 3535 | 565x |
obj |
| 3536 |
} |
|
| 3537 |
) |
|
| 3538 | ||
| 3539 |
.cfn_set_helper <- function(obj, value) {
|
|
| 3540 | 60x |
if (length(value) != ncol(obj)) {
|
| 3541 | ! |
stop("Did not get the right number of footnote ref values for cell_footnotes<- on a full row.")
|
| 3542 |
} |
|
| 3543 | ||
| 3544 | 60x |
row_cells(obj) <- mapply( |
| 3545 | 60x |
function(cell, fns) {
|
| 3546 | 191x |
if (is.list(fns)) {
|
| 3547 | 185x |
cell_footnotes(cell) <- lapply(fns, RefFootnote) |
| 3548 |
} else {
|
|
| 3549 | 6x |
cell_footnotes(cell) <- list(RefFootnote(fns)) |
| 3550 |
} |
|
| 3551 | 191x |
cell |
| 3552 |
}, |
|
| 3553 | 60x |
cell = row_cells(obj), |
| 3554 | 60x |
fns = value, SIMPLIFY = FALSE |
| 3555 |
) |
|
| 3556 | 60x |
obj |
| 3557 |
} |
|
| 3558 | ||
| 3559 |
#' @export |
|
| 3560 |
#' @rdname int_methods |
|
| 3561 |
setMethod("cell_footnotes<-", "DataRow",
|
|
| 3562 |
definition = .cfn_set_helper |
|
| 3563 |
) |
|
| 3564 | ||
| 3565 |
#' @export |
|
| 3566 |
#' @rdname int_methods |
|
| 3567 |
setMethod("cell_footnotes<-", "ContentRow",
|
|
| 3568 |
definition = .cfn_set_helper |
|
| 3569 |
) |
|
| 3570 | ||
| 3571 |
# Deprecated methods ---- |
|
| 3572 | ||
| 3573 |
#' @export |
|
| 3574 |
#' @rdname ref_fnotes |
|
| 3575 | ! |
setGeneric("col_fnotes_here", function(obj) standardGeneric("col_fnotes_here"))
|
| 3576 | ||
| 3577 |
#' @export |
|
| 3578 |
#' @rdname ref_fnotes |
|
| 3579 |
setMethod("col_fnotes_here", "ANY", function(obj) {
|
|
| 3580 | ! |
lifecycle::deprecate_warn( |
| 3581 | ! |
when = "0.6.6", |
| 3582 | ! |
what = "col_fnotes_here()", |
| 3583 | ! |
with = "col_footnotes()" |
| 3584 |
) |
|
| 3585 | ! |
col_footnotes(obj) |
| 3586 |
}) |
|
| 3587 | ||
| 3588 |
#' @export |
|
| 3589 |
#' @rdname ref_fnotes |
|
| 3590 | ! |
setGeneric("col_fnotes_here<-", function(obj, value) standardGeneric("col_fnotes_here<-"))
|
| 3591 | ||
| 3592 |
#' @export |
|
| 3593 |
#' @rdname int_methods |
|
| 3594 |
setMethod("col_fnotes_here<-", "ANY", function(obj, value) {
|
|
| 3595 | ! |
lifecycle::deprecate_warn( |
| 3596 | ! |
when = "0.6.6", |
| 3597 | ! |
what = I("col_fnotes_here()<-"),
|
| 3598 | ! |
with = I("col_footnotes()<-")
|
| 3599 |
) |
|
| 3600 | ! |
col_footnotes(obj) <- value |
| 3601 |
}) |
|
| 3602 | ||
| 3603 |
#' @export |
|
| 3604 |
#' @rdname ref_fnotes |
|
| 3605 | 18160x |
setGeneric("col_footnotes", function(obj) standardGeneric("col_footnotes"))
|
| 3606 | ||
| 3607 |
#' @export |
|
| 3608 |
#' @rdname int_methods |
|
| 3609 | 1540x |
setMethod("col_footnotes", "LayoutColTree", function(obj) obj@col_footnotes)
|
| 3610 | ||
| 3611 |
#' @export |
|
| 3612 |
#' @rdname int_methods |
|
| 3613 | 16145x |
setMethod("col_footnotes", "LayoutColLeaf", function(obj) obj@col_footnotes)
|
| 3614 | ||
| 3615 |
#' @export |
|
| 3616 |
#' @rdname ref_fnotes |
|
| 3617 | 2227x |
setGeneric("col_footnotes<-", function(obj, value) standardGeneric("col_footnotes<-"))
|
| 3618 | ||
| 3619 |
#' @export |
|
| 3620 |
#' @rdname int_methods |
|
| 3621 |
setMethod("col_footnotes<-", "LayoutColTree", function(obj, value) {
|
|
| 3622 | 837x |
obj@col_footnotes <- make_ref_value(value) |
| 3623 | 837x |
obj |
| 3624 |
}) |
|
| 3625 | ||
| 3626 |
#' @export |
|
| 3627 |
#' @rdname int_methods |
|
| 3628 |
setMethod("col_footnotes<-", "LayoutColLeaf", function(obj, value) {
|
|
| 3629 | 1390x |
obj@col_footnotes <- make_ref_value(value) |
| 3630 | 1390x |
obj |
| 3631 |
}) |
|
| 3632 | ||
| 3633 |
#' @export |
|
| 3634 |
#' @rdname int_methods |
|
| 3635 |
setMethod( |
|
| 3636 |
"col_footnotes", "VTableTree", |
|
| 3637 |
function(obj) {
|
|
| 3638 | 475x |
ctree <- coltree(obj) |
| 3639 | 475x |
cols <- tree_children(ctree) |
| 3640 | 475x |
while (all(sapply(cols, is, "LayoutColTree"))) {
|
| 3641 | 150x |
cols <- lapply(cols, tree_children) |
| 3642 | 150x |
cols <- unlist(cols, recursive = FALSE) |
| 3643 |
} |
|
| 3644 | 475x |
all_col_fnotes <- lapply(cols, col_footnotes) |
| 3645 | 475x |
if (is.null(unlist(all_col_fnotes))) {
|
| 3646 | 470x |
return(NULL) |
| 3647 |
} |
|
| 3648 | ||
| 3649 | 5x |
all_col_fnotes |
| 3650 |
} |
|
| 3651 |
) |
|
| 3652 | ||
| 3653 |
#' @export |
|
| 3654 |
#' @rdname ref_fnotes |
|
| 3655 | 594x |
setGeneric("ref_index", function(obj) standardGeneric("ref_index"))
|
| 3656 | ||
| 3657 |
#' @export |
|
| 3658 |
#' @rdname int_methods |
|
| 3659 |
setMethod( |
|
| 3660 |
"ref_index", "RefFootnote", |
|
| 3661 | 594x |
function(obj) obj@index |
| 3662 |
) |
|
| 3663 | ||
| 3664 |
#' @export |
|
| 3665 |
#' @rdname ref_fnotes |
|
| 3666 | 71x |
setGeneric("ref_index<-", function(obj, value) standardGeneric("ref_index<-"))
|
| 3667 | ||
| 3668 |
#' @export |
|
| 3669 |
#' @rdname int_methods |
|
| 3670 |
setMethod( |
|
| 3671 |
"ref_index<-", "RefFootnote", |
|
| 3672 |
function(obj, value) {
|
|
| 3673 | 71x |
obj@index <- value |
| 3674 | 71x |
obj |
| 3675 |
} |
|
| 3676 |
) |
|
| 3677 | ||
| 3678 |
#' @export |
|
| 3679 |
#' @rdname ref_fnotes |
|
| 3680 | 523x |
setGeneric("ref_symbol", function(obj) standardGeneric("ref_symbol"))
|
| 3681 | ||
| 3682 |
#' @export |
|
| 3683 |
#' @rdname int_methods |
|
| 3684 |
setMethod( |
|
| 3685 |
"ref_symbol", "RefFootnote", |
|
| 3686 | 523x |
function(obj) obj@symbol |
| 3687 |
) |
|
| 3688 | ||
| 3689 |
#' @export |
|
| 3690 |
#' @rdname ref_fnotes |
|
| 3691 | ! |
setGeneric("ref_symbol<-", function(obj, value) standardGeneric("ref_symbol<-"))
|
| 3692 | ||
| 3693 |
#' @export |
|
| 3694 |
#' @rdname int_methods |
|
| 3695 |
setMethod( |
|
| 3696 |
"ref_symbol<-", "RefFootnote", |
|
| 3697 |
function(obj, value) {
|
|
| 3698 | ! |
obj@symbol <- value |
| 3699 | ! |
obj |
| 3700 |
} |
|
| 3701 |
) |
|
| 3702 | ||
| 3703 |
#' @export |
|
| 3704 |
#' @rdname ref_fnotes |
|
| 3705 | 515x |
setGeneric("ref_msg", function(obj) standardGeneric("ref_msg"))
|
| 3706 | ||
| 3707 |
#' @export |
|
| 3708 |
#' @rdname int_methods |
|
| 3709 |
setMethod( |
|
| 3710 |
"ref_msg", "RefFootnote", |
|
| 3711 | 515x |
function(obj) obj@value |
| 3712 |
) |
|
| 3713 | ||
| 3714 | 20x |
setGeneric(".fnote_set_inner<-", function(ttrp, colpath, value) standardGeneric(".fnote_set_inner<-"))
|
| 3715 | ||
| 3716 |
setMethod( |
|
| 3717 |
".fnote_set_inner<-", c("TableRow", "NULL"),
|
|
| 3718 |
function(ttrp, colpath, value) {
|
|
| 3719 | 7x |
row_footnotes(ttrp) <- value |
| 3720 | 7x |
ttrp |
| 3721 |
} |
|
| 3722 |
) |
|
| 3723 | ||
| 3724 |
setMethod( |
|
| 3725 |
".fnote_set_inner<-", c("TableRow", "character"),
|
|
| 3726 |
function(ttrp, colpath, value) {
|
|
| 3727 | 6x |
ind <- .path_to_pos(path = colpath, tt = ttrp, cols = TRUE) |
| 3728 | 6x |
cfns <- cell_footnotes(ttrp) |
| 3729 | 6x |
cfns[[ind]] <- value |
| 3730 | 6x |
cell_footnotes(ttrp) <- cfns |
| 3731 | 6x |
ttrp |
| 3732 |
} |
|
| 3733 |
) |
|
| 3734 | ||
| 3735 |
setMethod( |
|
| 3736 |
".fnote_set_inner<-", c("InstantiatedColumnInfo", "character"),
|
|
| 3737 |
function(ttrp, colpath, value) {
|
|
| 3738 | 1x |
ctree <- col_fnotes_at_path(coltree(ttrp), colpath, fnotes = value) |
| 3739 | 1x |
coltree(ttrp) <- ctree |
| 3740 | 1x |
ttrp |
| 3741 |
} |
|
| 3742 |
) |
|
| 3743 | ||
| 3744 |
setMethod( |
|
| 3745 |
".fnote_set_inner<-", c("VTableTree", "ANY"),
|
|
| 3746 |
function(ttrp, colpath, value) {
|
|
| 3747 | 6x |
if (labelrow_visible(ttrp) && !is.null(value)) {
|
| 3748 | 2x |
lblrw <- tt_labelrow(ttrp) |
| 3749 | 2x |
row_footnotes(lblrw) <- value |
| 3750 | 2x |
tt_labelrow(ttrp) <- lblrw |
| 3751 | 4x |
} else if (NROW(content_table(ttrp)) == 1L) {
|
| 3752 | 4x |
ctbl <- content_table(ttrp) |
| 3753 | 4x |
pth <- make_row_df(ctbl)$path[[1]] |
| 3754 | 4x |
fnotes_at_path(ctbl, pth, colpath) <- value |
| 3755 | 4x |
content_table(ttrp) <- ctbl |
| 3756 |
} else {
|
|
| 3757 |
stop("an error occurred. this shouldn't happen. please contact the maintainer") # nocov
|
|
| 3758 |
} |
|
| 3759 | 6x |
ttrp |
| 3760 |
} |
|
| 3761 |
) |
|
| 3762 | ||
| 3763 |
#' @param rowpath (`character` or `NULL`)\cr path within row structure. `NULL` indicates the footnote should |
|
| 3764 |
#' go on the column rather than cell. |
|
| 3765 |
#' @param colpath (`character` or `NULL`)\cr path within column structure. `NULL` indicates footnote should go |
|
| 3766 |
#' on the row rather than cell. |
|
| 3767 |
#' @param reset_idx (`flag`)\cr whether the numbering for referential footnotes should be immediately |
|
| 3768 |
#' recalculated. Defaults to `TRUE`. |
|
| 3769 |
#' |
|
| 3770 |
#' @examples |
|
| 3771 |
#' # How to add referencial footnotes after having created a table |
|
| 3772 |
#' lyt <- basic_table() %>% |
|
| 3773 |
#' split_rows_by("SEX", page_by = TRUE) %>%
|
|
| 3774 |
#' analyze("AGE")
|
|
| 3775 |
#' |
|
| 3776 |
#' tbl <- build_table(lyt, DM) |
|
| 3777 |
#' tbl <- trim_rows(tbl) |
|
| 3778 |
#' # Check the row and col structure to add precise references |
|
| 3779 |
#' # row_paths(tbl) |
|
| 3780 |
#' # col_paths(t) |
|
| 3781 |
#' # row_paths_summary(tbl) |
|
| 3782 |
#' # col_paths_summary(tbl) |
|
| 3783 |
#' |
|
| 3784 |
#' # Add the citation numbers on the table and relative references in the footnotes |
|
| 3785 |
#' fnotes_at_path(tbl, rowpath = c("SEX", "F", "AGE", "Mean")) <- "Famous paper 1"
|
|
| 3786 |
#' fnotes_at_path(tbl, rowpath = c("SEX", "UNDIFFERENTIATED")) <- "Unfamous paper 2"
|
|
| 3787 |
#' # tbl |
|
| 3788 |
#' |
|
| 3789 |
#' @seealso [row_paths()], [col_paths()], [row_paths_summary()], [col_paths_summary()] |
|
| 3790 |
#' |
|
| 3791 |
#' @export |
|
| 3792 |
#' @rdname ref_fnotes |
|
| 3793 |
setGeneric("fnotes_at_path<-", function(obj,
|
|
| 3794 |
rowpath = NULL, |
|
| 3795 |
colpath = NULL, |
|
| 3796 |
reset_idx = TRUE, |
|
| 3797 |
value) {
|
|
| 3798 | 20x |
standardGeneric("fnotes_at_path<-")
|
| 3799 |
}) |
|
| 3800 | ||
| 3801 |
## non-null rowpath, null or non-null colpath |
|
| 3802 |
#' @inheritParams fnotes_at_path<- |
|
| 3803 |
#' |
|
| 3804 |
#' @export |
|
| 3805 |
#' @rdname int_methods |
|
| 3806 |
setMethod( |
|
| 3807 |
"fnotes_at_path<-", c("VTableTree", "character"),
|
|
| 3808 |
function(obj, |
|
| 3809 |
rowpath = NULL, |
|
| 3810 |
colpath = NULL, |
|
| 3811 |
reset_idx = TRUE, |
|
| 3812 |
value) {
|
|
| 3813 | 19x |
rw <- tt_at_path(obj, rowpath) |
| 3814 | 19x |
.fnote_set_inner(rw, colpath) <- value |
| 3815 | 19x |
tt_at_path(obj, rowpath) <- rw |
| 3816 | 19x |
if (reset_idx) {
|
| 3817 | 19x |
obj <- update_ref_indexing(obj) |
| 3818 |
} |
|
| 3819 | 19x |
obj |
| 3820 |
} |
|
| 3821 |
) |
|
| 3822 | ||
| 3823 |
#' @export |
|
| 3824 |
#' @rdname int_methods |
|
| 3825 |
setMethod( |
|
| 3826 |
"fnotes_at_path<-", c("VTableTree", "NULL"),
|
|
| 3827 |
function(obj, rowpath = NULL, colpath = NULL, reset_idx = TRUE, value) {
|
|
| 3828 | 1x |
cinfo <- col_info(obj) |
| 3829 | 1x |
.fnote_set_inner(cinfo, colpath) <- value |
| 3830 | 1x |
col_info(obj) <- cinfo |
| 3831 | 1x |
if (reset_idx) {
|
| 3832 | 1x |
obj <- update_ref_indexing(obj) |
| 3833 |
} |
|
| 3834 | 1x |
obj |
| 3835 |
} |
|
| 3836 |
) |
|
| 3837 | ||
| 3838 |
#' @export |
|
| 3839 |
#' @rdname int_methods |
|
| 3840 | 3231x |
setGeneric("has_force_pag", function(obj) standardGeneric("has_force_pag"))
|
| 3841 | ||
| 3842 |
#' @exportMethod has_force_pag |
|
| 3843 |
#' @rdname int_methods |
|
| 3844 | 354x |
setMethod("has_force_pag", "TableTree", function(obj) !is.na(ptitle_prefix(obj)))
|
| 3845 | ||
| 3846 |
#' @exportMethod has_force_pag |
|
| 3847 |
#' @rdname int_methods |
|
| 3848 | 1807x |
setMethod("has_force_pag", "Split", function(obj) !is.na(ptitle_prefix(obj)))
|
| 3849 | ||
| 3850 |
#' @exportMethod has_force_pag |
|
| 3851 |
#' @rdname int_methods |
|
| 3852 | 1018x |
setMethod("has_force_pag", "VTableNodeInfo", function(obj) FALSE)
|
| 3853 | ||
| 3854 | 2685x |
setGeneric("ptitle_prefix", function(obj) standardGeneric("ptitle_prefix"))
|
| 3855 | ||
| 3856 | 362x |
setMethod("ptitle_prefix", "TableTree", function(obj) obj@page_title_prefix)
|
| 3857 | ||
| 3858 | 2271x |
setMethod("ptitle_prefix", "Split", function(obj) obj@page_title_prefix)
|
| 3859 | ||
| 3860 | ! |
setMethod("ptitle_prefix", "ANY", function(obj) NULL)
|
| 3861 | ||
| 3862 | 389x |
setMethod("page_titles", "VTableTree", function(obj) obj@page_titles)
|
| 3863 | ||
| 3864 |
setMethod("page_titles<-", "VTableTree", function(obj, value) {
|
|
| 3865 | 19x |
obj@page_titles <- value |
| 3866 | 19x |
obj |
| 3867 |
}) |
|
| 3868 | ||
| 3869 |
## Horizontal separator -------------------------------------------------------- |
|
| 3870 | ||
| 3871 |
#' Access or recursively set header-body separator for tables |
|
| 3872 |
#' |
|
| 3873 |
#' @inheritParams gen_args |
|
| 3874 |
#' @param value (`string`)\cr string to use as new header/body separator. |
|
| 3875 |
#' |
|
| 3876 |
#' @return |
|
| 3877 |
#' * `horizontal_sep` returns the string acting as the header separator. |
|
| 3878 |
#' * `horizontal_sep<-` returns `obj`, with the new header separator applied recursively to it and all its |
|
| 3879 |
#' subtables. |
|
| 3880 |
#' |
|
| 3881 |
#' @export |
|
| 3882 | 397x |
setGeneric("horizontal_sep", function(obj) standardGeneric("horizontal_sep"))
|
| 3883 | ||
| 3884 |
#' @rdname horizontal_sep |
|
| 3885 |
#' @export |
|
| 3886 |
setMethod( |
|
| 3887 |
"horizontal_sep", "VTableTree", |
|
| 3888 | 397x |
function(obj) obj@horizontal_sep |
| 3889 |
) |
|
| 3890 | ||
| 3891 |
#' @rdname horizontal_sep |
|
| 3892 |
#' @export |
|
| 3893 | 27818x |
setGeneric("horizontal_sep<-", function(obj, value) standardGeneric("horizontal_sep<-"))
|
| 3894 | ||
| 3895 |
#' @rdname horizontal_sep |
|
| 3896 |
#' @export |
|
| 3897 |
setMethod( |
|
| 3898 |
"horizontal_sep<-", "VTableTree", |
|
| 3899 |
function(obj, value) {
|
|
| 3900 | 15308x |
cont <- content_table(obj) |
| 3901 | 15308x |
if (NROW(cont) > 0) {
|
| 3902 | 2066x |
horizontal_sep(cont) <- value |
| 3903 | 2066x |
content_table(obj) <- cont |
| 3904 |
} |
|
| 3905 | ||
| 3906 | 15308x |
kids <- lapply(tree_children(obj), |
| 3907 | 15308x |
`horizontal_sep<-`, |
| 3908 | 15308x |
value = value |
| 3909 |
) |
|
| 3910 | ||
| 3911 | 15308x |
tree_children(obj) <- kids |
| 3912 | 15308x |
obj@horizontal_sep <- value |
| 3913 | 15308x |
obj |
| 3914 |
} |
|
| 3915 |
) |
|
| 3916 | ||
| 3917 |
#' @rdname horizontal_sep |
|
| 3918 |
#' @export |
|
| 3919 |
setMethod( |
|
| 3920 |
"horizontal_sep<-", "TableRow", |
|
| 3921 | 12510x |
function(obj, value) obj |
| 3922 |
) |
|
| 3923 | ||
| 3924 |
## Section dividers ------------------------------------------------------------ |
|
| 3925 | ||
| 3926 |
# Used for splits |
|
| 3927 | 1910x |
setGeneric("spl_section_div", function(obj) standardGeneric("spl_section_div"))
|
| 3928 | ||
| 3929 |
setMethod( |
|
| 3930 |
"spl_section_div", "Split", |
|
| 3931 | 1910x |
function(obj) obj@child_section_div |
| 3932 |
) |
|
| 3933 | ||
| 3934 | ! |
setGeneric("spl_section_div<-", function(obj, value) standardGeneric("spl_section_div<-"))
|
| 3935 | ||
| 3936 |
setMethod( |
|
| 3937 |
"spl_section_div<-", "Split", |
|
| 3938 |
function(obj, value) {
|
|
| 3939 | ! |
obj@child_section_div <- value |
| 3940 | ! |
obj |
| 3941 |
} |
|
| 3942 |
) |
|
| 3943 | ||
| 3944 |
# Used for table object parts |
|
| 3945 | 46872x |
setGeneric("trailing_section_div", function(obj) standardGeneric("trailing_section_div"))
|
| 3946 | 13565x |
setMethod("trailing_section_div", "VTableTree", function(obj) obj@trailing_section_div)
|
| 3947 | 8391x |
setMethod("trailing_section_div", "LabelRow", function(obj) obj@trailing_section_div)
|
| 3948 | 24916x |
setMethod("trailing_section_div", "TableRow", function(obj) obj@trailing_section_div)
|
| 3949 | ||
| 3950 | 250x |
setGeneric("trailing_section_div<-", function(obj, value) standardGeneric("trailing_section_div<-"))
|
| 3951 |
setMethod("trailing_section_div<-", "VTableTree", function(obj, value) {
|
|
| 3952 | 183x |
obj@trailing_section_div <- value |
| 3953 | 183x |
obj |
| 3954 |
}) |
|
| 3955 |
setMethod("trailing_section_div<-", "LabelRow", function(obj, value) {
|
|
| 3956 | 19x |
obj@trailing_section_div <- value |
| 3957 | 19x |
obj |
| 3958 |
}) |
|
| 3959 |
setMethod("trailing_section_div<-", "TableRow", function(obj, value) {
|
|
| 3960 | 48x |
obj@trailing_section_div <- value |
| 3961 | 48x |
obj |
| 3962 |
}) |
|
| 3963 | ||
| 3964 |
#' Section dividers accessor and setter |
|
| 3965 |
#' |
|
| 3966 |
#' `section_div` can be used to set or get the section divider for a table object |
|
| 3967 |
#' produced by [build_table()]. When assigned in post-processing (`section_div<-`) |
|
| 3968 |
#' the table can have a section divider after every row, each assigned independently. |
|
| 3969 |
#' If assigning during layout creation, only [split_rows_by()] (and its related row-wise |
|
| 3970 |
#' splits) and [analyze()] have a `section_div` parameter that will produce separators |
|
| 3971 |
#' between split sections and data subgroups, respectively. These two approaches |
|
| 3972 |
#' generally should not be mixed (see Details). |
|
| 3973 |
#' |
|
| 3974 |
#' @inheritParams gen_args |
|
| 3975 |
#' @param obj (`VTableTree`)\cr table object. This can be of any class that inherits from `VTableTree` |
|
| 3976 |
#' or `TableRow`/`LabelRow`. |
|
| 3977 |
#' @param only_sep_sections (`flag`)\cr defaults to `FALSE` for `section_div<-`. Allows |
|
| 3978 |
#' you to set the section divider only for sections that are splits or analyses if the number of |
|
| 3979 |
#' values is less than the number of rows in the table. If `TRUE`, the section divider will |
|
| 3980 |
#' be set for all rows of the table. |
|
| 3981 |
#' @param value (`character`)\cr vector of strings to use as section dividers |
|
| 3982 |
#' (a single string for `section_div_at_path<-`). Each string's character(s) |
|
| 3983 |
#' are repeated to the full width of the printed table. Non-`NA` strings |
|
| 3984 |
#' will result in a trailing separator at the associated location (see Details); |
|
| 3985 |
#' values of `NA_character_` result in no visible divider when the table is printed/exported. |
|
| 3986 |
#' For `section_div<-`, `value`'s length should the number of rows in `obj`, |
|
| 3987 |
#' when `only_sep_sections` is `FALSE` and should be less than or equal to |
|
| 3988 |
#' the maximum number of nested split/analyze steps anywhere in the |
|
| 3989 |
#' layout corresponding to the table when `only_sep_sections` is `TRUE`. |
|
| 3990 |
#' See the Details section below for more information. |
|
| 3991 |
#' |
|
| 3992 |
#' @return The section divider string. Each line that does not have a trailing separator |
|
| 3993 |
#' will have `NA_character_` as section divider. |
|
| 3994 |
#' |
|
| 3995 |
#' @seealso [basic_table()] parameter `header_section_div` and `top_level_section_div` for global |
|
| 3996 |
#' section dividers. |
|
| 3997 |
#' |
|
| 3998 |
#' @details |
|
| 3999 |
#' |
|
| 4000 |
#' Section dividers provide visual breaks between structural elements |
|
| 4001 |
#' of a table in row space. They are repeated to fill a full line of |
|
| 4002 |
#' the table and printed after the element (row, subtable) they are |
|
| 4003 |
#' associated with. Use a value of `" "` to display a blank line |
|
| 4004 |
#' section divider in the table. A section divider of |
|
| 4005 |
#' `NA_character_` indicates no visible divider (i.e., no line at all) |
|
| 4006 |
#' should be printed for that row or section when rendering the table. |
|
| 4007 |
#' |
|
| 4008 |
#' When multiple section dividers would appear consecutively with no |
|
| 4009 |
#' rows between them (e.g., a subtable and its last row both having a |
|
| 4010 |
#' section divider set), only the *least specific* section divider |
|
| 4011 |
#' (the subtable divider in this example) will be displayed when |
|
| 4012 |
#' rendering the table. This is to avoid multiple non-informative |
|
| 4013 |
#' lines of consecutive dividers when there is nested splitting in |
|
| 4014 |
#' the row structure of a table. |
|
| 4015 |
#' |
|
| 4016 |
#' `section_div_at_path<-` accepts a single path (which can include |
|
| 4017 |
#' the `'*'` wildcard), and a single string in `value` and sets the |
|
| 4018 |
#' section divider on the element(s) of `obj` that the path resolve |
|
| 4019 |
#' to. |
|
| 4020 |
#' |
|
| 4021 |
#' For `section_div<-` `value` should be a character vector. When you |
|
| 4022 |
#' want to only affect sections or splits, please use |
|
| 4023 |
#' `only_sep_sections` or provide a shorter vector than the number |
|
| 4024 |
#' of rows. Ideally, the length of the vector should be less than |
|
| 4025 |
#' the number of splits with, eventually, the leaf-level, |
|
| 4026 |
#' i.e. `DataRow` where analyze results are. Note that if only one |
|
| 4027 |
#' value is inserted, only the first split will be affected. If |
|
| 4028 |
#' `only_sep_sections = TRUE`, which is the default for |
|
| 4029 |
#' `section_div()` produced from the table construction, the section |
|
| 4030 |
#' divider will be set for all the splits and eventually analyses, |
|
| 4031 |
#' but not for the header or each row of the table. This can be set |
|
| 4032 |
#' with `header_section_div` in [basic_table()] or, eventually, with |
|
| 4033 |
#' `hsep` in [build_table()]. If `only_sep_sections` is `FALSE`, |
|
| 4034 |
#' "section" dividers will be set for each row in the table |
|
| 4035 |
#' *including content and label rows*. |
|
| 4036 |
#' |
|
| 4037 |
#' In `section_div<-`, when `only_sep_sections` is `FALSE` |
|
| 4038 |
#' *all higher order section divs are removed, even when new value |
|
| 4039 |
#' for a row that they would apply to is `NA`*. |
|
| 4040 |
#' |
|
| 4041 |
#' @note Section dividers which would appear after the last row of the |
|
| 4042 |
#' table (ie those on the last row or last elementary subtable in |
|
| 4043 |
#' the table) are never printed when rendering the table. |
|
| 4044 |
#' |
|
| 4045 |
#' @note when called on an individual row object, `section_div` and |
|
| 4046 |
#' `section_div<-` get and set the trialing divider for that row. |
|
| 4047 |
#' In generally this is to be avoided; when manually constructing |
|
| 4048 |
#' row objects, the `trailing_section_div` argument can set the |
|
| 4049 |
#' trailing divider directly during creation. |
|
| 4050 |
#' |
|
| 4051 |
#' @examples |
|
| 4052 |
#' # Data |
|
| 4053 |
#' df <- data.frame( |
|
| 4054 |
#' cat = c( |
|
| 4055 |
#' "really long thing its so ", "long" |
|
| 4056 |
#' ), |
|
| 4057 |
#' value = c(6, 3, 10, 1) |
|
| 4058 |
#' ) |
|
| 4059 |
#' fast_afun <- function(x) list("m" = rcell(mean(x), format = "xx."), "m/2" = max(x) / 2)
|
|
| 4060 |
#' |
|
| 4061 |
#' tbl <- basic_table() %>% |
|
| 4062 |
#' split_rows_by("cat", section_div = "~") %>%
|
|
| 4063 |
#' analyze("value", afun = fast_afun, section_div = " ") %>%
|
|
| 4064 |
#' build_table(df) |
|
| 4065 |
#' |
|
| 4066 |
#' # Getter |
|
| 4067 |
#' section_div(tbl) |
|
| 4068 |
#' |
|
| 4069 |
#' # Setter |
|
| 4070 |
#' section_div(tbl) <- letters[seq_len(nrow(tbl))] |
|
| 4071 |
#' tbl |
|
| 4072 |
#' |
|
| 4073 |
#' # last letter can appear if there is another table |
|
| 4074 |
#' rbind(tbl, tbl) |
|
| 4075 |
#' |
|
| 4076 |
#' # header_section_div |
|
| 4077 |
#' header_section_div(tbl) <- "+" |
|
| 4078 |
#' tbl |
|
| 4079 |
#' |
|
| 4080 |
#' @docType methods |
|
| 4081 |
#' @rdname section_div |
|
| 4082 |
#' @export |
|
| 4083 | 380x |
setGeneric("section_div", function(obj) standardGeneric("section_div"))
|
| 4084 | ||
| 4085 |
#' @rdname section_div |
|
| 4086 |
#' @aliases section_div,VTableTree-method |
|
| 4087 |
setMethod("section_div", "VTableTree", function(obj) {
|
|
| 4088 |
## simpler but slower because it currently calls make_row_df then subsets |
|
| 4089 |
## section_div_info(obj)$trailing_sep |
|
| 4090 |
## TODO reimplement section_div_info based on logic here then |
|
| 4091 |
## replace code below with single call above |
|
| 4092 | 154x |
content_row_tbl <- content_table(obj) |
| 4093 | 154x |
is_content_table <- isS4(content_row_tbl) && nrow(content_row_tbl) > 0 # otherwise NA or NULL |
| 4094 | 154x |
if (labelrow_visible(obj)) {
|
| 4095 | 63x |
lrdiv <- trailing_section_div(tt_labelrow(obj)) |
| 4096 |
} else {
|
|
| 4097 | 91x |
lrdiv <- NULL |
| 4098 |
} |
|
| 4099 | ||
| 4100 | 154x |
if (is_content_table) {
|
| 4101 | 4x |
ctdivs <- section_div(content_row_tbl) |
| 4102 |
} else {
|
|
| 4103 | 150x |
ctdivs <- NULL |
| 4104 |
} |
|
| 4105 | 154x |
section_div <- trailing_section_div(obj) |
| 4106 | 154x |
rest_of_tree <- section_div(tree_children(obj)) |
| 4107 |
## Case it is the section itself and not the labels to have a trailing sep |
|
| 4108 | 154x |
if (!is.na(section_div)) {
|
| 4109 | 51x |
rest_of_tree[length(rest_of_tree)] <- section_div |
| 4110 |
} |
|
| 4111 | 154x |
unname(c(lrdiv, ctdivs, rest_of_tree)) |
| 4112 |
}) |
|
| 4113 | ||
| 4114 |
#' @rdname section_div |
|
| 4115 |
#' @aliases section_div,list-method |
|
| 4116 |
setMethod("section_div", "list", function(obj) {
|
|
| 4117 | 154x |
unlist(lapply(obj, section_div)) |
| 4118 |
}) |
|
| 4119 | ||
| 4120 |
#' @rdname section_div |
|
| 4121 |
#' @aliases section_div,TableRow-method |
|
| 4122 |
setMethod("section_div", "TableRow", function(obj) {
|
|
| 4123 | 72x |
trailing_section_div(obj) |
| 4124 |
}) |
|
| 4125 | ||
| 4126 | ||
| 4127 | ||
| 4128 | ||
| 4129 |
# section_div setter from table object |
|
| 4130 |
#' @rdname section_div |
|
| 4131 |
#' @export |
|
| 4132 |
setGeneric("section_div<-", function(obj, only_sep_sections = FALSE, value) {
|
|
| 4133 | 16x |
standardGeneric("section_div<-")
|
| 4134 |
}) |
|
| 4135 | ||
| 4136 |
#' @rdname section_div |
|
| 4137 |
#' @aliases section_div<-,VTableTree-method |
|
| 4138 |
setMethod("section_div<-", "VTableTree", function(obj, only_sep_sections = FALSE, value) {
|
|
| 4139 | 10x |
sdf <- section_div_info(obj) |
| 4140 | 10x |
value <- as.character(value) |
| 4141 | 10x |
pths <- sdf$path |
| 4142 | 10x |
if (length(value) < length(pths)) {
|
| 4143 | 3x |
only_sep_sections <- TRUE |
| 4144 |
} |
|
| 4145 |
## I really don't like this but it should be the correct generalization |
|
| 4146 |
## of the previous behavior :( |
|
| 4147 | 10x |
if (only_sep_sections) {
|
| 4148 | 4x |
curpth <- c(if (grepl("root", obj_name(obj))) "*", "*") # root split and val)
|
| 4149 | 4x |
for (i in seq_along(value)) {
|
| 4150 | 9x |
v <- value[i] |
| 4151 | 9x |
found <- FALSE |
| 4152 |
## split-value pairs (or multi-analysis analysis pairs...) |
|
| 4153 | 9x |
if (tt_row_path_exists(obj, curpth, tt_type = "table")) {
|
| 4154 | 7x |
section_div_at_path(obj, curpth, tt_type = "table") <- v |
| 4155 | 7x |
found <- TRUE |
| 4156 |
} |
|
| 4157 |
## last step was an analysis instead of another split_value pair |
|
| 4158 |
## remember, currently only analyze and summarize_row_groups |
|
| 4159 |
## create elementary tables, and an odd number of *s will never |
|
| 4160 |
## resolve to a content table, so this tt_type means analysis |
|
| 4161 |
## table in this context |
|
| 4162 | 9x |
if (tt_row_path_exists(obj, head(curpth, -1), tt_type = "elemtable")) {
|
| 4163 | 2x |
section_div_at_path(obj, head(curpth, -1), tt_type = "elemtable") <- v |
| 4164 | 2x |
found <- TRUE |
| 4165 |
} |
|
| 4166 | 9x |
if (!found) {
|
| 4167 | 1x |
warning( |
| 4168 | 1x |
"Unable to find ", ceiling(length(curpth) / 2), " levels of nesting", |
| 4169 | 1x |
" in table structure. Ignoring remaining ", length(value) - i, |
| 4170 | 1x |
" section_div values." |
| 4171 |
) |
|
| 4172 | 1x |
break |
| 4173 |
} |
|
| 4174 | 8x |
curpth <- c(curpth, "*", "*") ## add another split/value pair level of nesting |
| 4175 |
} |
|
| 4176 |
} else { ## guaranteed length(value) >= nrow(obj)
|
|
| 4177 | 6x |
if (length(value) > nrow(obj)) {
|
| 4178 | 1x |
warning( |
| 4179 | 1x |
"Got more section_div values than rows. Ignoring ", |
| 4180 | 1x |
nrow(obj) - length(value), " values." |
| 4181 |
) |
|
| 4182 | 1x |
value <- value[seq_len(nrow(obj))] |
| 4183 |
} |
|
| 4184 |
## clear out the structural (ie from layout) section divs so all |
|
| 4185 |
## the row divs take effect. Not sure this is right but its the existing behavior |
|
| 4186 | 6x |
obj <- clear_subtable_sectdivs(obj) |
| 4187 | 6x |
for (i in seq_along(pths)) {
|
| 4188 | 61x |
section_div_at_path(obj, labelrow = TRUE, pths[[i]], tt_type = "row") <- value[i] |
| 4189 |
} |
|
| 4190 |
} |
|
| 4191 | 10x |
obj |
| 4192 |
}) |
|
| 4193 | ||
| 4194 |
#' @rdname section_div |
|
| 4195 |
#' @aliases section_div<-,TableRow-method |
|
| 4196 |
setMethod("section_div<-", "TableRow", function(obj, only_sep_sections = FALSE, value) {
|
|
| 4197 | 6x |
trailing_section_div(obj) <- value |
| 4198 | 6x |
obj |
| 4199 |
}) |
|
| 4200 | ||
| 4201 |
#' @rdname section_div |
|
| 4202 |
#' @export |
|
| 4203 | 674x |
setGeneric("header_section_div", function(obj) standardGeneric("header_section_div"))
|
| 4204 | ||
| 4205 |
#' @rdname section_div |
|
| 4206 |
#' @aliases header_section_div,PreDataTableLayouts-method |
|
| 4207 |
setMethod( |
|
| 4208 |
"header_section_div", "PreDataTableLayouts", |
|
| 4209 | 335x |
function(obj) obj@header_section_div |
| 4210 |
) |
|
| 4211 | ||
| 4212 |
#' @rdname section_div |
|
| 4213 |
#' @aliases header_section_div,PreDataTableLayouts-method |
|
| 4214 |
setMethod( |
|
| 4215 |
"header_section_div", "VTableTree", |
|
| 4216 | 339x |
function(obj) obj@header_section_div |
| 4217 |
) |
|
| 4218 | ||
| 4219 |
#' @rdname section_div |
|
| 4220 |
#' @export |
|
| 4221 | 281x |
setGeneric("header_section_div<-", function(obj, value) standardGeneric("header_section_div<-"))
|
| 4222 | ||
| 4223 |
#' @rdname section_div |
|
| 4224 |
#' @aliases header_section_div<-,PreDataTableLayouts-method |
|
| 4225 |
setMethod( |
|
| 4226 |
"header_section_div<-", "PreDataTableLayouts", |
|
| 4227 |
function(obj, value) {
|
|
| 4228 | 1x |
.check_header_section_div(value) |
| 4229 | 1x |
obj@header_section_div <- value |
| 4230 | 1x |
obj |
| 4231 |
} |
|
| 4232 |
) |
|
| 4233 | ||
| 4234 |
#' @rdname section_div |
|
| 4235 |
#' @aliases header_section_div<-,PreDataTableLayouts-method |
|
| 4236 |
setMethod( |
|
| 4237 |
"header_section_div<-", "VTableTree", |
|
| 4238 |
function(obj, value) {
|
|
| 4239 | 280x |
.check_header_section_div(value) |
| 4240 | 280x |
obj@header_section_div <- value |
| 4241 | 280x |
obj |
| 4242 |
} |
|
| 4243 |
) |
|
| 4244 | ||
| 4245 |
.check_header_section_div <- function(chr) {
|
|
| 4246 | 642x |
if (!is.na(chr) && (!is.character(chr) || length(chr) > 1 || nchar(chr) > 1 || nchar(chr) == 0)) {
|
| 4247 | ! |
stop("header_section_div must be a single character or NA_character_ if not used")
|
| 4248 |
} |
|
| 4249 | 642x |
invisible(TRUE) |
| 4250 |
} |
|
| 4251 | ||
| 4252 |
#' @rdname section_div |
|
| 4253 |
#' @export |
|
| 4254 | 339x |
setGeneric("top_level_section_div", function(obj) standardGeneric("top_level_section_div"))
|
| 4255 | ||
| 4256 |
#' @rdname section_div |
|
| 4257 |
#' @aliases top_level_section_div,PreDataTableLayouts-method |
|
| 4258 |
setMethod( |
|
| 4259 |
"top_level_section_div", "PreDataTableLayouts", |
|
| 4260 | 339x |
function(obj) obj@top_level_section_div |
| 4261 |
) |
|
| 4262 | ||
| 4263 |
#' @rdname section_div |
|
| 4264 |
#' @export |
|
| 4265 | 1x |
setGeneric("top_level_section_div<-", function(obj, value) standardGeneric("top_level_section_div<-"))
|
| 4266 | ||
| 4267 |
#' @rdname section_div |
|
| 4268 |
#' @aliases top_level_section_div<-,PreDataTableLayouts-method |
|
| 4269 |
setMethod( |
|
| 4270 |
"top_level_section_div<-", "PreDataTableLayouts", |
|
| 4271 |
function(obj, value) {
|
|
| 4272 | 1x |
checkmate::assert_character(value, len = 1, n.chars = 1) |
| 4273 | 1x |
obj@top_level_section_div <- value |
| 4274 | 1x |
obj |
| 4275 |
} |
|
| 4276 |
) |
|
| 4277 | ||
| 4278 |
#' @rdname section_div |
|
| 4279 |
#' @details A `section_div` -> modify -> `section_div<-` workflow will |
|
| 4280 |
#' not work to modify section dividers declared in a layout (i.e., |
|
| 4281 |
#' with `split_rows_by*(., section_div=)` or |
|
| 4282 |
#' `analyze(.,section_div=)`) after the table has been built. In |
|
| 4283 |
#' that case a row 'inherits' its section divider behavior from |
|
| 4284 |
#' the largest subtable that has a section divider set and for |
|
| 4285 |
#' which it is the final row. Instead it clears the higher-order |
|
| 4286 |
#' section dividers and sets an individual divider on each row |
|
| 4287 |
#' (setting `NA_character_` for rows that had no divider after them |
|
| 4288 |
#' when rendering). This means that if pruning is done after |
|
| 4289 |
#' the above process and the last row in a "section" is pruned, |
|
| 4290 |
#' the last remaining row *will not inherit the section's divider* |
|
| 4291 |
#' the way it would before the modification by `section_div<-`. |
|
| 4292 |
#' |
|
| 4293 |
#' Generally it is advisable to use `section_div_at_path<-` - often |
|
| 4294 |
#' with `"*"` wildcards in the path - to modify |
|
| 4295 |
#' dividers declared in the layout instead of `section_div<-`. |
|
| 4296 |
#' Alternatively, pruning should be done *before* calling |
|
| 4297 |
#' `section_div<-` (when passing a a vector of length `nrow(tt)`), |
|
| 4298 |
#' when a script or function will do both operations on a table. |
|
| 4299 |
#' |
|
| 4300 |
#' Setting section_dividers for rows which do not currently inherit |
|
| 4301 |
#' section divider behavior from a containing subtable will work |
|
| 4302 |
#' as expected. |
|
| 4303 |
#' |
|
| 4304 |
#' `section_div_info` returns a data.frame of section divider |
|
| 4305 |
#' info (a subset of the result of `make_row_df` when called on a |
|
| 4306 |
#' table tree or row object). This information can be used to reset |
|
| 4307 |
#' section dividers at the correct path via `section_div_at_path` for |
|
| 4308 |
#' tables which have section dividers deriving from their layout ( |
|
| 4309 |
#' which will be attached to subtables, rather than rows). |
|
| 4310 |
#' @return For `section_div_info`, a dataframe containing `label`, |
|
| 4311 |
#' `name`, "node_class", `path`, `trailing_sep` (the effective divider, whether |
|
| 4312 |
#' inherited or not), `self_section_div` (the divider set on the |
|
| 4313 |
#' row itself), and `sect_div_from_path` (the path to the table |
|
| 4314 |
#' element the value in `trailing_sep` is inherited from, or |
|
| 4315 |
#' `NA_character_` for label rows, which are not pathable). |
|
| 4316 |
#' @export |
|
| 4317 |
section_div_info <- function(obj) {
|
|
| 4318 |
## default fontspec is already NULL so no speedup here |
|
| 4319 | 21x |
make_row_df(obj)[, c( |
| 4320 | 21x |
"label", |
| 4321 | 21x |
"name", |
| 4322 | 21x |
"node_class", |
| 4323 | 21x |
"path", |
| 4324 | 21x |
"trailing_sep", |
| 4325 | 21x |
"self_section_div", |
| 4326 | 21x |
"sect_div_from_path" |
| 4327 |
)] |
|
| 4328 |
} |
|
| 4329 | ||
| 4330 |
#' @rdname section_div |
|
| 4331 |
#' @param path (`character`)\cr The path of the element(s) to |
|
| 4332 |
#' set section_div(s) on. Can include `'*'` wildcards for |
|
| 4333 |
#' `section_div_at_path<-` only. |
|
| 4334 |
#' @param labelrow (`logical(1)`)\cr For `section_div_at_path`, |
|
| 4335 |
#' when `path` leads to a subtable, indicates whether the section |
|
| 4336 |
#' div be set/retrieved for the subtable (`FALSE`, the default) or the |
|
| 4337 |
#' subtable's label row (`TRUE`). Ignored when `path` resolves to an |
|
| 4338 |
#' individual row. |
|
| 4339 |
#' @export |
|
| 4340 |
section_div_at_path <- function(obj, path, labelrow = FALSE) {
|
|
| 4341 | 10x |
tt <- tt_at_path(obj, path) |
| 4342 | 8x |
if (is(tt, "VTableTree") && labelrow) {
|
| 4343 | 2x |
tt <- tt_labelrow(tt) |
| 4344 |
} |
|
| 4345 | 8x |
trailing_section_div(tt) |
| 4346 |
} |
|
| 4347 | ||
| 4348 |
clear_subtable_sectdivs <- function(obj) {
|
|
| 4349 | 6x |
rdf <- make_row_df(obj, visible_only = FALSE) |
| 4350 | 6x |
rdf <- rdf[grepl("Table", rdf$node_class), ]
|
| 4351 | 6x |
for (pth in rdf$path) {
|
| 4352 | 57x |
section_div_at_path(obj, pth, labelrow = FALSE, tt_type = "table") <- NA_character_ |
| 4353 |
} |
|
| 4354 | 6x |
obj |
| 4355 |
} |
|
| 4356 | ||
| 4357 |
#' @rdname section_div |
|
| 4358 |
#' @param .prev_path (`character`)\cr Internal detail, do not manually set. |
|
| 4359 |
#' @export |
|
| 4360 |
`section_div_at_path<-` <- function(obj, |
|
| 4361 |
path, |
|
| 4362 |
.prev_path = character(), |
|
| 4363 |
labelrow = FALSE, |
|
| 4364 |
tt_type = c("any", "row", "table", "elemtable"),
|
|
| 4365 |
value = " ") {
|
|
| 4366 | 191x |
tt_type <- match.arg(tt_type) |
| 4367 | 191x |
if (labelrow && tt_type == "any") {
|
| 4368 | 1x |
tt_type <- "table" |
| 4369 |
} |
|
| 4370 | 191x |
if (NROW(obj) == 0) {
|
| 4371 | ! |
return(character()) |
| 4372 |
} |
|
| 4373 | ||
| 4374 | 191x |
if (path[1] == "root") {
|
| 4375 | 53x |
if (obj_name(obj) == "root") {
|
| 4376 | 53x |
.prev_path <- c(.prev_path, path[1]) |
| 4377 |
} |
|
| 4378 | 53x |
path <- path[-1] |
| 4379 |
} |
|
| 4380 | 191x |
if (identical(obj_name(obj), path[1])) {
|
| 4381 | 65x |
.prev_path <- c(.prev_path, path[1]) |
| 4382 | 65x |
path <- path[-1] |
| 4383 |
} |
|
| 4384 | 191x |
curpath <- path |
| 4385 | 191x |
subtree <- obj |
| 4386 | 191x |
backpath <- c() |
| 4387 | 191x |
count <- 0 |
| 4388 | 191x |
while (length(curpath) > 0) {
|
| 4389 | 440x |
curname <- curpath[1] |
| 4390 | 440x |
if (!is(subtree, "VTableTree")) {
|
| 4391 | 1x |
stop( |
| 4392 | 1x |
"Path continues after resolving to individual row.\n\tOccurred at path: ", |
| 4393 | 1x |
paste(c(.prev_path, path[seq_len(count)]), collapse = " -> "), |
| 4394 | 1x |
"\n\tRemaining unresolved path: ", |
| 4395 | 1x |
paste(tail(path, -1 * count), collapse = "->"), |
| 4396 | 1x |
"\nUse 'make_row_df(obj, visible_only = TRUE)[, c(\"label\", \"path\", \"node_class\")]' or \n", |
| 4397 | 1x |
"'table_structure(obj)' to explore valid paths." |
| 4398 |
) |
|
| 4399 |
} |
|
| 4400 | 439x |
oldkids <- tree_children(subtree) |
| 4401 | 439x |
if (curname == "*") {
|
| 4402 | 60x |
oldnames <- vapply(oldkids, obj_name, "") |
| 4403 | 60x |
if (length(curpath) > 1) {
|
| 4404 |
## look ahead and only step into kids where the remaining path will |
|
| 4405 |
## successfully resolve |
|
| 4406 | 30x |
kidmatches <- which(vapply(oldkids, tt_row_path_exists, TRUE, path = curpath[-1], tt_type = tt_type)) |
| 4407 | 30x |
if (length(kidmatches) == 0) {
|
| 4408 | 3x |
stop( |
| 4409 | 3x |
"Unable to resolve * in path. \n\tOccurred at path: ", |
| 4410 | 3x |
paste(c(.prev_path, path[seq_len(count + 1)]), collapse = " -> "), |
| 4411 | 3x |
"\n\tLookahead found no matches (type ", tt_type, ") for the remaining path: ", |
| 4412 | 3x |
paste(path[-1 * seq_len(count + 1)], collapse = "->"), |
| 4413 | 3x |
"\nUse 'make_row_df(obj, visible_only = TRUE)[, c(\"label\", \"path\", \"node_class\")]' or \n", |
| 4414 | 3x |
"'table_structure(obj)' to explore valid paths." |
| 4415 |
) |
|
| 4416 |
} |
|
| 4417 | 27x |
newkids <- lapply(seq_along(oldkids), function(i) {
|
| 4418 | 49x |
kid <- oldkids[[i]] |
| 4419 | 49x |
if (i %in% kidmatches) {
|
| 4420 | 47x |
new_prev_path <- c( |
| 4421 | 47x |
.prev_path, backpath, |
| 4422 | 47x |
paste0("* (", oldnames[i], ")")
|
| 4423 |
) |
|
| 4424 | 47x |
section_div_at_path(kid, |
| 4425 | 47x |
path = curpath[-1], |
| 4426 | 47x |
.prev_path = new_prev_path |
| 4427 | 47x |
) <- value |
| 4428 |
} |
|
| 4429 | 49x |
kid |
| 4430 |
}) |
|
| 4431 |
} else {
|
|
| 4432 | 30x |
newkids <- lapply(oldkids, function(kdi) {
|
| 4433 | 59x |
trailing_section_div(kdi) <- value |
| 4434 | 59x |
kdi |
| 4435 |
}) |
|
| 4436 |
} |
|
| 4437 | 57x |
names(newkids) <- oldnames |
| 4438 | 57x |
newtab <- subtree |
| 4439 | 57x |
tree_children(newtab) <- newkids |
| 4440 | 57x |
if (length(backpath) > 0) {
|
| 4441 | 3x |
ret <- recursive_replace(obj, backpath, value = newtab) |
| 4442 |
} else {
|
|
| 4443 | 54x |
ret <- newtab |
| 4444 |
} |
|
| 4445 | 57x |
return(ret) |
| 4446 | 379x |
} else if (curname == "@content") {
|
| 4447 | 22x |
ctab <- content_table(subtree) |
| 4448 |
# curpath is just 'content' resolve to content table |
|
| 4449 | 22x |
if (length(curpath) == 1) {
|
| 4450 | 11x |
trailing_section_div(ctab) <- value |
| 4451 |
## weird to set section divs on content rows but that is what the |
|
| 4452 |
## section_div()<- <full vector> behavior calls for |
|
| 4453 | 11x |
} else if (length(curpath) == 2 && tt_row_path_exists(ctab, curpath[2])) {
|
| 4454 | 11x |
section_div_at_path(ctab, curpath[2], tt_type = "row") <- value |
| 4455 |
} else {
|
|
| 4456 | ! |
stop( |
| 4457 | ! |
"Unable to resolve path step involving @content \n\t occurred at path: ", |
| 4458 | ! |
paste(c(.prev_path, path[seq_len(count)]), collapse = " -> "), |
| 4459 | ! |
"\nUse 'make_row_df(obj, visible_only = TRUE)[, c(\"label\", \"path\", \"node_class\")]' or \n", |
| 4460 | ! |
"'table_structure(obj)' to explore valid paths." |
| 4461 |
) |
|
| 4462 |
} |
|
| 4463 | 22x |
content_table(subtree) <- ctab |
| 4464 | 22x |
if (length(backpath) > 0) {
|
| 4465 | 22x |
ret <- recursive_replace(obj, backpath, value = subtree) |
| 4466 |
} else {
|
|
| 4467 | ! |
ret <- subtree |
| 4468 |
} |
|
| 4469 | 22x |
return(ret) |
| 4470 | 357x |
} else if (!(curname %in% names(oldkids))) {
|
| 4471 | 1x |
stop( |
| 4472 | 1x |
"Unable to find child(ren) '", curname, "'\n\t occurred at path: ", |
| 4473 | 1x |
paste(c(.prev_path, path[seq_len(count)]), collapse = " -> "), |
| 4474 | 1x |
"\n Use 'make_row_df(obj, visible_only = TRUE)[, c(\"label\", \"path\", \"node_class\")]' or \n", |
| 4475 | 1x |
"'table_structure(obj)' to explore valid paths." |
| 4476 |
) |
|
| 4477 |
} |
|
| 4478 | 356x |
subtree <- tree_children(subtree)[[curname]] |
| 4479 | 356x |
backpath <- c(backpath, curpath[1]) |
| 4480 | 356x |
curpath <- curpath[-1] |
| 4481 | 356x |
count <- count + 1 |
| 4482 |
} |
|
| 4483 |
## ElementaryTables have 2 modes, label row which |
|
| 4484 |
## puts the section div after the label row (weird |
|
| 4485 |
## but necessary for the replacing every row's section div |
|
| 4486 |
## case), or tree, where we set trailing div |
|
| 4487 |
## **on the subtable itself** |
|
| 4488 |
## |
|
| 4489 |
## womp womp. tt_type_ok fails for subtables when we want their label row. |
|
| 4490 | 107x |
if (!tt_type_ok(subtree, tt_type) && !(labelrow && is(subtree, "VTableTree") && tt_type == "row")) {
|
| 4491 | ! |
stop( |
| 4492 | ! |
"Path ", |
| 4493 | ! |
paste(c(.prev_path, path[seq_len(count)]), collapse = " -> "), |
| 4494 | ! |
" lead to an element of the wrong tt_type (got ", class(subtree), |
| 4495 | ! |
" expected ", tt_type |
| 4496 |
) |
|
| 4497 | 107x |
} else if (is(subtree, "TableRow") || !labelrow) {
|
| 4498 |
## rows can only set it on themselves |
|
| 4499 |
## if its a table (and tables are allowed by tt_type) it sets it on |
|
| 4500 |
## itself iff labelrow is FALSE |
|
| 4501 | ||
| 4502 | 90x |
trailing_section_div(subtree) <- value |
| 4503 | 90x |
newtree <- subtree |
| 4504 | 17x |
} else if (labelrow && is(subtree, "VTableTree")) {
|
| 4505 | 17x |
lr <- tt_labelrow(subtree) |
| 4506 | 17x |
trailing_section_div(lr) <- value |
| 4507 | 17x |
tt_labelrow(subtree) <- lr |
| 4508 | 17x |
newtree <- subtree |
| 4509 |
} |
|
| 4510 | 107x |
tt_at_path(obj, path) <- newtree |
| 4511 | 107x |
obj |
| 4512 |
} |
|
| 4513 | ||
| 4514 |
## table_inset ---------------------------------------------------------- |
|
| 4515 | ||
| 4516 |
#' @rdname formatters_methods |
|
| 4517 |
#' @export |
|
| 4518 |
setMethod( |
|
| 4519 |
"table_inset", "VTableNodeInfo", ## VTableTree", |
|
| 4520 | 344x |
function(obj) obj@table_inset |
| 4521 |
) |
|
| 4522 | ||
| 4523 |
#' @rdname formatters_methods |
|
| 4524 |
#' @export |
|
| 4525 |
setMethod( |
|
| 4526 |
"table_inset", "PreDataTableLayouts", |
|
| 4527 | 334x |
function(obj) obj@table_inset |
| 4528 |
) |
|
| 4529 | ||
| 4530 |
## #' @rdname formatters_methods |
|
| 4531 |
## #' @export |
|
| 4532 |
## setMethod("table_inset", "InstantiatedColumnInfo",
|
|
| 4533 |
## function(obj) obj@table_inset) |
|
| 4534 | ||
| 4535 |
#' @rdname formatters_methods |
|
| 4536 |
#' @export |
|
| 4537 |
setMethod( |
|
| 4538 |
"table_inset<-", "VTableNodeInfo", ## "VTableTree", |
|
| 4539 |
function(obj, value) {
|
|
| 4540 | 18743x |
if (!is.integer(value)) {
|
| 4541 | 5x |
value <- as.integer(value) |
| 4542 |
} |
|
| 4543 | 18743x |
if (is.na(value) || value < 0) {
|
| 4544 | ! |
stop("Got invalid table_inset value, must be an integer > 0")
|
| 4545 |
} |
|
| 4546 | 18743x |
cont <- content_table(obj) |
| 4547 | 18743x |
if (NROW(cont) > 0) {
|
| 4548 | 1571x |
table_inset(cont) <- value |
| 4549 | 1571x |
content_table(obj) <- cont |
| 4550 |
} |
|
| 4551 | ||
| 4552 | 18743x |
if (length(tree_children(obj)) > 0) {
|
| 4553 | 5574x |
kids <- lapply(tree_children(obj), |
| 4554 | 5574x |
`table_inset<-`, |
| 4555 | 5574x |
value = value |
| 4556 |
) |
|
| 4557 | 5574x |
tree_children(obj) <- kids |
| 4558 |
} |
|
| 4559 | 18743x |
obj@table_inset <- value |
| 4560 | 18743x |
obj |
| 4561 |
} |
|
| 4562 |
) |
|
| 4563 | ||
| 4564 |
#' @rdname formatters_methods |
|
| 4565 |
#' @export |
|
| 4566 |
setMethod( |
|
| 4567 |
"table_inset<-", "PreDataTableLayouts", |
|
| 4568 |
function(obj, value) {
|
|
| 4569 | ! |
if (!is.integer(value)) {
|
| 4570 | ! |
value <- as.integer(value) |
| 4571 |
} |
|
| 4572 | ! |
if (is.na(value) || value < 0) {
|
| 4573 | ! |
stop("Got invalid table_inset value, must be an integer > 0")
|
| 4574 |
} |
|
| 4575 | ||
| 4576 | ! |
obj@table_inset <- value |
| 4577 | ! |
obj |
| 4578 |
} |
|
| 4579 |
) |
|
| 4580 | ||
| 4581 |
#' @rdname formatters_methods |
|
| 4582 |
#' @export |
|
| 4583 |
setMethod( |
|
| 4584 |
"table_inset<-", "InstantiatedColumnInfo", |
|
| 4585 |
function(obj, value) {
|
|
| 4586 | ! |
if (!is.integer(value)) {
|
| 4587 | ! |
value <- as.integer(value) |
| 4588 |
} |
|
| 4589 | ! |
if (is.na(value) || value < 0) {
|
| 4590 | ! |
stop("Got invalid table_inset value, must be an integer > 0")
|
| 4591 |
} |
|
| 4592 | ! |
obj@table_inset <- value |
| 4593 | ! |
obj |
| 4594 |
} |
|
| 4595 |
) |
|
| 4596 | ||
| 4597 |
# stat_names for ARD ----------------------------------------------------------- |
|
| 4598 |
# |
|
| 4599 |
#' @rdname int_methods |
|
| 4600 |
#' @export |
|
| 4601 | 1751x |
setGeneric("obj_stat_names", function(obj) standardGeneric("obj_stat_names"))
|
| 4602 |
# |
|
| 4603 |
#' @rdname int_methods |
|
| 4604 |
#' @export |
|
| 4605 | 8x |
setGeneric("obj_stat_names<-", function(obj, value) standardGeneric("obj_stat_names<-"))
|
| 4606 | ||
| 4607 |
#' @rdname int_methods |
|
| 4608 |
#' @export |
|
| 4609 |
setMethod("obj_stat_names<-", "CellValue", function(obj, value) {
|
|
| 4610 | 8x |
attr(obj, "stat_names") <- value |
| 4611 | 8x |
obj |
| 4612 |
}) |
|
| 4613 | ||
| 4614 |
#' @rdname int_methods |
|
| 4615 |
#' @export |
|
| 4616 | 1751x |
setMethod("obj_stat_names", "CellValue", function(obj) attr(obj, "stat_names"))
|
| 4617 | ||
| 4618 |
#' @rdname int_methods |
|
| 4619 |
#' @export |
|
| 4620 |
setMethod( |
|
| 4621 |
"obj_stat_names", "RowsVerticalSection", |
|
| 4622 | ! |
function(obj) lapply(obj, obj_stat_names) |
| 4623 |
) |
| 1 |
match_extra_args <- function(f, |
|
| 2 |
.N_col, |
|
| 3 |
.N_total, |
|
| 4 |
.all_col_exprs, |
|
| 5 |
.all_col_counts, |
|
| 6 |
.var, |
|
| 7 |
.ref_group = NULL, |
|
| 8 |
.alt_df_row = NULL, |
|
| 9 |
.alt_df = NULL, |
|
| 10 |
.alt_df_full = NULL, |
|
| 11 |
.ref_full = NULL, |
|
| 12 |
.in_ref_col = NULL, |
|
| 13 |
.spl_context = NULL, |
|
| 14 |
.N_row, |
|
| 15 |
.df_row, |
|
| 16 |
extras) {
|
|
| 17 |
# This list is always present |
|
| 18 | 6572x |
possargs <- c( |
| 19 | 6572x |
list( |
| 20 | 6572x |
.N_col = .N_col, |
| 21 | 6572x |
.N_total = .N_total, |
| 22 | 6572x |
.N_row = .N_row, |
| 23 | 6572x |
.df_row = .df_row, |
| 24 | 6572x |
.all_col_exprs = .all_col_exprs, |
| 25 | 6572x |
.all_col_counts = .all_col_counts |
| 26 |
), |
|
| 27 | 6572x |
extras |
| 28 |
) |
|
| 29 | ||
| 30 |
## specialized arguments that must be named in formals, cannot go |
|
| 31 |
## anonymously into ... |
|
| 32 | 6572x |
if (!is.null(.var) && nzchar(.var)) {
|
| 33 | 5179x |
possargs <- c(possargs, list(.var = .var)) |
| 34 |
} |
|
| 35 | 6572x |
if (!is.null(.ref_group)) {
|
| 36 | 2018x |
possargs <- c(possargs, list(.ref_group = .ref_group)) |
| 37 |
} |
|
| 38 | 6572x |
if (!is.null(.alt_df_row)) {
|
| 39 | 105x |
possargs <- c(possargs, list(.alt_df_row = .alt_df_row)) |
| 40 |
} |
|
| 41 | 6572x |
if (!is.null(.alt_df)) {
|
| 42 | 105x |
possargs <- c(possargs, list(.alt_df = .alt_df)) |
| 43 |
} |
|
| 44 | ||
| 45 | 6572x |
if (!is.null(.alt_df_full)) {
|
| 46 | 210x |
possargs <- c(possargs, list(.alt_df_full = .alt_df_full)) |
| 47 |
} |
|
| 48 | ||
| 49 | 6572x |
if (!is.null(.ref_full)) {
|
| 50 | 141x |
possargs <- c(possargs, list(.ref_full = .ref_full)) |
| 51 |
} |
|
| 52 | 6572x |
if (!is.null(.in_ref_col)) {
|
| 53 | 141x |
possargs <- c(possargs, list(.in_ref_col = .in_ref_col)) |
| 54 |
} |
|
| 55 | ||
| 56 |
# Special case: .spl_context |
|
| 57 | 6572x |
if (!is.null(.spl_context) && !(".spl_context" %in% names(possargs))) {
|
| 58 | 6572x |
possargs <- c(possargs, list(.spl_context = .spl_context)) |
| 59 |
} else {
|
|
| 60 | ! |
possargs$.spl_context <- NULL |
| 61 |
} |
|
| 62 | ||
| 63 |
# Extra args handling |
|
| 64 | 6572x |
formargs <- formals(f) |
| 65 | 6572x |
formnms <- names(formargs) |
| 66 | 6572x |
exnms <- names(extras) |
| 67 | 6572x |
if (is.null(formargs)) {
|
| 68 | 208x |
return(NULL) |
| 69 | 6364x |
} else if ("..." %in% names(formargs)) {
|
| 70 | 5596x |
formnms <- c(formnms, exnms[nzchar(exnms)]) |
| 71 |
} |
|
| 72 | 6364x |
possargs[names(possargs) %in% formnms] |
| 73 |
} |
|
| 74 | ||
| 75 |
#' @noRd |
|
| 76 |
#' @return A `RowsVerticalSection` object representing the `k x 1` section of the |
|
| 77 |
#' table being generated, with `k` the number of rows the analysis function |
|
| 78 |
#' generates. |
|
| 79 |
gen_onerv <- function(csub, col, count, cextr, cpath, |
|
| 80 |
dfpart, func, totcount, splextra, |
|
| 81 |
all_col_exprs, |
|
| 82 |
all_col_counts, |
|
| 83 |
takesdf = .takes_df(func), |
|
| 84 |
baselinedf, |
|
| 85 |
alt_dfpart, |
|
| 86 |
alt_df_full, |
|
| 87 |
inclNAs, |
|
| 88 |
col_parent_inds, |
|
| 89 |
spl_context) {
|
|
| 90 | 6572x |
if (NROW(spl_context) > 0) {
|
| 91 | 6551x |
spl_context$cur_col_id <- paste(cpath[seq(2, length(cpath), 2)], collapse = ".") |
| 92 | 6551x |
spl_context$cur_col_subset <- col_parent_inds |
| 93 | 6551x |
spl_context$cur_col_expr <- list(csub) |
| 94 | 6551x |
spl_context$cur_col_n <- vapply(col_parent_inds, sum, 1L) |
| 95 | 6551x |
spl_context$cur_col_split <- list(cpath[seq(1, length(cpath), 2)]) |
| 96 | 6551x |
spl_context$cur_col_split_val <- list(cpath[seq(2, length(cpath), 2)]) |
| 97 |
} |
|
| 98 | ||
| 99 |
# Making .alt_df from alt_dfpart (i.e. .alt_df_row) |
|
| 100 | 6572x |
if (NROW(alt_dfpart) > 0) {
|
| 101 | 105x |
alt_dfpart_fil <- alt_dfpart[eval(csub, envir = alt_dfpart), , drop = FALSE] |
| 102 | 105x |
if (!is.null(col) && col %in% names(alt_dfpart_fil) && !inclNAs) {
|
| 103 | 99x |
alt_dfpart_fil <- alt_dfpart_fil[!is.na(alt_dfpart_fil[[col]]), , |
| 104 | 99x |
drop = FALSE |
| 105 |
] |
|
| 106 |
} |
|
| 107 |
} else {
|
|
| 108 | 6467x |
alt_dfpart_fil <- alt_dfpart |
| 109 |
} |
|
| 110 | ||
| 111 |
## workaround for https://github.com/insightsengineering/rtables/issues/159 |
|
| 112 | 6572x |
if (NROW(dfpart) > 0) {
|
| 113 | 5704x |
inds <- eval(csub, envir = dfpart) |
| 114 | 5704x |
dat <- dfpart[inds, , drop = FALSE] |
| 115 |
} else {
|
|
| 116 | 868x |
dat <- dfpart |
| 117 |
} |
|
| 118 | 6572x |
if (!is.null(col) && !inclNAs) {
|
| 119 | 5153x |
dat <- dat[!is.na(dat[[col]]), , drop = FALSE] |
| 120 |
} |
|
| 121 | ||
| 122 | 6572x |
fullrefcoldat <- cextr$.ref_full |
| 123 | 6572x |
if (!is.null(fullrefcoldat)) {
|
| 124 | 141x |
cextr$.ref_full <- NULL |
| 125 |
} |
|
| 126 | 6572x |
inrefcol <- cextr$.in_ref_col |
| 127 | 6572x |
if (!is.null(fullrefcoldat)) {
|
| 128 | 141x |
cextr$.in_ref_col <- NULL |
| 129 |
} |
|
| 130 | ||
| 131 | 6572x |
exargs <- c(cextr, splextra) |
| 132 | ||
| 133 |
## behavior for x/df and ref-data (full and group) |
|
| 134 |
## match |
|
| 135 | 6572x |
if (!is.null(col) && !takesdf) {
|
| 136 | 4236x |
dat <- dat[[col]] |
| 137 | 4236x |
fullrefcoldat <- fullrefcoldat[[col]] |
| 138 | 4236x |
baselinedf <- baselinedf[[col]] |
| 139 |
} |
|
| 140 | 6572x |
args <- list(dat) |
| 141 | ||
| 142 | 6572x |
names(all_col_counts) <- names(all_col_exprs) |
| 143 | ||
| 144 | 6572x |
exargs <- match_extra_args(func, |
| 145 | 6572x |
.N_col = count, |
| 146 | 6572x |
.N_total = totcount, |
| 147 | 6572x |
.all_col_exprs = all_col_exprs, |
| 148 | 6572x |
.all_col_counts = all_col_counts, |
| 149 | 6572x |
.var = col, |
| 150 | 6572x |
.ref_group = baselinedf, |
| 151 | 6572x |
.alt_df_row = alt_dfpart, |
| 152 | 6572x |
.alt_df = alt_dfpart_fil, |
| 153 | 6572x |
.alt_df_full = alt_df_full, |
| 154 | 6572x |
.ref_full = fullrefcoldat, |
| 155 | 6572x |
.in_ref_col = inrefcol, |
| 156 | 6572x |
.N_row = NROW(dfpart), |
| 157 | 6572x |
.df_row = dfpart, |
| 158 | 6572x |
.spl_context = spl_context, |
| 159 | 6572x |
extras = c( |
| 160 | 6572x |
cextr, |
| 161 | 6572x |
splextra |
| 162 |
) |
|
| 163 |
) |
|
| 164 | ||
| 165 | 6572x |
args <- c(args, exargs) |
| 166 | ||
| 167 | 6572x |
val <- do.call(func, args) |
| 168 | 6569x |
if (!is(val, "RowsVerticalSection")) {
|
| 169 | 4045x |
if (!is(val, "list")) {
|
| 170 | 3549x |
val <- list(val) |
| 171 |
} |
|
| 172 | 4045x |
ret <- in_rows( |
| 173 | 4045x |
.list = val, |
| 174 | 4045x |
.labels = unlist(value_labels(val)), |
| 175 | 4045x |
.names = names(val) |
| 176 |
) |
|
| 177 |
} else {
|
|
| 178 | 2524x |
ret <- val |
| 179 |
} |
|
| 180 | 6569x |
ret |
| 181 |
} |
|
| 182 | ||
| 183 |
strip_multivar_suffix <- function(x) {
|
|
| 184 | 236x |
gsub("\\._\\[\\[[0-9]\\]\\]_\\.$", "", x)
|
| 185 |
} |
|
| 186 | ||
| 187 |
## Generate all values (one for each column) for one or more rows |
|
| 188 |
## by calling func once per column (as defined by cinfo) |
|
| 189 |
#' @noRd |
|
| 190 |
#' @return A list of `m` `RowsVerticalSection` objects, one for each (leaf) column in the table. |
|
| 191 |
gen_rowvalues <- function(dfpart, |
|
| 192 |
datcol, |
|
| 193 |
cinfo, |
|
| 194 |
func, |
|
| 195 |
splextra, |
|
| 196 |
takesdf = NULL, |
|
| 197 |
baselines, |
|
| 198 |
alt_dfpart, |
|
| 199 |
alt_df_full, |
|
| 200 |
inclNAs, |
|
| 201 |
spl_context) {
|
|
| 202 | 1817x |
colexprs <- col_exprs(cinfo) |
| 203 | 1817x |
colcounts <- col_counts(cinfo) |
| 204 | 1817x |
colextras <- col_extra_args(cinfo, NULL) |
| 205 | 1817x |
cpaths <- col_paths(cinfo) |
| 206 |
## XXX I don't think this is used anywhere??? |
|
| 207 |
## splextra = c(splextra, list(.spl_context = spl_context)) |
|
| 208 | 1817x |
totcount <- col_total(cinfo) |
| 209 | ||
| 210 | 1817x |
colleaves <- collect_leaves(cinfo@tree_layout) |
| 211 | ||
| 212 | 1817x |
gotflist <- is.list(func) |
| 213 | ||
| 214 |
## one set of named args to be applied to all columns |
|
| 215 | 1817x |
if (!is.null(names(splextra))) {
|
| 216 | 25x |
splextra <- list(splextra) |
| 217 |
} else {
|
|
| 218 | 1792x |
length(splextra) <- ncol(cinfo) |
| 219 |
} |
|
| 220 | ||
| 221 | 1817x |
if (!gotflist) {
|
| 222 | 1252x |
func <- list(func) |
| 223 | 565x |
} else if (length(splextra) == 1) {
|
| 224 | 129x |
splextra <- rep(splextra, length.out = length(func)) |
| 225 |
} |
|
| 226 |
## if(length(func)) == 1 && names(spl) |
|
| 227 |
## splextra = list(splextra) |
|
| 228 | ||
| 229 |
## we are in analyze_colvars, so we have to match |
|
| 230 |
## the exargs value by position for each column repeatedly |
|
| 231 |
## across the higher level col splits. |
|
| 232 | 1817x |
if (!is.null(datcol) && is.na(datcol)) {
|
| 233 | 56x |
datcol <- character(length(colleaves)) |
| 234 | 56x |
exargs <- vector("list", length(colleaves))
|
| 235 | 56x |
for (i in seq_along(colleaves)) {
|
| 236 | 236x |
x <- colleaves[[i]] |
| 237 | ||
| 238 | 236x |
pos <- tree_pos(x) |
| 239 | 236x |
spls <- pos_splits(pos) |
| 240 |
## values have the suffix but we are populating datacol |
|
| 241 |
## so it has to match var numbers so strip the suffixes back off |
|
| 242 | 236x |
splvals <- strip_multivar_suffix(rawvalues(pos)) |
| 243 | 236x |
n <- length(spls) |
| 244 | 236x |
datcol[i] <- if (is(spls[[n]], "MultiVarSplit")) {
|
| 245 | 236x |
splvals[n] |
| 246 |
} else {
|
|
| 247 | 236x |
NA_character_ |
| 248 |
} |
|
| 249 | 236x |
argpos <- match(datcol[i], spl_payload(spls[[n]])) |
| 250 |
## single bracket here because assigning NULL into a list removes |
|
| 251 |
## the position entirely |
|
| 252 | 236x |
exargs[i] <- if (argpos <= length(splextra)) {
|
| 253 | 236x |
splextra[argpos] |
| 254 |
} else {
|
|
| 255 | ! |
list(NULL) |
| 256 |
} |
|
| 257 |
} |
|
| 258 |
## }) |
|
| 259 | 56x |
if (all(is.na(datcol))) {
|
| 260 | ! |
datcol <- list(NULL) |
| 261 | 56x |
} else if (any(is.na(datcol))) {
|
| 262 | ! |
stop("mix of var and non-var columns with NA analysis rowvara")
|
| 263 |
} |
|
| 264 |
} else {
|
|
| 265 | 1761x |
exargs <- splextra |
| 266 | 1761x |
if (is.null(datcol)) {
|
| 267 | 363x |
datcol <- list(NULL) |
| 268 |
} |
|
| 269 | 1761x |
datcol <- rep(datcol, length(colexprs)) |
| 270 |
## if(gotflist) |
|
| 271 |
## length(exargs) <- length(func) ## func is a list |
|
| 272 | 1761x |
exargs <- rep(exargs, length.out = length(colexprs)) |
| 273 |
} |
|
| 274 | 1817x |
allfuncs <- rep(func, length.out = length(colexprs)) |
| 275 | ||
| 276 | 1817x |
if (is.null(takesdf)) {
|
| 277 | 1308x |
takesdf <- .takes_df(allfuncs) |
| 278 |
} |
|
| 279 | ||
| 280 | 1817x |
rawvals <- mapply(gen_onerv, |
| 281 | 1817x |
csub = colexprs, |
| 282 | 1817x |
col = datcol, |
| 283 | 1817x |
count = colcounts, |
| 284 | 1817x |
cextr = colextras, |
| 285 | 1817x |
cpath = cpaths, |
| 286 | 1817x |
baselinedf = baselines, |
| 287 | 1817x |
alt_dfpart = list(alt_dfpart), |
| 288 | 1817x |
func = allfuncs, |
| 289 | 1817x |
takesdf = takesdf, |
| 290 | 1817x |
col_parent_inds = spl_context[, names(colexprs), |
| 291 | 1817x |
drop = FALSE |
| 292 |
], |
|
| 293 | 1817x |
all_col_exprs = list(colexprs), |
| 294 | 1817x |
all_col_counts = list(colcounts), |
| 295 | 1817x |
splextra = exargs, |
| 296 | 1817x |
MoreArgs = list( |
| 297 | 1817x |
dfpart = dfpart, |
| 298 | 1817x |
alt_df_full = alt_df_full, |
| 299 | 1817x |
totcount = totcount, |
| 300 | 1817x |
inclNAs = inclNAs, |
| 301 | 1817x |
spl_context = spl_context |
| 302 |
), |
|
| 303 | 1817x |
SIMPLIFY = FALSE |
| 304 |
) |
|
| 305 | ||
| 306 | 1814x |
names(rawvals) <- names(colexprs) |
| 307 | 1814x |
rawvals |
| 308 |
} |
|
| 309 | ||
| 310 |
.strip_lst_rvals <- function(lst) {
|
|
| 311 | ! |
lapply(lst, rawvalues) |
| 312 |
} |
|
| 313 | ||
| 314 |
#' @noRd |
|
| 315 |
#' @return A list of table rows, even when only one is generated. |
|
| 316 |
.make_tablerows <- function(dfpart, |
|
| 317 |
alt_dfpart, |
|
| 318 |
alt_df_full, |
|
| 319 |
func, |
|
| 320 |
cinfo, |
|
| 321 |
datcol = NULL, |
|
| 322 |
lev = 1L, |
|
| 323 |
rvlab = NA_character_, |
|
| 324 |
format = NULL, |
|
| 325 |
defrowlabs = NULL, |
|
| 326 |
rowconstr = DataRow, |
|
| 327 |
splextra = list(), |
|
| 328 |
takesdf = NULL, |
|
| 329 |
baselines = replicate( |
|
| 330 |
length(col_exprs(cinfo)), |
|
| 331 |
list(dfpart[0, ]) |
|
| 332 |
), |
|
| 333 |
inclNAs, |
|
| 334 |
spl_context = context_df_row(cinfo = cinfo)) {
|
|
| 335 | 1817x |
if (is.null(datcol) && !is.na(rvlab)) {
|
| 336 | ! |
stop("NULL datcol but non-na rowvar label")
|
| 337 |
} |
|
| 338 | 1817x |
if (!is.null(datcol) && !is.na(datcol)) {
|
| 339 | 1398x |
if (!all(datcol %in% names(dfpart))) {
|
| 340 | ! |
stop( |
| 341 | ! |
"specified analysis variable (", datcol,
|
| 342 | ! |
") not present in data" |
| 343 |
) |
|
| 344 |
} |
|
| 345 | ||
| 346 | 1398x |
rowvar <- datcol |
| 347 |
} else {
|
|
| 348 | 419x |
rowvar <- NA_character_ |
| 349 |
} |
|
| 350 | ||
| 351 | 1817x |
rawvals <- gen_rowvalues(dfpart, |
| 352 | 1817x |
alt_dfpart = alt_dfpart, |
| 353 | 1817x |
alt_df_full = alt_df_full, |
| 354 | 1817x |
datcol = datcol, |
| 355 | 1817x |
cinfo = cinfo, |
| 356 | 1817x |
func = func, |
| 357 | 1817x |
splextra = splextra, |
| 358 | 1817x |
takesdf = takesdf, |
| 359 | 1817x |
baselines = baselines, |
| 360 | 1817x |
inclNAs = inclNAs, |
| 361 | 1817x |
spl_context = spl_context |
| 362 |
) |
|
| 363 | ||
| 364 |
## if(is.null(rvtypes)) |
|
| 365 |
## rvtypes = rep(NA_character_, length(rawvals)) |
|
| 366 | 1814x |
lens <- vapply(rawvals, length, NA_integer_) |
| 367 | 1814x |
unqlens <- unique(lens) |
| 368 |
## length 0 returns are ok to not match cause they are |
|
| 369 |
## just empty space we can fill in as needed. |
|
| 370 | 1814x |
if (length(unqlens[unqlens > 0]) != 1L) { ## length(unqlens) != 1 &&
|
| 371 |
## (0 %in% unqlens && length(unqlens) != 2)) {
|
|
| 372 | 1x |
stop( |
| 373 | 1x |
"Number of rows generated by analysis function do not match ", |
| 374 | 1x |
"across all columns. ", |
| 375 | 1x |
if (!is.na(datcol) && is.character(dfpart[[datcol]])) {
|
| 376 | ! |
paste( |
| 377 | ! |
"\nPerhaps convert analysis variable", datcol, |
| 378 | ! |
"to a factor?" |
| 379 |
) |
|
| 380 |
} |
|
| 381 |
) |
|
| 382 |
} |
|
| 383 | 1813x |
maxind <- match(max(unqlens), lens) |
| 384 | ||
| 385 |
## look if we got labels, if not apply the |
|
| 386 |
## default row labels |
|
| 387 |
## this is guaranteed to be a RowsVerticalSection object. |
|
| 388 | 1813x |
rv1col <- rawvals[[maxind]] |
| 389 |
## nocov start |
|
| 390 |
if (!is(rv1col, "RowsVerticalSection")) {
|
|
| 391 |
stop( |
|
| 392 |
"gen_rowvalues appears to have generated something that was not ", |
|
| 393 |
"a RowsVerticalSection object. Please contact the maintainer." |
|
| 394 |
) |
|
| 395 |
} |
|
| 396 |
# nocov end |
|
| 397 | ||
| 398 | 1813x |
labels <- value_labels(rv1col) |
| 399 | ||
| 400 | 1813x |
ncrows <- max(unqlens) |
| 401 | 1813x |
if (ncrows == 0) {
|
| 402 | ! |
return(list()) |
| 403 |
} |
|
| 404 | 1813x |
stopifnot(ncrows > 0) |
| 405 | ||
| 406 | 1813x |
if (is.null(labels)) {
|
| 407 | 224x |
if (length(rawvals[[maxind]]) == length(defrowlabs)) {
|
| 408 | 213x |
labels <- defrowlabs |
| 409 |
} else {
|
|
| 410 | 11x |
labels <- rep("", ncrows)
|
| 411 |
} |
|
| 412 |
} |
|
| 413 | ||
| 414 | 1813x |
rfootnotes <- rep(list(list(), length(rv1col))) |
| 415 | 1813x |
nms <- value_names(rv1col) |
| 416 | 1813x |
rfootnotes <- row_footnotes(rv1col) |
| 417 | ||
| 418 | 1813x |
imods <- indent_mod(rv1col) ## rv1col@indent_mods |
| 419 | 1813x |
unwrapped_vals <- lapply(rawvals, as, Class = "list", strict = TRUE) |
| 420 | ||
| 421 | 1813x |
formatvec <- NULL |
| 422 | 1813x |
if (!is.null(format)) {
|
| 423 | 208x |
if (is.function(format)) {
|
| 424 | 1x |
format <- list(format) |
| 425 |
} |
|
| 426 | 208x |
formatvec <- rep(format, length.out = ncrows) |
| 427 |
} |
|
| 428 | ||
| 429 | 1813x |
trows <- lapply(1:ncrows, function(i) {
|
| 430 | 3047x |
rowvals <- lapply(unwrapped_vals, function(colvals) {
|
| 431 | 10618x |
colvals[[i]] |
| 432 |
}) |
|
| 433 | 3047x |
imod <- unique(vapply(rowvals, indent_mod, 0L)) |
| 434 | 3047x |
if (length(imod) != 1) {
|
| 435 | ! |
stop( |
| 436 | ! |
"Different cells in the same row appear to have been given ", |
| 437 | ! |
"different indent_mod values" |
| 438 |
) |
|
| 439 |
} |
|
| 440 | 3047x |
rowconstr( |
| 441 | 3047x |
vals = rowvals, |
| 442 | 3047x |
cinfo = cinfo, |
| 443 | 3047x |
lev = lev, |
| 444 | 3047x |
label = labels[i], |
| 445 | 3047x |
name = nms[i], ## labels[i], ## XXX this is probably wrong?! |
| 446 | 3047x |
var = rowvar, |
| 447 | 3047x |
format = formatvec[[i]], |
| 448 | 3047x |
indent_mod = imods[[i]] %||% 0L, |
| 449 | 3047x |
footnotes = rfootnotes[[i]] ## one bracket so list |
| 450 |
) |
|
| 451 |
}) |
|
| 452 | 1813x |
trows |
| 453 |
} |
|
| 454 | ||
| 455 |
.make_caller <- function(parent_cfun, clabelstr = "") {
|
|
| 456 | 520x |
formalnms <- names(formals(parent_cfun)) |
| 457 |
## note the <- here |
|
| 458 | 520x |
if (!is.na(dotspos <- match("...", formalnms))) {
|
| 459 | 1x |
toremove <- dotspos |
| 460 |
} else {
|
|
| 461 | 519x |
toremove <- NULL |
| 462 |
} |
|
| 463 | ||
| 464 | 520x |
labelstrpos <- match("labelstr", names(formals(parent_cfun)))
|
| 465 | 520x |
if (is.na(labelstrpos)) {
|
| 466 | ! |
stop( |
| 467 | ! |
"content function does not appear to accept the labelstr", |
| 468 | ! |
"arguent" |
| 469 |
) |
|
| 470 |
} |
|
| 471 | 520x |
toremove <- c(toremove, labelstrpos) |
| 472 | 520x |
formalnms <- formalnms[-1 * toremove] |
| 473 | ||
| 474 | 520x |
caller <- eval(parser_helper(text = paste( |
| 475 | 520x |
"function() { parent_cfun(",
|
| 476 | 520x |
paste(formalnms, "=", |
| 477 | 520x |
formalnms, |
| 478 | 520x |
collapse = ", " |
| 479 |
), |
|
| 480 | 520x |
", labelstr = clabelstr, ...)}" |
| 481 |
))) |
|
| 482 | 520x |
formals(caller) <- c( |
| 483 | 520x |
formals(parent_cfun)[-labelstrpos], |
| 484 | 520x |
alist("..." = )
|
| 485 | 520x |
) # nolint |
| 486 | 520x |
caller |
| 487 |
} |
|
| 488 | ||
| 489 |
# Makes content table xxx renaming |
|
| 490 |
.make_ctab <- function(df, |
|
| 491 |
lvl, ## treepos, |
|
| 492 |
name, |
|
| 493 |
label, |
|
| 494 |
cinfo, |
|
| 495 |
parent_cfun = NULL, |
|
| 496 |
format = NULL, |
|
| 497 |
na_str = NA_character_, |
|
| 498 |
indent_mod = 0L, |
|
| 499 |
cvar = NULL, |
|
| 500 |
inclNAs, |
|
| 501 |
alt_df, |
|
| 502 |
alt_df_full, |
|
| 503 |
extra_args, |
|
| 504 |
spl_context = context_df_row(cinfo = cinfo)) {
|
|
| 505 | 2105x |
if (length(cvar) == 0 || is.na(cvar) || identical(nchar(cvar), 0L)) {
|
| 506 | 1925x |
cvar <- NULL |
| 507 |
} |
|
| 508 | 2105x |
if (!is.null(parent_cfun)) {
|
| 509 |
## cfunc <- .make_caller(parent_cfun, label) |
|
| 510 | 509x |
cfunc <- lapply(parent_cfun, .make_caller, clabelstr = label) |
| 511 | 509x |
contkids <- tryCatch( |
| 512 | 509x |
.make_tablerows(df, |
| 513 | 509x |
lev = lvl, |
| 514 | 509x |
func = cfunc, |
| 515 | 509x |
cinfo = cinfo, |
| 516 | 509x |
rowconstr = ContentRow, |
| 517 | 509x |
datcol = cvar, |
| 518 | 509x |
takesdf = rep(.takes_df(cfunc), |
| 519 | 509x |
length.out = ncol(cinfo) |
| 520 |
), |
|
| 521 | 509x |
inclNAs = FALSE, |
| 522 | 509x |
alt_dfpart = alt_df, |
| 523 | 509x |
alt_df_full = alt_df_full, |
| 524 | 509x |
splextra = extra_args, |
| 525 | 509x |
spl_context = spl_context |
| 526 |
), |
|
| 527 | 509x |
error = function(e) e |
| 528 |
) |
|
| 529 | 509x |
if (is(contkids, "error")) {
|
| 530 | 1x |
stop("Error in content (summary) function: ", contkids$message,
|
| 531 | 1x |
"\n\toccured at path: ", |
| 532 | 1x |
spl_context_to_disp_path(spl_context), |
| 533 | 1x |
call. = FALSE |
| 534 |
) |
|
| 535 |
} |
|
| 536 |
} else {
|
|
| 537 | 1596x |
contkids <- list() |
| 538 |
} |
|
| 539 | 2104x |
ctab <- ElementaryTable( |
| 540 | 2104x |
kids = contkids, |
| 541 | 2104x |
name = paste0(name, "@content"), |
| 542 | 2104x |
lev = lvl, |
| 543 | 2104x |
labelrow = LabelRow(), |
| 544 | 2104x |
cinfo = cinfo, |
| 545 | 2104x |
iscontent = TRUE, |
| 546 | 2104x |
format = format, |
| 547 | 2104x |
indent_mod = indent_mod, |
| 548 | 2104x |
na_str = na_str |
| 549 |
) |
|
| 550 | 2104x |
ctab |
| 551 |
} |
|
| 552 | ||
| 553 |
.make_analyzed_tab <- function(df, |
|
| 554 |
alt_df, |
|
| 555 |
alt_df_full, |
|
| 556 |
spl, |
|
| 557 |
cinfo, |
|
| 558 |
partlabel = "", |
|
| 559 |
dolab = TRUE, |
|
| 560 |
lvl, |
|
| 561 |
baselines, |
|
| 562 |
spl_context) {
|
|
| 563 | 1309x |
stopifnot(is(spl, "VAnalyzeSplit")) |
| 564 | 1309x |
check_validsplit(spl, df) |
| 565 | 1308x |
defrlabel <- spl@default_rowlabel |
| 566 | 1308x |
if (nchar(defrlabel) == 0 && !missing(partlabel) && nchar(partlabel) > 0) {
|
| 567 | ! |
defrlabel <- partlabel |
| 568 |
} |
|
| 569 | 1308x |
kids <- tryCatch( |
| 570 | 1308x |
.make_tablerows(df, |
| 571 | 1308x |
func = analysis_fun(spl), |
| 572 | 1308x |
defrowlabs = defrlabel, # XXX |
| 573 | 1308x |
cinfo = cinfo, |
| 574 | 1308x |
datcol = spl_payload(spl), |
| 575 | 1308x |
lev = lvl + 1L, |
| 576 | 1308x |
format = obj_format(spl), |
| 577 | 1308x |
splextra = split_exargs(spl), |
| 578 | 1308x |
baselines = baselines, |
| 579 | 1308x |
alt_dfpart = alt_df, |
| 580 | 1308x |
alt_df_full = alt_df_full, |
| 581 | 1308x |
inclNAs = avar_inclNAs(spl), |
| 582 | 1308x |
spl_context = spl_context |
| 583 |
), |
|
| 584 | 1308x |
error = function(e) e |
| 585 |
) |
|
| 586 | ||
| 587 |
# Adding section_div for DataRows (analyze leaves) |
|
| 588 |
# kids <- .set_kids_section_div(kids, spl_section_div(spl), "DataRow") |
|
| 589 | ||
| 590 | 1308x |
if (is(kids, "error")) {
|
| 591 | 3x |
stop("Error applying analysis function (var - ",
|
| 592 | 3x |
spl_payload(spl) %||% "colvars", "): ", kids$message, |
| 593 | 3x |
"\n\toccured at (row) path: ", |
| 594 | 3x |
spl_context_to_disp_path(spl_context), |
| 595 | 3x |
call. = FALSE |
| 596 |
) |
|
| 597 |
} |
|
| 598 | 1305x |
lab <- obj_label(spl) |
| 599 | 1305x |
ret <- TableTree( |
| 600 | 1305x |
kids = kids, |
| 601 | 1305x |
name = obj_name(spl), |
| 602 | 1305x |
label = lab, |
| 603 | 1305x |
lev = lvl, |
| 604 | 1305x |
cinfo = cinfo, |
| 605 | 1305x |
format = obj_format(spl), |
| 606 | 1305x |
na_str = obj_na_str(spl), |
| 607 | 1305x |
indent_mod = indent_mod(spl), |
| 608 | 1305x |
trailing_section_div = spl_section_div(spl) |
| 609 |
) |
|
| 610 | ||
| 611 | 1305x |
labelrow_visible(ret) <- dolab |
| 612 | 1305x |
ret |
| 613 |
} |
|
| 614 | ||
| 615 |
#' @param ... all arguments to `recurse_applysplit`, methods may only use some of them. |
|
| 616 |
#' @return A `list` of children to place at this level. |
|
| 617 |
#' |
|
| 618 |
#' @noRd |
|
| 619 |
setGeneric(".make_split_kids", function(spl, have_controws, make_lrow, ...) {
|
|
| 620 | 1942x |
standardGeneric(".make_split_kids")
|
| 621 |
}) |
|
| 622 | ||
| 623 |
## single AnalyzeSplit |
|
| 624 |
setMethod( |
|
| 625 |
".make_split_kids", "VAnalyzeSplit", |
|
| 626 |
function(spl, |
|
| 627 |
have_controws, ## unused here |
|
| 628 |
make_lrow, ## unused here |
|
| 629 |
..., |
|
| 630 |
df, |
|
| 631 |
alt_df, |
|
| 632 |
alt_df_full, |
|
| 633 |
lvl, |
|
| 634 |
name, |
|
| 635 |
cinfo, |
|
| 636 |
baselines, |
|
| 637 |
spl_context, |
|
| 638 |
nsibs = 0) {
|
|
| 639 | 1309x |
spvis <- labelrow_visible(spl) |
| 640 | 1309x |
if (is.na(spvis)) {
|
| 641 | 266x |
spvis <- nsibs > 0 |
| 642 |
} |
|
| 643 | ||
| 644 | 1309x |
ret <- .make_analyzed_tab( |
| 645 | 1309x |
df = df, |
| 646 | 1309x |
alt_df, |
| 647 | 1309x |
alt_df_full = alt_df_full, |
| 648 | 1309x |
spl = spl, |
| 649 | 1309x |
cinfo = cinfo, |
| 650 | 1309x |
lvl = lvl + 1L, |
| 651 | 1309x |
dolab = spvis, |
| 652 | 1309x |
partlabel = obj_label(spl), |
| 653 | 1309x |
baselines = baselines, |
| 654 | 1309x |
spl_context = spl_context |
| 655 |
) |
|
| 656 | 1305x |
indent_mod(ret) <- indent_mod(spl) |
| 657 | ||
| 658 | 1305x |
kids <- list(ret) |
| 659 | 1305x |
names(kids) <- obj_name(ret) |
| 660 | 1305x |
kids |
| 661 |
} |
|
| 662 |
) |
|
| 663 | ||
| 664 |
# Adding section_divisors to TableRow |
|
| 665 |
.set_kids_section_div <- function(lst, trailing_section_div_char, allowed_class = "VTableTree") {
|
|
| 666 | 605x |
if (!is.na(trailing_section_div_char)) {
|
| 667 | 24x |
lst <- lapply( |
| 668 | 24x |
lst, |
| 669 | 24x |
function(k) {
|
| 670 | 63x |
if (is(k, allowed_class)) {
|
| 671 | 63x |
trailing_section_div(k) <- trailing_section_div_char |
| 672 |
} |
|
| 673 | 63x |
k |
| 674 |
} |
|
| 675 |
) |
|
| 676 |
} |
|
| 677 | 605x |
lst |
| 678 |
} |
|
| 679 | ||
| 680 |
## 1 or more AnalyzeSplits |
|
| 681 |
setMethod( |
|
| 682 |
".make_split_kids", "AnalyzeMultiVars", |
|
| 683 |
function(spl, |
|
| 684 |
have_controws, |
|
| 685 |
make_lrow, ## used here |
|
| 686 |
spl_context, |
|
| 687 |
...) { ## all passed directly down to VAnalyzeSplit method
|
|
| 688 | 141x |
avspls <- spl_payload(spl) |
| 689 | ||
| 690 | 141x |
nspl <- length(avspls) |
| 691 | ||
| 692 | 141x |
kids <- unlist(lapply(avspls, |
| 693 | 141x |
.make_split_kids, |
| 694 | 141x |
nsibs = nspl - 1, |
| 695 | 141x |
have_controws = have_controws, |
| 696 | 141x |
make_lrow = make_lrow, |
| 697 | 141x |
spl_context = spl_context, |
| 698 |
... |
|
| 699 |
)) |
|
| 700 | ||
| 701 | 141x |
kids <- .set_kids_section_div(kids, spl_section_div(spl), "VTableTree") |
| 702 | ||
| 703 |
## XXX this seems like it should be identical not !identical |
|
| 704 |
## TODO FIXME |
|
| 705 | 141x |
if (!identical(make_lrow, FALSE) && !have_controws && length(kids) == 1) {
|
| 706 |
## we only analyzed one var so |
|
| 707 |
## we don't need an extra wrapper table |
|
| 708 |
## in the structure |
|
| 709 | ! |
stopifnot(identical( |
| 710 | ! |
obj_name(kids[[1]]), |
| 711 | ! |
spl_payload(spl) |
| 712 |
)) |
|
| 713 | ! |
return(kids[[1]]) |
| 714 |
} |
|
| 715 |
## this will be the variables |
|
| 716 |
## nms = sapply(spl_payload(spl), spl_payload) |
|
| 717 | ||
| 718 | 141x |
nms <- vapply(kids, obj_name, "") |
| 719 | 141x |
labs <- vapply(kids, obj_label, "") |
| 720 | 141x |
if (length(unique(nms)) != length(nms) && length(unique(nms)) != length(nms)) {
|
| 721 | 2x |
warning("Non-unique sibling analysis table names. Using Labels ",
|
| 722 | 2x |
"instead. Use the table_names argument to analyze to avoid ", |
| 723 | 2x |
"this when analyzing the same variable multiple times.", |
| 724 | 2x |
"\n\toccured at (row) path: ", |
| 725 | 2x |
spl_context_to_disp_path(spl_context), |
| 726 | 2x |
call. = FALSE |
| 727 |
) |
|
| 728 | 2x |
kids <- mapply(function(k, nm) {
|
| 729 | 4x |
obj_name(k) <- nm |
| 730 | 4x |
k |
| 731 | 2x |
}, k = kids, nm = labs, SIMPLIFY = FALSE) |
| 732 | 2x |
nms <- labs |
| 733 |
} |
|
| 734 | ||
| 735 | 141x |
nms[is.na(nms)] <- "" |
| 736 | ||
| 737 | 141x |
names(kids) <- nms |
| 738 | 141x |
kids |
| 739 |
} |
|
| 740 |
) |
|
| 741 | ||
| 742 |
setMethod( |
|
| 743 |
".make_split_kids", "Split", |
|
| 744 |
function(spl, |
|
| 745 |
have_controws, |
|
| 746 |
make_lrow, |
|
| 747 |
..., |
|
| 748 |
splvec, ## passed to recursive_applysplit |
|
| 749 |
df, ## used to apply split |
|
| 750 |
alt_df, ## used to apply split for alternative df |
|
| 751 |
alt_df_full, ## passed to recursive_applysplit |
|
| 752 |
lvl, ## used to calculate innerlev |
|
| 753 |
cinfo, ## used for sanity check |
|
| 754 |
baselines, ## used to calc new baselines |
|
| 755 |
spl_context) {
|
|
| 756 |
## do the core splitting of data into children for this split |
|
| 757 | 492x |
rawpart <- do_split(spl, df, spl_context = spl_context) |
| 758 | 479x |
dataspl <- rawpart[["datasplit"]] |
| 759 |
## these are SplitValue objects |
|
| 760 | 479x |
splvals <- rawpart[["values"]] |
| 761 | 479x |
partlabels <- rawpart[["labels"]] |
| 762 | 479x |
if (is.factor(partlabels)) {
|
| 763 | ! |
partlabels <- as.character(partlabels) |
| 764 |
} |
|
| 765 | 479x |
nms <- unlist(value_names(splvals)) |
| 766 | 479x |
if (is.factor(nms)) {
|
| 767 | ! |
nms <- as.character(nms) |
| 768 |
} |
|
| 769 | ||
| 770 |
## Get new baseline values |
|
| 771 |
## |
|
| 772 |
## XXX this is a lot of data churn, if it proves too slow |
|
| 773 |
## we can |
|
| 774 |
## a) check if any of the analyses (i.e. the afuns) need the baseline in this |
|
| 775 |
## splitvec and not do any of this if not, or |
|
| 776 |
## b) refactor row splitting to behave like column splitting |
|
| 777 |
## |
|
| 778 |
## (b) seems the better design but is a major reworking of the guts of how |
|
| 779 |
## rtables tabulation works |
|
| 780 |
## (a) will only help if analyses that use baseline |
|
| 781 |
## info are mixed with those who don't. |
|
| 782 | 479x |
newbl_raw <- lapply(baselines, function(dat) {
|
| 783 |
# If no ref_group is specified |
|
| 784 | 1652x |
if (is.null(dat)) {
|
| 785 | 1632x |
return(NULL) |
| 786 |
} |
|
| 787 | ||
| 788 |
## apply the same splitting on the |
|
| 789 | 20x |
bldataspl <- tryCatch(do_split(spl, dat, spl_context = spl_context)[["datasplit"]], |
| 790 | 20x |
error = function(e) e |
| 791 |
) |
|
| 792 | ||
| 793 |
# Error localization |
|
| 794 | 20x |
if (is(bldataspl, "error")) {
|
| 795 | ! |
stop("Following error encountered in splitting .ref_group (baselines): ",
|
| 796 | ! |
bldataspl$message, |
| 797 | ! |
call. = FALSE |
| 798 |
) |
|
| 799 |
} |
|
| 800 | ||
| 801 |
## we only keep the ones corresponding with actual data splits |
|
| 802 | 20x |
res <- lapply( |
| 803 | 20x |
names(dataspl), |
| 804 | 20x |
function(nm) {
|
| 805 | 52x |
if (nm %in% names(bldataspl)) {
|
| 806 | 52x |
bldataspl[[nm]] |
| 807 |
} else {
|
|
| 808 | ! |
dataspl[[1]][0, ] |
| 809 |
} |
|
| 810 |
} |
|
| 811 |
) |
|
| 812 | ||
| 813 | 20x |
names(res) <- names(dataspl) |
| 814 | 20x |
res |
| 815 |
}) |
|
| 816 | ||
| 817 | 479x |
newbaselines <- lapply(names(dataspl), function(nm) {
|
| 818 | 1409x |
lapply(newbl_raw, function(rawdat) {
|
| 819 | 4843x |
if (nm %in% names(rawdat)) {
|
| 820 | 52x |
rawdat[[nm]] |
| 821 |
} else {
|
|
| 822 | 4791x |
rawdat[[1]][0, ] |
| 823 |
} |
|
| 824 |
}) |
|
| 825 |
}) |
|
| 826 | ||
| 827 | 479x |
if (length(newbaselines) != length(dataspl)) {
|
| 828 | ! |
stop( |
| 829 | ! |
"Baselines (ref_group) after row split does not have", |
| 830 | ! |
" the same number of levels of input data split. ", |
| 831 | ! |
"Contact the maintainer." |
| 832 | ! |
) # nocov |
| 833 |
} |
|
| 834 | 479x |
if (!(length(newbaselines) == 0 || |
| 835 | 479x |
identical( |
| 836 | 479x |
unique(sapply(newbaselines, length)), |
| 837 | 479x |
length(col_exprs(cinfo)) |
| 838 |
))) {
|
|
| 839 | ! |
stop( |
| 840 | ! |
"Baselines (ref_group) do not have the same number of columns", |
| 841 | ! |
" in each split. Contact the maintainer." |
| 842 | ! |
) # nocov |
| 843 |
} |
|
| 844 | ||
| 845 |
# If params are not present do not do the calculation |
|
| 846 | 479x |
acdf_param <- check_afun_cfun_params( |
| 847 | 479x |
SplitVector(spl, splvec), |
| 848 | 479x |
c(".alt_df", ".alt_df_row")
|
| 849 |
) |
|
| 850 | ||
| 851 |
# Apply same split for alt_counts_df |
|
| 852 | 479x |
if (!is.null(alt_df) && any(acdf_param)) {
|
| 853 | 17x |
alt_dfpart <- tryCatch( |
| 854 | 17x |
do_split(spl, alt_df, |
| 855 | 17x |
spl_context = spl_context |
| 856 | 17x |
)[["datasplit"]], |
| 857 | 17x |
error = function(e) e |
| 858 |
) |
|
| 859 | ||
| 860 |
# Removing NA rows - to explore why this happens at all in a split |
|
| 861 |
# This would be a fix but it is done in post-processing instead of pre-proc -> xxx |
|
| 862 |
# x alt_dfpart <- lapply(alt_dfpart, function(data) {
|
|
| 863 |
# x data[!apply(is.na(data), 1, all), ] |
|
| 864 |
# x }) |
|
| 865 | ||
| 866 |
# Error localization |
|
| 867 | 17x |
if (is(alt_dfpart, "error")) {
|
| 868 | 2x |
stop("Following error encountered in splitting alt_counts_df: ",
|
| 869 | 2x |
alt_dfpart$message, |
| 870 | 2x |
call. = FALSE |
| 871 |
) |
|
| 872 |
} |
|
| 873 |
# Error if split does not have the same values in the alt_df (and order) |
|
| 874 |
# The following breaks if there are different levels (do_split returns empty list) |
|
| 875 |
# or if there are different number of the same levels. Added handling of NAs |
|
| 876 |
# in the values of the factor when is all only NAs |
|
| 877 | 15x |
is_all_na <- all(is.na(alt_df[[spl_payload(spl)]])) |
| 878 | ||
| 879 | 15x |
if (!all(names(dataspl) %in% names(alt_dfpart)) || length(alt_dfpart) != length(dataspl) || is_all_na) {
|
| 880 | 5x |
alt_df_spl_vals <- unique(alt_df[[spl_payload(spl)]]) |
| 881 | 5x |
end_part <- "" |
| 882 | ||
| 883 | 5x |
if (!all(alt_df_spl_vals %in% levels(alt_df_spl_vals))) {
|
| 884 | 2x |
end_part <- paste0( |
| 885 | 2x |
" and following levels: ", |
| 886 | 2x |
paste_vec(levels(alt_df_spl_vals)) |
| 887 |
) |
|
| 888 |
} |
|
| 889 | ||
| 890 | 5x |
if (is_all_na) {
|
| 891 | 2x |
end_part <- ". Found only NAs in alt_counts_df split" |
| 892 |
} |
|
| 893 | ||
| 894 | 5x |
stop( |
| 895 | 5x |
"alt_counts_df split variable(s) [", spl_payload(spl), |
| 896 | 5x |
"] (in split ", as.character(class(spl)), |
| 897 | 5x |
") does not have the same factor levels of df.\ndf has c(", '"',
|
| 898 | 5x |
paste(names(dataspl), collapse = '", "'), '"', ") levels while alt_counts_df has ", |
| 899 | 5x |
ifelse(length(alt_df_spl_vals) > 0, paste_vec(alt_df_spl_vals), ""), |
| 900 | 5x |
" unique values", end_part |
| 901 |
) |
|
| 902 |
} |
|
| 903 |
} else {
|
|
| 904 | 462x |
alt_dfpart <- setNames(rep(list(NULL), length(dataspl)), names(dataspl)) |
| 905 |
} |
|
| 906 | ||
| 907 | ||
| 908 | 472x |
innerlev <- lvl + (have_controws || is.na(make_lrow) || make_lrow) |
| 909 |
## do full recursive_applysplit on each part of the split defined by spl |
|
| 910 | 472x |
inner <- unlist(mapply( |
| 911 | 472x |
function(dfpart, alt_dfpart, nm, label, baselines, splval) {
|
| 912 | 1367x |
rsplval <- context_df_row( |
| 913 | 1367x |
split = obj_name(spl), |
| 914 | 1367x |
value = value_names(splval), |
| 915 | 1367x |
full_parent_df = list(dfpart), |
| 916 | 1367x |
cinfo = cinfo |
| 917 |
) |
|
| 918 | ||
| 919 |
## if(length(rsplval) > 0) |
|
| 920 |
## rsplval <- setNames(rsplval, obj_name(spl)) |
|
| 921 | 1367x |
recursive_applysplit( |
| 922 | 1367x |
df = dfpart, |
| 923 | 1367x |
alt_df = alt_dfpart, |
| 924 | 1367x |
alt_df_full = alt_df_full, |
| 925 | 1367x |
name = nm, |
| 926 | 1367x |
lvl = innerlev, |
| 927 | 1367x |
splvec = splvec, |
| 928 | 1367x |
cinfo = cinfo, |
| 929 | 1367x |
make_lrow = label_kids(spl), |
| 930 | 1367x |
parent_cfun = content_fun(spl), |
| 931 | 1367x |
cformat = content_format(spl), |
| 932 | 1367x |
cna_str = content_na_str(spl), |
| 933 | 1367x |
partlabel = label, |
| 934 | 1367x |
cindent_mod = content_indent_mod(spl), |
| 935 | 1367x |
cvar = content_var(spl), |
| 936 | 1367x |
baselines = baselines, |
| 937 | 1367x |
cextra_args = content_extra_args(spl), |
| 938 |
## splval should still be retaining its name |
|
| 939 | 1367x |
spl_context = rbind(spl_context, rsplval) |
| 940 |
) |
|
| 941 |
}, |
|
| 942 | 472x |
dfpart = dataspl, |
| 943 | 472x |
alt_dfpart = alt_dfpart, |
| 944 | 472x |
label = partlabels, |
| 945 | 472x |
nm = nms, |
| 946 | 472x |
baselines = newbaselines, |
| 947 | 472x |
splval = splvals, |
| 948 | 472x |
SIMPLIFY = FALSE |
| 949 |
)) |
|
| 950 | ||
| 951 |
# Setting the kids section separator if they inherits VTableTree |
|
| 952 | 464x |
inner <- .set_kids_section_div( |
| 953 | 464x |
inner, |
| 954 | 464x |
trailing_section_div_char = spl_section_div(spl), |
| 955 | 464x |
allowed_class = "VTableTree" |
| 956 |
) |
|
| 957 | ||
| 958 |
## This is where we need to build the structural tables |
|
| 959 |
## even if they are invisible because their labels are not |
|
| 960 |
## not shown. |
|
| 961 | 464x |
innertab <- TableTree( |
| 962 | 464x |
kids = inner, |
| 963 | 464x |
name = obj_name(spl), |
| 964 | 464x |
labelrow = LabelRow( |
| 965 | 464x |
label = obj_label(spl), |
| 966 | 464x |
vis = isTRUE(vis_label(spl)) |
| 967 |
), |
|
| 968 | 464x |
cinfo = cinfo, |
| 969 | 464x |
iscontent = FALSE, |
| 970 | 464x |
indent_mod = indent_mod(spl), |
| 971 | 464x |
page_title = ptitle_prefix(spl) |
| 972 |
) |
|
| 973 |
## kids = inner |
|
| 974 | 464x |
kids <- list(innertab) |
| 975 | 464x |
kids |
| 976 |
} |
|
| 977 |
) |
|
| 978 | ||
| 979 |
context_df_row <- function(split = character(), |
|
| 980 |
value = character(), |
|
| 981 |
full_parent_df = list(), |
|
| 982 |
cinfo = NULL) {
|
|
| 983 | 3262x |
ret <- data.frame( |
| 984 | 3262x |
split = split, |
| 985 | 3262x |
value = value, |
| 986 | 3262x |
full_parent_df = I(full_parent_df), |
| 987 |
# parent_cold_inds = I(parent_col_inds), |
|
| 988 | 3262x |
stringsAsFactors = FALSE |
| 989 |
) |
|
| 990 | 3262x |
if (nrow(ret) > 0) {
|
| 991 | 3249x |
ret$all_cols_n <- nrow(full_parent_df[[1]]) |
| 992 |
} else {
|
|
| 993 | 13x |
ret$all_cols_n <- integer() ## should this be numeric??? This never happens |
| 994 |
} |
|
| 995 | ||
| 996 | 3262x |
if (!is.null(cinfo)) {
|
| 997 | 1748x |
if (nrow(ret) > 0) {
|
| 998 | 1739x |
colcols <- as.data.frame(lapply(col_exprs(cinfo), function(e) {
|
| 999 | 5938x |
vals <- eval(e, envir = full_parent_df[[1]]) |
| 1000 | 5938x |
if (identical(vals, TRUE)) {
|
| 1001 | 719x |
vals <- rep(vals, length.out = nrow(full_parent_df[[1]])) |
| 1002 |
} |
|
| 1003 | 5938x |
I(list(vals)) |
| 1004 |
})) |
|
| 1005 |
} else {
|
|
| 1006 | 9x |
colcols <- as.data.frame(rep(list(logical()), ncol(cinfo))) |
| 1007 |
} |
|
| 1008 | 1748x |
names(colcols) <- names(col_exprs(cinfo)) |
| 1009 | 1748x |
ret <- cbind(ret, colcols) |
| 1010 |
} |
|
| 1011 | 3262x |
ret |
| 1012 |
} |
|
| 1013 | ||
| 1014 |
recursive_applysplit <- function(df, |
|
| 1015 |
lvl = 0L, |
|
| 1016 |
alt_df, |
|
| 1017 |
alt_df_full, |
|
| 1018 |
splvec, |
|
| 1019 |
name, |
|
| 1020 |
# label, |
|
| 1021 |
make_lrow = NA, |
|
| 1022 |
partlabel = "", |
|
| 1023 |
cinfo, |
|
| 1024 |
parent_cfun = NULL, |
|
| 1025 |
cformat = NULL, |
|
| 1026 |
cna_str = NA_character_, |
|
| 1027 |
cindent_mod = 0L, |
|
| 1028 |
cextra_args = list(), |
|
| 1029 |
cvar = NULL, |
|
| 1030 |
baselines = lapply( |
|
| 1031 |
col_extra_args(cinfo), |
|
| 1032 |
function(x) x$.ref_full |
|
| 1033 |
), |
|
| 1034 |
spl_context = context_df_row(cinfo = cinfo), |
|
| 1035 |
no_outer_tbl = FALSE, |
|
| 1036 |
parent_sect_split = NA_character_) {
|
|
| 1037 |
## pre-existing table was added to the layout |
|
| 1038 | 1748x |
if (length(splvec) == 1L && is(splvec[[1]], "VTableNodeInfo")) {
|
| 1039 | 1x |
return(splvec[[1]]) |
| 1040 |
} |
|
| 1041 | ||
| 1042 |
## the content function is the one from the PREVIOUS |
|
| 1043 |
## split, i.e. the one whose children we are now constructing |
|
| 1044 |
## this is a bit annoying but makes the semantics for |
|
| 1045 |
## declaring layouts much more sane. |
|
| 1046 | 1747x |
ctab <- .make_ctab(df, |
| 1047 | 1747x |
lvl = lvl, |
| 1048 | 1747x |
name = name, |
| 1049 | 1747x |
label = partlabel, |
| 1050 | 1747x |
cinfo = cinfo, |
| 1051 | 1747x |
parent_cfun = parent_cfun, |
| 1052 | 1747x |
format = cformat, |
| 1053 | 1747x |
na_str = cna_str, |
| 1054 | 1747x |
indent_mod = cindent_mod, |
| 1055 | 1747x |
cvar = cvar, |
| 1056 | 1747x |
alt_df = alt_df, |
| 1057 | 1747x |
alt_df_full = alt_df_full, |
| 1058 | 1747x |
extra_args = cextra_args, |
| 1059 | 1747x |
spl_context = spl_context |
| 1060 |
) |
|
| 1061 | ||
| 1062 | 1746x |
nonroot <- lvl != 0L |
| 1063 | ||
| 1064 | 1746x |
if (is.na(make_lrow)) {
|
| 1065 | 1415x |
make_lrow <- if (nrow(ctab) > 0 || !nzchar(partlabel)) FALSE else TRUE |
| 1066 |
} |
|
| 1067 |
## never print an empty row label for root. |
|
| 1068 | 1746x |
if (make_lrow && partlabel == "" && !nonroot) {
|
| 1069 | 7x |
make_lrow <- FALSE |
| 1070 |
} |
|
| 1071 | ||
| 1072 | 1746x |
if (length(splvec) == 0L) {
|
| 1073 | 99x |
kids <- list() |
| 1074 | 99x |
imod <- 0L |
| 1075 | 99x |
spl <- NULL |
| 1076 |
} else {
|
|
| 1077 | 1647x |
spl <- splvec[[1]] |
| 1078 | 1647x |
splvec <- splvec[-1] |
| 1079 | ||
| 1080 |
## we pass this everything recursive_applysplit received and |
|
| 1081 |
## it all gets passed around through ... as needed |
|
| 1082 |
## to the various methods of .make_split_kids |
|
| 1083 | 1647x |
kids <- .make_split_kids( |
| 1084 | 1647x |
spl = spl, |
| 1085 | 1647x |
df = df, |
| 1086 | 1647x |
alt_df = alt_df, |
| 1087 | 1647x |
alt_df_full = alt_df_full, |
| 1088 | 1647x |
lvl = lvl, |
| 1089 | 1647x |
splvec = splvec, |
| 1090 | 1647x |
name = name, |
| 1091 | 1647x |
make_lrow = make_lrow, |
| 1092 | 1647x |
partlabel = partlabel, |
| 1093 | 1647x |
cinfo = cinfo, |
| 1094 | 1647x |
parent_cfun = parent_cfun, |
| 1095 | 1647x |
cformat = cformat, |
| 1096 | 1647x |
cindent_mod = cindent_mod, |
| 1097 | 1647x |
cextra_args = cextra_args, cvar = cvar, |
| 1098 | 1647x |
baselines = baselines, |
| 1099 | 1647x |
spl_context = spl_context, |
| 1100 | 1647x |
have_controws = nrow(ctab) > 0 |
| 1101 |
) |
|
| 1102 | 1615x |
imod <- 0L |
| 1103 |
} ## end length(splvec) |
|
| 1104 | ||
| 1105 | 1714x |
if (is.na(make_lrow)) {
|
| 1106 | ! |
make_lrow <- if (nrow(ctab) > 0 || !nzchar(partlabel)) FALSE else TRUE |
| 1107 |
} |
|
| 1108 |
## never print an empty row label for root. |
|
| 1109 | 1714x |
if (make_lrow && partlabel == "" && !nonroot) {
|
| 1110 | ! |
make_lrow <- FALSE |
| 1111 |
} |
|
| 1112 | ||
| 1113 |
## this is only true when called from build_table and the first split |
|
| 1114 |
## in (one of the) SplitVector is NOT an AnalyzeMultiVars split. |
|
| 1115 |
## in that case we would be "double creating" the structural |
|
| 1116 |
## subtable |
|
| 1117 | 1714x |
if (no_outer_tbl) {
|
| 1118 | 321x |
ret <- kids[[1]] |
| 1119 | 321x |
indent_mod(ret) <- indent_mod(spl) |
| 1120 | 1393x |
} else if (nrow(ctab) > 0L || length(kids) > 0L) {
|
| 1121 |
## previously we checked if the child had an identical label |
|
| 1122 |
## but I don't think thats needed anymore. |
|
| 1123 | 1393x |
tlabel <- partlabel |
| 1124 | 1393x |
ret <- TableTree( |
| 1125 | 1393x |
cont = ctab, |
| 1126 | 1393x |
kids = kids, |
| 1127 | 1393x |
name = name, |
| 1128 | 1393x |
label = tlabel, # partlabel, |
| 1129 | 1393x |
lev = lvl, |
| 1130 | 1393x |
iscontent = FALSE, |
| 1131 | 1393x |
labelrow = LabelRow( |
| 1132 | 1393x |
lev = lvl, |
| 1133 | 1393x |
label = tlabel, |
| 1134 | 1393x |
cinfo = cinfo, |
| 1135 | 1393x |
vis = make_lrow |
| 1136 |
), |
|
| 1137 | 1393x |
cinfo = cinfo, |
| 1138 | 1393x |
indent_mod = imod |
| 1139 |
) |
|
| 1140 |
} else {
|
|
| 1141 | ! |
ret <- NULL |
| 1142 |
} |
|
| 1143 | ||
| 1144 |
## if(!is.null(spl) && !is.na(spl_section_sep(spl))) |
|
| 1145 |
## ret <- apply_kids_section_sep(ret, spl_section_sep(spl)) |
|
| 1146 |
## ## message(sprintf("indent modifier: %d", indentmod))
|
|
| 1147 |
## if(!is.null(ret)) |
|
| 1148 |
## indent_mod(ret) = indentmod |
|
| 1149 | 1714x |
ret |
| 1150 |
} |
|
| 1151 | ||
| 1152 |
#' Create a table from a layout and data |
|
| 1153 |
#' |
|
| 1154 |
#' Layouts are used to describe a table pre-data. `build_table` is used to create a table |
|
| 1155 |
#' using a layout and a dataset. |
|
| 1156 |
#' |
|
| 1157 |
#' @inheritParams gen_args |
|
| 1158 |
#' @inheritParams lyt_args |
|
| 1159 |
#' @param col_counts (`numeric` or `NULL`)\cr `r lifecycle::badge("deprecated")` if non-`NULL`, column counts
|
|
| 1160 |
#' *for leaf-columns only* which override those calculated automatically during tabulation. Must specify |
|
| 1161 |
#' "counts" for *all* leaf-columns if non-`NULL`. `NA` elements will be replaced with the automatically |
|
| 1162 |
#' calculated counts. Turns on display of leaf-column counts when non-`NULL`. |
|
| 1163 |
#' @param col_total (`integer(1)`)\cr the total observations across all columns. Defaults to `nrow(df)`. |
|
| 1164 |
#' @param ... ignored. |
|
| 1165 |
#' |
|
| 1166 |
#' @details |
|
| 1167 |
#' When `alt_counts_df` is specified, column counts are calculated by applying the exact column subsetting |
|
| 1168 |
#' expressions determined when applying column splitting to the main data (`df`) to `alt_counts_df` and |
|
| 1169 |
#' counting the observations in each resulting subset. |
|
| 1170 |
#' |
|
| 1171 |
#' In particular, this means that in the case of splitting based on cuts of the data, any dynamic cuts will have |
|
| 1172 |
#' been calculated based on `df` and simply re-used for the count calculation. |
|
| 1173 |
#' |
|
| 1174 |
#' @note |
|
| 1175 |
#' When overriding the column counts or totals care must be taken that, e.g., `length()` or `nrow()` are not called |
|
| 1176 |
#' within tabulation functions, because those will NOT give the overridden counts. Writing/using tabulation |
|
| 1177 |
#' functions which accept `.N_col` and `.N_total` or do not rely on column counts at all (even implicitly) is the |
|
| 1178 |
#' only way to ensure overridden counts are fully respected. |
|
| 1179 |
#' |
|
| 1180 |
#' @return A `TableTree` or `ElementaryTable` object representing the table created by performing the tabulations |
|
| 1181 |
#' declared in `lyt` to the data `df`. |
|
| 1182 |
#' |
|
| 1183 |
#' @examples |
|
| 1184 |
#' lyt <- basic_table() %>% |
|
| 1185 |
#' split_cols_by("Species") %>%
|
|
| 1186 |
#' analyze("Sepal.Length", afun = function(x) {
|
|
| 1187 |
#' list( |
|
| 1188 |
#' "mean (sd)" = rcell(c(mean(x), sd(x)), format = "xx.xx (xx.xx)"), |
|
| 1189 |
#' "range" = diff(range(x)) |
|
| 1190 |
#' ) |
|
| 1191 |
#' }) |
|
| 1192 |
#' lyt |
|
| 1193 |
#' |
|
| 1194 |
#' tbl <- build_table(lyt, iris) |
|
| 1195 |
#' tbl |
|
| 1196 |
#' |
|
| 1197 |
#' # analyze multiple variables |
|
| 1198 |
#' lyt2 <- basic_table() %>% |
|
| 1199 |
#' split_cols_by("Species") %>%
|
|
| 1200 |
#' analyze(c("Sepal.Length", "Petal.Width"), afun = function(x) {
|
|
| 1201 |
#' list( |
|
| 1202 |
#' "mean (sd)" = rcell(c(mean(x), sd(x)), format = "xx.xx (xx.xx)"), |
|
| 1203 |
#' "range" = diff(range(x)) |
|
| 1204 |
#' ) |
|
| 1205 |
#' }) |
|
| 1206 |
#' |
|
| 1207 |
#' tbl2 <- build_table(lyt2, iris) |
|
| 1208 |
#' tbl2 |
|
| 1209 |
#' |
|
| 1210 |
#' # an example more relevant for clinical trials with column counts |
|
| 1211 |
#' lyt3 <- basic_table(show_colcounts = TRUE) %>% |
|
| 1212 |
#' split_cols_by("ARM") %>%
|
|
| 1213 |
#' analyze("AGE", afun = function(x) {
|
|
| 1214 |
#' setNames(as.list(fivenum(x)), c( |
|
| 1215 |
#' "minimum", "lower-hinge", "median", |
|
| 1216 |
#' "upper-hinge", "maximum" |
|
| 1217 |
#' )) |
|
| 1218 |
#' }) |
|
| 1219 |
#' |
|
| 1220 |
#' tbl3 <- build_table(lyt3, DM) |
|
| 1221 |
#' tbl3 |
|
| 1222 |
#' |
|
| 1223 |
#' tbl4 <- build_table(lyt3, subset(DM, AGE > 40)) |
|
| 1224 |
#' tbl4 |
|
| 1225 |
#' |
|
| 1226 |
#' # with column counts calculated based on different data |
|
| 1227 |
#' miniDM <- DM[sample(1:NROW(DM), 100), ] |
|
| 1228 |
#' tbl5 <- build_table(lyt3, DM, alt_counts_df = miniDM) |
|
| 1229 |
#' tbl5 |
|
| 1230 |
#' |
|
| 1231 |
#' tbl6 <- build_table(lyt3, DM, col_counts = 1:3) |
|
| 1232 |
#' tbl6 |
|
| 1233 |
#' |
|
| 1234 |
#' @author Gabriel Becker |
|
| 1235 |
#' @export |
|
| 1236 |
build_table <- function(lyt, df, |
|
| 1237 |
alt_counts_df = NULL, |
|
| 1238 |
col_counts = NULL, |
|
| 1239 |
col_total = if (is.null(alt_counts_df)) nrow(df) else nrow(alt_counts_df), |
|
| 1240 |
topleft = NULL, |
|
| 1241 |
hsep = default_hsep(), |
|
| 1242 |
...) {
|
|
| 1243 | 378x |
if (!is(lyt, "PreDataTableLayouts")) {
|
| 1244 | ! |
stop( |
| 1245 | ! |
"lyt must be a PreDataTableLayouts object. Got object of class ", |
| 1246 | ! |
class(lyt) |
| 1247 |
) |
|
| 1248 |
} |
|
| 1249 | ||
| 1250 |
## if no columns are defined (e.g. because lyt is NULL) |
|
| 1251 |
## add a single overall column as the "most basic" |
|
| 1252 |
## table column structure that makes sense |
|
| 1253 | 378x |
clyt <- clayout(lyt) |
| 1254 | 378x |
if (length(clyt) == 1 && length(clyt[[1]]) == 0) {
|
| 1255 | 132x |
clyt[[1]] <- add_overall_col(clyt[[1]], "") |
| 1256 | 132x |
clayout(lyt) <- clyt |
| 1257 |
} |
|
| 1258 | ||
| 1259 |
## do checks and defensive programming now that we have the data |
|
| 1260 | 378x |
lyt <- fix_dyncuts(lyt, df) |
| 1261 | 378x |
lyt <- set_def_child_ord(lyt, df) |
| 1262 | 377x |
lyt <- fix_analyze_vis(lyt) |
| 1263 | 377x |
df <- fix_split_vars(lyt, df, char_ok = is.null(col_counts)) |
| 1264 | 368x |
alt_params <- check_afun_cfun_params(lyt, c(".alt_df", ".alt_df_row"))
|
| 1265 | 368x |
if (any(alt_params) && is.null(alt_counts_df)) {
|
| 1266 | 2x |
stop( |
| 1267 | 2x |
"Layout contains afun/cfun functions that have optional parameters ", |
| 1268 | 2x |
".alt_df and/or .alt_df_row, but no alt_counts_df was provided in ", |
| 1269 | 2x |
"build_table()." |
| 1270 |
) |
|
| 1271 |
} |
|
| 1272 | ||
| 1273 | 366x |
rtpos <- TreePos() |
| 1274 | 366x |
cinfo <- create_colinfo(lyt, df, rtpos, |
| 1275 | 366x |
counts = col_counts, |
| 1276 | 366x |
alt_counts_df = alt_counts_df, |
| 1277 | 366x |
total = col_total, |
| 1278 | 366x |
topleft |
| 1279 |
) |
|
| 1280 | 358x |
if (!is.null(col_counts)) {
|
| 1281 | 3x |
toreplace <- !is.na(col_counts) |
| 1282 | 3x |
newccs <- col_counts(cinfo) ## old actual counts |
| 1283 | 3x |
newccs[toreplace] <- col_counts[toreplace] |
| 1284 | 3x |
col_counts(cinfo) <- newccs |
| 1285 | 3x |
leaf_paths <- col_paths(cinfo) |
| 1286 | 3x |
for (pth in leaf_paths) {
|
| 1287 | 21x |
colcount_visible(cinfo, pth) <- TRUE |
| 1288 |
} |
|
| 1289 |
} |
|
| 1290 | 358x |
rlyt <- rlayout(lyt) |
| 1291 | 358x |
rtspl <- root_spl(rlyt) |
| 1292 | 358x |
ctab <- .make_ctab(df, 0L, |
| 1293 | 358x |
alt_df = NULL, |
| 1294 | 358x |
alt_df_full = alt_counts_df, |
| 1295 | 358x |
name = "root", |
| 1296 | 358x |
label = "", |
| 1297 | 358x |
cinfo = cinfo, ## cexprs, ctree, |
| 1298 | 358x |
parent_cfun = content_fun(rtspl), |
| 1299 | 358x |
format = content_format(rtspl), |
| 1300 | 358x |
na_str = content_na_str(rtspl), |
| 1301 | 358x |
indent_mod = 0L, |
| 1302 | 358x |
cvar = content_var(rtspl), |
| 1303 | 358x |
extra_args = content_extra_args(rtspl) |
| 1304 |
) |
|
| 1305 | ||
| 1306 | 358x |
kids <- lapply(seq_along(rlyt), function(i) {
|
| 1307 | 395x |
splvec <- rlyt[[i]] |
| 1308 | 395x |
if (length(splvec) == 0) {
|
| 1309 | 14x |
return(NULL) |
| 1310 |
} |
|
| 1311 | 381x |
firstspl <- splvec[[1]] |
| 1312 | 381x |
nm <- obj_name(firstspl) |
| 1313 |
## XXX unused, probably shouldn't be? |
|
| 1314 |
## this seems to be covered by grabbing the partlabel |
|
| 1315 |
## TODO confirm this |
|
| 1316 |
## lab <- obj_label(firstspl) |
|
| 1317 | 381x |
recursive_applysplit( |
| 1318 | 381x |
df = df, lvl = 0L, |
| 1319 | 381x |
alt_df = alt_counts_df, |
| 1320 | 381x |
alt_df_full = alt_counts_df, |
| 1321 | 381x |
name = nm, |
| 1322 | 381x |
splvec = splvec, |
| 1323 | 381x |
cinfo = cinfo, |
| 1324 |
## XXX are these ALWAYS right? |
|
| 1325 | 381x |
make_lrow = label_kids(firstspl), |
| 1326 | 381x |
parent_cfun = NULL, |
| 1327 | 381x |
cformat = content_format(firstspl), |
| 1328 | 381x |
cna_str = content_na_str(firstspl), |
| 1329 | 381x |
cvar = content_var(firstspl), |
| 1330 | 381x |
cextra_args = content_extra_args(firstspl), |
| 1331 | 381x |
spl_context = context_df_row( |
| 1332 | 381x |
split = "root", value = "root", |
| 1333 | 381x |
full_parent_df = list(df), |
| 1334 | 381x |
cinfo = cinfo |
| 1335 |
), |
|
| 1336 |
## we DO want the 'outer table' if the first |
|
| 1337 |
## one is a multi-analyze |
|
| 1338 | 381x |
no_outer_tbl = !is(firstspl, "AnalyzeMultiVars") |
| 1339 |
) |
|
| 1340 |
}) |
|
| 1341 | 333x |
kids <- kids[!sapply(kids, is.null)] |
| 1342 | 319x |
if (length(kids) > 0) names(kids) <- sapply(kids, obj_name) |
| 1343 | ||
| 1344 |
# top level divisor |
|
| 1345 | 333x |
if (!is.na(top_level_section_div(lyt))) {
|
| 1346 | 2x |
kids <- lapply(kids, function(first_level_kids) {
|
| 1347 | 4x |
trailing_section_div(first_level_kids) <- top_level_section_div(lyt) |
| 1348 | 4x |
first_level_kids |
| 1349 |
}) |
|
| 1350 |
} |
|
| 1351 | ||
| 1352 | 333x |
if (nrow(ctab) == 0L && length(kids) == 1L && is(kids[[1]], "VTableTree")) {
|
| 1353 | 279x |
tab <- kids[[1]] |
| 1354 | 279x |
main_title(tab) <- main_title(lyt) |
| 1355 | 279x |
subtitles(tab) <- subtitles(lyt) |
| 1356 | 279x |
main_footer(tab) <- main_footer(lyt) |
| 1357 | 279x |
prov_footer(tab) <- prov_footer(lyt) |
| 1358 | 279x |
header_section_div(tab) <- header_section_div(lyt) |
| 1359 |
} else {
|
|
| 1360 | 54x |
tab <- TableTree( |
| 1361 | 54x |
cont = ctab, |
| 1362 | 54x |
kids = kids, |
| 1363 | 54x |
lev = 0L, |
| 1364 | 54x |
name = "root", |
| 1365 | 54x |
label = "", |
| 1366 | 54x |
iscontent = FALSE, |
| 1367 | 54x |
cinfo = cinfo, |
| 1368 | 54x |
format = obj_format(rtspl), |
| 1369 | 54x |
na_str = obj_na_str(rtspl), |
| 1370 | 54x |
title = main_title(lyt), |
| 1371 | 54x |
subtitles = subtitles(lyt), |
| 1372 | 54x |
main_footer = main_footer(lyt), |
| 1373 | 54x |
prov_footer = prov_footer(lyt), |
| 1374 | 54x |
header_section_div = header_section_div(lyt) |
| 1375 |
) |
|
| 1376 |
} |
|
| 1377 | ||
| 1378 |
## This seems to be unneeded, not clear what 'top_left' check it refers to |
|
| 1379 |
## but both top_left taller than column headers and very long topleft are now |
|
| 1380 |
## allowed, so this is just wasted computation. |
|
| 1381 | ||
| 1382 |
## ## this is where the top_left check lives right now. refactor later maybe |
|
| 1383 |
## ## but now just call it so the error gets thrown when I want it to |
|
| 1384 |
## unused <- matrix_form(tab) |
|
| 1385 | 333x |
tab <- update_ref_indexing(tab) |
| 1386 | 333x |
horizontal_sep(tab) <- hsep |
| 1387 | 333x |
if (table_inset(lyt) > 0) {
|
| 1388 | 1x |
table_inset(tab) <- table_inset(lyt) |
| 1389 |
} |
|
| 1390 | 333x |
tab |
| 1391 |
} |
|
| 1392 | ||
| 1393 |
# fix_split_vars ---- |
|
| 1394 |
# These checks guarantee that all the split variables are present in the data. |
|
| 1395 |
# No generic is needed because it is not dependent on the input layout but |
|
| 1396 |
# on the df. |
|
| 1397 |
fix_one_split_var <- function(spl, df, char_ok = TRUE) {
|
|
| 1398 | 609x |
var <- spl_payload(spl) |
| 1399 | 609x |
if (!(var %in% names(df))) {
|
| 1400 | 2x |
stop("Split variable [", var, "] not found in data being tabulated.")
|
| 1401 |
} |
|
| 1402 | 607x |
varvec <- df[[var]] |
| 1403 | 607x |
if (!is(varvec, "character") && !is.factor(varvec)) {
|
| 1404 | 1x |
message(sprintf( |
| 1405 | 1x |
paste( |
| 1406 | 1x |
"Split var [%s] was not character or factor.", |
| 1407 | 1x |
"Converting to factor" |
| 1408 |
), |
|
| 1409 | 1x |
var |
| 1410 |
)) |
|
| 1411 | 1x |
varvec <- factor(varvec) |
| 1412 | 1x |
df[[var]] <- varvec |
| 1413 | 606x |
} else if (is(varvec, "character") && !char_ok) {
|
| 1414 | 1x |
stop( |
| 1415 | 1x |
"Overriding column counts is not supported when splitting on ", |
| 1416 | 1x |
"character variables.\n Please convert all column split variables to ", |
| 1417 | 1x |
"factors." |
| 1418 |
) |
|
| 1419 |
} |
|
| 1420 | ||
| 1421 | 606x |
if (is.factor(varvec)) {
|
| 1422 | 439x |
levs <- levels(varvec) |
| 1423 |
} else {
|
|
| 1424 | 167x |
levs <- unique(varvec) |
| 1425 |
} |
|
| 1426 | 606x |
if (!all(nzchar(levs))) {
|
| 1427 | 4x |
stop( |
| 1428 | 4x |
"Got empty string level in splitting variable ", var, |
| 1429 | 4x |
" This is not supported.\nIf display as an empty level is ", |
| 1430 | 4x |
"desired use a value-labeling variable." |
| 1431 |
) |
|
| 1432 |
} |
|
| 1433 | ||
| 1434 |
## handle label var |
|
| 1435 | 602x |
lblvar <- spl_label_var(spl) |
| 1436 | 602x |
have_lblvar <- !identical(var, lblvar) |
| 1437 | 602x |
if (have_lblvar) {
|
| 1438 | 88x |
if (!(lblvar %in% names(df))) {
|
| 1439 | 1x |
stop( |
| 1440 | 1x |
"Value label variable [", lblvar, |
| 1441 | 1x |
"] not found in data being tabulated." |
| 1442 |
) |
|
| 1443 |
} |
|
| 1444 | 87x |
lblvec <- df[[lblvar]] |
| 1445 | 87x |
tab <- table(varvec, lblvec) |
| 1446 | ||
| 1447 | 87x |
if (any(rowSums(tab > 0) > 1) || any(colSums(tab > 0) > 1)) {
|
| 1448 | 1x |
stop(sprintf( |
| 1449 | 1x |
paste( |
| 1450 | 1x |
"There does not appear to be a 1-1", |
| 1451 | 1x |
"correspondence between values in split var", |
| 1452 | 1x |
"[%s] and label var [%s]" |
| 1453 |
), |
|
| 1454 | 1x |
var, lblvar |
| 1455 |
)) |
|
| 1456 |
} |
|
| 1457 | ||
| 1458 | 86x |
if (!is(lblvec, "character") && !is.factor(lblvec)) {
|
| 1459 | ! |
message(sprintf( |
| 1460 | ! |
paste( |
| 1461 | ! |
"Split label var [%s] was not character or", |
| 1462 | ! |
"factor. Converting to factor" |
| 1463 |
), |
|
| 1464 | ! |
var |
| 1465 |
)) |
|
| 1466 | ! |
lblvec <- factor(lblvec) |
| 1467 | ! |
df[[lblvar]] <- lblvec |
| 1468 |
} |
|
| 1469 |
} |
|
| 1470 | ||
| 1471 | 600x |
df |
| 1472 |
} |
|
| 1473 | ||
| 1474 |
fix_split_vars <- function(lyt, df, char_ok) {
|
|
| 1475 | 377x |
df <- fix_split_vars_inner(clayout(lyt), df, char_ok = char_ok) |
| 1476 | 373x |
df <- fix_split_vars_inner(rlayout(lyt), df, char_ok = TRUE) |
| 1477 | 368x |
df |
| 1478 | ||
| 1479 |
## clyt <- clayout(lyt) |
|
| 1480 |
## rlyt <- rlayout(lyt) |
|
| 1481 | ||
| 1482 |
## allspls <- unlist(list(clyt, rlyt)) |
|
| 1483 |
## VarLevelSplit includes sublclass VarLevWBaselineSplit |
|
| 1484 |
} |
|
| 1485 | ||
| 1486 |
fix_split_vars_inner <- function(lyt, df, char_ok) {
|
|
| 1487 | 750x |
stopifnot(is(lyt, "PreDataAxisLayout")) |
| 1488 | 750x |
allspls <- unlist(lyt) |
| 1489 | 750x |
varspls <- allspls[sapply(allspls, is, "VarLevelSplit")] |
| 1490 | 750x |
unqvarinds <- !duplicated(sapply(varspls, spl_payload)) |
| 1491 | 750x |
unqvarspls <- varspls[unqvarinds] |
| 1492 | 609x |
for (spl in unqvarspls) df <- fix_one_split_var(spl, df, char_ok = char_ok) |
| 1493 | ||
| 1494 | 741x |
df |
| 1495 |
} |
|
| 1496 | ||
| 1497 |
# set_def_child_ord ---- |
|
| 1498 |
## the table is built by recursively splitting the data and doing things to each |
|
| 1499 |
## piece. The order (or even values) of unique(df[[col]]) is not guaranteed to |
|
| 1500 |
## be the same in all the different partitions. This addresses that. |
|
| 1501 |
setGeneric( |
|
| 1502 |
"set_def_child_ord", |
|
| 1503 | 4322x |
function(lyt, df) standardGeneric("set_def_child_ord")
|
| 1504 |
) |
|
| 1505 | ||
| 1506 |
setMethod( |
|
| 1507 |
"set_def_child_ord", "PreDataTableLayouts", |
|
| 1508 |
function(lyt, df) {
|
|
| 1509 | 378x |
clayout(lyt) <- set_def_child_ord(clayout(lyt), df) |
| 1510 | 377x |
rlayout(lyt) <- set_def_child_ord(rlayout(lyt), df) |
| 1511 | 377x |
lyt |
| 1512 |
} |
|
| 1513 |
) |
|
| 1514 | ||
| 1515 |
setMethod( |
|
| 1516 |
"set_def_child_ord", "PreDataAxisLayout", |
|
| 1517 |
function(lyt, df) {
|
|
| 1518 | 1123x |
lyt@.Data <- lapply(lyt, set_def_child_ord, df = df) |
| 1519 | 1122x |
lyt |
| 1520 |
} |
|
| 1521 |
) |
|
| 1522 | ||
| 1523 |
setMethod( |
|
| 1524 |
"set_def_child_ord", "SplitVector", |
|
| 1525 |
function(lyt, df) {
|
|
| 1526 | 1179x |
lyt[] <- lapply(lyt, set_def_child_ord, df = df) |
| 1527 | 1178x |
lyt |
| 1528 |
} |
|
| 1529 |
) |
|
| 1530 | ||
| 1531 |
## for most split types, don't do anything |
|
| 1532 |
## becuause their ordering already isn't data-based |
|
| 1533 |
setMethod( |
|
| 1534 |
"set_def_child_ord", "ANY", |
|
| 1535 | 723x |
function(lyt, df) lyt |
| 1536 |
) |
|
| 1537 | ||
| 1538 |
setMethod( |
|
| 1539 |
"set_def_child_ord", "VarLevelSplit", |
|
| 1540 |
function(lyt, df) {
|
|
| 1541 | 902x |
if (!is.null(spl_child_order(lyt))) {
|
| 1542 | 288x |
return(lyt) |
| 1543 |
} |
|
| 1544 | ||
| 1545 | 614x |
vec <- df[[spl_payload(lyt)]] |
| 1546 | 614x |
vals <- if (is.factor(vec)) {
|
| 1547 | 443x |
levels(vec) |
| 1548 |
} else {
|
|
| 1549 | 171x |
unique(vec) |
| 1550 |
} |
|
| 1551 | 614x |
spl_child_order(lyt) <- vals |
| 1552 | 614x |
lyt |
| 1553 |
} |
|
| 1554 |
) |
|
| 1555 | ||
| 1556 |
setMethod( |
|
| 1557 |
"set_def_child_ord", "VarLevWBaselineSplit", |
|
| 1558 |
function(lyt, df) {
|
|
| 1559 | 17x |
bline <- spl_ref_group(lyt) |
| 1560 | 17x |
if (!is.null(spl_child_order(lyt)) && match(bline, spl_child_order(lyt), nomatch = -1) == 1L) {
|
| 1561 | 6x |
return(lyt) |
| 1562 |
} |
|
| 1563 | ||
| 1564 | 11x |
if (!is.null(split_fun(lyt))) {
|
| 1565 |
## expensive but sadly necessary, I think |
|
| 1566 | 3x |
pinfo <- do_split(lyt, df, spl_context = context_df_row()) |
| 1567 | 3x |
vals <- sort(unlist(value_names(pinfo$values))) |
| 1568 |
} else {
|
|
| 1569 | 8x |
vec <- df[[spl_payload(lyt)]] |
| 1570 | 8x |
vals <- if (is.factor(vec)) {
|
| 1571 | 5x |
levels(vec) |
| 1572 |
} else {
|
|
| 1573 | 3x |
unique(vec) |
| 1574 |
} |
|
| 1575 |
} |
|
| 1576 | 11x |
if (!bline %in% vals) {
|
| 1577 | 1x |
stop(paste0( |
| 1578 | 1x |
'Reference group "', bline, '"', " was not present in the levels of ", spl_payload(lyt), " in the data." |
| 1579 |
)) |
|
| 1580 |
} |
|
| 1581 | 10x |
spl_child_order(lyt) <- vals |
| 1582 | 10x |
lyt |
| 1583 |
} |
|
| 1584 |
) |
|
| 1585 | ||
| 1586 |
splitvec_to_coltree <- function(df, splvec, pos = NULL, |
|
| 1587 |
lvl = 1L, label = "", |
|
| 1588 |
spl_context = context_df_row(cinfo = NULL), |
|
| 1589 |
alt_counts_df = df, |
|
| 1590 |
global_cc_format) {
|
|
| 1591 | 1924x |
stopifnot( |
| 1592 | 1924x |
lvl <= length(splvec) + 1L, |
| 1593 | 1924x |
is(splvec, "SplitVector") |
| 1594 |
) |
|
| 1595 | ||
| 1596 | ||
| 1597 | 1924x |
if (lvl == length(splvec) + 1L) {
|
| 1598 |
## XXX this should be a LayoutColree I Think. |
|
| 1599 | 1255x |
nm <- unlist(tail(value_names(pos), 1)) %||% "" |
| 1600 | 1255x |
spl <- tail(pos_splits(pos), 1)[[1]] |
| 1601 | 1255x |
fmt <- colcount_format(spl) %||% global_cc_format |
| 1602 | 1255x |
LayoutColLeaf( |
| 1603 | 1255x |
lev = lvl - 1L, |
| 1604 | 1255x |
label = label, |
| 1605 | 1255x |
tpos = pos, |
| 1606 | 1255x |
name = nm, |
| 1607 | 1255x |
colcount = NROW(alt_counts_df), |
| 1608 | 1255x |
disp_ccounts = disp_ccounts(spl), |
| 1609 | 1255x |
colcount_format = fmt |
| 1610 |
) |
|
| 1611 |
} else {
|
|
| 1612 | 669x |
spl <- splvec[[lvl]] |
| 1613 | 669x |
nm <- if (is.null(pos) || length(pos_splits(pos)) == 0) {
|
| 1614 | 418x |
obj_name(spl) |
| 1615 |
} else {
|
|
| 1616 | 251x |
unlist(tail( |
| 1617 | 251x |
value_names(pos), |
| 1618 | 251x |
1 |
| 1619 |
)) |
|
| 1620 |
} |
|
| 1621 | 669x |
rawpart <- do_split(spl, df, |
| 1622 | 669x |
trim = FALSE, |
| 1623 | 669x |
spl_context = spl_context |
| 1624 |
) |
|
| 1625 | 666x |
datparts <- rawpart[["datasplit"]] |
| 1626 | 666x |
vals <- rawpart[["values"]] |
| 1627 | 666x |
labs <- rawpart[["labels"]] |
| 1628 | ||
| 1629 | 666x |
force(alt_counts_df) |
| 1630 | 666x |
kids <- mapply( |
| 1631 | 666x |
function(dfpart, value, partlab) {
|
| 1632 |
## we could pass subset expression in here but the spec |
|
| 1633 |
## currently doesn't call for it in column space |
|
| 1634 | 1510x |
newprev <- context_df_row( |
| 1635 | 1510x |
split = obj_name(spl), |
| 1636 | 1510x |
value = value_names(value), |
| 1637 | 1510x |
full_parent_df = list(dfpart), |
| 1638 | 1510x |
cinfo = NULL |
| 1639 |
) |
|
| 1640 |
## subset expressions handled inside make_child_pos, |
|
| 1641 |
## value is (optionally, for the moment) carrying it around |
|
| 1642 | 1510x |
newpos <- make_child_pos(pos, spl, value, partlab) |
| 1643 | 1510x |
acdf_subset_expr <- make_subset_expr(spl, value) |
| 1644 | 1510x |
new_acdf_subset <- try(eval(acdf_subset_expr, alt_counts_df), silent = TRUE) |
| 1645 | 1510x |
if (is(new_acdf_subset, "try-error")) {
|
| 1646 | 4x |
stop(sprintf( |
| 1647 | 4x |
paste( |
| 1648 | 4x |
ifelse(identical(df, alt_counts_df), "df", "alt_counts_df"), |
| 1649 | 4x |
"appears incompatible with column-split", |
| 1650 | 4x |
"structure. Offending column subset", |
| 1651 | 4x |
"expression: %s\nOriginal error", |
| 1652 | 4x |
"message: %s" |
| 1653 | 4x |
), deparse(acdf_subset_expr[[1]]), |
| 1654 | 4x |
conditionMessage(attr(new_acdf_subset, "condition")) |
| 1655 |
)) |
|
| 1656 |
} |
|
| 1657 | ||
| 1658 | 1506x |
splitvec_to_coltree(dfpart, splvec, newpos, |
| 1659 | 1506x |
lvl + 1L, partlab, |
| 1660 | 1506x |
spl_context = rbind(spl_context, newprev), |
| 1661 | 1506x |
alt_counts_df = alt_counts_df[new_acdf_subset, , drop = FALSE], |
| 1662 | 1506x |
global_cc_format = global_cc_format |
| 1663 |
) |
|
| 1664 |
}, |
|
| 1665 | 666x |
dfpart = datparts, value = vals, |
| 1666 | 666x |
partlab = labs, SIMPLIFY = FALSE |
| 1667 |
) |
|
| 1668 | 660x |
disp_cc <- FALSE |
| 1669 | 660x |
cc_format <- global_cc_format # this doesn't matter probably, but its technically more correct |
| 1670 | 660x |
if (lvl > 1) {
|
| 1671 | 249x |
disp_cc <- disp_ccounts(splvec[[lvl - 1]]) |
| 1672 | 249x |
cc_format <- colcount_format(splvec[[lvl - 1]]) %||% global_cc_format |
| 1673 |
} |
|
| 1674 | ||
| 1675 | 660x |
names(kids) <- value_names(vals) |
| 1676 | 660x |
LayoutColTree( |
| 1677 | 660x |
lev = lvl, label = label, |
| 1678 | 660x |
spl = spl, |
| 1679 | 660x |
kids = kids, tpos = pos, |
| 1680 | 660x |
name = nm, |
| 1681 | 660x |
summary_function = content_fun(spl), |
| 1682 | 660x |
colcount = NROW(alt_counts_df), |
| 1683 | 660x |
disp_ccounts = disp_cc, |
| 1684 | 660x |
colcount_format = cc_format |
| 1685 |
) |
|
| 1686 |
} |
|
| 1687 |
} |
|
| 1688 | ||
| 1689 |
# fix_analyze_vis ---- |
|
| 1690 |
## now that we know for sure the number of siblings |
|
| 1691 |
## collaplse NAs to TRUE/FALSE for whether |
|
| 1692 |
## labelrows should be visible for ElementaryTables |
|
| 1693 |
## generatead from analyzing a single variable |
|
| 1694 | 1168x |
setGeneric("fix_analyze_vis", function(lyt) standardGeneric("fix_analyze_vis"))
|
| 1695 | ||
| 1696 |
setMethod( |
|
| 1697 |
"fix_analyze_vis", "PreDataTableLayouts", |
|
| 1698 |
function(lyt) {
|
|
| 1699 | 377x |
rlayout(lyt) <- fix_analyze_vis(rlayout(lyt)) |
| 1700 | 377x |
lyt |
| 1701 |
} |
|
| 1702 |
) |
|
| 1703 | ||
| 1704 |
setMethod( |
|
| 1705 |
"fix_analyze_vis", "PreDataRowLayout", |
|
| 1706 |
function(lyt) {
|
|
| 1707 | 377x |
splvecs <- lapply(lyt, fix_analyze_vis) |
| 1708 | 377x |
PreDataRowLayout( |
| 1709 | 377x |
root = root_spl(lyt), |
| 1710 | 377x |
lst = splvecs |
| 1711 |
) |
|
| 1712 |
} |
|
| 1713 |
) |
|
| 1714 | ||
| 1715 |
setMethod( |
|
| 1716 |
"fix_analyze_vis", "SplitVector", |
|
| 1717 |
function(lyt) {
|
|
| 1718 | 414x |
len <- length(lyt) |
| 1719 | 414x |
if (len == 0) {
|
| 1720 | 14x |
return(lyt) |
| 1721 |
} |
|
| 1722 | 400x |
lastspl <- lyt[[len]] |
| 1723 | 400x |
if (!(is(lastspl, "VAnalyzeSplit") || is(lastspl, "AnalyzeMultivar"))) {
|
| 1724 | 90x |
return(lyt) |
| 1725 |
} |
|
| 1726 | ||
| 1727 | 310x |
if (is(lastspl, "VAnalyzeSplit") && is.na(labelrow_visible(lastspl))) {
|
| 1728 |
## labelrow_visible(lastspl) = FALSE |
|
| 1729 | 303x |
labelrow_visible(lastspl) <- "hidden" |
| 1730 | 7x |
} else if (is(lastspl, "AnalyzeMultiVar")) {
|
| 1731 | ! |
pld <- spl_payload(lastspl) |
| 1732 | ! |
newpld <- lapply(pld, function(sp, havesibs) {
|
| 1733 | ! |
if (is.na(labelrow_visible(sp))) {
|
| 1734 | ! |
labelrow_visible(sp) <- havesibs |
| 1735 |
} |
|
| 1736 | ! |
}, havesibs = len > 1) |
| 1737 | ! |
spl_payload(lastspl) <- newpld |
| 1738 |
## pretty sure this isn't needed... |
|
| 1739 | ! |
if (is.na(label_kids(lastspl))) {
|
| 1740 | ! |
label_kids(lastspl) <- len > 1 |
| 1741 |
} |
|
| 1742 |
} |
|
| 1743 | 310x |
lyt[[len]] <- lastspl |
| 1744 | 310x |
lyt |
| 1745 |
} |
|
| 1746 |
) |
|
| 1747 | ||
| 1748 |
# check_afun_cfun_params ---- |
|
| 1749 | ||
| 1750 |
# This checks if the input params are used anywhere in cfun/afun |
|
| 1751 |
setGeneric("check_afun_cfun_params", function(lyt, params) {
|
|
| 1752 | 3677x |
standardGeneric("check_afun_cfun_params")
|
| 1753 |
}) |
|
| 1754 | ||
| 1755 |
setMethod( |
|
| 1756 |
"check_afun_cfun_params", "PreDataTableLayouts", |
|
| 1757 |
function(lyt, params) {
|
|
| 1758 |
# clayout does not have analysis functions |
|
| 1759 | 368x |
check_afun_cfun_params(rlayout(lyt), params) |
| 1760 |
} |
|
| 1761 |
) |
|
| 1762 | ||
| 1763 |
setMethod( |
|
| 1764 |
"check_afun_cfun_params", "PreDataRowLayout", |
|
| 1765 |
function(lyt, params) {
|
|
| 1766 | 368x |
ro_spl_parm_l <- check_afun_cfun_params(root_spl(lyt), params) |
| 1767 | 368x |
r_spl_parm_l <- lapply(lyt, check_afun_cfun_params, params = params) |
| 1768 | 368x |
Reduce(`|`, c(list(ro_spl_parm_l), r_spl_parm_l)) |
| 1769 |
} |
|
| 1770 |
) |
|
| 1771 | ||
| 1772 |
# Main function for checking parameters |
|
| 1773 |
setMethod( |
|
| 1774 |
"check_afun_cfun_params", "SplitVector", |
|
| 1775 |
function(lyt, params) {
|
|
| 1776 | 884x |
param_l <- lapply(lyt, check_afun_cfun_params, params = params) |
| 1777 | 884x |
Reduce(`|`, param_l) |
| 1778 |
} |
|
| 1779 |
) |
|
| 1780 | ||
| 1781 |
# Helper function for check_afun_cfun_params |
|
| 1782 |
.afun_cfun_switch <- function(spl_i) {
|
|
| 1783 | 2056x |
if (is(spl_i, "VAnalyzeSplit")) {
|
| 1784 | 679x |
analysis_fun(spl_i) |
| 1785 |
} else {
|
|
| 1786 | 1377x |
content_fun(spl_i) |
| 1787 |
} |
|
| 1788 |
} |
|
| 1789 | ||
| 1790 |
# Extreme case that happens only when using add_existing_table |
|
| 1791 |
setMethod( |
|
| 1792 |
"check_afun_cfun_params", "VTableTree", |
|
| 1793 |
function(lyt, params) {
|
|
| 1794 | 1x |
setNames(logical(length(params)), params) # All FALSE |
| 1795 |
} |
|
| 1796 |
) |
|
| 1797 | ||
| 1798 |
setMethod( |
|
| 1799 |
"check_afun_cfun_params", "Split", |
|
| 1800 |
function(lyt, params) {
|
|
| 1801 |
# Extract function in the split |
|
| 1802 | 2056x |
fnc <- .afun_cfun_switch(lyt) |
| 1803 | ||
| 1804 |
# For each parameter, check if it is called |
|
| 1805 | 2056x |
sapply(params, function(pai) any(unlist(func_takes(fnc, pai)))) |
| 1806 |
} |
|
| 1807 |
) |
|
| 1808 | ||
| 1809 |
# Helper functions ---- |
|
| 1810 | ||
| 1811 | 231x |
count <- function(df, ...) NROW(df) |
| 1812 | ||
| 1813 |
guess_format <- function(val) {
|
|
| 1814 | 1054x |
if (length(val) == 1) {
|
| 1815 | 1042x |
if (is.integer(val) || !is.numeric(val)) {
|
| 1816 | 226x |
"xx" |
| 1817 |
} else {
|
|
| 1818 | 816x |
"xx.xx" |
| 1819 |
} |
|
| 1820 | 12x |
} else if (length(val) == 2) {
|
| 1821 | 12x |
"xx.x / xx.x" |
| 1822 | ! |
} else if (length(val) == 3) {
|
| 1823 | ! |
"xx.x (xx.x - xx.x)" |
| 1824 |
} else {
|
|
| 1825 | ! |
stop("got value of length > 3")
|
| 1826 |
} |
|
| 1827 |
} |
|
| 1828 | ||
| 1829 |
.quick_afun <- function(afun, lbls) {
|
|
| 1830 | 14x |
if (.takes_df(afun)) {
|
| 1831 | 5x |
function(df, .spl_context, ...) {
|
| 1832 | 226x |
if (!is.null(lbls) && length(lbls) == 1 && is.na(lbls)) {
|
| 1833 | 222x |
lbls <- tail(.spl_context$value, 1) |
| 1834 |
} |
|
| 1835 | 226x |
if (".spl_context" %in% names(formals(afun))) {
|
| 1836 | ! |
res <- afun(df = df, .spl_context = .spl_context, ...) |
| 1837 |
} else {
|
|
| 1838 | 226x |
res <- afun(df = df, ...) |
| 1839 |
} |
|
| 1840 | 226x |
if (is(res, "RowsVerticalSection")) {
|
| 1841 | ! |
ret <- res |
| 1842 |
} else {
|
|
| 1843 | 226x |
if (!is.list(res)) {
|
| 1844 | 226x |
ret <- rcell(res, label = lbls, format = guess_format(res)) |
| 1845 |
} else {
|
|
| 1846 | ! |
if (!is.null(lbls) && length(lbls) == length(res) && all(!is.na(lbls))) {
|
| 1847 | ! |
names(res) <- lbls |
| 1848 |
} |
|
| 1849 | ! |
ret <- in_rows(.list = res, .labels = names(res), .formats = vapply(res, guess_format, "")) |
| 1850 |
} |
|
| 1851 |
} |
|
| 1852 | 226x |
ret |
| 1853 |
} |
|
| 1854 |
} else {
|
|
| 1855 | 9x |
function(x, .spl_context, ...) {
|
| 1856 | 387x |
if (!is.null(lbls) && length(lbls) == 1 && is.na(lbls)) {
|
| 1857 | 225x |
lbls <- tail(.spl_context$value, 1) |
| 1858 |
} |
|
| 1859 | 387x |
if (".spl_context" %in% names(formals(afun))) {
|
| 1860 | ! |
res <- afun(x = x, .spl_context = .spl_context, ...) |
| 1861 |
} else {
|
|
| 1862 | 387x |
res <- afun(x = x, ...) |
| 1863 |
} |
|
| 1864 | 387x |
if (is(res, "RowsVerticalSection")) {
|
| 1865 | ! |
ret <- res |
| 1866 |
} else {
|
|
| 1867 | 387x |
if (!is.list(res)) {
|
| 1868 | 297x |
ret <- rcell(res, label = lbls, format = guess_format(res)) |
| 1869 |
} else {
|
|
| 1870 | 90x |
if (!is.null(lbls) && length(lbls) == length(res) && all(!is.na(lbls))) {
|
| 1871 | 9x |
names(res) <- lbls |
| 1872 |
} |
|
| 1873 | 90x |
ret <- in_rows(.list = res, .labels = names(res), .formats = vapply(res, guess_format, "")) |
| 1874 |
} |
|
| 1875 |
} |
|
| 1876 | 387x |
ret |
| 1877 |
} |
|
| 1878 |
} |
|
| 1879 |
} |
|
| 1880 | ||
| 1881 |
# qtable ---- |
|
| 1882 | ||
| 1883 |
n_cells_res <- function(res) {
|
|
| 1884 | 8x |
ans <- 1L |
| 1885 | 8x |
if (is.list(res)) {
|
| 1886 | 4x |
ans <- length(res) |
| 1887 | 4x |
} else if (is(res, "RowsVerticalSection")) {
|
| 1888 | ! |
ans <- length(res$values) |
| 1889 |
} # XXX penetrating the abstraction |
|
| 1890 | 8x |
ans |
| 1891 |
} |
|
| 1892 | ||
| 1893 |
#' Generalized frequency table |
|
| 1894 |
#' |
|
| 1895 |
#' This function provides a convenience interface for generating generalizations of a 2-way frequency table. Row and |
|
| 1896 |
#' column space can be facetted by variables, and an analysis function can be specified. The function then builds a |
|
| 1897 |
#' layout with the specified layout and applies it to the data provided. |
|
| 1898 |
#' |
|
| 1899 |
#' @inheritParams constr_args |
|
| 1900 |
#' @inheritParams basic_table |
|
| 1901 |
#' @param row_vars (`character`)\cr the names of variables to be used in row facetting. |
|
| 1902 |
#' @param col_vars (`character`)\cr the names of variables to be used in column facetting. |
|
| 1903 |
#' @param data (`data.frame`)\cr the data to tabulate. |
|
| 1904 |
#' @param avar (`string`)\cr the variable to be analyzed. Defaults to the first variable in `data`. |
|
| 1905 |
#' @param row_labels (`character` or `NULL`)\cr row label(s) which should be applied to the analysis rows. Length must |
|
| 1906 |
#' match the number of rows generated by `afun`. |
|
| 1907 |
#' @param afun (`function`)\cr the function to generate the analysis row cell values. This can be a proper analysis |
|
| 1908 |
#' function, or a function which returns a vector or list. Vectors are taken as multi-valued single cells, whereas |
|
| 1909 |
#' lists are interpreted as multiple cells. |
|
| 1910 |
#' @param drop_levels (`flag`)\cr whether unobserved factor levels should be dropped during facetting. Defaults to |
|
| 1911 |
#' `TRUE`. |
|
| 1912 |
#' @param summarize_groups (`flag`)\cr whether each level of nesting should include marginal summary rows. Defaults to |
|
| 1913 |
#' `FALSE`. |
|
| 1914 |
#' @param ... additional arguments passed to `afun`. |
|
| 1915 |
#' @param .default_rlabel (`string`)\cr this is an implementation detail that should not be set by end users. |
|
| 1916 |
#' |
|
| 1917 |
#' @details |
|
| 1918 |
#' This function creates a table with a single top-level structure in both row and column dimensions involving faceting |
|
| 1919 |
#' by 0 or more variables in each dimension. |
|
| 1920 |
#' |
|
| 1921 |
#' The display of the table depends on certain details of the tabulation. In the case of an `afun` which returns a |
|
| 1922 |
#' single cell's contents (either a scalar or a vector of 2 or 3 elements), the label rows for the deepest-nested row |
|
| 1923 |
#' facets will be hidden and the labels used there will be used as the analysis row labels. In the case of an `afun` |
|
| 1924 |
#' which returns a list (corresponding to multiple cells), the names of the list will be used as the analysis row |
|
| 1925 |
#' labels and the deepest-nested facet row labels will be visible. |
|
| 1926 |
#' |
|
| 1927 |
#' The table will be annotated in the top-left area with an informative label displaying the analysis variable |
|
| 1928 |
#' (`avar`), if set, and the function used (captured via substitute) where possible, or 'count' if not. One exception |
|
| 1929 |
#' where the user may directly modify the top-left area (via `row_labels`) is the case of a table with row facets and |
|
| 1930 |
#' an `afun` which returns a single row. |
|
| 1931 |
#' |
|
| 1932 |
#' @return |
|
| 1933 |
#' * `qtable` returns a built `TableTree` object representing the desired table |
|
| 1934 |
#' * `qtable_layout` returns a `PreDataTableLayouts` object declaring the structure of the desired table, suitable for |
|
| 1935 |
#' passing to [build_table()]. |
|
| 1936 |
#' |
|
| 1937 |
#' @examples |
|
| 1938 |
#' qtable(ex_adsl) |
|
| 1939 |
#' qtable(ex_adsl, row_vars = "ARM") |
|
| 1940 |
#' qtable(ex_adsl, col_vars = "ARM") |
|
| 1941 |
#' qtable(ex_adsl, row_vars = "SEX", col_vars = "ARM") |
|
| 1942 |
#' qtable(ex_adsl, row_vars = c("COUNTRY", "SEX"), col_vars = c("ARM", "STRATA1"))
|
|
| 1943 |
#' qtable(ex_adsl, |
|
| 1944 |
#' row_vars = c("COUNTRY", "SEX"),
|
|
| 1945 |
#' col_vars = c("ARM", "STRATA1"), avar = "AGE", afun = mean
|
|
| 1946 |
#' ) |
|
| 1947 |
#' summary_list <- function(x, ...) as.list(summary(x)) |
|
| 1948 |
#' qtable(ex_adsl, row_vars = "SEX", col_vars = "ARM", avar = "AGE", afun = summary_list) |
|
| 1949 |
#' suppressWarnings(qtable(ex_adsl, |
|
| 1950 |
#' row_vars = "SEX", |
|
| 1951 |
#' col_vars = "ARM", avar = "AGE", afun = range |
|
| 1952 |
#' )) |
|
| 1953 |
#' |
|
| 1954 |
#' @export |
|
| 1955 |
qtable_layout <- function(data, |
|
| 1956 |
row_vars = character(), |
|
| 1957 |
col_vars = character(), |
|
| 1958 |
avar = NULL, |
|
| 1959 |
row_labels = NULL, |
|
| 1960 |
afun = NULL, |
|
| 1961 |
summarize_groups = FALSE, |
|
| 1962 |
title = "", |
|
| 1963 |
subtitles = character(), |
|
| 1964 |
main_footer = character(), |
|
| 1965 |
prov_footer = character(), |
|
| 1966 |
show_colcounts = TRUE, |
|
| 1967 |
drop_levels = TRUE, |
|
| 1968 |
..., |
|
| 1969 |
.default_rlabel = NULL) {
|
|
| 1970 | 16x |
subafun <- substitute(afun) |
| 1971 | 16x |
if (!is.null(.default_rlabel)) {
|
| 1972 | 16x |
dflt_row_lbl <- .default_rlabel |
| 1973 |
} else if ( |
|
| 1974 | ! |
is.name(subafun) && |
| 1975 | ! |
is.function(afun) && |
| 1976 |
## this is gross. basically testing |
|
| 1977 |
## if the symbol we have corresponds |
|
| 1978 |
## in some meaningful way to the function |
|
| 1979 |
## we will be calling. |
|
| 1980 | ! |
identical( |
| 1981 | ! |
mget( |
| 1982 | ! |
as.character(subafun), |
| 1983 | ! |
mode = "function", |
| 1984 | ! |
envir = parent.frame(1), |
| 1985 | ! |
ifnotfound = list(NULL), |
| 1986 | ! |
inherits = TRUE |
| 1987 | ! |
)[[1]], |
| 1988 | ! |
afun |
| 1989 |
) |
|
| 1990 |
) {
|
|
| 1991 | ! |
dflt_row_lbl <- paste(avar, as.character(subafun), sep = " - ") |
| 1992 |
} else {
|
|
| 1993 | ! |
dflt_row_lbl <- if (is.null(avar)) "count" else avar |
| 1994 |
} |
|
| 1995 | ||
| 1996 | 16x |
if (is.null(afun)) {
|
| 1997 | 5x |
afun <- count |
| 1998 |
} |
|
| 1999 | ||
| 2000 | 16x |
if (is.null(avar)) {
|
| 2001 | 5x |
avar <- names(data)[1] |
| 2002 |
} |
|
| 2003 | 16x |
fakeres <- afun(data[[avar]], ...) |
| 2004 | 16x |
multirow <- is.list(fakeres) || is(fakeres, "RowsVerticalSection") || summarize_groups |
| 2005 |
## this is before we plug in the default so if not specified by the user |
|
| 2006 |
## explicitly, row_labels is NULL at this point. |
|
| 2007 | 16x |
if (!is.null(row_labels) && length(row_labels) != n_cells_res(fakeres)) {
|
| 2008 | 2x |
stop( |
| 2009 | 2x |
"Length of row_labels (",
|
| 2010 | 2x |
length(row_labels), |
| 2011 | 2x |
") does not agree with number of rows generated by analysis function (",
|
| 2012 | 2x |
n_cells_res(fakeres), |
| 2013 |
")." |
|
| 2014 |
) |
|
| 2015 |
} |
|
| 2016 | ||
| 2017 | 14x |
if (is.null(row_labels)) {
|
| 2018 | 10x |
row_labels <- dflt_row_lbl |
| 2019 |
} |
|
| 2020 | ||
| 2021 | 14x |
lyt <- basic_table( |
| 2022 | 14x |
title = title, |
| 2023 | 14x |
subtitles = subtitles, |
| 2024 | 14x |
main_footer = main_footer, |
| 2025 | 14x |
prov_footer = prov_footer, |
| 2026 | 14x |
show_colcounts = show_colcounts |
| 2027 |
) |
|
| 2028 | ||
| 2029 | 14x |
for (var in col_vars) lyt <- split_cols_by(lyt, var) |
| 2030 | ||
| 2031 | 14x |
for (var in head(row_vars, -1)) {
|
| 2032 | 4x |
lyt <- split_rows_by(lyt, var, split_fun = if (drop_levels) drop_split_levels else NULL) |
| 2033 | 4x |
if (summarize_groups) {
|
| 2034 | 2x |
lyt <- summarize_row_groups(lyt) |
| 2035 |
} |
|
| 2036 |
} |
|
| 2037 | ||
| 2038 | 14x |
tleft <- if (multirow || length(row_vars) > 0) dflt_row_lbl else character() |
| 2039 | 14x |
if (length(row_vars) > 0) {
|
| 2040 | 10x |
if (!multirow) {
|
| 2041 |
## in the single row in splitting case, we use the row label as the topleft |
|
| 2042 |
## and the split values as the row labels for a more compact apeparance |
|
| 2043 | 6x |
tleft <- row_labels |
| 2044 | 6x |
row_labels <- NA_character_ |
| 2045 | 6x |
lyt <- split_rows_by( |
| 2046 | 6x |
lyt, tail(row_vars, 1), |
| 2047 | 6x |
split_fun = if (drop_levels) drop_split_levels else NULL, child_labels = "hidden" |
| 2048 |
) |
|
| 2049 |
} else {
|
|
| 2050 | 4x |
lyt <- split_rows_by(lyt, tail(row_vars, 1), split_fun = if (drop_levels) drop_split_levels else NULL) |
| 2051 |
} |
|
| 2052 | 10x |
if (summarize_groups) {
|
| 2053 | 2x |
lyt <- summarize_row_groups(lyt) |
| 2054 |
} |
|
| 2055 |
} |
|
| 2056 | 14x |
inner_afun <- .quick_afun(afun, row_labels) |
| 2057 | 14x |
lyt <- analyze(lyt, avar, afun = inner_afun, extra_args = list(...)) |
| 2058 | 14x |
lyt <- append_topleft(lyt, tleft) |
| 2059 |
} |
|
| 2060 | ||
| 2061 |
#' @rdname qtable_layout |
|
| 2062 |
#' @export |
|
| 2063 |
qtable <- function(data, |
|
| 2064 |
row_vars = character(), |
|
| 2065 |
col_vars = character(), |
|
| 2066 |
avar = NULL, |
|
| 2067 |
row_labels = NULL, |
|
| 2068 |
afun = NULL, |
|
| 2069 |
summarize_groups = FALSE, |
|
| 2070 |
title = "", |
|
| 2071 |
subtitles = character(), |
|
| 2072 |
main_footer = character(), |
|
| 2073 |
prov_footer = character(), |
|
| 2074 |
show_colcounts = TRUE, |
|
| 2075 |
drop_levels = TRUE, |
|
| 2076 |
...) {
|
|
| 2077 |
## this involves substitution so it needs to appear in both functions. Gross but true. |
|
| 2078 | 16x |
subafun <- substitute(afun) |
| 2079 |
if ( |
|
| 2080 | 16x |
is.name(subafun) && is.function(afun) && |
| 2081 |
## this is gross. basically testing |
|
| 2082 |
## if the symbol we have corresponds |
|
| 2083 |
## in some meaningful way to the function |
|
| 2084 |
## we will be calling. |
|
| 2085 | 16x |
identical( |
| 2086 | 16x |
mget( |
| 2087 | 16x |
as.character(subafun), |
| 2088 | 16x |
mode = "function", envir = parent.frame(1), ifnotfound = list(NULL), inherits = TRUE |
| 2089 | 16x |
)[[1]], |
| 2090 | 16x |
afun |
| 2091 |
) |
|
| 2092 |
) {
|
|
| 2093 | 11x |
dflt_row_lbl <- paste(avar, as.character(subafun), sep = " - ") |
| 2094 |
} else {
|
|
| 2095 | 5x |
dflt_row_lbl <- if (is.null(avar)) "count" else avar |
| 2096 |
} |
|
| 2097 | ||
| 2098 | 16x |
lyt <- qtable_layout( |
| 2099 | 16x |
data = data, |
| 2100 | 16x |
row_vars = row_vars, |
| 2101 | 16x |
col_vars = col_vars, |
| 2102 | 16x |
avar = avar, |
| 2103 | 16x |
row_labels = row_labels, |
| 2104 | 16x |
afun = afun, |
| 2105 | 16x |
summarize_groups = summarize_groups, |
| 2106 | 16x |
title = title, |
| 2107 | 16x |
subtitles = subtitles, |
| 2108 | 16x |
main_footer = main_footer, |
| 2109 | 16x |
prov_footer = prov_footer, |
| 2110 | 16x |
show_colcounts = show_colcounts, |
| 2111 | 16x |
drop_levels = drop_levels, |
| 2112 |
..., |
|
| 2113 | 16x |
.default_rlabel = dflt_row_lbl |
| 2114 |
) |
|
| 2115 | 14x |
build_table(lyt, data) |
| 2116 |
} |
| 1 |
label_pos_values <- c("hidden", "visible", "topleft")
|
|
| 2 | ||
| 3 |
#' @name internal_methods |
|
| 4 |
#' @rdname int_methods |
|
| 5 |
NULL |
|
| 6 | ||
| 7 |
#' Combine `SplitVector` objects |
|
| 8 |
#' |
|
| 9 |
#' @param x (`SplitVector`)\cr a `SplitVector` object. |
|
| 10 |
#' @param ... splits or `SplitVector` objects. |
|
| 11 |
#' |
|
| 12 |
#' @return Various, but should be considered implementation details. |
|
| 13 |
#' |
|
| 14 |
#' @rdname int_methods |
|
| 15 |
#' @exportMethod c |
|
| 16 |
setMethod("c", "SplitVector", function(x, ...) {
|
|
| 17 | 458x |
arglst <- list(...) |
| 18 | 458x |
stopifnot(all(sapply(arglst, is, "Split"))) |
| 19 | 458x |
tmp <- c(unclass(x), arglst) |
| 20 | 458x |
SplitVector(lst = tmp) |
| 21 |
}) |
|
| 22 | ||
| 23 |
## split_rows and split_cols are "recursive method stacks" which follow |
|
| 24 |
## the general pattern of accept object -> call add_*_split on slot of object -> |
|
| 25 |
## update object with value returned from slot method, return object. |
|
| 26 |
## |
|
| 27 |
## Thus each of the methods is idempotent, returning an updated object of the |
|
| 28 |
## same class it was passed. The exception for idempotency is the NULL method |
|
| 29 |
## which constructs a PreDataTableLayouts object with the specified split in the |
|
| 30 |
## correct place. |
|
| 31 | ||
| 32 |
## The cascading (by class) in this case is as follows for the row case: |
|
| 33 |
## PreDataTableLayouts -> PreDataRowLayout -> SplitVector |
|
| 34 |
#' @param cmpnd_fun (`function`)\cr intended for internal use. |
|
| 35 |
#' @param pos (`numeric(1)`)\cr intended for internal use. |
|
| 36 |
#' @param spl (`Split`)\cr the split. |
|
| 37 |
#' |
|
| 38 |
#' @rdname int_methods |
|
| 39 |
setGeneric( |
|
| 40 |
"split_rows", |
|
| 41 |
function(lyt = NULL, spl, pos, |
|
| 42 |
cmpnd_fun = AnalyzeMultiVars) {
|
|
| 43 | 1930x |
standardGeneric("split_rows")
|
| 44 |
} |
|
| 45 |
) |
|
| 46 | ||
| 47 |
#' @rdname int_methods |
|
| 48 |
setMethod("split_rows", "NULL", function(lyt, spl, pos, cmpnd_fun = AnalyzeMultiVars) {
|
|
| 49 | 1x |
lifecycle::deprecate_warn( |
| 50 | 1x |
when = "0.3.8", |
| 51 | 1x |
what = I("split_rows(NULL)"),
|
| 52 | 1x |
with = "basic_table()", |
| 53 | 1x |
details = "Initializing layouts via `NULL` is no longer supported." |
| 54 |
) |
|
| 55 | 1x |
rl <- PreDataRowLayout(SplitVector(spl)) |
| 56 | 1x |
cl <- PreDataColLayout() |
| 57 | 1x |
PreDataTableLayouts(rlayout = rl, clayout = cl) |
| 58 |
}) |
|
| 59 | ||
| 60 |
#' @rdname int_methods |
|
| 61 |
setMethod( |
|
| 62 |
"split_rows", "PreDataRowLayout", |
|
| 63 |
function(lyt, spl, pos, cmpnd_fun = AnalyzeMultiVars) {
|
|
| 64 | 656x |
stopifnot(pos > 0 && pos <= length(lyt) + 1) |
| 65 | 656x |
tmp <- if (pos <= length(lyt)) {
|
| 66 | 617x |
split_rows(lyt[[pos]], spl, pos, cmpnd_fun) |
| 67 |
} else {
|
|
| 68 | 39x |
if (pos != 1 && has_force_pag(spl)) {
|
| 69 | 1x |
stop("page_by splits cannot have top-level siblings",
|
| 70 | 1x |
call. = FALSE |
| 71 |
) |
|
| 72 |
} |
|
| 73 | 38x |
SplitVector(spl) |
| 74 |
} |
|
| 75 | 654x |
lyt[[pos]] <- tmp |
| 76 | 654x |
lyt |
| 77 |
} |
|
| 78 |
) |
|
| 79 | ||
| 80 |
is_analysis_spl <- function(spl) {
|
|
| 81 | ! |
is(spl, "VAnalyzeSplit") || is(spl, "AnalyzeMultiVars") |
| 82 |
} |
|
| 83 | ||
| 84 |
## note "pos" is ignored here because it is for which nest-chain |
|
| 85 |
## spl should be placed in, NOIT for where in that chain it should go |
|
| 86 |
#' @rdname int_methods |
|
| 87 |
setMethod( |
|
| 88 |
"split_rows", "SplitVector", |
|
| 89 |
function(lyt, spl, pos, cmpnd_fun = AnalyzeMultiVars) {
|
|
| 90 |
## if(is_analysis_spl(spl) && |
|
| 91 |
## is_analysis_spl(last_rowsplit(lyt))) {
|
|
| 92 |
## return(cmpnd_last_rowsplit(lyt, spl, cmpnd_fun)) |
|
| 93 |
## } |
|
| 94 | ||
| 95 | 617x |
if (has_force_pag(spl) && length(lyt) > 0 && !has_force_pag(lyt[[length(lyt)]])) {
|
| 96 | 1x |
stop("page_by splits cannot be nested within non-page_by splits",
|
| 97 | 1x |
call. = FALSE |
| 98 |
) |
|
| 99 |
} |
|
| 100 | 616x |
tmp <- c(unclass(lyt), spl) |
| 101 | 616x |
SplitVector(lst = tmp) |
| 102 |
} |
|
| 103 |
) |
|
| 104 | ||
| 105 |
#' @rdname int_methods |
|
| 106 |
setMethod( |
|
| 107 |
"split_rows", "PreDataTableLayouts", |
|
| 108 |
function(lyt, spl, pos) {
|
|
| 109 | 656x |
rlyt <- rlayout(lyt) |
| 110 | 656x |
addtl <- FALSE |
| 111 | 656x |
split_label <- obj_label(spl) |
| 112 |
if ( |
|
| 113 | 656x |
is(spl, "Split") && ## exclude existing tables that are being tacked in |
| 114 | 656x |
identical(label_position(spl), "topleft") && |
| 115 | 656x |
length(split_label) == 1 && nzchar(split_label) |
| 116 |
) {
|
|
| 117 | 17x |
addtl <- TRUE |
| 118 |
## label_position(spl) <- "hidden" |
|
| 119 |
} |
|
| 120 | ||
| 121 | 656x |
rlyt <- split_rows(rlyt, spl, pos) |
| 122 | 654x |
rlayout(lyt) <- rlyt |
| 123 | 654x |
if (addtl) {
|
| 124 | 17x |
lyt <- append_topleft(lyt, indent_string(split_label, .tl_indent(lyt))) |
| 125 |
} |
|
| 126 | 654x |
lyt |
| 127 |
} |
|
| 128 |
) |
|
| 129 | ||
| 130 |
#' @rdname int_methods |
|
| 131 |
setMethod( |
|
| 132 |
"split_rows", "ANY", |
|
| 133 |
function(lyt, spl, pos) {
|
|
| 134 | ! |
stop("nope. can't add a row split to that (", class(lyt), "). contact the maintaner.")
|
| 135 |
} |
|
| 136 |
) |
|
| 137 | ||
| 138 |
## cmpnd_last_rowsplit ===== |
|
| 139 | ||
| 140 |
#' @rdname int_methods |
|
| 141 |
#' |
|
| 142 |
#' @param constructor (`function`)\cr constructor function. |
|
| 143 | 94x |
setGeneric("cmpnd_last_rowsplit", function(lyt, spl, constructor) standardGeneric("cmpnd_last_rowsplit"))
|
| 144 | ||
| 145 |
#' @rdname int_methods |
|
| 146 |
setMethod("cmpnd_last_rowsplit", "NULL", function(lyt, spl, constructor) {
|
|
| 147 |
stop("no existing splits to compound with. contact the maintainer") # nocov
|
|
| 148 |
}) |
|
| 149 | ||
| 150 |
#' @rdname int_methods |
|
| 151 |
setMethod( |
|
| 152 |
"cmpnd_last_rowsplit", "PreDataRowLayout", |
|
| 153 |
function(lyt, spl, constructor) {
|
|
| 154 | 31x |
pos <- length(lyt) |
| 155 | 31x |
tmp <- cmpnd_last_rowsplit(lyt[[pos]], spl, constructor) |
| 156 | 31x |
lyt[[pos]] <- tmp |
| 157 | 31x |
lyt |
| 158 |
} |
|
| 159 |
) |
|
| 160 |
#' @rdname int_methods |
|
| 161 |
setMethod( |
|
| 162 |
"cmpnd_last_rowsplit", "SplitVector", |
|
| 163 |
function(lyt, spl, constructor) {
|
|
| 164 | 32x |
pos <- length(lyt) |
| 165 | 32x |
lst <- lyt[[pos]] |
| 166 | 32x |
tmp <- if (is(lst, "CompoundSplit")) {
|
| 167 | 3x |
spl_payload(lst) <- c( |
| 168 | 3x |
.uncompound(spl_payload(lst)), |
| 169 | 3x |
.uncompound(spl) |
| 170 |
) |
|
| 171 | 3x |
obj_name(lst) <- make_ma_name(spl = lst) |
| 172 | 3x |
lst |
| 173 |
## XXX never reached because AnalzyeMultiVars inherits from |
|
| 174 |
## CompoundSplit??? |
|
| 175 |
} else {
|
|
| 176 | 29x |
constructor(.payload = list(lst, spl)) |
| 177 |
} |
|
| 178 | 32x |
lyt[[pos]] <- tmp |
| 179 | 32x |
lyt |
| 180 |
} |
|
| 181 |
) |
|
| 182 | ||
| 183 |
#' @rdname int_methods |
|
| 184 |
setMethod( |
|
| 185 |
"cmpnd_last_rowsplit", "PreDataTableLayouts", |
|
| 186 |
function(lyt, spl, constructor) {
|
|
| 187 | 31x |
rlyt <- rlayout(lyt) |
| 188 | 31x |
rlyt <- cmpnd_last_rowsplit(rlyt, spl, constructor) |
| 189 | 31x |
rlayout(lyt) <- rlyt |
| 190 | 31x |
lyt |
| 191 |
} |
|
| 192 |
) |
|
| 193 |
#' @rdname int_methods |
|
| 194 |
setMethod( |
|
| 195 |
"cmpnd_last_rowsplit", "ANY", |
|
| 196 |
function(lyt, spl, constructor) {
|
|
| 197 | ! |
stop( |
| 198 | ! |
"nope. can't do cmpnd_last_rowsplit to that (",
|
| 199 | ! |
class(lyt), "). contact the maintaner." |
| 200 |
) |
|
| 201 |
} |
|
| 202 |
) |
|
| 203 | ||
| 204 |
## split_cols ==== |
|
| 205 | ||
| 206 |
#' @rdname int_methods |
|
| 207 |
setGeneric( |
|
| 208 |
"split_cols", |
|
| 209 |
function(lyt = NULL, spl, pos) {
|
|
| 210 | 1129x |
standardGeneric("split_cols")
|
| 211 |
} |
|
| 212 |
) |
|
| 213 | ||
| 214 |
#' @rdname int_methods |
|
| 215 |
setMethod("split_cols", "NULL", function(lyt, spl, pos) {
|
|
| 216 | 1x |
lifecycle::deprecate_warn( |
| 217 | 1x |
when = "0.3.8", |
| 218 | 1x |
what = I("split_cols(NULL)"),
|
| 219 | 1x |
with = "basic_table()", |
| 220 | 1x |
details = "Initializing layouts via `NULL` is no longer supported." |
| 221 |
) |
|
| 222 | 1x |
cl <- PreDataColLayout(SplitVector(spl)) |
| 223 | 1x |
rl <- PreDataRowLayout() |
| 224 | 1x |
PreDataTableLayouts(rlayout = rl, clayout = cl) |
| 225 |
}) |
|
| 226 | ||
| 227 |
#' @rdname int_methods |
|
| 228 |
setMethod( |
|
| 229 |
"split_cols", "PreDataColLayout", |
|
| 230 |
function(lyt, spl, pos) {
|
|
| 231 | 335x |
stopifnot(pos > 0 && pos <= length(lyt) + 1) |
| 232 | 335x |
tmp <- if (pos <= length(lyt)) {
|
| 233 | 326x |
split_cols(lyt[[pos]], spl, pos) |
| 234 |
} else {
|
|
| 235 | 9x |
SplitVector(spl) |
| 236 |
} |
|
| 237 | ||
| 238 | 335x |
lyt[[pos]] <- tmp |
| 239 | 335x |
lyt |
| 240 |
} |
|
| 241 |
) |
|
| 242 | ||
| 243 |
#' @rdname int_methods |
|
| 244 |
setMethod( |
|
| 245 |
"split_cols", "SplitVector", |
|
| 246 |
function(lyt, spl, pos) {
|
|
| 247 | 458x |
tmp <- c(lyt, spl) |
| 248 | 458x |
SplitVector(lst = tmp) |
| 249 |
} |
|
| 250 |
) |
|
| 251 | ||
| 252 |
#' @rdname int_methods |
|
| 253 |
setMethod( |
|
| 254 |
"split_cols", "PreDataTableLayouts", |
|
| 255 |
function(lyt, spl, pos) {
|
|
| 256 | 335x |
rlyt <- lyt@col_layout |
| 257 | 335x |
rlyt <- split_cols(rlyt, spl, pos) |
| 258 | 335x |
lyt@col_layout <- rlyt |
| 259 | 335x |
lyt |
| 260 |
} |
|
| 261 |
) |
|
| 262 | ||
| 263 |
#' @rdname int_methods |
|
| 264 |
setMethod( |
|
| 265 |
"split_cols", "ANY", |
|
| 266 |
function(lyt, spl, pos) {
|
|
| 267 | ! |
stop( |
| 268 | ! |
"nope. can't add a col split to that (", class(lyt),
|
| 269 | ! |
"). contact the maintaner." |
| 270 |
) |
|
| 271 |
} |
|
| 272 |
) |
|
| 273 | ||
| 274 |
# Constructors ===== |
|
| 275 | ||
| 276 |
## Pipe-able functions to add the various types of splits to the current layout |
|
| 277 |
## for both row and column. These all act as wrappers to the split_cols and |
|
| 278 |
## split_rows method stacks. |
|
| 279 | ||
| 280 |
#' Declaring a column-split based on levels of a variable |
|
| 281 |
#' |
|
| 282 |
#' Will generate children for each subset of a categorical variable. |
|
| 283 |
#' |
|
| 284 |
#' @inheritParams lyt_args |
|
| 285 |
#' @param ref_group (`string` or `NULL`)\cr level of `var` that should be considered `ref_group`/reference. |
|
| 286 |
#' |
|
| 287 |
#' @return A `PreDataTableLayouts` object suitable for passing to further layouting functions, and to [build_table()]. |
|
| 288 |
#' |
|
| 289 |
#' @inheritSection custom_split_funs Custom Splitting Function Details |
|
| 290 |
#' |
|
| 291 |
#' @examples |
|
| 292 |
#' lyt <- basic_table() %>% |
|
| 293 |
#' split_cols_by("ARM") %>%
|
|
| 294 |
#' analyze(c("AGE", "BMRKR2"))
|
|
| 295 |
#' |
|
| 296 |
#' tbl <- build_table(lyt, ex_adsl) |
|
| 297 |
#' tbl |
|
| 298 |
#' |
|
| 299 |
#' # Let's look at the splits in more detail |
|
| 300 |
#' |
|
| 301 |
#' lyt1 <- basic_table() %>% split_cols_by("ARM")
|
|
| 302 |
#' lyt1 |
|
| 303 |
#' |
|
| 304 |
#' # add an analysis (summary) |
|
| 305 |
#' lyt2 <- lyt1 %>% |
|
| 306 |
#' analyze(c("AGE", "COUNTRY"),
|
|
| 307 |
#' afun = list_wrap_x(summary), |
|
| 308 |
#' format = "xx.xx" |
|
| 309 |
#' ) |
|
| 310 |
#' lyt2 |
|
| 311 |
#' |
|
| 312 |
#' tbl2 <- build_table(lyt2, DM) |
|
| 313 |
#' tbl2 |
|
| 314 |
#' |
|
| 315 |
#' @examplesIf require(dplyr) |
|
| 316 |
#' # By default sequentially adding layouts results in nesting |
|
| 317 |
#' library(dplyr) |
|
| 318 |
#' |
|
| 319 |
#' DM_MF <- DM %>% |
|
| 320 |
#' filter(SEX %in% c("M", "F")) %>%
|
|
| 321 |
#' mutate(SEX = droplevels(SEX)) |
|
| 322 |
#' |
|
| 323 |
#' lyt3 <- basic_table() %>% |
|
| 324 |
#' split_cols_by("ARM") %>%
|
|
| 325 |
#' split_cols_by("SEX") %>%
|
|
| 326 |
#' analyze(c("AGE", "COUNTRY"),
|
|
| 327 |
#' afun = list_wrap_x(summary), |
|
| 328 |
#' format = "xx.xx" |
|
| 329 |
#' ) |
|
| 330 |
#' lyt3 |
|
| 331 |
#' |
|
| 332 |
#' tbl3 <- build_table(lyt3, DM_MF) |
|
| 333 |
#' tbl3 |
|
| 334 |
#' |
|
| 335 |
#' # nested=TRUE vs not |
|
| 336 |
#' lyt4 <- basic_table() %>% |
|
| 337 |
#' split_cols_by("ARM") %>%
|
|
| 338 |
#' split_rows_by("SEX", split_fun = drop_split_levels) %>%
|
|
| 339 |
#' split_rows_by("RACE", split_fun = drop_split_levels) %>%
|
|
| 340 |
#' analyze("AGE")
|
|
| 341 |
#' lyt4 |
|
| 342 |
#' |
|
| 343 |
#' tbl4 <- build_table(lyt4, DM) |
|
| 344 |
#' tbl4 |
|
| 345 |
#' |
|
| 346 |
#' lyt5 <- basic_table() %>% |
|
| 347 |
#' split_cols_by("ARM") %>%
|
|
| 348 |
#' split_rows_by("SEX", split_fun = drop_split_levels) %>%
|
|
| 349 |
#' analyze("AGE") %>%
|
|
| 350 |
#' split_rows_by("RACE", nested = FALSE, split_fun = drop_split_levels) %>%
|
|
| 351 |
#' analyze("AGE")
|
|
| 352 |
#' lyt5 |
|
| 353 |
#' |
|
| 354 |
#' tbl5 <- build_table(lyt5, DM) |
|
| 355 |
#' tbl5 |
|
| 356 |
#' |
|
| 357 |
#' @author Gabriel Becker |
|
| 358 |
#' @export |
|
| 359 |
split_cols_by <- function(lyt, |
|
| 360 |
var, |
|
| 361 |
labels_var = var, |
|
| 362 |
split_label = var, |
|
| 363 |
split_fun = NULL, |
|
| 364 |
format = NULL, |
|
| 365 |
nested = TRUE, |
|
| 366 |
child_labels = c("default", "visible", "hidden"),
|
|
| 367 |
extra_args = list(), |
|
| 368 |
ref_group = NULL, |
|
| 369 |
show_colcounts = FALSE, |
|
| 370 |
colcount_format = NULL) { ## ,
|
|
| 371 | 298x |
if (is.null(ref_group)) {
|
| 372 | 289x |
spl <- VarLevelSplit( |
| 373 | 289x |
var = var, |
| 374 | 289x |
split_label = split_label, |
| 375 | 289x |
labels_var = labels_var, |
| 376 | 289x |
split_format = format, |
| 377 | 289x |
child_labels = child_labels, |
| 378 | 289x |
split_fun = split_fun, |
| 379 | 289x |
extra_args = extra_args, |
| 380 | 289x |
show_colcounts = show_colcounts, |
| 381 | 289x |
colcount_format = colcount_format |
| 382 |
) |
|
| 383 |
} else {
|
|
| 384 | 9x |
spl <- VarLevWBaselineSplit( |
| 385 | 9x |
var = var, |
| 386 | 9x |
ref_group = ref_group, |
| 387 | 9x |
split_label = split_label, |
| 388 | 9x |
split_fun = split_fun, |
| 389 | 9x |
labels_var = labels_var, |
| 390 | 9x |
split_format = format, |
| 391 | 9x |
show_colcounts = show_colcounts, |
| 392 | 9x |
colcount_format = colcount_format |
| 393 |
) |
|
| 394 |
} |
|
| 395 | 298x |
pos <- next_cpos(lyt, nested) |
| 396 | 298x |
split_cols(lyt, spl, pos) |
| 397 |
} |
|
| 398 | ||
| 399 |
## .tl_indent ==== |
|
| 400 | ||
| 401 | 51x |
setGeneric(".tl_indent_inner", function(lyt) standardGeneric(".tl_indent_inner"))
|
| 402 | ||
| 403 |
setMethod( |
|
| 404 |
".tl_indent_inner", "PreDataTableLayouts", |
|
| 405 | 17x |
function(lyt) .tl_indent_inner(rlayout(lyt)) |
| 406 |
) |
|
| 407 |
setMethod( |
|
| 408 |
".tl_indent_inner", "PreDataRowLayout", |
|
| 409 |
function(lyt) {
|
|
| 410 | 17x |
if (length(lyt) == 0 || length(lyt[[1]]) == 0) {
|
| 411 | ! |
0L |
| 412 |
} else {
|
|
| 413 | 17x |
.tl_indent_inner(lyt[[length(lyt)]]) |
| 414 |
} |
|
| 415 |
} |
|
| 416 |
) |
|
| 417 | ||
| 418 |
setMethod( |
|
| 419 |
".tl_indent_inner", "SplitVector", |
|
| 420 |
function(lyt) {
|
|
| 421 | 17x |
sum(vapply(lyt, function(x) label_position(x) == "topleft", TRUE)) - 1L |
| 422 |
} |
|
| 423 |
) ## length(lyt) - 1L) |
|
| 424 | ||
| 425 |
.tl_indent <- function(lyt, nested = TRUE) {
|
|
| 426 | 17x |
if (!nested) {
|
| 427 | ! |
0L |
| 428 |
} else {
|
|
| 429 | 17x |
.tl_indent_inner(lyt) |
| 430 |
} |
|
| 431 |
} |
|
| 432 | ||
| 433 |
#' Add rows according to levels of a variable |
|
| 434 |
#' |
|
| 435 |
#' @inheritParams lyt_args |
|
| 436 |
#' |
|
| 437 |
#' @inherit split_cols_by return |
|
| 438 |
#' |
|
| 439 |
#' @inheritSection custom_split_funs Custom Splitting Function Details |
|
| 440 |
#' |
|
| 441 |
#' @note |
|
| 442 |
#' If `var` is a factor with empty unobserved levels and `labels_var` is specified, it must also be a factor |
|
| 443 |
#' with the same number of levels as `var`. Currently the error that occurs when this is not the case is not very |
|
| 444 |
#' informative, but that will change in the future. |
|
| 445 |
#' |
|
| 446 |
#' @examples |
|
| 447 |
#' lyt <- basic_table() %>% |
|
| 448 |
#' split_cols_by("ARM") %>%
|
|
| 449 |
#' split_rows_by("RACE", split_fun = drop_split_levels) %>%
|
|
| 450 |
#' analyze("AGE", mean, var_labels = "Age", format = "xx.xx")
|
|
| 451 |
#' |
|
| 452 |
#' tbl <- build_table(lyt, DM) |
|
| 453 |
#' tbl |
|
| 454 |
#' |
|
| 455 |
#' lyt2 <- basic_table() %>% |
|
| 456 |
#' split_cols_by("ARM") %>%
|
|
| 457 |
#' split_rows_by("RACE") %>%
|
|
| 458 |
#' analyze("AGE", mean, var_labels = "Age", format = "xx.xx")
|
|
| 459 |
#' |
|
| 460 |
#' tbl2 <- build_table(lyt2, DM) |
|
| 461 |
#' tbl2 |
|
| 462 |
#' |
|
| 463 |
#' lyt3 <- basic_table() %>% |
|
| 464 |
#' split_cols_by("ARM") %>%
|
|
| 465 |
#' split_cols_by("SEX") %>%
|
|
| 466 |
#' summarize_row_groups(label_fstr = "Overall (N)") %>% |
|
| 467 |
#' split_rows_by("RACE",
|
|
| 468 |
#' split_label = "Ethnicity", labels_var = "ethn_lab", |
|
| 469 |
#' split_fun = drop_split_levels |
|
| 470 |
#' ) %>% |
|
| 471 |
#' summarize_row_groups("RACE", label_fstr = "%s (n)") %>%
|
|
| 472 |
#' analyze("AGE", var_labels = "Age", afun = mean, format = "xx.xx")
|
|
| 473 |
#' |
|
| 474 |
#' lyt3 |
|
| 475 |
#' |
|
| 476 |
#' @examplesIf require(dplyr) |
|
| 477 |
#' library(dplyr) |
|
| 478 |
#' |
|
| 479 |
#' DM2 <- DM %>% |
|
| 480 |
#' filter(SEX %in% c("M", "F")) %>%
|
|
| 481 |
#' mutate( |
|
| 482 |
#' SEX = droplevels(SEX), |
|
| 483 |
#' gender_lab = c( |
|
| 484 |
#' "F" = "Female", "M" = "Male", |
|
| 485 |
#' "U" = "Unknown", |
|
| 486 |
#' "UNDIFFERENTIATED" = "Undifferentiated" |
|
| 487 |
#' )[SEX], |
|
| 488 |
#' ethn_lab = c( |
|
| 489 |
#' "ASIAN" = "Asian", |
|
| 490 |
#' "BLACK OR AFRICAN AMERICAN" = "Black or African American", |
|
| 491 |
#' "WHITE" = "White", |
|
| 492 |
#' "AMERICAN INDIAN OR ALASKA NATIVE" = "American Indian or Alaska Native", |
|
| 493 |
#' "MULTIPLE" = "Multiple", |
|
| 494 |
#' "NATIVE HAWAIIAN OR OTHER PACIFIC ISLANDER" = |
|
| 495 |
#' "Native Hawaiian or Other Pacific Islander", |
|
| 496 |
#' "OTHER" = "Other", "UNKNOWN" = "Unknown" |
|
| 497 |
#' )[RACE] |
|
| 498 |
#' ) |
|
| 499 |
#' |
|
| 500 |
#' tbl3 <- build_table(lyt3, DM2) |
|
| 501 |
#' tbl3 |
|
| 502 |
#' |
|
| 503 |
#' @author Gabriel Becker |
|
| 504 |
#' @export |
|
| 505 |
split_rows_by <- function(lyt, |
|
| 506 |
var, |
|
| 507 |
labels_var = var, |
|
| 508 |
split_label = var, |
|
| 509 |
split_fun = NULL, |
|
| 510 |
parent_name = var, |
|
| 511 |
format = NULL, |
|
| 512 |
na_str = NA_character_, |
|
| 513 |
nested = TRUE, |
|
| 514 |
child_labels = c("default", "visible", "hidden"),
|
|
| 515 |
label_pos = "hidden", |
|
| 516 |
indent_mod = 0L, |
|
| 517 |
page_by = FALSE, |
|
| 518 |
page_prefix = split_label, |
|
| 519 |
section_div = NA_character_) {
|
|
| 520 | 295x |
label_pos <- match.arg(label_pos, label_pos_values) |
| 521 | 295x |
child_labels <- match.arg(child_labels) |
| 522 | 295x |
spl <- VarLevelSplit( |
| 523 | 295x |
var = var, |
| 524 | 295x |
split_label = split_label, |
| 525 | 295x |
label_pos = label_pos, |
| 526 | 295x |
labels_var = labels_var, |
| 527 | 295x |
split_fun = split_fun, |
| 528 | 295x |
split_format = format, |
| 529 | 295x |
split_na_str = na_str, |
| 530 | 295x |
child_labels = child_labels, |
| 531 | 295x |
indent_mod = indent_mod, |
| 532 | 295x |
page_prefix = if (page_by) page_prefix else NA_character_, |
| 533 | 295x |
section_div = section_div, |
| 534 | 295x |
split_name = parent_name |
| 535 |
) |
|
| 536 | ||
| 537 | 295x |
pos <- next_rpos(lyt, nested) |
| 538 | 295x |
ret <- split_rows(lyt, spl, pos) |
| 539 | ||
| 540 | 293x |
ret |
| 541 |
} |
|
| 542 | ||
| 543 |
#' Associate multiple variables with columns |
|
| 544 |
#' |
|
| 545 |
#' In some cases, the variable to be ultimately analyzed is most naturally defined on a column, not a row, basis. |
|
| 546 |
#' When we need columns to reflect different variables entirely, rather than different levels of a single |
|
| 547 |
#' variable, we use `split_cols_by_multivar`. |
|
| 548 |
#' |
|
| 549 |
#' @inheritParams lyt_args |
|
| 550 |
#' |
|
| 551 |
#' @inherit split_cols_by return |
|
| 552 |
#' |
|
| 553 |
#' @seealso [analyze_colvars()] |
|
| 554 |
#' |
|
| 555 |
#' @examplesIf require(dplyr) |
|
| 556 |
#' library(dplyr) |
|
| 557 |
#' |
|
| 558 |
#' ANL <- DM %>% mutate(value = rnorm(n()), pctdiff = runif(n())) |
|
| 559 |
#' |
|
| 560 |
#' ## toy example where we take the mean of the first variable and the |
|
| 561 |
#' ## count of >.5 for the second. |
|
| 562 |
#' colfuns <- list( |
|
| 563 |
#' function(x) in_rows(mean = mean(x), .formats = "xx.x"), |
|
| 564 |
#' function(x) in_rows("# x > 5" = sum(x > .5), .formats = "xx")
|
|
| 565 |
#' ) |
|
| 566 |
#' |
|
| 567 |
#' lyt <- basic_table() %>% |
|
| 568 |
#' split_cols_by("ARM") %>%
|
|
| 569 |
#' split_cols_by_multivar(c("value", "pctdiff")) %>%
|
|
| 570 |
#' split_rows_by("RACE",
|
|
| 571 |
#' split_label = "ethnicity", |
|
| 572 |
#' split_fun = drop_split_levels |
|
| 573 |
#' ) %>% |
|
| 574 |
#' summarize_row_groups() %>% |
|
| 575 |
#' analyze_colvars(afun = colfuns) |
|
| 576 |
#' lyt |
|
| 577 |
#' |
|
| 578 |
#' tbl <- build_table(lyt, ANL) |
|
| 579 |
#' tbl |
|
| 580 |
#' |
|
| 581 |
#' @author Gabriel Becker |
|
| 582 |
#' @export |
|
| 583 |
split_cols_by_multivar <- function(lyt, |
|
| 584 |
vars, |
|
| 585 |
split_fun = NULL, |
|
| 586 |
varlabels = vars, |
|
| 587 |
varnames = NULL, |
|
| 588 |
nested = TRUE, |
|
| 589 |
extra_args = list(), |
|
| 590 |
## for completeness even though it doesn't make sense |
|
| 591 |
show_colcounts = FALSE, |
|
| 592 |
colcount_format = NULL) {
|
|
| 593 | 25x |
spl <- MultiVarSplit( |
| 594 | 25x |
vars = vars, split_label = "", |
| 595 | 25x |
varlabels = varlabels, |
| 596 | 25x |
varnames = varnames, |
| 597 | 25x |
split_fun = split_fun, |
| 598 | 25x |
extra_args = extra_args, |
| 599 | 25x |
show_colcounts = show_colcounts, |
| 600 | 25x |
colcount_format = colcount_format |
| 601 |
) |
|
| 602 | 25x |
pos <- next_cpos(lyt, nested) |
| 603 | 25x |
split_cols(lyt, spl, pos) |
| 604 |
} |
|
| 605 | ||
| 606 |
#' Associate multiple variables with rows |
|
| 607 |
#' |
|
| 608 |
#' When we need rows to reflect different variables rather than different |
|
| 609 |
#' levels of a single variable, we use `split_rows_by_multivar`. |
|
| 610 |
#' |
|
| 611 |
#' @inheritParams lyt_args |
|
| 612 |
#' |
|
| 613 |
#' @inherit split_rows_by return |
|
| 614 |
#' |
|
| 615 |
#' @seealso [split_rows_by()] for typical row splitting, and [split_cols_by_multivar()] to perform the same type of |
|
| 616 |
#' split on a column basis. |
|
| 617 |
#' |
|
| 618 |
#' @examples |
|
| 619 |
#' lyt <- basic_table() %>% |
|
| 620 |
#' split_cols_by("ARM") %>%
|
|
| 621 |
#' split_rows_by_multivar(c("SEX", "STRATA1")) %>%
|
|
| 622 |
#' summarize_row_groups() %>% |
|
| 623 |
#' analyze(c("AGE", "SEX"))
|
|
| 624 |
#' |
|
| 625 |
#' tbl <- build_table(lyt, DM) |
|
| 626 |
#' tbl |
|
| 627 |
#' |
|
| 628 |
#' @export |
|
| 629 |
split_rows_by_multivar <- function(lyt, |
|
| 630 |
vars, |
|
| 631 |
split_fun = NULL, |
|
| 632 |
split_label = "", |
|
| 633 |
varlabels = vars, |
|
| 634 |
parent_name = "multivars", |
|
| 635 |
format = NULL, |
|
| 636 |
na_str = NA_character_, |
|
| 637 |
nested = TRUE, |
|
| 638 |
child_labels = c("default", "visible", "hidden"),
|
|
| 639 |
indent_mod = 0L, |
|
| 640 |
section_div = NA_character_, |
|
| 641 |
extra_args = list()) {
|
|
| 642 | 3x |
child_labels <- match.arg(child_labels) |
| 643 | 3x |
spl <- MultiVarSplit( |
| 644 | 3x |
vars = vars, split_label = split_label, varlabels, |
| 645 | 3x |
split_format = format, |
| 646 | 3x |
split_na_str = na_str, |
| 647 | 3x |
child_labels = child_labels, |
| 648 | 3x |
indent_mod = indent_mod, |
| 649 | 3x |
split_fun = split_fun, |
| 650 | 3x |
section_div = section_div, |
| 651 | 3x |
extra_args = extra_args, |
| 652 | 3x |
split_name = parent_name |
| 653 |
) |
|
| 654 | 3x |
pos <- next_rpos(lyt, nested) |
| 655 | 3x |
split_rows(lyt, spl, pos) |
| 656 |
} |
|
| 657 | ||
| 658 |
#' Split on static or dynamic cuts of the data |
|
| 659 |
#' |
|
| 660 |
#' Create columns (or row splits) based on values (such as quartiles) of `var`. |
|
| 661 |
#' |
|
| 662 |
#' @inheritParams lyt_args |
|
| 663 |
#' |
|
| 664 |
#' @details For dynamic cuts, the cut is transformed into a static cut by [build_table()] *based on the full dataset*, |
|
| 665 |
#' before proceeding. Thus even when nested within another split in column/row space, the resulting split will reflect |
|
| 666 |
#' the overall values (e.g., quartiles) in the dataset, NOT the values for subset it is nested under. |
|
| 667 |
#' |
|
| 668 |
#' @inherit split_cols_by return |
|
| 669 |
#' |
|
| 670 |
#' @examplesIf require(dplyr) |
|
| 671 |
#' library(dplyr) |
|
| 672 |
#' |
|
| 673 |
#' # split_cols_by_cuts |
|
| 674 |
#' lyt <- basic_table() %>% |
|
| 675 |
#' split_cols_by("ARM") %>%
|
|
| 676 |
#' split_cols_by_cuts("AGE",
|
|
| 677 |
#' split_label = "Age", |
|
| 678 |
#' cuts = c(0, 25, 35, 1000), |
|
| 679 |
#' cutlabels = c("young", "medium", "old")
|
|
| 680 |
#' ) %>% |
|
| 681 |
#' analyze(c("BMRKR2", "STRATA2")) %>%
|
|
| 682 |
#' append_topleft("counts")
|
|
| 683 |
#' |
|
| 684 |
#' tbl <- build_table(lyt, ex_adsl) |
|
| 685 |
#' tbl |
|
| 686 |
#' |
|
| 687 |
#' # split_rows_by_cuts |
|
| 688 |
#' lyt2 <- basic_table() %>% |
|
| 689 |
#' split_cols_by("ARM") %>%
|
|
| 690 |
#' split_rows_by_cuts("AGE",
|
|
| 691 |
#' split_label = "Age", |
|
| 692 |
#' cuts = c(0, 25, 35, 1000), |
|
| 693 |
#' cutlabels = c("young", "medium", "old")
|
|
| 694 |
#' ) %>% |
|
| 695 |
#' analyze(c("BMRKR2", "STRATA2")) %>%
|
|
| 696 |
#' append_topleft("counts")
|
|
| 697 |
#' |
|
| 698 |
#' |
|
| 699 |
#' tbl2 <- build_table(lyt2, ex_adsl) |
|
| 700 |
#' tbl2 |
|
| 701 |
#' |
|
| 702 |
#' # split_cols_by_quartiles |
|
| 703 |
#' |
|
| 704 |
#' lyt3 <- basic_table() %>% |
|
| 705 |
#' split_cols_by("ARM") %>%
|
|
| 706 |
#' split_cols_by_quartiles("AGE", split_label = "Age") %>%
|
|
| 707 |
#' analyze(c("BMRKR2", "STRATA2")) %>%
|
|
| 708 |
#' append_topleft("counts")
|
|
| 709 |
#' |
|
| 710 |
#' tbl3 <- build_table(lyt3, ex_adsl) |
|
| 711 |
#' tbl3 |
|
| 712 |
#' |
|
| 713 |
#' # split_rows_by_quartiles |
|
| 714 |
#' lyt4 <- basic_table(show_colcounts = TRUE) %>% |
|
| 715 |
#' split_cols_by("ARM") %>%
|
|
| 716 |
#' split_rows_by_quartiles("AGE", split_label = "Age") %>%
|
|
| 717 |
#' analyze("BMRKR2") %>%
|
|
| 718 |
#' append_topleft(c("Age Quartiles", " Counts BMRKR2"))
|
|
| 719 |
#' |
|
| 720 |
#' tbl4 <- build_table(lyt4, ex_adsl) |
|
| 721 |
#' tbl4 |
|
| 722 |
#' |
|
| 723 |
#' # split_cols_by_cutfun |
|
| 724 |
#' cutfun <- function(x) {
|
|
| 725 |
#' cutpoints <- c( |
|
| 726 |
#' min(x), |
|
| 727 |
#' mean(x), |
|
| 728 |
#' max(x) |
|
| 729 |
#' ) |
|
| 730 |
#' |
|
| 731 |
#' names(cutpoints) <- c("", "Younger", "Older")
|
|
| 732 |
#' cutpoints |
|
| 733 |
#' } |
|
| 734 |
#' |
|
| 735 |
#' lyt5 <- basic_table() %>% |
|
| 736 |
#' split_cols_by_cutfun("AGE", cutfun = cutfun) %>%
|
|
| 737 |
#' analyze("SEX")
|
|
| 738 |
#' |
|
| 739 |
#' tbl5 <- build_table(lyt5, ex_adsl) |
|
| 740 |
#' tbl5 |
|
| 741 |
#' |
|
| 742 |
#' # split_rows_by_cutfun |
|
| 743 |
#' lyt6 <- basic_table() %>% |
|
| 744 |
#' split_cols_by("SEX") %>%
|
|
| 745 |
#' split_rows_by_cutfun("AGE", cutfun = cutfun) %>%
|
|
| 746 |
#' analyze("BMRKR2")
|
|
| 747 |
#' |
|
| 748 |
#' tbl6 <- build_table(lyt6, ex_adsl) |
|
| 749 |
#' tbl6 |
|
| 750 |
#' |
|
| 751 |
#' @author Gabriel Becker |
|
| 752 |
#' @export |
|
| 753 |
#' @rdname varcuts |
|
| 754 |
split_cols_by_cuts <- function(lyt, var, cuts, |
|
| 755 |
cutlabels = NULL, |
|
| 756 |
split_label = var, |
|
| 757 |
nested = TRUE, |
|
| 758 |
cumulative = FALSE, |
|
| 759 |
show_colcounts = FALSE, |
|
| 760 |
colcount_format = NULL) {
|
|
| 761 | 3x |
spl <- make_static_cut_split( |
| 762 | 3x |
var = var, |
| 763 | 3x |
split_label = split_label, |
| 764 | 3x |
cuts = cuts, |
| 765 | 3x |
cutlabels = cutlabels, |
| 766 | 3x |
cumulative = cumulative, |
| 767 | 3x |
show_colcounts = show_colcounts, |
| 768 | 3x |
colcount_format = colcount_format |
| 769 |
) |
|
| 770 |
## if(cumulative) |
|
| 771 |
## spl = as(spl, "CumulativeCutSplit") |
|
| 772 | 3x |
pos <- next_cpos(lyt, nested) |
| 773 | 3x |
split_cols(lyt, spl, pos) |
| 774 |
} |
|
| 775 | ||
| 776 |
#' @export |
|
| 777 |
#' @rdname varcuts |
|
| 778 |
split_rows_by_cuts <- function(lyt, var, cuts, |
|
| 779 |
cutlabels = NULL, |
|
| 780 |
split_label = var, |
|
| 781 |
parent_name = var, |
|
| 782 |
format = NULL, |
|
| 783 |
na_str = NA_character_, |
|
| 784 |
nested = TRUE, |
|
| 785 |
cumulative = FALSE, |
|
| 786 |
label_pos = "hidden", |
|
| 787 |
section_div = NA_character_) {
|
|
| 788 | 2x |
label_pos <- match.arg(label_pos, label_pos_values) |
| 789 |
## VarStaticCutSplit( |
|
| 790 | 2x |
spl <- make_static_cut_split(var, split_label, |
| 791 | 2x |
cuts = cuts, |
| 792 | 2x |
cutlabels = cutlabels, |
| 793 | 2x |
split_format = format, |
| 794 | 2x |
split_na_str = na_str, |
| 795 | 2x |
label_pos = label_pos, |
| 796 | 2x |
cumulative = cumulative, |
| 797 | 2x |
section_div = section_div, |
| 798 | 2x |
split_name = parent_name |
| 799 |
) |
|
| 800 |
## if(cumulative) |
|
| 801 |
## spl = as(spl, "CumulativeCutSplit") |
|
| 802 | 2x |
pos <- next_rpos(lyt, nested) |
| 803 | 2x |
split_rows(lyt, spl, pos) |
| 804 |
} |
|
| 805 | ||
| 806 |
#' @export |
|
| 807 |
#' @rdname varcuts |
|
| 808 |
split_cols_by_cutfun <- function(lyt, var, |
|
| 809 |
cutfun = qtile_cuts, |
|
| 810 |
cutlabelfun = function(x) NULL, |
|
| 811 |
split_label = var, |
|
| 812 |
nested = TRUE, |
|
| 813 |
extra_args = list(), |
|
| 814 |
cumulative = FALSE, |
|
| 815 |
show_colcounts = FALSE, |
|
| 816 |
colcount_format = NULL) {
|
|
| 817 | 3x |
spl <- VarDynCutSplit(var, split_label, |
| 818 | 3x |
cutfun = cutfun, |
| 819 | 3x |
cutlabelfun = cutlabelfun, |
| 820 | 3x |
extra_args = extra_args, |
| 821 | 3x |
cumulative = cumulative, |
| 822 | 3x |
label_pos = "hidden", |
| 823 | 3x |
show_colcounts = show_colcounts, |
| 824 | 3x |
colcount_format = colcount_format |
| 825 |
) |
|
| 826 | 3x |
pos <- next_cpos(lyt, nested) |
| 827 | 3x |
split_cols(lyt, spl, pos) |
| 828 |
} |
|
| 829 | ||
| 830 |
#' @export |
|
| 831 |
#' @rdname varcuts |
|
| 832 |
split_cols_by_quartiles <- function(lyt, var, split_label = var, |
|
| 833 |
nested = TRUE, |
|
| 834 |
extra_args = list(), |
|
| 835 |
cumulative = FALSE, |
|
| 836 |
show_colcounts = FALSE, |
|
| 837 |
colcount_format = NULL) {
|
|
| 838 | 2x |
split_cols_by_cutfun( |
| 839 | 2x |
lyt = lyt, |
| 840 | 2x |
var = var, |
| 841 | 2x |
split_label = split_label, |
| 842 | 2x |
cutfun = qtile_cuts, |
| 843 | 2x |
cutlabelfun = function(x) {
|
| 844 | 2x |
c( |
| 845 | 2x |
"[min, Q1]", |
| 846 | 2x |
"(Q1, Q2]", |
| 847 | 2x |
"(Q2, Q3]", |
| 848 | 2x |
"(Q3, max]" |
| 849 |
) |
|
| 850 |
}, |
|
| 851 | 2x |
nested = nested, |
| 852 | 2x |
extra_args = extra_args, |
| 853 | 2x |
cumulative = cumulative, |
| 854 | 2x |
show_colcounts = show_colcounts, |
| 855 | 2x |
colcount_format = colcount_format |
| 856 |
) |
|
| 857 |
## spl = VarDynCutSplit(var, split_label, cutfun = qtile_cuts, |
|
| 858 |
## cutlabelfun = function(x) c("[min, Q1]",
|
|
| 859 |
## "(Q1, Q2]", |
|
| 860 |
## "(Q2, Q3]", |
|
| 861 |
## "(Q3, max]"), |
|
| 862 |
## split_format = format, |
|
| 863 |
## extra_args = extra_args, |
|
| 864 |
## cumulative = cumulative, |
|
| 865 |
## label_pos = "hidden") |
|
| 866 |
## pos = next_cpos(lyt, nested) |
|
| 867 |
## split_cols(lyt, spl, pos) |
|
| 868 |
} |
|
| 869 | ||
| 870 |
#' @export |
|
| 871 |
#' @rdname varcuts |
|
| 872 |
split_rows_by_quartiles <- function(lyt, var, split_label = var, |
|
| 873 |
parent_name = var, |
|
| 874 |
format = NULL, |
|
| 875 |
na_str = NA_character_, |
|
| 876 |
nested = TRUE, |
|
| 877 |
child_labels = c("default", "visible", "hidden"),
|
|
| 878 |
extra_args = list(), |
|
| 879 |
cumulative = FALSE, |
|
| 880 |
indent_mod = 0L, |
|
| 881 |
label_pos = "hidden", |
|
| 882 |
section_div = NA_character_) {
|
|
| 883 | 2x |
split_rows_by_cutfun( |
| 884 | 2x |
lyt = lyt, |
| 885 | 2x |
var = var, |
| 886 | 2x |
split_label = split_label, |
| 887 | 2x |
parent_name = parent_name, |
| 888 | 2x |
format = format, |
| 889 | 2x |
na_str = na_str, |
| 890 | 2x |
cutfun = qtile_cuts, |
| 891 | 2x |
cutlabelfun = function(x) {
|
| 892 | 2x |
c( |
| 893 | 2x |
"[min, Q1]", |
| 894 | 2x |
"(Q1, Q2]", |
| 895 | 2x |
"(Q2, Q3]", |
| 896 | 2x |
"(Q3, max]" |
| 897 |
) |
|
| 898 |
}, |
|
| 899 | 2x |
nested = nested, |
| 900 | 2x |
child_labels = child_labels, |
| 901 | 2x |
extra_args = extra_args, |
| 902 | 2x |
cumulative = cumulative, |
| 903 | 2x |
indent_mod = indent_mod, |
| 904 | 2x |
label_pos = label_pos, |
| 905 | 2x |
section_div = section_div |
| 906 |
) |
|
| 907 | ||
| 908 |
## label_pos <- match.arg(label_pos, label_pos_values) |
|
| 909 |
## spl = VarDynCutSplit(var, split_label, cutfun = qtile_cuts, |
|
| 910 |
## cutlabelfun = , |
|
| 911 |
## split_format = format, |
|
| 912 |
## child_labels = child_labels, |
|
| 913 |
## extra_args = extra_args, |
|
| 914 |
## cumulative = cumulative, |
|
| 915 |
## indent_mod = indent_mod, |
|
| 916 |
## label_pos = label_pos) |
|
| 917 |
## pos = next_rpos(lyt, nested) |
|
| 918 |
## split_rows(lyt, spl, pos) |
|
| 919 |
} |
|
| 920 | ||
| 921 |
qtile_cuts <- function(x) {
|
|
| 922 | 6x |
ret <- quantile(x) |
| 923 | 6x |
names(ret) <- c( |
| 924 |
"", |
|
| 925 | 6x |
"1st qrtile", |
| 926 | 6x |
"2nd qrtile", |
| 927 | 6x |
"3rd qrtile", |
| 928 | 6x |
"4th qrtile" |
| 929 |
) |
|
| 930 | 6x |
ret |
| 931 |
} |
|
| 932 | ||
| 933 |
#' @export |
|
| 934 |
#' @rdname varcuts |
|
| 935 |
split_rows_by_cutfun <- function(lyt, var, |
|
| 936 |
cutfun = qtile_cuts, |
|
| 937 |
cutlabelfun = function(x) NULL, |
|
| 938 |
split_label = var, |
|
| 939 |
parent_name = var, |
|
| 940 |
format = NULL, |
|
| 941 |
na_str = NA_character_, |
|
| 942 |
nested = TRUE, |
|
| 943 |
child_labels = c("default", "visible", "hidden"),
|
|
| 944 |
extra_args = list(), |
|
| 945 |
cumulative = FALSE, |
|
| 946 |
indent_mod = 0L, |
|
| 947 |
label_pos = "hidden", |
|
| 948 |
section_div = NA_character_) {
|
|
| 949 | 2x |
label_pos <- match.arg(label_pos, label_pos_values) |
| 950 | 2x |
child_labels <- match.arg(child_labels) |
| 951 | 2x |
spl <- VarDynCutSplit(var, split_label, |
| 952 | 2x |
cutfun = cutfun, |
| 953 | 2x |
cutlabelfun = cutlabelfun, |
| 954 | 2x |
split_format = format, |
| 955 | 2x |
split_na_str = na_str, |
| 956 | 2x |
child_labels = child_labels, |
| 957 | 2x |
extra_args = extra_args, |
| 958 | 2x |
cumulative = cumulative, |
| 959 | 2x |
indent_mod = indent_mod, |
| 960 | 2x |
label_pos = label_pos, |
| 961 | 2x |
section_div = section_div, |
| 962 | 2x |
split_name = parent_name |
| 963 |
) |
|
| 964 | 2x |
pos <- next_rpos(lyt, nested) |
| 965 | 2x |
split_rows(lyt, spl, pos) |
| 966 |
} |
|
| 967 | ||
| 968 |
#' .spl_context within analysis and split functions |
|
| 969 |
#' |
|
| 970 |
#' `.spl_context` is an optional parameter for any of rtables' special functions, i.e. `afun` (analysis function |
|
| 971 |
#' in [analyze()]), `cfun` (content or label function in [summarize_row_groups()]), or `split_fun` (e.g. for |
|
| 972 |
#' [split_rows_by()]). |
|
| 973 |
#' |
|
| 974 |
#' @details |
|
| 975 |
#' The `.spl_context` `data.frame` gives information about the subsets of data corresponding to the splits within |
|
| 976 |
#' which the current `analyze` action is nested. Taken together, these correspond to the path that the resulting (set |
|
| 977 |
#' of) rows the analysis function is creating, although the information is in a slightly different form. Each split |
|
| 978 |
#' (which correspond to groups of rows in the resulting table), as well as the initial 'root' "split", is represented |
|
| 979 |
#' via the following columns: |
|
| 980 |
#' |
|
| 981 |
#' \describe{
|
|
| 982 |
#' \item{split}{The name of the split (often the variable being split).}
|
|
| 983 |
#' \item{value}{The string representation of the value at that split (`split`).}
|
|
| 984 |
#' \item{full_parent_df}{A `data.frame` containing the full data (i.e. across all columns) corresponding to the path
|
|
| 985 |
#' defined by the combination of `split` and `value` of this row *and all rows above this row*.} |
|
| 986 |
#' \item{all_cols_n}{The number of observations corresponding to the row grouping (union of all columns).}
|
|
| 987 |
#' \item{column for each column in the table structure (*row-split and analyze contexts only*)}{These list columns
|
|
| 988 |
#' (named the same as `names(col_exprs(tab))`) contain logical vectors corresponding to the subset of this row's |
|
| 989 |
#' `full_parent_df` corresponding to the column.} |
|
| 990 |
#' \item{cur_col_id}{Identifier of the current column. This may be an internal name, constructed by pasting the
|
|
| 991 |
#' column path together.} |
|
| 992 |
#' \item{cur_col_subset}{List column containing logical vectors indicating the subset of this row's `full_parent_df`
|
|
| 993 |
#' for the column currently being created by the analysis function.} |
|
| 994 |
#' \item{cur_col_expr}{List of current column expression. This may be used to filter `.alt_df_row`, or any external
|
|
| 995 |
#' data, by column. Filtering `.alt_df_row` by columns produces `.alt_df`.} |
|
| 996 |
#' \item{cur_col_n}{Integer column containing the observation counts for that split.}
|
|
| 997 |
#' \item{cur_col_split}{Current column split names. This is recovered from the current column path.}
|
|
| 998 |
#' \item{cur_col_split_val}{Current column split values. This is recovered from the current column path.}
|
|
| 999 |
#' } |
|
| 1000 |
#' |
|
| 1001 |
#' @note |
|
| 1002 |
#' Within analysis functions that accept `.spl_context`, the `all_cols_n` and `cur_col_n` columns of the data frame |
|
| 1003 |
#' will contain the 'true' observation counts corresponding to the row-group and row-group x column subsets of the |
|
| 1004 |
#' data. These numbers will not, and currently cannot, reflect alternate column observation counts provided by the |
|
| 1005 |
#' `alt_counts_df`, `col_counts` or `col_total` arguments to [build_table()]. |
|
| 1006 |
#' |
|
| 1007 |
#' @name spl_context |
|
| 1008 |
NULL |
|
| 1009 | ||
| 1010 |
#' Additional parameters within analysis and content functions (`afun`/`cfun`) |
|
| 1011 |
#' |
|
| 1012 |
#' @description |
|
| 1013 |
#' It is possible to add specific parameters to `afun` and `cfun`, in [analyze()] and [summarize_row_groups()], |
|
| 1014 |
#' respectively. These parameters grant access to relevant information like the row split structure (see |
|
| 1015 |
#' [spl_context]) and the predefined baseline (`.ref_group`). |
|
| 1016 |
#' |
|
| 1017 |
#' @details |
|
| 1018 |
#' We list and describe all the parameters that can be added to a custom analysis function below: |
|
| 1019 |
#' |
|
| 1020 |
#' \describe{
|
|
| 1021 |
#' \item{.N_col}{Column-wise N (column count) for the full column being tabulated within.}
|
|
| 1022 |
#' \item{.N_total}{Overall N (all observation count, defined as sum of column counts) for the tabulation.}
|
|
| 1023 |
#' \item{.N_row}{Row-wise N (row group count) for the group of observations being analyzed (i.e. with no
|
|
| 1024 |
#' column-based subsetting).} |
|
| 1025 |
#' \item{.df_row}{`data.frame` for observations in the row group being analyzed (i.e. with no column-based
|
|
| 1026 |
#' subsetting).} |
|
| 1027 |
#' \item{.var}{Variable being analyzed.}
|
|
| 1028 |
#' \item{.ref_group}{`data.frame` or vector of subset corresponding to the `ref_group` column including subsetting
|
|
| 1029 |
#' defined by row-splitting. Only required/meaningful if a `ref_group` column has been defined.} |
|
| 1030 |
#' \item{.ref_full}{`data.frame` or vector of subset corresponding to the `ref_group` column without subsetting
|
|
| 1031 |
#' defined by row-splitting. Only required/meaningful if a `ref_group` column has been defined.} |
|
| 1032 |
#' \item{.in_ref_col}{Boolean indicating if calculation is done for cells within the reference column.}
|
|
| 1033 |
#' \item{.spl_context}{`data.frame` where each row gives information about a previous 'ancestor' split state.
|
|
| 1034 |
#' See [spl_context].} |
|
| 1035 |
#' \item{.alt_df_row}{`data.frame`, i.e. the `alt_counts_df` after row splitting. It can be used with
|
|
| 1036 |
#' `.all_col_exprs` and `.spl_context` information to retrieve current faceting, but for `alt_count_df`. |
|
| 1037 |
#' It can be an empty table if all the entries are filtered out.} |
|
| 1038 |
#' \item{.alt_df}{`data.frame`, `.alt_df_row` but filtered by columns expression. This data present the same
|
|
| 1039 |
#' faceting of main data `df`. This also filters `NA`s out if related parameters are set to do so (e.g. `inclNAs` |
|
| 1040 |
#' in [analyze()]). Similarly to `.alt_df_row`, it can be an empty data.frame if all the entries are filtered out.} |
|
| 1041 |
#' \item{.alt_df_full}{`data.frame`, the full `alt_counts_df` as passed into `build_table`.
|
|
| 1042 |
#' Unlike `.alt_df` and `.alt_df_row`, this parameter can be used in cases |
|
| 1043 |
#' where the variables required for row splitting are not present in `alt_counts_df`.} |
|
| 1044 |
#' \item{.all_col_exprs}{List of expressions. Each of them represents a different column splitting.}
|
|
| 1045 |
#' \item{.all_col_counts}{Vector of integers. Each of them represents the global count for each column. It differs
|
|
| 1046 |
#' if `alt_counts_df` is used (see [build_table()]).} |
|
| 1047 |
#' } |
|
| 1048 |
#' |
|
| 1049 |
#' @note If any of these formals is specified incorrectly or not present in the tabulation machinery, it will be |
|
| 1050 |
#' treated as if missing. For example, `.ref_group` will be missing if no baseline is previously defined during |
|
| 1051 |
#' data splitting (via `ref_group` parameters in, e.g., [split_rows_by()]). Similarly, if no `alt_counts_df` is |
|
| 1052 |
#' provided to [build_table()], `.alt_df_row` and `.alt_df` will not be present. |
|
| 1053 |
#' |
|
| 1054 |
#' @name additional_fun_params |
|
| 1055 |
NULL |
|
| 1056 | ||
| 1057 |
#' Generate rows analyzing variables across columns |
|
| 1058 |
#' |
|
| 1059 |
#' Adding *analyzed variables* to our table layout defines the primary tabulation to be performed. We do this by |
|
| 1060 |
#' adding calls to `analyze` and/or [analyze_colvars()] into our layout pipeline. As with adding further splitting, |
|
| 1061 |
#' the tabulation will occur at the current/next level of nesting by default. |
|
| 1062 |
#' |
|
| 1063 |
#' @inheritParams lyt_args |
|
| 1064 |
#' @param section_div (`string`)\cr string which should be repeated as a section divider after the set of rows defined |
|
| 1065 |
#' by (each sub-analysis/variable) of this analyze instruction, or |
|
| 1066 |
#' `NA_character_` (the default) for no section divider. This section |
|
| 1067 |
#' divider will be overridden by a split-level section divider when |
|
| 1068 |
#' both apply to the same position in the rendered output. |
|
| 1069 |
#' |
|
| 1070 |
#' @inherit split_cols_by return |
|
| 1071 |
#' |
|
| 1072 |
#' @details |
|
| 1073 |
#' When non-`NULL`, `format` is used to specify formats for all generated rows, and can be a character vector, a |
|
| 1074 |
#' function, or a list of functions. It will be repped out to the number of rows once this is calculated during the |
|
| 1075 |
#' tabulation process, but will be overridden by formats specified within `rcell` calls in `afun`. |
|
| 1076 |
#' |
|
| 1077 |
#' The analysis function (`afun`) should take as its first parameter either `x` or `df`. Whichever of these the |
|
| 1078 |
#' function accepts will change the behavior when tabulation is performed as follows: |
|
| 1079 |
#' |
|
| 1080 |
#' - If `afun`'s first parameter is `x`, it will receive the corresponding subset *vector* of data from the relevant |
|
| 1081 |
#' column (from `var` here) of the raw data being used to build the table. |
|
| 1082 |
#' - If `afun`'s first parameter is `df`, it will receive the corresponding subset *data frame* (i.e. all columns) of |
|
| 1083 |
#' the raw data being tabulated. |
|
| 1084 |
#' |
|
| 1085 |
#' In addition to differentiation on the first argument, the analysis function can optionally accept a number of |
|
| 1086 |
#' other parameters which, *if and only if* present in the formals, will be passed to the function by the tabulation |
|
| 1087 |
#' machinery. These are listed and described in [additional_fun_params]. |
|
| 1088 |
#' |
|
| 1089 |
#' @note None of the arguments described in [additional_fun_params] can be overridden via `extra_args` or when calling |
|
| 1090 |
#' [make_afun()]. `.N_col` and `.N_total` can be overridden via the `col_counts` argument to [build_table()]. |
|
| 1091 |
#' Alternative values for the others must be calculated within `afun` based on a combination of extra arguments and |
|
| 1092 |
#' the unmodified values provided by the tabulation framework. |
|
| 1093 |
#' |
|
| 1094 |
#' @examples |
|
| 1095 |
#' lyt <- basic_table() %>% |
|
| 1096 |
#' split_cols_by("ARM") %>%
|
|
| 1097 |
#' analyze("AGE", afun = list_wrap_x(summary), format = "xx.xx")
|
|
| 1098 |
#' lyt |
|
| 1099 |
#' |
|
| 1100 |
#' tbl <- build_table(lyt, DM) |
|
| 1101 |
#' tbl |
|
| 1102 |
#' |
|
| 1103 |
#' lyt2 <- basic_table() %>% |
|
| 1104 |
#' split_cols_by("Species") %>%
|
|
| 1105 |
#' analyze(head(names(iris), -1), afun = function(x) {
|
|
| 1106 |
#' list( |
|
| 1107 |
#' "mean / sd" = rcell(c(mean(x), sd(x)), format = "xx.xx (xx.xx)"), |
|
| 1108 |
#' "range" = rcell(diff(range(x)), format = "xx.xx") |
|
| 1109 |
#' ) |
|
| 1110 |
#' }) |
|
| 1111 |
#' lyt2 |
|
| 1112 |
#' |
|
| 1113 |
#' tbl2 <- build_table(lyt2, iris) |
|
| 1114 |
#' tbl2 |
|
| 1115 |
#' |
|
| 1116 |
#' @author Gabriel Becker |
|
| 1117 |
#' @export |
|
| 1118 |
analyze <- function(lyt, |
|
| 1119 |
vars, |
|
| 1120 |
afun = simple_analysis, |
|
| 1121 |
var_labels = vars, |
|
| 1122 |
## really wish I hadn't named this table_names |
|
| 1123 |
## but can't break backwards compat :( |
|
| 1124 |
table_names = vars, |
|
| 1125 |
parent_name = NULL, |
|
| 1126 |
format = NULL, |
|
| 1127 |
na_str = NA_character_, |
|
| 1128 |
nested = TRUE, |
|
| 1129 |
## can't name this na_rm symbol conflict with possible afuns!! |
|
| 1130 |
inclNAs = FALSE, |
|
| 1131 |
extra_args = list(), |
|
| 1132 |
show_labels = c("default", "visible", "hidden"),
|
|
| 1133 |
indent_mod = 0L, |
|
| 1134 |
section_div = NA_character_) {
|
|
| 1135 | 364x |
show_labels <- match.arg(show_labels) |
| 1136 | 364x |
subafun <- substitute(afun) |
| 1137 |
# R treats a single NA value as a logical atomic. The below |
|
| 1138 |
# maps all the NAs in `var_labels` to NA_character_ required by `Split` |
|
| 1139 |
# and avoids the error when `var_labels` is just c(NA). |
|
| 1140 | 364x |
var_labels <- vapply(var_labels, function(label) ifelse(is.na(label), NA_character_, label), character(1)) |
| 1141 |
if ( |
|
| 1142 | 364x |
is.name(subafun) && |
| 1143 | 364x |
is.function(afun) && |
| 1144 |
## this is gross. basically testing |
|
| 1145 |
## if the symbol we have corresponds |
|
| 1146 |
## in some meaningful way to the function |
|
| 1147 |
## we will be calling. |
|
| 1148 | 364x |
identical( |
| 1149 | 364x |
mget( |
| 1150 | 364x |
as.character(subafun), |
| 1151 | 364x |
mode = "function", |
| 1152 | 364x |
ifnotfound = list(NULL), |
| 1153 | 364x |
inherits = TRUE |
| 1154 | 364x |
)[[1]], afun |
| 1155 |
) |
|
| 1156 |
) {
|
|
| 1157 | 221x |
defrowlab <- as.character(subafun) |
| 1158 |
} else {
|
|
| 1159 | 143x |
defrowlab <- var_labels |
| 1160 |
} |
|
| 1161 | ||
| 1162 | 364x |
spl <- AnalyzeMultiVars(vars, var_labels, |
| 1163 | 364x |
afun = afun, |
| 1164 | 364x |
split_format = format, |
| 1165 | 364x |
split_na_str = na_str, |
| 1166 | 364x |
defrowlab = defrowlab, |
| 1167 | 364x |
inclNAs = inclNAs, |
| 1168 | 364x |
extra_args = extra_args, |
| 1169 | 364x |
indent_mod = indent_mod, |
| 1170 | 364x |
child_names = table_names, |
| 1171 | 364x |
child_labels = show_labels, |
| 1172 | 364x |
section_div = section_div, |
| 1173 | 364x |
split_name = parent_name |
| 1174 |
) |
|
| 1175 | ||
| 1176 | 364x |
if (nested && (is(last_rowsplit(lyt), "VAnalyzeSplit") || is(last_rowsplit(lyt), "AnalyzeMultiVars"))) {
|
| 1177 | 31x |
cmpnd_last_rowsplit(lyt, spl, AnalyzeMultiVars) |
| 1178 |
} else {
|
|
| 1179 |
## analysis compounding now done in split_rows |
|
| 1180 | 331x |
pos <- next_rpos(lyt, nested) |
| 1181 | 331x |
split_rows(lyt, spl, pos) |
| 1182 |
} |
|
| 1183 |
} |
|
| 1184 | ||
| 1185 |
get_acolvar_name <- function(lyt) {
|
|
| 1186 |
## clyt <- clayout(lyt) |
|
| 1187 |
## stopifnot(length(clyt) == 1L) |
|
| 1188 |
## vec = clyt[[1]] |
|
| 1189 |
## vcls = vapply(vec, class, "") |
|
| 1190 |
## pos = max(which(vcls == "MultiVarSplit")) |
|
| 1191 | 23x |
paste(c("ac", get_acolvar_vars(lyt)), collapse = "_")
|
| 1192 |
} |
|
| 1193 | ||
| 1194 |
get_acolvar_vars <- function(lyt) {
|
|
| 1195 | 36x |
clyt <- clayout(lyt) |
| 1196 | 36x |
stopifnot(length(clyt) == 1L) |
| 1197 | 36x |
vec <- clyt[[1]] |
| 1198 | 36x |
vcls <- vapply(vec, class, "") |
| 1199 | 36x |
pos <- which(vcls == "MultiVarSplit") |
| 1200 | 36x |
if (length(pos) > 0) {
|
| 1201 | 36x |
spl_payload(vec[[pos]]) |
| 1202 |
} else {
|
|
| 1203 | ! |
"non_multivar" |
| 1204 |
} |
|
| 1205 |
} |
|
| 1206 | ||
| 1207 |
#' Generate rows analyzing different variables across columns |
|
| 1208 |
#' |
|
| 1209 |
#' @inheritParams lyt_args |
|
| 1210 |
#' @param afun (`function` or `list`)\cr function(s) to be used to calculate the values in each column. The list |
|
| 1211 |
#' will be repped out as needed and matched by position with the columns during tabulation. This functions |
|
| 1212 |
#' accepts the same parameters as [analyze()] like `afun` and `format`. For further information see |
|
| 1213 |
#' [additional_fun_params]. |
|
| 1214 |
#' |
|
| 1215 |
#' @inherit split_cols_by return |
|
| 1216 |
#' |
|
| 1217 |
#' @seealso [split_cols_by_multivar()] |
|
| 1218 |
#' |
|
| 1219 |
#' @examplesIf require(dplyr) |
|
| 1220 |
#' library(dplyr) |
|
| 1221 |
#' |
|
| 1222 |
#' ANL <- DM %>% mutate(value = rnorm(n()), pctdiff = runif(n())) |
|
| 1223 |
#' |
|
| 1224 |
#' ## toy example where we take the mean of the first variable and the |
|
| 1225 |
#' ## count of >.5 for the second. |
|
| 1226 |
#' colfuns <- list( |
|
| 1227 |
#' function(x) rcell(mean(x), format = "xx.x"), |
|
| 1228 |
#' function(x) rcell(sum(x > .5), format = "xx") |
|
| 1229 |
#' ) |
|
| 1230 |
#' |
|
| 1231 |
#' lyt <- basic_table() %>% |
|
| 1232 |
#' split_cols_by("ARM") %>%
|
|
| 1233 |
#' split_cols_by_multivar(c("value", "pctdiff")) %>%
|
|
| 1234 |
#' split_rows_by("RACE",
|
|
| 1235 |
#' split_label = "ethnicity", |
|
| 1236 |
#' split_fun = drop_split_levels |
|
| 1237 |
#' ) %>% |
|
| 1238 |
#' summarize_row_groups() %>% |
|
| 1239 |
#' analyze_colvars(afun = colfuns) |
|
| 1240 |
#' lyt |
|
| 1241 |
#' |
|
| 1242 |
#' tbl <- build_table(lyt, ANL) |
|
| 1243 |
#' tbl |
|
| 1244 |
#' |
|
| 1245 |
#' lyt2 <- basic_table() %>% |
|
| 1246 |
#' split_cols_by("ARM") %>%
|
|
| 1247 |
#' split_cols_by_multivar(c("value", "pctdiff"),
|
|
| 1248 |
#' varlabels = c("Measurement", "Pct Diff")
|
|
| 1249 |
#' ) %>% |
|
| 1250 |
#' split_rows_by("RACE",
|
|
| 1251 |
#' split_label = "ethnicity", |
|
| 1252 |
#' split_fun = drop_split_levels |
|
| 1253 |
#' ) %>% |
|
| 1254 |
#' summarize_row_groups() %>% |
|
| 1255 |
#' analyze_colvars(afun = mean, format = "xx.xx") |
|
| 1256 |
#' |
|
| 1257 |
#' tbl2 <- build_table(lyt2, ANL) |
|
| 1258 |
#' tbl2 |
|
| 1259 |
#' |
|
| 1260 |
#' @author Gabriel Becker |
|
| 1261 |
#' @export |
|
| 1262 |
analyze_colvars <- function(lyt, |
|
| 1263 |
afun, |
|
| 1264 |
parent_name = get_acolvar_name(lyt), |
|
| 1265 |
format = NULL, |
|
| 1266 |
na_str = NA_character_, |
|
| 1267 |
nested = TRUE, |
|
| 1268 |
extra_args = list(), |
|
| 1269 |
indent_mod = 0L, |
|
| 1270 |
inclNAs = FALSE) {
|
|
| 1271 | 23x |
if (is.function(afun)) {
|
| 1272 | 13x |
subafun <- substitute(afun) |
| 1273 |
if ( |
|
| 1274 | 13x |
is.name(subafun) && |
| 1275 | 13x |
is.function(afun) && |
| 1276 |
## this is gross. basically testing |
|
| 1277 |
## if the symbol we have corresponds |
|
| 1278 |
## in some meaningful way to the function |
|
| 1279 |
## we will be calling. |
|
| 1280 | 13x |
identical( |
| 1281 | 13x |
mget( |
| 1282 | 13x |
as.character(subafun), |
| 1283 | 13x |
mode = "function", |
| 1284 | 13x |
ifnotfound = list(NULL), |
| 1285 | 13x |
inherits = TRUE |
| 1286 | 13x |
)[[1]], |
| 1287 | 13x |
afun |
| 1288 |
) |
|
| 1289 |
) {
|
|
| 1290 | 13x |
defrowlab <- as.character(subafun) |
| 1291 |
} else {
|
|
| 1292 | ! |
defrowlab <- "" |
| 1293 |
} |
|
| 1294 | 13x |
afun <- lapply( |
| 1295 | 13x |
get_acolvar_vars(lyt), |
| 1296 | 13x |
function(x) afun |
| 1297 |
) |
|
| 1298 |
} else {
|
|
| 1299 | 10x |
defrowlab <- "" |
| 1300 |
} |
|
| 1301 | 23x |
spl <- AnalyzeColVarSplit( |
| 1302 | 23x |
afun = afun, |
| 1303 | 23x |
defrowlab = defrowlab, |
| 1304 | 23x |
split_format = format, |
| 1305 | 23x |
split_na_str = na_str, |
| 1306 | 23x |
split_name = parent_name, |
| 1307 | 23x |
indent_mod = indent_mod, |
| 1308 | 23x |
extra_args = extra_args, |
| 1309 | 23x |
inclNAs = inclNAs |
| 1310 |
) |
|
| 1311 | 23x |
pos <- next_rpos(lyt, nested, for_analyze = TRUE) |
| 1312 | 23x |
split_rows(lyt, spl, pos) |
| 1313 |
} |
|
| 1314 | ||
| 1315 |
## Add a total column at the next **top level** spot in |
|
| 1316 |
## the column layout. |
|
| 1317 | ||
| 1318 |
#' Add overall column |
|
| 1319 |
#' |
|
| 1320 |
#' This function will *only* add an overall column at the *top* level of splitting, NOT within existing column splits. |
|
| 1321 |
#' See [add_overall_level()] for the recommended way to add overall columns more generally within existing splits. |
|
| 1322 |
#' |
|
| 1323 |
#' @inheritParams lyt_args |
|
| 1324 |
#' |
|
| 1325 |
#' @inherit split_cols_by return |
|
| 1326 |
#' |
|
| 1327 |
#' @seealso [add_overall_level()] |
|
| 1328 |
#' |
|
| 1329 |
#' @examples |
|
| 1330 |
#' lyt <- basic_table() %>% |
|
| 1331 |
#' split_cols_by("ARM") %>%
|
|
| 1332 |
#' add_overall_col("All Patients") %>%
|
|
| 1333 |
#' analyze("AGE")
|
|
| 1334 |
#' lyt |
|
| 1335 |
#' |
|
| 1336 |
#' tbl <- build_table(lyt, DM) |
|
| 1337 |
#' tbl |
|
| 1338 |
#' |
|
| 1339 |
#' @export |
|
| 1340 |
add_overall_col <- function(lyt, label) {
|
|
| 1341 | 139x |
spl <- AllSplit(label) |
| 1342 | 139x |
split_cols( |
| 1343 | 139x |
lyt, |
| 1344 | 139x |
spl, |
| 1345 | 139x |
next_cpos(lyt, FALSE) |
| 1346 |
) |
|
| 1347 |
} |
|
| 1348 | ||
| 1349 |
## add_row_summary ==== |
|
| 1350 | ||
| 1351 |
#' @inheritParams lyt_args |
|
| 1352 |
#' |
|
| 1353 |
#' @export |
|
| 1354 |
#' |
|
| 1355 |
#' @rdname int_methods |
|
| 1356 |
setGeneric( |
|
| 1357 |
".add_row_summary", |
|
| 1358 |
function(lyt, |
|
| 1359 |
label, |
|
| 1360 |
cfun, |
|
| 1361 |
child_labels = c("default", "visible", "hidden"),
|
|
| 1362 |
cformat = NULL, |
|
| 1363 |
cna_str = "-", |
|
| 1364 |
indent_mod = 0L, |
|
| 1365 |
cvar = "", |
|
| 1366 |
extra_args = list()) {
|
|
| 1367 | 491x |
standardGeneric(".add_row_summary")
|
| 1368 |
} |
|
| 1369 |
) |
|
| 1370 | ||
| 1371 |
#' @rdname int_methods |
|
| 1372 |
setMethod( |
|
| 1373 |
".add_row_summary", "PreDataTableLayouts", |
|
| 1374 |
function(lyt, |
|
| 1375 |
label, |
|
| 1376 |
cfun, |
|
| 1377 |
child_labels = c("default", "visible", "hidden"),
|
|
| 1378 |
cformat = NULL, |
|
| 1379 |
cna_str = "-", |
|
| 1380 |
indent_mod = 0L, |
|
| 1381 |
cvar = "", |
|
| 1382 |
extra_args = list()) {
|
|
| 1383 | 125x |
child_labels <- match.arg(child_labels) |
| 1384 | 125x |
tmp <- .add_row_summary(rlayout(lyt), label, cfun, |
| 1385 | 125x |
child_labels = child_labels, |
| 1386 | 125x |
cformat = cformat, |
| 1387 | 125x |
cna_str = cna_str, |
| 1388 | 125x |
indent_mod = indent_mod, |
| 1389 | 125x |
cvar = cvar, |
| 1390 | 125x |
extra_args = extra_args |
| 1391 |
) |
|
| 1392 | 125x |
rlayout(lyt) <- tmp |
| 1393 | 125x |
lyt |
| 1394 |
} |
|
| 1395 |
) |
|
| 1396 | ||
| 1397 |
#' @rdname int_methods |
|
| 1398 |
setMethod( |
|
| 1399 |
".add_row_summary", "PreDataRowLayout", |
|
| 1400 |
function(lyt, |
|
| 1401 |
label, |
|
| 1402 |
cfun, |
|
| 1403 |
child_labels = c("default", "visible", "hidden"),
|
|
| 1404 |
cformat = NULL, |
|
| 1405 |
cna_str = "-", |
|
| 1406 |
indent_mod = 0L, |
|
| 1407 |
cvar = "", |
|
| 1408 |
extra_args = list()) {
|
|
| 1409 | 125x |
child_labels <- match.arg(child_labels) |
| 1410 | 125x |
if (length(lyt) == 0 || (length(lyt) == 1 && length(lyt[[1]]) == 0)) {
|
| 1411 |
## XXX ignoring indent mod here |
|
| 1412 | 9x |
rt <- root_spl(lyt) |
| 1413 | 9x |
rt <- .add_row_summary(rt, |
| 1414 | 9x |
label, |
| 1415 | 9x |
cfun, |
| 1416 | 9x |
child_labels = child_labels, |
| 1417 | 9x |
cformat = cformat, |
| 1418 | 9x |
cna_str = cna_str, |
| 1419 | 9x |
cvar = cvar, |
| 1420 | 9x |
extra_args = extra_args |
| 1421 |
) |
|
| 1422 | 9x |
root_spl(lyt) <- rt |
| 1423 |
} else {
|
|
| 1424 | 116x |
ind <- length(lyt) |
| 1425 | 116x |
tmp <- .add_row_summary(lyt[[ind]], label, cfun, |
| 1426 | 116x |
child_labels = child_labels, |
| 1427 | 116x |
cformat = cformat, |
| 1428 | 116x |
cna_str = cna_str, |
| 1429 | 116x |
indent_mod = indent_mod, |
| 1430 | 116x |
cvar = cvar, |
| 1431 | 116x |
extra_args = extra_args |
| 1432 |
) |
|
| 1433 | 116x |
lyt[[ind]] <- tmp |
| 1434 |
} |
|
| 1435 | 125x |
lyt |
| 1436 |
} |
|
| 1437 |
) |
|
| 1438 | ||
| 1439 |
#' @rdname int_methods |
|
| 1440 |
setMethod( |
|
| 1441 |
".add_row_summary", "SplitVector", |
|
| 1442 |
function(lyt, |
|
| 1443 |
label, |
|
| 1444 |
cfun, |
|
| 1445 |
child_labels = c("default", "visible", "hidden"),
|
|
| 1446 |
cformat = NULL, |
|
| 1447 |
cna_str = "-", |
|
| 1448 |
indent_mod = 0L, |
|
| 1449 |
cvar = "", |
|
| 1450 |
extra_args = list()) {
|
|
| 1451 | 116x |
child_labels <- match.arg(child_labels) |
| 1452 | 116x |
ind <- length(lyt) |
| 1453 | ! |
if (ind == 0) stop("no split to add content rows at")
|
| 1454 | 116x |
spl <- lyt[[ind]] |
| 1455 |
# if(is(spl, "AnalyzeVarSplit")) |
|
| 1456 |
# stop("can't add content rows to analyze variable split")
|
|
| 1457 | 116x |
tmp <- .add_row_summary(spl, |
| 1458 | 116x |
label, |
| 1459 | 116x |
cfun, |
| 1460 | 116x |
child_labels = child_labels, |
| 1461 | 116x |
cformat = cformat, |
| 1462 | 116x |
cna_str = cna_str, |
| 1463 | 116x |
indent_mod = indent_mod, |
| 1464 | 116x |
cvar = cvar, |
| 1465 | 116x |
extra_args = extra_args |
| 1466 |
) |
|
| 1467 | 116x |
lyt[[ind]] <- tmp |
| 1468 | 116x |
lyt |
| 1469 |
} |
|
| 1470 |
) |
|
| 1471 | ||
| 1472 |
#' @rdname int_methods |
|
| 1473 |
setMethod( |
|
| 1474 |
".add_row_summary", "Split", |
|
| 1475 |
function(lyt, |
|
| 1476 |
label, |
|
| 1477 |
cfun, |
|
| 1478 |
child_labels = c("default", "visible", "hidden"),
|
|
| 1479 |
cformat = NULL, |
|
| 1480 |
cna_str = "-", |
|
| 1481 |
indent_mod = 0L, |
|
| 1482 |
cvar = "", |
|
| 1483 |
extra_args = list()) {
|
|
| 1484 | 125x |
child_labels <- match.arg(child_labels) |
| 1485 |
# lbl_kids = .labelkids_helper(child_labels) |
|
| 1486 | 125x |
content_fun(lyt) <- cfun |
| 1487 | 125x |
content_indent_mod(lyt) <- indent_mod |
| 1488 | 125x |
content_var(lyt) <- cvar |
| 1489 |
## obj_format(lyt) = cformat |
|
| 1490 | 125x |
content_format(lyt) <- cformat |
| 1491 | 125x |
if (!identical(child_labels, "default") && !identical(child_labels, label_kids(lyt))) {
|
| 1492 | ! |
label_kids(lyt) <- child_labels |
| 1493 |
} |
|
| 1494 | 125x |
content_na_str <- cna_str |
| 1495 | 125x |
content_extra_args(lyt) <- extra_args |
| 1496 | 125x |
lyt |
| 1497 |
} |
|
| 1498 |
) |
|
| 1499 | ||
| 1500 |
.count_raw_constr <- function(var, format, label_fstr) {
|
|
| 1501 | 1x |
function(df, labelstr = "") {
|
| 1502 | 3x |
if (grepl("%s", label_fstr, fixed = TRUE)) {
|
| 1503 | ! |
label <- sprintf(label_fstr, labelstr) |
| 1504 |
} else {
|
|
| 1505 | 3x |
label <- label_fstr |
| 1506 |
} |
|
| 1507 | 3x |
if (is(df, "data.frame")) {
|
| 1508 | 3x |
if (!is.null(var) && nzchar(var)) {
|
| 1509 | 3x |
cnt <- sum(!is.na(df[[var]])) |
| 1510 |
} else {
|
|
| 1511 | ! |
cnt <- nrow(df) |
| 1512 |
} |
|
| 1513 | 1x |
} else { # df is the data column vector
|
| 1514 | ! |
cnt <- sum(!is.na(df)) |
| 1515 |
} |
|
| 1516 | 3x |
ret <- rcell(cnt, |
| 1517 | 3x |
format = format, |
| 1518 | 3x |
label = label, |
| 1519 | 3x |
stat_names = "n" |
| 1520 |
) |
|
| 1521 | 3x |
ret |
| 1522 |
} |
|
| 1523 |
} |
|
| 1524 | ||
| 1525 |
.count_wpcts_constr <- function(var, format, label_fstr) {
|
|
| 1526 | 110x |
function(df, labelstr = "", .N_col) {
|
| 1527 | 1692x |
if (grepl("%s", label_fstr, fixed = TRUE)) {
|
| 1528 | 1668x |
label <- sprintf(label_fstr, labelstr) |
| 1529 |
} else {
|
|
| 1530 | 24x |
label <- label_fstr |
| 1531 |
} |
|
| 1532 | 1692x |
if (is(df, "data.frame")) {
|
| 1533 | 1692x |
if (!is.null(var) && nzchar(var)) {
|
| 1534 | 407x |
cnt <- sum(!is.na(df[[var]])) |
| 1535 |
} else {
|
|
| 1536 | 1285x |
cnt <- nrow(df) |
| 1537 |
} |
|
| 1538 | 110x |
} else { # df is the data column vector
|
| 1539 | ! |
cnt <- sum(!is.na(df)) |
| 1540 |
} |
|
| 1541 |
## the formatter does the *100 so we don't here. |
|
| 1542 |
## Elements are named with stat_names so that ARD generation has access to them |
|
| 1543 | 1692x |
ret <- rcell(c(cnt, cnt / .N_col), |
| 1544 | 1692x |
format = format, |
| 1545 | 1692x |
label = label, |
| 1546 | 1692x |
stat_names = c("n", "p")
|
| 1547 |
) |
|
| 1548 | 1692x |
ret |
| 1549 |
} |
|
| 1550 |
} |
|
| 1551 | ||
| 1552 |
.validate_cfuns <- function(fun) {
|
|
| 1553 | 131x |
if (is.list(fun)) {
|
| 1554 | 2x |
return(unlist(lapply(fun, .validate_cfuns))) |
| 1555 |
} |
|
| 1556 | ||
| 1557 | 129x |
frmls <- formals(fun) |
| 1558 | 129x |
ls_pos <- match("labelstr", names(frmls))
|
| 1559 | 129x |
if (is.na(ls_pos)) {
|
| 1560 | ! |
stop("content functions must explicitly accept a 'labelstr' argument")
|
| 1561 |
} |
|
| 1562 | ||
| 1563 | 129x |
list(fun) |
| 1564 |
} |
|
| 1565 | ||
| 1566 |
#' Analysis function to count levels of a factor with percentage of the column total |
|
| 1567 |
#' |
|
| 1568 |
#' @param x (`factor`)\cr a vector of data, provided by rtables pagination machinery. |
|
| 1569 |
#' @param .N_col (`integer(1)`)\cr total count for the column, provided by rtables pagination machinery. |
|
| 1570 |
#' |
|
| 1571 |
#' @return A `RowsVerticalSection` object with counts (and percents) for each level of the factor. |
|
| 1572 |
#' |
|
| 1573 |
#' @examples |
|
| 1574 |
#' counts_wpcts(DM$SEX, 400) |
|
| 1575 |
#' |
|
| 1576 |
#' @export |
|
| 1577 |
counts_wpcts <- function(x, .N_col) {
|
|
| 1578 | 2x |
if (!is.factor(x)) {
|
| 1579 | 1x |
stop( |
| 1580 | 1x |
"using the 'counts_wpcts' analysis function requires factor data ", |
| 1581 | 1x |
"to guarantee equal numbers of rows across all collumns, got class ", |
| 1582 | 1x |
class(x), "." |
| 1583 |
) |
|
| 1584 |
} |
|
| 1585 | 1x |
ret <- table(x) |
| 1586 | 1x |
in_rows(.list = lapply(ret, function(y) rcell(y * c(1, 1 / .N_col), format = "xx (xx.x%)"))) |
| 1587 |
} |
|
| 1588 | ||
| 1589 |
#' Add a content row of summary counts |
|
| 1590 |
#' |
|
| 1591 |
#' @inheritParams lyt_args |
|
| 1592 |
#' |
|
| 1593 |
#' @inherit split_cols_by return |
|
| 1594 |
#' |
|
| 1595 |
#' @details |
|
| 1596 |
#' If `format` expects 1 value (i.e. it is specified as a format string and `xx` appears for two values |
|
| 1597 |
#' (i.e. `xx` appears twice in the format string) or is specified as a function, then both raw and percent of |
|
| 1598 |
#' column total counts are calculated. If `format` is a format string where `xx` appears only one time, only |
|
| 1599 |
#' raw counts are used. |
|
| 1600 |
#' |
|
| 1601 |
#' `cfun` must accept `x` or `df` as its first argument. For the `df` argument `cfun` will receive the subset |
|
| 1602 |
#' `data.frame` corresponding with the row- and column-splitting for the cell being calculated. Must accept |
|
| 1603 |
#' `labelstr` as the second parameter, which accepts the `label` of the level of the parent split currently |
|
| 1604 |
#' being summarized. Can additionally take any optional argument supported by analysis functions. (see [analyze()]). |
|
| 1605 |
#' |
|
| 1606 |
#' In addition, if complex custom functions are needed, we suggest checking the available [additional_fun_params] |
|
| 1607 |
#' that can be used in `cfun`. |
|
| 1608 |
#' |
|
| 1609 |
#' @examples |
|
| 1610 |
#' DM2 <- subset(DM, COUNTRY %in% c("USA", "CAN", "CHN"))
|
|
| 1611 |
#' |
|
| 1612 |
#' lyt <- basic_table() %>% |
|
| 1613 |
#' split_cols_by("ARM") %>%
|
|
| 1614 |
#' split_rows_by("COUNTRY", split_fun = drop_split_levels) %>%
|
|
| 1615 |
#' summarize_row_groups(label_fstr = "%s (n)") %>% |
|
| 1616 |
#' analyze("AGE", afun = list_wrap_x(summary), format = "xx.xx")
|
|
| 1617 |
#' lyt |
|
| 1618 |
#' |
|
| 1619 |
#' tbl <- build_table(lyt, DM2) |
|
| 1620 |
#' tbl |
|
| 1621 |
#' |
|
| 1622 |
#' row_paths_summary(tbl) # summary count is a content table |
|
| 1623 |
#' |
|
| 1624 |
#' ## use a cfun and extra_args to customize summarization |
|
| 1625 |
#' ## behavior |
|
| 1626 |
#' sfun <- function(x, labelstr, trim) {
|
|
| 1627 |
#' in_rows( |
|
| 1628 |
#' c(mean(x, trim = trim), trim), |
|
| 1629 |
#' .formats = "xx.x (xx.x%)", |
|
| 1630 |
#' .labels = sprintf( |
|
| 1631 |
#' "%s (Trimmed mean and trim %%)", |
|
| 1632 |
#' labelstr |
|
| 1633 |
#' ) |
|
| 1634 |
#' ) |
|
| 1635 |
#' } |
|
| 1636 |
#' |
|
| 1637 |
#' lyt2 <- basic_table(show_colcounts = TRUE) %>% |
|
| 1638 |
#' split_cols_by("ARM") %>%
|
|
| 1639 |
#' split_rows_by("COUNTRY", split_fun = drop_split_levels) %>%
|
|
| 1640 |
#' summarize_row_groups("AGE",
|
|
| 1641 |
#' cfun = sfun, |
|
| 1642 |
#' extra_args = list(trim = .2) |
|
| 1643 |
#' ) %>% |
|
| 1644 |
#' analyze("AGE", afun = list_wrap_x(summary), format = "xx.xx") %>%
|
|
| 1645 |
#' append_topleft(c("Country", " Age"))
|
|
| 1646 |
#' |
|
| 1647 |
#' tbl2 <- build_table(lyt2, DM2) |
|
| 1648 |
#' tbl2 |
|
| 1649 |
#' |
|
| 1650 |
#' @author Gabriel Becker |
|
| 1651 |
#' @export |
|
| 1652 |
summarize_row_groups <- function(lyt, |
|
| 1653 |
var = "", |
|
| 1654 |
label_fstr = "%s", |
|
| 1655 |
format = "xx (xx.x%)", |
|
| 1656 |
na_str = "-", |
|
| 1657 |
cfun = NULL, |
|
| 1658 |
indent_mod = 0L, |
|
| 1659 |
extra_args = list()) {
|
|
| 1660 | 125x |
if (is.null(cfun)) {
|
| 1661 | 111x |
if (is.character(format) && length(gregexpr("xx(\\.x*){0,1}", format)[[1]]) == 1) {
|
| 1662 | 1x |
cfun <- .count_raw_constr(var, format, label_fstr) |
| 1663 |
} else {
|
|
| 1664 | 110x |
cfun <- .count_wpcts_constr(var, format, label_fstr) |
| 1665 |
} |
|
| 1666 |
} |
|
| 1667 | 125x |
cfun <- .validate_cfuns(cfun) |
| 1668 | 125x |
.add_row_summary(lyt, |
| 1669 | 125x |
cfun = cfun, |
| 1670 | 125x |
cformat = format, |
| 1671 | 125x |
cna_str = na_str, |
| 1672 | 125x |
indent_mod = indent_mod, |
| 1673 | 125x |
cvar = var, |
| 1674 | 125x |
extra_args = extra_args |
| 1675 |
) |
|
| 1676 |
} |
|
| 1677 | ||
| 1678 |
#' Add the column population counts to the header |
|
| 1679 |
#' |
|
| 1680 |
#' Add the data derived column counts. |
|
| 1681 |
#' |
|
| 1682 |
#' @details It is often the case that the the column counts derived from the |
|
| 1683 |
#' input data to [build_table()] is not representative of the population counts. |
|
| 1684 |
#' For example, if events are counted in the table and the header should |
|
| 1685 |
#' display the number of subjects and not the total number of events. |
|
| 1686 |
#' |
|
| 1687 |
#' @inheritParams lyt_args |
|
| 1688 |
#' |
|
| 1689 |
#' @inherit split_cols_by return |
|
| 1690 |
#' |
|
| 1691 |
#' @examples |
|
| 1692 |
#' lyt <- basic_table() %>% |
|
| 1693 |
#' split_cols_by("ARM") %>%
|
|
| 1694 |
#' add_colcounts() %>% |
|
| 1695 |
#' split_rows_by("RACE", split_fun = drop_split_levels) %>%
|
|
| 1696 |
#' analyze("AGE", afun = function(x) list(min = min(x), max = max(x)))
|
|
| 1697 |
#' lyt |
|
| 1698 |
#' |
|
| 1699 |
#' tbl <- build_table(lyt, DM) |
|
| 1700 |
#' tbl |
|
| 1701 |
#' |
|
| 1702 |
#' @author Gabriel Becker |
|
| 1703 |
#' @export |
|
| 1704 |
add_colcounts <- function(lyt, format = "(N=xx)") {
|
|
| 1705 | 5x |
if (is.null(lyt)) {
|
| 1706 | ! |
lyt <- PreDataTableLayouts() |
| 1707 |
} |
|
| 1708 | 5x |
disp_ccounts(lyt) <- TRUE |
| 1709 | 5x |
colcount_format(lyt) <- format |
| 1710 | 5x |
lyt |
| 1711 |
} |
|
| 1712 | ||
| 1713 |
## Currently existing tables can ONLY be added as new entries at the top level, never at any level of nesting. |
|
| 1714 |
#' Add an already calculated table to the layout |
|
| 1715 |
#' |
|
| 1716 |
#' @inheritParams lyt_args |
|
| 1717 |
#' @inheritParams gen_args |
|
| 1718 |
#' |
|
| 1719 |
#' @inherit split_cols_by return |
|
| 1720 |
#' |
|
| 1721 |
#' @examples |
|
| 1722 |
#' lyt1 <- basic_table() %>% |
|
| 1723 |
#' split_cols_by("ARM") %>%
|
|
| 1724 |
#' analyze("AGE", afun = mean, format = "xx.xx")
|
|
| 1725 |
#' |
|
| 1726 |
#' tbl1 <- build_table(lyt1, DM) |
|
| 1727 |
#' tbl1 |
|
| 1728 |
#' |
|
| 1729 |
#' lyt2 <- basic_table() %>% |
|
| 1730 |
#' split_cols_by("ARM") %>%
|
|
| 1731 |
#' analyze("AGE", afun = sd, format = "xx.xx") %>%
|
|
| 1732 |
#' add_existing_table(tbl1) |
|
| 1733 |
#' |
|
| 1734 |
#' tbl2 <- build_table(lyt2, DM) |
|
| 1735 |
#' tbl2 |
|
| 1736 |
#' |
|
| 1737 |
#' table_structure(tbl2) |
|
| 1738 |
#' row_paths_summary(tbl2) |
|
| 1739 |
#' |
|
| 1740 |
#' @author Gabriel Becker |
|
| 1741 |
#' @export |
|
| 1742 |
add_existing_table <- function(lyt, tt, indent_mod = 0) {
|
|
| 1743 | 1x |
indent_mod(tt) <- indent_mod |
| 1744 | 1x |
lyt <- split_rows( |
| 1745 | 1x |
lyt, |
| 1746 | 1x |
tt, |
| 1747 | 1x |
next_rpos(lyt, nested = FALSE) |
| 1748 |
) |
|
| 1749 | 1x |
lyt |
| 1750 |
} |
|
| 1751 | ||
| 1752 |
## takes_coln = function(f) {
|
|
| 1753 |
## stopifnot(is(f, "function")) |
|
| 1754 |
## forms = names(formals(f)) |
|
| 1755 |
## res = ".N_col" %in% forms |
|
| 1756 |
## res |
|
| 1757 |
## } |
|
| 1758 | ||
| 1759 |
## takes_totn = function(f) {
|
|
| 1760 |
## stopifnot(is(f, "function")) |
|
| 1761 |
## forms = names(formals(f)) |
|
| 1762 |
## res = ".N_total" %in% forms |
|
| 1763 |
## res |
|
| 1764 |
## } |
|
| 1765 | ||
| 1766 |
## use data to transform dynamic cuts to static cuts |
|
| 1767 |
#' @rdname int_methods |
|
| 1768 | 3110x |
setGeneric("fix_dyncuts", function(spl, df) standardGeneric("fix_dyncuts"))
|
| 1769 | ||
| 1770 |
#' @rdname int_methods |
|
| 1771 | 1167x |
setMethod("fix_dyncuts", "Split", function(spl, df) spl)
|
| 1772 | ||
| 1773 |
#' @rdname int_methods |
|
| 1774 |
setMethod( |
|
| 1775 |
"fix_dyncuts", "VarDynCutSplit", |
|
| 1776 |
function(spl, df) {
|
|
| 1777 | 5x |
var <- spl_payload(spl) |
| 1778 | 5x |
varvec <- df[[var]] |
| 1779 | ||
| 1780 | 5x |
cfun <- spl_cutfun(spl) |
| 1781 | 5x |
cuts <- cfun(varvec) |
| 1782 | 5x |
cutlabels <- spl_cutlabelfun(spl)(cuts) |
| 1783 | 5x |
if (length(cutlabels) != length(cuts) - 1 && !is.null(names(cuts))) {
|
| 1784 | 1x |
cutlabels <- names(cuts)[-1] |
| 1785 |
} |
|
| 1786 | ||
| 1787 | 5x |
ret <- make_static_cut_split( |
| 1788 | 5x |
var = var, split_label = obj_label(spl), |
| 1789 | 5x |
cuts = cuts, cutlabels = cutlabels, |
| 1790 | 5x |
cumulative = spl_is_cmlcuts(spl) |
| 1791 |
) |
|
| 1792 |
## ret = VarStaticCutSplit(var = var, split_label = obj_label(spl), |
|
| 1793 |
## cuts = cuts, cutlabels = cutlabels) |
|
| 1794 |
## ## classes are tthe same structurally CumulativeCutSplit |
|
| 1795 |
## ## is just a sentinal so it can hit different make_subset_expr |
|
| 1796 |
## ## method |
|
| 1797 |
## if(spl_is_cmlcuts(spl)) |
|
| 1798 |
## ret = as(ret, "CumulativeCutSplit") |
|
| 1799 | 5x |
ret |
| 1800 |
} |
|
| 1801 |
) |
|
| 1802 | ||
| 1803 |
#' @rdname int_methods |
|
| 1804 |
setMethod( |
|
| 1805 |
"fix_dyncuts", "VTableTree", |
|
| 1806 | 1x |
function(spl, df) spl |
| 1807 |
) |
|
| 1808 | ||
| 1809 |
.fd_helper <- function(spl, df) {
|
|
| 1810 | 1559x |
lst <- lapply(spl, fix_dyncuts, df = df) |
| 1811 | 1559x |
spl@.Data <- lst |
| 1812 | 1559x |
spl |
| 1813 |
} |
|
| 1814 | ||
| 1815 |
#' @rdname int_methods |
|
| 1816 |
setMethod( |
|
| 1817 |
"fix_dyncuts", "PreDataRowLayout", |
|
| 1818 |
function(spl, df) {
|
|
| 1819 |
# rt = root_spl(spl) |
|
| 1820 | 378x |
ret <- .fd_helper(spl, df) |
| 1821 |
# root_spl(ret) = rt |
|
| 1822 | 378x |
ret |
| 1823 |
} |
|
| 1824 |
) |
|
| 1825 | ||
| 1826 |
#' @rdname int_methods |
|
| 1827 |
setMethod( |
|
| 1828 |
"fix_dyncuts", "PreDataColLayout", |
|
| 1829 |
function(spl, df) {
|
|
| 1830 |
# rt = root_spl(spl) |
|
| 1831 | 378x |
ret <- .fd_helper(spl, df) |
| 1832 |
# root_spl(ret) = rt |
|
| 1833 |
# disp_ccounts(ret) = disp_ccounts(spl) |
|
| 1834 |
# colcount_format(ret) = colcount_format(spl) |
|
| 1835 | 378x |
ret |
| 1836 |
} |
|
| 1837 |
) |
|
| 1838 | ||
| 1839 |
#' @rdname int_methods |
|
| 1840 |
setMethod( |
|
| 1841 |
"fix_dyncuts", "SplitVector", |
|
| 1842 |
function(spl, df) {
|
|
| 1843 | 803x |
.fd_helper(spl, df) |
| 1844 |
} |
|
| 1845 |
) |
|
| 1846 | ||
| 1847 |
#' @rdname int_methods |
|
| 1848 |
setMethod( |
|
| 1849 |
"fix_dyncuts", "PreDataTableLayouts", |
|
| 1850 |
function(spl, df) {
|
|
| 1851 | 378x |
rlayout(spl) <- fix_dyncuts(rlayout(spl), df) |
| 1852 | 378x |
clayout(spl) <- fix_dyncuts(clayout(spl), df) |
| 1853 | 378x |
spl |
| 1854 |
} |
|
| 1855 |
) |
|
| 1856 | ||
| 1857 |
## Manual column construction in a simple (seeming to the user) way. |
|
| 1858 |
#' Manual column declaration |
|
| 1859 |
#' |
|
| 1860 |
#' @param ... one or more vectors of levels to appear in the column space. If more than one set of levels is given, |
|
| 1861 |
#' the values of the second are nested within each value of the first, and so on. |
|
| 1862 |
#' @param .lst (`list`)\cr a list of sets of levels, by default populated via `list(...)`. |
|
| 1863 |
#' @param ccount_format (`FormatSpec`)\cr the format to use when counts are displayed. |
|
| 1864 |
#' |
|
| 1865 |
#' @return An `InstantiatedColumnInfo` object, suitable for declaring the column structure for a manually constructed |
|
| 1866 |
#' table. |
|
| 1867 |
#' |
|
| 1868 |
#' @examples |
|
| 1869 |
#' # simple one level column space |
|
| 1870 |
#' rows <- lapply(1:5, function(i) {
|
|
| 1871 |
#' DataRow(rep(i, times = 3)) |
|
| 1872 |
#' }) |
|
| 1873 |
#' tbl <- TableTree(kids = rows, cinfo = manual_cols(split = c("a", "b", "c")))
|
|
| 1874 |
#' tbl |
|
| 1875 |
#' |
|
| 1876 |
#' # manually declared nesting |
|
| 1877 |
#' tbl2 <- TableTree( |
|
| 1878 |
#' kids = list(DataRow(as.list(1:4))), |
|
| 1879 |
#' cinfo = manual_cols( |
|
| 1880 |
#' Arm = c("Arm A", "Arm B"),
|
|
| 1881 |
#' Gender = c("M", "F")
|
|
| 1882 |
#' ) |
|
| 1883 |
#' ) |
|
| 1884 |
#' tbl2 |
|
| 1885 |
#' |
|
| 1886 |
#' @author Gabriel Becker |
|
| 1887 |
#' @export |
|
| 1888 |
manual_cols <- function(..., .lst = list(...), ccount_format = NULL) {
|
|
| 1889 | 41x |
if (is.null(names(.lst))) {
|
| 1890 | 41x |
names(.lst) <- paste("colsplit", seq_along(.lst))
|
| 1891 |
} |
|
| 1892 | ||
| 1893 | 41x |
splvec <- SplitVector(lst = mapply(ManualSplit, |
| 1894 | 41x |
levels = .lst, |
| 1895 | 41x |
label = names(.lst) |
| 1896 |
)) |
|
| 1897 | 41x |
ctree <- splitvec_to_coltree(data.frame(), splvec = splvec, pos = TreePos(), global_cc_format = ccount_format) |
| 1898 | ||
| 1899 | 41x |
ret <- InstantiatedColumnInfo(treelyt = ctree) |
| 1900 | 41x |
rm_all_colcounts(ret) |
| 1901 |
} |
|
| 1902 | ||
| 1903 | ||
| 1904 |
#' Set all column counts at all levels of nesting to NA |
|
| 1905 |
#' |
|
| 1906 |
#' @inheritParams gen_args |
|
| 1907 |
#' |
|
| 1908 |
#' @return `obj` with all column counts reset to missing |
|
| 1909 |
#' |
|
| 1910 |
#' @export |
|
| 1911 |
#' @examples |
|
| 1912 |
#' lyt <- basic_table() %>% |
|
| 1913 |
#' split_cols_by("ARM") %>%
|
|
| 1914 |
#' split_cols_by("SEX") %>%
|
|
| 1915 |
#' analyze("AGE")
|
|
| 1916 |
#' tbl <- build_table(lyt, ex_adsl) |
|
| 1917 |
#' |
|
| 1918 |
#' # before |
|
| 1919 |
#' col_counts(tbl) |
|
| 1920 |
#' tbl <- rm_all_colcounts(tbl) |
|
| 1921 |
#' col_counts(tbl) |
|
| 1922 | 229x |
setGeneric("rm_all_colcounts", function(obj) standardGeneric("rm_all_colcounts"))
|
| 1923 | ||
| 1924 |
#' @rdname rm_all_colcounts |
|
| 1925 |
#' @export |
|
| 1926 |
setMethod( |
|
| 1927 |
"rm_all_colcounts", "VTableTree", |
|
| 1928 |
function(obj) {
|
|
| 1929 | ! |
cinfo <- col_info(obj) |
| 1930 | ! |
cinfo <- rm_all_colcounts(cinfo) |
| 1931 | ! |
col_info(obj) <- cinfo |
| 1932 | ! |
obj |
| 1933 |
} |
|
| 1934 |
) |
|
| 1935 | ||
| 1936 |
#' @rdname rm_all_colcounts |
|
| 1937 |
#' @export |
|
| 1938 |
setMethod( |
|
| 1939 |
"rm_all_colcounts", "InstantiatedColumnInfo", |
|
| 1940 |
function(obj) {
|
|
| 1941 | 41x |
ctree <- coltree(obj) |
| 1942 | 41x |
ctree <- rm_all_colcounts(ctree) |
| 1943 | 41x |
coltree(obj) <- ctree |
| 1944 | 41x |
obj |
| 1945 |
} |
|
| 1946 |
) |
|
| 1947 | ||
| 1948 |
#' @rdname rm_all_colcounts |
|
| 1949 |
#' @export |
|
| 1950 |
setMethod( |
|
| 1951 |
"rm_all_colcounts", "LayoutColTree", |
|
| 1952 |
function(obj) {
|
|
| 1953 | 52x |
obj@column_count <- NA_integer_ |
| 1954 | 52x |
tree_children(obj) <- lapply(tree_children(obj), rm_all_colcounts) |
| 1955 | 52x |
obj |
| 1956 |
} |
|
| 1957 |
) |
|
| 1958 | ||
| 1959 |
#' @rdname rm_all_colcounts |
|
| 1960 |
#' @export |
|
| 1961 |
setMethod( |
|
| 1962 |
"rm_all_colcounts", "LayoutColLeaf", |
|
| 1963 |
function(obj) {
|
|
| 1964 | 136x |
obj@column_count <- NA_integer_ |
| 1965 | 136x |
obj |
| 1966 |
} |
|
| 1967 |
) |
|
| 1968 | ||
| 1969 |
#' Returns a function that coerces the return values of a function to a list |
|
| 1970 |
#' |
|
| 1971 |
#' @param f (`function`)\cr the function to wrap. |
|
| 1972 |
#' |
|
| 1973 |
#' @details |
|
| 1974 |
#' `list_wrap_x` generates a wrapper which takes `x` as its first argument, while `list_wrap_df` generates an |
|
| 1975 |
#' otherwise identical wrapper function whose first argument is named `df`. |
|
| 1976 |
#' |
|
| 1977 |
#' We provide both because when using the functions as tabulation in [analyze()], functions which take `df` as |
|
| 1978 |
#' their first argument are passed the full subset data frame, while those which accept anything else notably |
|
| 1979 |
#' including `x` are passed only the relevant subset of the variable being analyzed. |
|
| 1980 |
#' |
|
| 1981 |
#' @return A function that returns a list of `CellValue` objects. |
|
| 1982 |
#' |
|
| 1983 |
#' @examples |
|
| 1984 |
#' summary(iris$Sepal.Length) |
|
| 1985 |
#' |
|
| 1986 |
#' f <- list_wrap_x(summary) |
|
| 1987 |
#' f(x = iris$Sepal.Length) |
|
| 1988 |
#' |
|
| 1989 |
#' f2 <- list_wrap_df(summary) |
|
| 1990 |
#' f2(df = iris$Sepal.Length) |
|
| 1991 |
#' |
|
| 1992 |
#' @author Gabriel Becker |
|
| 1993 |
#' @rdname list_wrap |
|
| 1994 |
#' @export |
|
| 1995 |
list_wrap_x <- function(f) {
|
|
| 1996 | 17x |
function(x, ...) {
|
| 1997 | 74x |
vs <- as.list(f(x, ...)) |
| 1998 | 74x |
ret <- mapply( |
| 1999 | 74x |
function(v, nm) {
|
| 2000 | 258x |
rcell(v, label = nm) |
| 2001 |
}, |
|
| 2002 | 74x |
v = vs, |
| 2003 | 74x |
nm = names(vs) |
| 2004 |
) |
|
| 2005 | 74x |
ret |
| 2006 |
} |
|
| 2007 |
} |
|
| 2008 | ||
| 2009 |
#' @rdname list_wrap |
|
| 2010 |
#' @export |
|
| 2011 |
list_wrap_df <- function(f) {
|
|
| 2012 | 1x |
function(df, ...) {
|
| 2013 | 1x |
vs <- as.list(f(df, ...)) |
| 2014 | 1x |
ret <- mapply( |
| 2015 | 1x |
function(v, nm) {
|
| 2016 | 6x |
rcell(v, label = nm) |
| 2017 |
}, |
|
| 2018 | 1x |
v = vs, |
| 2019 | 1x |
nm = names(vs) |
| 2020 |
) |
|
| 2021 | 1x |
ret |
| 2022 |
} |
|
| 2023 |
} |
|
| 2024 | ||
| 2025 |
#' Layout with 1 column and zero rows |
|
| 2026 |
#' |
|
| 2027 |
#' Every layout must start with a basic table. |
|
| 2028 |
#' |
|
| 2029 |
#' @inheritParams constr_args |
|
| 2030 |
#' @param show_colcounts (`logical(1)`)\cr Indicates whether the lowest level of |
|
| 2031 |
#' applied to data. `NA`, the default, indicates that the `show_colcounts` |
|
| 2032 |
#' argument(s) passed to the relevant calls to `split_cols_by*` |
|
| 2033 |
#' functions. Non-missing values will override the behavior specified in |
|
| 2034 |
#' column splitting layout instructions which create the lowest level, or |
|
| 2035 |
#' leaf, columns. |
|
| 2036 |
#' @param colcount_format (`string`)\cr format for use when displaying the column counts. Must be 1d, or 2d |
|
| 2037 |
#' where one component is a percent. This will also apply to any displayed higher |
|
| 2038 |
#' level column counts where an explicit format was not specified. Defaults to `"(N=xx)"`. See Details below. |
|
| 2039 |
#' @param top_level_section_div (`character(1)`)\cr if assigned a single character, the first (top level) split |
|
| 2040 |
#' or division of the table will be highlighted by a line made of that character. See [section_div] for more |
|
| 2041 |
#' information. |
|
| 2042 |
#' |
|
| 2043 |
#' @details |
|
| 2044 |
#' `colcount_format` is ignored if `show_colcounts` is `FALSE` (the default). When `show_colcounts` is `TRUE`, |
|
| 2045 |
#' and `colcount_format` is 2-dimensional with a percent component, the value component for the percent is always |
|
| 2046 |
#' populated with `1` (i.e. 100%). 1d formats are used to render the counts exactly as they normally would be, |
|
| 2047 |
#' while 2d formats which don't include a percent, and all 3d formats result in an error. Formats in the form of |
|
| 2048 |
#' functions are not supported for `colcount` format. See [formatters::list_valid_format_labels()] for the list |
|
| 2049 |
#' of valid format labels to select from. |
|
| 2050 |
#' |
|
| 2051 |
#' @inherit split_cols_by return |
|
| 2052 |
#' |
|
| 2053 |
#' @note |
|
| 2054 |
#' - Because percent components in `colcount_format` are *always* populated with the value 1, we can get arguably |
|
| 2055 |
#' strange results, such as that individual arm columns and a combined "all patients" column all list "100%" as |
|
| 2056 |
#' their percentage, even though the individual arm columns represent strict subsets of the "all patients" column. |
|
| 2057 |
#' |
|
| 2058 |
#' - Note that subtitles ([formatters::subtitles()]) and footers ([formatters::main_footer()] and |
|
| 2059 |
#' [formatters::prov_footer()]) that span more than one line can be supplied as a character vector to maintain |
|
| 2060 |
#' indentation on multiple lines. |
|
| 2061 |
#' |
|
| 2062 |
#' @examples |
|
| 2063 |
#' lyt <- basic_table() %>% |
|
| 2064 |
#' analyze("AGE", afun = mean)
|
|
| 2065 |
#' |
|
| 2066 |
#' tbl <- build_table(lyt, DM) |
|
| 2067 |
#' tbl |
|
| 2068 |
#' |
|
| 2069 |
#' lyt2 <- basic_table( |
|
| 2070 |
#' title = "Title of table", |
|
| 2071 |
#' subtitles = c("a number", "of subtitles"),
|
|
| 2072 |
#' main_footer = "test footer", |
|
| 2073 |
#' prov_footer = paste( |
|
| 2074 |
#' "test.R program, executed at", |
|
| 2075 |
#' Sys.time() |
|
| 2076 |
#' ) |
|
| 2077 |
#' ) %>% |
|
| 2078 |
#' split_cols_by("ARM") %>%
|
|
| 2079 |
#' analyze("AGE", mean)
|
|
| 2080 |
#' |
|
| 2081 |
#' tbl2 <- build_table(lyt2, DM) |
|
| 2082 |
#' tbl2 |
|
| 2083 |
#' |
|
| 2084 |
#' lyt3 <- basic_table( |
|
| 2085 |
#' show_colcounts = TRUE, |
|
| 2086 |
#' colcount_format = "xx. (xx.%)" |
|
| 2087 |
#' ) %>% |
|
| 2088 |
#' split_cols_by("ARM")
|
|
| 2089 |
#' |
|
| 2090 |
#' @export |
|
| 2091 |
basic_table <- function(title = "", |
|
| 2092 |
subtitles = character(), |
|
| 2093 |
main_footer = character(), |
|
| 2094 |
prov_footer = character(), |
|
| 2095 |
show_colcounts = NA, # FALSE, |
|
| 2096 |
colcount_format = "(N=xx)", |
|
| 2097 |
header_section_div = NA_character_, |
|
| 2098 |
top_level_section_div = NA_character_, |
|
| 2099 |
inset = 0L) {
|
|
| 2100 | 363x |
inset <- as.integer(inset) |
| 2101 | 363x |
if (is.na(inset) || inset < 0L) {
|
| 2102 | 2x |
stop("Got invalid table_inset value, must be an integer > 0")
|
| 2103 |
} |
|
| 2104 | 361x |
.check_header_section_div(header_section_div) |
| 2105 | 361x |
checkmate::assert_character(top_level_section_div, len = 1, n.chars = 1) |
| 2106 | ||
| 2107 | 361x |
ret <- PreDataTableLayouts( |
| 2108 | 361x |
title = title, |
| 2109 | 361x |
subtitles = subtitles, |
| 2110 | 361x |
main_footer = main_footer, |
| 2111 | 361x |
prov_footer = prov_footer, |
| 2112 | 361x |
header_section_div = header_section_div, |
| 2113 | 361x |
top_level_section_div = top_level_section_div, |
| 2114 | 361x |
table_inset = as.integer(inset) |
| 2115 |
) |
|
| 2116 | ||
| 2117 |
## unconditional now, NA case is handled in cinfo construction |
|
| 2118 | 361x |
disp_ccounts(ret) <- show_colcounts |
| 2119 | 361x |
colcount_format(ret) <- colcount_format |
| 2120 |
## if (isTRUE(show_colcounts)) {
|
|
| 2121 |
## ret <- add_colcounts(ret, format = colcount_format) |
|
| 2122 |
## } |
|
| 2123 | 361x |
ret |
| 2124 |
} |
|
| 2125 | ||
| 2126 |
#' Append a description to the 'top-left' materials for the layout |
|
| 2127 |
#' |
|
| 2128 |
#' This function *adds* `newlines` to the current set of "top-left materials". |
|
| 2129 |
#' |
|
| 2130 |
#' @details |
|
| 2131 |
#' Adds `newlines` to the set of strings representing the 'top-left' materials declared in the layout (the content |
|
| 2132 |
#' displayed to the left of the column labels when the resulting tables are printed). |
|
| 2133 |
#' |
|
| 2134 |
#' Top-left material strings are stored and then displayed *exactly as is*, no structure or indenting is applied to |
|
| 2135 |
#' them either when they are added or when they are displayed. |
|
| 2136 |
#' |
|
| 2137 |
#' @inheritParams lyt_args |
|
| 2138 |
#' @param newlines (`character`)\cr the new line(s) to be added to the materials. |
|
| 2139 |
#' |
|
| 2140 |
#' @note |
|
| 2141 |
#' Currently, where in the construction of the layout this is called makes no difference, as it is independent of |
|
| 2142 |
#' the actual splitting keywords. This may change in the future. |
|
| 2143 |
#' |
|
| 2144 |
#' This function is experimental, its name and the details of its behavior are subject to change in future versions. |
|
| 2145 |
#' |
|
| 2146 |
#' @inherit split_cols_by return |
|
| 2147 |
#' |
|
| 2148 |
#' @seealso [top_left()] |
|
| 2149 |
#' |
|
| 2150 |
#' @examplesIf require(dplyr) |
|
| 2151 |
#' library(dplyr) |
|
| 2152 |
#' |
|
| 2153 |
#' DM2 <- DM %>% mutate(RACE = factor(RACE), SEX = factor(SEX)) |
|
| 2154 |
#' |
|
| 2155 |
#' lyt <- basic_table() %>% |
|
| 2156 |
#' split_cols_by("ARM") %>%
|
|
| 2157 |
#' split_cols_by("SEX") %>%
|
|
| 2158 |
#' split_rows_by("RACE") %>%
|
|
| 2159 |
#' append_topleft("Ethnicity") %>%
|
|
| 2160 |
#' analyze("AGE") %>%
|
|
| 2161 |
#' append_topleft(" Age")
|
|
| 2162 |
#' |
|
| 2163 |
#' tbl <- build_table(lyt, DM2) |
|
| 2164 |
#' tbl |
|
| 2165 |
#' |
|
| 2166 |
#' @export |
|
| 2167 |
append_topleft <- function(lyt, newlines) {
|
|
| 2168 | 51x |
stopifnot( |
| 2169 | 51x |
is(lyt, "PreDataTableLayouts"), |
| 2170 | 51x |
is(newlines, "character") |
| 2171 |
) |
|
| 2172 | 51x |
lyt@top_left <- c(lyt@top_left, newlines) |
| 2173 | 51x |
lyt |
| 2174 |
} |
| 1 |
# Generics and how they are used directly ------------------------------------- |
|
| 2 | ||
| 3 |
## check_validsplit - Check if the split is valid for the data, error if not |
|
| 4 | ||
| 5 |
## .apply_spl_extras - Generate Extras |
|
| 6 | ||
| 7 |
## .apply_spl_datapart - generate data partition |
|
| 8 | ||
| 9 |
## .apply_spl_rawvals - Generate raw (i.e. non SplitValue object) partition values |
|
| 10 | ||
| 11 |
setGeneric( |
|
| 12 |
".applysplit_rawvals", |
|
| 13 | 1101x |
function(spl, df) standardGeneric(".applysplit_rawvals")
|
| 14 |
) |
|
| 15 | ||
| 16 |
setGeneric( |
|
| 17 |
".applysplit_datapart", |
|
| 18 | 1185x |
function(spl, df, vals) standardGeneric(".applysplit_datapart")
|
| 19 |
) |
|
| 20 | ||
| 21 |
setGeneric( |
|
| 22 |
".applysplit_extras", |
|
| 23 | 1185x |
function(spl, df, vals) standardGeneric(".applysplit_extras")
|
| 24 |
) |
|
| 25 | ||
| 26 |
setGeneric( |
|
| 27 |
".applysplit_partlabels", |
|
| 28 | 1182x |
function(spl, df, vals, labels) standardGeneric(".applysplit_partlabels")
|
| 29 |
) |
|
| 30 | ||
| 31 |
setGeneric( |
|
| 32 |
"check_validsplit", |
|
| 33 | 2523x |
function(spl, df) standardGeneric("check_validsplit")
|
| 34 |
) |
|
| 35 | ||
| 36 |
setGeneric( |
|
| 37 |
".applysplit_ref_vals", |
|
| 38 | 17x |
function(spl, df, vals) standardGeneric(".applysplit_ref_vals")
|
| 39 |
) |
|
| 40 |
# Custom split fncs ------------------------------------------------------------ |
|
| 41 |
#' Custom split functions |
|
| 42 |
#' |
|
| 43 |
#' Split functions provide the work-horse for `rtables`'s generalized partitioning. These functions accept a (sub)set |
|
| 44 |
#' of incoming data and a split object, and return "splits" of that data. |
|
| 45 |
#' |
|
| 46 |
#' @section Custom Splitting Function Details: |
|
| 47 |
#' |
|
| 48 |
#' User-defined custom split functions can perform any type of computation on the incoming data provided that they |
|
| 49 |
#' meet the requirements for generating "splits" of the incoming data based on the split object. |
|
| 50 |
#' |
|
| 51 |
#' Split functions are functions that accept: |
|
| 52 |
#' \describe{
|
|
| 53 |
#' \item{df}{a `data.frame` of incoming data to be split.}
|
|
| 54 |
#' \item{spl}{a Split object. This is largely an internal detail custom functions will not need to worry about,
|
|
| 55 |
#' but `obj_name(spl)`, for example, will give the name of the split as it will appear in paths in the resulting |
|
| 56 |
#' table.} |
|
| 57 |
#' \item{vals}{any pre-calculated values. If given non-`NULL` values, the values returned should match these.
|
|
| 58 |
#' Should be `NULL` in most cases and can usually be ignored.} |
|
| 59 |
#' \item{labels}{any pre-calculated value labels. Same as above for `values`.}
|
|
| 60 |
#' \item{trim}{if `TRUE`, resulting splits that are empty are removed.}
|
|
| 61 |
#' \item{(optional) .spl_context}{a `data.frame` describing previously performed splits which collectively
|
|
| 62 |
#' arrived at `df`.} |
|
| 63 |
#' } |
|
| 64 |
#' |
|
| 65 |
#' The function must then output a named `list` with the following elements: |
|
| 66 |
#' |
|
| 67 |
#' \describe{
|
|
| 68 |
#' \item{values}{the vector of all values corresponding to the splits of `df`.}
|
|
| 69 |
#' \item{datasplit}{a list of `data.frame`s representing the groupings of the actual observations from `df`.}
|
|
| 70 |
#' \item{labels}{a character vector giving a string label for each value listed in the `values` element above.}
|
|
| 71 |
#' \item{(optional) extras}{if present, extra arguments are to be passed to summary and analysis functions
|
|
| 72 |
#' whenever they are executed on the corresponding element of `datasplit` or a subset thereof.} |
|
| 73 |
#' } |
|
| 74 |
#' |
|
| 75 |
#' One way to generate custom splitting functions is to wrap existing split functions and modify either the incoming |
|
| 76 |
#' data before they are called or their outputs. |
|
| 77 |
#' |
|
| 78 |
#' @seealso [make_split_fun()] for the API for creating custom split functions, and [split_funcs] for a variety of |
|
| 79 |
#' pre-defined split functions. |
|
| 80 |
#' |
|
| 81 |
#' @examples |
|
| 82 |
#' # Example of a picky split function. The number of values in the column variable |
|
| 83 |
#' # var decrees if we are going to print also the column with all observation |
|
| 84 |
#' # or not. |
|
| 85 |
#' |
|
| 86 |
#' picky_splitter <- function(var) {
|
|
| 87 |
#' # Main layout function |
|
| 88 |
#' function(df, spl, vals, labels, trim) {
|
|
| 89 |
#' orig_vals <- vals |
|
| 90 |
#' |
|
| 91 |
#' # Check for number of levels if all are selected |
|
| 92 |
#' if (is.null(vals)) {
|
|
| 93 |
#' vec <- df[[var]] |
|
| 94 |
#' vals <- unique(vec) |
|
| 95 |
#' } |
|
| 96 |
#' |
|
| 97 |
#' # Do a split with or without All obs |
|
| 98 |
#' if (length(vals) == 1) {
|
|
| 99 |
#' do_base_split(spl = spl, df = df, vals = vals, labels = labels, trim = trim) |
|
| 100 |
#' } else {
|
|
| 101 |
#' fnc_tmp <- add_overall_level("Overall", label = "All Obs", first = FALSE)
|
|
| 102 |
#' fnc_tmp(df = df, spl = spl, vals = orig_vals, trim = trim) |
|
| 103 |
#' } |
|
| 104 |
#' } |
|
| 105 |
#' } |
|
| 106 |
#' |
|
| 107 |
#' # Data sub-set |
|
| 108 |
#' d1 <- subset(ex_adsl, ARM == "A: Drug X" | (ARM == "B: Placebo" & SEX == "F")) |
|
| 109 |
#' d1 <- subset(d1, SEX %in% c("M", "F"))
|
|
| 110 |
#' d1$SEX <- factor(d1$SEX) |
|
| 111 |
#' |
|
| 112 |
#' # This table uses the number of values in the SEX column to add the overall col or not |
|
| 113 |
#' lyt <- basic_table() %>% |
|
| 114 |
#' split_cols_by("ARM", split_fun = drop_split_levels) %>%
|
|
| 115 |
#' split_cols_by("SEX", split_fun = picky_splitter("SEX")) %>%
|
|
| 116 |
#' analyze("AGE", show_labels = "visible")
|
|
| 117 |
#' tbl <- build_table(lyt, d1) |
|
| 118 |
#' tbl |
|
| 119 |
#' |
|
| 120 |
#' @name custom_split_funs |
|
| 121 |
NULL |
|
| 122 | ||
| 123 |
## do various cleaning, and naming, plus |
|
| 124 |
## ensure partinfo$values contains SplitValue objects only |
|
| 125 |
.fixupvals <- function(partinfo) {
|
|
| 126 | 1212x |
if (is.factor(partinfo$labels)) {
|
| 127 | ! |
partinfo$labels <- as.character(partinfo$labels) |
| 128 |
} |
|
| 129 | ||
| 130 | 1212x |
vals <- partinfo$values |
| 131 | 1212x |
if (is.factor(vals)) {
|
| 132 | ! |
vals <- levels(vals)[vals] |
| 133 |
} |
|
| 134 | 1212x |
extr <- partinfo$extras |
| 135 | 1212x |
dpart <- partinfo$datasplit |
| 136 | 1212x |
labels <- partinfo$labels |
| 137 | 1212x |
if (is.null(labels)) {
|
| 138 | ! |
if (!is.null(names(vals))) {
|
| 139 | ! |
labels <- names(vals) |
| 140 | ! |
} else if (!is.null(names(dpart))) {
|
| 141 | ! |
labels <- names(dpart) |
| 142 | ! |
} else if (!is.null(names(extr))) {
|
| 143 | ! |
labels <- names(extr) |
| 144 |
} |
|
| 145 |
} |
|
| 146 | ||
| 147 | 1212x |
subsets <- partinfo$subset_exprs |
| 148 | 1212x |
if (is.null(subsets)) {
|
| 149 | 1196x |
subsets <- vector(mode = "list", length = length(vals)) |
| 150 |
## use labels here cause we already did all that work |
|
| 151 |
## to get the names on the labels vector right |
|
| 152 | 1196x |
names(subsets) <- names(labels) |
| 153 |
} |
|
| 154 | ||
| 155 | 1212x |
if (is.null(vals) && !is.null(extr)) {
|
| 156 | ! |
vals <- seq_along(extr) |
| 157 |
} |
|
| 158 | ||
| 159 | 1212x |
if (length(vals) == 0) {
|
| 160 | 13x |
stopifnot(length(extr) == 0) |
| 161 | 13x |
return(partinfo) |
| 162 |
} |
|
| 163 |
## length(vals) > 0 from here down |
|
| 164 | ||
| 165 | 1199x |
if (are(vals, "SplitValue") && !are(vals, "LevelComboSplitValue")) {
|
| 166 | 22x |
if (!is.null(extr)) {
|
| 167 |
## in_ref_cols is in here for some reason even though its already in the SplitValue object. |
|
| 168 |
## https://github.com/insightsengineering/rtables/issues/707#issuecomment-1678810598 |
|
| 169 |
## the if is a bandaid. |
|
| 170 |
## XXX FIXME RIGHT |
|
| 171 | 3x |
sq <- seq_along(vals) |
| 172 | 3x |
if (any(vapply(sq, function(i) !all(names(extr[[i]]) %in% names(splv_extra(vals[[i]]))), TRUE))) {
|
| 173 | ! |
warning( |
| 174 | ! |
"Got a partinfo list with values that are ", |
| 175 | ! |
"already SplitValue objects and non-null extras ", |
| 176 | ! |
"element. This shouldn't happen" |
| 177 |
) |
|
| 178 |
} |
|
| 179 |
} |
|
| 180 |
} else {
|
|
| 181 | 1177x |
if (is.null(extr)) {
|
| 182 | 6x |
extr <- rep(list(list()), length(vals)) |
| 183 |
} |
|
| 184 | 1177x |
vals <- make_splvalue_vec(vals, extr, labels = labels, subset_exprs = subsets) |
| 185 |
} |
|
| 186 |
## we're done with this so take it off |
|
| 187 | 1199x |
partinfo$extras <- NULL |
| 188 | ||
| 189 | 1199x |
vnames <- value_names(vals) |
| 190 | 1199x |
names(vals) <- vnames |
| 191 | 1199x |
partinfo$values <- vals |
| 192 | ||
| 193 | 1199x |
if (!identical(names(dpart), vnames)) {
|
| 194 | 1199x |
names(dpart) <- vnames |
| 195 | 1199x |
partinfo$datasplit <- dpart |
| 196 |
} |
|
| 197 | ||
| 198 | 1199x |
partinfo$labels <- labels |
| 199 | ||
| 200 | 1199x |
stopifnot(length(unique(sapply(partinfo, NROW))) == 1) |
| 201 | 1199x |
partinfo |
| 202 |
} |
|
| 203 | ||
| 204 |
.add_ref_extras <- function(spl, df, partinfo) {
|
|
| 205 |
## this is only the .in_ref_col booleans |
|
| 206 | 17x |
refvals <- .applysplit_ref_vals(spl, df, partinfo$values) |
| 207 | 17x |
ref_ind <- which(unlist(refvals)) |
| 208 | 17x |
stopifnot(length(ref_ind) == 1) |
| 209 | ||
| 210 | 17x |
vnames <- value_names(partinfo$values) |
| 211 | 17x |
if (is.null(partinfo$extras)) {
|
| 212 | 3x |
names(refvals) <- vnames |
| 213 | 3x |
partinfo$extras <- refvals |
| 214 |
} else {
|
|
| 215 | 14x |
newextras <- mapply( |
| 216 | 14x |
function(old, incol, ref_full) {
|
| 217 | 37x |
c(old, list( |
| 218 | 37x |
.in_ref_col = incol, |
| 219 | 37x |
.ref_full = ref_full |
| 220 |
)) |
|
| 221 |
}, |
|
| 222 | 14x |
old = partinfo$extras, |
| 223 | 14x |
incol = unlist(refvals), |
| 224 | 14x |
MoreArgs = list(ref_full = partinfo$datasplit[[ref_ind]]), |
| 225 | 14x |
SIMPLIFY = FALSE |
| 226 |
) |
|
| 227 | 14x |
names(newextras) <- vnames |
| 228 | 14x |
partinfo$extras <- newextras |
| 229 |
} |
|
| 230 | 17x |
partinfo |
| 231 |
} |
|
| 232 | ||
| 233 |
#' Apply basic split (for use in custom split functions) |
|
| 234 |
#' |
|
| 235 |
#' This function is intended for use inside custom split functions. It applies the current split *as if it had no |
|
| 236 |
#' custom splitting function* so that those default splits can be further manipulated. |
|
| 237 |
#' |
|
| 238 |
#' @inheritParams gen_args |
|
| 239 |
#' @param vals (`ANY`)\cr already calculated/known values of the split. Generally should be left as `NULL`. |
|
| 240 |
#' @param labels (`character`)\cr labels associated with `vals`. Should be `NULL` whenever `vals` is, which should |
|
| 241 |
#' almost always be the case. |
|
| 242 |
#' @param trim (`flag`)\cr whether groups corresponding to empty data subsets should be removed. Defaults to |
|
| 243 |
#' `FALSE`. |
|
| 244 |
#' |
|
| 245 |
#' @return The result of the split being applied as if it had no custom split function. See [custom_split_funs]. |
|
| 246 |
#' |
|
| 247 |
#' @examples |
|
| 248 |
#' uneven_splfun <- function(df, spl, vals = NULL, labels = NULL, trim = FALSE) {
|
|
| 249 |
#' ret <- do_base_split(spl, df, vals, labels, trim) |
|
| 250 |
#' if (NROW(df) == 0) {
|
|
| 251 |
#' ret <- lapply(ret, function(x) x[1]) |
|
| 252 |
#' } |
|
| 253 |
#' ret |
|
| 254 |
#' } |
|
| 255 |
#' |
|
| 256 |
#' lyt <- basic_table() %>% |
|
| 257 |
#' split_cols_by("ARM") %>%
|
|
| 258 |
#' split_cols_by_multivar(c("USUBJID", "AESEQ", "BMRKR1"),
|
|
| 259 |
#' varlabels = c("N", "E", "BMR1"),
|
|
| 260 |
#' split_fun = uneven_splfun |
|
| 261 |
#' ) %>% |
|
| 262 |
#' analyze_colvars(list( |
|
| 263 |
#' USUBJID = function(x, ...) length(unique(x)), |
|
| 264 |
#' AESEQ = max, |
|
| 265 |
#' BMRKR1 = mean |
|
| 266 |
#' )) |
|
| 267 |
#' |
|
| 268 |
#' tbl <- build_table(lyt, subset(ex_adae, as.numeric(ARM) <= 2)) |
|
| 269 |
#' tbl |
|
| 270 |
#' |
|
| 271 |
#' @export |
|
| 272 |
do_base_split <- function(spl, df, vals = NULL, labels = NULL, trim = FALSE) {
|
|
| 273 | 13x |
spl2 <- spl |
| 274 | 13x |
split_fun(spl2) <- NULL |
| 275 | 13x |
do_split(spl2, |
| 276 | 13x |
df = df, vals = vals, labels = labels, trim = trim, |
| 277 | 13x |
spl_context = NULL |
| 278 |
) |
|
| 279 |
} |
|
| 280 | ||
| 281 |
### NB This is called at EACH level of recursive splitting |
|
| 282 |
do_split <- function(spl, |
|
| 283 |
df, |
|
| 284 |
vals = NULL, |
|
| 285 |
labels = NULL, |
|
| 286 |
trim = FALSE, |
|
| 287 |
spl_context) {
|
|
| 288 |
## this will error if, e.g., df doesn't have columns |
|
| 289 |
## required by spl, or generally any time the spl |
|
| 290 |
## can't be applied to df |
|
| 291 | 1214x |
check_validsplit(spl, df) |
| 292 |
## note the <- here!!! |
|
| 293 | 1213x |
if (!is.null(splfun <- split_fun(spl))) {
|
| 294 |
## Currently the contract is that split_functions take df, vals, labels and |
|
| 295 |
## return list(values=., datasplit=., labels = .), optionally with |
|
| 296 |
## an additional extras element |
|
| 297 | 370x |
if (func_takes(splfun, ".spl_context")) {
|
| 298 | 23x |
ret <- tryCatch( |
| 299 | 23x |
splfun(df, spl, vals, labels, |
| 300 | 23x |
trim = trim, |
| 301 | 23x |
.spl_context = spl_context |
| 302 |
), |
|
| 303 | 23x |
error = function(e) e |
| 304 | 23x |
) ## rawvalues(spl_context )) |
| 305 |
} else {
|
|
| 306 | 347x |
ret <- tryCatch(splfun(df, spl, vals, labels, trim = trim), |
| 307 | 347x |
error = function(e) e |
| 308 |
) |
|
| 309 |
} |
|
| 310 | 370x |
if (is(ret, "error")) {
|
| 311 | 14x |
stop( |
| 312 | 14x |
"Error applying custom split function: ", ret$message, "\n\tsplit: ", |
| 313 | 14x |
class(spl), " (", payloadmsg(spl), ")\n",
|
| 314 | 14x |
"\toccured at path: ", |
| 315 | 14x |
spl_context_to_disp_path(spl_context), "\n" |
| 316 |
) |
|
| 317 |
} |
|
| 318 |
} else {
|
|
| 319 | 843x |
ret <- .apply_split_inner(df = df, spl = spl, vals = vals, labels = labels, trim = trim) |
| 320 |
} |
|
| 321 | ||
| 322 |
## this adds .ref_full and .in_ref_col |
|
| 323 | 1199x |
if (is(spl, "VarLevWBaselineSplit")) {
|
| 324 | 17x |
ret <- .add_ref_extras(spl, df, ret) |
| 325 |
} |
|
| 326 | ||
| 327 |
## this: |
|
| 328 |
## - guarantees that ret$values contains SplitValue objects |
|
| 329 |
## - removes the extras element since its redundant after the above |
|
| 330 |
## - Ensures datasplit and values lists are named according to labels |
|
| 331 |
## - ensures labels are character not factor |
|
| 332 | 1199x |
ret <- .fixupvals(ret) |
| 333 |
## we didn't put this in .fixupvals because that get called withint he split functions |
|
| 334 |
## created by make_split_fun and its not clear this check should be happening then. |
|
| 335 | 1199x |
if (has_force_pag(spl) && length(ret$datasplit) == 0) { ## this means it's page_by=TRUE
|
| 336 | 3x |
stop( |
| 337 | 3x |
"Page-by split resulted in zero pages (no observed values of split variable?). \n\tsplit: ", |
| 338 | 3x |
class(spl), " (", payloadmsg(spl), ")\n",
|
| 339 | 3x |
"\toccured at path: ", |
| 340 | 3x |
spl_context_to_disp_path(spl_context), "\n" |
| 341 |
) |
|
| 342 |
} |
|
| 343 | 1196x |
ret |
| 344 |
} |
|
| 345 | ||
| 346 |
.apply_split_inner <- function(spl, df, vals = NULL, labels = NULL, trim = FALSE) {
|
|
| 347 | 1185x |
if (is.null(vals)) {
|
| 348 | 1101x |
vals <- .applysplit_rawvals(spl, df) |
| 349 |
} |
|
| 350 | 1185x |
extr <- .applysplit_extras(spl, df, vals) |
| 351 | ||
| 352 | 1185x |
if (is.null(vals)) {
|
| 353 | ! |
return(list( |
| 354 | ! |
values = list(), |
| 355 | ! |
datasplit = list(), |
| 356 | ! |
labels = list(), |
| 357 | ! |
extras = list() |
| 358 |
)) |
|
| 359 |
} |
|
| 360 | ||
| 361 | 1185x |
dpart <- .applysplit_datapart(spl, df, vals) |
| 362 | ||
| 363 | 1185x |
if (is.null(labels)) {
|
| 364 | 1182x |
labels <- .applysplit_partlabels(spl, df, vals, labels) |
| 365 |
} else {
|
|
| 366 | 3x |
stopifnot(names(labels) == names(vals)) |
| 367 |
} |
|
| 368 |
## get rid of columns that would not have any |
|
| 369 |
## observations. |
|
| 370 |
## |
|
| 371 |
## But only if there were any rows to start with |
|
| 372 |
## if not we're in a manually constructed table |
|
| 373 |
## column tree |
|
| 374 | 1185x |
if (trim) {
|
| 375 | ! |
hasdata <- sapply(dpart, function(x) nrow(x) > 0) |
| 376 | ! |
if (nrow(df) > 0 && length(dpart) > sum(hasdata)) { # some empties
|
| 377 | ! |
dpart <- dpart[hasdata] |
| 378 | ! |
vals <- vals[hasdata] |
| 379 | ! |
extr <- extr[hasdata] |
| 380 | ! |
labels <- labels[hasdata] |
| 381 |
} |
|
| 382 |
} |
|
| 383 | ||
| 384 | 1185x |
if (is.null(spl_child_order(spl)) || is(spl, "AllSplit")) {
|
| 385 | 191x |
vord <- seq_along(vals) |
| 386 |
} else {
|
|
| 387 | 994x |
vord <- match( |
| 388 | 994x |
spl_child_order(spl), |
| 389 | 994x |
vals |
| 390 |
) |
|
| 391 | 994x |
vord <- vord[!is.na(vord)] |
| 392 |
} |
|
| 393 | ||
| 394 |
## FIXME: should be an S4 object, not a list |
|
| 395 | 1185x |
ret <- list( |
| 396 | 1185x |
values = vals[vord], |
| 397 | 1185x |
datasplit = dpart[vord], |
| 398 | 1185x |
labels = labels[vord], |
| 399 | 1185x |
extras = extr[vord] |
| 400 |
) |
|
| 401 | 1185x |
ret |
| 402 |
} |
|
| 403 | ||
| 404 |
.checkvarsok <- function(spl, df) {
|
|
| 405 | 2256x |
vars <- spl_payload(spl) |
| 406 |
## could be multiple vars in the future? |
|
| 407 |
## no reason not to make that work here now. |
|
| 408 | 2256x |
if (!all(vars %in% names(df))) {
|
| 409 | 2x |
stop( |
| 410 | 2x |
" variable(s) [", |
| 411 | 2x |
paste(setdiff(vars, names(df)), |
| 412 | 2x |
collapse = ", " |
| 413 |
), |
|
| 414 | 2x |
"] not present in data. (",
|
| 415 | 2x |
class(spl), ")" |
| 416 |
) |
|
| 417 |
} |
|
| 418 | 2254x |
invisible(NULL) |
| 419 |
} |
|
| 420 | ||
| 421 |
### Methods to verify a split appears to be valid, applicable |
|
| 422 |
### to the ***current subset*** of the df. |
|
| 423 |
### |
|
| 424 |
### This is called at each level of recursive splitting so |
|
| 425 |
### do NOT make it check, e.g., if the ref_group level of |
|
| 426 |
### a factor is present in the data, because it may not be. |
|
| 427 | ||
| 428 |
setMethod( |
|
| 429 |
"check_validsplit", "VarLevelSplit", |
|
| 430 |
function(spl, df) {
|
|
| 431 | 945x |
.checkvarsok(spl, df) |
| 432 |
} |
|
| 433 |
) |
|
| 434 | ||
| 435 |
setMethod( |
|
| 436 |
"check_validsplit", "MultiVarSplit", |
|
| 437 |
function(spl, df) {
|
|
| 438 | 58x |
.checkvarsok(spl, df) |
| 439 |
} |
|
| 440 |
) |
|
| 441 | ||
| 442 |
setMethod( |
|
| 443 |
"check_validsplit", "VAnalyzeSplit", |
|
| 444 |
function(spl, df) {
|
|
| 445 | 1309x |
if (!is.na(spl_payload(spl))) {
|
| 446 | 1253x |
.checkvarsok(spl, df) |
| 447 |
} else {
|
|
| 448 | 56x |
TRUE |
| 449 |
} |
|
| 450 |
} |
|
| 451 |
) |
|
| 452 | ||
| 453 |
setMethod( |
|
| 454 |
"check_validsplit", "CompoundSplit", |
|
| 455 |
function(spl, df) {
|
|
| 456 | ! |
all(sapply(spl_payload(spl), df)) |
| 457 |
} |
|
| 458 |
) |
|
| 459 | ||
| 460 |
## default does nothing, add methods as they become |
|
| 461 |
## required |
|
| 462 |
setMethod( |
|
| 463 |
"check_validsplit", "Split", |
|
| 464 | 159x |
function(spl, df) invisible(NULL) |
| 465 |
) |
|
| 466 | ||
| 467 |
setMethod( |
|
| 468 |
".applysplit_rawvals", "VarLevelSplit", |
|
| 469 |
function(spl, df) {
|
|
| 470 | 840x |
varvec <- df[[spl_payload(spl)]] |
| 471 | 840x |
if (is.factor(varvec)) {
|
| 472 | 637x |
levels(varvec) |
| 473 |
} else {
|
|
| 474 | 203x |
unique(varvec) |
| 475 |
} |
|
| 476 |
} |
|
| 477 |
) |
|
| 478 | ||
| 479 |
setMethod( |
|
| 480 |
".applysplit_rawvals", "MultiVarSplit", |
|
| 481 |
function(spl, df) {
|
|
| 482 |
## spl_payload(spl) |
|
| 483 | 50x |
spl_varnames(spl) |
| 484 |
} |
|
| 485 |
) |
|
| 486 | ||
| 487 |
setMethod( |
|
| 488 |
".applysplit_rawvals", "AllSplit", |
|
| 489 | 137x |
function(spl, df) obj_name(spl) |
| 490 |
) # "all obs") |
|
| 491 | ||
| 492 |
setMethod( |
|
| 493 |
".applysplit_rawvals", "ManualSplit", |
|
| 494 | 52x |
function(spl, df) spl@levels |
| 495 |
) |
|
| 496 | ||
| 497 |
## setMethod(".applysplit_rawvals", "NULLSplit",
|
|
| 498 |
## function(spl, df) "") |
|
| 499 | ||
| 500 |
setMethod( |
|
| 501 |
".applysplit_rawvals", "VAnalyzeSplit", |
|
| 502 | ! |
function(spl, df) spl_payload(spl) |
| 503 |
) |
|
| 504 | ||
| 505 |
## formfactor here is gross we're gonna have ot do this |
|
| 506 |
## all again in tthe data split part :-/ |
|
| 507 |
setMethod( |
|
| 508 |
".applysplit_rawvals", "VarStaticCutSplit", |
|
| 509 |
function(spl, df) {
|
|
| 510 | 22x |
spl_cutlabels(spl) |
| 511 |
} |
|
| 512 |
) |
|
| 513 | ||
| 514 |
setMethod( |
|
| 515 |
".applysplit_datapart", "VarLevelSplit", |
|
| 516 |
function(spl, df, vals) {
|
|
| 517 | 924x |
if (!(spl_payload(spl) %in% names(df))) {
|
| 518 | ! |
stop( |
| 519 | ! |
"Attempted to split on values of column (", spl_payload(spl),
|
| 520 | ! |
") not present in the data" |
| 521 |
) |
|
| 522 |
} |
|
| 523 | 924x |
ret <- lapply(seq_along(vals), function(i) {
|
| 524 | 2526x |
spl_col <- df[[spl_payload(spl)]] |
| 525 | 2526x |
df[!is.na(spl_col) & spl_col == vals[[i]], ] |
| 526 |
}) |
|
| 527 | 924x |
names(ret) <- as.character(vals) |
| 528 | 924x |
ret |
| 529 |
} |
|
| 530 |
) |
|
| 531 | ||
| 532 |
setMethod( |
|
| 533 |
".applysplit_datapart", "MultiVarSplit", |
|
| 534 |
function(spl, df, vals) {
|
|
| 535 | 50x |
allvnms <- spl_varnames(spl) |
| 536 | 50x |
if (!is.null(vals) && !identical(allvnms, vals)) {
|
| 537 | ! |
incl <- match(vals, allvnms) |
| 538 |
} else {
|
|
| 539 | 50x |
incl <- seq_along(allvnms) |
| 540 |
} |
|
| 541 | 50x |
vars <- spl_payload(spl)[incl] |
| 542 |
## don't remove nas |
|
| 543 |
## ret = lapply(vars, function(cl) {
|
|
| 544 |
## df[!is.na(df[[cl]]),] |
|
| 545 |
## }) |
|
| 546 | 50x |
ret <- rep(list(df), length(vars)) |
| 547 | 50x |
names(ret) <- vals |
| 548 | 50x |
ret |
| 549 |
} |
|
| 550 |
) |
|
| 551 | ||
| 552 |
setMethod( |
|
| 553 |
".applysplit_datapart", "AllSplit", |
|
| 554 | 137x |
function(spl, df, vals) list(df) |
| 555 |
) |
|
| 556 | ||
| 557 |
## ## not sure I need this |
|
| 558 |
setMethod( |
|
| 559 |
".applysplit_datapart", "ManualSplit", |
|
| 560 | 52x |
function(spl, df, vals) rep(list(df), times = length(vals)) |
| 561 |
) |
|
| 562 | ||
| 563 |
## setMethod(".applysplit_datapart", "NULLSplit",
|
|
| 564 |
## function(spl, df, vals) list(df[FALSE,])) |
|
| 565 | ||
| 566 |
setMethod( |
|
| 567 |
".applysplit_datapart", "VarStaticCutSplit", |
|
| 568 |
function(spl, df, vals) {
|
|
| 569 |
# lbs = spl_cutlabels(spl) |
|
| 570 | 14x |
var <- spl_payload(spl) |
| 571 | 14x |
varvec <- df[[var]] |
| 572 | 14x |
cts <- spl_cuts(spl) |
| 573 | 14x |
cfct <- cut(varvec, cts, include.lowest = TRUE) # , labels = lbs) |
| 574 | 14x |
split(df, cfct, drop = FALSE) |
| 575 |
} |
|
| 576 |
) |
|
| 577 | ||
| 578 |
setMethod( |
|
| 579 |
".applysplit_datapart", "CumulativeCutSplit", |
|
| 580 |
function(spl, df, vals) {
|
|
| 581 |
# lbs = spl_cutlabels(spl) |
|
| 582 | 8x |
var <- spl_payload(spl) |
| 583 | 8x |
varvec <- df[[var]] |
| 584 | 8x |
cts <- spl_cuts(spl) |
| 585 | 8x |
cfct <- cut(varvec, cts, include.lowest = TRUE) # , labels = lbs) |
| 586 | 8x |
ret <- lapply( |
| 587 | 8x |
seq_len(length(levels(cfct))), |
| 588 | 8x |
function(i) df[as.integer(cfct) <= i, ] |
| 589 |
) |
|
| 590 | 8x |
names(ret) <- levels(cfct) |
| 591 | 8x |
ret |
| 592 |
} |
|
| 593 |
) |
|
| 594 | ||
| 595 |
## XXX TODO *CutSplit Methods |
|
| 596 | ||
| 597 |
setClass("NullSentinel", contains = "NULL")
|
|
| 598 |
nullsentinel <- new("NullSentinel")
|
|
| 599 | ! |
noarg <- function() nullsentinel |
| 600 | ||
| 601 |
## Extras generation methods |
|
| 602 |
setMethod( |
|
| 603 |
".applysplit_extras", "Split", |
|
| 604 |
function(spl, df, vals) {
|
|
| 605 | 1133x |
splex <- split_exargs(spl) |
| 606 | 1133x |
nvals <- length(vals) |
| 607 | 1133x |
lapply(seq_len(nvals), function(vpos) {
|
| 608 | 2859x |
one_ex <- lapply(splex, function(arg) {
|
| 609 | ! |
if (length(arg) >= vpos) {
|
| 610 | ! |
arg[[vpos]] |
| 611 |
} else {
|
|
| 612 | ! |
noarg() |
| 613 |
} |
|
| 614 |
}) |
|
| 615 | 2859x |
names(one_ex) <- names(splex) |
| 616 | 2859x |
one_ex <- one_ex[!sapply(one_ex, is, "NullSentinel")] |
| 617 | 2859x |
one_ex |
| 618 |
}) |
|
| 619 |
} |
|
| 620 |
) |
|
| 621 | ||
| 622 |
setMethod( |
|
| 623 |
".applysplit_ref_vals", "Split", |
|
| 624 | ! |
function(spl, df, vals) rep(list(NULL), length(vals)) |
| 625 |
) |
|
| 626 | ||
| 627 |
setMethod( |
|
| 628 |
".applysplit_ref_vals", "VarLevWBaselineSplit", |
|
| 629 |
function(spl, df, vals) {
|
|
| 630 | 17x |
bl_level <- spl@ref_group_value # XXX XXX |
| 631 | 17x |
vnames <- value_names(vals) |
| 632 | 17x |
ret <- lapply(vnames, function(vl) {
|
| 633 | 46x |
list(.in_ref_col = vl == bl_level) |
| 634 |
}) |
|
| 635 | 17x |
names(ret) <- vnames |
| 636 | 17x |
ret |
| 637 |
} |
|
| 638 |
) |
|
| 639 | ||
| 640 |
## XXX TODO FIXME |
|
| 641 |
setMethod( |
|
| 642 |
".applysplit_partlabels", "Split", |
|
| 643 | 159x |
function(spl, df, vals, labels) as.character(vals) |
| 644 |
) |
|
| 645 | ||
| 646 |
setMethod( |
|
| 647 |
".applysplit_partlabels", "VarLevelSplit", |
|
| 648 |
function(spl, df, vals, labels) {
|
|
| 649 | 921x |
varname <- spl_payload(spl) |
| 650 | 921x |
vlabelname <- spl_labelvar(spl) |
| 651 | 921x |
varvec <- df[[varname]] |
| 652 |
## we used to check if vals was NULL but |
|
| 653 |
## this is called after a short-circuit return in .apply_split_inner in that |
|
| 654 |
## case |
|
| 655 |
## so vals is guaranteed to be non-null here |
|
| 656 | 921x |
if (is.null(labels)) {
|
| 657 | 921x |
if (varname == vlabelname) {
|
| 658 | 786x |
labels <- vals |
| 659 |
} else {
|
|
| 660 | 135x |
labfact <- is.factor(df[[vlabelname]]) |
| 661 | 135x |
lablevs <- if (labfact) levels(df[[vlabelname]]) else NULL |
| 662 | 135x |
labels <- sapply(vals, function(v) {
|
| 663 | 272x |
vlabel <- unique(df[varvec == v, vlabelname, drop = TRUE]) |
| 664 |
## TODO remove this once 1-to-1 value-label map is enforced |
|
| 665 |
## elsewhere. |
|
| 666 | 272x |
stopifnot(length(vlabel) < 2) |
| 667 | 272x |
if (length(vlabel) == 0) {
|
| 668 | ! |
vlabel <- "" |
| 669 | 272x |
} else if (labfact) {
|
| 670 | 6x |
vlabel <- lablevs[vlabel] |
| 671 |
} |
|
| 672 | 272x |
vlabel |
| 673 |
}) |
|
| 674 |
} |
|
| 675 |
} |
|
| 676 | 921x |
names(labels) <- as.character(vals) |
| 677 | 921x |
labels |
| 678 |
} |
|
| 679 |
) |
|
| 680 | ||
| 681 |
setMethod( |
|
| 682 |
".applysplit_partlabels", "MultiVarSplit", |
|
| 683 | 50x |
function(spl, df, vals, labels) value_labels(spl) |
| 684 |
) |
|
| 685 | ||
| 686 |
make_splvalue_vec <- function(vals, extrs = list(list()), labels = vals, |
|
| 687 |
subset_exprs) {
|
|
| 688 | 3105x |
if (length(vals) == 0) {
|
| 689 | 418x |
return(vals) |
| 690 |
} |
|
| 691 | ||
| 692 | 2687x |
if (is(extrs, "AsIs")) {
|
| 693 | ! |
extrs <- unclass(extrs) |
| 694 |
} |
|
| 695 |
## if(are(vals, "SplitValue")) {
|
|
| 696 | ||
| 697 |
## return(vals) |
|
| 698 |
## } |
|
| 699 | ||
| 700 | 2687x |
mapply(SplitValue, |
| 701 | 2687x |
val = vals, extr = extrs, |
| 702 | 2687x |
label = labels, |
| 703 | 2687x |
sub_expr = subset_exprs, |
| 704 | 2687x |
SIMPLIFY = FALSE |
| 705 |
) |
|
| 706 |
} |
| 1 |
## Rules for pagination |
|
| 2 |
## |
|
| 3 |
## 1. user defined number of lines per page |
|
| 4 |
## 2. all lines have the same height |
|
| 5 |
## 3. header always reprinted on all pages |
|
| 6 |
## 4. "Label-rows", i.e. content rows above break in the nesting structure, optionally reprinted (default TRUE) |
|
| 7 |
## 5. Never (?) break on a "label"/content row |
|
| 8 |
## 6. Never (?) break on the second (i.e. after the first) data row at a particular leaf Elementary table. |
|
| 9 |
## |
|
| 10 |
## Current behavior: paginate_ttree takes a TableTree object and |
|
| 11 |
## returns a list of rtable (S3) objects for printing. |
|
| 12 | ||
| 13 |
#' @inheritParams formatters::nlines |
|
| 14 |
#' |
|
| 15 |
#' @rdname formatters_methods |
|
| 16 |
#' @aliases nlines,TableRow-method |
|
| 17 |
#' @exportMethod nlines |
|
| 18 |
setMethod( |
|
| 19 |
"nlines", "TableRow", |
|
| 20 |
function(x, colwidths, max_width, fontspec, col_gap = 3) {
|
|
| 21 | 12426x |
fns <- sum(unlist(lapply(row_footnotes(x), nlines, max_width = max_width, fontspec = fontspec))) + |
| 22 | 12426x |
sum(unlist(lapply(cell_footnotes(x), nlines, max_width = max_width, fontspec = fontspec))) |
| 23 | 12426x |
fcells <- as.vector(get_formatted_cells(x)) |
| 24 | 12426x |
spans <- row_cspans(x) |
| 25 | 12426x |
have_cw <- !is.null(colwidths) |
| 26 |
## handle spanning so that the projected word-wrapping from nlines is correct |
|
| 27 | 12426x |
if (any(spans > 1)) {
|
| 28 | 10x |
new_fcells <- character(length(spans)) |
| 29 | 10x |
new_colwidths <- numeric(length(spans)) |
| 30 | 10x |
cur_fcells <- fcells |
| 31 | 10x |
cur_colwidths <- colwidths[-1] ## not the row labels they can't span |
| 32 | 10x |
for (i in seq_along(spans)) {
|
| 33 | 24x |
spi <- spans[i] |
| 34 | 24x |
new_fcells[i] <- cur_fcells[1] ## 1 cause we're trimming it every loop |
| 35 | 24x |
new_colwidths[i] <- sum(head(cur_colwidths, spi)) + col_gap * (spi - 1) |
| 36 | 24x |
cur_fcells <- tail(cur_fcells, -1 * spi) |
| 37 | 24x |
cur_colwidths <- tail(cur_colwidths, -1 * spi) |
| 38 |
} |
|
| 39 | 10x |
if (have_cw) {
|
| 40 | 4x |
colwidths <- c(colwidths[1], new_colwidths) |
| 41 |
} |
|
| 42 | 10x |
fcells <- new_fcells |
| 43 |
} |
|
| 44 | ||
| 45 |
## rowext <- max(vapply(strsplit(c(obj_label(x), fcells), "\n", fixed = TRUE), |
|
| 46 |
## length, |
|
| 47 |
## 1L)) |
|
| 48 | 12426x |
rowext <- max( |
| 49 | 12426x |
unlist( |
| 50 | 12426x |
mapply( |
| 51 | 12426x |
function(s, w) {
|
| 52 | 65105x |
nlines(strsplit(s, "\n", fixed = TRUE), max_width = w, fontspec = fontspec) |
| 53 |
}, |
|
| 54 | 12426x |
s = c(obj_label(x), fcells), |
| 55 | 12426x |
w = (colwidths %||% max_width) %||% vector("list", length(c(obj_label(x), fcells))),
|
| 56 | 12426x |
SIMPLIFY = FALSE |
| 57 |
) |
|
| 58 |
) |
|
| 59 |
) |
|
| 60 | ||
| 61 | 12426x |
rowext + fns |
| 62 |
} |
|
| 63 |
) |
|
| 64 | ||
| 65 |
#' @export |
|
| 66 |
#' @rdname formatters_methods |
|
| 67 |
setMethod( |
|
| 68 |
"nlines", "LabelRow", |
|
| 69 |
function(x, colwidths, max_width, fontspec = fontspec, col_gap = NULL) {
|
|
| 70 | 4155x |
if (labelrow_visible(x)) {
|
| 71 | 4155x |
nlines(strsplit(obj_label(x), "\n", fixed = TRUE)[[1]], max_width = colwidths[1], fontspec = fontspec) + |
| 72 | 4155x |
sum(unlist(lapply(row_footnotes(x), nlines, max_width = max_width, fontspec = fontspec))) |
| 73 |
} else {
|
|
| 74 | ! |
0L |
| 75 |
} |
|
| 76 |
} |
|
| 77 |
) |
|
| 78 | ||
| 79 |
#' @export |
|
| 80 |
#' @rdname formatters_methods |
|
| 81 |
setMethod( |
|
| 82 |
"nlines", "RefFootnote", |
|
| 83 |
function(x, colwidths, max_width, fontspec, col_gap = NULL) {
|
|
| 84 | 298x |
nlines(format_fnote_note(x), colwidths = colwidths, max_width = max_width, fontspec = fontspec) |
| 85 |
} |
|
| 86 |
) |
|
| 87 | ||
| 88 |
#' @export |
|
| 89 |
#' @rdname formatters_methods |
|
| 90 |
setMethod( |
|
| 91 |
"nlines", "InstantiatedColumnInfo", |
|
| 92 |
function(x, colwidths, max_width, fontspec, col_gap = 3) {
|
|
| 93 | 6x |
h_rows <- .do_tbl_h_piece2(x) |
| 94 | 6x |
tl <- top_left(x) %||% rep("", length(h_rows))
|
| 95 | 6x |
main_nls <- vapply( |
| 96 | 6x |
seq_along(h_rows), |
| 97 | 6x |
function(i) {
|
| 98 | 10x |
max( |
| 99 | 10x |
nlines(h_rows[[i]], |
| 100 | 10x |
colwidths = colwidths, |
| 101 | 10x |
fontspec = fontspec, |
| 102 | 10x |
col_gap = col_gap |
| 103 |
), |
|
| 104 | 10x |
nlines(tl[i], |
| 105 | 10x |
colwidths = colwidths[1], |
| 106 | 10x |
fontspec = fontspec |
| 107 |
) |
|
| 108 |
) |
|
| 109 |
}, |
|
| 110 | 6x |
1L |
| 111 |
) |
|
| 112 | ||
| 113 |
## lfs <- collect_leaves(coltree(x)) |
|
| 114 |
## depths <- sapply(lfs, function(l) length(pos_splits(l))) |
|
| 115 | ||
| 116 | 6x |
coldf <- make_col_df(x, colwidths = colwidths) |
| 117 | 6x |
have_fnotes <- length(unlist(coldf$col_fnotes)) > 0 |
| 118 |
## ret <- max(depths, length(top_left(x))) + |
|
| 119 |
## divider_height(x) |
|
| 120 | 6x |
ret <- sum(main_nls, divider_height(x)) |
| 121 | 6x |
if (have_fnotes) {
|
| 122 | ! |
ret <- sum( |
| 123 | ! |
ret, |
| 124 | ! |
vapply(unlist(coldf$col_fnotes), |
| 125 | ! |
nlines, |
| 126 | ! |
1, |
| 127 | ! |
max_width = max_width, |
| 128 | ! |
fontspec = fontspec |
| 129 |
), |
|
| 130 | ! |
2 * divider_height(x) |
| 131 |
) |
|
| 132 |
} |
|
| 133 | 6x |
ret |
| 134 |
} |
|
| 135 |
) |
|
| 136 | ||
| 137 |
col_dfrow <- function(col, |
|
| 138 |
nm = obj_name(col), |
|
| 139 |
lab = obj_label(col), |
|
| 140 |
cnum, |
|
| 141 |
pth = NULL, |
|
| 142 |
sibpos = NA_integer_, |
|
| 143 |
nsibs = NA_integer_, |
|
| 144 |
leaf_indices = cnum, |
|
| 145 |
span = length(leaf_indices), |
|
| 146 |
col_fnotes = list(), |
|
| 147 |
col_count = facet_colcount(col, NULL), |
|
| 148 |
ccount_visible = disp_ccounts(col), |
|
| 149 |
ccount_format = colcount_format(col), |
|
| 150 |
ccount_na_str, |
|
| 151 |
global_cc_format) {
|
|
| 152 | 14115x |
if (is.null(pth)) {
|
| 153 | 13431x |
pth <- pos_to_path(tree_pos(col)) |
| 154 |
} |
|
| 155 | 14115x |
data.frame( |
| 156 | 14115x |
stringsAsFactors = FALSE, |
| 157 | 14115x |
name = nm, |
| 158 | 14115x |
label = lab, |
| 159 | 14115x |
abs_pos = cnum, |
| 160 | 14115x |
path = I(list(pth)), |
| 161 | 14115x |
pos_in_siblings = sibpos, |
| 162 | 14115x |
n_siblings = nsibs, |
| 163 | 14115x |
leaf_indices = I(list(leaf_indices)), |
| 164 | 14115x |
total_span = span, |
| 165 | 14115x |
col_fnotes = I(list(col_fnotes)), |
| 166 | 14115x |
n_col_fnotes = length(col_fnotes), |
| 167 | 14115x |
col_count = col_count, |
| 168 | 14115x |
ccount_visible = ccount_visible, |
| 169 | 14115x |
ccount_format = ccount_format %||% global_cc_format, |
| 170 | 14115x |
ccount_na_str = ccount_na_str |
| 171 |
) |
|
| 172 |
} |
|
| 173 | ||
| 174 |
pos_to_path <- function(pos) {
|
|
| 175 | 50389x |
spls <- pos_splits(pos) |
| 176 | 50389x |
vals <- pos_splvals(pos) |
| 177 | ||
| 178 | 50389x |
path <- character() |
| 179 | 50389x |
for (i in seq_along(spls)) {
|
| 180 | 63602x |
nm <- obj_name(spls[[i]]) |
| 181 | 63602x |
val_i <- value_names(vals[[i]]) |
| 182 | 63602x |
path <- c( |
| 183 | 63602x |
path, |
| 184 | 63602x |
obj_name(spls[[i]]), |
| 185 |
## rawvalues(vals[[i]])) |
|
| 186 | 63602x |
if (!is.na(val_i)) val_i |
| 187 |
) |
|
| 188 |
} |
|
| 189 | 50389x |
path |
| 190 |
} |
|
| 191 | ||
| 192 | ||
| 193 |
add_sect_div_path <- function(df, path) {
|
|
| 194 | 16723x |
df[["sect_div_from_path"]] <- I(list(path)) |
| 195 | 16723x |
df |
| 196 |
} |
|
| 197 | ||
| 198 |
# make_row_df --------------------------------------------------------------- |
|
| 199 | ||
| 200 |
## We now extend the rowdf beyond what is (currently as of writing) defined in |
|
| 201 |
## formatters with the following columns to support section divider shenanigans: |
|
| 202 |
## - self_section_div - the section divider set on that exact object (row, or table if visible_only=FALSE) |
|
| 203 |
## - sect_div_from_path - the path to the element `trailing_section_div` was |
|
| 204 |
## inherited from (or NA_character_ for label rows which aren't pathable and |
|
| 205 |
## can only receive section divs from themselves). |
|
| 206 |
## This should probably be migrated down into formatters for the next release but |
|
| 207 |
## its fine here for the rtables patch release because nothing in formatters |
|
| 208 |
## (ie printing or pagination) needs or uses this new info. |
|
| 209 |
## TODO: migrate above down to formatters |
|
| 210 | ||
| 211 |
#' @inherit formatters::make_row_df |
|
| 212 |
#' |
|
| 213 |
# #' @note The technically present root tree node is excluded from the summary returned by both `make_row_df` and |
|
| 214 |
# #' `make_col_df`, as it is simply the row/column structure of `tt` and thus not useful for pathing or pagination. |
|
| 215 |
# #' |
|
| 216 |
# #' @return a data.frame of row/column-structure information used by the pagination machinery. |
|
| 217 |
# #' |
|
| 218 |
# #' @export |
|
| 219 |
# #' @name make_row_df |
|
| 220 |
# #' @rdname make_row_df |
|
| 221 |
# #' @aliases make_row_df,VTableTree-method |
|
| 222 |
#' @rdname formatters_methods |
|
| 223 |
#' @exportMethod make_row_df |
|
| 224 |
setMethod( |
|
| 225 |
"make_row_df", "VTableTree", |
|
| 226 |
function(tt, |
|
| 227 |
colwidths = NULL, |
|
| 228 |
visible_only = TRUE, |
|
| 229 |
rownum = 0, |
|
| 230 |
indent = 0L, |
|
| 231 |
path = character(), |
|
| 232 |
incontent = FALSE, |
|
| 233 |
repr_ext = 0L, |
|
| 234 |
repr_inds = integer(), |
|
| 235 |
sibpos = NA_integer_, |
|
| 236 |
nsibs = NA_integer_, |
|
| 237 |
max_width = NULL, |
|
| 238 |
fontspec = NULL, |
|
| 239 |
col_gap = 3) {
|
|
| 240 | 11302x |
indent <- indent + indent_mod(tt) |
| 241 |
## retained for debugging info |
|
| 242 | 11302x |
orig_rownum <- rownum # nolint |
| 243 | 11302x |
if (incontent) {
|
| 244 | 1474x |
path <- c(path, "@content") |
| 245 | 9828x |
} else if (length(path) > 0 || nzchar(obj_name(tt))) { ## don't add "" for root
|
| 246 |
## else if (length(path) > 0 && nzchar(obj_name(tt))) ## don't add "" for root # nolint |
|
| 247 | 9780x |
path <- c(path, obj_name(tt)) |
| 248 |
} |
|
| 249 | 11302x |
ret <- list() |
| 250 | ||
| 251 |
## note this is the **table** not the label row |
|
| 252 | 11302x |
if (!visible_only) {
|
| 253 | 152x |
tabrdf <- pagdfrow( |
| 254 | 152x |
rnum = NA, |
| 255 | 152x |
nm = obj_name(tt), |
| 256 | 152x |
lab = "", |
| 257 | 152x |
pth = path, |
| 258 | 152x |
colwidths = colwidths, |
| 259 | 152x |
repext = repr_ext, |
| 260 | 152x |
repind = list(repr_inds), |
| 261 | 152x |
extent = 0, |
| 262 | 152x |
indent = indent, |
| 263 | 152x |
rclass = class(tt), sibpos = sibpos, |
| 264 | 152x |
nsibs = nsibs, |
| 265 | 152x |
nrowrefs = 0L, |
| 266 | 152x |
ncellrefs = 0L, |
| 267 | 152x |
nreflines = 0L, |
| 268 | 152x |
fontspec = fontspec, |
| 269 | 152x |
trailing_sep = trailing_section_div(tt) |
| 270 |
) |
|
| 271 | 152x |
tabrdf <- add_sect_div_path(tabrdf, path) |
| 272 | 152x |
tabrdf$self_section_div <- trailing_section_div(tt) |
| 273 | 152x |
ret <- c( |
| 274 | 152x |
ret, |
| 275 | 152x |
list(tabrdf) |
| 276 |
) |
|
| 277 |
} |
|
| 278 | 11302x |
if (labelrow_visible(tt)) {
|
| 279 | 4135x |
lr <- tt_labelrow(tt) |
| 280 | 4135x |
newdf <- make_row_df(lr, |
| 281 | 4135x |
colwidths = colwidths, |
| 282 | 4135x |
visible_only = visible_only, |
| 283 | 4135x |
rownum = rownum, |
| 284 | 4135x |
indent = indent, |
| 285 | 4135x |
path = path, |
| 286 | 4135x |
incontent = TRUE, |
| 287 | 4135x |
repr_ext = repr_ext, |
| 288 | 4135x |
repr_inds = repr_inds, |
| 289 | 4135x |
max_width = max_width, |
| 290 | 4135x |
fontspec = fontspec |
| 291 |
) |
|
| 292 | 4135x |
rownum <- max(newdf$abs_rownumber, na.rm = TRUE) |
| 293 |
# newdf <- add_sect_div_path(newdf, NA_character_) ## label rows aren't pathable... |
|
| 294 | ||
| 295 | 4135x |
ret <- c( |
| 296 | 4135x |
ret, |
| 297 | 4135x |
list(newdf) |
| 298 |
) |
|
| 299 | 4135x |
repr_ext <- repr_ext + 1L |
| 300 | 4135x |
repr_inds <- c(repr_inds, rownum) |
| 301 | 4135x |
indent <- indent + 1L |
| 302 |
} |
|
| 303 | ||
| 304 | 11302x |
if (NROW(content_table(tt)) > 0) {
|
| 305 | 1474x |
ct_tt <- content_table(tt) |
| 306 | 1474x |
cind <- indent + indent_mod(ct_tt) |
| 307 |
## this isn't right, we can display content and label rows at the |
|
| 308 |
## same time (though by default we don't) and they could, in theory |
|
| 309 |
## have different trailing section divs... |
|
| 310 |
## trailing_section_div(ct_tt) <- trailing_section_div(tt_labelrow(tt)) |
|
| 311 | 1474x |
contdf <- make_row_df(ct_tt, |
| 312 | 1474x |
colwidths = colwidths, |
| 313 | 1474x |
visible_only = visible_only, |
| 314 | 1474x |
rownum = rownum, |
| 315 | 1474x |
indent = cind, |
| 316 | 1474x |
path = path, |
| 317 | 1474x |
incontent = TRUE, |
| 318 | 1474x |
repr_ext = repr_ext, |
| 319 | 1474x |
repr_inds = repr_inds, |
| 320 | 1474x |
max_width = max_width, |
| 321 | 1474x |
fontspec = fontspec |
| 322 |
) |
|
| 323 | 1474x |
crnums <- contdf$abs_rownumber |
| 324 | 1474x |
crnums <- crnums[!is.na(crnums)] |
| 325 | ||
| 326 | 1474x |
newrownum <- max(crnums, na.rm = TRUE) |
| 327 | 1474x |
if (is.finite(newrownum)) {
|
| 328 | 1474x |
rownum <- newrownum |
| 329 | 1474x |
repr_ext <- repr_ext + length(crnums) |
| 330 | 1474x |
repr_inds <- c(repr_inds, crnums) |
| 331 |
} |
|
| 332 |
## if someone attached a trailing separator to a content table somehow |
|
| 333 |
## weird but not /technically/ impossible, it overrides its last row's div, |
|
| 334 |
## as always |
|
| 335 | 1474x |
if (!is.na(trailing_section_div(ct_tt))) {
|
| 336 | ! |
contdf$trailing_section_div[nrow(contdf)] <- trailing_section_div(ct_tt) |
| 337 | ! |
contdf$sect_div_from_path[[nrow(contdf)]] <- c(path, "@content") |
| 338 |
} |
|
| 339 | 1474x |
ret <- c(ret, list(contdf)) |
| 340 | 1474x |
indent <- cind + 1 |
| 341 |
} |
|
| 342 | ||
| 343 | 11302x |
allkids <- tree_children(tt) |
| 344 | 11302x |
newnsibs <- length(allkids) |
| 345 | 11302x |
for (i in seq_along(allkids)) {
|
| 346 | 21266x |
kid <- allkids[[i]] |
| 347 | 21266x |
kiddfs <- make_row_df(kid, |
| 348 | 21266x |
colwidths = colwidths, |
| 349 | 21266x |
visible_only = visible_only, |
| 350 | 21266x |
rownum = force(rownum), |
| 351 | 21266x |
indent = indent, ## + 1, |
| 352 | 21266x |
path = path, |
| 353 | 21266x |
incontent = incontent, |
| 354 | 21266x |
repr_ext = repr_ext, |
| 355 | 21266x |
repr_inds = repr_inds, |
| 356 | 21266x |
nsibs = newnsibs, |
| 357 | 21266x |
sibpos = i, |
| 358 | 21266x |
max_width = max_width, |
| 359 | 21266x |
fontspec = fontspec |
| 360 |
) |
|
| 361 | ||
| 362 |
# print(kiddfs$abs_rownumber) |
|
| 363 | 21266x |
rownum <- max(rownum + 1L, kiddfs$abs_rownumber, na.rm = TRUE) |
| 364 | 21266x |
ret <- c(ret, list(kiddfs)) |
| 365 |
} |
|
| 366 | ||
| 367 | 11302x |
ret <- do.call(rbind, ret) |
| 368 | ||
| 369 |
## Case where it has Elementary table or VTableTree section_div it is overridden |
|
| 370 |
## precedence is least specific -> most specific, so the last row of any |
|
| 371 |
## subtable is overridden by the subtable's div, we are calling make_row_df |
|
| 372 |
## recursively so this achieves that even when levels of structure are skipped |
|
| 373 |
## e.g. grandparent has a section div but section div doesn't (we now have |
|
| 374 |
## a test for this) |
|
| 375 | 11302x |
if (!is.na(trailing_section_div(tt))) {
|
| 376 | 315x |
ret$trailing_sep[nrow(ret)] <- trailing_section_div(tt) |
| 377 | 315x |
ret$sect_div_from_path[[nrow(ret)]] <- path |
| 378 |
} |
|
| 379 | 11302x |
ret |
| 380 |
} |
|
| 381 |
) |
|
| 382 | ||
| 383 |
# #' @exportMethod make_row_df |
|
| 384 |
#' @inherit formatters::make_row_df |
|
| 385 |
#' |
|
| 386 |
#' @export |
|
| 387 |
#' @rdname formatters_methods |
|
| 388 |
setMethod( |
|
| 389 |
"make_row_df", "TableRow", |
|
| 390 |
function(tt, colwidths = NULL, visible_only = TRUE, |
|
| 391 |
rownum = 0, |
|
| 392 |
indent = 0L, |
|
| 393 |
path = "root", |
|
| 394 |
incontent = FALSE, |
|
| 395 |
repr_ext = 0L, |
|
| 396 |
repr_inds = integer(), |
|
| 397 |
sibpos = NA_integer_, |
|
| 398 |
nsibs = NA_integer_, |
|
| 399 |
max_width = NULL, |
|
| 400 |
fontspec, |
|
| 401 |
col_gap = 3) {
|
|
| 402 | 12416x |
indent <- indent + indent_mod(tt) |
| 403 | 12416x |
rownum <- rownum + 1 |
| 404 | 12416x |
rrefs <- row_footnotes(tt) |
| 405 | 12416x |
crefs <- cell_footnotes(tt) |
| 406 | 12416x |
reflines <- sum( |
| 407 | 12416x |
sapply( |
| 408 | 12416x |
c(rrefs, crefs), |
| 409 | 12416x |
nlines, |
| 410 | 12416x |
colwidths = colwidths, |
| 411 | 12416x |
max_width = max_width, |
| 412 | 12416x |
fontspec = fontspec, |
| 413 | 12416x |
col_gap = col_gap |
| 414 |
) |
|
| 415 | 12416x |
) ## col_gap not strictly necessary as these aren't rows, but why not |
| 416 | 12416x |
self_path <- c(path, unname(obj_name(tt))) |
| 417 | 12416x |
ret <- pagdfrow( |
| 418 | 12416x |
row = tt, |
| 419 | 12416x |
rnum = rownum, |
| 420 | 12416x |
colwidths = colwidths, |
| 421 | 12416x |
sibpos = sibpos, |
| 422 | 12416x |
nsibs = nsibs, |
| 423 | 12416x |
pth = self_path, |
| 424 | 12416x |
repext = repr_ext, |
| 425 | 12416x |
repind = repr_inds, |
| 426 | 12416x |
indent = indent, |
| 427 | 12416x |
extent = nlines(tt, colwidths = colwidths, max_width = max_width, fontspec = fontspec, col_gap = col_gap), |
| 428 |
## these two are unlist calls cause they come in lists even with no footnotes |
|
| 429 | 12416x |
nrowrefs = length(rrefs), |
| 430 | 12416x |
ncellrefs = length(unlist(crefs)), |
| 431 | 12416x |
nreflines = reflines, |
| 432 | 12416x |
trailing_sep = trailing_section_div(tt), |
| 433 | 12416x |
fontspec = fontspec |
| 434 |
) |
|
| 435 | 12416x |
ret <- add_sect_div_path(ret, self_path) |
| 436 | 12416x |
ret$self_section_div <- trailing_section_div(tt) |
| 437 | 12416x |
ret |
| 438 |
} |
|
| 439 |
) |
|
| 440 | ||
| 441 |
# #' @exportMethod make_row_df |
|
| 442 |
#' @export |
|
| 443 |
#' @rdname formatters_methods |
|
| 444 |
setMethod( |
|
| 445 |
"make_row_df", "LabelRow", |
|
| 446 |
function(tt, colwidths = NULL, visible_only = TRUE, |
|
| 447 |
rownum = 0, |
|
| 448 |
indent = 0L, |
|
| 449 |
path = "root", |
|
| 450 |
incontent = FALSE, |
|
| 451 |
repr_ext = 0L, |
|
| 452 |
repr_inds = integer(), |
|
| 453 |
sibpos = NA_integer_, |
|
| 454 |
nsibs = NA_integer_, |
|
| 455 |
max_width = NULL, |
|
| 456 |
fontspec, |
|
| 457 |
col_gap = 3) {
|
|
| 458 | 4155x |
rownum <- rownum + 1 |
| 459 | 4155x |
indent <- indent + indent_mod(tt) |
| 460 | 4155x |
ret <- pagdfrow(tt, |
| 461 | 4155x |
extent = nlines(tt, |
| 462 | 4155x |
colwidths = colwidths, |
| 463 | 4155x |
max_width = max_width, |
| 464 | 4155x |
fontspec = fontspec, |
| 465 | 4155x |
col_gap = col_gap |
| 466 |
), |
|
| 467 | 4155x |
rnum = rownum, |
| 468 | 4155x |
colwidths = colwidths, |
| 469 | 4155x |
sibpos = sibpos, |
| 470 | 4155x |
nsibs = nsibs, |
| 471 | 4155x |
pth = path, |
| 472 | 4155x |
repext = repr_ext, |
| 473 | 4155x |
repind = repr_inds, |
| 474 | 4155x |
indent = indent, |
| 475 | 4155x |
nrowrefs = length(row_footnotes(tt)), |
| 476 | 4155x |
ncellrefs = 0L, |
| 477 | 4155x |
nreflines = sum(vapply(row_footnotes(tt), nlines, NA_integer_, |
| 478 | 4155x |
colwidths = colwidths, |
| 479 | 4155x |
max_width = max_width, |
| 480 | 4155x |
fontspec = fontspec, |
| 481 | 4155x |
col_gap = col_gap |
| 482 |
)), |
|
| 483 | 4155x |
trailing_sep = trailing_section_div(tt), |
| 484 | 4155x |
fontspec = fontspec |
| 485 |
) |
|
| 486 | 4155x |
ret <- add_sect_div_path(ret, NA_character_) |
| 487 | 4155x |
ret$self_section_div <- trailing_section_div(tt) |
| 488 | 4155x |
if (!labelrow_visible(tt)) {
|
| 489 | ! |
ret <- ret[0, , drop = FALSE] |
| 490 |
} |
|
| 491 | 4155x |
ret |
| 492 |
} |
|
| 493 |
) |
|
| 494 | ||
| 495 |
setGeneric("inner_col_df", function(ct,
|
|
| 496 |
colwidths = NULL, |
|
| 497 |
visible_only = TRUE, |
|
| 498 |
colnum = 0L, |
|
| 499 |
sibpos = NA_integer_, |
|
| 500 |
nsibs = NA_integer_, |
|
| 501 |
ncolref = 0L, |
|
| 502 |
na_str, |
|
| 503 |
global_cc_format) {
|
|
| 504 | 21036x |
standardGeneric("inner_col_df")
|
| 505 |
}) |
|
| 506 | ||
| 507 |
#' Column layout summary |
|
| 508 |
#' |
|
| 509 |
#' Used for pagination. Generate a structural summary of the columns of an `rtables` table and return it as a |
|
| 510 |
#' `data.frame`. |
|
| 511 |
#' |
|
| 512 |
#' @inheritParams formatters::make_row_df |
|
| 513 |
#' @param ccount_format (`FormatSpec`)\cr The format to be used by default for |
|
| 514 |
#' column counts if one is not specified for an individual column count. |
|
| 515 |
#' @param na_str (`character(1)`)\cr The string to display when a column count is NA. Users should not need to set this. |
|
| 516 |
#' @export |
|
| 517 |
make_col_df <- function(tt, |
|
| 518 |
colwidths = NULL, |
|
| 519 |
visible_only = TRUE, |
|
| 520 |
na_str = "", |
|
| 521 |
ccount_format = colcount_format(tt) %||% "(N=xx)") {
|
|
| 522 | 3846x |
ctree <- coltree(tt, ccount_format = colcount_format(tt)) ## this is a null op if its already a coltree object |
| 523 | 3846x |
rows <- inner_col_df(ctree, |
| 524 |
## colwidths is currently unused anyway... propose_column_widths(matrix_form(tt, indent_rownames=TRUE)), |
|
| 525 | 3846x |
colwidths = colwidths, |
| 526 | 3846x |
visible_only = visible_only, |
| 527 | 3846x |
colnum = 1L, |
| 528 | 3846x |
sibpos = 1L, |
| 529 | 3846x |
nsibs = 1L, |
| 530 | 3846x |
na_str = na_str, |
| 531 | 3846x |
global_cc_format = ccount_format |
| 532 | 3846x |
) ## nsiblings includes current so 1 means "only child" |
| 533 | ||
| 534 | 3846x |
do.call(rbind, rows) |
| 535 |
} |
|
| 536 | ||
| 537 |
setMethod( |
|
| 538 |
"inner_col_df", "LayoutColLeaf", |
|
| 539 |
function(ct, colwidths, visible_only, |
|
| 540 |
colnum, |
|
| 541 |
sibpos, |
|
| 542 |
nsibs, |
|
| 543 |
na_str, |
|
| 544 |
global_cc_format) {
|
|
| 545 | 13431x |
list(col_dfrow( |
| 546 | 13431x |
col = ct, |
| 547 | 13431x |
cnum = colnum, |
| 548 | 13431x |
sibpos = sibpos, |
| 549 | 13431x |
nsibs = nsibs, |
| 550 | 13431x |
leaf_indices = colnum, |
| 551 | 13431x |
col_fnotes = col_footnotes(ct), |
| 552 | 13431x |
ccount_na_str = na_str, |
| 553 | 13431x |
global_cc_format = global_cc_format |
| 554 |
)) |
|
| 555 |
} |
|
| 556 |
) |
|
| 557 | ||
| 558 |
setMethod( |
|
| 559 |
"inner_col_df", "LayoutColTree", |
|
| 560 |
function(ct, colwidths, visible_only, |
|
| 561 |
colnum, |
|
| 562 |
sibpos, |
|
| 563 |
nsibs, |
|
| 564 |
na_str, |
|
| 565 |
global_cc_format) {
|
|
| 566 | 7605x |
kids <- tree_children(ct) |
| 567 | 7605x |
ret <- vector("list", length(kids))
|
| 568 | 7605x |
for (i in seq_along(kids)) {
|
| 569 | 17190x |
k <- kids[[i]] |
| 570 | 17190x |
newrows <- do.call( |
| 571 | 17190x |
rbind, |
| 572 | 17190x |
inner_col_df(k, |
| 573 | 17190x |
colnum = colnum, |
| 574 | 17190x |
sibpos = i, |
| 575 | 17190x |
nsibs = length(kids), |
| 576 | 17190x |
visible_only = visible_only, |
| 577 | 17190x |
na_str = na_str, |
| 578 | 17190x |
global_cc_format = global_cc_format |
| 579 |
) |
|
| 580 |
) |
|
| 581 | 17190x |
colnum <- max(newrows$abs_pos, colnum, na.rm = TRUE) + 1 |
| 582 | 17190x |
ret[[i]] <- newrows |
| 583 |
} |
|
| 584 | ||
| 585 | 7605x |
if (!visible_only) {
|
| 586 | 1418x |
allindices <- unlist(lapply(ret, function(df) df$abs_pos[!is.na(df$abs_pos)])) |
| 587 | 1418x |
thispth <- pos_to_path(tree_pos(ct)) |
| 588 | 1418x |
if (any(nzchar(thispth))) {
|
| 589 | 684x |
thisone <- list(col_dfrow( |
| 590 | 684x |
col = ct, |
| 591 | 684x |
cnum = NA_integer_, |
| 592 | 684x |
leaf_indices = allindices, |
| 593 | 684x |
sibpos = sibpos, |
| 594 | 684x |
nsibs = nsibs, |
| 595 | 684x |
pth = thispth, |
| 596 | 684x |
col_fnotes = col_footnotes(ct), |
| 597 | 684x |
ccount_na_str = na_str, |
| 598 | 684x |
global_cc_format = global_cc_format |
| 599 |
)) |
|
| 600 | 684x |
ret <- c(thisone, ret) |
| 601 |
} |
|
| 602 |
} |
|
| 603 | ||
| 604 | 7605x |
ret |
| 605 |
} |
|
| 606 |
) |
|
| 607 | ||
| 608 |
## THIS INCLUDES BOTH "table stub" (i.e. column label and top_left) AND |
|
| 609 |
## title/subtitle!!!!! |
|
| 610 |
.header_rep_nlines <- function(tt, colwidths, max_width, fontspec, verbose = FALSE) {
|
|
| 611 | 3x |
cinfo_lines <- nlines(col_info(tt), colwidths = colwidths, max_width = max_width, fontspec = fontspec) |
| 612 | 3x |
if (any(nzchar(all_titles(tt)))) {
|
| 613 |
## +1 is for blank line between subtitles and divider |
|
| 614 | 2x |
tlines <- sum(nlines(all_titles(tt), |
| 615 | 2x |
colwidths = colwidths, |
| 616 | 2x |
max_width = max_width, |
| 617 | 2x |
fontspec = fontspec |
| 618 | 2x |
)) + divider_height(tt) + 1L |
| 619 |
} else {
|
|
| 620 | 1x |
tlines <- 0 |
| 621 |
} |
|
| 622 | 3x |
ret <- cinfo_lines + tlines |
| 623 | 3x |
if (verbose) {
|
| 624 | ! |
message( |
| 625 | ! |
"Lines required for header content: ", |
| 626 | ! |
ret, " (col info: ", cinfo_lines, ", titles: ", tlines, ")" |
| 627 |
) |
|
| 628 |
} |
|
| 629 | 3x |
ret |
| 630 |
} |
|
| 631 | ||
| 632 |
## this is ***only*** lines that are expected to be repeated on multiple pages: |
|
| 633 |
## main footer, prov footer, and referential footnotes on **columns** |
|
| 634 | ||
| 635 |
.footer_rep_nlines <- function(tt, colwidths, max_width, have_cfnotes, fontspec, verbose = FALSE) {
|
|
| 636 | 3x |
flines <- nlines(main_footer(tt), |
| 637 | 3x |
colwidths = colwidths, |
| 638 | 3x |
max_width = max_width - table_inset(tt), |
| 639 | 3x |
fontspec = fontspec |
| 640 |
) + |
|
| 641 | 3x |
nlines(prov_footer(tt), colwidths = colwidths, max_width = max_width, fontspec = fontspec) |
| 642 | 3x |
if (flines > 0) {
|
| 643 | 2x |
dl_contrib <- if (have_cfnotes) 0 else divider_height(tt) |
| 644 | 2x |
flines <- flines + dl_contrib + 1L |
| 645 |
} |
|
| 646 | ||
| 647 | 3x |
if (verbose) {
|
| 648 | ! |
message( |
| 649 | ! |
"Determining lines required for footer content", |
| 650 | ! |
if (have_cfnotes) " [column fnotes present]", |
| 651 | ! |
": ", flines, " lines" |
| 652 |
) |
|
| 653 |
} |
|
| 654 | ||
| 655 | 3x |
flines |
| 656 |
} |
|
| 657 | ||
| 658 |
# Pagination --------------------------------------------------------------- |
|
| 659 | ||
| 660 |
#' Pagination of a `TableTree` |
|
| 661 |
#' |
|
| 662 |
#' Paginate an `rtables` table in the vertical and/or horizontal direction, as required for the specified page size. |
|
| 663 |
#' |
|
| 664 |
#' @inheritParams gen_args |
|
| 665 |
#' @inheritParams paginate_table |
|
| 666 |
#' @param lpp (`numeric(1)`)\cr maximum lines per page including (re)printed header and context rows. |
|
| 667 |
#' @param min_siblings (`numeric(1)`)\cr minimum sibling rows which must appear on either side of pagination row for a |
|
| 668 |
#' mid-subtable split to be valid. Defaults to 2. |
|
| 669 |
#' @param nosplitin (`character`)\cr names of sub-tables where page-breaks are not allowed, regardless of other |
|
| 670 |
#' considerations. Defaults to none. |
|
| 671 |
#' |
|
| 672 |
#' @return |
|
| 673 |
#' * `pag_tt_indices` returns a list of paginated-groups of row-indices of `tt`. |
|
| 674 |
#' * `paginate_table` returns the subtables defined by subsetting by the indices defined by `pag_tt_indices`. |
|
| 675 |
#' |
|
| 676 |
#' @details |
|
| 677 |
#' `rtables` pagination is context aware, meaning that label rows and row-group summaries (content rows) are repeated |
|
| 678 |
#' after (vertical) pagination, as appropriate. This allows the reader to immediately understand where they are in the |
|
| 679 |
#' table after turning to a new page, but does also mean that a rendered, paginated table will take up more lines of |
|
| 680 |
#' text than rendering the table without pagination would. |
|
| 681 |
#' |
|
| 682 |
#' Pagination also takes into account word-wrapping of title, footer, column-label, and formatted cell value content. |
|
| 683 |
#' |
|
| 684 |
#' Vertical pagination information (pagination `data.frame`) is created using (`make_row_df`). |
|
| 685 |
#' |
|
| 686 |
#' Horizontal pagination is performed by creating a pagination data frame for the columns, and then applying the same |
|
| 687 |
#' algorithm used for vertical pagination to it. |
|
| 688 |
#' |
|
| 689 |
#' If physical page size and font information are specified, these are used to derive lines-per-page (`lpp`) and |
|
| 690 |
#' characters-per-page (`cpp`) values. |
|
| 691 |
#' |
|
| 692 |
#' The full multi-direction pagination algorithm then is as follows: |
|
| 693 |
#' |
|
| 694 |
#' 0. Adjust `lpp` and `cpp` to account for rendered elements that are not rows (columns): |
|
| 695 |
#' - titles/footers/column labels, and horizontal dividers in the vertical pagination case |
|
| 696 |
#' - row-labels, table_inset, and top-left materials in the horizontal case |
|
| 697 |
#' 1. Perform 'forced pagination' representing page-by row splits, generating 1 or more tables. |
|
| 698 |
#' 2. Perform vertical pagination separately on each table generated in (1). |
|
| 699 |
#' 3. Perform horizontal pagination **on the entire table** and apply the results to each table |
|
| 700 |
#' page generated in (1)-(2). |
|
| 701 |
#' 4. Return a list of subtables representing full bi-directional pagination. |
|
| 702 |
#' |
|
| 703 |
#' Pagination in both directions is done using the *Core Pagination Algorithm* implemented in the `formatters` package: |
|
| 704 |
#' |
|
| 705 |
#' @inheritSection formatters::pagination_algo Pagination Algorithm |
|
| 706 |
#' |
|
| 707 |
#' @examples |
|
| 708 |
#' s_summary <- function(x) {
|
|
| 709 |
#' if (is.numeric(x)) {
|
|
| 710 |
#' in_rows( |
|
| 711 |
#' "n" = rcell(sum(!is.na(x)), format = "xx"), |
|
| 712 |
#' "Mean (sd)" = rcell(c(mean(x, na.rm = TRUE), sd(x, na.rm = TRUE)), |
|
| 713 |
#' format = "xx.xx (xx.xx)" |
|
| 714 |
#' ), |
|
| 715 |
#' "IQR" = rcell(IQR(x, na.rm = TRUE), format = "xx.xx"), |
|
| 716 |
#' "min - max" = rcell(range(x, na.rm = TRUE), format = "xx.xx - xx.xx") |
|
| 717 |
#' ) |
|
| 718 |
#' } else if (is.factor(x)) {
|
|
| 719 |
#' vs <- as.list(table(x)) |
|
| 720 |
#' do.call(in_rows, lapply(vs, rcell, format = "xx")) |
|
| 721 |
#' } else {
|
|
| 722 |
#' ( |
|
| 723 |
#' stop("type not supported")
|
|
| 724 |
#' ) |
|
| 725 |
#' } |
|
| 726 |
#' } |
|
| 727 |
#' |
|
| 728 |
#' lyt <- basic_table() %>% |
|
| 729 |
#' split_cols_by(var = "ARM") %>% |
|
| 730 |
#' analyze(c("AGE", "SEX", "BEP01FL", "BMRKR1", "BMRKR2", "COUNTRY"), afun = s_summary)
|
|
| 731 |
#' |
|
| 732 |
#' tbl <- build_table(lyt, ex_adsl) |
|
| 733 |
#' tbl |
|
| 734 |
#' |
|
| 735 |
#' nrow(tbl) |
|
| 736 |
#' |
|
| 737 |
#' row_paths_summary(tbl) |
|
| 738 |
#' |
|
| 739 |
#' tbls <- paginate_table(tbl, lpp = 15) |
|
| 740 |
#' mf <- matrix_form(tbl, indent_rownames = TRUE) |
|
| 741 |
#' w_tbls <- propose_column_widths(mf) # so that we have the same column widths |
|
| 742 |
#' |
|
| 743 |
#' |
|
| 744 |
#' tmp <- lapply(tbls, function(tbli) {
|
|
| 745 |
#' cat(toString(tbli, widths = w_tbls)) |
|
| 746 |
#' cat("\n\n")
|
|
| 747 |
#' cat("~~~~ PAGE BREAK ~~~~")
|
|
| 748 |
#' cat("\n\n")
|
|
| 749 |
#' }) |
|
| 750 |
#' |
|
| 751 |
#' @rdname paginate |
|
| 752 |
#' @export |
|
| 753 |
pag_tt_indices <- function(tt, |
|
| 754 |
lpp = 15, |
|
| 755 |
min_siblings = 2, |
|
| 756 |
nosplitin = character(), |
|
| 757 |
colwidths = NULL, |
|
| 758 |
max_width = NULL, |
|
| 759 |
fontspec = NULL, |
|
| 760 |
col_gap = 3, |
|
| 761 |
verbose = FALSE) {
|
|
| 762 | 3x |
dheight <- divider_height(tt) |
| 763 | ||
| 764 |
# cinfo_lines <- nlines(col_info(tt), colwidths = colwidths, max_width = max_width) |
|
| 765 | 3x |
coldf <- make_col_df(tt, colwidths) |
| 766 | 3x |
have_cfnotes <- length(unlist(coldf$col_fnotes)) > 0 |
| 767 | ||
| 768 | 3x |
hlines <- .header_rep_nlines(tt, |
| 769 | 3x |
colwidths = colwidths, max_width = max_width, |
| 770 | 3x |
verbose = verbose, |
| 771 | 3x |
fontspec = fontspec |
| 772 |
) |
|
| 773 |
## if(any(nzchar(all_titles(tt)))) {
|
|
| 774 |
## tlines <- sum(nlines(all_titles(tt), colwidths = colwidths, max_width = max_width)) + |
|
| 775 |
## length(wrap_txt(all_titles(tt), max_width = max_width)) + |
|
| 776 |
## dheight + 1L |
|
| 777 |
## } else {
|
|
| 778 |
## tlines <- 0 |
|
| 779 |
## } |
|
| 780 |
## flines <- nlines(main_footer(tt), colwidths = colwidths, |
|
| 781 |
## max_width = max_width - table_inset(tt)) + |
|
| 782 |
## nlines(prov_footer(tt), colwidths = colwidths, max_width = max_width) |
|
| 783 |
## if(flines > 0) {
|
|
| 784 |
## dl_contrib <- if(have_cfnotes) 0 else dheight |
|
| 785 |
## flines <- flines + dl_contrib + 1L |
|
| 786 |
## } |
|
| 787 | 3x |
flines <- .footer_rep_nlines(tt, |
| 788 | 3x |
colwidths = colwidths, |
| 789 | 3x |
max_width = max_width, |
| 790 | 3x |
have_cfnotes = have_cfnotes, |
| 791 | 3x |
fontspec = fontspec, |
| 792 | 3x |
verbose = verbose |
| 793 |
) |
|
| 794 |
## row lines per page |
|
| 795 | 3x |
rlpp <- lpp - hlines - flines |
| 796 | 3x |
if (verbose) {
|
| 797 | ! |
message( |
| 798 | ! |
"Adjusted Lines Per Page: ", |
| 799 | ! |
rlpp, " (original lpp: ", lpp, ")" |
| 800 |
) |
|
| 801 |
} |
|
| 802 | 3x |
pagdf <- make_row_df(tt, colwidths, max_width = max_width) |
| 803 | ||
| 804 | 3x |
pag_indices_inner(pagdf, |
| 805 | 3x |
rlpp = rlpp, min_siblings = min_siblings, |
| 806 | 3x |
nosplitin = nosplitin, |
| 807 | 3x |
verbose = verbose, |
| 808 | 3x |
have_col_fnotes = have_cfnotes, |
| 809 | 3x |
div_height = dheight, |
| 810 | 3x |
col_gap = col_gap, |
| 811 | 3x |
has_rowlabels = TRUE |
| 812 |
) |
|
| 813 |
} |
|
| 814 | ||
| 815 |
copy_title_footer <- function(to, from, newptitle) {
|
|
| 816 | 18x |
main_title(to) <- main_title(from) |
| 817 | 18x |
subtitles(to) <- subtitles(from) |
| 818 | 18x |
page_titles(to) <- c(page_titles(from), newptitle) |
| 819 | 18x |
main_footer(to) <- main_footer(from) |
| 820 | 18x |
prov_footer(to) <- prov_footer(from) |
| 821 | 18x |
to |
| 822 |
} |
|
| 823 | ||
| 824 |
pag_btw_kids <- function(tt) {
|
|
| 825 | 8x |
pref <- ptitle_prefix(tt) |
| 826 | 8x |
lapply( |
| 827 | 8x |
tree_children(tt), |
| 828 | 8x |
function(tbl) {
|
| 829 | 18x |
tbl <- copy_title_footer( |
| 830 | 18x |
tbl, tt, |
| 831 | 18x |
paste(pref, obj_label(tbl), sep = ": ") |
| 832 |
) |
|
| 833 | 18x |
labelrow_visible(tbl) <- FALSE |
| 834 | 18x |
tbl |
| 835 |
} |
|
| 836 |
) |
|
| 837 |
} |
|
| 838 | ||
| 839 |
force_paginate <- function(tt, |
|
| 840 |
force_pag = vapply(tree_children(tt), has_force_pag, NA), |
|
| 841 |
verbose = FALSE) {
|
|
| 842 |
## forced pagination is happening at this |
|
| 843 | 121x |
if (has_force_pag(tt)) {
|
| 844 | 8x |
ret <- pag_btw_kids(tt) |
| 845 | 8x |
return(unlist(lapply(ret, force_paginate))) |
| 846 |
} |
|
| 847 | 113x |
chunks <- list() |
| 848 | 113x |
kinds <- seq_along(force_pag) |
| 849 | 113x |
while (length(kinds) > 0) {
|
| 850 | 113x |
if (force_pag[kinds[1]]) {
|
| 851 | ! |
outertbl <- copy_title_footer( |
| 852 | ! |
tree_children(tt)[[kinds[1]]], |
| 853 | ! |
tt, |
| 854 | ! |
NULL |
| 855 |
) |
|
| 856 | ||
| 857 | ! |
chunks <- c(chunks, force_paginate(outertbl)) |
| 858 | ! |
kinds <- kinds[-1] |
| 859 |
} else {
|
|
| 860 | 113x |
tmptbl <- tt |
| 861 | 113x |
runend <- min(which(force_pag[kinds]), length(kinds)) |
| 862 | 113x |
useinds <- 1:runend |
| 863 | 113x |
tree_children(tmptbl) <- tree_children(tt)[useinds] |
| 864 | 113x |
chunks <- c(chunks, tmptbl) |
| 865 | 113x |
kinds <- kinds[-useinds] |
| 866 |
} |
|
| 867 |
} |
|
| 868 | 113x |
unlist(chunks, recursive = TRUE) |
| 869 |
} |
|
| 870 | ||
| 871 |
#' @importFrom formatters do_forced_paginate |
|
| 872 |
setMethod( |
|
| 873 |
"do_forced_paginate", "VTableTree", |
|
| 874 | 103x |
function(obj) force_paginate(obj) |
| 875 |
) |
|
| 876 | ||
| 877 | 186x |
non_null_na <- function(x) !is.null(x) && is.na(x) |
| 878 | ||
| 879 |
#' @inheritParams formatters::vert_pag_indices |
|
| 880 |
#' @inheritParams formatters::page_lcpp |
|
| 881 |
#' @inheritParams formatters::toString |
|
| 882 |
#' @param cpp (`numeric(1)` or `NULL`)\cr width (in characters) of the pages for horizontal pagination. |
|
| 883 |
#' `NA` (the default) indicates `cpp` should be inferred from the page size; `NULL` indicates no horizontal |
|
| 884 |
#' pagination should be done regardless of page size. |
|
| 885 |
#' |
|
| 886 |
#' @rdname paginate |
|
| 887 |
#' @aliases paginate_table |
|
| 888 |
#' @export |
|
| 889 |
paginate_table <- function(tt, |
|
| 890 |
page_type = "letter", |
|
| 891 |
font_family = "Courier", |
|
| 892 |
font_size = 8, |
|
| 893 |
lineheight = 1, |
|
| 894 |
landscape = FALSE, |
|
| 895 |
pg_width = NULL, |
|
| 896 |
pg_height = NULL, |
|
| 897 |
margins = c(top = .5, bottom = .5, left = .75, right = .75), |
|
| 898 |
lpp = NA_integer_, |
|
| 899 |
cpp = NA_integer_, |
|
| 900 |
min_siblings = 2, |
|
| 901 |
nosplitin = character(), |
|
| 902 |
colwidths = NULL, |
|
| 903 |
tf_wrap = FALSE, |
|
| 904 |
max_width = NULL, |
|
| 905 |
fontspec = font_spec(font_family, font_size, lineheight), |
|
| 906 |
col_gap = 3, |
|
| 907 |
verbose = FALSE) {
|
|
| 908 | 51x |
new_dev <- open_font_dev(fontspec) |
| 909 | 51x |
if (new_dev) {
|
| 910 | 38x |
on.exit(close_font_dev()) |
| 911 |
} |
|
| 912 | ||
| 913 | 51x |
if ((non_null_na(lpp) || non_null_na(cpp)) && |
| 914 | 51x |
(!is.null(page_type) || (!is.null(pg_width) && !is.null(pg_height)))) { # nolint
|
| 915 | 12x |
pg_lcpp <- page_lcpp( |
| 916 | 12x |
page_type = page_type, |
| 917 | 12x |
font_family = font_family, |
| 918 | 12x |
font_size = font_size, |
| 919 | 12x |
lineheight = lineheight, |
| 920 | 12x |
pg_width = pg_width, |
| 921 | 12x |
pg_height = pg_height, |
| 922 | 12x |
margins = margins, |
| 923 | 12x |
landscape = landscape, |
| 924 | 12x |
fontspec = fontspec |
| 925 |
) |
|
| 926 | ||
| 927 | 12x |
if (non_null_na(lpp)) {
|
| 928 | 6x |
lpp <- pg_lcpp$lpp |
| 929 |
} |
|
| 930 | 12x |
if (is.na(cpp)) {
|
| 931 | 8x |
cpp <- pg_lcpp$cpp |
| 932 |
} |
|
| 933 |
} else {
|
|
| 934 | 39x |
if (non_null_na(cpp)) {
|
| 935 | ! |
cpp <- NULL |
| 936 |
} |
|
| 937 | 39x |
if (non_null_na(lpp)) {
|
| 938 | ! |
lpp <- 70 |
| 939 |
} |
|
| 940 |
} |
|
| 941 | ||
| 942 | 51x |
if (is.null(colwidths)) {
|
| 943 | 34x |
colwidths <- propose_column_widths( |
| 944 | 34x |
matrix_form( |
| 945 | 34x |
tt, |
| 946 | 34x |
indent_rownames = TRUE, |
| 947 | 34x |
fontspec = fontspec, |
| 948 | 34x |
col_gap = col_gap |
| 949 |
), |
|
| 950 | 34x |
fontspec = fontspec |
| 951 |
) |
|
| 952 |
} |
|
| 953 | ||
| 954 | 51x |
if (!tf_wrap) {
|
| 955 | 41x |
if (!is.null(max_width)) {
|
| 956 | ! |
warning("tf_wrap is FALSE - ignoring non-null max_width value.")
|
| 957 |
} |
|
| 958 | 41x |
max_width <- NULL |
| 959 | 10x |
} else if (is.null(max_width)) {
|
| 960 | 5x |
max_width <- cpp |
| 961 | 5x |
} else if (identical(max_width, "auto")) {
|
| 962 |
## XXX this 3 is column sep width!!!!!!! |
|
| 963 | ! |
max_width <- sum(colwidths) + col_gap * (length(colwidths) - 1) |
| 964 |
} |
|
| 965 | 51x |
if (!is.null(cpp) && !is.null(max_width) && max_width > cpp) {
|
| 966 | ! |
warning("max_width specified is wider than characters per page width (cpp).")
|
| 967 |
} |
|
| 968 | ||
| 969 |
## taken care of in vert_pag_indices now |
|
| 970 |
## if(!is.null(cpp)) |
|
| 971 |
## cpp <- cpp - table_inset(tt) |
|
| 972 | ||
| 973 | 51x |
force_pag <- vapply(tree_children(tt), has_force_pag, TRUE) |
| 974 | 51x |
if (has_force_pag(tt) || any(force_pag)) {
|
| 975 | 5x |
spltabs <- do_forced_paginate(tt) |
| 976 | 5x |
spltabs <- unlist(spltabs, recursive = TRUE) |
| 977 | 5x |
ret <- lapply(spltabs, paginate_table, |
| 978 | 5x |
lpp = lpp, |
| 979 | 5x |
cpp = cpp, |
| 980 | 5x |
min_siblings = min_siblings, |
| 981 | 5x |
nosplitin = nosplitin, |
| 982 | 5x |
colwidths = colwidths, |
| 983 | 5x |
tf_wrap = tf_wrap, |
| 984 | 5x |
max_width = max_width, |
| 985 | 5x |
fontspec = fontspec, |
| 986 | 5x |
verbose = verbose, |
| 987 | 5x |
col_gap = col_gap |
| 988 |
) |
|
| 989 | 5x |
return(unlist(ret, recursive = TRUE)) |
| 990 |
} |
|
| 991 | ||
| 992 | 46x |
inds <- paginate_indices(tt, |
| 993 | 46x |
page_type = page_type, |
| 994 | 46x |
fontspec = fontspec, |
| 995 |
## font_family = font_family, |
|
| 996 |
## font_size = font_size, |
|
| 997 |
## lineheight = lineheight, |
|
| 998 | 46x |
landscape = landscape, |
| 999 | 46x |
pg_width = pg_width, |
| 1000 | 46x |
pg_height = pg_height, |
| 1001 | 46x |
margins = margins, |
| 1002 | 46x |
lpp = lpp, |
| 1003 | 46x |
cpp = cpp, |
| 1004 | 46x |
min_siblings = min_siblings, |
| 1005 | 46x |
nosplitin = nosplitin, |
| 1006 | 46x |
colwidths = colwidths, |
| 1007 | 46x |
tf_wrap = tf_wrap, |
| 1008 | 46x |
max_width = max_width, |
| 1009 | 46x |
col_gap = col_gap, |
| 1010 | 46x |
verbose = verbose |
| 1011 | 46x |
) ## paginate_table apparently doesn't accept indent_size |
| 1012 | ||
| 1013 | 41x |
res <- lapply( |
| 1014 | 41x |
inds$pag_row_indices, |
| 1015 | 41x |
function(ii) {
|
| 1016 | 115x |
subt <- tt[ii, drop = FALSE, keep_titles = TRUE, keep_topleft = TRUE, reindex_refs = FALSE] |
| 1017 | 115x |
lapply( |
| 1018 | 115x |
inds$pag_col_indices, |
| 1019 | 115x |
function(jj) {
|
| 1020 | 214x |
subt[, jj, drop = FALSE, keep_titles = TRUE, keep_topleft = TRUE, reindex_refs = FALSE] |
| 1021 |
} |
|
| 1022 |
) |
|
| 1023 |
} |
|
| 1024 |
) |
|
| 1025 | 41x |
res <- unlist(res, recursive = FALSE) |
| 1026 | 41x |
res |
| 1027 |
} |
| 1 |
# paths summary ---- |
|
| 2 | ||
| 3 |
#' Get a list of table row/column paths |
|
| 4 |
#' |
|
| 5 |
#' @param x (`VTableTree`)\cr an `rtable` object. |
|
| 6 |
#' |
|
| 7 |
#' @return A list of paths to each row/column within `x`. |
|
| 8 |
#' |
|
| 9 |
#' @seealso [cell_values()], [`fnotes_at_path<-`], [row_paths_summary()], [col_paths_summary()] |
|
| 10 |
#' |
|
| 11 |
#' @examples |
|
| 12 |
#' lyt <- basic_table() %>% |
|
| 13 |
#' split_cols_by("ARM") %>%
|
|
| 14 |
#' analyze(c("SEX", "AGE"))
|
|
| 15 |
#' |
|
| 16 |
#' tbl <- build_table(lyt, ex_adsl) |
|
| 17 |
#' tbl |
|
| 18 |
#' |
|
| 19 |
#' row_paths(tbl) |
|
| 20 |
#' col_paths(tbl) |
|
| 21 |
#' |
|
| 22 |
#' cell_values(tbl, c("AGE", "Mean"), c("ARM", "B: Placebo"))
|
|
| 23 |
#' |
|
| 24 |
#' @rdname make_col_row_df |
|
| 25 |
#' @export |
|
| 26 |
row_paths <- function(x) {
|
|
| 27 | 46x |
stopifnot(is_rtable(x)) |
| 28 | 46x |
make_row_df(x, visible_only = TRUE)$path |
| 29 |
} |
|
| 30 | ||
| 31 |
#' @rdname make_col_row_df |
|
| 32 |
#' @export |
|
| 33 |
col_paths <- function(x) {
|
|
| 34 | 2654x |
if (!is(coltree(x), "LayoutColTree")) {
|
| 35 | ! |
stop("I don't know how to extract the column paths from an object of class ", class(x))
|
| 36 |
} |
|
| 37 | 2654x |
make_col_df(x, visible_only = TRUE)$path |
| 38 |
} |
|
| 39 | ||
| 40 |
#' Print row/column paths summary |
|
| 41 |
#' |
|
| 42 |
#' @param x (`VTableTree`)\cr an `rtable` object. |
|
| 43 |
#' |
|
| 44 |
#' @return A data frame summarizing the row- or column-structure of `x`. |
|
| 45 |
#' |
|
| 46 |
#' @examplesIf require(dplyr) |
|
| 47 |
#' ex_adsl_MF <- ex_adsl %>% dplyr::filter(SEX %in% c("M", "F"))
|
|
| 48 |
#' |
|
| 49 |
#' lyt <- basic_table() %>% |
|
| 50 |
#' split_cols_by("ARM") %>%
|
|
| 51 |
#' split_cols_by("SEX", split_fun = drop_split_levels) %>%
|
|
| 52 |
#' analyze(c("AGE", "BMRKR2"))
|
|
| 53 |
#' |
|
| 54 |
#' tbl <- build_table(lyt, ex_adsl_MF) |
|
| 55 |
#' tbl |
|
| 56 |
#' |
|
| 57 |
#' df <- row_paths_summary(tbl) |
|
| 58 |
#' df |
|
| 59 |
#' |
|
| 60 |
#' col_paths_summary(tbl) |
|
| 61 |
#' |
|
| 62 |
#' # manually constructed table |
|
| 63 |
#' tbl2 <- rtable( |
|
| 64 |
#' rheader( |
|
| 65 |
#' rrow( |
|
| 66 |
#' "row 1", rcell("a", colspan = 2),
|
|
| 67 |
#' rcell("b", colspan = 2)
|
|
| 68 |
#' ), |
|
| 69 |
#' rrow("h2", "a", "b", "c", "d")
|
|
| 70 |
#' ), |
|
| 71 |
#' rrow("r1", 1, 2, 1, 2), rrow("r2", 3, 4, 2, 1)
|
|
| 72 |
#' ) |
|
| 73 |
#' col_paths_summary(tbl2) |
|
| 74 |
#' |
|
| 75 |
#' @export |
|
| 76 |
row_paths_summary <- function(x) {
|
|
| 77 | 1x |
stopifnot(is_rtable(x)) |
| 78 | ||
| 79 | 1x |
if (nrow(x) == 0) {
|
| 80 | ! |
return("rowname node_class path\n---------------------\n")
|
| 81 |
} |
|
| 82 | ||
| 83 | 1x |
pagdf <- make_row_df(x, visible_only = TRUE) |
| 84 | 1x |
row.names(pagdf) <- NULL |
| 85 | ||
| 86 | 1x |
mat <- rbind( |
| 87 | 1x |
c("rowname", "node_class", "path"),
|
| 88 | 1x |
t(apply(pagdf, 1, function(xi) {
|
| 89 | 28x |
c( |
| 90 | 28x |
indent_string(xi$label, xi$indent), |
| 91 | 28x |
xi$node_class, |
| 92 | 28x |
paste(xi$path, collapse = ", ") |
| 93 |
) |
|
| 94 |
})) |
|
| 95 |
) |
|
| 96 | ||
| 97 | 1x |
txt <- mat_as_string(mat) |
| 98 | 1x |
cat(txt) |
| 99 | 1x |
cat("\n")
|
| 100 | ||
| 101 | 1x |
invisible(pagdf[, c("label", "indent", "node_class", "path")])
|
| 102 |
} |
|
| 103 | ||
| 104 |
#' @rdname row_paths_summary |
|
| 105 |
#' @export |
|
| 106 |
col_paths_summary <- function(x) {
|
|
| 107 | 1x |
stopifnot(is_rtable(x)) |
| 108 | ||
| 109 | 1x |
pagdf <- make_col_df(x, visible_only = FALSE) |
| 110 | 1x |
row.names(pagdf) <- NULL |
| 111 | ||
| 112 | 1x |
mat <- rbind( |
| 113 | 1x |
c("label", "path"),
|
| 114 | 1x |
t(apply(pagdf, 1, function(xi) {
|
| 115 | 6x |
c( |
| 116 | 6x |
indent_string(xi$label, floor(length(xi$path) / 2 - 1)), |
| 117 | 6x |
paste(xi$path, collapse = ", ") |
| 118 |
) |
|
| 119 |
})) |
|
| 120 |
) |
|
| 121 | ||
| 122 | 1x |
txt <- mat_as_string(mat) |
| 123 | 1x |
cat(txt) |
| 124 | 1x |
cat("\n")
|
| 125 | ||
| 126 | 1x |
invisible(pagdf[, c("label", "path")])
|
| 127 |
} |
|
| 128 | ||
| 129 |
# Rows ---- |
|
| 130 |
# . Summarize Rows ---- |
|
| 131 | ||
| 132 |
# summarize_row_df <- |
|
| 133 |
# function(name, |
|
| 134 |
# label, |
|
| 135 |
# indent, |
|
| 136 |
# depth, |
|
| 137 |
# rowtype, |
|
| 138 |
# indent_mod, |
|
| 139 |
# level) {
|
|
| 140 |
# data.frame( |
|
| 141 |
# name = name, |
|
| 142 |
# label = label, |
|
| 143 |
# indent = indent, |
|
| 144 |
# depth = level, |
|
| 145 |
# rowtype = rowtype, |
|
| 146 |
# indent_mod = indent_mod, |
|
| 147 |
# level = level, |
|
| 148 |
# stringsAsFactors = FALSE |
|
| 149 |
# ) |
|
| 150 |
# } |
|
| 151 | ||
| 152 |
#' Summarize rows |
|
| 153 |
#' |
|
| 154 |
#' @inheritParams gen_args |
|
| 155 |
#' @param depth (`numeric(1)`)\cr depth. |
|
| 156 |
#' @param indent (`numeric(1)`)\cr indent. |
|
| 157 |
#' |
|
| 158 |
#' @examplesIf require(dplyr) |
|
| 159 |
#' library(dplyr) |
|
| 160 |
#' |
|
| 161 |
#' iris2 <- iris %>% |
|
| 162 |
#' group_by(Species) %>% |
|
| 163 |
#' mutate(group = as.factor(rep_len(c("a", "b"), length.out = n()))) %>%
|
|
| 164 |
#' ungroup() |
|
| 165 |
#' |
|
| 166 |
#' lyt <- basic_table() %>% |
|
| 167 |
#' split_cols_by("Species") %>%
|
|
| 168 |
#' split_cols_by("group") %>%
|
|
| 169 |
#' analyze(c("Sepal.Length", "Petal.Width"),
|
|
| 170 |
#' afun = list_wrap_x(summary), |
|
| 171 |
#' format = "xx.xx" |
|
| 172 |
#' ) |
|
| 173 |
#' |
|
| 174 |
#' tbl <- build_table(lyt, iris2) |
|
| 175 |
#' |
|
| 176 |
#' @rdname int_methods |
|
| 177 |
setGeneric("summarize_rows_inner", function(obj, depth = 0, indent = 0) {
|
|
| 178 | ! |
standardGeneric("summarize_rows_inner")
|
| 179 |
}) |
|
| 180 | ||
| 181 |
#' @rdname int_methods |
|
| 182 |
setMethod( |
|
| 183 |
"summarize_rows_inner", "TableTree", |
|
| 184 |
function(obj, depth = 0, indent = 0) {
|
|
| 185 | ! |
indent <- max(0L, indent + indent_mod(obj)) |
| 186 | ||
| 187 | ! |
lr <- summarize_rows_inner(tt_labelrow(obj), depth, indent) |
| 188 | ! |
if (!is.null(lr)) {
|
| 189 | ! |
ret <- list(lr) |
| 190 |
} else {
|
|
| 191 | ! |
ret <- list() |
| 192 |
} |
|
| 193 | ||
| 194 | ! |
indent <- indent + (!is.null(lr)) |
| 195 | ||
| 196 | ! |
ctab <- content_table(obj) |
| 197 | ! |
if (NROW(ctab)) {
|
| 198 | ! |
ct <- summarize_rows_inner(ctab, |
| 199 | ! |
depth = depth, |
| 200 | ! |
indent = indent + indent_mod(ctab) |
| 201 |
) |
|
| 202 | ! |
ret <- c(ret, ct) |
| 203 | ! |
indent <- indent + (length(ct) > 0) * (1 + indent_mod(ctab)) |
| 204 |
} |
|
| 205 | ||
| 206 | ! |
kids <- tree_children(obj) |
| 207 | ! |
els <- lapply(tree_children(obj), summarize_rows_inner, |
| 208 | ! |
depth = depth + 1, indent = indent |
| 209 |
) |
|
| 210 | ! |
if (!are(kids, "TableRow")) {
|
| 211 | ! |
if (!are(kids, "VTableTree")) {
|
| 212 |
## hatchet job of a hack, wrap em just so we can unlist em all at |
|
| 213 |
## the same level |
|
| 214 | ! |
rowinds <- vapply(kids, is, NA, class2 = "TableRow") |
| 215 | ! |
els[rowinds] <- lapply(els[rowinds], function(x) list(x)) |
| 216 |
} |
|
| 217 | ! |
els <- unlist(els, recursive = FALSE) |
| 218 |
} |
|
| 219 | ! |
ret <- c(ret, els) |
| 220 | ! |
ret |
| 221 |
## df <- do.call(rbind, c(list(lr), list(ct), els)) |
|
| 222 | ||
| 223 |
## row.names(df) <- NULL |
|
| 224 |
## df |
|
| 225 |
} |
|
| 226 |
) |
|
| 227 | ||
| 228 |
# Print Table Structure ---- |
|
| 229 | ||
| 230 |
#' Summarize table |
|
| 231 |
#' |
|
| 232 |
#' @param x (`VTableTree`)\cr a table object. |
|
| 233 |
#' @param detail (`string`)\cr either `row` or `subtable`. |
|
| 234 |
#' |
|
| 235 |
#' @return No return value. Called for the side-effect of printing a row- or subtable-structure summary of `x`. |
|
| 236 |
#' |
|
| 237 |
#' @examplesIf require(dplyr) |
|
| 238 |
#' library(dplyr) |
|
| 239 |
#' |
|
| 240 |
#' iris2 <- iris %>% |
|
| 241 |
#' group_by(Species) %>% |
|
| 242 |
#' mutate(group = as.factor(rep_len(c("a", "b"), length.out = n()))) %>%
|
|
| 243 |
#' ungroup() |
|
| 244 |
#' |
|
| 245 |
#' lyt <- basic_table() %>% |
|
| 246 |
#' split_cols_by("Species") %>%
|
|
| 247 |
#' split_cols_by("group") %>%
|
|
| 248 |
#' analyze(c("Sepal.Length", "Petal.Width"),
|
|
| 249 |
#' afun = list_wrap_x(summary), |
|
| 250 |
#' format = "xx.xx" |
|
| 251 |
#' ) |
|
| 252 |
#' |
|
| 253 |
#' tbl <- build_table(lyt, iris2) |
|
| 254 |
#' tbl |
|
| 255 |
#' |
|
| 256 |
#' row_paths(tbl) |
|
| 257 |
#' |
|
| 258 |
#' table_structure(tbl) |
|
| 259 |
#' |
|
| 260 |
#' table_structure(tbl, detail = "row") |
|
| 261 |
#' |
|
| 262 |
#' @export |
|
| 263 |
table_structure <- function(x, detail = c("subtable", "row")) {
|
|
| 264 | 2x |
detail <- match.arg(detail) |
| 265 | ||
| 266 | 2x |
switch(detail, |
| 267 | 1x |
subtable = treestruct(x), |
| 268 | 1x |
row = table_structure_inner(x), |
| 269 | ! |
stop("unsupported level of detail ", detail)
|
| 270 |
) |
|
| 271 |
} |
|
| 272 | ||
| 273 |
#' @param obj (`VTableTree`)\cr a table object. |
|
| 274 |
#' @param depth (`numeric(1)`)\cr depth in tree. |
|
| 275 |
#' @param indent (`numeric(1)`)\cr indent. |
|
| 276 |
#' @param print_indent (`numeric(1)`)\cr indent for printing. |
|
| 277 |
#' |
|
| 278 |
#' @rdname int_methods |
|
| 279 |
setGeneric( |
|
| 280 |
"table_structure_inner", |
|
| 281 |
function(obj, |
|
| 282 |
depth = 0, |
|
| 283 |
indent = 0, |
|
| 284 |
print_indent = 0) {
|
|
| 285 | 70x |
standardGeneric("table_structure_inner")
|
| 286 |
} |
|
| 287 |
) |
|
| 288 | ||
| 289 |
scat <- function(..., indent = 0, newline = TRUE) {
|
|
| 290 | 101x |
txt <- paste(..., collapse = "", sep = "") |
| 291 | ||
| 292 | 101x |
cat(indent_string(txt, indent)) |
| 293 | ||
| 294 | 101x |
if (newline) cat("\n")
|
| 295 |
} |
|
| 296 | ||
| 297 |
## helper functions |
|
| 298 |
obj_visible <- function(x) {
|
|
| 299 | 50x |
x@visible |
| 300 |
} |
|
| 301 | ||
| 302 |
is_empty_labelrow <- function(x) {
|
|
| 303 | 4x |
obj_label(x) == "" && !labelrow_visible(x) |
| 304 |
} |
|
| 305 | ||
| 306 |
is_empty_ElementaryTable <- function(x) {
|
|
| 307 | 10x |
length(tree_children(x)) == 0 && is_empty_labelrow(tt_labelrow(x)) |
| 308 |
} |
|
| 309 | ||
| 310 |
#' @param object (`VTableTree`)\cr a table object. |
|
| 311 |
#' |
|
| 312 |
#' @rdname int_methods |
|
| 313 |
#' @export |
|
| 314 |
setGeneric("str", function(object, ...) {
|
|
| 315 | ! |
standardGeneric("str")
|
| 316 |
}) |
|
| 317 | ||
| 318 |
#' @param max.level (`numeric(1)`)\cr passed to `utils::str`. Defaults to 3 for the `VTableTree` method, unlike |
|
| 319 |
#' the underlying default of `NA`. `NA` is *not* appropriate for `VTableTree` objects. |
|
| 320 |
#' |
|
| 321 |
#' @rdname int_methods |
|
| 322 |
#' @export |
|
| 323 |
setMethod( |
|
| 324 |
"str", "VTableTree", |
|
| 325 |
function(object, max.level = 3L, ...) {
|
|
| 326 | ! |
utils::str(object, max.level = max.level, ...) |
| 327 | ! |
warning("str provides a low level, implementation-detail-specific description of the TableTree object structure. ",
|
| 328 | ! |
"See table_structure(.) for a summary of table struture intended for end users.", |
| 329 | ! |
call. = FALSE |
| 330 |
) |
|
| 331 | ! |
invisible(NULL) |
| 332 |
} |
|
| 333 |
) |
|
| 334 | ||
| 335 |
#' @inheritParams table_structure_inner |
|
| 336 |
#' @rdname int_methods |
|
| 337 |
setMethod( |
|
| 338 |
"table_structure_inner", "TableTree", |
|
| 339 |
function(obj, depth = 0, indent = 0, print_indent = 0) {
|
|
| 340 | 10x |
indent <- indent + indent_mod(obj) |
| 341 | ||
| 342 | 10x |
scat("TableTree: ", "[", obj_name(obj), "] (",
|
| 343 | 10x |
obj_label(obj), ")", |
| 344 | 10x |
indent = print_indent |
| 345 |
) |
|
| 346 | ||
| 347 | 10x |
table_structure_inner( |
| 348 | 10x |
tt_labelrow(obj), depth, indent, |
| 349 | 10x |
print_indent + 1 |
| 350 |
) |
|
| 351 | ||
| 352 | 10x |
ctab <- content_table(obj) |
| 353 | 10x |
visible_content <- if (is_empty_ElementaryTable(ctab)) {
|
| 354 |
# scat("content: -", indent = print_indent + 1)
|
|
| 355 | 4x |
FALSE |
| 356 |
} else {
|
|
| 357 | 6x |
scat("content:", indent = print_indent + 1)
|
| 358 | 6x |
table_structure_inner(ctab, |
| 359 | 6x |
depth = depth, |
| 360 | 6x |
indent = indent + indent_mod(ctab), |
| 361 | 6x |
print_indent = print_indent + 2 |
| 362 |
) |
|
| 363 |
} |
|
| 364 | ||
| 365 | 10x |
if (length(tree_children(obj)) == 0) {
|
| 366 | ! |
scat("children: - ", indent = print_indent + 1)
|
| 367 |
} else {
|
|
| 368 | 10x |
scat("children: ", indent = print_indent + 1)
|
| 369 | 10x |
lapply(tree_children(obj), table_structure_inner, |
| 370 | 10x |
depth = depth + 1, |
| 371 | 10x |
indent = indent + visible_content * (1 + indent_mod(ctab)), |
| 372 | 10x |
print_indent = print_indent + 2 |
| 373 |
) |
|
| 374 |
} |
|
| 375 | ||
| 376 | 10x |
invisible(NULL) |
| 377 |
} |
|
| 378 |
) |
|
| 379 | ||
| 380 |
#' @rdname int_methods |
|
| 381 |
setMethod( |
|
| 382 |
"table_structure_inner", "ElementaryTable", |
|
| 383 |
function(obj, depth = 0, indent = 0, print_indent = 0) {
|
|
| 384 | 15x |
scat("ElementaryTable: ", "[", obj_name(obj),
|
| 385 | 15x |
"] (", obj_label(obj), ")",
|
| 386 | 15x |
indent = print_indent |
| 387 |
) |
|
| 388 | ||
| 389 | 15x |
indent <- indent + indent_mod(obj) |
| 390 | ||
| 391 | 15x |
table_structure_inner( |
| 392 | 15x |
tt_labelrow(obj), depth, |
| 393 | 15x |
indent, print_indent + 1 |
| 394 |
) |
|
| 395 | ||
| 396 | 15x |
if (length(tree_children(obj)) == 0) {
|
| 397 | ! |
scat("children: - ", indent = print_indent + 1)
|
| 398 |
} else {
|
|
| 399 | 15x |
scat("children: ", indent = print_indent + 1)
|
| 400 | 15x |
lapply(tree_children(obj), table_structure_inner, |
| 401 | 15x |
depth = depth + 1, indent = indent, |
| 402 | 15x |
print_indent = print_indent + 2 |
| 403 |
) |
|
| 404 |
} |
|
| 405 | ||
| 406 | 15x |
invisible(NULL) |
| 407 |
} |
|
| 408 |
) |
|
| 409 | ||
| 410 |
#' @rdname int_methods |
|
| 411 |
setMethod( |
|
| 412 |
"table_structure_inner", "TableRow", |
|
| 413 |
function(obj, depth = 0, indent = 0, print_indent = 0) {
|
|
| 414 | 20x |
scat(class(obj), ": ", "[", obj_name(obj), "] (",
|
| 415 | 20x |
obj_label(obj), ")", |
| 416 | 20x |
indent = print_indent |
| 417 |
) |
|
| 418 | ||
| 419 | 20x |
indent <- indent + indent_mod(obj) |
| 420 | ||
| 421 | 20x |
invisible(NULL) |
| 422 |
} |
|
| 423 |
) |
|
| 424 | ||
| 425 |
#' @rdname int_methods |
|
| 426 |
setMethod( |
|
| 427 |
"table_structure_inner", "LabelRow", |
|
| 428 |
function(obj, depth = 0, indent = 0, print_indent = 0) {
|
|
| 429 | 25x |
indent <- indent + indent_mod(obj) |
| 430 | ||
| 431 | 25x |
txtvis <- if (!obj_visible(obj)) " - <not visible>" else "" |
| 432 | ||
| 433 | 25x |
scat("labelrow: ", "[", obj_name(obj), "] (", obj_label(obj), ")",
|
| 434 | 25x |
txtvis, |
| 435 | 25x |
indent = print_indent |
| 436 |
) |
|
| 437 | ||
| 438 | 25x |
obj_visible(obj) |
| 439 |
} |
|
| 440 |
) |
| 1 |
#' Create an `rtable` row |
|
| 2 |
#' |
|
| 3 |
#' @inheritParams compat_args |
|
| 4 |
#' @param ... cell values. |
|
| 5 |
#' |
|
| 6 |
#' @return A row object of the context-appropriate type (label or data). |
|
| 7 |
#' |
|
| 8 |
#' @examples |
|
| 9 |
#' rrow("ABC", c(1, 2), c(3, 2), format = "xx (xx.%)")
|
|
| 10 |
#' rrow("")
|
|
| 11 |
#' |
|
| 12 |
#' @family compatibility |
|
| 13 |
#' @export |
|
| 14 |
rrow <- function(row.name = "", ..., format = NULL, indent = 0, inset = 0L) {
|
|
| 15 | 258x |
vals <- list(...) |
| 16 | 258x |
if (is.null(row.name)) {
|
| 17 | 40x |
row.name <- "" |
| 18 | 218x |
} else if (!is(row.name, "character")) {
|
| 19 | ! |
stop("row.name must be NULL or a character string")
|
| 20 |
} |
|
| 21 | 258x |
if (length(vals) == 0L) {
|
| 22 | 22x |
LabelRow( |
| 23 | 22x |
lev = as.integer(indent), |
| 24 | 22x |
label = row.name, |
| 25 | 22x |
name = row.name, |
| 26 | 22x |
vis = TRUE, |
| 27 | 22x |
table_inset = 0L |
| 28 |
) |
|
| 29 |
} else {
|
|
| 30 | 236x |
csps <- as.integer(sapply(vals, function(x) {
|
| 31 | 1391x |
attr(x, "colspan", exact = TRUE) %||% 1L |
| 32 |
})) |
|
| 33 |
## we have to leave the formats on the cells and NOT the row unless we were |
|
| 34 |
## already told to do so, because row formats get clobbered when cbinding |
|
| 35 |
## but cell formats do not. |
|
| 36 |
## formats = sapply(vals, obj_format) |
|
| 37 |
## if(is.character(formats) && length(unique(formats)) == 1L && is.null(format)) |
|
| 38 |
## format = unique(formats) |
|
| 39 | 236x |
DataRow( |
| 40 | 236x |
vals = vals, lev = as.integer(indent), label = row.name, |
| 41 | 236x |
name = row.name, ## XXX TODO |
| 42 | 236x |
cspan = csps, |
| 43 | 236x |
format = format, |
| 44 | 236x |
table_inset = as.integer(inset) |
| 45 |
) |
|
| 46 |
} |
|
| 47 |
} |
|
| 48 | ||
| 49 |
#' Create an `rtable` row from a vector or list of values |
|
| 50 |
#' |
|
| 51 |
#' @inheritParams compat_args |
|
| 52 |
#' @param ... values in vector/list form. |
|
| 53 |
#' |
|
| 54 |
#' @inherit rrow return |
|
| 55 |
#' |
|
| 56 |
#' @examples |
|
| 57 |
#' rrowl("a", c(1, 2, 3), format = "xx")
|
|
| 58 |
#' rrowl("a", c(1, 2, 3), c(4, 5, 6), format = "xx")
|
|
| 59 |
#' |
|
| 60 |
#' |
|
| 61 |
#' rrowl("N", table(iris$Species))
|
|
| 62 |
#' rrowl("N", table(iris$Species), format = "xx")
|
|
| 63 |
#' |
|
| 64 |
#' x <- tapply(iris$Sepal.Length, iris$Species, mean, simplify = FALSE) |
|
| 65 |
#' |
|
| 66 |
#' rrow(row.name = "row 1", x) |
|
| 67 |
#' rrow("ABC", 2, 3)
|
|
| 68 |
#' |
|
| 69 |
#' rrowl(row.name = "row 1", c(1, 2), c(3, 4)) |
|
| 70 |
#' rrow(row.name = "row 2", c(1, 2), c(3, 4)) |
|
| 71 |
#' |
|
| 72 |
#' @family compatibility |
|
| 73 |
#' @export |
|
| 74 |
rrowl <- function(row.name, ..., format = NULL, indent = 0, inset = 0L) {
|
|
| 75 | 38x |
dots <- list(...) |
| 76 | 38x |
args_list <- c(list( |
| 77 | 38x |
row.name = row.name, format = format, |
| 78 | 38x |
indent = indent, inset = inset |
| 79 | 38x |
), val = unlist(lapply(dots, as.list), recursive = FALSE)) |
| 80 | 38x |
do.call(rrow, args_list) |
| 81 |
} |
|
| 82 | ||
| 83 |
## rcell moved to tt_afun_utils.R |
|
| 84 | ||
| 85 |
## inefficient trash |
|
| 86 |
paste_em_n <- function(lst, n, sep = ".") {
|
|
| 87 | 9x |
ret <- lst[[1]] |
| 88 | 9x |
if (n > 1) {
|
| 89 | 4x |
for (i in 2:n) {
|
| 90 | 4x |
ret <- paste(ret, lst[[i]], sep = sep) |
| 91 |
} |
|
| 92 |
} |
|
| 93 | 9x |
ret |
| 94 |
} |
|
| 95 | ||
| 96 |
hrows_to_colinfo <- function(rows) {
|
|
| 97 | 34x |
nr <- length(rows) |
| 98 | 34x |
stopifnot(nr > 0) |
| 99 | 34x |
cspans <- lapply(rows, row_cspans) |
| 100 | 34x |
vals <- lapply(rows, function(x) unlist(row_values(x))) |
| 101 | 34x |
unqvals <- lapply(vals, unique) |
| 102 | 34x |
formats <- lapply(rows, obj_format) |
| 103 | 34x |
counts <- NULL |
| 104 | 34x |
if (formats[nr] == "(N=xx)" || all(sapply(row_cells(rows[[nr]]), obj_format) == "(N=xx)")) { ## count row
|
| 105 | 1x |
counts <- vals[[nr]] |
| 106 | 1x |
vals <- vals[-nr] |
| 107 | 1x |
cspans <- cspans[-nr] |
| 108 | 1x |
nr <- nr - 1 |
| 109 |
} |
|
| 110 |
## easiest case, one header row no counts. we're done |
|
| 111 |
## XXX could one row but cspan ever make sense???? |
|
| 112 |
## I don't think so? |
|
| 113 | 34x |
if (nr == 1) { ## && all(cspans == 1L)) {
|
| 114 | 29x |
ret <- manual_cols(unlist(vals[[1]])) |
| 115 | 29x |
if (!is.null(counts)) {
|
| 116 | 1x |
col_counts(ret) <- counts |
| 117 | 1x |
disp_ccounts(ret) <- TRUE |
| 118 |
} |
|
| 119 | 29x |
return(ret) |
| 120 |
} |
|
| 121 |
## second easiest case full repeated nestin |
|
| 122 | 5x |
repvals <- mapply(function(v, csp) rep(v, times = csp), |
| 123 | 5x |
v = vals, csp = cspans, SIMPLIFY = FALSE |
| 124 |
) |
|
| 125 | ||
| 126 |
## nr > 1 here |
|
| 127 | 5x |
fullnest <- TRUE |
| 128 | 5x |
for (i in 2:nr) {
|
| 129 | 5x |
psted <- paste_em_n(repvals, i - 1) |
| 130 | 5x |
spl <- split(repvals[[i]], psted) |
| 131 | 5x |
if (!all(sapply(spl, function(x) identical(x, spl[[1]])))) {
|
| 132 | 4x |
fullnest <- FALSE |
| 133 | 4x |
break |
| 134 |
} |
|
| 135 |
} |
|
| 136 | ||
| 137 |
## if its full nesting we're done, so put |
|
| 138 |
## the counts on as necessary and return. |
|
| 139 | 5x |
if (fullnest) {
|
| 140 | 1x |
ret <- manual_cols(.lst = unqvals) |
| 141 | 1x |
if (!is.null(counts)) {
|
| 142 | ! |
col_counts(ret) <- counts |
| 143 | ! |
disp_ccounts(ret) <- TRUE |
| 144 |
} |
|
| 145 | 1x |
return(ret) |
| 146 |
} |
|
| 147 | ||
| 148 |
## booo. the fully complex case where the multiple rows |
|
| 149 |
## really don't represent nesting at all, each top level |
|
| 150 |
## can have different sub labels |
|
| 151 | ||
| 152 |
## we will build it up as if it were full nesting and then prune |
|
| 153 |
## based on the columns we actually want. |
|
| 154 | ||
| 155 | 4x |
fullcolinfo <- manual_cols(.lst = unqvals) |
| 156 | 4x |
fullbusiness <- names(collect_leaves(coltree(fullcolinfo))) |
| 157 | 4x |
wanted <- paste_em_n(repvals, nr) |
| 158 | 4x |
wantcols <- match(wanted, fullbusiness) |
| 159 | 4x |
stopifnot(all(!is.na(wantcols))) |
| 160 | ||
| 161 | 4x |
subset_cols(fullcolinfo, wantcols) |
| 162 |
} |
|
| 163 | ||
| 164 |
#' Create a header |
|
| 165 |
#' |
|
| 166 |
#' @inheritParams compat_args |
|
| 167 |
#' @param ... row specifications, either as character vectors or the output from [rrow()], [DataRow()], |
|
| 168 |
#' [LabelRow()], etc. |
|
| 169 |
#' |
|
| 170 |
#' @return A `InstantiatedColumnInfo` object. |
|
| 171 |
#' |
|
| 172 |
#' @examples |
|
| 173 |
#' h1 <- rheader(c("A", "B", "C"))
|
|
| 174 |
#' h1 |
|
| 175 |
#' |
|
| 176 |
#' h2 <- rheader( |
|
| 177 |
#' rrow(NULL, rcell("group 1", colspan = 2), rcell("group 2", colspan = 2)),
|
|
| 178 |
#' rrow(NULL, "A", "B", "A", "B") |
|
| 179 |
#' ) |
|
| 180 |
#' h2 |
|
| 181 |
#' |
|
| 182 |
#' @family compatibility |
|
| 183 |
#' @export |
|
| 184 |
rheader <- function(..., format = "xx", .lst = NULL) {
|
|
| 185 | 3x |
if (!is.null(.lst)) {
|
| 186 | ! |
args <- .lst |
| 187 |
} else {
|
|
| 188 | 3x |
args <- list(...) |
| 189 |
} |
|
| 190 | 3x |
rrows <- if (length(args) == 1 && !is(args[[1]], "TableRow")) {
|
| 191 | ! |
list(rrowl(row.name = NULL, val = args[[1]], format = format)) |
| 192 | 3x |
} else if (are(args, "TableRow")) {
|
| 193 | 3x |
args |
| 194 |
} |
|
| 195 | ||
| 196 | 3x |
hrows_to_colinfo(rrows) |
| 197 |
} |
|
| 198 | ||
| 199 |
.char_to_hrows <- function(hdr) {
|
|
| 200 | 31x |
nlfnd <- grep("\n", hdr, fixed = TRUE)
|
| 201 | 31x |
if (length(nlfnd) == 0) {
|
| 202 | 27x |
return(list(rrowl(NULL, hdr))) |
| 203 |
} |
|
| 204 | ||
| 205 | 4x |
stopifnot(length(nlfnd) == length(hdr)) |
| 206 | 4x |
raw <- strsplit(hdr, "\n", fixed = TRUE) |
| 207 | 4x |
lens <- unique(sapply(raw, length)) |
| 208 | 4x |
stopifnot(length(lens) == 1L) |
| 209 | 4x |
lapply( |
| 210 | 4x |
seq(1, lens), |
| 211 | 4x |
function(i) {
|
| 212 | 8x |
rrowl(NULL, vapply(raw, `[`, NA_character_, i = i)) |
| 213 |
} |
|
| 214 |
) |
|
| 215 |
} |
|
| 216 | ||
| 217 |
#' Create a table |
|
| 218 |
#' |
|
| 219 |
#' @inheritParams compat_args |
|
| 220 |
#' @inheritParams gen_args |
|
| 221 |
#' @param header (`TableRow`, `character`, or `InstantiatedColumnInfo`)\cr information defining the header |
|
| 222 |
#' (column structure) of the table. This can be as row objects (legacy), character vectors, or an |
|
| 223 |
#' `InstantiatedColumnInfo` object. |
|
| 224 |
#' @param ... rows to place in the table. |
|
| 225 |
#' |
|
| 226 |
#' @return A formal table object of the appropriate type (`ElementaryTable` or `TableTree`). |
|
| 227 |
#' |
|
| 228 |
#' @examples |
|
| 229 |
#' rtable( |
|
| 230 |
#' header = LETTERS[1:3], |
|
| 231 |
#' rrow("one to three", 1, 2, 3),
|
|
| 232 |
#' rrow("more stuff", rcell(pi, format = "xx.xx"), "test", "and more")
|
|
| 233 |
#' ) |
|
| 234 |
#' |
|
| 235 |
#' # Table with multirow header |
|
| 236 |
#' |
|
| 237 |
#' sel <- iris$Species == "setosa" |
|
| 238 |
#' mtbl <- rtable( |
|
| 239 |
#' header = rheader( |
|
| 240 |
#' rrow( |
|
| 241 |
#' row.name = NULL, rcell("Sepal.Length", colspan = 2),
|
|
| 242 |
#' rcell("Petal.Length", colspan = 2)
|
|
| 243 |
#' ), |
|
| 244 |
#' rrow(NULL, "mean", "median", "mean", "median") |
|
| 245 |
#' ), |
|
| 246 |
#' rrow( |
|
| 247 |
#' row.name = "All Species", |
|
| 248 |
#' mean(iris$Sepal.Length), median(iris$Sepal.Length), |
|
| 249 |
#' mean(iris$Petal.Length), median(iris$Petal.Length), |
|
| 250 |
#' format = "xx.xx" |
|
| 251 |
#' ), |
|
| 252 |
#' rrow( |
|
| 253 |
#' row.name = "Setosa", |
|
| 254 |
#' mean(iris$Sepal.Length[sel]), median(iris$Sepal.Length[sel]), |
|
| 255 |
#' mean(iris$Petal.Length[sel]), median(iris$Petal.Length[sel]) |
|
| 256 |
#' ) |
|
| 257 |
#' ) |
|
| 258 |
#' |
|
| 259 |
#' mtbl |
|
| 260 |
#' |
|
| 261 |
#' names(mtbl) # always first row of header |
|
| 262 |
#' |
|
| 263 |
#' # Single row header |
|
| 264 |
#' |
|
| 265 |
#' tbl <- rtable( |
|
| 266 |
#' header = c("Treatement\nN=100", "Comparison\nN=300"),
|
|
| 267 |
#' format = "xx (xx.xx%)", |
|
| 268 |
#' rrow("A", c(104, .2), c(100, .4)),
|
|
| 269 |
#' rrow("B", c(23, .4), c(43, .5)),
|
|
| 270 |
#' rrow(""),
|
|
| 271 |
#' rrow("this is a very long section header"),
|
|
| 272 |
#' rrow("estimate", rcell(55.23, "xx.xx", colspan = 2)),
|
|
| 273 |
#' rrow("95% CI", indent = 1, rcell(c(44.8, 67.4), format = "(xx.x, xx.x)", colspan = 2))
|
|
| 274 |
#' ) |
|
| 275 |
#' tbl |
|
| 276 |
#' |
|
| 277 |
#' row.names(tbl) |
|
| 278 |
#' names(tbl) |
|
| 279 |
#' |
|
| 280 |
#' # Subsetting |
|
| 281 |
#' |
|
| 282 |
#' tbl[1, ] |
|
| 283 |
#' tbl[, 1] |
|
| 284 |
#' |
|
| 285 |
#' tbl[1, 2] |
|
| 286 |
#' tbl[2, 1] |
|
| 287 |
#' |
|
| 288 |
#' tbl[3, 2] |
|
| 289 |
#' tbl[5, 1] |
|
| 290 |
#' tbl[5, 2] |
|
| 291 |
#' |
|
| 292 |
#' # Data Structure methods |
|
| 293 |
#' |
|
| 294 |
#' dim(tbl) |
|
| 295 |
#' nrow(tbl) |
|
| 296 |
#' ncol(tbl) |
|
| 297 |
#' names(tbl) |
|
| 298 |
#' |
|
| 299 |
#' # Colspans |
|
| 300 |
#' |
|
| 301 |
#' tbl2 <- rtable( |
|
| 302 |
#' c("A", "B", "C", "D", "E"),
|
|
| 303 |
#' format = "xx", |
|
| 304 |
#' rrow("r1", 1, 2, 3, 4, 5),
|
|
| 305 |
#' rrow("r2", rcell("sp2", colspan = 2), "sp1", rcell("sp2-2", colspan = 2))
|
|
| 306 |
#' ) |
|
| 307 |
#' tbl2 |
|
| 308 |
#' |
|
| 309 |
#' @family compatibility |
|
| 310 |
#' @export |
|
| 311 |
rtable <- function(header, ..., format = NULL, hsep = default_hsep(), |
|
| 312 |
inset = 0L) {
|
|
| 313 | 34x |
if (is.character(header)) {
|
| 314 | 31x |
header <- .char_to_hrows(header) |
| 315 |
} # list(rrowl(NULL, header)) |
|
| 316 | 34x |
if (is.list(header)) {
|
| 317 | 31x |
if (are(header, "TableRow")) {
|
| 318 | 31x |
colinfo <- hrows_to_colinfo(header) |
| 319 | ! |
} else if (are(header, "list")) {
|
| 320 | ! |
colinfo <- do.call(rheader, header) |
| 321 |
} |
|
| 322 | 3x |
} else if (is(header, "InstantiatedColumnInfo")) {
|
| 323 | 3x |
colinfo <- header |
| 324 | ! |
} else if (is(header, "TableRow")) {
|
| 325 | ! |
colinfo <- hrows_to_colinfo(list(header)) |
| 326 |
} else {
|
|
| 327 | ! |
stop("problems")
|
| 328 |
} |
|
| 329 | ||
| 330 | 34x |
body <- list(...) |
| 331 |
## XXX this shouldn't be needed. hacky |
|
| 332 | 34x |
if (length(body) == 1 && is.list(body[[1]])) {
|
| 333 | ! |
body <- body[[1]] |
| 334 |
} |
|
| 335 | 34x |
if (are(body, "ElementaryTable") && |
| 336 | 34x |
all(sapply(body, function(tb) {
|
| 337 | ! |
nrow(tb) == 1 && obj_name(tb) == "" |
| 338 |
}))) {
|
|
| 339 | 1x |
body <- lapply(body, function(tb) tree_children(tb)[[1]]) |
| 340 |
} |
|
| 341 | ||
| 342 | 34x |
TableTree( |
| 343 | 34x |
kids = body, format = format, cinfo = colinfo, |
| 344 | 34x |
labelrow = LabelRow(lev = 0L, label = "", vis = FALSE), |
| 345 | 34x |
hsep = hsep, inset = inset |
| 346 |
) |
|
| 347 |
} |
|
| 348 | ||
| 349 |
#' @rdname rtable |
|
| 350 |
#' @export |
|
| 351 |
rtablel <- function(header, ..., format = NULL, hsep = default_hsep(), inset = 0L) {
|
|
| 352 | 1x |
dots <- list(...) |
| 353 | 1x |
args_list <- c(list(header = header, format = format, hsep = hsep, inset = inset), unlist(lapply( |
| 354 | 1x |
dots, |
| 355 | 1x |
as.list |
| 356 | 1x |
), recursive = FALSE)) |
| 357 | 1x |
do.call(rtable, args_list) |
| 358 |
} |
|
| 359 | ||
| 360 |
# All object annotations are identical (and exist) |
|
| 361 |
all_annots_identical <- function(all_annots) {
|
|
| 362 | 60x |
if (!is.list(all_annots)) {
|
| 363 | 15x |
all_annots[1] != "" && length(unique(all_annots)) == 1 |
| 364 |
} else {
|
|
| 365 | 45x |
length(all_annots[[1]]) > 0 && Reduce(identical, all_annots) |
| 366 |
} |
|
| 367 |
} |
|
| 368 | ||
| 369 |
# Only first object has annotations |
|
| 370 |
only_first_annot <- function(all_annots) {
|
|
| 371 | 56x |
if (!is.list(all_annots)) {
|
| 372 | 14x |
all_annots[1] != "" && all(all_annots[-1] == "") |
| 373 |
} else {
|
|
| 374 | 42x |
length(all_annots[[1]]) > 0 && all(sapply(all_annots, length)[-1] == 0) |
| 375 |
} |
|
| 376 |
} |
|
| 377 | ||
| 378 |
#' @param gap `r lifecycle::badge("deprecated")` ignored.
|
|
| 379 |
#' @param check_headers `r lifecycle::badge("deprecated")` ignored.
|
|
| 380 |
#' |
|
| 381 |
#' @return A formal table object. |
|
| 382 |
#' |
|
| 383 |
#' @rdname rbind |
|
| 384 |
#' @aliases rbind |
|
| 385 |
#' @export |
|
| 386 |
rbindl_rtables <- function(x, gap = lifecycle::deprecated(), check_headers = lifecycle::deprecated()) {
|
|
| 387 |
## nocov start |
|
| 388 |
if (lifecycle::is_present(gap)) {
|
|
| 389 |
lifecycle::deprecate_warn( |
|
| 390 |
when = "0.3.2", |
|
| 391 |
what = "rbindl_rtables(gap)" |
|
| 392 |
) |
|
| 393 |
} |
|
| 394 |
if (lifecycle::is_present(check_headers)) {
|
|
| 395 |
lifecycle::deprecate_warn( |
|
| 396 |
when = "0.3.2", |
|
| 397 |
what = "rbindl_rtables(check_headers)" |
|
| 398 |
) |
|
| 399 |
} |
|
| 400 |
## nocov end |
|
| 401 | ||
| 402 | 16x |
firstcols <- col_info(x[[1]]) |
| 403 | 16x |
i <- 1 |
| 404 | 16x |
while (no_colinfo(firstcols) && i <= length(x)) {
|
| 405 | 2x |
firstcols <- col_info(x[[i]]) |
| 406 | 2x |
i <- i + 1 |
| 407 |
} |
|
| 408 | ||
| 409 | 16x |
lapply(x, function(xi) chk_compat_cinfos(x[[1]], xi)) ## col_info(xi))) |
| 410 | ||
| 411 | 15x |
rbind_annot <- list( |
| 412 | 15x |
main_title = "", |
| 413 | 15x |
subtitles = character(), |
| 414 | 15x |
main_footer = character(), |
| 415 | 15x |
prov_footer = character() |
| 416 |
) |
|
| 417 | ||
| 418 |
# Titles/footer info are (independently) retained from first object if |
|
| 419 |
# identical or missing in all other objects |
|
| 420 | 15x |
all_titles <- sapply(x, main_title) |
| 421 | 15x |
if (all_annots_identical(all_titles) || only_first_annot(all_titles)) {
|
| 422 | 2x |
rbind_annot[["main_title"]] <- all_titles[[1]] |
| 423 |
} |
|
| 424 | ||
| 425 | 15x |
all_sts <- lapply(x, subtitles) |
| 426 | 15x |
if (all_annots_identical(all_sts) || only_first_annot(all_sts)) {
|
| 427 | 2x |
rbind_annot[["subtitles"]] <- all_sts[[1]] |
| 428 |
} |
|
| 429 | ||
| 430 | 15x |
all_ftrs <- lapply(x, main_footer) |
| 431 | 15x |
if (all_annots_identical(all_ftrs) || only_first_annot(all_ftrs)) {
|
| 432 | 2x |
rbind_annot[["main_footer"]] <- all_ftrs[[1]] |
| 433 |
} |
|
| 434 | ||
| 435 | 15x |
all_pfs <- lapply(x, prov_footer) |
| 436 | 15x |
if (all_annots_identical(all_pfs) || only_first_annot(all_pfs)) {
|
| 437 | 2x |
rbind_annot[["prov_footer"]] <- all_pfs[[1]] |
| 438 |
} |
|
| 439 | ||
| 440 |
## if we got only ElementaryTable and |
|
| 441 |
## TableRow objects, construct a new |
|
| 442 |
## elementary table with all the rows |
|
| 443 |
## instead of adding nesting. |
|
| 444 | ||
| 445 |
## we used to check for xi not being a lable row, why?? XXX |
|
| 446 | 15x |
if (all(sapply(x, function(xi) {
|
| 447 | 30x |
(is(xi, "ElementaryTable") && !labelrow_visible(xi)) || |
| 448 | 30x |
is(xi, "TableRow") |
| 449 | 15x |
}))) { ## && !is(xi, "LabelRow")}))) {
|
| 450 | 8x |
x <- unlist(lapply(x, function(xi) {
|
| 451 | 16x |
if (is(xi, "TableRow")) {
|
| 452 | 4x |
xi |
| 453 |
} else {
|
|
| 454 | 12x |
lst <- tree_children(xi) |
| 455 | 12x |
lapply(lst, indent, |
| 456 | 12x |
by = indent_mod(xi) |
| 457 |
) |
|
| 458 |
} |
|
| 459 |
})) |
|
| 460 |
} |
|
| 461 | ||
| 462 | 15x |
TableTree( |
| 463 | 15x |
kids = x, |
| 464 | 15x |
cinfo = firstcols, |
| 465 | 15x |
name = "rbind_root", |
| 466 | 15x |
label = "", |
| 467 | 15x |
title = rbind_annot[["main_title"]], |
| 468 | 15x |
subtitles = rbind_annot[["subtitles"]], |
| 469 | 15x |
main_footer = rbind_annot[["main_footer"]], |
| 470 | 15x |
prov_footer = rbind_annot[["prov_footer"]] |
| 471 |
) |
|
| 472 |
} |
|
| 473 | ||
| 474 |
#' Row-bind `TableTree` and related objects |
|
| 475 |
#' |
|
| 476 |
#' @param deparse.level (`numeric(1)`)\cr currently ignored. |
|
| 477 |
#' @param ... (`ANY`)\cr elements to be stacked. |
|
| 478 |
#' |
|
| 479 |
#' @note |
|
| 480 |
#' When objects are row-bound, titles and footer information is retained from the first object (if any exists) if all |
|
| 481 |
#' other objects have no titles/footers or have identical titles/footers. Otherwise, all titles/footers are removed |
|
| 482 |
#' and must be set for the bound table via the [formatters::main_title()], [formatters::subtitles()], |
|
| 483 |
#' [formatters::main_footer()], and [formatters::prov_footer()] functions. |
|
| 484 |
#' |
|
| 485 |
#' @examples |
|
| 486 |
#' mtbl <- rtable( |
|
| 487 |
#' header = rheader( |
|
| 488 |
#' rrow(row.name = NULL, rcell("Sepal.Length", colspan = 2), rcell("Petal.Length", colspan = 2)),
|
|
| 489 |
#' rrow(NULL, "mean", "median", "mean", "median") |
|
| 490 |
#' ), |
|
| 491 |
#' rrow( |
|
| 492 |
#' row.name = "All Species", |
|
| 493 |
#' mean(iris$Sepal.Length), median(iris$Sepal.Length), |
|
| 494 |
#' mean(iris$Petal.Length), median(iris$Petal.Length), |
|
| 495 |
#' format = "xx.xx" |
|
| 496 |
#' ) |
|
| 497 |
#' ) |
|
| 498 |
#' |
|
| 499 |
#' mtbl2 <- with(subset(iris, Species == "setosa"), rtable( |
|
| 500 |
#' header = rheader( |
|
| 501 |
#' rrow(row.name = NULL, rcell("Sepal.Length", colspan = 2), rcell("Petal.Length", colspan = 2)),
|
|
| 502 |
#' rrow(NULL, "mean", "median", "mean", "median") |
|
| 503 |
#' ), |
|
| 504 |
#' rrow( |
|
| 505 |
#' row.name = "Setosa", |
|
| 506 |
#' mean(Sepal.Length), median(Sepal.Length), |
|
| 507 |
#' mean(Petal.Length), median(Petal.Length), |
|
| 508 |
#' format = "xx.xx" |
|
| 509 |
#' ) |
|
| 510 |
#' )) |
|
| 511 |
#' |
|
| 512 |
#' rbind(mtbl, mtbl2) |
|
| 513 |
#' rbind(mtbl, rrow(), mtbl2) |
|
| 514 |
#' rbind(mtbl, rrow("aaa"), indent(mtbl2))
|
|
| 515 |
#' |
|
| 516 |
#' @exportMethod rbind |
|
| 517 |
#' @rdname rbind |
|
| 518 |
setMethod( |
|
| 519 |
"rbind", "VTableNodeInfo", |
|
| 520 |
function(..., deparse.level = 1) {
|
|
| 521 | ! |
rbindl_rtables(list(...)) |
| 522 |
} |
|
| 523 |
) |
|
| 524 | ||
| 525 |
#' @param y (`ANY`)\cr second element to be row-bound via `rbind2`. |
|
| 526 |
#' |
|
| 527 |
#' @exportMethod rbind2 |
|
| 528 |
#' @rdname int_methods |
|
| 529 |
setMethod( |
|
| 530 |
"rbind2", c("VTableNodeInfo", "missing"),
|
|
| 531 |
function(x, y) {
|
|
| 532 | 2x |
TableTree(kids = list(x), cinfo = col_info(x), name = "rbind_root", label = "") |
| 533 |
} |
|
| 534 |
) |
|
| 535 | ||
| 536 |
#' @param x (`VTableNodeInfo`)\cr `TableTree`, `ElementaryTable`, or `TableRow` object. |
|
| 537 |
#' @param y (`VTableNodeInfo`)\cr `TableTree`, `ElementaryTable`, or `TableRow` object. |
|
| 538 |
#' |
|
| 539 |
#' @exportMethod rbind2 |
|
| 540 |
#' @rdname rbind |
|
| 541 |
setMethod( |
|
| 542 |
"rbind2", "VTableNodeInfo", |
|
| 543 |
function(x, y) {
|
|
| 544 | 12x |
rbindl_rtables(list(x, y)) |
| 545 |
} |
|
| 546 |
) |
|
| 547 | ||
| 548 |
EmptyTreePos <- TreePos() |
|
| 549 | ||
| 550 |
## this is painful to do right but we were doing it wrong |
|
| 551 |
## before and it now matters because count display information |
|
| 552 |
## is in the tree which means all points in the structure |
|
| 553 |
## must be pathable, which they aren't if siblings have |
|
| 554 |
## identical names |
|
| 555 |
fix_col_nm_recursive <- function(ct, newname, rename_obj = TRUE, oldnm) {
|
|
| 556 | 120x |
if (rename_obj) {
|
| 557 | 19x |
obj_name(ct) <- newname |
| 558 |
} |
|
| 559 | 120x |
if (is(ct, "LayoutColTree")) {
|
| 560 | 45x |
kids <- tree_children(ct) |
| 561 | 45x |
kidnms <- names(kids) |
| 562 | 45x |
newkids <- lapply(kids, fix_col_nm_recursive, |
| 563 | 45x |
newname = newname, |
| 564 | 45x |
rename_obj = FALSE, |
| 565 | 45x |
oldnm = oldnm |
| 566 |
) |
|
| 567 | 45x |
names(newkids) <- kidnms |
| 568 | 45x |
tree_children(ct) <- newkids |
| 569 |
} |
|
| 570 | 120x |
mypos <- tree_pos(ct) |
| 571 | 120x |
if (!identical(mypos, EmptyTreePos)) {
|
| 572 | 97x |
spls <- pos_splits(mypos) |
| 573 | 97x |
firstspl <- spls[[1]] |
| 574 | 97x |
if (obj_name(firstspl) == oldnm) {
|
| 575 | ! |
obj_name(firstspl) <- newname |
| 576 | ! |
spls[[1]] <- firstspl |
| 577 | ! |
pos_splits(mypos) <- spls |
| 578 | ! |
tree_pos(ct) <- mypos |
| 579 |
} |
|
| 580 |
} |
|
| 581 | 120x |
if (!rename_obj) {
|
| 582 | 101x |
spls <- pos_splits(mypos) |
| 583 | 101x |
splvals <- pos_splvals(mypos) |
| 584 | 101x |
pos_splits(mypos) <- c( |
| 585 | 101x |
list(AllSplit(split_name = newname)), |
| 586 | 101x |
spls |
| 587 |
) |
|
| 588 | 101x |
pos_splvals(mypos) <- c( |
| 589 | 101x |
list(SplitValue(NA_character_, |
| 590 | 101x |
sub_expr = quote(TRUE) |
| 591 |
)), |
|
| 592 | 101x |
splvals |
| 593 |
) |
|
| 594 | 101x |
tree_pos(ct) <- mypos |
| 595 |
} |
|
| 596 | 120x |
ct |
| 597 |
} |
|
| 598 | ||
| 599 |
fix_nms <- function(ct) {
|
|
| 600 | 129x |
if (is(ct, "LayoutColLeaf")) {
|
| 601 | 75x |
return(ct) |
| 602 |
} |
|
| 603 | 54x |
kids <- lapply(tree_children(ct), fix_nms) |
| 604 | 54x |
names(kids) <- vapply(kids, obj_name, "") |
| 605 | 54x |
tree_children(ct) <- kids |
| 606 | 54x |
ct |
| 607 |
} |
|
| 608 | ||
| 609 |
make_cbind_names <- function(num, tokens) {
|
|
| 610 | 9x |
cbind_tokens <- grep("^(new_)*cbind_tbl", tokens, value = TRUE)
|
| 611 | 9x |
ret <- paste0("cbind_tbl_", seq_len(num))
|
| 612 | 9x |
if (length(cbind_tokens) == 0) {
|
| 613 | 9x |
return(ret) |
| 614 |
} |
|
| 615 | ! |
oldprefixes <- gsub("cbind_tbl.*", "", cbind_tokens)
|
| 616 | ! |
oldprefix <- oldprefixes[which.max(nchar(oldprefixes))] |
| 617 | ! |
paste0("new_", oldprefix, ret)
|
| 618 |
} |
|
| 619 | ||
| 620 |
combine_cinfo <- function(..., new_total = NULL, sync_count_vis) {
|
|
| 621 | 10x |
tabs <- list(...) |
| 622 | 10x |
chk_cbindable_many(tabs) |
| 623 | 9x |
cinfs <- lapply(tabs, col_info) |
| 624 | 9x |
stopifnot(are(cinfs, "InstantiatedColumnInfo")) |
| 625 | ||
| 626 | 9x |
ctrees <- lapply(cinfs, coltree) |
| 627 | 9x |
oldnms <- nms <- vapply(ctrees, obj_name, "") |
| 628 | 9x |
path_els <- unique(unlist(lapply(ctrees, col_paths), recursive = TRUE)) |
| 629 | 9x |
nms <- make_cbind_names(num = length(oldnms), tokens = path_els) |
| 630 | ||
| 631 | 9x |
ctrees <- mapply(function(ct, nm, oldnm) {
|
| 632 | 19x |
ct <- fix_col_nm_recursive(ct, nm, rename_obj = TRUE, oldnm = "") # oldnm) |
| 633 | 19x |
ct |
| 634 | 9x |
}, ct = ctrees, nm = nms, oldnm = oldnms, SIMPLIFY = FALSE) |
| 635 | 9x |
names(ctrees) <- nms |
| 636 | ||
| 637 | 9x |
newctree <- LayoutColTree(kids = ctrees, colcount = NA_integer_, name = "cbind_root") |
| 638 | 9x |
newctree <- fix_nms(newctree) |
| 639 | 9x |
newcounts <- unlist(lapply(cinfs, col_counts)) |
| 640 | 9x |
if (is.null(new_total)) {
|
| 641 | 9x |
new_total <- sum(newcounts) |
| 642 |
} |
|
| 643 | 9x |
newexprs <- unlist(lapply(cinfs, col_exprs), recursive = FALSE) |
| 644 | 9x |
newexargs <- unlist(lapply(cinfs, col_extra_args), recursive = FALSE) %||% vector("list", length(newcounts))
|
| 645 | 9x |
if (!sync_count_vis) {
|
| 646 | 1x |
newdisp <- NA |
| 647 |
} else {
|
|
| 648 | 8x |
newdisp <- any(vapply(cinfs, disp_ccounts, NA)) |
| 649 |
} |
|
| 650 | 9x |
alltls <- lapply(cinfs, top_left) |
| 651 | 9x |
newtl <- character() |
| 652 | 9x |
if (!are(tabs, "TableRow")) {
|
| 653 | 9x |
alltls <- alltls[vapply(alltls, function(x) length(x) > 0, NA)] ## these are already enforced to all be the same |
| 654 | 9x |
if (length(alltls) > 0) {
|
| 655 | ! |
newtl <- alltls[[1]] |
| 656 |
} |
|
| 657 |
} |
|
| 658 | 9x |
InstantiatedColumnInfo( |
| 659 | 9x |
treelyt = newctree, |
| 660 | 9x |
csubs = newexprs, |
| 661 | 9x |
extras = newexargs, |
| 662 | 9x |
cnts = newcounts, |
| 663 | 9x |
dispcounts = newdisp, |
| 664 | 9x |
countformat = colcount_format(cinfs[[1]]), |
| 665 | 9x |
total_cnt = new_total, |
| 666 | 9x |
topleft = newtl |
| 667 |
) |
|
| 668 |
} |
|
| 669 | ||
| 670 |
nz_len_els <- function(lst) {
|
|
| 671 | 100x |
if (is(lst, "list")) {
|
| 672 | 13x |
lst[vapply(lst, function(x) length(x) > 0, NA)] |
| 673 | 87x |
} else if (is(lst, "character")) {
|
| 674 | 74x |
lst[nzchar(lst)] |
| 675 |
} else {
|
|
| 676 | 13x |
lst |
| 677 |
} |
|
| 678 |
} |
|
| 679 | ||
| 680 |
has_one_unq <- function(x) {
|
|
| 681 | 100x |
length(unique(nz_len_els(x))) <= 1 |
| 682 |
} |
|
| 683 | ||
| 684 |
classvec <- function(lst, enforce_one = TRUE) {
|
|
| 685 | 26x |
if (enforce_one) {
|
| 686 | 26x |
vapply(lst, class, "") |
| 687 |
} else {
|
|
| 688 | ! |
lapply(lst, class) |
| 689 |
} |
|
| 690 |
} |
|
| 691 | ||
| 692 |
chk_cbindable_many <- function(lst) {
|
|
| 693 |
## we actually want is/inherits there but no easy way |
|
| 694 |
## to figure out what the lowest base class is |
|
| 695 |
## that I can think of right now, so we do the |
|
| 696 |
## broken wrong thing instead :( |
|
| 697 | 15x |
if (are(lst, "TableRow")) {
|
| 698 | 2x |
if (!has_one_unq(classvec(lst))) {
|
| 699 | 1x |
stop("Cannot cbind different types of TableRow objects together")
|
| 700 |
} |
|
| 701 | 1x |
return(TRUE) |
| 702 |
} |
|
| 703 |
## if(!are(lst, "VTableTree") |
|
| 704 |
## stop("Not all elements to be bound are TableTrees or TableRows")
|
|
| 705 | ||
| 706 | 13x |
nrs <- vapply(lst, NROW, 1L) |
| 707 | 13x |
if (!has_one_unq(nrs)) {
|
| 708 | ! |
stop("Not all elements to be bound have matching numbers of rows")
|
| 709 |
} |
|
| 710 | ||
| 711 | 13x |
tls <- lapply(lst, top_left) |
| 712 | 13x |
if (!has_one_unq(tls[vapply(tls, function(x) length(x) > 0, NA)])) {
|
| 713 | 2x |
stop( |
| 714 | 2x |
"Elements to be bound have differing top-left content: ", |
| 715 | 2x |
paste(which(!duplicated(tls)), collapse = " ") |
| 716 |
) |
|
| 717 |
} |
|
| 718 | ||
| 719 | 11x |
if (all(vapply(lst, function(x) nrow(x) == 0, NA))) {
|
| 720 | 1x |
return(TRUE) |
| 721 |
} |
|
| 722 | ||
| 723 | 10x |
rns <- matrix(vapply(lst, row.names, rep("", nrs[[1]])),
|
| 724 | 10x |
nrow = nrs[[1]] |
| 725 |
) |
|
| 726 | 10x |
rnsok <- apply(rns, 1, has_one_unq) |
| 727 | 10x |
if (!all(rnsok)) {
|
| 728 | ! |
stop( |
| 729 | ! |
"Mismatching, non-empty row names detected in rows ", |
| 730 | ! |
paste(which(!rnsok), collapse = " ") |
| 731 |
) |
|
| 732 |
} |
|
| 733 | ||
| 734 | 10x |
rws <- lapply(lst, collect_leaves, add.labrows = TRUE) |
| 735 | 10x |
rwclsmat <- matrix(unlist(lapply(rws, classvec)), |
| 736 | 10x |
ncol = length(lst) |
| 737 |
) |
|
| 738 | ||
| 739 | 10x |
rwsok <- apply(rwclsmat, 1, has_one_unq) |
| 740 | 10x |
if (!all(rwsok)) {
|
| 741 | ! |
stop( |
| 742 | ! |
"Mismatching row classes found for rows: ", |
| 743 | ! |
paste(which(!rwsok), collapse = " ") |
| 744 |
) |
|
| 745 |
} |
|
| 746 | 10x |
TRUE |
| 747 |
} |
|
| 748 | ||
| 749 |
#' Column-bind two `TableTree` objects |
|
| 750 |
#' |
|
| 751 |
#' @param x (`TableTree` or `TableRow`)\cr a table or row object. |
|
| 752 |
#' @param ... one or more further objects of the same class as `x`. |
|
| 753 |
#' @param sync_count_vis (`logical(1)`)\cr should column count |
|
| 754 |
#' visibility be synced across the new and existing columns. |
|
| 755 |
#' Currently defaults to `TRUE` for backwards compatibility but |
|
| 756 |
#' this may change in future releases. |
|
| 757 |
#' |
|
| 758 |
#' @inherit rbindl_rtables return |
|
| 759 |
#' |
|
| 760 |
#' @examples |
|
| 761 |
#' x <- rtable(c("A", "B"), rrow("row 1", 1, 2), rrow("row 2", 3, 4))
|
|
| 762 |
#' y <- rtable("C", rrow("row 1", 5), rrow("row 2", 6))
|
|
| 763 |
#' z <- rtable("D", rrow("row 1", 9), rrow("row 2", 10))
|
|
| 764 |
#' |
|
| 765 |
#' t1 <- cbind_rtables(x, y) |
|
| 766 |
#' t1 |
|
| 767 |
#' |
|
| 768 |
#' t2 <- cbind_rtables(x, y, z) |
|
| 769 |
#' t2 |
|
| 770 |
#' |
|
| 771 |
#' col_paths_summary(t1) |
|
| 772 |
#' col_paths_summary(t2) |
|
| 773 |
#' |
|
| 774 |
#' @export |
|
| 775 |
cbind_rtables <- function(x, ..., sync_count_vis = TRUE) {
|
|
| 776 | 10x |
lst <- list(...) |
| 777 | 10x |
newcinfo <- combine_cinfo(x, ..., sync_count_vis = sync_count_vis) |
| 778 | 9x |
recurse_cbindl(x, cinfo = newcinfo, .list = lst) |
| 779 |
} |
|
| 780 | ||
| 781 | 89x |
setGeneric("recurse_cbindl", function(x, cinfo, .list = NULL) standardGeneric("recurse_cbindl"))
|
| 782 | ||
| 783 |
setMethod( |
|
| 784 |
"recurse_cbindl", c( |
|
| 785 |
x = "VTableNodeInfo", |
|
| 786 |
cinfo = "NULL" |
|
| 787 |
), |
|
| 788 |
function(x, cinfo, .list = NULL) {
|
|
| 789 | ! |
recurse_cbindl(x, cinfo = combine_cinfo(.list), .list = .list) |
| 790 |
} |
|
| 791 |
) |
|
| 792 | ||
| 793 |
setMethod( |
|
| 794 |
"recurse_cbindl", c( |
|
| 795 |
x = "TableTree", |
|
| 796 |
cinfo = "InstantiatedColumnInfo" |
|
| 797 |
), |
|
| 798 |
function(x, cinfo, .list = NULL) {
|
|
| 799 | 18x |
stopifnot(are(.list, "VTableTree")) |
| 800 |
## chk_cbindable(x, y) |
|
| 801 | 18x |
xcont <- content_table(x) |
| 802 | 18x |
lstconts <- lapply(.list, content_table) |
| 803 | 18x |
lcontnrows <- vapply(lstconts, NROW, 1L) |
| 804 | 18x |
unqnrcont <- unique(c(NROW(xcont), lcontnrows)) |
| 805 | 18x |
if (length(unqnrcont) > 1) {
|
| 806 | ! |
stop( |
| 807 | ! |
"Got differing numbers of content rows [", |
| 808 | ! |
paste(unqnrcont, collapse = ", "), |
| 809 | ! |
"]. Unable to cbind these rtables" |
| 810 |
) |
|
| 811 |
} |
|
| 812 | ||
| 813 | 18x |
if (unqnrcont == 0) {
|
| 814 | 18x |
cont <- ElementaryTable(cinfo = cinfo) |
| 815 |
} else {
|
|
| 816 | ! |
cont <- recurse_cbindl(xcont, |
| 817 | ! |
.list = lstconts, |
| 818 | ! |
cinfo = cinfo |
| 819 |
) |
|
| 820 |
} |
|
| 821 | ||
| 822 | 18x |
kids <- lapply( |
| 823 | 18x |
seq_along(tree_children(x)), |
| 824 | 18x |
function(i) {
|
| 825 | 27x |
recurse_cbindl( |
| 826 | 27x |
x = tree_children(x)[[i]], |
| 827 | 27x |
cinfo = cinfo, |
| 828 | 27x |
.list = lapply(.list, function(tt) tree_children(tt)[[i]]) |
| 829 |
) |
|
| 830 |
} |
|
| 831 |
) |
|
| 832 | 18x |
names(kids) <- names(tree_children(x)) |
| 833 | 18x |
TableTree( |
| 834 | 18x |
kids = kids, labelrow = recurse_cbindl(tt_labelrow(x), |
| 835 | 18x |
cinfo = cinfo, |
| 836 | 18x |
.list = lapply(.list, tt_labelrow) |
| 837 |
), |
|
| 838 | 18x |
cont = cont, |
| 839 | 18x |
name = obj_name(x), |
| 840 | 18x |
lev = tt_level(x), |
| 841 | 18x |
cinfo = cinfo, |
| 842 | 18x |
format = obj_format(x) |
| 843 |
) |
|
| 844 |
} |
|
| 845 |
) |
|
| 846 | ||
| 847 |
setMethod( |
|
| 848 |
"recurse_cbindl", c( |
|
| 849 |
x = "ElementaryTable", |
|
| 850 |
cinfo = "InstantiatedColumnInfo" |
|
| 851 |
), |
|
| 852 |
function(x, cinfo, .list) {
|
|
| 853 | 18x |
stopifnot(are(.list, class(x))) |
| 854 |
## chk_cbindable(x,y) |
|
| 855 | 18x |
if (nrow(x) == 0 && all(vapply(.list, nrow, 1L) == 0)) {
|
| 856 | 1x |
col_info(x) <- cinfo |
| 857 | 1x |
return(x) ## this needs testing... I was right, it did #136 |
| 858 |
} |
|
| 859 | 17x |
kids <- lapply( |
| 860 | 17x |
seq_along(tree_children(x)), |
| 861 | 17x |
function(i) {
|
| 862 | 18x |
recurse_cbindl( |
| 863 | 18x |
x = tree_children(x)[[i]], |
| 864 | 18x |
cinfo = cinfo, |
| 865 | 18x |
.list = lapply(.list, function(tt) tree_children(tt)[[i]]) |
| 866 |
) |
|
| 867 |
} |
|
| 868 |
) |
|
| 869 | 17x |
names(kids) <- names(tree_children(x)) |
| 870 | ||
| 871 | 17x |
ElementaryTable( |
| 872 | 17x |
kids = kids, |
| 873 | 17x |
labelrow = recurse_cbindl(tt_labelrow(x), |
| 874 | 17x |
.list = lapply(.list, tt_labelrow), |
| 875 | 17x |
cinfo |
| 876 |
), |
|
| 877 | 17x |
name = obj_name(x), |
| 878 | 17x |
lev = tt_level(x), |
| 879 | 17x |
cinfo = cinfo, |
| 880 | 17x |
format = obj_format(x), |
| 881 | 17x |
var = obj_avar(x) |
| 882 |
) |
|
| 883 |
} |
|
| 884 |
) |
|
| 885 | ||
| 886 |
.combine_rows <- function(x, cinfo = NULL, .list) {
|
|
| 887 | 18x |
stopifnot(are(.list, class(x))) |
| 888 | ||
| 889 | 18x |
avars <- c(obj_avar(x), unlist(lapply(.list, obj_avar), recursive = FALSE)) |
| 890 | 18x |
avars <- avars[!is.na(avars)] |
| 891 | ||
| 892 | 18x |
if (length(unique(avars)) > 1) {
|
| 893 | ! |
stop("Got rows that don't analyze the same variable")
|
| 894 |
} |
|
| 895 | ||
| 896 | 18x |
xlst <- c(list(x), .list) |
| 897 | ||
| 898 | 18x |
ncols <- vapply(xlst, ncol, 1L) |
| 899 | 18x |
totcols <- sum(ncols) |
| 900 | 18x |
cumncols <- cumsum(ncols) |
| 901 | 18x |
strtncols <- c(0L, head(cumncols, -1)) + 1L |
| 902 | 18x |
vals <- vector("list", totcols)
|
| 903 | 18x |
cspans <- integer(totcols) |
| 904 |
## vals[1:ncol(x)] <- row_values(x) |
|
| 905 |
## cpans[1:ncol(x)] <- row_cspans(x) |
|
| 906 | ||
| 907 | 18x |
for (i in seq_along(xlst)) {
|
| 908 | 37x |
strt <- strtncols[i] |
| 909 | 37x |
end <- cumncols[i] |
| 910 |
## full vars are here for debugging purposes |
|
| 911 | 37x |
fullvy <- vy <- row_cells(xlst[[i]]) # nolint |
| 912 | 37x |
fullcspy <- cspy <- row_cspans(xlst[[i]]) # nolint |
| 913 | ||
| 914 |
if ( |
|
| 915 | 37x |
i > 1 && |
| 916 | 37x |
identical(rawvalues(vy[[1]]), rawvalues(lastval)) && |
| 917 |
## cspy[1] == lastspn && |
|
| 918 | 37x |
lastspn > 1 |
| 919 |
) {
|
|
| 920 | ! |
vy <- vy[-1] |
| 921 | ! |
cspans[strt - 1L] <- lastspn + cspy[1] |
| 922 | ! |
cspy <- cspy[-1] |
| 923 | ! |
strt <- strt + 1L |
| 924 |
} |
|
| 925 | 37x |
if (length(vy) > 0) {
|
| 926 | 37x |
vals[strt:end] <- vy |
| 927 | 37x |
cspans[strt:end] <- cspy |
| 928 | 37x |
lastval <- vy[[length(vy)]] |
| 929 | 37x |
lastspn <- cspy[[length(cspy)]] |
| 930 |
} else {
|
|
| 931 |
## lastval stays the same |
|
| 932 | ! |
lastspn <- cspans[strtncols[i] - 1] ## already updated |
| 933 |
} |
|
| 934 |
} |
|
| 935 | ||
| 936 |
## Could be DataRow or ContentRow |
|
| 937 |
## This is ok because LabelRow is special cased |
|
| 938 | 18x |
constr_fun <- get(class(x), mode = "function") |
| 939 | 18x |
constr_fun( |
| 940 | 18x |
vals = vals, |
| 941 | 18x |
cspan = cspans, |
| 942 | 18x |
cinfo = cinfo, |
| 943 | 18x |
var = obj_avar(x), |
| 944 | 18x |
format = obj_format(x), |
| 945 | 18x |
name = obj_name(x), |
| 946 | 18x |
label = obj_label(x) |
| 947 |
) |
|
| 948 |
} |
|
| 949 | ||
| 950 |
setMethod( |
|
| 951 |
"recurse_cbindl", c( |
|
| 952 |
"TableRow", |
|
| 953 |
"InstantiatedColumnInfo" |
|
| 954 |
), |
|
| 955 |
function(x, cinfo = NULL, .list) {
|
|
| 956 | 18x |
.combine_rows(x, cinfo, .list) |
| 957 |
} |
|
| 958 |
) |
|
| 959 | ||
| 960 |
setMethod( |
|
| 961 |
"recurse_cbindl", c( |
|
| 962 |
x = "LabelRow", |
|
| 963 |
cinfo = "InstantiatedColumnInfo" |
|
| 964 |
), |
|
| 965 |
function(x, cinfo = NULL, .list) {
|
|
| 966 | 35x |
col_info(x) <- cinfo |
| 967 | 35x |
x |
| 968 |
} |
|
| 969 |
) |
|
| 970 | ||
| 971 |
## we don't care about the following discrepencies: |
|
| 972 |
## - ci2 having NA counts when ci1 doesn't |
|
| 973 |
## - mismatching display_ccounts values |
|
| 974 |
## - mismatching colcount formats |
|
| 975 |
## |
|
| 976 | ||
| 977 |
# chk_compat_cinfos <- function(ci1, ci2) {
|
|
| 978 |
chk_compat_cinfos <- function(tt1, tt2) {
|
|
| 979 | 41x |
nc1 <- ncol(tt1) |
| 980 | 41x |
nc2 <- ncol(tt2) |
| 981 | 41x |
if (nc1 != nc2 && nc1 > 0 && nc2 > 0) {
|
| 982 | 1x |
stop("Column structures contain different non-zero numbers of columns: ", nc1, ", ", nc2)
|
| 983 |
} |
|
| 984 | 40x |
if (no_colinfo(tt1) || no_colinfo(tt2)) {
|
| 985 | 10x |
return(TRUE) |
| 986 |
} |
|
| 987 | 30x |
ci1 <- col_info(tt1) |
| 988 | 30x |
ci2 <- col_info(tt2) |
| 989 |
## this will enforce same length and |
|
| 990 |
## same names, in addition to same |
|
| 991 |
## expressions so we dont need |
|
| 992 |
## to check those separateley |
|
| 993 | 30x |
if (!identical(col_exprs(ci1), col_exprs(ci2))) {
|
| 994 | ! |
stop("Column structures not compatible: subset expression lists not identical")
|
| 995 |
} |
|
| 996 | ||
| 997 | 30x |
if (any(!is.na(col_counts(ci2))) && |
| 998 | 30x |
!identical( |
| 999 | 30x |
col_counts(ci1), |
| 1000 | 30x |
col_counts(ci2) |
| 1001 |
)) {
|
|
| 1002 | ! |
stop("Column structures not compatible: 2nd column structure has non-matching, non-null column counts")
|
| 1003 |
} |
|
| 1004 | ||
| 1005 | 30x |
if (any(sapply( |
| 1006 | 30x |
col_extra_args(ci2), |
| 1007 | 30x |
function(x) length(x) > 0 |
| 1008 |
)) && |
|
| 1009 | 30x |
!identical( |
| 1010 | 30x |
col_extra_args(ci1), |
| 1011 | 30x |
col_extra_args(ci2) |
| 1012 |
)) {
|
|
| 1013 | ! |
stop( |
| 1014 | ! |
"Column structures not compatible: 2nd column structure has ", |
| 1015 | ! |
"non-matching, non-null extra args" |
| 1016 |
) |
|
| 1017 |
} |
|
| 1018 | ||
| 1019 | 30x |
if (any(nzchar(top_left(ci1))) && any(nzchar(top_left(ci2))) && !identical(top_left(ci1), top_left(ci2))) {
|
| 1020 | 1x |
stop( |
| 1021 | 1x |
"Top-left materials not compatible: Got non-empty, non-matching ", |
| 1022 | 1x |
"top-left materials. Clear them using top_left(x)<-character() ", |
| 1023 | 1x |
"before binding to force compatibility." |
| 1024 |
) |
|
| 1025 |
} |
|
| 1026 | 29x |
TRUE |
| 1027 |
} |
|
| 1028 | ||
| 1029 | ||
| 1030 |
#' Insert `rrow`s at (before) a specific location |
|
| 1031 |
#' |
|
| 1032 |
#' `r lifecycle::badge("deprecated")`
|
|
| 1033 |
#' |
|
| 1034 |
#' This function is deprecated and will be removed in a future release of `rtables`. Please use |
|
| 1035 |
#' [insert_row_at_path()] or [label_at_path()] instead. |
|
| 1036 |
#' |
|
| 1037 |
#' @param tbl (`VTableTree`)\cr a `rtable` object. |
|
| 1038 |
#' @param rrow (`TableRow`)\cr an `rrow` to append to `tbl`. |
|
| 1039 |
#' @param at (`integer(1)`)\cr position into which to put the `rrow`, defaults to beginning (i.e. row 1). |
|
| 1040 |
#' @param ascontent (`flag`)\cr currently ignored. |
|
| 1041 |
#' |
|
| 1042 |
#' @return A `TableTree` of the same specific class as `tbl`. |
|
| 1043 |
#' |
|
| 1044 |
#' @note |
|
| 1045 |
#' Label rows (i.e. a row with no data values, only a `row.name`) can only be inserted at positions which do |
|
| 1046 |
#' not already contain a label row when there is a non-trivial nested row structure in `tbl`. |
|
| 1047 |
#' |
|
| 1048 |
#' @examples |
|
| 1049 |
#' o <- options(warn = 0) |
|
| 1050 |
#' lyt <- basic_table() %>% |
|
| 1051 |
#' split_cols_by("Species") %>%
|
|
| 1052 |
#' analyze("Sepal.Length")
|
|
| 1053 |
#' |
|
| 1054 |
#' tbl <- build_table(lyt, iris) |
|
| 1055 |
#' |
|
| 1056 |
#' insert_rrow(tbl, rrow("Hello World"))
|
|
| 1057 |
#' insert_rrow(tbl, rrow("Hello World"), at = 2)
|
|
| 1058 |
#' |
|
| 1059 |
#' lyt2 <- basic_table() %>% |
|
| 1060 |
#' split_cols_by("Species") %>%
|
|
| 1061 |
#' split_rows_by("Species") %>%
|
|
| 1062 |
#' analyze("Sepal.Length")
|
|
| 1063 |
#' |
|
| 1064 |
#' tbl2 <- build_table(lyt2, iris) |
|
| 1065 |
#' |
|
| 1066 |
#' insert_rrow(tbl2, rrow("Hello World"))
|
|
| 1067 |
#' insert_rrow(tbl2, rrow("Hello World"), at = 2)
|
|
| 1068 |
#' insert_rrow(tbl2, rrow("Hello World"), at = 4)
|
|
| 1069 |
#' |
|
| 1070 |
#' insert_rrow(tbl2, rrow("new row", 5, 6, 7))
|
|
| 1071 |
#' |
|
| 1072 |
#' insert_rrow(tbl2, rrow("new row", 5, 6, 7), at = 3)
|
|
| 1073 |
#' |
|
| 1074 |
#' options(o) |
|
| 1075 |
#' |
|
| 1076 |
#' @export |
|
| 1077 |
insert_rrow <- function(tbl, rrow, at = 1, |
|
| 1078 |
ascontent = FALSE) {
|
|
| 1079 | 9x |
lifecycle::deprecate_warn( |
| 1080 | 9x |
when = "0.4.0", |
| 1081 | 9x |
what = "insert_rrow()", |
| 1082 | 9x |
with = I("insert_row_at_path() or label_at_path()")
|
| 1083 |
) |
|
| 1084 | 9x |
stopifnot( |
| 1085 | 9x |
is(tbl, "VTableTree"), |
| 1086 | 9x |
is(rrow, "TableRow"), |
| 1087 | 9x |
at >= 1 && at <= nrow(tbl) + 1 |
| 1088 |
) |
|
| 1089 | 9x |
chk_compat_cinfos(tbl, rrow) |
| 1090 | 8x |
if (no_colinfo(rrow)) {
|
| 1091 | 8x |
col_info(rrow) <- col_info(tbl) |
| 1092 |
} |
|
| 1093 | ||
| 1094 | 8x |
if (at == 1) {
|
| 1095 | 4x |
return(rbindl_rtables(list(rrow, tbl))) |
| 1096 | 4x |
} else if (at == nrow(tbl) + 1) {
|
| 1097 | 1x |
return(rbind2(tbl, rrow)) |
| 1098 |
} |
|
| 1099 | ||
| 1100 | 3x |
ret <- recurse_insert(tbl, rrow, |
| 1101 | 3x |
at = at, |
| 1102 | 3x |
pos = 0, |
| 1103 | 3x |
ascontent = ascontent |
| 1104 |
) |
|
| 1105 | 3x |
ret |
| 1106 |
} |
|
| 1107 | ||
| 1108 |
.insert_helper <- function(tt, row, at, pos, |
|
| 1109 |
ascontent = FALSE) {
|
|
| 1110 | 9x |
islab <- is(row, "LabelRow") |
| 1111 | 9x |
kids <- tree_children(tt) |
| 1112 | 9x |
numkids <- length(kids) |
| 1113 | 9x |
kidnrs <- sapply(kids, nrow) |
| 1114 | 9x |
cumpos <- pos + cumsum(kidnrs) |
| 1115 | 9x |
contnr <- if (is(tt, "TableTree")) {
|
| 1116 | 6x |
nrow(content_table(tt)) |
| 1117 |
} else {
|
|
| 1118 | 3x |
0 |
| 1119 |
} |
|
| 1120 | 9x |
contnr <- contnr + as.numeric(labelrow_visible(tt)) |
| 1121 | ||
| 1122 | 9x |
totnr <- nrow(tt) |
| 1123 | 9x |
endpos <- pos + totnr |
| 1124 | 9x |
atend <- !islab && endpos == at - 1 |
| 1125 | 9x |
if (at == pos + 1 && islab) {
|
| 1126 | 2x |
if (labelrow_visible(tt)) {
|
| 1127 | ! |
stop("Inserting a label row at a position that already has a label row is not currently supported")
|
| 1128 |
} |
|
| 1129 | 2x |
tt_labelrow(tt) <- row |
| 1130 | 2x |
return(tt) |
| 1131 |
} |
|
| 1132 | ||
| 1133 | 7x |
if (numkids == 0) {
|
| 1134 | ! |
kids <- list(row) |
| 1135 | 7x |
} else if (atend) {
|
| 1136 | 2x |
if (are(kids, "TableRow")) {
|
| 1137 | 1x |
kids <- c(kids, list(row)) |
| 1138 |
} else {
|
|
| 1139 | 1x |
kids[[numkids]] <- recurse_insert( |
| 1140 | 1x |
kids[[numkids]], |
| 1141 | 1x |
row = row, |
| 1142 | 1x |
at = at, |
| 1143 | 1x |
pos = pos + contnr + sum(kidnrs[-numkids]), |
| 1144 | 1x |
ascontent = ascontent |
| 1145 |
) |
|
| 1146 |
} |
|
| 1147 |
} else { # have >0 kids
|
|
| 1148 | 5x |
kidnrs <- sapply(kids, nrow) |
| 1149 | 5x |
cumpos <- pos + cumsum(kidnrs) |
| 1150 | ||
| 1151 |
## data rows go in the end of the |
|
| 1152 |
## preceding subtable (if applicable) |
|
| 1153 |
## label rows go in the beginning of |
|
| 1154 |
## one at at |
|
| 1155 | 5x |
ind <- min( |
| 1156 | 5x |
which((cumpos + !islab) >= at), |
| 1157 | 5x |
numkids |
| 1158 |
) |
|
| 1159 | 5x |
thekid <- kids[[ind]] |
| 1160 | ||
| 1161 | 5x |
if (is(thekid, "TableRow")) {
|
| 1162 | ! |
tt_level(row) <- tt_level(thekid) |
| 1163 | ! |
if (ind == 1) {
|
| 1164 | ! |
bef <- integer() |
| 1165 | ! |
aft <- 1:numkids |
| 1166 | ! |
} else if (ind == numkids) {
|
| 1167 | ! |
bef <- 1:(ind - 1) |
| 1168 | ! |
aft <- ind |
| 1169 |
} else {
|
|
| 1170 | ! |
bef <- 1:ind |
| 1171 | ! |
aft <- (ind + 1):numkids |
| 1172 |
} |
|
| 1173 | ! |
kids <- c( |
| 1174 | ! |
kids[bef], list(row), |
| 1175 | ! |
kids[aft] |
| 1176 |
) |
|
| 1177 |
} else { # kid is not a table row
|
|
| 1178 | 5x |
newpos <- if (ind == 1) {
|
| 1179 | 4x |
pos + contnr |
| 1180 |
} else {
|
|
| 1181 | 1x |
cumpos[ind - 1] |
| 1182 |
} |
|
| 1183 | ||
| 1184 | 5x |
kids[[ind]] <- recurse_insert(thekid, |
| 1185 | 5x |
row, |
| 1186 | 5x |
at, |
| 1187 | 5x |
pos = newpos, |
| 1188 | 5x |
ascontent = ascontent |
| 1189 |
) |
|
| 1190 |
} # end kid is not table row |
|
| 1191 |
} |
|
| 1192 | 7x |
tree_children(tt) <- kids |
| 1193 | 7x |
tt |
| 1194 |
} |
|
| 1195 | ||
| 1196 | 9x |
setGeneric("recurse_insert", function(tt, row, at, pos, ascontent = FALSE) standardGeneric("recurse_insert"))
|
| 1197 | ||
| 1198 |
setMethod( |
|
| 1199 |
"recurse_insert", "TableTree", |
|
| 1200 |
function(tt, row, at, pos, ascontent = FALSE) {
|
|
| 1201 | 6x |
ctab <- content_table(tt) |
| 1202 | 6x |
contnr <- nrow(ctab) |
| 1203 | 6x |
contpos <- pos + contnr |
| 1204 | 6x |
islab <- is(row, "LabelRow") |
| 1205 |
## this will NOT insert it as |
|
| 1206 | 6x |
if ((contnr > 0 || islab) && contpos > at) {
|
| 1207 | ! |
content_table(tt) <- recurse_insert(ctab, row, at, pos, TRUE) |
| 1208 | ! |
return(tt) |
| 1209 |
} |
|
| 1210 | ||
| 1211 | 6x |
.insert_helper(tt, row, |
| 1212 | 6x |
at = at, pos = pos + contnr, |
| 1213 | 6x |
ascontent = ascontent |
| 1214 |
) |
|
| 1215 |
} |
|
| 1216 |
) |
|
| 1217 | ||
| 1218 |
setMethod( |
|
| 1219 |
"recurse_insert", "ElementaryTable", |
|
| 1220 |
function(tt, row, at, pos, ascontent = FALSE) {
|
|
| 1221 | 3x |
.insert_helper(tt, row, |
| 1222 | 3x |
at = at, pos = pos, |
| 1223 | 3x |
ascontent = FALSE |
| 1224 |
) |
|
| 1225 |
} |
|
| 1226 |
) |
| 1 |
#' Find degenerate (sub)structures within a table |
|
| 2 |
#' |
|
| 3 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 4 |
#' |
|
| 5 |
#' This function returns a list with the row-paths to all structural subtables which contain no data rows (even if |
|
| 6 |
#' they have associated content rows). |
|
| 7 |
#' |
|
| 8 |
#' @param tt (`TableTree`)\cr a `TableTree` object. |
|
| 9 |
#' |
|
| 10 |
#' @return A list of character vectors representing the row paths, if any, to degenerate substructures within the table. |
|
| 11 |
#' |
|
| 12 |
#' @examples |
|
| 13 |
#' find_degen_struct(rtable("hi"))
|
|
| 14 |
#' |
|
| 15 |
#' @family table structure validation functions |
|
| 16 |
#' @export |
|
| 17 |
find_degen_struct <- function(tt) {
|
|
| 18 | 7x |
degen <- list() |
| 19 | ||
| 20 | 7x |
recurse_check <- function(tti, path) {
|
| 21 | 103x |
if (is(tti, "VTableTree")) {
|
| 22 | 103x |
kids <- tree_children(tti) |
| 23 | 103x |
if (length(kids) == 0) {
|
| 24 | 69x |
degen <<- c(degen, list(path)) |
| 25 |
} else {
|
|
| 26 | 34x |
for (i in seq_along(kids)) {
|
| 27 | 96x |
recurse_check(kids[[i]], path = c(path, names(kids)[i])) |
| 28 |
} |
|
| 29 |
} |
|
| 30 |
} |
|
| 31 |
} |
|
| 32 | 7x |
recurse_check(tt, obj_name(tt) %||% "root") |
| 33 | 7x |
degen |
| 34 |
} |
|
| 35 | ||
| 36 |
#' Validate and assert valid table structure |
|
| 37 |
#' |
|
| 38 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 39 |
#' |
|
| 40 |
#' A `TableTree` (`rtables`-built table) is considered degenerate if: |
|
| 41 |
#' \enumerate{
|
|
| 42 |
#' \item{It contains no subtables or data rows (content rows do not count).}
|
|
| 43 |
#' \item{It contains a subtable which is degenerate by the criterion above.}
|
|
| 44 |
#' } |
|
| 45 |
#' |
|
| 46 |
#' `validate_table_struct` assesses whether `tt` has a valid (non-degenerate) structure. |
|
| 47 |
#' |
|
| 48 |
#' `assert_valid_table` asserts a table must have a valid structure, and throws an informative error (the default) or |
|
| 49 |
#' warning (if `warn_only` is `TRUE`) if the table is degenerate (has invalid structure or contains one or more |
|
| 50 |
#' invalid substructures. |
|
| 51 |
#' |
|
| 52 |
#' @param tt (`TableTree`)\cr a `TableTree` object. |
|
| 53 |
#' |
|
| 54 |
#' @return |
|
| 55 |
#' * `validate_table_struct` returns a logical value indicating valid structure. |
|
| 56 |
#' * `assert_valid_table` is called for its side-effect of throwing an error or warning for degenerate tables. |
|
| 57 |
#' |
|
| 58 |
#' @note This function is experimental and the exact text of the warning/error is subject to change in future releases. |
|
| 59 |
#' |
|
| 60 |
#' @examples |
|
| 61 |
#' validate_table_struct(rtable("hahaha"))
|
|
| 62 |
#' \dontrun{
|
|
| 63 |
#' assert_valid_table(rtable("oops"))
|
|
| 64 |
#' } |
|
| 65 |
#' |
|
| 66 |
#' @family table structure validation functions |
|
| 67 |
#' @export |
|
| 68 |
validate_table_struct <- function(tt) {
|
|
| 69 | 1x |
degen_pths <- find_degen_struct(tt) |
| 70 | 1x |
length(degen_pths) == 0 |
| 71 |
} |
|
| 72 | ||
| 73 |
## XXX this doesn't handle content paths correctly |
|
| 74 |
.path_to_disp <- function(pth) {
|
|
| 75 | 4x |
if (length(pth) == 1) {
|
| 76 | 1x |
return(pth) |
| 77 |
} |
|
| 78 | 3x |
has_cont <- any(pth == "@content") |
| 79 | 3x |
if (has_cont) {
|
| 80 | ! |
contpos <- which(pth == "@content") |
| 81 | ! |
cont_disp <- paste(tail(pth, length(pth) - contpos + 1), |
| 82 | ! |
collapse = "->" |
| 83 |
) |
|
| 84 | ! |
pth <- head(pth, contpos) |
| 85 |
} else {
|
|
| 86 | 3x |
cont_disp <- character() |
| 87 |
} |
|
| 88 | ||
| 89 | 3x |
topaste <- character(0) |
| 90 | 3x |
fullpth <- pth |
| 91 | 3x |
while (length(pth) > 0) {
|
| 92 | 6x |
if (length(pth) <= 1) {
|
| 93 | ! |
topaste <- c(topaste, pth) |
| 94 | ! |
pth <- character() |
| 95 |
} else {
|
|
| 96 | 6x |
topaste <- c(topaste, sprintf("%s[%s]", pth[1], pth[2]))
|
| 97 | 6x |
pth <- tail(pth, -2) |
| 98 |
} |
|
| 99 |
} |
|
| 100 | 3x |
topaste <- c(topaste, cont_disp) |
| 101 | 3x |
paste(topaste, collapse = "->") |
| 102 |
} |
|
| 103 | ||
| 104 |
no_analyze_guess <- paste0( |
|
| 105 |
"Was this table created using ", |
|
| 106 |
"summarize_row_groups but no calls ", |
|
| 107 |
"to analyze?\n" |
|
| 108 |
) |
|
| 109 | ||
| 110 |
use_sanitize_msg <- paste(" Use sanitize_table_struct() to fix these issues")
|
|
| 111 | ||
| 112 |
make_degen_message <- function(degen_pths, tt) {
|
|
| 113 | 2x |
msg <- sprintf( |
| 114 | 2x |
paste0( |
| 115 | 2x |
"Invalid table - found %d (sub)structures which contain no data rows.", |
| 116 | 2x |
"\n\tThe first occured at path: %s" |
| 117 |
), |
|
| 118 | 2x |
length(degen_pths), .path_to_disp(degen_pths[[1]]) |
| 119 |
) |
|
| 120 | 2x |
if (length(degen_pths) == 1 && length(degen_pths[[1]]) == 1) {
|
| 121 | 1x |
msg <- paste(msg, " Likely Cause: Empty data or first row split on variable with only NA values", |
| 122 | 1x |
sep = "\n" |
| 123 |
) |
|
| 124 | 1x |
} else if (all(make_row_df(tt)$node_class %in% c("LabelRow", "ContentRow"))) {
|
| 125 | 1x |
msg <- paste(msg, " Cause: Layout did not contain any analyze() calls (only summarize_row_groups())", |
| 126 | 1x |
sep = "\n" |
| 127 |
) |
|
| 128 |
} |
|
| 129 | 2x |
msg <- paste(msg, use_sanitize_msg, sep = "\n") |
| 130 | 2x |
msg |
| 131 |
} |
|
| 132 | ||
| 133 |
#' @param warn_only (`flag`)\cr whether a warning should be thrown instead of an error. Defaults to `FALSE`. |
|
| 134 |
#' |
|
| 135 |
#' @rdname validate_table_struct |
|
| 136 |
#' @export |
|
| 137 |
assert_valid_table <- function(tt, warn_only = FALSE) {
|
|
| 138 | 2x |
degen_pths <- find_degen_struct(tt) |
| 139 | 2x |
if (length(degen_pths) == 0) {
|
| 140 | ! |
return(TRUE) |
| 141 |
} |
|
| 142 | ||
| 143 |
## we failed, now we build an informative error/warning message |
|
| 144 | 2x |
msg <- make_degen_message(degen_pths, tt) |
| 145 | ||
| 146 | 2x |
if (!warn_only) {
|
| 147 | 2x |
stop(msg) |
| 148 |
} |
|
| 149 | ! |
warning(msg) |
| 150 | ! |
return(FALSE) |
| 151 |
} |
|
| 152 | ||
| 153 |
#' Sanitize degenerate table structures |
|
| 154 |
#' |
|
| 155 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 156 |
#' |
|
| 157 |
#' Experimental function to correct structure of degenerate tables by adding messaging rows to empty sub-structures. |
|
| 158 |
#' |
|
| 159 |
#' @param tt (`TableTree`)\cr a `TableTree` object. |
|
| 160 |
#' @param empty_msg (`string`)\cr the string which should be spanned across the inserted empty rows. |
|
| 161 |
#' |
|
| 162 |
#' @details |
|
| 163 |
#' This function locates degenerate portions of the table (including the table overall in the case of a table with no |
|
| 164 |
#' data rows) and inserts a row which spans all columns with the message `empty_msg` at each one, generating a table |
|
| 165 |
#' guaranteed to be non-degenerate. |
|
| 166 |
#' |
|
| 167 |
#' @return If `tt` is already valid, it is returned unmodified. If `tt` is degenerate, a modified, non-degenerate |
|
| 168 |
#' version of the table is returned. |
|
| 169 |
#' |
|
| 170 |
#' @examples |
|
| 171 |
#' sanitize_table_struct(rtable("cool beans"))
|
|
| 172 |
#' |
|
| 173 |
#' lyt <- basic_table() %>% |
|
| 174 |
#' split_cols_by("ARM") %>%
|
|
| 175 |
#' split_rows_by("SEX") %>%
|
|
| 176 |
#' summarize_row_groups() |
|
| 177 |
#' |
|
| 178 |
#' ## Degenerate because it doesn't have any analyze calls -> no data rows |
|
| 179 |
#' badtab <- build_table(lyt, DM) |
|
| 180 |
#' sanitize_table_struct(badtab) |
|
| 181 |
#' |
|
| 182 |
#' @family table structure validation functions |
|
| 183 |
#' @export |
|
| 184 |
sanitize_table_struct <- function(tt, empty_msg = "-- This Section Contains No Data --") {
|
|
| 185 | 4x |
rdf <- make_row_df(tt) |
| 186 | ||
| 187 | 4x |
emptyrow <- DataRow( |
| 188 | 4x |
vals = list(empty_msg), |
| 189 | 4x |
name = "empty_section", |
| 190 | 4x |
label = "", |
| 191 | 4x |
cspan = ncol(tt), |
| 192 | 4x |
cinfo = col_info(tt), |
| 193 | 4x |
format = "xx", |
| 194 | 4x |
table_inset = table_inset(tt) |
| 195 |
) |
|
| 196 | 4x |
degen_pths <- find_degen_struct(tt) |
| 197 | ||
| 198 | 4x |
if (identical(degen_pths, list("root"))) {
|
| 199 | 2x |
tree_children(tt) <- list(empty_row = emptyrow) |
| 200 | 2x |
return(tt) |
| 201 |
} |
|
| 202 | ||
| 203 | 2x |
for (pth in degen_pths) {
|
| 204 |
## FIXME this shouldn't be necessary. why is it? |
|
| 205 | 33x |
tti <- tt_at_path(tt, path = pth) |
| 206 | 33x |
tree_children(tti) <- list(empty_section = emptyrow) |
| 207 | 33x |
tt_at_path(tt, path = pth) <- tti |
| 208 |
} |
|
| 209 | 2x |
tt |
| 210 |
} |
| 1 |
## Split types ----------------------------------------------------------------- |
|
| 2 |
## variable: split on distinct values of a variable |
|
| 3 |
## all: include all observations (root 'split') |
|
| 4 |
## rawcut: cut on static values of a variable |
|
| 5 |
## quantilecut: cut on quantiles of observed values for a variable |
|
| 6 |
## missing: split obs based on missingness of a variable/observation. This could be used for compare to ref_group?? |
|
| 7 |
## multicolumn: each child analyzes a different column |
|
| 8 |
## arbitrary: children are not related to each other in any systematic fashion. |
|
| 9 | ||
| 10 |
## null is ok here. |
|
| 11 |
check_ok_label <- function(lbl, multi_ok = FALSE) {
|
|
| 12 | 54947x |
if (length(lbl) == 0) {
|
| 13 | 12528x |
return(TRUE) |
| 14 |
} |
|
| 15 | ||
| 16 | 42419x |
if (length(lbl) > 1) {
|
| 17 | 2089x |
if (multi_ok) {
|
| 18 | 2089x |
return(all(vapply(lbl, check_ok_label, TRUE))) |
| 19 |
} |
|
| 20 | ! |
stop("got a label of length > 1")
|
| 21 |
} |
|
| 22 | ||
| 23 | 40330x |
if (grepl("([{}])", lbl)) {
|
| 24 | 1x |
stop("Labels cannot contain { or } due to their use for indicating referential footnotes")
|
| 25 |
} |
|
| 26 | 40329x |
invisible(TRUE) |
| 27 |
} |
|
| 28 | ||
| 29 |
valid_lbl_pos <- c("default", "visible", "hidden", "topleft")
|
|
| 30 |
.labelkids_helper <- function(charval) {
|
|
| 31 | 2784x |
ret <- switch(charval, |
| 32 | 2784x |
"default" = NA, |
| 33 | 2784x |
"visible" = TRUE, |
| 34 | 2784x |
"hidden" = FALSE, |
| 35 | 2784x |
"topleft" = FALSE, |
| 36 | 2784x |
stop( |
| 37 | 2784x |
"unrecognized charval in .labelkids_helper. ", |
| 38 | 2784x |
"this shouldn't ever happen" |
| 39 |
) |
|
| 40 |
) |
|
| 41 | 2784x |
ret |
| 42 |
} |
|
| 43 | ||
| 44 |
setOldClass("expression")
|
|
| 45 |
setClassUnion("SubsetDef", c("expression", "logical", "integer", "numeric"))
|
|
| 46 | ||
| 47 |
setClassUnion("integerOrNULL", c("NULL", "integer"))
|
|
| 48 |
setClassUnion("characterOrNULL", c("NULL", "character"))
|
|
| 49 | ||
| 50 |
## should XXX [splits, s_values, sval_labels, subset(?)] be a data.frame? |
|
| 51 |
setClass("TreePos", representation(
|
|
| 52 |
splits = "list", |
|
| 53 |
s_values = "list", |
|
| 54 |
sval_labels = "character", |
|
| 55 |
subset = "SubsetDef" |
|
| 56 |
), |
|
| 57 |
validity = function(object) {
|
|
| 58 |
nspl <- length(object@splits) |
|
| 59 |
length(object@s_values) == nspl && length(object@sval_labels) == nspl |
|
| 60 |
} |
|
| 61 |
) |
|
| 62 | ||
| 63 |
setClassUnion("functionOrNULL", c("NULL", "function"))
|
|
| 64 |
setClassUnion("listOrNULL", c("NULL", "list"))
|
|
| 65 |
## TODO (?) make "list" more specific, e.g FormatList, or FunctionList? |
|
| 66 |
setClassUnion("FormatSpec", c("NULL", "character", "function", "list"))
|
|
| 67 |
setClassUnion("ExprOrNULL", c("NULL", "expression"))
|
|
| 68 | ||
| 69 |
setClass("ValueWrapper", representation(
|
|
| 70 |
value = "ANY", |
|
| 71 |
label = "characterOrNULL", |
|
| 72 |
subset_expression = "ExprOrNULL" |
|
| 73 |
), |
|
| 74 |
contains = "VIRTUAL" |
|
| 75 |
) |
|
| 76 |
## heavier-weight than I'd like but I think we need |
|
| 77 |
## this to carry around thee subsets for |
|
| 78 |
## comparison-based splits |
|
| 79 | ||
| 80 |
setClass("SplitValue",
|
|
| 81 |
contains = "ValueWrapper", |
|
| 82 |
representation(extra = "list") |
|
| 83 |
) |
|
| 84 | ||
| 85 |
SplitValue <- function(val, extr = list(), label = val, sub_expr = NULL) {
|
|
| 86 | 5310x |
if (is(val, "SplitValue")) {
|
| 87 | 2217x |
if (length(splv_extra(val)) > 0) {
|
| 88 | 29x |
extr <- c(splv_extra(val), extr) |
| 89 |
} |
|
| 90 | 2217x |
splv_extra(val) <- extr |
| 91 | 2217x |
return(val) |
| 92 |
} |
|
| 93 | 3093x |
if (!is(extr, "list")) {
|
| 94 | ! |
extr <- list(extr) |
| 95 |
} |
|
| 96 | 3093x |
if (!is(label, "character")) {
|
| 97 | ! |
label <- as.character(label) |
| 98 |
} |
|
| 99 | ||
| 100 | 3093x |
if (!is.null(sub_expr) && !is.expression(sub_expr)) {
|
| 101 | 105x |
sub_expr <- as.expression(sub_expr) |
| 102 |
} ## sometimes they will be "call" objects, etc |
|
| 103 | 3093x |
check_ok_label(label) |
| 104 | 3093x |
new("SplitValue",
|
| 105 | 3093x |
value = val, |
| 106 | 3093x |
extra = extr, |
| 107 | 3093x |
label = label, |
| 108 | 3093x |
subset_expression = sub_expr |
| 109 |
) |
|
| 110 |
} |
|
| 111 | ||
| 112 |
setClass("LevelComboSplitValue",
|
|
| 113 |
contains = "SplitValue", |
|
| 114 |
representation(combolevels = "character") |
|
| 115 |
) |
|
| 116 | ||
| 117 |
## wrapped in user-facing `add_combo_facet` |
|
| 118 |
LevelComboSplitValue <- function(val, extr, combolevels, label = val, sub_expr = NULL) {
|
|
| 119 | 30x |
check_ok_label(label) |
| 120 | 30x |
new("LevelComboSplitValue",
|
| 121 | 30x |
value = val, |
| 122 | 30x |
extra = extr, |
| 123 | 30x |
combolevels = combolevels, |
| 124 | 30x |
label = label, |
| 125 | 30x |
subset_expression = sub_expr |
| 126 |
) |
|
| 127 |
} |
|
| 128 | ||
| 129 |
setClass("Split",
|
|
| 130 |
contains = "VIRTUAL", |
|
| 131 |
representation( |
|
| 132 |
payload = "ANY", |
|
| 133 |
name = "character", |
|
| 134 |
split_label = "character", |
|
| 135 |
split_format = "FormatSpec", |
|
| 136 |
split_na_str = "character", |
|
| 137 |
split_label_position = "character", |
|
| 138 |
## NB this is the function which is applied to |
|
| 139 |
## get the content rows for the CHILDREN of this |
|
| 140 |
## split!!! |
|
| 141 |
content_fun = "listOrNULL", ## functionOrNULL", |
|
| 142 |
content_format = "FormatSpec", |
|
| 143 |
content_na_str = "character", |
|
| 144 |
content_var = "character", |
|
| 145 |
label_children = "logical", |
|
| 146 |
extra_args = "list", |
|
| 147 |
indent_modifier = "integer", |
|
| 148 |
content_indent_modifier = "integer", |
|
| 149 |
content_extra_args = "list", |
|
| 150 |
page_title_prefix = "character", |
|
| 151 |
child_section_div = "character", |
|
| 152 |
child_show_colcounts = "logical", |
|
| 153 |
child_colcount_format = "FormatSpec" |
|
| 154 |
) |
|
| 155 |
) |
|
| 156 | ||
| 157 |
setClass("CustomizableSplit",
|
|
| 158 |
contains = "Split", |
|
| 159 |
representation(split_fun = "functionOrNULL") |
|
| 160 |
) |
|
| 161 | ||
| 162 |
#' @author Gabriel Becker |
|
| 163 |
#' @exportClass VarLevelSplit |
|
| 164 |
#' @rdname VarLevelSplit |
|
| 165 |
setClass("VarLevelSplit",
|
|
| 166 |
contains = "CustomizableSplit", |
|
| 167 |
representation( |
|
| 168 |
value_label_var = "character", |
|
| 169 |
value_order = "ANY" |
|
| 170 |
) |
|
| 171 |
) |
|
| 172 |
#' Split on levels within a variable |
|
| 173 |
#' |
|
| 174 |
#' @inheritParams lyt_args |
|
| 175 |
#' @inheritParams constr_args |
|
| 176 |
#' |
|
| 177 |
#' @return a `VarLevelSplit` object. |
|
| 178 |
#' |
|
| 179 |
#' @export |
|
| 180 |
VarLevelSplit <- function(var, |
|
| 181 |
split_label, |
|
| 182 |
labels_var = NULL, |
|
| 183 |
cfun = NULL, |
|
| 184 |
cformat = NULL, |
|
| 185 |
cna_str = NA_character_, |
|
| 186 |
split_fun = NULL, |
|
| 187 |
split_format = NULL, |
|
| 188 |
split_na_str = NA_character_, |
|
| 189 |
valorder = NULL, |
|
| 190 |
split_name = var, |
|
| 191 |
child_labels = c("default", "visible", "hidden"),
|
|
| 192 |
extra_args = list(), |
|
| 193 |
indent_mod = 0L, |
|
| 194 |
label_pos = c("topleft", "hidden", "visible"),
|
|
| 195 |
cindent_mod = 0L, |
|
| 196 |
cvar = "", |
|
| 197 |
cextra_args = list(), |
|
| 198 |
page_prefix = NA_character_, |
|
| 199 |
section_div = NA_character_, |
|
| 200 |
show_colcounts = FALSE, |
|
| 201 |
colcount_format = NULL) {
|
|
| 202 | 588x |
child_labels <- match.arg(child_labels) |
| 203 | 588x |
if (is.null(labels_var)) {
|
| 204 | 3x |
labels_var <- var |
| 205 |
} |
|
| 206 | 588x |
check_ok_label(split_label) |
| 207 | 588x |
new("VarLevelSplit",
|
| 208 | 588x |
payload = var, |
| 209 | 588x |
split_label = split_label, |
| 210 | 588x |
name = split_name, |
| 211 | 588x |
value_label_var = labels_var, |
| 212 | 588x |
content_fun = cfun, |
| 213 | 588x |
content_format = cformat, |
| 214 | 588x |
content_na_str = cna_str, |
| 215 | 588x |
split_fun = split_fun, |
| 216 | 588x |
split_format = split_format, |
| 217 | 588x |
split_na_str = split_na_str, |
| 218 | 588x |
value_order = NULL, |
| 219 | 588x |
label_children = .labelkids_helper(child_labels), |
| 220 | 588x |
extra_args = extra_args, |
| 221 | 588x |
indent_modifier = as.integer(indent_mod), |
| 222 | 588x |
content_indent_modifier = as.integer(cindent_mod), |
| 223 | 588x |
content_var = cvar, |
| 224 | 588x |
split_label_position = label_pos, |
| 225 | 588x |
content_extra_args = cextra_args, |
| 226 | 588x |
page_title_prefix = page_prefix, |
| 227 | 588x |
child_section_div = section_div, |
| 228 | 588x |
child_show_colcounts = show_colcounts, |
| 229 | 588x |
child_colcount_format = colcount_format |
| 230 |
) |
|
| 231 |
} |
|
| 232 | ||
| 233 |
setClass("AllSplit", contains = "Split")
|
|
| 234 | ||
| 235 |
AllSplit <- function(split_label = "", |
|
| 236 |
cfun = NULL, |
|
| 237 |
cformat = NULL, |
|
| 238 |
cna_str = NA_character_, |
|
| 239 |
split_format = NULL, |
|
| 240 |
split_na_str = NA_character_, |
|
| 241 |
split_name = NULL, |
|
| 242 |
extra_args = list(), |
|
| 243 |
indent_mod = 0L, |
|
| 244 |
cindent_mod = 0L, |
|
| 245 |
cvar = "", |
|
| 246 |
cextra_args = list(), |
|
| 247 |
show_colcounts = FALSE, |
|
| 248 |
colcount_format = NULL, |
|
| 249 |
...) {
|
|
| 250 | 241x |
if (is.null(split_name)) {
|
| 251 | 140x |
if (nzchar(split_label)) {
|
| 252 | 8x |
split_name <- split_label |
| 253 |
} else {
|
|
| 254 | 132x |
split_name <- "all obs" |
| 255 |
} |
|
| 256 |
} |
|
| 257 | 241x |
check_ok_label(split_label) |
| 258 | 241x |
new("AllSplit",
|
| 259 | 241x |
split_label = split_label, |
| 260 | 241x |
content_fun = cfun, |
| 261 | 241x |
content_format = cformat, |
| 262 | 241x |
content_na_str = cna_str, |
| 263 | 241x |
split_format = split_format, |
| 264 | 241x |
split_na_str = split_na_str, |
| 265 | 241x |
name = split_name, |
| 266 | 241x |
label_children = FALSE, |
| 267 | 241x |
extra_args = extra_args, |
| 268 | 241x |
indent_modifier = as.integer(indent_mod), |
| 269 | 241x |
content_indent_modifier = as.integer(cindent_mod), |
| 270 | 241x |
content_var = cvar, |
| 271 | 241x |
split_label_position = "hidden", |
| 272 | 241x |
content_extra_args = cextra_args, |
| 273 | 241x |
page_title_prefix = NA_character_, |
| 274 | 241x |
child_section_div = NA_character_, |
| 275 | 241x |
child_show_colcounts = show_colcounts, |
| 276 | 241x |
child_colcount_format = colcount_format |
| 277 |
) |
|
| 278 |
} |
|
| 279 | ||
| 280 |
setClass("RootSplit", contains = "AllSplit")
|
|
| 281 | ||
| 282 |
RootSplit <- function(split_label = "", cfun = NULL, cformat = NULL, cna_str = NA_character_, cvar = "", |
|
| 283 |
split_format = NULL, split_na_str = NA_character_, cextra_args = list(), ...) {
|
|
| 284 | 737x |
check_ok_label(split_label) |
| 285 | 737x |
new("RootSplit",
|
| 286 | 737x |
split_label = split_label, |
| 287 | 737x |
content_fun = cfun, |
| 288 | 737x |
content_format = cformat, |
| 289 | 737x |
content_na_str = cna_str, |
| 290 | 737x |
split_format = split_format, |
| 291 | 737x |
split_na_str = split_na_str, |
| 292 | 737x |
name = "root", |
| 293 | 737x |
label_children = FALSE, |
| 294 | 737x |
indent_modifier = 0L, |
| 295 | 737x |
content_indent_modifier = 0L, |
| 296 | 737x |
content_var = cvar, |
| 297 | 737x |
split_label_position = "hidden", |
| 298 | 737x |
content_extra_args = cextra_args, |
| 299 | 737x |
child_section_div = NA_character_, |
| 300 | 737x |
child_show_colcounts = FALSE, |
| 301 | 737x |
child_colcount_format = "(N=xx)" |
| 302 |
) |
|
| 303 |
} |
|
| 304 | ||
| 305 |
setClass("ManualSplit",
|
|
| 306 |
contains = "AllSplit", |
|
| 307 |
representation(levels = "character") |
|
| 308 |
) |
|
| 309 | ||
| 310 |
#' Manually defined split |
|
| 311 |
#' |
|
| 312 |
#' @inheritParams lyt_args |
|
| 313 |
#' @inheritParams constr_args |
|
| 314 |
#' @inheritParams gen_args |
|
| 315 |
#' @param levels (`character`)\cr levels of the split (i.e. the children of the manual split). |
|
| 316 |
#' |
|
| 317 |
#' @return A `ManualSplit` object. |
|
| 318 |
#' |
|
| 319 |
#' @author Gabriel Becker |
|
| 320 |
#' @export |
|
| 321 |
ManualSplit <- function(levels, label, name = "manual", |
|
| 322 |
extra_args = list(), |
|
| 323 |
indent_mod = 0L, |
|
| 324 |
cindent_mod = 0L, |
|
| 325 |
cvar = "", |
|
| 326 |
cextra_args = list(), |
|
| 327 |
label_pos = "visible", |
|
| 328 |
page_prefix = NA_character_, |
|
| 329 |
section_div = NA_character_) {
|
|
| 330 | 48x |
label_pos <- match.arg(label_pos, label_pos_values) |
| 331 | 48x |
check_ok_label(label, multi_ok = TRUE) |
| 332 | 48x |
new("ManualSplit",
|
| 333 | 48x |
split_label = label, |
| 334 | 48x |
levels = levels, |
| 335 | 48x |
name = name, |
| 336 | 48x |
label_children = FALSE, |
| 337 | 48x |
extra_args = extra_args, |
| 338 | 48x |
indent_modifier = 0L, |
| 339 | 48x |
content_indent_modifier = as.integer(cindent_mod), |
| 340 | 48x |
content_var = cvar, |
| 341 | 48x |
split_format = NULL, |
| 342 | 48x |
split_na_str = NA_character_, |
| 343 | 48x |
split_label_position = label_pos, |
| 344 | 48x |
page_title_prefix = page_prefix, |
| 345 | 48x |
child_section_div = section_div, |
| 346 | 48x |
child_show_colcounts = FALSE, |
| 347 | 48x |
child_colcount_format = "(N=xx)" |
| 348 |
) |
|
| 349 |
} |
|
| 350 | ||
| 351 |
## splits across which variables are being analynzed |
|
| 352 |
setClass("MultiVarSplit",
|
|
| 353 |
contains = "CustomizableSplit", ## "Split", |
|
| 354 |
representation( |
|
| 355 |
var_labels = "character", |
|
| 356 |
var_names = "character" |
|
| 357 |
), |
|
| 358 |
validity = function(object) {
|
|
| 359 |
length(object@payload) >= 1 && |
|
| 360 |
all(!is.na(object@payload)) && |
|
| 361 |
(length(object@var_labels) == 0 || length(object@payload) == length(object@var_labels)) |
|
| 362 |
} |
|
| 363 |
) |
|
| 364 | ||
| 365 |
.make_suffix_vec <- function(n) {
|
|
| 366 | 3x |
c( |
| 367 |
"", |
|
| 368 | 3x |
sprintf( |
| 369 | 3x |
"._[[%d]]_.", |
| 370 | 3x |
seq_len(n - 1) + 1L |
| 371 |
) |
|
| 372 |
) |
|
| 373 |
} |
|
| 374 | ||
| 375 |
.make_multivar_names <- function(vars) {
|
|
| 376 | 30x |
dups <- duplicated(vars) |
| 377 | 30x |
if (!any(dups)) {
|
| 378 | 27x |
return(vars) |
| 379 |
} |
|
| 380 | 3x |
dupvars <- unique(vars[dups]) |
| 381 | 3x |
ret <- vars |
| 382 | 3x |
for (v in dupvars) {
|
| 383 | 3x |
pos <- which(ret == v) |
| 384 | 3x |
ret[pos] <- paste0( |
| 385 | 3x |
ret[pos], |
| 386 | 3x |
.make_suffix_vec(length(pos)) |
| 387 |
) |
|
| 388 |
} |
|
| 389 | 3x |
ret |
| 390 |
} |
|
| 391 | ||
| 392 |
#' Split between two or more different variables |
|
| 393 |
#' |
|
| 394 |
#' @inheritParams lyt_args |
|
| 395 |
#' @inheritParams constr_args |
|
| 396 |
#' |
|
| 397 |
#' @return A `MultiVarSplit` object. |
|
| 398 |
#' |
|
| 399 |
#' @author Gabriel Becker |
|
| 400 |
#' @export |
|
| 401 |
MultiVarSplit <- function(vars, |
|
| 402 |
split_label = "", |
|
| 403 |
varlabels = NULL, |
|
| 404 |
varnames = NULL, |
|
| 405 |
cfun = NULL, |
|
| 406 |
cformat = NULL, |
|
| 407 |
cna_str = NA_character_, |
|
| 408 |
split_format = NULL, |
|
| 409 |
split_na_str = NA_character_, |
|
| 410 |
split_name = "multivars", |
|
| 411 |
child_labels = c("default", "visible", "hidden"),
|
|
| 412 |
extra_args = list(), |
|
| 413 |
indent_mod = 0L, |
|
| 414 |
cindent_mod = 0L, |
|
| 415 |
cvar = "", |
|
| 416 |
cextra_args = list(), |
|
| 417 |
label_pos = "visible", |
|
| 418 |
split_fun = NULL, |
|
| 419 |
page_prefix = NA_character_, |
|
| 420 |
section_div = NA_character_, |
|
| 421 |
show_colcounts = FALSE, |
|
| 422 |
colcount_format = NULL) {
|
|
| 423 | 30x |
check_ok_label(split_label) |
| 424 |
## no topleft allowed |
|
| 425 | 30x |
label_pos <- match.arg(label_pos, label_pos_values[-3]) |
| 426 | 30x |
child_labels <- match.arg(child_labels) |
| 427 | 30x |
if (length(vars) == 1 && grepl(":", vars)) {
|
| 428 | ! |
vars <- strsplit(vars, ":")[[1]] |
| 429 |
} |
|
| 430 | 30x |
if (length(varlabels) == 0) { ## covers NULL and character()
|
| 431 | 1x |
varlabels <- vars |
| 432 |
} |
|
| 433 | 30x |
vnames <- varnames %||% .make_multivar_names(vars) |
| 434 | 30x |
stopifnot(length(vnames) == length(vars)) |
| 435 | 30x |
new("MultiVarSplit",
|
| 436 | 30x |
payload = vars, |
| 437 | 30x |
split_label = split_label, |
| 438 | 30x |
var_labels = varlabels, |
| 439 | 30x |
var_names = vnames, |
| 440 | 30x |
content_fun = cfun, |
| 441 | 30x |
content_format = cformat, |
| 442 | 30x |
content_na_str = cna_str, |
| 443 | 30x |
split_format = split_format, |
| 444 | 30x |
split_na_str = split_na_str, |
| 445 | 30x |
label_children = .labelkids_helper(child_labels), |
| 446 | 30x |
name = split_name, |
| 447 | 30x |
extra_args = extra_args, |
| 448 | 30x |
indent_modifier = as.integer(indent_mod), |
| 449 | 30x |
content_indent_modifier = as.integer(cindent_mod), |
| 450 | 30x |
content_var = cvar, |
| 451 | 30x |
split_label_position = label_pos, |
| 452 | 30x |
content_extra_args = cextra_args, |
| 453 | 30x |
split_fun = split_fun, |
| 454 | 30x |
page_title_prefix = page_prefix, |
| 455 | 30x |
child_section_div = section_div, |
| 456 | 30x |
child_show_colcounts = show_colcounts, |
| 457 | 30x |
child_colcount_format = colcount_format |
| 458 |
) |
|
| 459 |
} |
|
| 460 | ||
| 461 |
#' Splits for cutting by values of a numeric variable |
|
| 462 |
#' |
|
| 463 |
#' @inheritParams lyt_args |
|
| 464 |
#' @inheritParams constr_args |
|
| 465 |
#' |
|
| 466 |
#' @exportClass VarStaticCutSplit |
|
| 467 |
#' @rdname cutsplits |
|
| 468 |
setClass("VarStaticCutSplit",
|
|
| 469 |
contains = "Split", |
|
| 470 |
representation( |
|
| 471 |
cuts = "numeric", |
|
| 472 |
cut_labels = "character" |
|
| 473 |
) |
|
| 474 |
) |
|
| 475 | ||
| 476 |
.is_cut_lab_lst <- function(cuts) {
|
|
| 477 | 12x |
is.list(cuts) && is.numeric(cuts[[1]]) && |
| 478 | 12x |
is.character(cuts[[2]]) && |
| 479 | 12x |
length(cuts[[1]]) == length(cuts[[2]]) |
| 480 |
} |
|
| 481 | ||
| 482 |
#' Create static cut or static cumulative cut split |
|
| 483 |
#' |
|
| 484 |
#' @inheritParams lyt_args |
|
| 485 |
#' @inheritParams constr_args |
|
| 486 |
#' |
|
| 487 |
#' @return A `VarStaticCutSplit`, `CumulativeCutSplit` object for `make_static_cut_split`, or a `VarDynCutSplit` |
|
| 488 |
#' object for [VarDynCutSplit()]. |
|
| 489 |
#' |
|
| 490 |
#' @rdname cutsplits |
|
| 491 |
make_static_cut_split <- function(var, |
|
| 492 |
split_label, |
|
| 493 |
cuts, |
|
| 494 |
cutlabels = NULL, |
|
| 495 |
cfun = NULL, |
|
| 496 |
cformat = NULL, |
|
| 497 |
cna_str = NA_character_, |
|
| 498 |
split_format = NULL, |
|
| 499 |
split_na_str = NA_character_, |
|
| 500 |
split_name = var, |
|
| 501 |
child_labels = c("default", "visible", "hidden"),
|
|
| 502 |
extra_args = list(), |
|
| 503 |
indent_mod = 0L, |
|
| 504 |
cindent_mod = 0L, |
|
| 505 |
cvar = "", |
|
| 506 |
cextra_args = list(), |
|
| 507 |
label_pos = "visible", |
|
| 508 |
cumulative = FALSE, |
|
| 509 |
page_prefix = NA_character_, |
|
| 510 |
section_div = NA_character_, |
|
| 511 |
show_colcounts = FALSE, |
|
| 512 |
colcount_format = NULL) {
|
|
| 513 | 12x |
cls <- if (cumulative) "CumulativeCutSplit" else "VarStaticCutSplit" |
| 514 | 12x |
check_ok_label(split_label) |
| 515 | ||
| 516 | 12x |
label_pos <- match.arg(label_pos, label_pos_values) |
| 517 | 12x |
child_labels <- match.arg(child_labels) |
| 518 | 12x |
if (.is_cut_lab_lst(cuts)) {
|
| 519 | ! |
cutlabels <- cuts[[2]] |
| 520 | ! |
cuts <- cuts[[1]] |
| 521 |
} |
|
| 522 | 12x |
if (is.unsorted(cuts, strictly = TRUE)) {
|
| 523 | ! |
stop("invalid cuts vector. not sorted unique values.")
|
| 524 |
} |
|
| 525 | ||
| 526 | 12x |
if (is.null(cutlabels) && !is.null(names(cuts))) {
|
| 527 | 1x |
cutlabels <- names(cuts)[-1] |
| 528 |
} ## XXX is this always right? |
|
| 529 | ||
| 530 | 12x |
new(cls, |
| 531 | 12x |
payload = var, |
| 532 | 12x |
split_label = split_label, |
| 533 | 12x |
cuts = cuts, |
| 534 | 12x |
cut_labels = cutlabels, |
| 535 | 12x |
content_fun = cfun, |
| 536 | 12x |
content_format = cformat, |
| 537 | 12x |
content_na_str = cna_str, |
| 538 | 12x |
split_format = split_format, |
| 539 | 12x |
split_na_str = split_na_str, |
| 540 | 12x |
name = split_name, |
| 541 | 12x |
label_children = .labelkids_helper(child_labels), |
| 542 | 12x |
extra_args = extra_args, |
| 543 | 12x |
indent_modifier = as.integer(indent_mod), |
| 544 | 12x |
content_indent_modifier = as.integer(cindent_mod), |
| 545 | 12x |
content_var = cvar, |
| 546 | 12x |
split_label_position = label_pos, |
| 547 | 12x |
content_extra_args = cextra_args, |
| 548 | 12x |
page_title_prefix = page_prefix, |
| 549 | 12x |
child_section_div = section_div, |
| 550 | 12x |
child_show_colcounts = show_colcounts, |
| 551 | 12x |
child_colcount_format = colcount_format |
| 552 |
) |
|
| 553 |
} |
|
| 554 | ||
| 555 |
#' @exportClass CumulativeCutSplit |
|
| 556 |
#' @rdname cutsplits |
|
| 557 |
setClass("CumulativeCutSplit", contains = "VarStaticCutSplit")
|
|
| 558 | ||
| 559 |
## make_static_cut_split with cumulative=TRUE is the constructor |
|
| 560 |
## for CumulativeCutSplit |
|
| 561 | ||
| 562 |
## do we want this to be a CustomizableSplit instead of |
|
| 563 |
## taking cut_fun? |
|
| 564 |
## cut_funct must take avector and no other arguments |
|
| 565 |
## and return a named vector of cut points |
|
| 566 |
#' @exportClass VarDynCutSplit |
|
| 567 |
#' @rdname cutsplits |
|
| 568 |
setClass("VarDynCutSplit",
|
|
| 569 |
contains = "Split", |
|
| 570 |
representation( |
|
| 571 |
cut_fun = "function", |
|
| 572 |
cut_label_fun = "function", |
|
| 573 |
cumulative_cuts = "logical" |
|
| 574 |
) |
|
| 575 |
) |
|
| 576 | ||
| 577 |
#' @export |
|
| 578 |
#' @rdname cutsplits |
|
| 579 |
VarDynCutSplit <- function(var, |
|
| 580 |
split_label, |
|
| 581 |
cutfun, |
|
| 582 |
cutlabelfun = function(x) NULL, |
|
| 583 |
cfun = NULL, |
|
| 584 |
cformat = NULL, |
|
| 585 |
cna_str = NA_character_, |
|
| 586 |
split_format = NULL, |
|
| 587 |
split_na_str = NA_character_, |
|
| 588 |
split_name = var, |
|
| 589 |
child_labels = c("default", "visible", "hidden"),
|
|
| 590 |
extra_args = list(), |
|
| 591 |
cumulative = FALSE, |
|
| 592 |
indent_mod = 0L, |
|
| 593 |
cindent_mod = 0L, |
|
| 594 |
cvar = "", |
|
| 595 |
cextra_args = list(), |
|
| 596 |
label_pos = "visible", |
|
| 597 |
page_prefix = NA_character_, |
|
| 598 |
section_div = NA_character_, |
|
| 599 |
show_colcounts = FALSE, |
|
| 600 |
colcount_format = NULL) {
|
|
| 601 | 6x |
check_ok_label(split_label) |
| 602 | 6x |
label_pos <- match.arg(label_pos, label_pos_values) |
| 603 | 6x |
child_labels <- match.arg(child_labels) |
| 604 | 6x |
new("VarDynCutSplit",
|
| 605 | 6x |
payload = var, |
| 606 | 6x |
split_label = split_label, |
| 607 | 6x |
cut_fun = cutfun, |
| 608 | 6x |
cumulative_cuts = cumulative, |
| 609 | 6x |
cut_label_fun = cutlabelfun, |
| 610 | 6x |
content_fun = cfun, |
| 611 | 6x |
content_format = cformat, |
| 612 | 6x |
content_na_str = cna_str, |
| 613 | 6x |
split_format = split_format, |
| 614 | 6x |
split_na_str = split_na_str, |
| 615 | 6x |
name = split_name, |
| 616 | 6x |
label_children = .labelkids_helper(child_labels), |
| 617 | 6x |
extra_args = extra_args, |
| 618 | 6x |
indent_modifier = as.integer(indent_mod), |
| 619 | 6x |
content_indent_modifier = as.integer(cindent_mod), |
| 620 | 6x |
content_var = cvar, |
| 621 | 6x |
split_label_position = label_pos, |
| 622 | 6x |
content_extra_args = cextra_args, |
| 623 | 6x |
page_title_prefix = page_prefix, |
| 624 | 6x |
child_section_div = section_div, |
| 625 | 6x |
child_show_colcounts = show_colcounts, |
| 626 | 6x |
child_colcount_format = colcount_format |
| 627 |
) |
|
| 628 |
} |
|
| 629 | ||
| 630 |
## NB analyze splits can't have content-related things |
|
| 631 |
setClass("VAnalyzeSplit",
|
|
| 632 |
contains = "Split", |
|
| 633 |
representation( |
|
| 634 |
default_rowlabel = "character", |
|
| 635 |
include_NAs = "logical", |
|
| 636 |
var_label_position = "character" |
|
| 637 |
) |
|
| 638 |
) |
|
| 639 | ||
| 640 |
setClass("AnalyzeVarSplit",
|
|
| 641 |
contains = "VAnalyzeSplit", |
|
| 642 |
representation(analysis_fun = "function") |
|
| 643 |
) |
|
| 644 | ||
| 645 |
setClass("AnalyzeColVarSplit",
|
|
| 646 |
contains = "VAnalyzeSplit", |
|
| 647 |
representation(analysis_fun = "list") |
|
| 648 |
) |
|
| 649 | ||
| 650 |
#' Define a subset tabulation/analysis |
|
| 651 |
#' |
|
| 652 |
#' @inheritParams lyt_args |
|
| 653 |
#' @inheritParams constr_args |
|
| 654 |
#' @param defrowlab (`character`)\cr default row labels, if not specified by the return value of `afun`. |
|
| 655 |
#' |
|
| 656 |
#' @return An `AnalyzeVarSplit` object. |
|
| 657 |
#' |
|
| 658 |
#' @author Gabriel Becker |
|
| 659 |
#' @export |
|
| 660 |
#' @rdname avarspl |
|
| 661 |
AnalyzeVarSplit <- function(var, |
|
| 662 |
split_label = var, |
|
| 663 |
afun, |
|
| 664 |
defrowlab = "", |
|
| 665 |
cfun = NULL, |
|
| 666 |
cformat = NULL, |
|
| 667 |
split_format = NULL, |
|
| 668 |
split_na_str = NA_character_, |
|
| 669 |
inclNAs = FALSE, |
|
| 670 |
split_name = var, |
|
| 671 |
extra_args = list(), |
|
| 672 |
indent_mod = 0L, |
|
| 673 |
label_pos = "default", |
|
| 674 |
cvar = "", |
|
| 675 |
section_div = NA_character_) {
|
|
| 676 | 404x |
check_ok_label(split_label) |
| 677 | 404x |
label_pos <- match.arg(label_pos, c("default", label_pos_values))
|
| 678 | 404x |
if (!any(nzchar(defrowlab))) {
|
| 679 | 1x |
defrowlab <- as.character(substitute(afun)) |
| 680 | 1x |
if (length(defrowlab) > 1 || startsWith(defrowlab, "function(")) {
|
| 681 | ! |
defrowlab <- "" |
| 682 |
} |
|
| 683 |
} |
|
| 684 | 404x |
new("AnalyzeVarSplit",
|
| 685 | 404x |
payload = var, |
| 686 | 404x |
split_label = split_label, |
| 687 | 404x |
content_fun = cfun, |
| 688 | 404x |
analysis_fun = afun, |
| 689 | 404x |
content_format = cformat, |
| 690 | 404x |
split_format = split_format, |
| 691 | 404x |
split_na_str = split_na_str, |
| 692 | 404x |
default_rowlabel = defrowlab, |
| 693 | 404x |
include_NAs = inclNAs, |
| 694 | 404x |
name = split_name, |
| 695 | 404x |
label_children = FALSE, |
| 696 | 404x |
extra_args = extra_args, |
| 697 | 404x |
indent_modifier = as.integer(indent_mod), |
| 698 | 404x |
content_indent_modifier = 0L, |
| 699 | 404x |
var_label_position = label_pos, |
| 700 | 404x |
content_var = cvar, |
| 701 | 404x |
page_title_prefix = NA_character_, |
| 702 | 404x |
child_section_div = section_div, |
| 703 | 404x |
child_show_colcounts = FALSE, |
| 704 | 404x |
child_colcount_format = NA_character_ |
| 705 | 404x |
) ## no content_extra_args |
| 706 |
} |
|
| 707 | ||
| 708 |
#' Define a subset tabulation/analysis |
|
| 709 |
#' |
|
| 710 |
#' @inheritParams lyt_args |
|
| 711 |
#' @inheritParams constr_args |
|
| 712 |
#' |
|
| 713 |
#' @author Gabriel Becker |
|
| 714 |
#' @export |
|
| 715 |
#' @rdname avarspl |
|
| 716 |
AnalyzeColVarSplit <- function(afun, |
|
| 717 |
defrowlab = "", |
|
| 718 |
cfun = NULL, |
|
| 719 |
cformat = NULL, |
|
| 720 |
split_format = NULL, |
|
| 721 |
split_na_str = NA_character_, |
|
| 722 |
inclNAs = FALSE, |
|
| 723 |
split_name = "", |
|
| 724 |
extra_args = list(), |
|
| 725 |
indent_mod = 0L, |
|
| 726 |
label_pos = "default", |
|
| 727 |
cvar = "", |
|
| 728 |
section_div = NA_character_) {
|
|
| 729 | 24x |
label_pos <- match.arg(label_pos, c("default", label_pos_values))
|
| 730 | 24x |
new("AnalyzeColVarSplit",
|
| 731 | 24x |
payload = NA_character_, |
| 732 | 24x |
split_label = "", |
| 733 | 24x |
content_fun = cfun, |
| 734 | 24x |
analysis_fun = afun, |
| 735 | 24x |
content_format = cformat, |
| 736 | 24x |
split_format = split_format, |
| 737 | 24x |
split_na_str = split_na_str, |
| 738 | 24x |
default_rowlabel = defrowlab, |
| 739 | 24x |
include_NAs = inclNAs, |
| 740 | 24x |
name = split_name, |
| 741 | 24x |
label_children = FALSE, |
| 742 | 24x |
extra_args = extra_args, |
| 743 | 24x |
indent_modifier = as.integer(indent_mod), |
| 744 | 24x |
content_indent_modifier = 0L, |
| 745 | 24x |
var_label_position = label_pos, |
| 746 | 24x |
content_var = cvar, |
| 747 | 24x |
page_title_prefix = NA_character_, |
| 748 | 24x |
child_section_div = section_div, |
| 749 | 24x |
child_show_colcounts = FALSE, |
| 750 | 24x |
child_colcount_format = NA_character_ |
| 751 | 24x |
) ## no content_extra_args |
| 752 |
} |
|
| 753 | ||
| 754 |
setClass("CompoundSplit",
|
|
| 755 |
contains = "Split", |
|
| 756 |
validity = function(object) are(object@payload, "Split") |
|
| 757 |
) |
|
| 758 | ||
| 759 |
setClass("AnalyzeMultiVars", contains = "CompoundSplit")
|
|
| 760 | ||
| 761 |
.repoutlst <- function(x, nv) {
|
|
| 762 | 2184x |
if (!is.function(x) && length(x) == nv) {
|
| 763 | 1034x |
return(x) |
| 764 |
} |
|
| 765 | 1150x |
if (!is(x, "list")) {
|
| 766 | 1150x |
x <- list(x) |
| 767 |
} |
|
| 768 | 1150x |
rep(x, length.out = nv) |
| 769 |
} |
|
| 770 | ||
| 771 |
.uncompound <- function(csplit) {
|
|
| 772 | 71x |
if (is(csplit, "list")) {
|
| 773 | 3x |
return(unlist(lapply(csplit, .uncompound))) |
| 774 |
} |
|
| 775 | ||
| 776 | 68x |
if (!is(csplit, "CompoundSplit")) {
|
| 777 | 67x |
return(csplit) |
| 778 |
} |
|
| 779 | ||
| 780 | 1x |
pld <- spl_payload(csplit) |
| 781 | 1x |
done <- all(!vapply(pld, is, TRUE, class2 = "CompoundSplit")) |
| 782 | 1x |
if (done) {
|
| 783 | 1x |
pld |
| 784 |
} else {
|
|
| 785 | ! |
unlist(lapply(pld, .uncompound)) |
| 786 |
} |
|
| 787 |
} |
|
| 788 | ||
| 789 |
strip_compound_name <- function(obj) {
|
|
| 790 | 11x |
nm <- obj_name(obj) |
| 791 | 11x |
gsub("^ma_", "", nm)
|
| 792 |
} |
|
| 793 | ||
| 794 |
make_ma_name <- function(spl, pld = spl_payload(spl)) {
|
|
| 795 | 3x |
paste( |
| 796 | 3x |
c( |
| 797 | 3x |
"ma", |
| 798 | 3x |
vapply(pld, strip_compound_name, "") |
| 799 |
), |
|
| 800 | 3x |
collapse = "_" |
| 801 |
) |
|
| 802 |
} |
|
| 803 | ||
| 804 |
#' @param .payload (`list`)\cr used internally, not intended to be set by end users. |
|
| 805 |
#' |
|
| 806 |
#' @return An `AnalyzeMultiVars` split object. |
|
| 807 |
#' |
|
| 808 |
#' @export |
|
| 809 |
#' @rdname avarspl |
|
| 810 |
AnalyzeMultiVars <- function(var, |
|
| 811 |
split_label = "", |
|
| 812 |
afun, |
|
| 813 |
defrowlab = "", |
|
| 814 |
cfun = NULL, |
|
| 815 |
cformat = NULL, |
|
| 816 |
split_format = NULL, |
|
| 817 |
split_na_str = NA_character_, |
|
| 818 |
inclNAs = FALSE, |
|
| 819 |
.payload = NULL, |
|
| 820 |
split_name = NULL, |
|
| 821 |
extra_args = list(), |
|
| 822 |
indent_mod = 0L, |
|
| 823 |
child_labels = c("default", "topleft", "visible", "hidden"),
|
|
| 824 |
child_names = var, |
|
| 825 |
cvar = "", |
|
| 826 |
section_div = NA_character_) {
|
|
| 827 |
## NB we used to resolve to strict TRUE/FALSE for label visibillity |
|
| 828 |
## in this function but that was too greedy for repeated |
|
| 829 |
## analyze calls, so that now occurs in the tabulation machinery |
|
| 830 |
## when the table is actually being built. |
|
| 831 |
## show_kidlabs = .labelkids_helper(match.arg(child_labels)) |
|
| 832 | 393x |
child_labels <- match.arg(child_labels) |
| 833 | 393x |
show_kidlabs <- child_labels |
| 834 | 393x |
if (is.null(.payload)) {
|
| 835 | 364x |
nv <- length(var) |
| 836 | 364x |
defrowlab <- .repoutlst(defrowlab, nv) |
| 837 | 364x |
afun <- .repoutlst(afun, nv) |
| 838 | 364x |
split_label <- .repoutlst(split_label, nv) |
| 839 | 364x |
check_ok_label(split_label, multi_ok = TRUE) |
| 840 | 364x |
cfun <- .repoutlst(cfun, nv) |
| 841 | 364x |
cformat <- .repoutlst(cformat, nv) |
| 842 |
## split_format = .repoutlst(split_format, nv) |
|
| 843 | 364x |
inclNAs <- .repoutlst(inclNAs, nv) |
| 844 | 364x |
section_div_if_multivar <- if (length(var) > 1) NA_character_ else section_div |
| 845 | 364x |
pld <- mapply(AnalyzeVarSplit, |
| 846 | 364x |
var = var, |
| 847 | 364x |
split_name = child_names, |
| 848 | 364x |
split_label = split_label, |
| 849 | 364x |
afun = afun, |
| 850 | 364x |
defrowlab = defrowlab, |
| 851 | 364x |
cfun = cfun, |
| 852 | 364x |
cformat = cformat, |
| 853 |
## split_format = split_format, |
|
| 854 | 364x |
inclNAs = inclNAs, |
| 855 | 364x |
MoreArgs = list( |
| 856 | 364x |
extra_args = extra_args, |
| 857 | 364x |
indent_mod = indent_mod, |
| 858 | 364x |
label_pos = show_kidlabs, |
| 859 | 364x |
split_format = split_format, |
| 860 | 364x |
split_na_str = split_na_str, |
| 861 | 364x |
section_div = section_div_if_multivar |
| 862 | 364x |
), ## rvis), |
| 863 | 364x |
SIMPLIFY = FALSE |
| 864 |
) |
|
| 865 |
} else {
|
|
| 866 |
## we're combining existing splits here |
|
| 867 | 29x |
pld <- unlist(lapply(.payload, .uncompound)) |
| 868 | ||
| 869 |
## only override the childen being combined if the constructor |
|
| 870 |
## was passed a non-default value for child_labels |
|
| 871 |
## and the child was at NA before |
|
| 872 | 29x |
pld <- lapply( |
| 873 | 29x |
pld, |
| 874 | 29x |
function(x) {
|
| 875 | 58x |
rvis <- label_position(x) ## labelrow_visible(x) |
| 876 | 58x |
if (!identical(show_kidlabs, "default")) { ## is.na(show_kidlabs)) {
|
| 877 | ! |
if (identical(rvis, "default")) { ## ois.na(rvis))
|
| 878 | ! |
rvis <- show_kidlabs |
| 879 |
} |
|
| 880 |
} |
|
| 881 | 58x |
label_position(x) <- rvis |
| 882 | 58x |
x |
| 883 |
} |
|
| 884 |
) |
|
| 885 |
} |
|
| 886 | 393x |
if (length(pld) == 1) {
|
| 887 | 329x |
ret <- pld[[1]] |
| 888 |
} else {
|
|
| 889 | 64x |
if (is.null(split_name)) {
|
| 890 | 63x |
split_name <- paste(c("ma", vapply(pld, obj_name, "")),
|
| 891 | 63x |
collapse = "_" |
| 892 |
) |
|
| 893 |
} |
|
| 894 | 64x |
ret <- new("AnalyzeMultiVars",
|
| 895 | 64x |
payload = pld, |
| 896 | 64x |
split_label = "", |
| 897 | 64x |
split_format = NULL, |
| 898 | 64x |
split_na_str = split_na_str, |
| 899 | 64x |
content_fun = NULL, |
| 900 | 64x |
content_format = NULL, |
| 901 |
## I beleive this is superfluous now |
|
| 902 |
## the payloads carry aroudn the real instructions |
|
| 903 |
## XXX |
|
| 904 | 64x |
label_children = .labelkids_helper(show_kidlabs), |
| 905 | 64x |
split_label_position = "hidden", ## XXX is this right? |
| 906 | 64x |
name = split_name, |
| 907 | 64x |
extra_args = extra_args, |
| 908 |
## modifier applied on splits in payload |
|
| 909 | 64x |
indent_modifier = 0L, |
| 910 | 64x |
content_indent_modifier = 0L, |
| 911 | 64x |
content_var = cvar, |
| 912 | 64x |
page_title_prefix = NA_character_, |
| 913 | 64x |
child_section_div = section_div |
| 914 |
) |
|
| 915 |
} |
|
| 916 | 393x |
ret |
| 917 |
} |
|
| 918 | ||
| 919 |
setClass("VarLevWBaselineSplit",
|
|
| 920 |
contains = "VarLevelSplit", |
|
| 921 |
representation( |
|
| 922 |
var = "character", |
|
| 923 |
ref_group_value = "character" |
|
| 924 |
) |
|
| 925 |
) |
|
| 926 | ||
| 927 |
#' @rdname VarLevelSplit |
|
| 928 |
#' @export |
|
| 929 |
VarLevWBaselineSplit <- function(var, |
|
| 930 |
ref_group, |
|
| 931 |
labels_var = var, |
|
| 932 |
split_label, |
|
| 933 |
split_fun = NULL, |
|
| 934 |
label_fstr = "%s - %s", |
|
| 935 |
## not needed I Think... |
|
| 936 |
cfun = NULL, |
|
| 937 |
cformat = NULL, |
|
| 938 |
cna_str = NA_character_, |
|
| 939 |
cvar = "", |
|
| 940 |
split_format = NULL, |
|
| 941 |
split_na_str = NA_character_, |
|
| 942 |
valorder = NULL, |
|
| 943 |
split_name = var, |
|
| 944 |
extra_args = list(), |
|
| 945 |
show_colcounts = FALSE, |
|
| 946 |
colcount_format = NULL) {
|
|
| 947 | 10x |
check_ok_label(split_label) |
| 948 | 10x |
new("VarLevWBaselineSplit",
|
| 949 | 10x |
payload = var, |
| 950 | 10x |
ref_group_value = ref_group, |
| 951 |
## This will occur at the row level not on the column split, for now |
|
| 952 |
## TODO revisit this to confirm its right |
|
| 953 |
## comparison_func = comparison, |
|
| 954 |
# label_format = label_fstr, |
|
| 955 | 10x |
value_label_var = labels_var, |
| 956 | 10x |
split_label = split_label, |
| 957 | 10x |
content_fun = cfun, |
| 958 | 10x |
content_format = cformat, |
| 959 | 10x |
content_na_str = cna_str, |
| 960 | 10x |
split_format = split_format, |
| 961 | 10x |
split_na_str = split_na_str, |
| 962 | 10x |
split_fun = split_fun, |
| 963 | 10x |
name = split_name, |
| 964 | 10x |
label_children = FALSE, |
| 965 | 10x |
extra_args = extra_args, |
| 966 |
## this is always a column split |
|
| 967 | 10x |
indent_modifier = 0L, |
| 968 | 10x |
content_indent_modifier = 0L, |
| 969 | 10x |
content_var = cvar, |
| 970 |
## so long as this is columnspace only |
|
| 971 | 10x |
page_title_prefix = NA_character_, |
| 972 | 10x |
child_section_div = NA_character_, |
| 973 | 10x |
child_show_colcounts = show_colcounts, |
| 974 | 10x |
child_colcount_format = colcount_format |
| 975 |
) |
|
| 976 |
} |
|
| 977 | ||
| 978 |
.chkname <- function(nm) {
|
|
| 979 | 21855x |
if (is.null(nm)) {
|
| 980 | ! |
nm <- "" |
| 981 |
} |
|
| 982 | 21855x |
if (length(nm) != 1) {
|
| 983 | ! |
stop("name is not of length one")
|
| 984 | 21855x |
} else if (is.na(nm)) {
|
| 985 | ! |
warning("Got missing value for name, converting to characters '<NA>'")
|
| 986 | ! |
nm <- "<NA>" |
| 987 |
} |
|
| 988 | 21855x |
nm |
| 989 |
} |
|
| 990 | ||
| 991 |
### Tree Position Representation |
|
| 992 |
### |
|
| 993 |
### Class(es) that represent position with in a |
|
| 994 |
### tree as parallel vectors of Split objects and |
|
| 995 |
### values chosen at that split, plus labeling info |
|
| 996 |
TreePos <- function(spls = list(), |
|
| 997 |
svals = list(), |
|
| 998 |
svlabels = character(), |
|
| 999 |
sub = NULL) {
|
|
| 1000 | 1928x |
check_ok_label(svlabels, multi_ok = TRUE) |
| 1001 | 1928x |
svals <- make_splvalue_vec(vals = svals, subset_exprs = lapply(svals, value_expr)) |
| 1002 | 1928x |
if (is.null(sub)) {
|
| 1003 | 418x |
if (length(spls) > 0) {
|
| 1004 | ! |
sub <- make_pos_subset( |
| 1005 | ! |
spls = spls, |
| 1006 | ! |
svals = svals |
| 1007 |
) |
|
| 1008 |
} else {
|
|
| 1009 | 418x |
sub <- expression(TRUE) |
| 1010 |
} |
|
| 1011 |
} |
|
| 1012 | 1928x |
new("TreePos",
|
| 1013 | 1928x |
splits = spls, s_values = svals, |
| 1014 | 1928x |
sval_labels = svlabels, |
| 1015 | 1928x |
subset = sub |
| 1016 |
) |
|
| 1017 |
} |
|
| 1018 | ||
| 1019 |
## Tree position convenience functions |
|
| 1020 |
## |
|
| 1021 |
make_child_pos <- function(parpos, |
|
| 1022 |
newspl, |
|
| 1023 |
newval, |
|
| 1024 |
newlab = newval, |
|
| 1025 |
newextra = list()) {
|
|
| 1026 | 1510x |
if (!is(newval, "SplitValue")) {
|
| 1027 | ! |
nsplitval <- SplitValue(newval, extr = newextra, label = newlab) |
| 1028 |
} else {
|
|
| 1029 | 1510x |
nsplitval <- newval |
| 1030 |
} |
|
| 1031 | 1510x |
check_ok_label(newlab) |
| 1032 | 1510x |
newpos <- TreePos( |
| 1033 | 1510x |
spls = c(pos_splits(parpos), newspl), |
| 1034 | 1510x |
svals = c(pos_splvals(parpos), nsplitval), |
| 1035 | 1510x |
svlabels = c(pos_splval_labels(parpos), newlab), |
| 1036 | 1510x |
sub = .combine_subset_exprs( |
| 1037 | 1510x |
pos_subset(parpos), |
| 1038 |
## this will grab the value's custom subset expression if present |
|
| 1039 | 1510x |
make_subset_expr(newspl, nsplitval) |
| 1040 |
) |
|
| 1041 |
) |
|
| 1042 | 1510x |
newpos |
| 1043 |
} |
|
| 1044 | ||
| 1045 |
## Virtual Classes for Tree Nodes and Layouts ================================= |
|
| 1046 |
## |
|
| 1047 |
## Virtual class hiearchy for the various types of trees in use in the S4 |
|
| 1048 |
## implementation of the TableTree machinery |
|
| 1049 | ||
| 1050 |
## core basics |
|
| 1051 |
setClass("VNodeInfo",
|
|
| 1052 |
contains = "VIRTUAL", |
|
| 1053 |
representation( |
|
| 1054 |
level = "integer", |
|
| 1055 |
name = "character" ## , |
|
| 1056 |
## label = "character" |
|
| 1057 |
) |
|
| 1058 |
) |
|
| 1059 | ||
| 1060 |
setClass("VTree",
|
|
| 1061 |
contains = c("VIRTUAL", "VNodeInfo"),
|
|
| 1062 |
representation(children = "list") |
|
| 1063 |
) |
|
| 1064 | ||
| 1065 |
setClass("VLeaf", contains = c("VIRTUAL", "VNodeInfo"))
|
|
| 1066 | ||
| 1067 |
## Layout trees ================================= |
|
| 1068 | ||
| 1069 |
# setClass("VLayoutNode", contains= c("VIRTUAL", "VNodeInfo"))
|
|
| 1070 | ||
| 1071 |
setClass("VLayoutLeaf",
|
|
| 1072 |
contains = c("VIRTUAL", "VLeaf"),
|
|
| 1073 |
representation( |
|
| 1074 |
pos_in_tree = "TreePos", |
|
| 1075 |
label = "character" |
|
| 1076 |
) |
|
| 1077 |
) |
|
| 1078 | ||
| 1079 |
setClass("VLayoutTree",
|
|
| 1080 |
contains = c("VIRTUAL", "VTree"),
|
|
| 1081 |
representation( |
|
| 1082 |
split = "Split", |
|
| 1083 |
pos_in_tree = "TreePos", |
|
| 1084 |
label = "character" |
|
| 1085 |
) |
|
| 1086 |
) |
|
| 1087 | ||
| 1088 |
setClassUnion("VLayoutNode", c("VLayoutLeaf", "VLayoutTree"))
|
|
| 1089 | ||
| 1090 |
## LayoutAxisTree classes ================================= |
|
| 1091 | ||
| 1092 |
setOldClass("function")
|
|
| 1093 |
setOldClass("NULL")
|
|
| 1094 |
setClassUnion("FunctionOrNULL", c("function", "NULL"))
|
|
| 1095 | ||
| 1096 |
setClass("LayoutAxisTree",
|
|
| 1097 |
contains = "VLayoutTree", |
|
| 1098 |
representation(summary_func = "FunctionOrNULL"), |
|
| 1099 |
validity = function(object) {
|
|
| 1100 |
all(sapply(object@children, function(x) is(x, "LayoutAxisTree") || is(x, "LayoutAxisLeaf"))) |
|
| 1101 |
} |
|
| 1102 |
) |
|
| 1103 | ||
| 1104 |
## this is only used for columns!!!! |
|
| 1105 |
setClass("LayoutAxisLeaf",
|
|
| 1106 |
contains = "VLayoutLeaf", ## "VNodeInfo", |
|
| 1107 |
representation( |
|
| 1108 |
func = "function", |
|
| 1109 |
display_columncounts = "logical", |
|
| 1110 |
columncount_format = "FormatSpec", # character", |
|
| 1111 |
col_footnotes = "list", |
|
| 1112 |
column_count = "integer" |
|
| 1113 |
) |
|
| 1114 |
) |
|
| 1115 | ||
| 1116 |
setClass("LayoutColTree",
|
|
| 1117 |
contains = "LayoutAxisTree", |
|
| 1118 |
representation( |
|
| 1119 |
display_columncounts = "logical", |
|
| 1120 |
columncount_format = "FormatSpec", # "character", |
|
| 1121 |
col_footnotes = "list", |
|
| 1122 |
column_count = "integer" |
|
| 1123 |
) |
|
| 1124 |
) |
|
| 1125 | ||
| 1126 |
setClass("LayoutColLeaf", contains = "LayoutAxisLeaf")
|
|
| 1127 |
LayoutColTree <- function(lev = 0L, |
|
| 1128 |
name = obj_name(spl), |
|
| 1129 |
label = obj_label(spl), |
|
| 1130 |
kids = list(), |
|
| 1131 |
spl = EmptyAllSplit, |
|
| 1132 |
tpos = TreePos(), |
|
| 1133 |
summary_function = NULL, |
|
| 1134 |
disp_ccounts = FALSE, |
|
| 1135 |
colcount_format = NULL, |
|
| 1136 |
footnotes = list(), |
|
| 1137 |
colcount) { ## ,
|
|
| 1138 |
## sub = expression(TRUE), |
|
| 1139 |
## svar = NA_character_, |
|
| 1140 |
## slab = NA_character_) {
|
|
| 1141 | 677x |
if (is.null(spl)) {
|
| 1142 | ! |
stop( |
| 1143 | ! |
"LayoutColTree constructor got NULL for spl. ", # nocov |
| 1144 | ! |
"This should never happen. Please contact the maintainer." |
| 1145 |
) |
|
| 1146 |
} # nocov |
|
| 1147 | 677x |
footnotes <- make_ref_value(footnotes) |
| 1148 | 677x |
check_ok_label(label) |
| 1149 | 677x |
new("LayoutColTree",
|
| 1150 | 677x |
level = lev, children = kids, |
| 1151 | 677x |
name = .chkname(name), |
| 1152 | 677x |
summary_func = summary_function, |
| 1153 | 677x |
pos_in_tree = tpos, |
| 1154 | 677x |
split = spl, |
| 1155 |
## subset = sub, |
|
| 1156 |
## splitvar = svar, |
|
| 1157 | 677x |
label = label, |
| 1158 | 677x |
display_columncounts = disp_ccounts, |
| 1159 | 677x |
columncount_format = colcount_format, |
| 1160 | 677x |
col_footnotes = footnotes, |
| 1161 | 677x |
column_count = colcount |
| 1162 |
) |
|
| 1163 |
} |
|
| 1164 | ||
| 1165 |
LayoutColLeaf <- function(lev = 0L, |
|
| 1166 |
name = label, |
|
| 1167 |
label = "", |
|
| 1168 |
tpos = TreePos(), |
|
| 1169 |
colcount, |
|
| 1170 |
disp_ccounts = FALSE, |
|
| 1171 |
colcount_format = NULL) {
|
|
| 1172 | 1255x |
check_ok_label(label) |
| 1173 | 1255x |
new("LayoutColLeaf",
|
| 1174 | 1255x |
level = lev, name = .chkname(name), label = label, |
| 1175 | 1255x |
pos_in_tree = tpos, |
| 1176 | 1255x |
column_count = colcount, |
| 1177 | 1255x |
display_columncounts = disp_ccounts, |
| 1178 | 1255x |
columncount_format = colcount_format |
| 1179 |
) |
|
| 1180 |
} |
|
| 1181 | ||
| 1182 |
## Instantiated column info class ============================================== |
|
| 1183 |
## |
|
| 1184 |
## This is so we don't need multiple arguments |
|
| 1185 |
## in the recursive functions that track |
|
| 1186 |
## various aspects of the column layout |
|
| 1187 |
## once its applied to the data. |
|
| 1188 | ||
| 1189 |
#' Instantiated column info |
|
| 1190 |
#' |
|
| 1191 |
#' @inheritParams gen_args |
|
| 1192 |
#' |
|
| 1193 |
#' @exportClass InstantiatedColumnInfo |
|
| 1194 |
#' @rdname cinfo |
|
| 1195 |
setClass( |
|
| 1196 |
"InstantiatedColumnInfo", |
|
| 1197 |
representation( |
|
| 1198 |
tree_layout = "VLayoutNode", ## LayoutColTree", |
|
| 1199 |
subset_exprs = "list", |
|
| 1200 |
cextra_args = "list", |
|
| 1201 |
counts = "integer", |
|
| 1202 |
total_count = "integer", |
|
| 1203 |
display_columncounts = "logical", |
|
| 1204 |
columncount_format = "FormatSpec", |
|
| 1205 |
columncount_na_str = "character", |
|
| 1206 |
top_left = "character" |
|
| 1207 |
) |
|
| 1208 |
) |
|
| 1209 | ||
| 1210 |
#' @param treelyt (`LayoutColTree`)\cr a `LayoutColTree` object. |
|
| 1211 |
#' @param csubs (`list`)\cr a list of subsetting expressions. |
|
| 1212 |
#' @param extras (`list`)\cr extra arguments associated with the columns. |
|
| 1213 |
#' @param cnts (`integer`)\cr counts. |
|
| 1214 |
#' @param total_cnt (`integer(1)`)\cr total observations represented across all columns. |
|
| 1215 |
#' @param dispcounts (`flag`)\cr whether the counts should be displayed as header info when the associated |
|
| 1216 |
#' table is printed. |
|
| 1217 |
#' @param countformat (`string`)\cr format for the counts if they are displayed. |
|
| 1218 |
#' @param count_na_str (`character`)\cr string to use in place of missing values when formatting counts. Defaults |
|
| 1219 |
#' to `""`. |
|
| 1220 |
#' |
|
| 1221 |
#' @return An `InstantiateadColumnInfo` object. |
|
| 1222 |
#' |
|
| 1223 |
#' @export |
|
| 1224 |
#' @rdname cinfo |
|
| 1225 |
InstantiatedColumnInfo <- function(treelyt = LayoutColTree(colcount = total_cnt), |
|
| 1226 |
csubs = list(expression(TRUE)), |
|
| 1227 |
extras = list(list()), |
|
| 1228 |
cnts = NA_integer_, |
|
| 1229 |
total_cnt = NA_integer_, |
|
| 1230 |
dispcounts = FALSE, |
|
| 1231 |
countformat = "(N=xx)", |
|
| 1232 |
count_na_str = "", |
|
| 1233 |
topleft = character()) {
|
|
| 1234 | 708x |
leaves <- collect_leaves(treelyt) |
| 1235 | 708x |
nl <- length(leaves) |
| 1236 | 708x |
extras <- rep(extras, length.out = nl) |
| 1237 | 708x |
cnts <- rep(cnts, length.out = nl) |
| 1238 | 708x |
csubs <- rep(csubs, length.out = nl) |
| 1239 | ||
| 1240 | 708x |
nleaves <- length(leaves) |
| 1241 | 708x |
snas <- sum(is.na(cnts)) |
| 1242 | 708x |
if (length(csubs) != nleaves || length(extras) != nleaves || length(cnts) != nleaves) {
|
| 1243 | ! |
stop( |
| 1244 | ! |
"Mismatching number of columns indicated by: csubs [", |
| 1245 | ! |
length(csubs), "], ", |
| 1246 | ! |
"treelyt [", nl, "], extras [", length(extras), |
| 1247 | ! |
"] and counts [", cnts, "]." |
| 1248 |
) |
|
| 1249 |
} |
|
| 1250 | 708x |
if (snas != 0 && snas != nleaves) {
|
| 1251 | 2x |
warning( |
| 1252 | 2x |
"Mixture of missing and non-missing column counts when ", |
| 1253 | 2x |
"creating column info." |
| 1254 |
) |
|
| 1255 |
} |
|
| 1256 | ||
| 1257 | 708x |
if (!is.na(dispcounts)) {
|
| 1258 | 433x |
pths <- col_paths(treelyt) |
| 1259 | 433x |
for (path in pths) {
|
| 1260 | 967x |
colcount_visible(treelyt, path) <- dispcounts |
| 1261 |
} |
|
| 1262 |
} else { ## na leaves the children as they are and dispcols goes to whether any of them are displayed for the leaves
|
|
| 1263 | 275x |
dispcounts <- any(vapply(leaves, disp_ccounts, NA)) |
| 1264 |
} |
|
| 1265 | ||
| 1266 | 708x |
new("InstantiatedColumnInfo",
|
| 1267 | 708x |
tree_layout = treelyt, |
| 1268 | 708x |
subset_exprs = csubs, |
| 1269 | 708x |
cextra_args = extras, |
| 1270 | 708x |
counts = cnts, |
| 1271 | 708x |
total_count = total_cnt, |
| 1272 | 708x |
display_columncounts = dispcounts, |
| 1273 | 708x |
columncount_format = countformat, |
| 1274 | 708x |
columncount_na_str = count_na_str, |
| 1275 | 708x |
top_left = topleft |
| 1276 |
) |
|
| 1277 |
} |
|
| 1278 | ||
| 1279 |
## TableTrees and row classes ================================================== |
|
| 1280 |
## XXX Rowspans as implemented dont really work |
|
| 1281 |
## they're aren't attached to the right data structures |
|
| 1282 |
## during conversions. |
|
| 1283 | ||
| 1284 |
## FIXME: if we ever actually need row spanning |
|
| 1285 |
setClass("VTableNodeInfo",
|
|
| 1286 |
contains = c("VNodeInfo", "VIRTUAL"),
|
|
| 1287 |
representation( |
|
| 1288 |
## col_layout = "VLayoutNode", |
|
| 1289 |
col_info = "InstantiatedColumnInfo", |
|
| 1290 |
format = "FormatSpec", |
|
| 1291 |
na_str = "character", |
|
| 1292 |
indent_modifier = "integer", |
|
| 1293 |
table_inset = "integer" |
|
| 1294 |
) |
|
| 1295 |
) |
|
| 1296 | ||
| 1297 |
setClass("TableRow",
|
|
| 1298 |
contains = c("VIRTUAL", "VLeaf", "VTableNodeInfo"),
|
|
| 1299 |
representation( |
|
| 1300 |
leaf_value = "ANY", |
|
| 1301 |
var_analyzed = "character", |
|
| 1302 |
## var_label = "character", |
|
| 1303 |
label = "character", |
|
| 1304 |
row_footnotes = "list", |
|
| 1305 |
trailing_section_div = "character" |
|
| 1306 |
) |
|
| 1307 |
) |
|
| 1308 | ||
| 1309 |
## TableTree Core Non-Virtual Classes ============== |
|
| 1310 |
## |
|
| 1311 |
#' Row classes and constructors |
|
| 1312 |
#' |
|
| 1313 |
#' @inheritParams constr_args |
|
| 1314 |
#' @inheritParams lyt_args |
|
| 1315 |
#' @param vis (`flag`)\cr whether the row should be visible (`LabelRow` only). |
|
| 1316 |
#' |
|
| 1317 |
#' @return A formal object representing a table row of the constructed type. |
|
| 1318 |
#' |
|
| 1319 |
#' @author Gabriel Becker |
|
| 1320 |
#' @export |
|
| 1321 |
#' @rdname rowclasses |
|
| 1322 |
LabelRow <- function(lev = 1L, |
|
| 1323 |
label = "", |
|
| 1324 |
name = label, |
|
| 1325 |
vis = !is.na(label) && nzchar(label), |
|
| 1326 |
cinfo = EmptyColInfo, |
|
| 1327 |
indent_mod = 0L, |
|
| 1328 |
table_inset = 0L, |
|
| 1329 |
trailing_section_div = NA_character_) {
|
|
| 1330 | 5451x |
check_ok_label(label) |
| 1331 | 5451x |
new("LabelRow",
|
| 1332 | 5451x |
leaf_value = list(), |
| 1333 | 5451x |
level = lev, |
| 1334 | 5451x |
label = label, |
| 1335 |
## XXX this means that a label row and its talbe can have the same name.... |
|
| 1336 |
## XXX that is bad but how bad remains to be seen |
|
| 1337 |
## XXX |
|
| 1338 | 5451x |
name = .chkname(name), |
| 1339 | 5451x |
col_info = cinfo, |
| 1340 | 5451x |
visible = vis, |
| 1341 | 5451x |
indent_modifier = as.integer(indent_mod), |
| 1342 | 5451x |
table_inset = as.integer(table_inset), |
| 1343 | 5451x |
trailing_section_div = trailing_section_div |
| 1344 |
) |
|
| 1345 |
} |
|
| 1346 | ||
| 1347 |
#' Row constructors and classes |
|
| 1348 |
#' |
|
| 1349 |
#' @rdname rowclasses |
|
| 1350 |
#' @exportClass DataRow |
|
| 1351 |
setClass("DataRow",
|
|
| 1352 |
contains = "TableRow", |
|
| 1353 |
representation(colspans = "integer") ## , |
|
| 1354 |
## pos_in_tree = "TableRowPos"), |
|
| 1355 |
## validity = function(object) {
|
|
| 1356 |
## lcsp = length(object@colspans) |
|
| 1357 |
## length(lcsp == 0) || lcsp == length(object@leaf_value) |
|
| 1358 |
## } |
|
| 1359 |
) |
|
| 1360 | ||
| 1361 |
#' @rdname rowclasses |
|
| 1362 |
#' @exportClass ContentRow |
|
| 1363 |
setClass("ContentRow",
|
|
| 1364 |
contains = "TableRow", |
|
| 1365 |
representation(colspans = "integer") ## , |
|
| 1366 |
## pos_in_tree = "TableRowPos"), |
|
| 1367 |
## validity = function(object) {
|
|
| 1368 |
## lcsp = length(object@colspans) |
|
| 1369 |
## length(lcsp == 0) || lcsp == length(object@leaf_value) |
|
| 1370 |
## } |
|
| 1371 |
) |
|
| 1372 | ||
| 1373 |
#' @rdname rowclasses |
|
| 1374 |
#' @exportClass LabelRow |
|
| 1375 |
setClass("LabelRow",
|
|
| 1376 |
contains = "TableRow", |
|
| 1377 |
representation(visible = "logical") |
|
| 1378 |
) |
|
| 1379 | ||
| 1380 |
#' @param klass (`character`)\cr internal detail. |
|
| 1381 |
#' |
|
| 1382 |
#' @export |
|
| 1383 |
#' @rdname rowclasses |
|
| 1384 |
.tablerow <- function(vals = list(), |
|
| 1385 |
name = "", |
|
| 1386 |
lev = 1L, |
|
| 1387 |
label = name, |
|
| 1388 |
cspan = rep(1L, length(vals)), |
|
| 1389 |
cinfo = EmptyColInfo, |
|
| 1390 |
var = NA_character_, |
|
| 1391 |
format = NULL, |
|
| 1392 |
na_str = NA_character_, |
|
| 1393 |
klass, |
|
| 1394 |
indent_mod = 0L, |
|
| 1395 |
footnotes = list(), |
|
| 1396 |
table_inset = 0L, |
|
| 1397 |
trailing_section_div = NA_character_) {
|
|
| 1398 | 3823x |
if ((missing(name) || is.null(name) || is.na(name) || nchar(name) == 0) && !missing(label)) {
|
| 1399 | 274x |
name <- label |
| 1400 |
} |
|
| 1401 | 3823x |
vals <- lapply(vals, rcell) |
| 1402 | 3823x |
rlabels <- unique(unlist(lapply(vals, obj_label))) |
| 1403 | 3823x |
if ((missing(label) || is.null(label) || identical(label, "")) && sum(nzchar(rlabels)) == 1) {
|
| 1404 | ! |
label <- rlabels[nzchar(rlabels)] |
| 1405 |
} |
|
| 1406 | 3823x |
if (missing(cspan) && !is.null(unlist(lapply(vals, cell_cspan)))) {
|
| 1407 | 3565x |
cspan <- vapply(vals, cell_cspan, 0L) |
| 1408 |
} |
|
| 1409 | ||
| 1410 | 3823x |
check_ok_label(label) |
| 1411 | 3823x |
rw <- new(klass, |
| 1412 | 3823x |
leaf_value = vals, |
| 1413 | 3823x |
name = .chkname(name), |
| 1414 | 3823x |
level = lev, |
| 1415 | 3823x |
label = .chkname(label), |
| 1416 | 3823x |
colspans = cspan, |
| 1417 | 3823x |
col_info = cinfo, |
| 1418 | 3823x |
var_analyzed = var, |
| 1419 |
## these are set in set_format_recursive below |
|
| 1420 | 3823x |
format = NULL, |
| 1421 | 3823x |
na_str = NA_character_, |
| 1422 | 3823x |
indent_modifier = indent_mod, |
| 1423 | 3823x |
row_footnotes = footnotes, |
| 1424 | 3823x |
table_inset = table_inset, |
| 1425 | 3823x |
trailing_section_div = trailing_section_div |
| 1426 |
) |
|
| 1427 | 3823x |
rw <- set_format_recursive(rw, format, na_str, FALSE) |
| 1428 | 3823x |
rw |
| 1429 |
} |
|
| 1430 | ||
| 1431 |
#' @param ... additional parameters passed to shared constructor (`.tablerow`). |
|
| 1432 |
#' |
|
| 1433 |
#' @export |
|
| 1434 |
#' @rdname rowclasses |
|
| 1435 | 3259x |
DataRow <- function(...) .tablerow(..., klass = "DataRow") |
| 1436 | ||
| 1437 |
#' @export |
|
| 1438 |
#' @rdname rowclasses |
|
| 1439 | 564x |
ContentRow <- function(...) .tablerow(..., klass = "ContentRow") |
| 1440 | ||
| 1441 |
setClass("VTitleFooter",
|
|
| 1442 |
contains = "VIRTUAL", |
|
| 1443 |
representation( |
|
| 1444 |
main_title = "character", |
|
| 1445 |
subtitles = "character", |
|
| 1446 |
main_footer = "character", |
|
| 1447 |
provenance_footer = "character" |
|
| 1448 |
) |
|
| 1449 |
) |
|
| 1450 | ||
| 1451 |
setClass("VTableTree",
|
|
| 1452 |
contains = c("VIRTUAL", "VTableNodeInfo", "VTree", "VTitleFooter"),
|
|
| 1453 |
representation( |
|
| 1454 |
children = "list", |
|
| 1455 |
rowspans = "data.frame", |
|
| 1456 |
labelrow = "LabelRow", |
|
| 1457 |
page_titles = "character", |
|
| 1458 |
horizontal_sep = "character", |
|
| 1459 |
header_section_div = "character", |
|
| 1460 |
trailing_section_div = "character" |
|
| 1461 |
) |
|
| 1462 |
) |
|
| 1463 | ||
| 1464 |
setClassUnion("IntegerOrNull", c("integer", "NULL"))
|
|
| 1465 |
## covered because it's ElementaryTable's validity method but covr misses it |
|
| 1466 |
## nocov start |
|
| 1467 |
etable_validity <- function(object) {
|
|
| 1468 |
kids <- tree_children(object) |
|
| 1469 |
all(sapply( |
|
| 1470 |
kids, |
|
| 1471 |
function(k) {
|
|
| 1472 |
(is(k, "DataRow") || is(k, "ContentRow")) |
|
| 1473 |
} |
|
| 1474 |
)) ### && |
|
| 1475 |
} |
|
| 1476 |
## nocov end |
|
| 1477 | ||
| 1478 |
#' `TableTree` classes |
|
| 1479 |
#' |
|
| 1480 |
#' @return A formal object representing a populated table. |
|
| 1481 |
#' |
|
| 1482 |
#' @author Gabriel Becker |
|
| 1483 |
#' @exportClass ElementaryTable |
|
| 1484 |
#' @rdname tabclasses |
|
| 1485 |
setClass("ElementaryTable",
|
|
| 1486 |
contains = "VTableTree", |
|
| 1487 |
representation(var_analyzed = "character"), |
|
| 1488 |
validity = etable_validity ## function(object) {
|
|
| 1489 |
) |
|
| 1490 | ||
| 1491 |
.enforce_valid_kids <- function(lst, colinfo) {
|
|
| 1492 |
## colinfo |
|
| 1493 | 6826x |
if (!no_colinfo(colinfo)) {
|
| 1494 | 6826x |
lst <- lapply( |
| 1495 | 6826x |
lst, |
| 1496 | 6826x |
function(x) {
|
| 1497 | 8739x |
if (no_colinfo(x)) {
|
| 1498 | 208x |
col_info(x) <- colinfo |
| 1499 | 8531x |
} else if (!identical(colinfo, col_info(x), ignore.environment = TRUE)) {
|
| 1500 |
## split functions from function factories (e.g. add_combo_levels) |
|
| 1501 |
## have different environments so we can't use identical here |
|
| 1502 |
## all.equal requires the **values within the closures** to be the |
|
| 1503 |
## same but not the actual enclosing environments. |
|
| 1504 | ! |
stop( |
| 1505 | ! |
"attempted to add child with non-matching, non-empty ", |
| 1506 | ! |
"column info to an existing table" |
| 1507 |
) |
|
| 1508 |
} |
|
| 1509 | 8739x |
x |
| 1510 |
} |
|
| 1511 |
) |
|
| 1512 |
} |
|
| 1513 | ||
| 1514 | 6826x |
if (are(lst, "ElementaryTable") && |
| 1515 | 6826x |
all(sapply(lst, function(tb) {
|
| 1516 | 1205x |
nrow(tb) <= 1 && identical(obj_name(tb), "") |
| 1517 |
}))) {
|
|
| 1518 | 1799x |
lst <- unlist(lapply(lst, function(tb) tree_children(tb)[[1]])) |
| 1519 |
} |
|
| 1520 | 6826x |
if (length(lst) == 0) {
|
| 1521 | 1799x |
return(list()) |
| 1522 |
} |
|
| 1523 |
## names |
|
| 1524 | 5027x |
realnames <- sapply(lst, obj_name) |
| 1525 | 5027x |
lstnames <- names(lst) |
| 1526 | 5027x |
if (is.null(lstnames)) {
|
| 1527 | 2132x |
names(lst) <- realnames |
| 1528 | 2895x |
} else if (!identical(realnames, lstnames)) {
|
| 1529 | 2895x |
names(lst) <- realnames |
| 1530 |
} |
|
| 1531 | 5027x |
if (any(duplicated(realnames))) {
|
| 1532 | 13x |
lst <- uniqify_child_names(lst) |
| 1533 |
} |
|
| 1534 | 5027x |
lst |
| 1535 |
} |
|
| 1536 | ||
| 1537 |
## assumes that list names match obj_names before calling this |
|
| 1538 |
uniqify_child_names <- function(kidlst) {
|
|
| 1539 | 13x |
while (any(duplicated(names(kidlst)))) {
|
| 1540 | 19x |
oldnms <- names(kidlst) |
| 1541 | 19x |
val_to_fix <- oldnms[duplicated(oldnms)][1] |
| 1542 | 19x |
inds <- which(oldnms == val_to_fix)[-1] ## don'tneed to change first one |
| 1543 | 19x |
newnms <- paste0(val_to_fix, "[", seq_along(inds) + 1, "]") |
| 1544 | 19x |
kidlst[inds] <- lapply( |
| 1545 | 19x |
seq_along(inds), |
| 1546 | 19x |
function(i) {
|
| 1547 | 30x |
kid <- kidlst[[inds[i]]] |
| 1548 | 30x |
c_tt <- content_table(kid) |
| 1549 |
## match existing behavior which is unfortunately somewhat inconsistent |
|
| 1550 |
## if the content table has a name update it, otherwise leave it as "" |
|
| 1551 |
## this is so tables created with parent_name = in the layout pass |
|
| 1552 |
## identicality checks with ones we're automatically uniqifying names in |
|
| 1553 | 30x |
if (!is.null(c_tt) && nzchar(obj_name(c_tt))) {
|
| 1554 | 1x |
obj_name(c_tt) <- gsub(oldnms[i], newnms[i], obj_name(c_tt), fixed = TRUE) |
| 1555 | 1x |
content_table(kid) <- c_tt |
| 1556 |
} |
|
| 1557 | 30x |
obj_name(kid) <- newnms[i] |
| 1558 | 30x |
kid |
| 1559 |
} |
|
| 1560 |
) |
|
| 1561 | 19x |
message( |
| 1562 | 19x |
"Modifying subtable (or row) names to ensure uniqueness among direct siblings\n[", |
| 1563 | 19x |
paste(val_to_fix, " -> {", paste(c(val_to_fix, newnms), collapse = ", "), "}]\n"),
|
| 1564 | 19x |
" To control table names use split_rows_by*(, parent_name =.) or ", |
| 1565 | 19x |
" analyze(., table_names = .) when analyzing a single variable, or ", |
| 1566 | 19x |
"analyze(., parent_name = .) when analyzing multiple variables in a single call.", |
| 1567 | 19x |
call. = FALSE |
| 1568 |
) |
|
| 1569 | 19x |
names(kidlst)[inds] <- newnms |
| 1570 |
} |
|
| 1571 | 13x |
kidlst |
| 1572 |
} |
|
| 1573 | ||
| 1574 | ||
| 1575 |
#' Table constructors and classes |
|
| 1576 |
#' |
|
| 1577 |
#' @inheritParams constr_args |
|
| 1578 |
#' @inheritParams gen_args |
|
| 1579 |
#' @inheritParams lyt_args |
|
| 1580 |
#' @param rspans (`data.frame`)\cr currently stored but otherwise ignored. |
|
| 1581 |
#' |
|
| 1582 |
#' @author Gabriel Becker |
|
| 1583 |
#' @export |
|
| 1584 |
#' @rdname tabclasses |
|
| 1585 |
ElementaryTable <- function(kids = list(), |
|
| 1586 |
name = "", |
|
| 1587 |
lev = 1L, |
|
| 1588 |
label = "", |
|
| 1589 |
labelrow = LabelRow( |
|
| 1590 |
lev = lev, |
|
| 1591 |
label = label, |
|
| 1592 |
vis = !isTRUE(iscontent) && |
|
| 1593 |
!is.na(label) && |
|
| 1594 |
nzchar(label) |
|
| 1595 |
), |
|
| 1596 |
rspans = data.frame(), |
|
| 1597 |
cinfo = NULL, |
|
| 1598 |
iscontent = NA, |
|
| 1599 |
var = NA_character_, |
|
| 1600 |
format = NULL, |
|
| 1601 |
na_str = NA_character_, |
|
| 1602 |
indent_mod = 0L, |
|
| 1603 |
title = "", |
|
| 1604 |
subtitles = character(), |
|
| 1605 |
main_footer = character(), |
|
| 1606 |
prov_footer = character(), |
|
| 1607 |
header_section_div = NA_character_, |
|
| 1608 |
hsep = default_hsep(), |
|
| 1609 |
trailing_section_div = NA_character_, |
|
| 1610 |
inset = 0L) {
|
|
| 1611 | 3531x |
check_ok_label(label) |
| 1612 | 3531x |
if (is.null(cinfo)) {
|
| 1613 | ! |
if (length(kids) > 0) {
|
| 1614 | ! |
cinfo <- col_info(kids[[1]]) |
| 1615 |
} else {
|
|
| 1616 | ! |
cinfo <- EmptyColInfo |
| 1617 |
} |
|
| 1618 |
} |
|
| 1619 | ||
| 1620 | 3531x |
if (no_colinfo(labelrow)) {
|
| 1621 | 2152x |
col_info(labelrow) <- cinfo |
| 1622 |
} |
|
| 1623 | 3531x |
kids <- .enforce_valid_kids(kids, cinfo) |
| 1624 | 3531x |
tab <- new("ElementaryTable",
|
| 1625 | 3531x |
children = kids, |
| 1626 | 3531x |
name = .chkname(name), |
| 1627 | 3531x |
level = lev, |
| 1628 | 3531x |
labelrow = labelrow, |
| 1629 | 3531x |
rowspans = rspans, |
| 1630 | 3531x |
col_info = cinfo, |
| 1631 | 3531x |
var_analyzed = var, |
| 1632 |
## XXX these are hardcoded, because they both get set during |
|
| 1633 |
## set_format_recursive anyway |
|
| 1634 | 3531x |
format = NULL, |
| 1635 | 3531x |
na_str = NA_character_, |
| 1636 | 3531x |
table_inset = 0L, |
| 1637 | 3531x |
indent_modifier = as.integer(indent_mod), |
| 1638 | 3531x |
main_title = title, |
| 1639 | 3531x |
subtitles = subtitles, |
| 1640 | 3531x |
main_footer = main_footer, |
| 1641 | 3531x |
provenance_footer = prov_footer, |
| 1642 | 3531x |
horizontal_sep = hsep, |
| 1643 | 3531x |
header_section_div = header_section_div, |
| 1644 | 3531x |
trailing_section_div = trailing_section_div |
| 1645 |
) |
|
| 1646 | 3531x |
tab <- set_format_recursive(tab, format, na_str, FALSE) |
| 1647 | 3531x |
table_inset(tab) <- as.integer(inset) |
| 1648 | 3531x |
tab |
| 1649 |
} |
|
| 1650 | ||
| 1651 |
ttable_validity <- function(object) {
|
|
| 1652 | ! |
all(sapply( |
| 1653 | ! |
tree_children(object), |
| 1654 | ! |
function(x) is(x, "VTableTree") || is(x, "TableRow") |
| 1655 |
)) |
|
| 1656 |
} |
|
| 1657 | ||
| 1658 |
.calc_cinfo <- function(cinfo, cont, kids) {
|
|
| 1659 | 3295x |
if (!is.null(cinfo)) {
|
| 1660 | 3295x |
cinfo |
| 1661 | ! |
} else if (!is.null(cont)) {
|
| 1662 | ! |
col_info(cont) |
| 1663 | ! |
} else if (length(kids) >= 1) {
|
| 1664 | ! |
col_info(kids[[1]]) |
| 1665 |
} else {
|
|
| 1666 | ! |
EmptyColInfo |
| 1667 |
} |
|
| 1668 |
} |
|
| 1669 | ||
| 1670 |
## under this model, non-leaf nodes can have a content table where rollup |
|
| 1671 |
## analyses live |
|
| 1672 |
#' @exportClass TableTree |
|
| 1673 |
#' @rdname tabclasses |
|
| 1674 |
setClass("TableTree",
|
|
| 1675 |
contains = c("VTableTree"),
|
|
| 1676 |
representation( |
|
| 1677 |
content = "ElementaryTable", |
|
| 1678 |
page_title_prefix = "character" |
|
| 1679 |
), |
|
| 1680 |
validity = ttable_validity |
|
| 1681 |
) |
|
| 1682 | ||
| 1683 |
#' @export |
|
| 1684 |
#' @rdname tabclasses |
|
| 1685 |
TableTree <- function(kids = list(), |
|
| 1686 |
name = if (!is.na(var)) var else "", |
|
| 1687 |
cont = EmptyElTable, |
|
| 1688 |
lev = 1L, |
|
| 1689 |
label = name, |
|
| 1690 |
labelrow = LabelRow( |
|
| 1691 |
lev = lev, |
|
| 1692 |
label = label, |
|
| 1693 |
vis = nrow(cont) == 0 && !is.na(label) && |
|
| 1694 |
nzchar(label) |
|
| 1695 |
), |
|
| 1696 |
rspans = data.frame(), |
|
| 1697 |
iscontent = NA, |
|
| 1698 |
var = NA_character_, |
|
| 1699 |
cinfo = NULL, |
|
| 1700 |
format = NULL, |
|
| 1701 |
na_str = NA_character_, |
|
| 1702 |
indent_mod = 0L, |
|
| 1703 |
title = "", |
|
| 1704 |
subtitles = character(), |
|
| 1705 |
main_footer = character(), |
|
| 1706 |
prov_footer = character(), |
|
| 1707 |
page_title = NA_character_, |
|
| 1708 |
hsep = default_hsep(), |
|
| 1709 |
header_section_div = NA_character_, |
|
| 1710 |
trailing_section_div = NA_character_, |
|
| 1711 |
inset = 0L) {
|
|
| 1712 | 3295x |
check_ok_label(label) |
| 1713 | 3295x |
cinfo <- .calc_cinfo(cinfo, cont, kids) |
| 1714 | ||
| 1715 | 3295x |
kids <- .enforce_valid_kids(kids, cinfo) |
| 1716 | 3295x |
if (isTRUE(iscontent) && !is.null(cont) && nrow(cont) > 0) {
|
| 1717 | ! |
stop("Got table tree with content table and content position")
|
| 1718 |
} |
|
| 1719 | 3295x |
if (no_colinfo(labelrow)) {
|
| 1720 | 1884x |
col_info(labelrow) <- cinfo |
| 1721 |
} |
|
| 1722 | 3295x |
if ((is.null(cont) || nrow(cont) == 0) && all(sapply(kids, is, "DataRow"))) {
|
| 1723 | 1362x |
if (!is.na(page_title)) {
|
| 1724 | ! |
stop("Got a page title prefix for an Elementary Table")
|
| 1725 |
} |
|
| 1726 |
## constructor takes care of recursive format application |
|
| 1727 | 1362x |
ElementaryTable( |
| 1728 | 1362x |
kids = kids, |
| 1729 | 1362x |
name = .chkname(name), |
| 1730 | 1362x |
lev = lev, |
| 1731 | 1362x |
labelrow = labelrow, |
| 1732 | 1362x |
rspans = rspans, |
| 1733 | 1362x |
cinfo = cinfo, |
| 1734 | 1362x |
var = var, |
| 1735 | 1362x |
format = format, |
| 1736 | 1362x |
na_str = na_str, |
| 1737 | 1362x |
indent_mod = indent_mod, |
| 1738 | 1362x |
title = title, |
| 1739 | 1362x |
subtitles = subtitles, |
| 1740 | 1362x |
main_footer = main_footer, |
| 1741 | 1362x |
prov_footer = prov_footer, |
| 1742 | 1362x |
hsep = hsep, |
| 1743 | 1362x |
header_section_div = header_section_div, |
| 1744 | 1362x |
trailing_section_div = trailing_section_div, |
| 1745 | 1362x |
inset = inset |
| 1746 |
) |
|
| 1747 |
} else {
|
|
| 1748 | 1933x |
tab <- new("TableTree",
|
| 1749 | 1933x |
content = cont, |
| 1750 | 1933x |
children = kids, |
| 1751 | 1933x |
name = .chkname(name), |
| 1752 | 1933x |
level = lev, |
| 1753 | 1933x |
labelrow = labelrow, |
| 1754 | 1933x |
rowspans = rspans, |
| 1755 | 1933x |
col_info = cinfo, |
| 1756 | 1933x |
format = NULL, |
| 1757 | 1933x |
na_str = na_str, |
| 1758 | 1933x |
table_inset = 0L, |
| 1759 | 1933x |
indent_modifier = as.integer(indent_mod), |
| 1760 | 1933x |
main_title = title, |
| 1761 | 1933x |
subtitles = subtitles, |
| 1762 | 1933x |
main_footer = main_footer, |
| 1763 | 1933x |
provenance_footer = prov_footer, |
| 1764 | 1933x |
page_title_prefix = page_title, |
| 1765 | 1933x |
horizontal_sep = "-", |
| 1766 | 1933x |
header_section_div = header_section_div, |
| 1767 | 1933x |
trailing_section_div = trailing_section_div |
| 1768 | 1933x |
) ## this is overridden below to get recursiveness |
| 1769 | 1933x |
tab <- set_format_recursive(tab, format, na_str, FALSE) |
| 1770 | ||
| 1771 |
## these is recursive |
|
| 1772 |
## XXX combine these probably |
|
| 1773 | 1933x |
horizontal_sep(tab) <- hsep |
| 1774 | 1933x |
table_inset(tab) <- as.integer(inset) |
| 1775 | 1933x |
tab |
| 1776 |
} |
|
| 1777 |
} |
|
| 1778 | ||
| 1779 |
### Pre-Data Layout Declaration Classes |
|
| 1780 |
### |
|
| 1781 |
### Notably these are NOT represented as trees |
|
| 1782 |
### because without data we cannot know what the |
|
| 1783 |
### children should be. |
|
| 1784 | ||
| 1785 |
## Vector (ordered list) of splits. |
|
| 1786 |
## |
|
| 1787 |
## This is a vector (ordered list) of splits to be |
|
| 1788 |
## applied recursively to the data when provided. |
|
| 1789 |
## |
|
| 1790 |
## For convenience, if this is length 1, it can contain |
|
| 1791 |
## a pre-existing TableTree/ElementaryTable. |
|
| 1792 |
## This is used for add_existing_table in colby_constructors.R |
|
| 1793 | ||
| 1794 |
setClass("SplitVector",
|
|
| 1795 |
contains = "list", |
|
| 1796 |
validity = function(object) {
|
|
| 1797 |
if (length(object) >= 1) {
|
|
| 1798 |
lst <- tail(object, 1)[[1]] |
|
| 1799 |
} else {
|
|
| 1800 |
lst <- NULL |
|
| 1801 |
} |
|
| 1802 |
all(sapply(head(object, -1), is, "Split")) && |
|
| 1803 |
(is.null(lst) || is(lst, "Split") || is(lst, "VTableNodeInfo")) |
|
| 1804 |
} |
|
| 1805 |
) |
|
| 1806 | ||
| 1807 |
SplitVector <- function(x = NULL, |
|
| 1808 |
..., |
|
| 1809 |
lst = list(...)) {
|
|
| 1810 | 2826x |
if (!is.null(x)) {
|
| 1811 | 528x |
lst <- unlist(c(list(x), lst), recursive = FALSE) |
| 1812 |
} |
|
| 1813 | 2826x |
new("SplitVector", lst)
|
| 1814 |
} |
|
| 1815 | ||
| 1816 |
avar_noneorlast <- function(vec) {
|
|
| 1817 | 1142x |
if (!is(vec, "SplitVector")) {
|
| 1818 | ! |
return(FALSE) |
| 1819 |
} |
|
| 1820 | 1142x |
if (length(vec) == 0) {
|
| 1821 | 738x |
return(TRUE) |
| 1822 |
} |
|
| 1823 | 404x |
isavar <- which(sapply(vec, is, "AnalyzeVarSplit")) |
| 1824 | 404x |
(length(isavar) == 0) || (length(isavar) == 1 && isavar == length(vec)) |
| 1825 |
} |
|
| 1826 | ||
| 1827 |
setClass("PreDataAxisLayout",
|
|
| 1828 |
contains = "list", |
|
| 1829 |
representation(root_split = "ANY"), |
|
| 1830 |
validity = function(object) {
|
|
| 1831 |
allleafs <- unlist(object, recursive = TRUE) |
|
| 1832 |
all(sapply(object, avar_noneorlast)) && |
|
| 1833 |
all(sapply( |
|
| 1834 |
allleafs, |
|
| 1835 |
## remember existing table trees can be added to layouts |
|
| 1836 |
## for now... |
|
| 1837 |
function(x) is(x, "Split") || is(x, "VTableTree") |
|
| 1838 |
)) |
|
| 1839 |
} |
|
| 1840 |
) |
|
| 1841 | ||
| 1842 |
setClass("PreDataColLayout",
|
|
| 1843 |
contains = "PreDataAxisLayout", |
|
| 1844 |
representation( |
|
| 1845 |
display_columncounts = "logical", |
|
| 1846 |
columncount_format = "FormatSpec" # "character" |
|
| 1847 |
) |
|
| 1848 |
) |
|
| 1849 | ||
| 1850 |
setClass("PreDataRowLayout", contains = "PreDataAxisLayout")
|
|
| 1851 | ||
| 1852 |
PreDataColLayout <- function(x = SplitVector(), |
|
| 1853 |
rtsp = RootSplit(), |
|
| 1854 |
..., |
|
| 1855 |
lst = list(x, ...), |
|
| 1856 |
disp_colcounts = NA, |
|
| 1857 |
colcount_format = "(N=xx)") {
|
|
| 1858 | 364x |
ret <- new("PreDataColLayout", lst,
|
| 1859 | 364x |
display_columncounts = disp_colcounts, |
| 1860 | 364x |
columncount_format = colcount_format |
| 1861 |
) |
|
| 1862 | 364x |
ret@root_split <- rtsp |
| 1863 | 364x |
ret |
| 1864 |
} |
|
| 1865 | ||
| 1866 |
PreDataRowLayout <- function(x = SplitVector(), |
|
| 1867 |
root = RootSplit(), |
|
| 1868 |
..., |
|
| 1869 |
lst = list(x, ...)) {
|
|
| 1870 | 741x |
new("PreDataRowLayout", lst, root_split = root)
|
| 1871 |
} |
|
| 1872 | ||
| 1873 |
setClass("PreDataTableLayouts",
|
|
| 1874 |
contains = "VTitleFooter", |
|
| 1875 |
representation( |
|
| 1876 |
row_layout = "PreDataRowLayout", |
|
| 1877 |
col_layout = "PreDataColLayout", |
|
| 1878 |
top_left = "character", |
|
| 1879 |
header_section_div = "character", |
|
| 1880 |
top_level_section_div = "character", |
|
| 1881 |
table_inset = "integer" |
|
| 1882 |
) |
|
| 1883 |
) |
|
| 1884 | ||
| 1885 |
PreDataTableLayouts <- function(rlayout = PreDataRowLayout(), |
|
| 1886 |
clayout = PreDataColLayout(), |
|
| 1887 |
topleft = character(), |
|
| 1888 |
title = "", |
|
| 1889 |
subtitles = character(), |
|
| 1890 |
main_footer = character(), |
|
| 1891 |
prov_footer = character(), |
|
| 1892 |
header_section_div = NA_character_, |
|
| 1893 |
top_level_section_div = NA_character_, |
|
| 1894 |
table_inset = 0L) {
|
|
| 1895 | 364x |
new("PreDataTableLayouts",
|
| 1896 | 364x |
row_layout = rlayout, |
| 1897 | 364x |
col_layout = clayout, |
| 1898 | 364x |
top_left = topleft, |
| 1899 | 364x |
main_title = title, |
| 1900 | 364x |
subtitles = subtitles, |
| 1901 | 364x |
main_footer = main_footer, |
| 1902 | 364x |
provenance_footer = prov_footer, |
| 1903 | 364x |
header_section_div = header_section_div, |
| 1904 | 364x |
top_level_section_div = top_level_section_div, |
| 1905 | 364x |
table_inset = table_inset |
| 1906 |
) |
|
| 1907 |
} |
|
| 1908 | ||
| 1909 |
## setClass("CellValue", contains = "ValueWrapper",
|
|
| 1910 |
## representation(format = "FormatSpec", |
|
| 1911 |
## colspan = "integerOrNULL", |
|
| 1912 |
## label = "characterOrNULL"), |
|
| 1913 |
## prototype = list(label ="", colspan = NULL, format = NULL)) |
|
| 1914 | ||
| 1915 |
setOldClass("CellValue")
|
|
| 1916 | ||
| 1917 |
#' Length of a Cell value |
|
| 1918 |
#' |
|
| 1919 |
#' @param x (`CellValue`)\cr a `CellValue` object. |
|
| 1920 |
#' |
|
| 1921 |
#' @return Always returns `1L`. |
|
| 1922 |
#' |
|
| 1923 |
#' @exportMethod length |
|
| 1924 |
setMethod( |
|
| 1925 |
"length", "CellValue", |
|
| 1926 | ! |
function(x) 1L |
| 1927 |
) |
|
| 1928 | ||
| 1929 |
setClass("RefFootnote", representation(
|
|
| 1930 |
value = "character", |
|
| 1931 |
index = "integer", |
|
| 1932 |
symbol = "character" |
|
| 1933 |
)) |
|
| 1934 | ||
| 1935 |
RefFootnote <- function(note, index = NA_integer_, symbol = NA_character_) {
|
|
| 1936 | 56x |
if (is(note, "RefFootnote")) {
|
| 1937 | 28x |
return(note) |
| 1938 | 28x |
} else if (length(note) == 0) {
|
| 1939 | ! |
return(NULL) |
| 1940 |
} |
|
| 1941 | 28x |
if (length(symbol) != 1L) {
|
| 1942 | ! |
stop( |
| 1943 | ! |
"Referential footnote can only have a single string as its index.", |
| 1944 | ! |
" Got char vector of length ", length(index) |
| 1945 |
) |
|
| 1946 |
} |
|
| 1947 | 28x |
if (!is.na(symbol) && (index == "NA" || grepl("[{}]", index))) {
|
| 1948 | ! |
stop( |
| 1949 | ! |
"The string 'NA' and strings containing '{' or '}' cannot be used as ",
|
| 1950 | ! |
"referential footnote index symbols. Got string '", index, "'." |
| 1951 |
) |
|
| 1952 |
} |
|
| 1953 | ||
| 1954 | 28x |
new("RefFootnote", value = note, index = index, symbol = symbol)
|
| 1955 |
} |
|
| 1956 | ||
| 1957 |
#' Constructor for Cell Value |
|
| 1958 |
#' |
|
| 1959 |
#' @inheritParams lyt_args |
|
| 1960 |
#' @inheritParams rcell |
|
| 1961 |
#' @param val (`ANY`)\cr value in the cell exactly as it should be passed to a formatter or returned when extracted. |
|
| 1962 |
#' |
|
| 1963 |
#' @return An object representing the value within a single cell within a populated table. The underlying structure |
|
| 1964 |
#' of this object is an implementation detail and should not be relied upon beyond calling accessors for the class. |
|
| 1965 |
#' |
|
| 1966 |
#' @export |
|
| 1967 | ||
| 1968 |
## Class definition |
|
| 1969 |
## [[1]] list: cell value |
|
| 1970 |
## format : format for cell |
|
| 1971 |
## colspan: column span info for cell |
|
| 1972 |
## label: row label to be used for parent row |
|
| 1973 |
## indent_mod: indent modifier to be used for parent row |
|
| 1974 |
CellValue <- function(val, format = NULL, colspan = 1L, label = NULL, |
|
| 1975 |
indent_mod = NULL, footnotes = NULL, |
|
| 1976 |
align = NULL, format_na_str = NULL, stat_names = NA_character_) {
|
|
| 1977 | 14336x |
if (is.null(colspan)) {
|
| 1978 | ! |
colspan <- 1L |
| 1979 |
} |
|
| 1980 | 14336x |
if (!is.null(colspan) && !is(colspan, "integer")) {
|
| 1981 | 10x |
colspan <- as.integer(colspan) |
| 1982 |
} |
|
| 1983 |
## if we're not given a label but the value has one associated with |
|
| 1984 |
## it we use that. |
|
| 1985 |
## NB: we need to be able to override a non-empty label with an empty one |
|
| 1986 |
## so we can't have "" mean "not given a label" here |
|
| 1987 | 14336x |
if ((is.null(label) || is.na(label)) && !is.null(obj_label(val))) {
|
| 1988 | 2x |
label <- obj_label(val) |
| 1989 |
} |
|
| 1990 | 14336x |
if (!is.list(footnotes)) {
|
| 1991 | 9x |
footnotes <- lapply(footnotes, RefFootnote) |
| 1992 |
} |
|
| 1993 | 14336x |
check_ok_label(label) |
| 1994 | 14336x |
ret <- structure(list(val), |
| 1995 | 14336x |
format = format, colspan = colspan, |
| 1996 | 14336x |
label = label, |
| 1997 | 14336x |
indent_mod = indent_mod, footnotes = footnotes, |
| 1998 | 14336x |
align = align, |
| 1999 | 14336x |
format_na_str = format_na_str, |
| 2000 | 14336x |
stat_names = stat_names, |
| 2001 | 14336x |
class = "CellValue" |
| 2002 |
) |
|
| 2003 | 14336x |
ret |
| 2004 |
} |
|
| 2005 | ||
| 2006 |
#' @method print CellValue |
|
| 2007 |
#' |
|
| 2008 |
#' @export |
|
| 2009 |
print.CellValue <- function(x, ...) {
|
|
| 2010 | ! |
cat(paste("rcell:", format_rcell(x), "\n"))
|
| 2011 | ! |
invisible(x) |
| 2012 |
} |
|
| 2013 | ||
| 2014 |
## too slow |
|
| 2015 |
# setClass("RowsVerticalSection", contains = "list",
|
|
| 2016 |
# representation = list(row_names = "characterOrNULL", |
|
| 2017 |
# row_labels = "characterOrNULL", |
|
| 2018 |
# row_formats = "ANY", |
|
| 2019 |
# indent_mods = "integerOrNULL")) |
|
| 2020 | ||
| 2021 |
setOldClass("RowsVerticalSection")
|
|
| 2022 |
RowsVerticalSection <- function(values, |
|
| 2023 |
names = names(values), |
|
| 2024 |
labels = NULL, |
|
| 2025 |
indent_mods = NULL, |
|
| 2026 |
formats = NULL, |
|
| 2027 |
footnotes = NULL, |
|
| 2028 |
format_na_strs = NULL) {
|
|
| 2029 | 6614x |
stopifnot(is(values, "list")) |
| 2030 |
## innernms <- value_names(values) |
|
| 2031 | ||
| 2032 | 6614x |
if (is.null(labels)) {
|
| 2033 | 3179x |
labels <- names(values) |
| 2034 |
} |
|
| 2035 | 6614x |
if (is.null(names) && all(nzchar(labels))) {
|
| 2036 | 3561x |
names <- labels |
| 2037 | 3053x |
} else if (is.null(labels) && !is.null(names)) {
|
| 2038 | 15x |
labels <- names |
| 2039 |
} |
|
| 2040 | ||
| 2041 | 6614x |
if (!is.null(indent_mods)) {
|
| 2042 | 68x |
indent_mods <- as.integer(indent_mods) |
| 2043 |
} |
|
| 2044 | 6614x |
check_ok_label(labels, multi_ok = TRUE) |
| 2045 | 6613x |
structure(values, |
| 2046 | 6613x |
class = "RowsVerticalSection", row_names = names, |
| 2047 | 6613x |
row_labels = labels, indent_mods = indent_mods, |
| 2048 | 6613x |
row_formats = formats, |
| 2049 | 6613x |
row_na_strs = format_na_strs, |
| 2050 | 6613x |
row_footnotes = lapply( |
| 2051 | 6613x |
footnotes, |
| 2052 |
## cause each row needs to accept |
|
| 2053 |
## a *list* of row footnotes |
|
| 2054 | 6613x |
function(fns) lapply(fns, RefFootnote) |
| 2055 |
) |
|
| 2056 |
) |
|
| 2057 |
} |
|
| 2058 | ||
| 2059 |
#' @method print RowsVerticalSection |
|
| 2060 |
#' |
|
| 2061 |
#' @export |
|
| 2062 |
print.RowsVerticalSection <- function(x, ...) {
|
|
| 2063 | 1x |
cat("RowsVerticalSection (in_rows) object print method:\n-------------------",
|
| 2064 | 1x |
"---------\n", |
| 2065 | 1x |
sep = "" |
| 2066 |
) |
|
| 2067 | 1x |
print(data.frame( |
| 2068 | 1x |
row_name = attr(x, "row_names", exact = TRUE), |
| 2069 | 1x |
formatted_cell = vapply(x, format_rcell, character(1)), |
| 2070 | 1x |
indent_mod = indent_mod(x), ## vapply(x, indent_mod, numeric(1)), |
| 2071 | 1x |
row_label = attr(x, "row_labels", exact = TRUE), |
| 2072 | 1x |
stringsAsFactors = FALSE, |
| 2073 | 1x |
row.names = NULL |
| 2074 | 1x |
), row.names = TRUE) |
| 2075 | 1x |
invisible(x) |
| 2076 |
} |
|
| 2077 | ||
| 2078 |
#### Empty default objects to avoid repeated calls |
|
| 2079 |
## EmptyColInfo <- InstantiatedColumnInfo() |
|
| 2080 |
## EmptyElTable <- ElementaryTable() |
|
| 2081 |
## EmptyRootSplit <- RootSplit() |
|
| 2082 |
## EmptyAllSplit <- AllSplit() |
| 1 |
do_recursive_replace <- function(tab, path, incontent = FALSE, value) { ## rows = NULL,
|
|
| 2 |
## cols = NULL, value) {
|
|
| 3 |
## don't want this in the recursive function |
|
| 4 |
## so thats why we have the do_ variant |
|
| 5 | 285x |
if (is.character(path) && length(path) > 1) {
|
| 6 | 212x |
path <- as.list(path) |
| 7 |
} |
|
| 8 | 285x |
if (length(path) > 0 && path[[1]] == obj_name(tab)) {
|
| 9 | 144x |
path <- path[-1] |
| 10 |
} |
|
| 11 | 285x |
recursive_replace(tab, path, value) ## incontent, rows, cols,value) |
| 12 |
} |
|
| 13 | ||
| 14 |
## different cases we want to support: |
|
| 15 |
## 1. Replace entire children for a particular node/position in the tree |
|
| 16 |
## 2. Replace entire rows at a particular (ElementaryTable) position within the |
|
| 17 |
## tree |
|
| 18 |
## 3. Replace specific cell values within a set of row x column positions within |
|
| 19 |
## an ElementaryTable at a particular position within the tree |
|
| 20 |
## 3. replace entire content table at a node position |
|
| 21 |
## 4. replace entire rows within the content table at a particular node position |
|
| 22 |
## in the tree |
|
| 23 |
## 5. replace data cell values for specific row/col positions within the content |
|
| 24 |
## table at a particular position within the tree |
|
| 25 | ||
| 26 |
## XXX This is wrong, what happens if a split (or more accurately, value) |
|
| 27 |
## happens more than once in the overall tree??? |
|
| 28 |
recursive_replace <- function(tab, path, value) { ## incontent = FALSE, rows = NULL, cols = NULL, value) {
|
|
| 29 | 1176x |
if (length(path) == 0) { ## done recursing
|
| 30 |
## if(is.null(rows) && is.null(cols)) { ## replacing whole subtree a this position
|
|
| 31 |
## if(incontent) {
|
|
| 32 |
## newkid = tab |
|
| 33 |
## content_table(newkid) = value |
|
| 34 |
## } else |
|
| 35 | 313x |
newkid <- value |
| 36 |
## newkid has either thee content table |
|
| 37 |
## replaced on the old kid or is the new |
|
| 38 |
## kid |
|
| 39 |
# } ## else { ## rows or cols (or both) non-null
|
|
| 40 |
## if(incontent) {
|
|
| 41 |
## ctab = content_table(tab) |
|
| 42 |
## ctab[rows, cols] = value |
|
| 43 |
## content_table(tab) = ctab |
|
| 44 |
## newkid = tab |
|
| 45 | ||
| 46 |
## } else {
|
|
| 47 |
## allkids = tree_children(tab) |
|
| 48 |
## stopifnot(are(allkids, "TableRow")) |
|
| 49 |
## newkid = tab |
|
| 50 |
## newkid[rows, cols] = value |
|
| 51 |
## } |
|
| 52 |
## } |
|
| 53 | 313x |
newkid |
| 54 | 863x |
} else if (path[[1]] == "@content") {
|
| 55 | 25x |
ctb <- content_table(tab) |
| 56 | 25x |
ctb <- recursive_replace(ctb, |
| 57 | 25x |
path = path[-1], |
| 58 |
## rows = rows, |
|
| 59 |
## cols = cols, |
|
| 60 | 25x |
value = value |
| 61 |
) |
|
| 62 | 25x |
content_table(tab) <- ctb |
| 63 | 25x |
tab |
| 64 |
} else { ## length(path) > 1, more recursing to do
|
|
| 65 | 838x |
kidel <- path[[1]] |
| 66 |
## broken up for debugabiliity, could be a single complex |
|
| 67 |
## expression |
|
| 68 |
## for now only the last step supports selecting |
|
| 69 |
## multiple kids |
|
| 70 | 838x |
stopifnot( |
| 71 | 838x |
length(kidel) == 1, |
| 72 | 838x |
is.character(kidel) || is.factor(kidel) |
| 73 |
) |
|
| 74 | 838x |
knms <- names(tree_children(tab)) |
| 75 | 838x |
if (!(kidel %in% knms)) {
|
| 76 | ! |
stop(sprintf("position element %s not in names of next level children", kidel))
|
| 77 | 838x |
} else if (sum(kidel == knms) > 1) {
|
| 78 | ! |
stop(sprintf("position element %s appears more than once, not currently supported", kidel))
|
| 79 |
} |
|
| 80 | ! |
if (is.factor(kidel)) kidel <- levels(kidel)[kidel] |
| 81 | 838x |
newkid <- recursive_replace( |
| 82 | 838x |
tree_children(tab)[[kidel]], |
| 83 | 838x |
path[-1], |
| 84 |
## incontent = incontent, |
|
| 85 |
## rows = rows, |
|
| 86 |
## cols = cols, |
|
| 87 | 838x |
value |
| 88 |
) |
|
| 89 | 838x |
tree_children(tab)[[kidel]] <- newkid |
| 90 | 838x |
tab |
| 91 |
} |
|
| 92 |
} |
|
| 93 | ||
| 94 | 1x |
coltree_split <- function(ctree) ctree@split |
| 95 | ||
| 96 |
col_fnotes_at_path <- function(ctree, path, fnotes) {
|
|
| 97 | 2x |
if (length(path) == 0) {
|
| 98 | 1x |
col_footnotes(ctree) <- fnotes |
| 99 | 1x |
return(ctree) |
| 100 |
} |
|
| 101 | ||
| 102 | 1x |
if (identical(path[1], obj_name(coltree_split(ctree)))) {
|
| 103 | 1x |
path <- path[-1] |
| 104 |
} else {
|
|
| 105 | ! |
stop(paste("Path appears invalid at step:", path[1]))
|
| 106 |
} |
|
| 107 | ||
| 108 | 1x |
kids <- tree_children(ctree) |
| 109 | 1x |
kidel <- path[[1]] |
| 110 | 1x |
knms <- names(kids) |
| 111 | 1x |
stopifnot(kidel %in% knms) |
| 112 | 1x |
newkid <- col_fnotes_at_path(kids[[kidel]], |
| 113 | 1x |
path[-1], |
| 114 | 1x |
fnotes = fnotes |
| 115 |
) |
|
| 116 | 1x |
kids[[kidel]] <- newkid |
| 117 | 1x |
tree_children(ctree) <- kids |
| 118 | 1x |
ctree |
| 119 |
} |
|
| 120 | ||
| 121 |
#' Insert row at path |
|
| 122 |
#' |
|
| 123 |
#' Insert a row into an existing table directly before or directly after an existing data (i.e., non-content and |
|
| 124 |
#' non-label) row, specified by its path. |
|
| 125 |
#' |
|
| 126 |
#' @inheritParams gen_args |
|
| 127 |
#' @param after (`flag`)\cr whether `value` should be added as a row directly before (`FALSE`, the default) or after |
|
| 128 |
#' (`TRUE`) the row specified by `path`. |
|
| 129 |
#' |
|
| 130 |
#' @seealso [DataRow()], [rrow()] |
|
| 131 |
#' |
|
| 132 |
#' @examples |
|
| 133 |
#' lyt <- basic_table() %>% |
|
| 134 |
#' split_rows_by("COUNTRY", split_fun = keep_split_levels(c("CHN", "USA"))) %>%
|
|
| 135 |
#' analyze("AGE")
|
|
| 136 |
#' |
|
| 137 |
#' tbl <- build_table(lyt, DM) |
|
| 138 |
#' |
|
| 139 |
#' tbl2 <- insert_row_at_path( |
|
| 140 |
#' tbl, c("COUNTRY", "CHN", "AGE", "Mean"),
|
|
| 141 |
#' rrow("new row", 555)
|
|
| 142 |
#' ) |
|
| 143 |
#' tbl2 |
|
| 144 |
#' |
|
| 145 |
#' tbl3 <- insert_row_at_path(tbl2, c("COUNTRY", "CHN", "AGE", "Mean"),
|
|
| 146 |
#' rrow("new row redux", 888),
|
|
| 147 |
#' after = TRUE |
|
| 148 |
#' ) |
|
| 149 |
#' tbl3 |
|
| 150 |
#' |
|
| 151 |
#' @export |
|
| 152 |
setGeneric("insert_row_at_path",
|
|
| 153 |
signature = c("tt", "value"),
|
|
| 154 |
function(tt, path, value, after = FALSE) {
|
|
| 155 | 6x |
standardGeneric("insert_row_at_path")
|
| 156 |
} |
|
| 157 |
) |
|
| 158 | ||
| 159 |
#' @rdname insert_row_at_path |
|
| 160 |
setMethod( |
|
| 161 |
"insert_row_at_path", c("VTableTree", "DataRow"),
|
|
| 162 |
function(tt, path, value, after = FALSE) {
|
|
| 163 | 6x |
if (no_colinfo(value)) {
|
| 164 | 6x |
col_info(value) <- col_info(tt) |
| 165 |
} else {
|
|
| 166 | ! |
chk_compat_cinfos(tt, value) |
| 167 |
} |
|
| 168 |
## retained for debugging |
|
| 169 | 6x |
origpath <- path # nolint |
| 170 | 6x |
idx_row <- tt_at_path(tt, path) |
| 171 | 6x |
if (!is(idx_row, "DataRow")) {
|
| 172 | 4x |
stop( |
| 173 | 4x |
"path must resolve fully to a non-content data row. Insertion of ", |
| 174 | 4x |
"rows elsewhere in the tree is not currently supported." |
| 175 |
) |
|
| 176 |
} |
|
| 177 | ||
| 178 | 2x |
posnm <- tail(path, 1) |
| 179 | ||
| 180 | 2x |
path <- head(path, -1) |
| 181 | ||
| 182 | 2x |
subtt <- tt_at_path(tt, path) |
| 183 | 2x |
kids <- tree_children(subtt) |
| 184 | 2x |
ind <- which(names(kids) == posnm) |
| 185 | 2x |
if (length(ind) != 1L) {
|
| 186 |
## nocov start |
|
| 187 |
stop( |
|
| 188 |
"table children do not appear to be named correctly at this ", |
|
| 189 |
"path. This should not happen, please contact the maintainer of ", |
|
| 190 |
"rtables." |
|
| 191 |
) |
|
| 192 |
## nocov end |
|
| 193 |
} |
|
| 194 | 2x |
if (after) {
|
| 195 | 1x |
ind <- ind + 1 |
| 196 |
} |
|
| 197 | ||
| 198 | 2x |
sq <- seq_along(kids) |
| 199 | 2x |
tree_children(subtt) <- c( |
| 200 | 2x |
kids[sq < ind], |
| 201 | 2x |
setNames(list(value), obj_name(value)), |
| 202 | 2x |
kids[sq >= ind] |
| 203 |
) |
|
| 204 | 2x |
tt_at_path(tt, path) <- subtt |
| 205 | 2x |
tt |
| 206 |
} |
|
| 207 |
) |
|
| 208 | ||
| 209 |
# nocov start |
|
| 210 |
#' @rdname insert_row_at_path |
|
| 211 |
setMethod( |
|
| 212 |
"insert_row_at_path", c("VTableTree", "ANY"),
|
|
| 213 |
function(tt, path, value) {
|
|
| 214 |
stop( |
|
| 215 |
"Currently only insertion of DataRow objects is supported. Got ", |
|
| 216 |
"object of class ", class(value), ". Please use rrow() or DataRow() ", |
|
| 217 |
"to construct your row before insertion." |
|
| 218 |
) |
|
| 219 |
} |
|
| 220 |
) |
|
| 221 | ||
| 222 |
# nocov end |
|
| 223 | ||
| 224 |
#' Label at path |
|
| 225 |
#' |
|
| 226 |
#' Accesses or sets the label at a path. |
|
| 227 |
#' |
|
| 228 |
#' @inheritParams gen_args |
|
| 229 |
#' |
|
| 230 |
#' @details |
|
| 231 |
#' If `path` resolves to a single row, the label for that row is retrieved or set. If, instead, `path` resolves to a |
|
| 232 |
#' subtable, the text for the row-label associated with that path is retrieved or set. In the subtable case, if the |
|
| 233 |
#' label text is set to a non-`NA` value, the `labelrow` will be set to visible, even if it was not before. Similarly, |
|
| 234 |
#' if the label row text for a subtable is set to `NA`, the label row will bet set to non-visible, so the row will not |
|
| 235 |
#' appear at all when the table is printed. |
|
| 236 |
#' |
|
| 237 |
#' @note When changing the row labels for content rows, it is important to path all the way to the *row*. Paths |
|
| 238 |
#' ending in `"@content"` will not exhibit the behavior you want, and are thus an error. See [row_paths()] for help |
|
| 239 |
#' determining the full paths to content rows. |
|
| 240 |
#' |
|
| 241 |
#' @examples |
|
| 242 |
#' lyt <- basic_table() %>% |
|
| 243 |
#' split_rows_by("COUNTRY", split_fun = keep_split_levels(c("CHN", "USA"))) %>%
|
|
| 244 |
#' analyze("AGE")
|
|
| 245 |
#' |
|
| 246 |
#' tbl <- build_table(lyt, DM) |
|
| 247 |
#' |
|
| 248 |
#' label_at_path(tbl, c("COUNTRY", "CHN"))
|
|
| 249 |
#' |
|
| 250 |
#' label_at_path(tbl, c("COUNTRY", "USA")) <- "United States"
|
|
| 251 |
#' tbl |
|
| 252 |
#' |
|
| 253 |
#' @export |
|
| 254 |
label_at_path <- function(tt, path) {
|
|
| 255 | 29x |
obj_label(tt_at_path(tt, path)) |
| 256 |
} |
|
| 257 | ||
| 258 |
#' @export |
|
| 259 |
#' @rdname label_at_path |
|
| 260 |
`label_at_path<-` <- function(tt, path, value) {
|
|
| 261 | 32x |
if (!is(tt, "VTableTree")) {
|
| 262 | ! |
stop("tt must be a TableTree or ElementaryTable object")
|
| 263 |
} |
|
| 264 | 32x |
if (is.null(value) || is.na(value)) {
|
| 265 | 1x |
value <- NA_character_ |
| 266 |
} |
|
| 267 | 32x |
subt <- tt_at_path(tt, path) |
| 268 | 32x |
obj_label(subt) <- value |
| 269 | 32x |
tt_at_path(tt, path) <- subt |
| 270 | 32x |
tt |
| 271 |
} |
|
| 272 | ||
| 273 |
#' Access or set table elements at specified path |
|
| 274 |
#' |
|
| 275 |
#' @inheritParams gen_args |
|
| 276 |
#' @param ... unused. |
|
| 277 |
#' |
|
| 278 |
#' @export |
|
| 279 |
#' @rdname ttap |
|
| 280 | 504x |
setGeneric("tt_at_path", function(tt, path, ...) standardGeneric("tt_at_path"))
|
| 281 | ||
| 282 |
#' @inheritParams tt_at_path |
|
| 283 |
#' |
|
| 284 |
#' @export |
|
| 285 |
#' @rdname int_methods |
|
| 286 |
setMethod( |
|
| 287 |
"tt_at_path", "VTableTree", |
|
| 288 |
function(tt, path, ...) {
|
|
| 289 | 504x |
stopifnot( |
| 290 | 504x |
is(path, "character"), |
| 291 | 504x |
length(path) > 0, |
| 292 | 504x |
!anyNA(path) |
| 293 |
) |
|
| 294 | ||
| 295 | 504x |
if (path[1] == "root" && obj_name(tt) != "root") {
|
| 296 | 3x |
path <- path[-1] |
| 297 |
} |
|
| 298 |
## handle pathing that hits the root split by name |
|
| 299 | 504x |
if (obj_name(tt) == path[1]) {
|
| 300 | 404x |
path <- path[-1] |
| 301 |
} |
|
| 302 | ||
| 303 |
# Extract sub-tables from the tree |
|
| 304 | 504x |
.extract_through_path(tt, path) |
| 305 |
} |
|
| 306 |
) |
|
| 307 | ||
| 308 |
# Recursive helper function to retrieve sub-tables from the tree |
|
| 309 |
## need to generalize this if we ever use it in a place other than tt_at_path |
|
| 310 |
## currently tt_at_path doesn't support "*" |
|
| 311 |
.extract_through_path <- function(cur_tbl, cur_path, no_stop = FALSE) {
|
|
| 312 | 504x |
while (length(cur_path > 0)) {
|
| 313 | 1542x |
kids <- tree_children(cur_tbl) |
| 314 | 1542x |
curname <- cur_path[1] |
| 315 | 1542x |
kids_names <- sapply(kids, obj_name) |
| 316 | 1542x |
if (curname == "@content") {
|
| 317 | 70x |
cur_tbl <- content_table(cur_tbl) |
| 318 | 1472x |
} else if (curname %in% kids_names) {
|
| 319 |
## we're now guarnateed that there will only be one match |
|
| 320 | 1468x |
cur_tbl <- kids[kids_names == curname][[1]] |
| 321 | 4x |
} else if (!no_stop && curname == "*") {
|
| 322 | 1x |
stop("Paths including '*' wildcards are not currently supported by tt_at_path.")
|
| 323 | 3x |
} else if (!no_stop) {
|
| 324 | 3x |
stop( |
| 325 | 3x |
"Path appears invalid for this tree at step '", curname, "'. Please use only", |
| 326 | 3x |
" row names and NOT row labels. You can retrieve them with `row_paths_summary()$path`." |
| 327 |
) |
|
| 328 |
} else {
|
|
| 329 | ! |
return(NULL) |
| 330 |
} |
|
| 331 | 1538x |
cur_path <- cur_path[-1] |
| 332 |
} |
|
| 333 | 500x |
cur_tbl |
| 334 |
} |
|
| 335 | ||
| 336 |
tt_type_ok <- function(obj, type) {
|
|
| 337 | 1562x |
switch(type, |
| 338 | 735x |
any = TRUE, |
| 339 | 422x |
row = is(obj, "TableRow"), |
| 340 | 305x |
table = is(obj, "VTableTree"), |
| 341 | 100x |
elemtable = is(obj, "ElementaryTable") |
| 342 |
) |
|
| 343 |
} |
|
| 344 | ||
| 345 |
#' Pathing |
|
| 346 |
#' |
|
| 347 |
#' Pathing is a method of using known structure within a table |
|
| 348 |
#' to specify elements within it in a self-describing, semantically |
|
| 349 |
#' meaningful way. |
|
| 350 |
#' |
|
| 351 |
#' @details A Path consists of a character vector of one or more elements which |
|
| 352 |
#' will be used to descend the tree structure of a table's row or column space. |
|
| 353 |
#' |
|
| 354 |
#' Existing paths will match the layout used to make the table in the form of |
|
| 355 |
#' split, split-value pairs corresponding to facets generated by `split_rows_by*` |
|
| 356 |
#' and, elementary subtables generated by `analyze`, and rows generated by the |
|
| 357 |
#' afun used. Groups summaries generated by `summarize_row_groups` are represented |
|
| 358 |
#' by the 'content table' attached to a subtable representing a facet generated |
|
| 359 |
#' by a `split_rows_by` instruction, and are addressed via `@content` instead |
|
| 360 |
#' of their name. |
|
| 361 |
#' |
|
| 362 |
#' For example, given the code |
|
| 363 |
#' \preformatted{
|
|
| 364 |
#' lyt <- basic_table() |> |
|
| 365 |
#' split_rows_by("ARM") |>
|
|
| 366 |
#' split_rows_by("RACE") |>
|
|
| 367 |
#' summarize_row_groups() |> |
|
| 368 |
#' analyze("SEX") |>
|
|
| 369 |
#' analyze("AGE", nested = FALSE)
|
|
| 370 |
#' |
|
| 371 |
#' tbl <- build_table(lyt, DM) |
|
| 372 |
#' } |
|
| 373 |
#' |
|
| 374 |
#' We know that there will be two top-level subtables, one representing |
|
| 375 |
#' (and generated via) the split on the `ARM` variable, and one |
|
| 376 |
#' generated from the non-nested analyze on `AGE`. These can be be |
|
| 377 |
#' 'pathed to' at `"ARM"` and `"AGE"`, respectively. Furthermore |
|
| 378 |
#' each value for `ARM` can be pathed to via, e.g., `c("ARM", "A: Drug X")`
|
|
| 379 |
#' or more generally using the pathing wildcard `"*"` at `c("ARM", "*")`.
|
|
| 380 |
#' |
|
| 381 |
#' A particular `SEX` analysis subtable, then, would be pathed to via the |
|
| 382 |
#' (row) path `c("ARM", "*", "RACE", "*", "SEX")`, e.g.
|
|
| 383 |
#' `c("ARM", "B: Placebo", "RACE", "ASIAN", "SEX")`. The group-summary for
|
|
| 384 |
#' Asians within the placebo group would be pathed to via |
|
| 385 |
#' `c("ARM", "B: Placebo", "RACE", "ASIAN", "@content")` for the table, and
|
|
| 386 |
#' `c("ARM", "B: Placebo", "RACE", "ASIAN", "@content", "ASIAN")` for the
|
|
| 387 |
#' row. |
|
| 388 |
#' |
|
| 389 |
#' @note some pathing-based functionality supports the "*" wildcard (typically |
|
| 390 |
#' 'setters'/functionality which alters a table and returns it) while some |
|
| 391 |
#' does not (typically 'getters' which retrieve a subtable/row from a table |
|
| 392 |
#' or some attribute of that subtable/row). |
|
| 393 |
#' |
|
| 394 |
#' @note The `"*"` wildcard will never act as `"@content"` to step into |
|
| 395 |
#' a subtable's content table; that must be specified in the path, via |
|
| 396 |
#' e.g., `c("*", "*", "@content")` instead of `c("*", "*", "*")`.
|
|
| 397 |
#' |
|
| 398 |
#' @description for `tt_row_path_exists`, tests whether a single path (potentially |
|
| 399 |
#' including `"*"` wildcards) resolves to at least one element satisfying |
|
| 400 |
#' `tt_type` (if specified). |
|
| 401 |
#' @inheritParams gen_args |
|
| 402 |
#' @return For `tt_row_path_exists`: `TRUE` if the path resolves to at least one |
|
| 403 |
#' substructure (subtable or row) that satisfies `tt_type`, or if the |
|
| 404 |
#' path is length 0; `FALSE` otherwise |
|
| 405 |
#' @export |
|
| 406 |
#' @examples |
|
| 407 |
#' lyt <- basic_table() |> |
|
| 408 |
#' split_rows_by("ARM") |>
|
|
| 409 |
#' split_rows_by("STRATA1") |>
|
|
| 410 |
#' analyze("SEX") |>
|
|
| 411 |
#' analyze("SEX", nested = FALSE)
|
|
| 412 |
#' tbl <- build_table(lyt, DM) |
|
| 413 |
#' tt_row_path_exists(tbl, c("root", "ARM", "*", "*", "*", "SEX")) # TRUE
|
|
| 414 |
#' tt_row_path_exists(tbl, c("ARM", "*", "*", "*", "SEX")) # TRUE
|
|
| 415 |
#' tt_row_path_exists(tbl, c("ARM", "*", "*", "SEX")) # FALSE
|
|
| 416 |
#' tt_row_path_exists(tbl, "FAKE") # FALSE |
|
| 417 |
#' tt_row_path_exists(tbl, c("ARM", "*", "STRATA1", "*", "SEX")) # TRUE
|
|
| 418 |
#' tt_row_path_exists(tbl, c("ARM", "*", "STRATA", "*", "SEX")) # FALSE
|
|
| 419 |
#' tt_row_path_exists(tbl, "SEX") # TRUE |
|
| 420 |
#' tt_row_path_exists(tbl, "SEX", tt_type = "table") # TRUE |
|
| 421 |
#' tt_row_path_exists(tbl, "SEX", tt_type = "elemtable") # TRUE |
|
| 422 |
#' tt_row_path_exists(tbl, "SEX", tt_type = "row") # FALSE |
|
| 423 |
#' tt_row_path_exists(tbl, c("SEX", "*")) # TRUE
|
|
| 424 |
tt_row_path_exists <- function(obj, path, tt_type = c("any", "row", "table", "elemtable")) {
|
|
| 425 | 5501x |
tt_type <- match.arg(tt_type) |
| 426 | 5501x |
if (length(path) == 0) {
|
| 427 |
## we matched everything and called it again, evaluate type condition and return answer |
|
| 428 | 1450x |
return(tt_type_ok(obj, tt_type)) |
| 429 | 4051x |
} else if (length(path) > 1 && (is.null(obj) || is(obj, "TableRow"))) {
|
| 430 |
## we got to a leaf node but still have >1 step remaining, path doesn't exist |
|
| 431 | 85x |
return(FALSE) |
| 432 |
} |
|
| 433 | ||
| 434 | 3966x |
if (path[1] == "root") {
|
| 435 | 444x |
path <- path[-1] |
| 436 |
} |
|
| 437 | 3966x |
if (length(path) > 0 && path[1] == obj_name(obj)) {
|
| 438 | ! |
path <- path[-1] ## character()[-1] is just character() again so this is ok |
| 439 |
} |
|
| 440 | ||
| 441 |
## annoying we have to do this again :-/ |
|
| 442 | 3966x |
if (length(path) == 0) {
|
| 443 |
## we matched everything and called it again, evaluate type condition and return answer |
|
| 444 | 5x |
return(tt_type_ok(obj, tt_type)) |
| 445 |
} |
|
| 446 | 3961x |
kids <- tree_children(obj) |
| 447 | 3961x |
kidnms <- vapply(kids, obj_name, "") |
| 448 | 3961x |
curpth <- path[1] |
| 449 | 3961x |
nextpth <- path[-1] |
| 450 | 3961x |
if (curpth == "*") {
|
| 451 | 1245x |
ret <- any(vapply(kids, tt_row_path_exists, path = nextpth, tt_type = tt_type, TRUE)) |
| 452 | 2716x |
} else if (curpth == "@content") {
|
| 453 | 189x |
ctab <- content_table(obj) |
| 454 | 189x |
if (NROW(ctab) == 0) {
|
| 455 | 75x |
return(FALSE) |
| 456 |
} |
|
| 457 | 114x |
ret <- tt_row_path_exists(ctab, nextpth, tt_type = tt_type) |
| 458 | 2527x |
} else if (curpth %in% kidnms) {
|
| 459 | 2442x |
ret <- tt_row_path_exists(kids[[curpth]], nextpth, tt_type = tt_type) |
| 460 |
} else {
|
|
| 461 | 85x |
ret <- FALSE |
| 462 |
} |
|
| 463 | 3886x |
ret |
| 464 |
} |
|
| 465 | ||
| 466 |
#' @rdname tt_row_path_exists |
|
| 467 |
#' @param .prev_path (`character`)\cr Internal implementation detail. |
|
| 468 |
#' Do not set manually. |
|
| 469 |
#' @description Given a path with at least one wildcard (`"*"`) in it, |
|
| 470 |
#' `tt_normalize_path` walks the tree and generates the complete |
|
| 471 |
#' set of fully specified (ie no wildcards) paths which exist in |
|
| 472 |
#' the row structure of `obj` |
|
| 473 |
#' @return for `tt_normalize_row_path`: a list of 0 or more fully |
|
| 474 |
#' specified paths which exist in the row structure of `obj` that |
|
| 475 |
#' match the original wildcard path, and which lead to an element |
|
| 476 |
#' of type `tt_type` (if specified other than `"any")`. |
|
| 477 |
#' @export |
|
| 478 |
#' @aliases pathing |
|
| 479 |
#' @examples |
|
| 480 |
#' tt_normalize_row_path(tbl, c("root", "ARM", "*", "*", "*", "SEX"))
|
|
| 481 |
#' tt_normalize_row_path(tbl, "SEX", tt_type = "row") # empty list |
|
| 482 |
tt_normalize_row_path <- function(obj, |
|
| 483 |
path, |
|
| 484 |
.prev_path = character(), |
|
| 485 |
tt_type = c("any", "row", "table", "elemtable")) {
|
|
| 486 | 430x |
if (length(path) == 0) {
|
| 487 | 98x |
return(list(.prev_path)) |
| 488 |
} |
|
| 489 | 332x |
tt_type <- match.arg(tt_type) |
| 490 | 332x |
if (!tt_row_path_exists(obj, path, tt_type = tt_type)) {
|
| 491 | 5x |
return(list()) |
| 492 |
} |
|
| 493 | 327x |
wcpos <- grep("^[*]$", path)
|
| 494 | 327x |
if (length(wcpos) == 0) {
|
| 495 | 187x |
return(list(c(.prev_path, path))) |
| 496 |
} |
|
| 497 | ||
| 498 | 140x |
befwc <- path[seq_len(wcpos[1] - 1)] |
| 499 | 140x |
if (length(befwc) > 0) {
|
| 500 | 25x |
subtbl <- tt_at_path(obj, befwc) |
| 501 |
} else {
|
|
| 502 | 115x |
subtbl <- obj |
| 503 |
} |
|
| 504 | 140x |
kids <- tree_children(subtbl) |
| 505 | ||
| 506 | 140x |
nextstps <- names(kids) |
| 507 | 140x |
aftrwc <- tail(path, -1 * wcpos[1]) |
| 508 | 140x |
if (tt_type != "any") {
|
| 509 | 6x |
if (length(aftrwc) > 0) {
|
| 510 | 6x |
keep <- vapply(nextstps, function(nm) tt_row_path_exists(kids[[nm]], aftrwc, tt_type = tt_type), TRUE) |
| 511 |
} else {
|
|
| 512 | ! |
keep <- vapply(kids, tt_type_ok, tt_type = tt_type, TRUE) |
| 513 |
} |
|
| 514 | 6x |
nextstps <- nextstps[keep] |
| 515 | 6x |
if (length(nextstps) == 0) {
|
| 516 | ! |
return(list()) |
| 517 |
} |
|
| 518 | 6x |
kids <- kids[keep] |
| 519 |
} |
|
| 520 | 140x |
unlist( |
| 521 | 140x |
recursive = FALSE, |
| 522 | 140x |
lapply( |
| 523 | 140x |
kids, |
| 524 | 140x |
function(kdi) {
|
| 525 | 243x |
tt_normalize_row_path(kdi, path = aftrwc, .prev_path = c(.prev_path, befwc, obj_name(kdi))) |
| 526 |
} |
|
| 527 |
) |
|
| 528 |
) |
|
| 529 |
} |
|
| 530 | ||
| 531 |
## XXX TODO some other day tt_normalize_col_path |
|
| 532 | ||
| 533 |
#' @note Setting `NULL` at a defined path removes the corresponding sub-table. |
|
| 534 |
#' |
|
| 535 |
#' @examples |
|
| 536 |
#' # Accessing sub table. |
|
| 537 |
#' lyt <- basic_table() %>% |
|
| 538 |
#' split_cols_by("ARM") %>%
|
|
| 539 |
#' split_rows_by("SEX") %>%
|
|
| 540 |
#' split_rows_by("BMRKR2") %>%
|
|
| 541 |
#' analyze("AGE")
|
|
| 542 |
#' |
|
| 543 |
#' tbl <- build_table(lyt, ex_adsl) %>% prune_table() |
|
| 544 |
#' sub_tbl <- tt_at_path(tbl, path = c("SEX", "F", "BMRKR2"))
|
|
| 545 |
#' |
|
| 546 |
#' # Removing sub table. |
|
| 547 |
#' tbl2 <- tbl |
|
| 548 |
#' tt_at_path(tbl2, path = c("SEX", "F")) <- NULL
|
|
| 549 |
#' tbl2 |
|
| 550 |
#' |
|
| 551 |
#' # Setting sub table. |
|
| 552 |
#' lyt3 <- basic_table() %>% |
|
| 553 |
#' split_cols_by("ARM") %>%
|
|
| 554 |
#' split_rows_by("SEX") %>%
|
|
| 555 |
#' analyze("BMRKR2")
|
|
| 556 |
#' |
|
| 557 |
#' tbl3 <- build_table(lyt3, ex_adsl) %>% prune_table() |
|
| 558 |
#' |
|
| 559 |
#' tt_at_path(tbl3, path = c("SEX", "F", "BMRKR2")) <- sub_tbl
|
|
| 560 |
#' tbl3 |
|
| 561 |
#' |
|
| 562 |
#' @export |
|
| 563 |
#' @rdname ttap |
|
| 564 |
setGeneric( |
|
| 565 |
"tt_at_path<-", |
|
| 566 | 285x |
function(tt, path, ..., value) standardGeneric("tt_at_path<-")
|
| 567 |
) |
|
| 568 | ||
| 569 |
#' @export |
|
| 570 |
#' @keywords internal |
|
| 571 |
#' @rdname int_methods |
|
| 572 |
setMethod( |
|
| 573 |
"tt_at_path<-", c(tt = "VTableTree", value = "VTableTree"), |
|
| 574 |
function(tt, path, ..., value) {
|
|
| 575 | 151x |
do_recursive_replace(tt, path = path, value = value) |
| 576 |
} |
|
| 577 |
) |
|
| 578 | ||
| 579 |
## this one removes the child at path from the parents list of children, |
|
| 580 |
## because that is how lists behave. |
|
| 581 |
#' @export |
|
| 582 |
#' @keywords internal |
|
| 583 |
#' @rdname int_methods |
|
| 584 |
setMethod( |
|
| 585 |
"tt_at_path<-", c(tt = "VTableTree", value = "NULL"), |
|
| 586 |
function(tt, path, ..., value) {
|
|
| 587 | 2x |
do_recursive_replace(tt, path = path, value = value) |
| 588 |
} |
|
| 589 |
) |
|
| 590 | ||
| 591 |
#' @export |
|
| 592 |
#' @keywords internal |
|
| 593 |
#' @rdname int_methods |
|
| 594 |
setMethod( |
|
| 595 |
"tt_at_path<-", c(tt = "VTableTree", value = "TableRow"), |
|
| 596 |
function(tt, path, ..., value) {
|
|
| 597 | 132x |
stopifnot(is(tt_at_path(tt = tt, path = path), "TableRow")) |
| 598 | 132x |
do_recursive_replace(tt, path = path, value = value) |
| 599 | ||
| 600 |
## ##i <- .path_to_pos(path = path, seq_len(nrow(tt)), tt, NROW) |
|
| 601 |
## i <- .path_to_pos(path = path, tt = tt) |
|
| 602 | ||
| 603 |
## replace_rows(tt, i = i, value = list(value)) |
|
| 604 |
} |
|
| 605 |
) |
|
| 606 | ||
| 607 |
#' Retrieve and assign elements of a `TableTree` |
|
| 608 |
#' |
|
| 609 |
#' @param x (`TableTree`)\cr a `TableTree` object. |
|
| 610 |
#' @param i (`numeric(1)`)\cr index. |
|
| 611 |
#' @param j (`numeric(1)`)\cr index. |
|
| 612 |
#' @param drop (`flag`)\cr whether the value in the cell should be returned if one cell is selected by the |
|
| 613 |
#' combination of `i` and `j`. It is not possible to return a vector of values. To do so please consider using |
|
| 614 |
#' [cell_values()]. Defaults to `FALSE`. |
|
| 615 |
#' @param ... additional arguments. Includes: |
|
| 616 |
#' \describe{
|
|
| 617 |
#' \item{`keep_topleft`}{(`flag`) (`[` only) whether the top-left material for the table should be retained after
|
|
| 618 |
#' subsetting. Defaults to `TRUE` if all rows are included (i.e. subsetting was by column), and drops it |
|
| 619 |
#' otherwise.} |
|
| 620 |
#' \item{`keep_titles`}{(`flag`) whether title information should be retained. Defaults to `FALSE`.}
|
|
| 621 |
#' \item{`keep_footers`}{(`flag`) whether non-referential footer information should be retained. Defaults to
|
|
| 622 |
#' `keep_titles`.} |
|
| 623 |
#' \item{`reindex_refs`}{(`flag`) whether referential footnotes should be re-indexed as if the resulting subset is
|
|
| 624 |
#' the entire table. Defaults to `TRUE`.} |
|
| 625 |
#' } |
|
| 626 |
#' @param value (`list`, `TableRow`, or `TableTree`)\cr replacement value. |
|
| 627 |
#' |
|
| 628 |
#' @details |
|
| 629 |
#' By default, subsetting drops the information about title, subtitle, main footer, provenance footer, and `topleft`. |
|
| 630 |
#' If only a column is selected and all rows are kept, the `topleft` information remains as default. Any referential |
|
| 631 |
#' footnote is kept whenever the subset table contains the referenced element. |
|
| 632 |
#' |
|
| 633 |
#' @return A `TableTree` (or `ElementaryTable`) object, unless a single cell was selected with `drop = TRUE`, in which |
|
| 634 |
#' case the (possibly multi-valued) fully stripped raw value of the selected cell. |
|
| 635 |
#' |
|
| 636 |
#' @note |
|
| 637 |
#' Subsetting always preserve the original order, even if provided indexes do not preserve it. If sorting is needed, |
|
| 638 |
#' please consider using `sort_at_path()`. Also note that `character` indices are treated as paths, not vectors of |
|
| 639 |
#' names in both `[` and `[<-`. |
|
| 640 |
#' |
|
| 641 |
#' @seealso |
|
| 642 |
#' * [sort_at_path()] to understand sorting. |
|
| 643 |
#' * [summarize_row_groups()] to understand path structure. |
|
| 644 |
#' |
|
| 645 |
#' @examples |
|
| 646 |
#' lyt <- basic_table( |
|
| 647 |
#' title = "Title", |
|
| 648 |
#' subtitles = c("Sub", "titles"),
|
|
| 649 |
#' prov_footer = "prov footer", |
|
| 650 |
#' main_footer = "main footer" |
|
| 651 |
#' ) %>% |
|
| 652 |
#' split_cols_by("ARM") %>%
|
|
| 653 |
#' split_rows_by("SEX") %>%
|
|
| 654 |
#' analyze(c("AGE"))
|
|
| 655 |
#' |
|
| 656 |
#' tbl <- build_table(lyt, DM) |
|
| 657 |
#' top_left(tbl) <- "Info" |
|
| 658 |
#' tbl |
|
| 659 |
#' |
|
| 660 |
#' # As default header, footer, and topleft information is lost |
|
| 661 |
#' tbl[1, ] |
|
| 662 |
#' tbl[1:2, 2] |
|
| 663 |
#' |
|
| 664 |
#' # Also boolean filters can work |
|
| 665 |
#' tbl[, c(FALSE, TRUE, FALSE)] |
|
| 666 |
#' |
|
| 667 |
#' # If drop = TRUE, the content values are directly retrieved |
|
| 668 |
#' tbl[2, 1] |
|
| 669 |
#' tbl[2, 1, drop = TRUE] |
|
| 670 |
#' |
|
| 671 |
#' # Drop works also if vectors are selected, but not matrices |
|
| 672 |
#' tbl[, 1, drop = TRUE] |
|
| 673 |
#' tbl[2, , drop = TRUE] |
|
| 674 |
#' tbl[1, 1, drop = TRUE] # NULL because it is a label row |
|
| 675 |
#' tbl[2, 1:2, drop = TRUE] # vectors can be returned only with cell_values() |
|
| 676 |
#' tbl[1:2, 1:2, drop = TRUE] # no dropping because it is a matrix |
|
| 677 |
#' |
|
| 678 |
#' # If all rows are selected, topleft is kept by default |
|
| 679 |
#' tbl[, 2] |
|
| 680 |
#' tbl[, 1] |
|
| 681 |
#' |
|
| 682 |
#' # It is possible to deselect values |
|
| 683 |
#' tbl[-2, ] |
|
| 684 |
#' tbl[, -1] |
|
| 685 |
#' |
|
| 686 |
#' # Values can be reassigned |
|
| 687 |
#' tbl[4, 2] <- rcell(999, format = "xx.x") |
|
| 688 |
#' tbl[2, ] <- list(rrow("FFF", 888, 666, 777))
|
|
| 689 |
#' tbl[6, ] <- list(-111, -222, -333) |
|
| 690 |
#' tbl |
|
| 691 |
#' |
|
| 692 |
#' # We can keep some information from the original table if we need |
|
| 693 |
#' tbl[1, 2, keep_titles = TRUE] |
|
| 694 |
#' tbl[1, 2, keep_footers = TRUE, keep_titles = FALSE] |
|
| 695 |
#' tbl[1, 2, keep_footers = FALSE, keep_titles = TRUE] |
|
| 696 |
#' tbl[1, 2, keep_footers = TRUE] |
|
| 697 |
#' tbl[1, 2, keep_topleft = TRUE] |
|
| 698 |
#' |
|
| 699 |
#' # Keeps the referential footnotes when subset contains them |
|
| 700 |
#' fnotes_at_path(tbl, rowpath = c("SEX", "M", "AGE", "Mean")) <- "important"
|
|
| 701 |
#' tbl[4, 1] |
|
| 702 |
#' tbl[2, 1] # None present |
|
| 703 |
#' |
|
| 704 |
#' # We can reindex referential footnotes, so that the new table does not depend |
|
| 705 |
#' # on the original one |
|
| 706 |
#' fnotes_at_path(tbl, rowpath = c("SEX", "U", "AGE", "Mean")) <- "important"
|
|
| 707 |
#' tbl[, 1] # both present |
|
| 708 |
#' tbl[5:6, 1] # {1} because it has been indexed again
|
|
| 709 |
#' tbl[5:6, 1, reindex_refs = FALSE] # {2} -> not reindexed
|
|
| 710 |
#' |
|
| 711 |
#' # Note that order can not be changed with subsetting |
|
| 712 |
#' tbl[c(4, 3, 1), c(3, 1)] # It preserves order and wanted selection |
|
| 713 |
#' |
|
| 714 |
#' @name brackets |
|
| 715 |
NULL |
|
| 716 | ||
| 717 |
#' @exportMethod [<- |
|
| 718 |
#' @rdname brackets |
|
| 719 |
setMethod( |
|
| 720 |
"[<-", c("VTableTree", value = "list"),
|
|
| 721 |
function(x, i, j, ..., value) {
|
|
| 722 | 7x |
nr <- nrow(x) |
| 723 | 7x |
if (missing(i)) {
|
| 724 | ! |
i <- seq_len(NROW(x)) |
| 725 | 7x |
} else if (is(i, "character")) {
|
| 726 | ! |
i <- .path_to_pos(i, x) |
| 727 |
} else {
|
|
| 728 | 7x |
i <- .j_to_posj(i, nr) |
| 729 |
} |
|
| 730 | ||
| 731 | 7x |
if (missing(j)) {
|
| 732 | 2x |
j <- seq_along(col_exprs(col_info(x))) |
| 733 | 5x |
} else if (is(j, "character")) {
|
| 734 | ! |
j <- .path_to_pos(j, x, cols = TRUE) |
| 735 |
} else {
|
|
| 736 | 5x |
j <- .j_to_posj(j, ncol(x)) |
| 737 |
} |
|
| 738 | ||
| 739 | 7x |
if (length(i) > 1 && length(j) < ncol(x)) {
|
| 740 | ! |
stop("cannot modify multiple rows in not all columns.")
|
| 741 |
} |
|
| 742 | ||
| 743 | 7x |
if (are(value, "TableRow")) {
|
| 744 | 1x |
value <- rep(value, length.out = length(i)) |
| 745 |
} else {
|
|
| 746 | 6x |
value <- rep(value, length.out = length(i) * length(j)) |
| 747 |
} |
|
| 748 | ||
| 749 | 7x |
counter <- 0 |
| 750 |
## this has access to value, i, and j by scoping |
|
| 751 | 7x |
replace_rowsbynum <- function(x, i, valifnone = NULL) {
|
| 752 | 51x |
maxi <- max(i) |
| 753 | 51x |
if (counter >= maxi) {
|
| 754 | ! |
return(valifnone) |
| 755 |
} |
|
| 756 | ||
| 757 | 51x |
if (labelrow_visible(x)) {
|
| 758 | 3x |
counter <<- counter + 1 |
| 759 | 3x |
if (counter %in% i) {
|
| 760 | 1x |
nxtval <- value[[1]] |
| 761 | 1x |
if (is(nxtval, "LabelRow")) {
|
| 762 | 1x |
tt_labelrow(x) <- nxtval |
| 763 |
} else {
|
|
| 764 | ! |
stop( |
| 765 | ! |
"can't replace label with value of class", |
| 766 | ! |
class(nxtval) |
| 767 |
) |
|
| 768 |
} |
|
| 769 |
## we're done with this one move to |
|
| 770 |
## the next |
|
| 771 | 1x |
value <<- value[-1] |
| 772 |
} |
|
| 773 |
} |
|
| 774 | 51x |
if (is(x, "TableTree") && nrow(content_table(x)) > 0) {
|
| 775 | 14x |
ctab <- content_table(x) |
| 776 | ||
| 777 | 14x |
content_table(x) <- replace_rowsbynum(ctab, i) |
| 778 |
} |
|
| 779 | 51x |
if (counter >= maxi) { # already done
|
| 780 | 4x |
return(x) |
| 781 |
} |
|
| 782 | 47x |
kids <- tree_children(x) |
| 783 | ||
| 784 | 47x |
if (length(kids) > 0) {
|
| 785 | 47x |
for (pos in seq_along(kids)) {
|
| 786 | 57x |
curkid <- kids[[pos]] |
| 787 | 57x |
if (is(curkid, "TableRow")) {
|
| 788 | 27x |
counter <<- counter + 1 |
| 789 | 27x |
if (counter %in% i) {
|
| 790 | 7x |
nxtval <- value[[1]] |
| 791 | 7x |
if (is(nxtval, class(curkid))) {
|
| 792 | 1x |
if (no_colinfo(nxtval) && length(row_values(nxtval)) == ncol(x)) {
|
| 793 | 1x |
col_info(nxtval) <- col_info(x) |
| 794 |
} |
|
| 795 | 1x |
stopifnot(identical(col_info(x), col_info(nxtval))) |
| 796 | 1x |
curkid <- nxtval |
| 797 | 1x |
value <- value[-1] |
| 798 |
} else {
|
|
| 799 | 6x |
if (is(nxtval, "CellValue")) {
|
| 800 | 4x |
rcs <- row_cells(curkid) |
| 801 | 4x |
rcs[j] <- value[seq_along(j)] |
| 802 | 4x |
row_cells(curkid) <- rcs |
| 803 |
} else {
|
|
| 804 | 2x |
rvs <- row_values(curkid) |
| 805 | 2x |
rvs[j] <- value[seq_along(j)] |
| 806 | 2x |
row_values(curkid) <- rvs |
| 807 |
} |
|
| 808 | 6x |
value <- value[-(seq_along(j))] |
| 809 |
} |
|
| 810 | 7x |
kids[[pos]] <- curkid |
| 811 |
} |
|
| 812 |
} else {
|
|
| 813 | 30x |
kids[[pos]] <- replace_rowsbynum(curkid, i) |
| 814 |
} |
|
| 815 | 57x |
if (counter >= maxi) {
|
| 816 | 17x |
break |
| 817 |
} |
|
| 818 |
} |
|
| 819 |
} |
|
| 820 | 47x |
tree_children(x) <- kids |
| 821 | 47x |
x |
| 822 |
} |
|
| 823 | 7x |
replace_rowsbynum(x, i, ...) |
| 824 |
} |
|
| 825 |
) |
|
| 826 | ||
| 827 |
#' @inheritParams brackets |
|
| 828 |
#' |
|
| 829 |
#' @exportMethod [<- |
|
| 830 |
#' @rdname int_methods |
|
| 831 |
#' @keywords internal |
|
| 832 |
setMethod( |
|
| 833 |
"[<-", c("VTableTree", value = "CellValue"),
|
|
| 834 |
function(x, i, j, ..., value) {
|
|
| 835 | 3x |
x[i = i, j = j, ...] <- list(value) |
| 836 | 3x |
x |
| 837 |
} |
|
| 838 |
) |
|
| 839 | ||
| 840 |
## this is going to be hard :( :( :( |
|
| 841 | ||
| 842 |
### selecting/removing columns |
|
| 843 | ||
| 844 |
## we have two options here: path like we do with rows and positional |
|
| 845 |
## in leaf space. |
|
| 846 |
#' Subset a table or row to particular columns |
|
| 847 |
#' @inheritParams gen_args |
|
| 848 |
#' @inheritParams brackets |
|
| 849 |
#' @inheritParams head |
|
| 850 |
#' @param j (`integer`, `logical` or `character`)\cr The column(s) to subset `tt` |
|
| 851 |
#' down to. Character vectors are interpreted as a *column path*, not as names. |
|
| 852 |
#' Path can include `"*"` wildcards. |
|
| 853 |
#' @param newcinfo (`NULL` or `InstantiatedColumnInfo`)\cr The new column info, |
|
| 854 |
#' if precomputed. Generally should not be manually set by users. |
|
| 855 |
#' @param ... Ignored. |
|
| 856 |
#' |
|
| 857 |
#' @examples |
|
| 858 |
#' lyt <- basic_table( |
|
| 859 |
#' title = "Title", |
|
| 860 |
#' subtitles = c("Sub", "titles"),
|
|
| 861 |
#' prov_footer = "prov footer", |
|
| 862 |
#' main_footer = "main footer" |
|
| 863 |
#' ) %>% |
|
| 864 |
#' split_cols_by("ARM") %>%
|
|
| 865 |
#' split_cols_by("SEX") %>%
|
|
| 866 |
#' analyze(c("AGE"))
|
|
| 867 |
#' |
|
| 868 |
#' tbl <- build_table(lyt, DM) |
|
| 869 |
#' |
|
| 870 |
#' subset_cols(tbl, c(1, 3)) |
|
| 871 |
#' subset_cols(tbl, c("ARM", "*", "SEX", "F"))
|
|
| 872 |
#' @export |
|
| 873 |
setGeneric( |
|
| 874 |
"subset_cols", |
|
| 875 |
function(tt, |
|
| 876 |
j, |
|
| 877 |
newcinfo = NULL, |
|
| 878 |
keep_topleft = TRUE, |
|
| 879 |
keep_titles = TRUE, |
|
| 880 |
keep_footers = keep_titles, |
|
| 881 |
...) {
|
|
| 882 | 10346x |
standardGeneric("subset_cols")
|
| 883 |
} |
|
| 884 |
) |
|
| 885 | ||
| 886 |
#' @exportMethod subset_cols |
|
| 887 |
#' @rdname subset_cols |
|
| 888 |
setMethod( |
|
| 889 |
"subset_cols", c("TableTree", "numeric"),
|
|
| 890 |
function(tt, j, newcinfo = NULL, |
|
| 891 |
keep_topleft, keep_titles, keep_footers, ...) {
|
|
| 892 | 904x |
j <- .j_to_posj(j, ncol(tt)) |
| 893 | 904x |
if (is.null(newcinfo)) {
|
| 894 | 170x |
cinfo <- col_info(tt) |
| 895 | 170x |
newcinfo <- subset_cols(cinfo, j, |
| 896 | 170x |
keep_topleft = keep_topleft, ... |
| 897 |
) |
|
| 898 |
} |
|
| 899 |
## topleft taken care of in creation of newcinfo |
|
| 900 | 904x |
kids <- tree_children(tt) |
| 901 | 904x |
newkids <- lapply(kids, subset_cols, j = j, newcinfo = newcinfo, ...) |
| 902 | 904x |
cont <- content_table(tt) |
| 903 | 904x |
newcont <- subset_cols(cont, j, newcinfo = newcinfo, ...) |
| 904 | 904x |
tt2 <- tt |
| 905 | 904x |
col_info(tt2) <- newcinfo |
| 906 | 904x |
content_table(tt2) <- newcont |
| 907 | 904x |
tree_children(tt2) <- newkids |
| 908 | 904x |
tt_labelrow(tt2) <- subset_cols(tt_labelrow(tt2), j, newcinfo, ...) |
| 909 | ||
| 910 | 904x |
tt2 <- .h_copy_titles_footers_topleft( |
| 911 | 904x |
tt2, tt, |
| 912 | 904x |
keep_titles, |
| 913 | 904x |
keep_footers, |
| 914 | 904x |
keep_topleft |
| 915 |
) |
|
| 916 | 904x |
tt2 |
| 917 |
} |
|
| 918 |
) |
|
| 919 | ||
| 920 |
#' @exportMethod subset_cols |
|
| 921 |
#' @rdname subset_cols |
|
| 922 |
setMethod( |
|
| 923 |
"subset_cols", c("ElementaryTable", "numeric"),
|
|
| 924 |
function(tt, j, newcinfo = NULL, |
|
| 925 |
keep_topleft, keep_titles, keep_footers, ...) {
|
|
| 926 | 1902x |
j <- .j_to_posj(j, ncol(tt)) |
| 927 | 1902x |
if (is.null(newcinfo)) {
|
| 928 | 98x |
cinfo <- col_info(tt) |
| 929 | 98x |
newcinfo <- subset_cols(cinfo, j, |
| 930 | 98x |
keep_topleft = keep_topleft, |
| 931 | 98x |
keep_titles = keep_titles, |
| 932 | 98x |
keep_footers = keep_footers, ... |
| 933 |
) |
|
| 934 |
} |
|
| 935 |
## topleft handled in creation of newcinfo |
|
| 936 | 1902x |
kids <- tree_children(tt) |
| 937 | 1902x |
newkids <- lapply(kids, subset_cols, j = j, newcinfo = newcinfo, ...) |
| 938 | 1902x |
tt2 <- tt |
| 939 | 1902x |
col_info(tt2) <- newcinfo |
| 940 | 1902x |
tree_children(tt2) <- newkids |
| 941 | 1902x |
tt_labelrow(tt2) <- subset_cols(tt_labelrow(tt2), j, newcinfo, ...) |
| 942 | 1902x |
tt2 <- .h_copy_titles_footers_topleft( |
| 943 | 1902x |
tt2, tt, |
| 944 | 1902x |
keep_titles, |
| 945 | 1902x |
keep_footers, |
| 946 | 1902x |
keep_topleft |
| 947 |
) |
|
| 948 | 1902x |
tt2 |
| 949 |
} |
|
| 950 |
) |
|
| 951 | ||
| 952 |
## small utility to transform any negative |
|
| 953 |
## indices into positive ones, given j |
|
| 954 |
## and total length |
|
| 955 | ||
| 956 |
.j_to_posj <- function(j, n) {
|
|
| 957 |
## This will work for logicals, numerics, integers |
|
| 958 | 15564x |
j <- seq_len(n)[j] |
| 959 | 15564x |
j |
| 960 |
} |
|
| 961 | ||
| 962 |
path_collapse_sep <- "`" |
|
| 963 |
escape_name_padding <- function(x) {
|
|
| 964 |
## ret <- gsub("._[[", "\\._\\[\\[", x, fixed = TRUE)
|
|
| 965 |
## ret <- gsub("]]_.", "\\]\\]_\\.", ret, fixed = TRUE)
|
|
| 966 | 166x |
ret <- gsub("[", "\\[", x, fixed = TRUE)
|
| 967 | 166x |
ret <- gsub("]", "\\]", ret, fixed = TRUE)
|
| 968 | 166x |
ret <- gsub(".", "\\.", ret, fixed = TRUE)
|
| 969 | 166x |
ret |
| 970 |
} |
|
| 971 |
path_to_regex <- function(path) {
|
|
| 972 | 66x |
paste(vapply(path, function(x) {
|
| 973 | 188x |
if (identical(x, "*")) {
|
| 974 | 22x |
paste0("[^", path_collapse_sep, "]+")
|
| 975 |
} else {
|
|
| 976 | 166x |
escape_name_padding(x) |
| 977 |
} |
|
| 978 | 66x |
}, ""), collapse = path_collapse_sep) |
| 979 |
} |
|
| 980 | ||
| 981 |
.path_to_pos <- function(path, tt, distinct_ok = TRUE, cols = FALSE) {
|
|
| 982 | 66x |
path <- path[!grepl("^(|root)$", path)]
|
| 983 | 66x |
if (cols) {
|
| 984 | 60x |
rowdf <- make_col_df(tt) |
| 985 |
} else {
|
|
| 986 | 6x |
rowdf <- make_row_df(tt) |
| 987 |
} |
|
| 988 | 66x |
if (length(path) == 0 || identical(path, "*") || identical(path, "root")) {
|
| 989 | ! |
return(seq(1, nrow(rowdf))) |
| 990 |
} |
|
| 991 | ||
| 992 | 66x |
paths <- rowdf$path |
| 993 | 66x |
pathregex <- path_to_regex(path) |
| 994 | 66x |
pathstrs <- vapply(paths, paste, "", collapse = path_collapse_sep) |
| 995 | 66x |
allmatchs <- grep(pathregex, pathstrs) |
| 996 | 66x |
if (length(allmatchs) == 0) {
|
| 997 | 1x |
stop( |
| 998 | 1x |
if (cols) "column path [" else "row path [", |
| 999 | 1x |
paste(path, collapse = "->"), |
| 1000 | 1x |
"] does not appear valid for this table" |
| 1001 |
) |
|
| 1002 |
} |
|
| 1003 | ||
| 1004 | 65x |
idxdiffs <- diff(allmatchs) |
| 1005 | 65x |
if (!distinct_ok && length(idxdiffs) > 0 && any(idxdiffs > 1)) {
|
| 1006 | ! |
firstnon <- min(which(idxdiffs > 1)) |
| 1007 |
## its firstnon here because we would want firstnon-1 but |
|
| 1008 |
## the diffs are actually shifted 1 so they cancel out |
|
| 1009 | ! |
allmatchs <- allmatchs[seq(1, firstnon)] |
| 1010 |
} |
|
| 1011 | 65x |
allmatchs |
| 1012 |
} |
|
| 1013 | ||
| 1014 |
## fix column spans that would be invalid |
|
| 1015 |
## after some columns are no longer there |
|
| 1016 |
.fix_rowcspans <- function(rw, j) {
|
|
| 1017 | 4077x |
cspans <- row_cspans(rw) |
| 1018 | 4077x |
nc <- sum(cspans) |
| 1019 | 4077x |
j <- .j_to_posj(j, nc) |
| 1020 |
## this is overly complicated |
|
| 1021 |
## we need the starting indices |
|
| 1022 |
## but the first span might not be 1, so |
|
| 1023 |
## we pad with 1 and then take off the last |
|
| 1024 | 4077x |
start <- cumsum(c(1, head(cspans, -1))) |
| 1025 | 4077x |
ends <- c(tail(start, -1) - 1, nc) |
| 1026 | 4077x |
res <- mapply(function(st, en) {
|
| 1027 | 23974x |
sum(j >= st & j <= en) |
| 1028 | 4077x |
}, st = start, en = ends) |
| 1029 | 4077x |
res <- res[res > 0] |
| 1030 | 4077x |
stopifnot(sum(res) == length(j)) |
| 1031 | 4077x |
res |
| 1032 |
} |
|
| 1033 | ||
| 1034 |
select_cells_j <- function(cells, j) {
|
|
| 1035 | 4077x |
if (length(j) != length(unique(j))) {
|
| 1036 | ! |
stop("duplicate column selections is not currently supported")
|
| 1037 |
} |
|
| 1038 | 4077x |
spans <- vapply( |
| 1039 | 4077x |
cells, function(x) cell_cspan(x), |
| 1040 | 4077x |
integer(1) |
| 1041 |
) |
|
| 1042 | 4077x |
inds <- rep(seq_along(cells), times = spans) |
| 1043 | 4077x |
selinds <- inds[j] |
| 1044 | 4077x |
retcells <- cells[selinds[!duplicated(selinds)]] |
| 1045 | 4077x |
newspans <- vapply( |
| 1046 | 4077x |
split(selinds, selinds), |
| 1047 | 4077x |
length, |
| 1048 | 4077x |
integer(1) |
| 1049 |
) |
|
| 1050 | ||
| 1051 | 4077x |
mapply(function(cl, sp) {
|
| 1052 | 7066x |
cell_cspan(cl) <- sp |
| 1053 | 7066x |
cl |
| 1054 | 4077x |
}, cl = retcells, sp = newspans, SIMPLIFY = FALSE) |
| 1055 |
} |
|
| 1056 | ||
| 1057 |
#' @exportMethod subset_cols |
|
| 1058 |
#' @rdname subset_cols |
|
| 1059 |
setMethod( |
|
| 1060 |
"subset_cols", c("ANY", "character"),
|
|
| 1061 |
function(tt, j, newcinfo = NULL, keep_topleft = TRUE, ...) {
|
|
| 1062 | 51x |
j <- .path_to_pos(path = j, tt = tt, cols = TRUE) |
| 1063 | 51x |
subset_cols(tt, j, newcinfo = newcinfo, keep_topleft = keep_topleft, ...) |
| 1064 |
} |
|
| 1065 |
) |
|
| 1066 | ||
| 1067 |
#' @exportMethod subset_cols |
|
| 1068 |
#' @rdname subset_cols |
|
| 1069 |
setMethod( |
|
| 1070 |
"subset_cols", c("TableRow", "numeric"),
|
|
| 1071 |
function(tt, j, newcinfo = NULL, keep_topleft = TRUE, ...) {
|
|
| 1072 | 4077x |
j <- .j_to_posj(j, ncol(tt)) |
| 1073 | 4077x |
if (is.null(newcinfo)) {
|
| 1074 | 28x |
cinfo <- col_info(tt) |
| 1075 | 28x |
newcinfo <- subset_cols(cinfo, j, keep_topleft = keep_topleft, ...) |
| 1076 |
} |
|
| 1077 | 4077x |
tt2 <- tt |
| 1078 | 4077x |
row_cells(tt2) <- select_cells_j(row_cells(tt2), j) |
| 1079 | ||
| 1080 | 4077x |
if (length(row_cspans(tt2)) > 0) {
|
| 1081 | 4077x |
row_cspans(tt2) <- .fix_rowcspans(tt2, j) |
| 1082 |
} |
|
| 1083 | 4077x |
col_info(tt2) <- newcinfo |
| 1084 | 4077x |
tt2 |
| 1085 |
} |
|
| 1086 |
) |
|
| 1087 | ||
| 1088 |
#' @exportMethod subset_cols |
|
| 1089 |
#' @rdname subset_cols |
|
| 1090 |
setMethod( |
|
| 1091 |
"subset_cols", c("LabelRow", "numeric"),
|
|
| 1092 |
function(tt, j, newcinfo = NULL, keep_topleft = TRUE, ...) {
|
|
| 1093 | 2812x |
j <- .j_to_posj(j, ncol(tt)) |
| 1094 | 2812x |
if (is.null(newcinfo)) {
|
| 1095 | ! |
cinfo <- col_info(tt) |
| 1096 | ! |
newcinfo <- subset_cols(cinfo, j, keep_topleft = keep_topleft, ...) |
| 1097 |
} |
|
| 1098 | 2812x |
col_info(tt) <- newcinfo |
| 1099 | 2812x |
tt |
| 1100 |
} |
|
| 1101 |
) |
|
| 1102 | ||
| 1103 |
#' @exportMethod subset_cols |
|
| 1104 |
#' @rdname subset_cols |
|
| 1105 |
setMethod( |
|
| 1106 |
"subset_cols", c("InstantiatedColumnInfo", "numeric"),
|
|
| 1107 |
function(tt, j, newcinfo = NULL, keep_topleft = TRUE, ...) {
|
|
| 1108 | 300x |
if (!is.null(newcinfo)) {
|
| 1109 | ! |
return(newcinfo) |
| 1110 |
} |
|
| 1111 | 300x |
j <- .j_to_posj(j, length(col_exprs(tt))) |
| 1112 | 300x |
newctree <- subset_cols(coltree(tt), j, NULL) |
| 1113 | 300x |
newcextra <- col_extra_args(tt)[j] |
| 1114 | 300x |
newcsubs <- col_exprs(tt)[j] |
| 1115 | 300x |
newcounts <- col_counts(tt)[j] |
| 1116 | 300x |
tl <- if (keep_topleft) top_left(tt) else character() |
| 1117 | 300x |
InstantiatedColumnInfo( |
| 1118 | 300x |
treelyt = newctree, |
| 1119 | 300x |
csubs = newcsubs, |
| 1120 | 300x |
extras = newcextra, |
| 1121 | 300x |
cnts = newcounts, |
| 1122 | 300x |
dispcounts = disp_ccounts(tt), |
| 1123 | 300x |
countformat = colcount_format(tt), |
| 1124 | 300x |
topleft = tl |
| 1125 |
) |
|
| 1126 |
} |
|
| 1127 |
) |
|
| 1128 | ||
| 1129 |
#' @exportMethod subset_cols |
|
| 1130 |
#' @rdname subset_cols |
|
| 1131 |
setMethod( |
|
| 1132 |
"subset_cols", c("LayoutColTree", "numeric"),
|
|
| 1133 |
function(tt, j, newcinfo = NULL, ...) {
|
|
| 1134 | 300x |
lst <- collect_leaves(tt) |
| 1135 | 300x |
j <- .j_to_posj(j, length(lst)) |
| 1136 | ||
| 1137 |
## j has only non-negative values from |
|
| 1138 |
## this point on |
|
| 1139 | 300x |
counter <- 0 |
| 1140 | 300x |
prune_children <- function(x, j) {
|
| 1141 | 800x |
kids <- tree_children(x) |
| 1142 | 800x |
newkids <- kids |
| 1143 | 800x |
for (i in seq_along(newkids)) {
|
| 1144 | 2188x |
if (is(newkids[[i]], "LayoutColLeaf")) {
|
| 1145 | 1688x |
counter <<- counter + 1 |
| 1146 | 1688x |
if (!(counter %in% j)) {
|
| 1147 | 1244x |
newkids[[i]] <- list() |
| 1148 | 300x |
} ## NULL removes the position entirely |
| 1149 |
} else {
|
|
| 1150 | 500x |
newkids[[i]] <- prune_children(newkids[[i]], j) |
| 1151 |
} |
|
| 1152 |
} |
|
| 1153 | ||
| 1154 | 800x |
newkids <- newkids[sapply(newkids, function(thing) length(thing) > 0)] |
| 1155 | 800x |
if (length(newkids) > 0) {
|
| 1156 | 553x |
tree_children(x) <- newkids |
| 1157 | 553x |
x |
| 1158 |
} else {
|
|
| 1159 | 247x |
list() |
| 1160 |
} |
|
| 1161 |
} |
|
| 1162 | 300x |
prune_children(tt, j) |
| 1163 |
} |
|
| 1164 |
) |
|
| 1165 | ||
| 1166 |
## label rows ARE included in the count |
|
| 1167 |
subset_by_rownum <- function(tt, |
|
| 1168 |
i, |
|
| 1169 |
keep_topleft = FALSE, |
|
| 1170 |
keep_titles = TRUE, |
|
| 1171 |
keep_footers = keep_titles, |
|
| 1172 |
...) {
|
|
| 1173 | 193x |
stopifnot(is(tt, "VTableNodeInfo")) |
| 1174 | 193x |
counter <- 0 |
| 1175 | 193x |
nr <- nrow(tt) |
| 1176 | 193x |
i <- .j_to_posj(i, nr) |
| 1177 | 193x |
if (length(i) == 0) {
|
| 1178 | 6x |
ret <- TableTree(cinfo = col_info(tt)) |
| 1179 | 6x |
if (isTRUE(keep_topleft)) {
|
| 1180 | 1x |
top_left(ret) <- top_left(tt) |
| 1181 |
} |
|
| 1182 | 6x |
if (isTRUE(keep_titles)) {
|
| 1183 | 3x |
main_title(ret) <- main_title(tt) |
| 1184 | 3x |
subtitles(ret) <- subtitles(tt) |
| 1185 |
} |
|
| 1186 | 6x |
if (isTRUE(keep_footers)) {
|
| 1187 | 4x |
main_footer(ret) <- main_footer(tt) |
| 1188 | 4x |
prov_footer(ret) <- prov_footer(tt) |
| 1189 |
} |
|
| 1190 | 6x |
return(ret) |
| 1191 |
} |
|
| 1192 | ||
| 1193 | 187x |
prune_rowsbynum <- function(x, i, valifnone = NULL) {
|
| 1194 | 1358x |
maxi <- max(i) |
| 1195 | 1358x |
if (counter > maxi) {
|
| 1196 | 146x |
return(valifnone) |
| 1197 |
} |
|
| 1198 | ||
| 1199 | 1212x |
if (labelrow_visible(x)) {
|
| 1200 | 498x |
counter <<- counter + 1 |
| 1201 | 498x |
if (!(counter %in% i)) {
|
| 1202 |
## XXX this should do whatever |
|
| 1203 |
## is required to 'remove' the Label Row |
|
| 1204 |
## (currently implicit based on |
|
| 1205 |
## the value of the label but |
|
| 1206 |
## that shold really probably change) |
|
| 1207 | 179x |
labelrow_visible(x) <- FALSE |
| 1208 |
} |
|
| 1209 |
} |
|
| 1210 | 1212x |
if (is(x, "TableTree") && nrow(content_table(x)) > 0) {
|
| 1211 | 93x |
ctab <- content_table(x) |
| 1212 | ||
| 1213 | 93x |
content_table(x) <- prune_rowsbynum(ctab, i, |
| 1214 | 93x |
valifnone = ElementaryTable( |
| 1215 | 93x |
cinfo = col_info(ctab), |
| 1216 | 93x |
iscontent = TRUE |
| 1217 |
) |
|
| 1218 |
) |
|
| 1219 |
} |
|
| 1220 | 1212x |
kids <- tree_children(x) |
| 1221 | 1212x |
if (counter > maxi) { # already done
|
| 1222 | 51x |
kids <- list() |
| 1223 | 1161x |
} else if (length(kids) > 0) {
|
| 1224 | 1159x |
for (pos in seq_along(kids)) {
|
| 1225 | 4157x |
if (is(kids[[pos]], "TableRow")) {
|
| 1226 | 3079x |
counter <<- counter + 1 |
| 1227 | 3079x |
if (!(counter %in% i)) {
|
| 1228 | 2148x |
kids[[pos]] <- list() |
| 1229 |
} |
|
| 1230 |
} else {
|
|
| 1231 | 1078x |
kids[[pos]] <- prune_rowsbynum(kids[[pos]], i, list()) |
| 1232 |
} |
|
| 1233 |
} |
|
| 1234 | 1159x |
kids <- kids[sapply(kids, function(x) NROW(x) > 0)] |
| 1235 |
} |
|
| 1236 | 1212x |
if (length(kids) == 0 && NROW(content_table(x)) == 0 && !labelrow_visible(x)) {
|
| 1237 | 364x |
return(valifnone) |
| 1238 |
} else {
|
|
| 1239 | 848x |
tree_children(x) <- kids |
| 1240 | 848x |
x |
| 1241 |
} |
|
| 1242 |
## ## if(length(kids) == 0) {
|
|
| 1243 |
## ## if(!is(x, "TableTree")) |
|
| 1244 |
## ## return(valifnone) |
|
| 1245 |
## ## } |
|
| 1246 |
## if(is(x, "VTableTree") && nrow(x) > 0) {
|
|
| 1247 |
## x |
|
| 1248 |
## } else {
|
|
| 1249 |
## valifnone |
|
| 1250 |
## } |
|
| 1251 |
} |
|
| 1252 | 187x |
ret <- prune_rowsbynum(tt, i) |
| 1253 | ||
| 1254 | 187x |
ret <- .h_copy_titles_footers_topleft( |
| 1255 | 187x |
ret, tt, |
| 1256 | 187x |
keep_titles, |
| 1257 | 187x |
keep_footers, |
| 1258 | 187x |
keep_topleft |
| 1259 |
) |
|
| 1260 | ||
| 1261 | 187x |
ret |
| 1262 |
} |
|
| 1263 | ||
| 1264 |
#' @exportMethod [ |
|
| 1265 |
#' @rdname brackets |
|
| 1266 |
setMethod( |
|
| 1267 |
"[", c("VTableTree", "logical", "logical"),
|
|
| 1268 |
function(x, i, j, ..., drop = FALSE) {
|
|
| 1269 | 2x |
i <- .j_to_posj(i, nrow(x)) |
| 1270 | 2x |
j <- .j_to_posj(j, ncol(x)) |
| 1271 | 2x |
x[i, j, ..., drop = drop] |
| 1272 |
} |
|
| 1273 |
) |
|
| 1274 | ||
| 1275 |
#' @exportMethod [ |
|
| 1276 |
#' @rdname int_methods |
|
| 1277 |
#' @keywords internal |
|
| 1278 |
setMethod( |
|
| 1279 |
"[", c("VTableTree", "logical", "ANY"),
|
|
| 1280 |
function(x, i, j, ..., drop = FALSE) {
|
|
| 1281 | 1x |
i <- .j_to_posj(i, nrow(x)) |
| 1282 | 1x |
x[i, j, ..., drop = drop] |
| 1283 |
} |
|
| 1284 |
) |
|
| 1285 | ||
| 1286 |
#' @exportMethod [ |
|
| 1287 |
#' @rdname int_methods |
|
| 1288 |
#' @keywords internal |
|
| 1289 |
setMethod( |
|
| 1290 |
"[", c("VTableTree", "logical", "missing"),
|
|
| 1291 |
function(x, i, j, ..., drop = FALSE) {
|
|
| 1292 | 4x |
j <- seq_len(ncol(x)) |
| 1293 | 4x |
i <- .j_to_posj(i, nrow(x)) |
| 1294 | 4x |
x[i, j, ..., drop = drop] |
| 1295 |
} |
|
| 1296 |
) |
|
| 1297 | ||
| 1298 |
#' @exportMethod [ |
|
| 1299 |
#' @rdname int_methods |
|
| 1300 |
#' @keywords internal |
|
| 1301 |
setMethod( |
|
| 1302 |
"[", c("VTableTree", "ANY", "logical"),
|
|
| 1303 |
function(x, i, j, ..., drop = FALSE) {
|
|
| 1304 | 2x |
j <- .j_to_posj(j, ncol(x)) |
| 1305 | 2x |
x[i, j, ..., drop = drop] |
| 1306 |
} |
|
| 1307 |
) |
|
| 1308 | ||
| 1309 |
#' @exportMethod [ |
|
| 1310 |
#' @rdname int_methods |
|
| 1311 |
#' @keywords internal |
|
| 1312 |
setMethod( |
|
| 1313 |
"[", c("VTableTree", "ANY", "missing"),
|
|
| 1314 |
function(x, i, j, ..., drop = FALSE) {
|
|
| 1315 | 151x |
j <- seq_len(ncol(x)) |
| 1316 | 151x |
x[i = i, j = j, ..., drop = drop] |
| 1317 |
} |
|
| 1318 |
) |
|
| 1319 | ||
| 1320 |
#' @exportMethod [ |
|
| 1321 |
#' @rdname int_methods |
|
| 1322 |
#' @keywords internal |
|
| 1323 |
setMethod( |
|
| 1324 |
"[", c("VTableTree", "missing", "ANY"),
|
|
| 1325 |
function(x, i, j, ..., drop = FALSE) {
|
|
| 1326 | 5x |
i <- seq_len(nrow(x)) |
| 1327 | 5x |
x[i = i, j = j, ..., drop = drop] |
| 1328 |
} |
|
| 1329 |
) |
|
| 1330 | ||
| 1331 |
#' @exportMethod [ |
|
| 1332 |
#' @rdname int_methods |
|
| 1333 |
#' @keywords internal |
|
| 1334 |
setMethod( |
|
| 1335 |
"[", c("VTableTree", "ANY", "character"),
|
|
| 1336 |
function(x, i, j, ..., drop = FALSE) {
|
|
| 1337 |
## j <- .colpath_to_j(j, coltree(x)) |
|
| 1338 | 3x |
j <- .path_to_pos(path = j, tt = x, cols = TRUE) |
| 1339 | 3x |
x[i = i, j = j, ..., drop = drop] |
| 1340 |
} |
|
| 1341 |
) |
|
| 1342 | ||
| 1343 |
#' @exportMethod [ |
|
| 1344 |
#' @rdname int_methods |
|
| 1345 |
#' @keywords internal |
|
| 1346 |
setMethod( |
|
| 1347 |
"[", c("VTableTree", "character", "ANY"),
|
|
| 1348 |
function(x, i, j, ..., drop = FALSE) {
|
|
| 1349 |
## i <- .path_to_pos(i, seq_len(nrow(x)), x, NROW) |
|
| 1350 | ! |
i <- .path_to_pos(i, x) |
| 1351 | ! |
x[i = i, j = j, ..., drop = drop] |
| 1352 |
} |
|
| 1353 |
) |
|
| 1354 | ||
| 1355 |
#' @exportMethod [ |
|
| 1356 |
#' @rdname int_methods |
|
| 1357 |
#' @keywords internal |
|
| 1358 |
setMethod( |
|
| 1359 |
"[", c("VTableTree", "character", "missing"),
|
|
| 1360 |
function(x, i, j, ..., drop = FALSE) {
|
|
| 1361 |
## i <- .path_to_pos(i, seq_len(nrow(x)), x, NROW) |
|
| 1362 | 6x |
j <- seq_len(ncol(x)) |
| 1363 | 6x |
i <- .path_to_pos(i, x) |
| 1364 | 5x |
x[i = i, j = j, ..., drop = drop] |
| 1365 |
} |
|
| 1366 |
) |
|
| 1367 | ||
| 1368 | ||
| 1369 |
## to avoid dispatch ambiguity. Not necessary, possibly not a good idea at all |
|
| 1370 |
#' @exportMethod [ |
|
| 1371 |
#' @rdname int_methods |
|
| 1372 |
#' @keywords internal |
|
| 1373 |
setMethod( |
|
| 1374 |
"[", c("VTableTree", "character", "character"),
|
|
| 1375 |
function(x, i, j, ..., drop = FALSE) {
|
|
| 1376 |
## i <- .path_to_pos(i, seq_len(nrow(x)), x, NROW) |
|
| 1377 | ! |
i <- .path_to_pos(i, x) |
| 1378 |
## j <- .colpath_to_j(j, coltree(x)) |
|
| 1379 | ! |
j <- .path_to_pos(path = j, tt = x, cols = TRUE) |
| 1380 | ! |
x[i = i, j = j, ..., drop = drop] |
| 1381 |
} |
|
| 1382 |
) |
|
| 1383 | ||
| 1384 |
#' @exportMethod [ |
|
| 1385 |
#' @rdname int_methods |
|
| 1386 |
#' @keywords internal |
|
| 1387 |
setMethod( |
|
| 1388 |
"[", c("VTableTree", "missing", "numeric"),
|
|
| 1389 |
function(x, i, j, ..., drop = FALSE) {
|
|
| 1390 | 243x |
i <- seq_len(nrow(x)) |
| 1391 | 243x |
x[i, j, ..., drop = drop] |
| 1392 |
} |
|
| 1393 |
) |
|
| 1394 | ||
| 1395 |
#' @exportMethod [ |
|
| 1396 |
#' @rdname int_methods |
|
| 1397 |
#' @keywords internal |
|
| 1398 |
setMethod( |
|
| 1399 |
"[", c("VTableTree", "numeric", "numeric"),
|
|
| 1400 |
function(x, i, j, ..., drop = FALSE) {
|
|
| 1401 |
## have to do it this way because we can't add an argument since we don't |
|
| 1402 |
## own the generic declaration |
|
| 1403 | 489x |
keep_topleft <- list(...)[["keep_topleft"]] %||% NA |
| 1404 | 489x |
keep_titles <- list(...)[["keep_titles"]] %||% FALSE |
| 1405 | 489x |
keep_footers <- list(...)[["keep_footers"]] %||% keep_titles |
| 1406 | 489x |
reindex_refs <- list(...)[["reindex_refs"]] %||% TRUE |
| 1407 | ||
| 1408 | 489x |
if (length(j) == 0 || (length(j) == 1 && !is.na(j) && j == 0)) {
|
| 1409 | 1x |
stop("No column selected. Please consider using rtables::row.names(<tbl>) to get the row names.")
|
| 1410 |
} |
|
| 1411 | ||
| 1412 | 488x |
nr <- nrow(x) |
| 1413 | 488x |
nc <- ncol(x) |
| 1414 | 488x |
i <- .j_to_posj(i, nr) |
| 1415 | 488x |
j <- .j_to_posj(j, nc) |
| 1416 | ||
| 1417 |
## if(!missing(i) && length(i) < nr) {
|
|
| 1418 | 488x |
if (length(i) < nr) { ## already populated by .j_to_posj
|
| 1419 | 193x |
keep_topleft <- isTRUE(keep_topleft) |
| 1420 | 193x |
x <- subset_by_rownum(x, i, |
| 1421 | 193x |
keep_topleft = keep_topleft, |
| 1422 | 193x |
keep_titles = keep_titles, |
| 1423 | 193x |
keep_footers = keep_footers |
| 1424 |
) |
|
| 1425 | 295x |
} else if (is.na(keep_topleft)) {
|
| 1426 | 57x |
keep_topleft <- TRUE |
| 1427 |
} |
|
| 1428 | ||
| 1429 |
## if(!missing(j) && length(j) < nc) |
|
| 1430 | 488x |
if (length(j) < nc) {
|
| 1431 | 235x |
x <- subset_cols(x, j, |
| 1432 | 235x |
keep_topleft = keep_topleft, |
| 1433 | 235x |
keep_titles = keep_titles, |
| 1434 | 235x |
keep_footers = keep_footers |
| 1435 |
) |
|
| 1436 |
} |
|
| 1437 | ||
| 1438 |
# Dropping everything |
|
| 1439 | 488x |
if (drop) {
|
| 1440 | 35x |
if (length(j) == 1L && length(i) == 1L) {
|
| 1441 | 30x |
rw <- collect_leaves(x, TRUE, TRUE)[[1]] |
| 1442 | 30x |
if (is(rw, "LabelRow")) {
|
| 1443 | 2x |
warning( |
| 1444 | 2x |
"The value selected with drop = TRUE belongs ", |
| 1445 | 2x |
"to a label row. NULL will be returned" |
| 1446 |
) |
|
| 1447 | 2x |
x <- NULL |
| 1448 |
} else {
|
|
| 1449 | 28x |
x <- row_values(rw)[[1]] |
| 1450 |
} |
|
| 1451 |
} else {
|
|
| 1452 | 5x |
warning( |
| 1453 | 5x |
"Trying to drop more than one subsetted value. ", |
| 1454 | 5x |
"We support this only with accessor function `cell_values()`. ", |
| 1455 | 5x |
"No drop will be done at this time." |
| 1456 |
) |
|
| 1457 | 5x |
drop <- FALSE |
| 1458 |
} |
|
| 1459 |
} |
|
| 1460 | 488x |
if (!drop) {
|
| 1461 | 458x |
if (!keep_topleft) {
|
| 1462 | 70x |
top_left(x) <- character() |
| 1463 |
} |
|
| 1464 | 458x |
if (reindex_refs) {
|
| 1465 | 122x |
x <- update_ref_indexing(x) |
| 1466 |
} |
|
| 1467 |
} |
|
| 1468 | 488x |
x |
| 1469 |
} |
|
| 1470 |
) |
|
| 1471 | ||
| 1472 |
#' @importFrom utils compareVersion |
|
| 1473 | ||
| 1474 |
setGeneric("tail", tail)
|
|
| 1475 | ||
| 1476 |
setMethod( |
|
| 1477 |
"tail", "VTableTree", |
|
| 1478 |
function(x, n = 6L, ...) {
|
|
| 1479 |
if (compareVersion("4.0.0", as.character(getRversion())) <= 0) {
|
|
| 1480 |
tail.matrix(x, n, keepnums = FALSE) |
|
| 1481 |
} else {
|
|
| 1482 |
tail.matrix(x, n, addrownums = FALSE) |
|
| 1483 |
} |
|
| 1484 |
} |
|
| 1485 |
) |
|
| 1486 | ||
| 1487 |
setGeneric("head", head)
|
|
| 1488 | ||
| 1489 |
setMethod( |
|
| 1490 |
"head", "VTableTree", |
|
| 1491 |
function(x, n = 6L, ...) {
|
|
| 1492 |
head.matrix(x, n) |
|
| 1493 |
} |
|
| 1494 |
) |
|
| 1495 | ||
| 1496 |
#' Retrieve cell values by row and column path |
|
| 1497 |
#' |
|
| 1498 |
#' @inheritParams gen_args |
|
| 1499 |
#' @param rowpath (`character`)\cr path in row-split space to the desired row(s). Can include `"@content"`. |
|
| 1500 |
#' @param colpath (`character`)\cr path in column-split space to the desired column(s). Can include `"*"`. |
|
| 1501 |
#' @param omit_labrows (`flag`)\cr whether label rows underneath `rowpath` should be omitted (`TRUE`, the default), |
|
| 1502 |
#' or return empty lists of cell "values" (`FALSE`). |
|
| 1503 |
#' |
|
| 1504 |
#' @return |
|
| 1505 |
#' * `cell_values` returns a `list` (regardless of the type of value the cells hold). If `rowpath` defines a path to |
|
| 1506 |
#' a single row, `cell_values` returns the list of cell values for that row, otherwise a list of such lists, one for |
|
| 1507 |
#' each row captured underneath `rowpath`. This occurs after subsetting to `colpath` has occurred. |
|
| 1508 |
#' * `value_at` returns the "unwrapped" value of a single cell, or an error, if the combination of `rowpath` and |
|
| 1509 |
#' `colpath` do not define the location of a single cell in `tt`. |
|
| 1510 |
#' |
|
| 1511 |
#' @note `cell_values` will return a single cell's value wrapped in a list. Use `value_at` to receive the "bare" cell |
|
| 1512 |
#' value. |
|
| 1513 |
#' |
|
| 1514 |
#' @examples |
|
| 1515 |
#' lyt <- basic_table() %>% |
|
| 1516 |
#' split_cols_by("ARM") %>%
|
|
| 1517 |
#' split_cols_by("SEX") %>%
|
|
| 1518 |
#' split_rows_by("RACE") %>%
|
|
| 1519 |
#' summarize_row_groups() %>% |
|
| 1520 |
#' split_rows_by("STRATA1") %>%
|
|
| 1521 |
#' analyze("AGE")
|
|
| 1522 |
#' |
|
| 1523 |
#' @examplesIf require(dplyr) |
|
| 1524 |
#' library(dplyr) ## for mutate |
|
| 1525 |
#' tbl <- build_table(lyt, DM %>% |
|
| 1526 |
#' mutate(SEX = droplevels(SEX), RACE = droplevels(RACE))) |
|
| 1527 |
#' |
|
| 1528 |
#' row_paths_summary(tbl) |
|
| 1529 |
#' col_paths_summary(tbl) |
|
| 1530 |
#' |
|
| 1531 |
#' cell_values( |
|
| 1532 |
#' tbl, c("RACE", "ASIAN", "STRATA1", "B"),
|
|
| 1533 |
#' c("ARM", "A: Drug X", "SEX", "F")
|
|
| 1534 |
#' ) |
|
| 1535 |
#' |
|
| 1536 |
#' # it's also possible to access multiple values by being less specific |
|
| 1537 |
#' cell_values( |
|
| 1538 |
#' tbl, c("RACE", "ASIAN", "STRATA1"),
|
|
| 1539 |
#' c("ARM", "A: Drug X", "SEX", "F")
|
|
| 1540 |
#' ) |
|
| 1541 |
#' cell_values(tbl, c("RACE", "ASIAN"), c("ARM", "A: Drug X", "SEX", "M"))
|
|
| 1542 |
#' |
|
| 1543 |
#' ## any arm, male columns from the ASIAN content (i.e. summary) row |
|
| 1544 |
#' cell_values( |
|
| 1545 |
#' tbl, c("RACE", "ASIAN", "@content"),
|
|
| 1546 |
#' c("ARM", "B: Placebo", "SEX", "M")
|
|
| 1547 |
#' ) |
|
| 1548 |
#' cell_values( |
|
| 1549 |
#' tbl, c("RACE", "ASIAN", "@content"),
|
|
| 1550 |
#' c("ARM", "*", "SEX", "M")
|
|
| 1551 |
#' ) |
|
| 1552 |
#' |
|
| 1553 |
#' ## all columns |
|
| 1554 |
#' cell_values(tbl, c("RACE", "ASIAN", "STRATA1", "B"))
|
|
| 1555 |
#' |
|
| 1556 |
#' ## all columns for the Combination arm |
|
| 1557 |
#' cell_values( |
|
| 1558 |
#' tbl, c("RACE", "ASIAN", "STRATA1", "B"),
|
|
| 1559 |
#' c("ARM", "C: Combination")
|
|
| 1560 |
#' ) |
|
| 1561 |
#' |
|
| 1562 |
#' cvlist <- cell_values( |
|
| 1563 |
#' tbl, c("RACE", "ASIAN", "STRATA1", "B", "AGE", "Mean"),
|
|
| 1564 |
#' c("ARM", "B: Placebo", "SEX", "M")
|
|
| 1565 |
#' ) |
|
| 1566 |
#' cvnolist <- value_at( |
|
| 1567 |
#' tbl, c("RACE", "ASIAN", "STRATA1", "B", "AGE", "Mean"),
|
|
| 1568 |
#' c("ARM", "B: Placebo", "SEX", "M")
|
|
| 1569 |
#' ) |
|
| 1570 |
#' stopifnot(identical(cvlist[[1]], cvnolist)) |
|
| 1571 |
#' |
|
| 1572 |
#' @rdname cell_values |
|
| 1573 |
#' @export |
|
| 1574 |
setGeneric("cell_values", function(tt, rowpath = NULL, colpath = NULL, omit_labrows = TRUE) {
|
|
| 1575 | 197x |
standardGeneric("cell_values")
|
| 1576 |
}) |
|
| 1577 | ||
| 1578 |
#' @rdname int_methods |
|
| 1579 |
#' @keywords internal |
|
| 1580 |
#' @exportMethod cell_values |
|
| 1581 |
setMethod( |
|
| 1582 |
"cell_values", "VTableTree", |
|
| 1583 |
function(tt, rowpath, colpath = NULL, omit_labrows = TRUE) {
|
|
| 1584 | 194x |
.inner_cell_value(tt, |
| 1585 | 194x |
rowpath = rowpath, colpath = colpath, |
| 1586 | 194x |
omit_labrows = omit_labrows, value_at = FALSE |
| 1587 |
) |
|
| 1588 |
} |
|
| 1589 |
) |
|
| 1590 | ||
| 1591 |
#' @rdname int_methods |
|
| 1592 |
#' @keywords internal |
|
| 1593 |
#' @exportMethod cell_values |
|
| 1594 |
setMethod( |
|
| 1595 |
"cell_values", "TableRow", |
|
| 1596 |
function(tt, rowpath, colpath = NULL, omit_labrows = TRUE) {
|
|
| 1597 | 2x |
if (!is.null(rowpath)) {
|
| 1598 | 1x |
stop("cell_values on TableRow objects must have NULL rowpath")
|
| 1599 |
} |
|
| 1600 | 1x |
.inner_cell_value(tt, |
| 1601 | 1x |
rowpath = rowpath, colpath = colpath, |
| 1602 | 1x |
omit_labrows = omit_labrows, value_at = FALSE |
| 1603 |
) |
|
| 1604 |
} |
|
| 1605 |
) |
|
| 1606 | ||
| 1607 |
#' @rdname int_methods |
|
| 1608 |
#' @keywords internal |
|
| 1609 |
#' @exportMethod cell_values |
|
| 1610 |
setMethod( |
|
| 1611 |
"cell_values", "LabelRow", |
|
| 1612 |
function(tt, rowpath, colpath = NULL, omit_labrows = TRUE) {
|
|
| 1613 | 1x |
stop("calling cell_values on LabelRow is not meaningful")
|
| 1614 |
} |
|
| 1615 |
) |
|
| 1616 | ||
| 1617 |
#' @rdname cell_values |
|
| 1618 |
#' @export |
|
| 1619 |
setGeneric("value_at", function(tt, rowpath = NULL, colpath = NULL) {
|
|
| 1620 | 8x |
standardGeneric("value_at")
|
| 1621 |
}) |
|
| 1622 | ||
| 1623 |
#' @rdname cell_values |
|
| 1624 |
#' @exportMethod value_at |
|
| 1625 |
setMethod( |
|
| 1626 |
"value_at", "VTableTree", |
|
| 1627 |
function(tt, rowpath, colpath = NULL) {
|
|
| 1628 | 7x |
.inner_cell_value(tt, |
| 1629 | 7x |
rowpath = rowpath, colpath = colpath, |
| 1630 | 7x |
omit_labrows = FALSE, value_at = TRUE |
| 1631 |
) |
|
| 1632 |
} |
|
| 1633 |
) |
|
| 1634 | ||
| 1635 |
#' @rdname int_methods |
|
| 1636 |
#' @keywords internal |
|
| 1637 |
#' @exportMethod value_at |
|
| 1638 |
setMethod( |
|
| 1639 |
"value_at", "TableRow", |
|
| 1640 |
function(tt, rowpath, colpath = NULL) {
|
|
| 1641 | 1x |
.inner_cell_value(tt, |
| 1642 | 1x |
rowpath = rowpath, colpath = colpath, |
| 1643 | 1x |
omit_labrows = FALSE, value_at = TRUE |
| 1644 |
) |
|
| 1645 |
} |
|
| 1646 |
) |
|
| 1647 | ||
| 1648 |
#' @rdname int_methods |
|
| 1649 |
#' @keywords internal |
|
| 1650 |
#' @exportMethod value_at |
|
| 1651 |
setMethod( |
|
| 1652 |
"value_at", "LabelRow", |
|
| 1653 |
function(tt, rowpath, colpath = NULL) {
|
|
| 1654 | ! |
stop("calling value_at for LabelRow objects is not meaningful")
|
| 1655 |
} |
|
| 1656 |
) |
|
| 1657 | ||
| 1658 |
.inner_cell_value <- function(tt, |
|
| 1659 |
rowpath, |
|
| 1660 |
colpath = NULL, |
|
| 1661 |
omit_labrows = TRUE, |
|
| 1662 |
value_at = FALSE) {
|
|
| 1663 | 203x |
if (is.null(rowpath)) {
|
| 1664 | 124x |
subtree <- tt |
| 1665 |
} else {
|
|
| 1666 | 79x |
subtree <- tt_at_path(tt, rowpath) |
| 1667 |
} |
|
| 1668 | 202x |
if (!is.null(colpath)) {
|
| 1669 | 28x |
subtree <- subset_cols(subtree, colpath) |
| 1670 |
} |
|
| 1671 | ||
| 1672 | 202x |
rows <- collect_leaves(subtree, TRUE, !omit_labrows) |
| 1673 | 202x |
if (value_at && (ncol(subtree) != 1 || length(rows) != 1)) {
|
| 1674 | 3x |
stop("Combination of rowpath and colpath does not select individual cell.\n",
|
| 1675 | 3x |
" To retrieve more than one cell value at a time use cell_values().", |
| 1676 | 3x |
call. = FALSE |
| 1677 |
) |
|
| 1678 |
} |
|
| 1679 | 199x |
if (length(rows) == 1) {
|
| 1680 | 102x |
ret <- row_values(rows[[1]]) |
| 1681 | 102x |
if (value_at && ncol(subtree) == 1) {
|
| 1682 | 5x |
ret <- ret[[1]] |
| 1683 |
} |
|
| 1684 | 102x |
ret |
| 1685 |
} else {
|
|
| 1686 | 97x |
lapply(rows, row_values) |
| 1687 |
} |
|
| 1688 |
} |
|
| 1689 | ||
| 1690 |
## empty_table is created in onLoad because it depends on other things there. |
|
| 1691 | ||
| 1692 |
# Helper function to copy or not header, footer, and topleft information |
|
| 1693 |
.h_copy_titles_footers_topleft <- function(new, |
|
| 1694 |
old, |
|
| 1695 |
keep_titles, |
|
| 1696 |
keep_footers, |
|
| 1697 |
keep_topleft, |
|
| 1698 |
reindex_refs = FALSE, |
|
| 1699 |
empt_tbl = empty_table) {
|
|
| 1700 |
## Please note that the standard adopted come from an empty table |
|
| 1701 | ||
| 1702 |
# titles |
|
| 1703 | 3002x |
if (isTRUE(keep_titles)) {
|
| 1704 | 2819x |
main_title(new) <- main_title(old) |
| 1705 | 2819x |
subtitles(new) <- subtitles(old) |
| 1706 |
} else {
|
|
| 1707 | 183x |
main_title(new) <- main_title(empt_tbl) |
| 1708 | 183x |
subtitles(new) <- subtitles(empt_tbl) |
| 1709 |
} |
|
| 1710 | ||
| 1711 |
# fnotes |
|
| 1712 | 3002x |
if (isTRUE(keep_footers)) {
|
| 1713 | 2825x |
main_footer(new) <- main_footer(old) |
| 1714 | 2825x |
prov_footer(new) <- prov_footer(old) |
| 1715 |
} else {
|
|
| 1716 | 177x |
main_footer(new) <- main_footer(empt_tbl) |
| 1717 | 177x |
prov_footer(new) <- prov_footer(empt_tbl) |
| 1718 |
} |
|
| 1719 | ||
| 1720 |
# topleft |
|
| 1721 | 3002x |
if (isTRUE(keep_topleft)) {
|
| 1722 | 2847x |
top_left(new) <- top_left(old) |
| 1723 |
} else {
|
|
| 1724 | 155x |
top_left(new) <- top_left(empt_tbl) |
| 1725 |
} |
|
| 1726 | ||
| 1727 |
# reindex references |
|
| 1728 | 3002x |
if (reindex_refs) {
|
| 1729 | ! |
new <- update_ref_indexing(new) |
| 1730 |
} |
|
| 1731 | ||
| 1732 | 3002x |
new |
| 1733 |
} |
|
| 1734 | ||
| 1735 |
#' Head and tail methods |
|
| 1736 |
#' |
|
| 1737 |
#' @inheritParams utils::head |
|
| 1738 |
#' @param keep_topleft (`flag`)\cr if `TRUE` (the default), top_left material for the table will be carried over to the |
|
| 1739 |
#' subset. |
|
| 1740 |
#' @param keep_titles (`flag`)\cr if `TRUE` (the default), all title material for the table will be carried over to the |
|
| 1741 |
#' subset. |
|
| 1742 |
#' @param keep_footers (`flag`)\cr if `TRUE`, all footer material for the table will be carried over to the subset. It |
|
| 1743 |
#' defaults to `keep_titles`. |
|
| 1744 |
#' @param reindex_refs (`flag`)\cr defaults to `FALSE`. If `TRUE`, referential footnotes will be reindexed for the |
|
| 1745 |
#' subset. |
|
| 1746 |
#' |
|
| 1747 |
#' @docType methods |
|
| 1748 |
#' @export |
|
| 1749 |
#' @rdname head_tail |
|
| 1750 |
setGeneric("head")
|
|
| 1751 | ||
| 1752 |
#' @docType methods |
|
| 1753 |
#' @export |
|
| 1754 |
#' @rdname head_tail |
|
| 1755 |
setMethod( |
|
| 1756 |
"head", "VTableTree", |
|
| 1757 |
function(x, n = 6, ..., keep_topleft = TRUE, |
|
| 1758 |
keep_titles = TRUE, |
|
| 1759 |
keep_footers = keep_titles, |
|
| 1760 |
## FALSE because this is a glance |
|
| 1761 |
## more often than a subset op |
|
| 1762 |
reindex_refs = FALSE) {
|
|
| 1763 |
## default |
|
| 1764 | 5x |
res <- callNextMethod() |
| 1765 | 5x |
res <- .h_copy_titles_footers_topleft( |
| 1766 | 5x |
old = x, new = res, |
| 1767 | 5x |
keep_topleft = keep_topleft, |
| 1768 | 5x |
keep_titles = keep_titles, |
| 1769 | 5x |
keep_footers = keep_footers, |
| 1770 | 5x |
reindex_refs = reindex_refs |
| 1771 |
) |
|
| 1772 | 5x |
res |
| 1773 |
} |
|
| 1774 |
) |
|
| 1775 | ||
| 1776 |
#' @docType methods |
|
| 1777 |
#' @export |
|
| 1778 |
#' @rdname head_tail |
|
| 1779 |
setGeneric("tail")
|
|
| 1780 | ||
| 1781 |
#' @docType methods |
|
| 1782 |
#' @export |
|
| 1783 |
#' @rdname head_tail |
|
| 1784 |
setMethod( |
|
| 1785 |
"tail", "VTableTree", |
|
| 1786 |
function(x, n = 6, ..., keep_topleft = TRUE, |
|
| 1787 |
keep_titles = TRUE, |
|
| 1788 |
keep_footers = keep_titles, |
|
| 1789 |
## FALSE because this is a glance |
|
| 1790 |
## more often than a subset op |
|
| 1791 |
reindex_refs = FALSE) {
|
|
| 1792 | 4x |
res <- callNextMethod() |
| 1793 | 4x |
res <- .h_copy_titles_footers_topleft( |
| 1794 | 4x |
old = x, new = res, |
| 1795 | 4x |
keep_topleft = keep_topleft, |
| 1796 | 4x |
keep_titles = keep_titles, |
| 1797 | 4x |
keep_footers = keep_footers, |
| 1798 | 4x |
reindex_refs = reindex_refs |
| 1799 |
) |
|
| 1800 | 4x |
res |
| 1801 |
} |
|
| 1802 |
) |
| 1 |
#' Cell value constructors |
|
| 2 |
#' |
|
| 3 |
#' Construct a cell value and associate formatting, labeling, indenting, and column spanning information with it. |
|
| 4 |
#' |
|
| 5 |
#' @inheritParams compat_args |
|
| 6 |
#' @inheritParams lyt_args |
|
| 7 |
#' @param x (`ANY`)\cr cell value. |
|
| 8 |
#' @param format (`string` or `function`)\cr the format label (string) or `formatters` function to apply to `x`. |
|
| 9 |
#' See [formatters::list_valid_format_labels()] for currently supported format labels. |
|
| 10 |
#' @param label (`string` or `NULL`)\cr label. If non-`NULL`, it will be looked at when determining row labels. |
|
| 11 |
#' @param colspan (`integer(1)`)\cr column span value. |
|
| 12 |
#' @param footnotes (`list` or `NULL`)\cr referential footnote messages for the cell. |
|
| 13 |
#' @param stat_names (`character` or `NA`)\cr names for the statistics in the cell. It can be a vector of strings. |
|
| 14 |
#' If `NA`, statistic names are not specified. |
|
| 15 |
#' |
|
| 16 |
#' @inherit CellValue return |
|
| 17 |
#' |
|
| 18 |
#' @note Currently column spanning is only supported for defining header structure. |
|
| 19 |
#' |
|
| 20 |
#' @examples |
|
| 21 |
#' rcell(1, format = "xx.x") |
|
| 22 |
#' rcell(c(1, 2), format = c("xx - xx"))
|
|
| 23 |
#' rcell(c(1, 2), stat_names = c("Rand1", "Rand2"))
|
|
| 24 |
#' |
|
| 25 |
#' @rdname rcell |
|
| 26 |
#' @export |
|
| 27 |
rcell <- function(x, |
|
| 28 |
format = NULL, |
|
| 29 |
colspan = 1L, |
|
| 30 |
label = NULL, |
|
| 31 |
indent_mod = NULL, |
|
| 32 |
footnotes = NULL, |
|
| 33 |
align = NULL, |
|
| 34 |
format_na_str = NULL, |
|
| 35 |
stat_names = NULL) {
|
|
| 36 | 36001x |
checkmate::assert_character(stat_names, null.ok = TRUE) |
| 37 | 36001x |
if (!is.null(align)) {
|
| 38 | 56x |
check_aligns(align) |
| 39 |
} |
|
| 40 | 36001x |
if (is(x, "CellValue")) {
|
| 41 | 21674x |
if (!is.null(label)) {
|
| 42 | 1x |
obj_label(x) <- label |
| 43 |
} |
|
| 44 | 21674x |
if (colspan != 1L) {
|
| 45 | 1x |
cell_cspan(x) <- colspan |
| 46 |
} |
|
| 47 | 21674x |
if (!is.null(indent_mod)) {
|
| 48 | 1x |
indent_mod(x) <- indent_mod |
| 49 |
} |
|
| 50 | 21674x |
if (!is.null(format)) {
|
| 51 | 1x |
obj_format(x) <- format |
| 52 |
} |
|
| 53 | 21674x |
if (!is.null(footnotes)) {
|
| 54 | 374x |
cell_footnotes(x) <- lapply(footnotes, RefFootnote) |
| 55 |
} |
|
| 56 | 21674x |
if (!is.null(format_na_str)) {
|
| 57 | ! |
obj_na_str(x) <- format_na_str |
| 58 |
} |
|
| 59 | 21674x |
if (!is.null(stat_names)) {
|
| 60 | 8x |
obj_stat_names(x) <- stat_names |
| 61 |
} |
|
| 62 | 21674x |
ret <- x |
| 63 |
} else {
|
|
| 64 | 14327x |
if (is.null(label)) {
|
| 65 | 11241x |
label <- obj_label(x) |
| 66 |
} |
|
| 67 | 14327x |
if (is.null(format)) {
|
| 68 | 7975x |
format <- obj_format(x) |
| 69 |
} |
|
| 70 | 14327x |
if (is.null(indent_mod)) {
|
| 71 | 14327x |
indent_mod <- indent_mod(x) |
| 72 |
} |
|
| 73 | 14327x |
footnotes <- lapply(footnotes, RefFootnote) |
| 74 | 14327x |
ret <- CellValue( |
| 75 | 14327x |
val = x, |
| 76 | 14327x |
format = format, |
| 77 | 14327x |
colspan = colspan, |
| 78 | 14327x |
label = label, |
| 79 | 14327x |
indent_mod = indent_mod, |
| 80 | 14327x |
footnotes = footnotes, |
| 81 | 14327x |
format_na_str = format_na_str, |
| 82 | 14327x |
stat_names = stat_names %||% NA_character_ |
| 83 | 14327x |
) # RefFootnote(footnote)) |
| 84 |
} |
|
| 85 | 36001x |
if (!is.null(align)) {
|
| 86 | 56x |
cell_align(ret) <- align |
| 87 |
} |
|
| 88 | 36001x |
ret |
| 89 |
} |
|
| 90 | ||
| 91 |
#' @param is_ref (`flag`)\cr whether function is being used in the reference column (i.e. `.in_ref_col` should be |
|
| 92 |
#' passed to this argument). |
|
| 93 |
#' @param refval (`ANY`)\cr value to use when in the reference column. Defaults to `NULL`. |
|
| 94 |
#' |
|
| 95 |
#' @details |
|
| 96 |
#' `non_ref_rcell` provides the common *blank for cells in the reference column, this value otherwise*, and should |
|
| 97 |
#' be passed the value of `.in_ref_col` when it is used. |
|
| 98 |
#' |
|
| 99 |
#' @rdname rcell |
|
| 100 |
#' @export |
|
| 101 |
non_ref_rcell <- function(x, is_ref, format = NULL, colspan = 1L, |
|
| 102 |
label = NULL, indent_mod = NULL, |
|
| 103 |
refval = NULL, |
|
| 104 |
align = "center", |
|
| 105 |
format_na_str = NULL) {
|
|
| 106 | 2x |
val <- if (is_ref) refval else x |
| 107 | 2x |
rcell(val, |
| 108 | 2x |
format = format, colspan = colspan, label = label, |
| 109 | 2x |
indent_mod = indent_mod, align = align, |
| 110 | 2x |
format_na_str = format_na_str |
| 111 |
) |
|
| 112 |
} |
|
| 113 | ||
| 114 |
#' Create multiple rows in analysis or summary functions |
|
| 115 |
#' |
|
| 116 |
#' Define the cells that get placed into multiple rows in `afun`. |
|
| 117 |
#' |
|
| 118 |
#' @param ... single row defining expressions. |
|
| 119 |
#' @param .list (`list`)\cr list cell content (usually `rcells`). The `.list` is concatenated to `...`. |
|
| 120 |
#' @param .names (`character` or `NULL`)\cr names of the returned list/structure. |
|
| 121 |
#' @param .labels (`character` or `NULL`)\cr labels for the defined rows. |
|
| 122 |
#' @param .formats (`character` or `NULL`)\cr formats for the values. |
|
| 123 |
#' @param .indent_mods (`integer` or `NULL`)\cr indent modifications for the defined rows. |
|
| 124 |
#' @param .cell_footnotes (`list`)\cr referential footnote messages to be associated by name with *cells*. |
|
| 125 |
#' @param .row_footnotes (`list`)\cr referential footnotes messages to be associated by name with *rows*. |
|
| 126 |
#' @param .aligns (`character` or `NULL`)\cr alignments for the cells. Standard for `NULL` is `"center"`. |
|
| 127 |
#' See [formatters::list_valid_aligns()] for currently supported alignments. |
|
| 128 |
#' @param .format_na_strs (`character` or `NULL`)\cr NA strings for the cells. |
|
| 129 |
#' @param .stat_names (`list`)\cr names for the statistics in the cells. |
|
| 130 |
#' It can be a vector of values. If `list(NULL)`, statistic names are not specified and will |
|
| 131 |
#' appear as `NA`. |
|
| 132 |
#' |
|
| 133 |
#' @note In post-processing, referential footnotes can also be added using row and column |
|
| 134 |
#' paths with [`fnotes_at_path<-`]. |
|
| 135 |
#' |
|
| 136 |
#' @return A `RowsVerticalSection` object (or `NULL`). The details of this object should be considered an |
|
| 137 |
#' internal implementation detail. |
|
| 138 |
#' |
|
| 139 |
#' @seealso [analyze()] |
|
| 140 |
#' |
|
| 141 |
#' @examples |
|
| 142 |
#' in_rows(1, 2, 3, .names = c("a", "b", "c"))
|
|
| 143 |
#' in_rows(1, 2, 3, .labels = c("a", "b", "c"))
|
|
| 144 |
#' in_rows(1, 2, 3, .names = c("a", "b", "c"), .labels = c("AAA", "BBB", "CCC"))
|
|
| 145 |
#' in_rows( |
|
| 146 |
#' .list = list(a = c(NA, NA)), |
|
| 147 |
#' .formats = "xx - xx", |
|
| 148 |
#' .format_na_strs = list(c("asda", "lkjklj"))
|
|
| 149 |
#' ) |
|
| 150 |
#' in_rows(.list = list(a = c(NA, NA)), .format_na_strs = c("asda", "lkjklj"))
|
|
| 151 |
#' |
|
| 152 |
#' in_rows(.list = list(a = 1, b = 2, c = 3)) |
|
| 153 |
#' in_rows(1, 2, .list = list(3), .names = c("a", "b", "c"))
|
|
| 154 |
#' |
|
| 155 |
#' lyt <- basic_table() %>% |
|
| 156 |
#' split_cols_by("ARM") %>%
|
|
| 157 |
#' analyze("AGE", afun = function(x) {
|
|
| 158 |
#' in_rows( |
|
| 159 |
#' "Mean (sd)" = rcell(c(mean(x), sd(x)), format = "xx.xx (xx.xx)"), |
|
| 160 |
#' "Range" = rcell(range(x), format = "xx.xx - xx.xx") |
|
| 161 |
#' ) |
|
| 162 |
#' }) |
|
| 163 |
#' |
|
| 164 |
#' tbl <- build_table(lyt, ex_adsl) |
|
| 165 |
#' tbl |
|
| 166 |
#' |
|
| 167 |
#' @export |
|
| 168 |
in_rows <- function(..., .list = NULL, .names = NULL, |
|
| 169 |
.labels = NULL, |
|
| 170 |
.formats = NULL, |
|
| 171 |
.indent_mods = NULL, |
|
| 172 |
.cell_footnotes = list(NULL), |
|
| 173 |
.row_footnotes = list(NULL), |
|
| 174 |
.aligns = NULL, |
|
| 175 |
.format_na_strs = NULL, |
|
| 176 |
.stat_names = list(NULL)) {
|
|
| 177 | 6614x |
if (is.function(.formats)) {
|
| 178 | ! |
.formats <- list(.formats) |
| 179 |
} |
|
| 180 | ||
| 181 | 6614x |
l <- c(list(...), .list) |
| 182 | ||
| 183 | 6614x |
if (missing(.names) && missing(.labels)) {
|
| 184 | 2290x |
if (length(l) > 0 && is.null(names(l))) {
|
| 185 | ! |
stop("need a named list")
|
| 186 |
} else {
|
|
| 187 | 2290x |
.names <- names(l) |
| 188 |
} |
|
| 189 | 2290x |
stopifnot(!anyNA(.names)) |
| 190 |
} |
|
| 191 | ||
| 192 | 6614x |
if (length(l) == 0) {
|
| 193 |
if ( |
|
| 194 | ! |
length(.labels) > 0 || |
| 195 | ! |
length(.formats) > 0 || |
| 196 | ! |
length(.names) > 0 || |
| 197 | ! |
length(.indent_mods) > 0 || |
| 198 | ! |
length(.format_na_strs) > 0 || |
| 199 | ! |
(!all(is.na(.stat_names)) && length(.stat_names) > 0) |
| 200 |
) {
|
|
| 201 | ! |
stop( |
| 202 | ! |
"in_rows got 0 rows but length >0 of at least one of ", |
| 203 | ! |
".labels, .formats, .names, .indent_mods, .format_na_strs, .stat_names. ", |
| 204 | ! |
"Does your analysis/summary function handle the 0 row ", |
| 205 | ! |
"df/length 0 x case?" |
| 206 |
) |
|
| 207 |
} |
|
| 208 | ! |
l2 <- list() |
| 209 |
} else {
|
|
| 210 | 6614x |
if (is.null(.formats)) {
|
| 211 | 6144x |
.formats <- list(NULL) |
| 212 |
} |
|
| 213 | 6614x |
stopifnot(is.list(.cell_footnotes)) |
| 214 | 6614x |
if (length(.cell_footnotes) != length(l)) {
|
| 215 | 1445x |
.cell_footnotes <- c( |
| 216 | 1445x |
.cell_footnotes, |
| 217 | 1445x |
setNames( |
| 218 | 1445x |
rep(list(character()), |
| 219 | 1445x |
length.out = length(setdiff( |
| 220 | 1445x |
names(l), |
| 221 | 1445x |
names(.cell_footnotes) |
| 222 |
)) |
|
| 223 |
), |
|
| 224 | 1445x |
setdiff( |
| 225 | 1445x |
names(l), |
| 226 | 1445x |
names(.cell_footnotes) |
| 227 |
) |
|
| 228 |
) |
|
| 229 |
) |
|
| 230 | 1445x |
.cell_footnotes <- .cell_footnotes[names(l)] |
| 231 |
} |
|
| 232 | 6614x |
if (is.null(.aligns)) {
|
| 233 | 6611x |
.aligns <- list(NULL) |
| 234 |
} |
|
| 235 | ||
| 236 | 6614x |
l2 <- mapply(rcell, |
| 237 | 6614x |
x = l, format = .formats, |
| 238 | 6614x |
footnotes = .cell_footnotes %||% list(NULL), |
| 239 | 6614x |
align = .aligns, |
| 240 | 6614x |
format_na_str = .format_na_strs %||% list(NULL), |
| 241 | 6614x |
stat_names = .stat_names %||% list(NULL), |
| 242 | 6614x |
SIMPLIFY = FALSE |
| 243 |
) |
|
| 244 |
} |
|
| 245 | 6614x |
if (is.null(.labels)) {
|
| 246 | 3248x |
objlabs <- vapply(l2, function(x) obj_label(x) %||% "", "") |
| 247 | 3248x |
if (any(nzchar(objlabs))) {
|
| 248 | 69x |
.labels <- objlabs |
| 249 |
} |
|
| 250 |
} |
|
| 251 | ||
| 252 | 6614x |
if (is.null(.names) && !is.null(names(l))) {
|
| 253 | 99x |
.names <- names(l) |
| 254 |
} |
|
| 255 | 6614x |
stopifnot(is.list(.row_footnotes)) |
| 256 | 6614x |
if (length(.row_footnotes) != length(l2)) {
|
| 257 | 1445x |
tmp <- .row_footnotes |
| 258 | 1445x |
.row_footnotes <- vector("list", length(l2))
|
| 259 | 1445x |
pos <- match(names(tmp), .names) |
| 260 | 1445x |
nonna <- which(!is.na(pos)) |
| 261 | 1445x |
.row_footnotes[pos] <- tmp[nonna] |
| 262 |
# length(.row_footnotes) <- length(l2) |
|
| 263 |
} |
|
| 264 | 6614x |
ret <- RowsVerticalSection(l2, |
| 265 | 6614x |
names = .names, |
| 266 | 6614x |
labels = .labels, |
| 267 | 6614x |
indent_mods = .indent_mods, |
| 268 | 6614x |
formats = .formats, |
| 269 | 6614x |
footnotes = .row_footnotes, |
| 270 | 6614x |
format_na_strs = .format_na_strs |
| 271 |
) |
|
| 272 |
## if(!is.null(.names)) |
|
| 273 |
## names(l2) <- .names |
|
| 274 |
## else |
|
| 275 |
## names(l2) <- names(l) |
|
| 276 | ! |
if (length(ret) == 0) NULL else ret |
| 277 | ||
| 278 |
## if (length(l) == 0) NULL else l |
|
| 279 |
} |
|
| 280 | ||
| 281 |
.validate_nms <- function(vals, .stats, arg) {
|
|
| 282 | 268x |
if (!is.null(arg)) {
|
| 283 | 112x |
if (is.null(names(arg))) {
|
| 284 | ! |
stopifnot(length(arg) == length(.stats)) |
| 285 | ! |
names(arg) <- names(vals) |
| 286 |
} else {
|
|
| 287 | 112x |
lblpos <- match(names(arg), names(vals)) |
| 288 | 112x |
stopifnot(!anyNA(lblpos)) |
| 289 |
} |
|
| 290 |
} |
|
| 291 | 268x |
arg |
| 292 |
} |
|
| 293 | ||
| 294 |
#' Create a custom analysis function wrapping an existing function |
|
| 295 |
#' |
|
| 296 |
#' @param fun (`function`)\cr the function to be wrapped in a new customized analysis function. |
|
| 297 |
#' `fun` should return a named `list`. |
|
| 298 |
#' @param .stats (`character`)\cr names of elements to keep from `fun`'s full output. |
|
| 299 |
#' @param .formats (`ANY`)\cr vector or list of formats to override any defaults applied by `fun`. |
|
| 300 |
#' @param .labels (`character`)\cr vector of labels to override defaults returned by `fun`. |
|
| 301 |
#' @param .indent_mods (`integer`)\cr named vector of indent modifiers for the generated rows. |
|
| 302 |
#' @param .ungroup_stats (`character`)\cr vector of names, which must match elements of `.stats`. |
|
| 303 |
#' @param ... additional arguments to `fun` which effectively become new defaults. These can still be |
|
| 304 |
#' overridden by `extra_args` within a split. |
|
| 305 |
#' @param .null_ref_cells (`flag`)\cr whether cells for the reference column should be `NULL`-ed by the |
|
| 306 |
#' returned analysis function. Defaults to `TRUE` if `fun` accepts `.in_ref_col` as a formal argument. Note |
|
| 307 |
#' this argument occurs after `...` so it must be *fully* specified by name when set. |
|
| 308 |
#' @param .format_na_strs (`ANY`)\cr vector/list of `NA` strings to override any defaults applied by `fun`. |
|
| 309 |
#' |
|
| 310 |
#' @return A function suitable for use in [analyze()] with element selection, reformatting, and relabeling |
|
| 311 |
#' performed automatically. |
|
| 312 |
#' |
|
| 313 |
#' @note |
|
| 314 |
#' Setting `.ungroup_stats` to non-`NULL` changes the *structure* of the value(s) returned by `fun`, rather than |
|
| 315 |
#' just labeling (`.labels`), formatting (`.formats`), and selecting amongst (`.stats`) them. This means that |
|
| 316 |
#' subsequent `make_afun` calls to customize the output further both can and must operate on the new structure, |
|
| 317 |
#' *not* the original structure returned by `fun`. See the final pair of examples below. |
|
| 318 |
#' |
|
| 319 |
#' @seealso [analyze()] |
|
| 320 |
#' |
|
| 321 |
#' @examples |
|
| 322 |
#' s_summary <- function(x) {
|
|
| 323 |
#' stopifnot(is.numeric(x)) |
|
| 324 |
#' |
|
| 325 |
#' list( |
|
| 326 |
#' n = sum(!is.na(x)), |
|
| 327 |
#' mean_sd = c(mean = mean(x), sd = sd(x)), |
|
| 328 |
#' min_max = range(x) |
|
| 329 |
#' ) |
|
| 330 |
#' } |
|
| 331 |
#' |
|
| 332 |
#' s_summary(iris$Sepal.Length) |
|
| 333 |
#' |
|
| 334 |
#' a_summary <- make_afun( |
|
| 335 |
#' fun = s_summary, |
|
| 336 |
#' .formats = c(n = "xx", mean_sd = "xx.xx (xx.xx)", min_max = "xx.xx - xx.xx"), |
|
| 337 |
#' .labels = c(n = "n", mean_sd = "Mean (sd)", min_max = "min - max") |
|
| 338 |
#' ) |
|
| 339 |
#' |
|
| 340 |
#' a_summary(x = iris$Sepal.Length) |
|
| 341 |
#' |
|
| 342 |
#' a_summary2 <- make_afun(a_summary, .stats = c("n", "mean_sd"))
|
|
| 343 |
#' |
|
| 344 |
#' a_summary2(x = iris$Sepal.Length) |
|
| 345 |
#' |
|
| 346 |
#' a_summary3 <- make_afun(a_summary, .formats = c(mean_sd = "(xx.xxx, xx.xxx)")) |
|
| 347 |
#' |
|
| 348 |
#' s_foo <- function(df, .N_col, a = 1, b = 2) {
|
|
| 349 |
#' list( |
|
| 350 |
#' nrow_df = nrow(df), |
|
| 351 |
#' .N_col = .N_col, |
|
| 352 |
#' a = a, |
|
| 353 |
#' b = b |
|
| 354 |
#' ) |
|
| 355 |
#' } |
|
| 356 |
#' |
|
| 357 |
#' s_foo(iris, 40) |
|
| 358 |
#' |
|
| 359 |
#' a_foo <- make_afun(s_foo, |
|
| 360 |
#' b = 4, |
|
| 361 |
#' .formats = c(nrow_df = "xx.xx", ".N_col" = "xx.", a = "xx", b = "xx.x"), |
|
| 362 |
#' .labels = c( |
|
| 363 |
#' nrow_df = "Nrow df", |
|
| 364 |
#' ".N_col" = "n in cols", a = "a value", b = "b value" |
|
| 365 |
#' ), |
|
| 366 |
#' .indent_mods = c(nrow_df = 2L, a = 1L) |
|
| 367 |
#' ) |
|
| 368 |
#' |
|
| 369 |
#' a_foo(iris, .N_col = 40) |
|
| 370 |
#' a_foo2 <- make_afun(a_foo, .labels = c(nrow_df = "Number of Rows")) |
|
| 371 |
#' a_foo2(iris, .N_col = 40) |
|
| 372 |
#' |
|
| 373 |
#' # grouping and further customization |
|
| 374 |
#' s_grp <- function(df, .N_col, a = 1, b = 2) {
|
|
| 375 |
#' list( |
|
| 376 |
#' nrow_df = nrow(df), |
|
| 377 |
#' .N_col = .N_col, |
|
| 378 |
#' letters = list( |
|
| 379 |
#' a = a, |
|
| 380 |
#' b = b |
|
| 381 |
#' ) |
|
| 382 |
#' ) |
|
| 383 |
#' } |
|
| 384 |
#' a_grp <- make_afun(s_grp, |
|
| 385 |
#' b = 3, |
|
| 386 |
#' .labels = c( |
|
| 387 |
#' nrow_df = "row count", |
|
| 388 |
#' .N_col = "count in column" |
|
| 389 |
#' ), |
|
| 390 |
#' .formats = c(nrow_df = "xx.", .N_col = "xx."), |
|
| 391 |
#' .indent_mods = c(letters = 1L), |
|
| 392 |
#' .ungroup_stats = "letters" |
|
| 393 |
#' ) |
|
| 394 |
#' a_grp(iris, 40) |
|
| 395 |
#' a_aftergrp <- make_afun(a_grp, |
|
| 396 |
#' .stats = c("nrow_df", "b"),
|
|
| 397 |
#' .formats = c(b = "xx.") |
|
| 398 |
#' ) |
|
| 399 |
#' a_aftergrp(iris, 40) |
|
| 400 |
#' |
|
| 401 |
#' s_ref <- function(x, .in_ref_col, .ref_group) {
|
|
| 402 |
#' list( |
|
| 403 |
#' mean_diff = mean(x) - mean(.ref_group) |
|
| 404 |
#' ) |
|
| 405 |
#' } |
|
| 406 |
#' |
|
| 407 |
#' a_ref <- make_afun(s_ref, |
|
| 408 |
#' .labels = c(mean_diff = "Mean Difference from Ref") |
|
| 409 |
#' ) |
|
| 410 |
#' a_ref(iris$Sepal.Length, .in_ref_col = TRUE, 1:10) |
|
| 411 |
#' a_ref(iris$Sepal.Length, .in_ref_col = FALSE, 1:10) |
|
| 412 |
#' |
|
| 413 |
#' @export |
|
| 414 |
make_afun <- function(fun, |
|
| 415 |
.stats = NULL, |
|
| 416 |
.formats = NULL, |
|
| 417 |
.labels = NULL, |
|
| 418 |
.indent_mods = NULL, |
|
| 419 |
.ungroup_stats = NULL, |
|
| 420 |
.format_na_strs = NULL, |
|
| 421 |
..., |
|
| 422 |
.null_ref_cells = ".in_ref_col" %in% names(formals(fun))) {
|
|
| 423 |
## there is a LOT more computing-on-the-language hackery in here that I |
|
| 424 |
## would prefer, but currently this is the way I see to do everything we |
|
| 425 |
## want to do. |
|
| 426 | ||
| 427 |
## too clever by three-quarters (because half wasn't enough) |
|
| 428 |
## gross scope hackery |
|
| 429 | 23x |
fun_args <- force(list(...)) |
| 430 | 23x |
fun_fnames <- names(formals(fun)) |
| 431 | ||
| 432 |
## force EVERYTHING otherwise calling this within loops is the stuff of |
|
| 433 |
## nightmares |
|
| 434 | 23x |
force(.stats) |
| 435 | 23x |
force(.formats) |
| 436 | 23x |
force(.format_na_strs) |
| 437 | 23x |
force(.labels) |
| 438 | 23x |
force(.indent_mods) |
| 439 | 23x |
force(.ungroup_stats) |
| 440 | 23x |
force(.null_ref_cells) ## this one probably isn't needed? |
| 441 | ||
| 442 | 23x |
ret <- function(x, ...) { ## remember formals get clobbered here
|
| 443 | ||
| 444 |
## this helper will grab the value and wrap it in a named list if |
|
| 445 |
## we need the variable and return list() otherwise. |
|
| 446 |
## We define it in here so that the scoping hackery works correctly |
|
| 447 | 66x |
.if_in_formals <- function(nm, ifnot = list(), named_lwrap = TRUE) {
|
| 448 | 660x |
val <- if (nm %in% fun_fnames) get(nm) else ifnot |
| 449 | 660x |
if (named_lwrap && !identical(val, ifnot)) {
|
| 450 | 78x |
setNames(list(val), nm) |
| 451 |
} else {
|
|
| 452 | 582x |
val |
| 453 |
} |
|
| 454 |
} |
|
| 455 | ||
| 456 | 66x |
custargs <- fun_args |
| 457 | ||
| 458 |
## special handling cause I need it at the bottom as well |
|
| 459 | 66x |
in_rc_argl <- .if_in_formals(".in_ref_col")
|
| 460 | 66x |
.in_ref_col <- if (length(in_rc_argl) > 0) in_rc_argl[[1]] else FALSE |
| 461 | ||
| 462 | 66x |
sfunargs <- c( |
| 463 |
## these are either named lists containing the arg, or list() |
|
| 464 |
## depending on whether fun accept the argument or not |
|
| 465 | 66x |
.if_in_formals("x"),
|
| 466 | 66x |
.if_in_formals("df"),
|
| 467 | 66x |
.if_in_formals(".N_col"),
|
| 468 | 66x |
.if_in_formals(".N_total"),
|
| 469 | 66x |
.if_in_formals(".N_row"),
|
| 470 | 66x |
.if_in_formals(".ref_group"),
|
| 471 | 66x |
in_rc_argl, |
| 472 | 66x |
.if_in_formals(".df_row"),
|
| 473 | 66x |
.if_in_formals(".var"),
|
| 474 | 66x |
.if_in_formals(".ref_full")
|
| 475 |
) |
|
| 476 | ||
| 477 | 66x |
allvars <- setdiff(fun_fnames, c("...", names(sfunargs)))
|
| 478 |
## values int he actual call to this function override customization |
|
| 479 |
## done by the constructor. evalparse is to avoid a "... in wrong context" NOTE |
|
| 480 | 66x |
if ("..." %in% fun_fnames) {
|
| 481 | 5x |
exargs <- eval(parser_helper(text = "list(...)")) |
| 482 | 5x |
custargs[names(exargs)] <- exargs |
| 483 | 5x |
allvars <- unique(c(allvars, names(custargs))) |
| 484 |
} |
|
| 485 | ||
| 486 | 66x |
for (var in allvars) {
|
| 487 |
## not missing, i.e. specified in the direct call, takes precedence |
|
| 488 | 22x |
if (var %in% fun_fnames && eval(parser_helper(text = paste0("!missing(", var, ")")))) {
|
| 489 | 5x |
sfunargs[[var]] <- get(var) |
| 490 | 17x |
} else if (var %in% names(custargs)) { ## not specified in the call, but specified in the constructor
|
| 491 | 4x |
sfunargs[[var]] <- custargs[[var]] |
| 492 |
} |
|
| 493 |
## else left out so we hit the original default we inherited from fun |
|
| 494 |
} |
|
| 495 | ||
| 496 | 66x |
rawvals <- do.call(fun, sfunargs) |
| 497 | ||
| 498 |
## note single brackets here so its a list |
|
| 499 |
## no matter what. thats important! |
|
| 500 | 66x |
final_vals <- if (is.null(.stats)) rawvals else rawvals[.stats] |
| 501 | ||
| 502 | 66x |
if (!is.list(rawvals)) {
|
| 503 | ! |
stop("make_afun expects a function fun that always returns a list")
|
| 504 |
} |
|
| 505 | 66x |
if (!is.null(.stats)) {
|
| 506 | 10x |
stopifnot(all(.stats %in% names(rawvals))) |
| 507 |
} else {
|
|
| 508 | 56x |
.stats <- names(rawvals) |
| 509 |
} |
|
| 510 | 66x |
if (!is.null(.ungroup_stats) && !all(.ungroup_stats %in% .stats)) {
|
| 511 | ! |
stop( |
| 512 | ! |
"Stats specified for ungrouping not included in non-null .stats list: ", |
| 513 | ! |
setdiff(.ungroup_stats, .stats) |
| 514 |
) |
|
| 515 |
} |
|
| 516 | ||
| 517 | 66x |
.labels <- .validate_nms(final_vals, .stats, .labels) |
| 518 | 66x |
.formats <- .validate_nms(final_vals, .stats, .formats) |
| 519 | 66x |
.indent_mods <- .validate_nms(final_vals, .stats, .indent_mods) |
| 520 | 66x |
.format_na_strs <- .validate_nms(final_vals, .stats, .format_na_strs) |
| 521 | ||
| 522 | 66x |
final_labels <- value_labels(final_vals) |
| 523 | 66x |
final_labels[names(.labels)] <- .labels |
| 524 | ||
| 525 | 66x |
final_formats <- lapply(final_vals, obj_format) |
| 526 | 66x |
final_formats[names(.formats)] <- .formats |
| 527 | ||
| 528 | 66x |
final_format_na_strs <- lapply(final_vals, obj_na_str) |
| 529 | 66x |
final_format_na_strs[names(.format_na_strs)] <- .format_na_strs |
| 530 | ||
| 531 | 66x |
if (is(final_vals, "RowsVerticalSection")) {
|
| 532 | 20x |
final_imods <- indent_mod(final_vals) |
| 533 |
} else {
|
|
| 534 | 46x |
final_imods <- vapply(final_vals, indent_mod, 1L) |
| 535 |
} |
|
| 536 | 66x |
final_imods[names(.indent_mods)] <- .indent_mods |
| 537 | ||
| 538 | 66x |
if (!is.null(.ungroup_stats)) {
|
| 539 | 2x |
for (nm in .ungroup_stats) {
|
| 540 | 3x |
tmp <- final_vals[[nm]] |
| 541 | 3x |
if (is(tmp, "CellValue")) {
|
| 542 | 1x |
tmp <- tmp[[1]] |
| 543 | 23x |
} ## unwrap it |
| 544 | 3x |
final_vals <- insert_replace(final_vals, nm, tmp) |
| 545 | 3x |
stopifnot(all(nzchar(names(final_vals)))) |
| 546 | ||
| 547 | 3x |
final_labels <- insert_replace( |
| 548 | 3x |
final_labels, |
| 549 | 3x |
nm, |
| 550 | 3x |
setNames( |
| 551 | 3x |
value_labels(tmp), |
| 552 | 3x |
names(tmp) |
| 553 |
) |
|
| 554 |
) |
|
| 555 | 3x |
final_formats <- insert_replace( |
| 556 | 3x |
final_formats, |
| 557 | 3x |
nm, |
| 558 | 3x |
setNames( |
| 559 | 3x |
rep(final_formats[nm], |
| 560 | 3x |
length.out = length(tmp) |
| 561 |
), |
|
| 562 | 3x |
names(tmp) |
| 563 |
) |
|
| 564 |
) |
|
| 565 | 3x |
final_format_na_strs <- insert_replace( |
| 566 | 3x |
final_format_na_strs, |
| 567 | 3x |
nm, |
| 568 | 3x |
setNames( |
| 569 | 3x |
rep(final_format_na_strs[nm], |
| 570 | 3x |
length.out = length(tmp) |
| 571 |
), |
|
| 572 | 3x |
names(tmp) |
| 573 |
) |
|
| 574 |
) |
|
| 575 | 3x |
final_imods <- insert_replace( |
| 576 | 3x |
final_imods, |
| 577 | 3x |
nm, |
| 578 | 3x |
setNames( |
| 579 | 3x |
rep(final_imods[nm], |
| 580 | 3x |
length.out = length(tmp) |
| 581 |
), |
|
| 582 | 3x |
names(tmp) |
| 583 |
) |
|
| 584 |
) |
|
| 585 |
} |
|
| 586 |
} |
|
| 587 | 66x |
rcells <- mapply( |
| 588 | 66x |
function(x, f, l, na_str) {
|
| 589 | 197x |
if (is(x, "CellValue")) {
|
| 590 | 65x |
obj_label(x) <- l |
| 591 | 65x |
obj_format(x) <- f |
| 592 | 65x |
obj_na_str(x) <- na_str |
| 593 |
# indent_mod(x) <- im |
|
| 594 | 65x |
x |
| 595 | 132x |
} else if (.null_ref_cells) {
|
| 596 | ! |
non_ref_rcell(x, |
| 597 | ! |
is_ref = .in_ref_col, |
| 598 | ! |
format = f, label = l, |
| 599 | ! |
format_na_str = na_str |
| 600 | ! |
) # , indent_mod = im) |
| 601 |
} else {
|
|
| 602 | 132x |
rcell(x, format = f, label = l, format_na_str = na_str) # , indent_mod = im) |
| 603 |
} |
|
| 604 |
}, |
|
| 605 | 66x |
f = final_formats, x = final_vals, |
| 606 | 66x |
l = final_labels, |
| 607 | 66x |
na_str = final_format_na_strs, |
| 608 |
# im = final_imods, |
|
| 609 | 66x |
SIMPLIFY = FALSE |
| 610 |
) |
|
| 611 | 66x |
in_rows(.list = rcells, .indent_mods = final_imods) ## , .labels = .labels) |
| 612 |
} |
|
| 613 | 23x |
formals(ret) <- formals(fun) |
| 614 | 23x |
ret |
| 615 |
} |
|
| 616 | ||
| 617 |
insert_replace <- function(x, nm, newvals = x[[nm]]) {
|
|
| 618 | 15x |
i <- match(nm, names(x)) |
| 619 | 15x |
if (is.na(i)) {
|
| 620 | ! |
stop("name not found")
|
| 621 |
} |
|
| 622 | 15x |
bef <- if (i > 1) 1:(i - 1) else numeric() |
| 623 | 15x |
aft <- if (i < length(x)) (i + 1):length(x) else numeric() |
| 624 | 15x |
ret <- c(x[bef], newvals, x[aft]) |
| 625 | 15x |
names(ret) <- c(names(x)[bef], names(newvals), names(x)[aft]) |
| 626 | 15x |
ret |
| 627 |
} |
|
| 628 | ||
| 629 |
parser_helper <- function(text, envir = parent.frame(2)) {
|
|
| 630 | 545x |
parse(text = text, keep.source = FALSE) |
| 631 |
} |
|
| 632 | ||
| 633 |
length_w_name <- function(x, .parent_splval) {
|
|
| 634 | ! |
in_rows(length(x), |
| 635 | ! |
.names = value_labels(.parent_splval) |
| 636 |
) |
|
| 637 |
} |
| 1 |
# as_result_df ------------------------------------------------------------ |
|
| 2 |
#' Generate a result data frame |
|
| 3 |
#' |
|
| 4 |
#' Collection of utilities to extract `data.frame` objects from `TableTree` objects. |
|
| 5 |
#' |
|
| 6 |
#' @inheritParams gen_args |
|
| 7 |
#' @param spec (`function`)\cr function that generates the result data frame from a table (`TableTree`). |
|
| 8 |
#' It defaults to `NULL`, for standard processing. |
|
| 9 |
#' @param expand_colnames (`flag`)\cr when `TRUE`, the result data frame will have expanded column |
|
| 10 |
#' names above the usual output. This is useful when the result data frame is used for further processing. |
|
| 11 |
#' @param data_format (`string`)\cr the format of the data in the result data frame. It can be one value |
|
| 12 |
#' between `"full_precision"` (default), `"strings"`, and `"numeric"`. The last two values show the numeric |
|
| 13 |
#' data with the visible precision. |
|
| 14 |
#' @param make_ard (`flag`)\cr when `TRUE`, the result data frame will have only one statistic per row. |
|
| 15 |
#' @param keep_label_rows (`flag`)\cr when `TRUE`, the result data frame will have all labels |
|
| 16 |
#' as they appear in the final table. |
|
| 17 |
#' @param simplify (`flag`)\cr when `TRUE`, the result data frame will have only visible labels and |
|
| 18 |
#' result columns. Consider showing also label rows with `keep_label_rows = TRUE`. This output can be |
|
| 19 |
#' used again to create a `TableTree` object with [df_to_tt()]. |
|
| 20 |
#' @param add_tbl_name_split (`flag`)\cr when `TRUE` and when the table has more than one |
|
| 21 |
#' `analyze(table_names = "<diff_names>")`, the table names will be present as a group split named |
|
| 22 |
#' `"<analysis_spl_tbl_name>"`. |
|
| 23 |
#' @param verbose (`flag`)\cr when `TRUE`, the function will print additional information for |
|
| 24 |
#' `data_format != "full_precision"`. |
|
| 25 |
#' @param ... additional arguments passed to spec-specific result data frame function (`spec`). When |
|
| 26 |
#' using `make_ard = TRUE`, it is possible to turn off the extraction of the exact string decimals |
|
| 27 |
#' printed by the table with `add_tbl_str_decimals = FALSE`. |
|
| 28 |
#' |
|
| 29 |
#' @return |
|
| 30 |
#' * `as_result_df` returns a result `data.frame`. |
|
| 31 |
#' |
|
| 32 |
#' @seealso [df_to_tt()] when using `simplify = TRUE` and [formatters::make_row_df()] to have a |
|
| 33 |
#' comprehensive view of the hierarchical structure of the rows. |
|
| 34 |
#' |
|
| 35 |
#' @note When `parent_name` is used when constructing a layout to directly control |
|
| 36 |
#' the name of subtables in a table, that will be reflected in the 'group' values |
|
| 37 |
#' returned in the result dataframe/ard. When automatic de-duplication of sibling names |
|
| 38 |
#' is performed by `rtables`, that is automatically undone during the result |
|
| 39 |
#' df creation process, so the group values will be as if the relevant siblings |
|
| 40 |
#' had identical names. |
|
| 41 |
#' |
|
| 42 |
#' @examples |
|
| 43 |
#' lyt <- basic_table() %>% |
|
| 44 |
#' split_cols_by("ARM") %>%
|
|
| 45 |
#' split_rows_by("STRATA1") %>%
|
|
| 46 |
#' analyze(c("AGE", "BMRKR2"))
|
|
| 47 |
#' |
|
| 48 |
#' tbl <- build_table(lyt, ex_adsl) |
|
| 49 |
#' as_result_df(tbl, simplify = TRUE) |
|
| 50 |
#' |
|
| 51 |
#' @name data.frame_export |
|
| 52 |
#' @export |
|
| 53 |
as_result_df <- function(tt, spec = NULL, |
|
| 54 |
data_format = c("full_precision", "strings", "numeric"),
|
|
| 55 |
make_ard = FALSE, |
|
| 56 |
expand_colnames = FALSE, |
|
| 57 |
keep_label_rows = FALSE, |
|
| 58 |
add_tbl_name_split = FALSE, |
|
| 59 |
simplify = FALSE, |
|
| 60 |
verbose = FALSE, |
|
| 61 |
...) {
|
|
| 62 | 51x |
data_format <- data_format[[1]] |
| 63 | 51x |
checkmate::assert_class(tt, "VTableTree") |
| 64 | 51x |
checkmate::assert_function(spec, null.ok = TRUE) |
| 65 | 51x |
checkmate::assert_choice(data_format[[1]], choices = eval(formals(as_result_df)[["data_format"]])) |
| 66 | 51x |
checkmate::assert_flag(make_ard) |
| 67 | 51x |
checkmate::assert_flag(expand_colnames) |
| 68 | 51x |
checkmate::assert_flag(keep_label_rows) |
| 69 | 51x |
checkmate::assert_flag(simplify) |
| 70 | 51x |
checkmate::assert_flag(add_tbl_name_split) |
| 71 | 51x |
checkmate::assert_flag(verbose) |
| 72 | ||
| 73 | 51x |
if (nrow(tt) == 0) {
|
| 74 | 2x |
return(sanitize_table_struct(tt)) |
| 75 |
} |
|
| 76 | ||
| 77 | 49x |
if (make_ard) {
|
| 78 | 26x |
simplify <- FALSE |
| 79 | 26x |
expand_colnames <- TRUE |
| 80 | 26x |
keep_label_rows <- FALSE |
| 81 |
} |
|
| 82 | ||
| 83 | 49x |
if (is.null(spec)) {
|
| 84 |
# raw values |
|
| 85 | 49x |
rawvals <- cell_values(tt) |
| 86 | 49x |
cellvals_init <- .make_df_from_raw_data(rawvals, nr = nrow(tt), nc = ncol(tt)) |
| 87 | ||
| 88 | 49x |
if (data_format %in% c("strings", "numeric")) {
|
| 89 |
# we keep previous calculations to check the format of the data |
|
| 90 | 22x |
mf_tt <- matrix_form(tt) |
| 91 | 22x |
mf_result_chars <- mf_strings(mf_tt)[-seq_len(mf_nlheader(mf_tt)), -1, drop = FALSE] |
| 92 | 22x |
is_not_label_rows <- make_row_df(tt)$node_class != "LabelRow" |
| 93 | 22x |
mf_result_chars <- .remove_empty_elements(mf_result_chars, is_not_label_rows) |
| 94 | 22x |
mf_result_numeric <- .make_numeric_char_mf(mf_result_chars) |
| 95 | 22x |
mf_result_chars <- as.data.frame(mf_result_chars) |
| 96 | 22x |
mf_result_numeric <- as.data.frame(mf_result_numeric) |
| 97 | 22x |
cond1 <- !setequal(dim(mf_result_chars), dim(cellvals_init)) |
| 98 | 22x |
cond2 <- !setequal(dim(mf_result_numeric), dim(cellvals_init)) |
| 99 | 22x |
if (cond1 || cond2) {
|
| 100 | ! |
stop( |
| 101 | ! |
"The extracted numeric data.frame does not have the same dimension of the", |
| 102 | ! |
" cell values extracted with cell_values(). This is a bug. Please report it." |
| 103 | ! |
) # nocov |
| 104 |
} |
|
| 105 | ||
| 106 | 22x |
colnames(mf_result_chars) <- colnames(cellvals_init) |
| 107 | 22x |
colnames(mf_result_numeric) <- colnames(cellvals_init) |
| 108 | 22x |
if (data_format == "strings") {
|
| 109 | 5x |
cellvals <- mf_result_chars |
| 110 | 5x |
if (isTRUE(make_ard)) {
|
| 111 | ! |
stop("make_ard = TRUE is not compatible with data_format = 'strings'")
|
| 112 |
} |
|
| 113 | 17x |
} else if (data_format == "numeric") {
|
| 114 | 17x |
if (isTRUE(make_ard)) {
|
| 115 | 13x |
cellvals <- .convert_to_character(mf_result_numeric) |
| 116 |
} else {
|
|
| 117 | 4x |
cellvals <- mf_result_numeric |
| 118 |
} |
|
| 119 |
} |
|
| 120 | 22x |
diff_in_cellvals <- length(unlist(cellvals_init)) - length(unlist(cellvals)) |
| 121 | 22x |
if (make_ard && abs(diff_in_cellvals) > 0) {
|
| 122 | 1x |
warning_msg <- paste0( |
| 123 | 1x |
"Found ", abs(diff_in_cellvals), " cell values that differ from ", |
| 124 | 1x |
"printed values. This is possibly related to conditional formatting. " |
| 125 |
) |
|
| 126 | ||
| 127 |
# number of values difference mask between cellvals and cellvals_init (TRUE if different) |
|
| 128 | 1x |
dmc <- .lengths_with_nulls(unlist(cellvals, recursive = FALSE)) != |
| 129 | 1x |
.lengths_with_nulls(unlist(cellvals_init, recursive = FALSE)) |
| 130 | 1x |
dmc <- matrix(dmc, nrow = nrow(cellvals), ncol = ncol(cellvals)) |
| 131 | ||
| 132 |
# Mainly used for debugging |
|
| 133 | 1x |
warning_msg <- if (verbose) { # beware that \n will break this (use make_row_df(tt)$self_extent for fix)
|
| 134 | 1x |
selected_rows_to_print <- mf_strings(mf_tt)[-seq_len(mf_nlheader(mf_tt)), , drop = FALSE] |
| 135 | 1x |
selected_rows_to_print <- selected_rows_to_print[!make_row_df(tt)$node_class == "LabelRow", , drop = FALSE] |
| 136 | 1x |
selected_rows_to_print <- cbind( |
| 137 | 1x |
which(apply(dmc, 1, any, simplify = TRUE)), |
| 138 | 1x |
as.data.frame(selected_rows_to_print[apply(dmc, 1, any), , drop = FALSE]) |
| 139 |
) |
|
| 140 | 1x |
colnames(selected_rows_to_print) <- c("row_number", "row_name", colnames(cellvals_init))
|
| 141 | 1x |
paste0( |
| 142 | 1x |
warning_msg, |
| 143 | 1x |
"\n", |
| 144 | 1x |
"The following row names were modified: ", |
| 145 | 1x |
paste(selected_rows_to_print$row_name, sep = ", ", collapse = ", "), |
| 146 | 1x |
"\n" |
| 147 |
) |
|
| 148 |
} else {
|
|
| 149 | ! |
paste0(warning_msg, "To see the affected row names use `verbose = TRUE`.") |
| 150 |
} |
|
| 151 | 1x |
warning(warning_msg) |
| 152 | 1x |
cellvals[dmc] <- cellvals_init[dmc] |
| 153 |
} |
|
| 154 |
} else {
|
|
| 155 | 27x |
cellvals <- cellvals_init |
| 156 |
} |
|
| 157 | ||
| 158 | 49x |
rdf <- make_row_df(tt) |
| 159 | ||
| 160 | 49x |
df <- rdf[, c("name", "label", "abs_rownumber", "path", "reprint_inds", "node_class")]
|
| 161 |
# Removing initial root elements from path (out of the loop -> right maxlen) |
|
| 162 | 49x |
df$path <- lapply(df$path, .fix_raw_row_path, |
| 163 | 49x |
which_root_name = c("root", "rbind_root"),
|
| 164 | 49x |
all = TRUE |
| 165 |
) |
|
| 166 | ||
| 167 |
# Correcting maxlen for even number of paths (only multianalysis diff table names) |
|
| 168 | 49x |
maxlen <- max(lengths(df$path)) |
| 169 | 49x |
if (maxlen %% 2 != 0) {
|
| 170 | 10x |
maxlen <- if (add_tbl_name_split) {
|
| 171 | 4x |
maxlen + 1 |
| 172 |
} else {
|
|
| 173 | 6x |
maxlen - 1 |
| 174 |
} |
|
| 175 |
} |
|
| 176 | ||
| 177 |
# Loop for metadata (path and details from make_row_df) |
|
| 178 | 49x |
metadf <- do.call( |
| 179 | 49x |
rbind.data.frame, |
| 180 | 49x |
lapply( |
| 181 | 49x |
seq_len(NROW(df)), |
| 182 | 49x |
function(ii) {
|
| 183 | 897x |
handle_rdf_row(df[ii, ], maxlen = maxlen, add_tbl_name_split = add_tbl_name_split) |
| 184 |
} |
|
| 185 |
) |
|
| 186 |
) |
|
| 187 | ||
| 188 |
# Should we keep label rows with NAs instead of values? |
|
| 189 | 49x |
if (keep_label_rows) {
|
| 190 | 6x |
cellvals_mat_struct <- as.data.frame( |
| 191 | 6x |
matrix(NA, nrow = nrow(rdf), ncol = ncol(cellvals)) |
| 192 |
) |
|
| 193 | 6x |
colnames(cellvals_mat_struct) <- colnames(cellvals) |
| 194 | 6x |
cellvals_mat_struct[metadf$node_class != "LabelRow", ] <- cellvals |
| 195 | 6x |
ret <- cbind(metadf, cellvals_mat_struct) |
| 196 |
} else {
|
|
| 197 | 43x |
ret <- cbind( |
| 198 | 43x |
metadf[metadf$node_class != "LabelRow", ], |
| 199 | 43x |
cellvals |
| 200 |
) |
|
| 201 |
} |
|
| 202 | ||
| 203 |
# Fix for content rows analysis variable label |
|
| 204 | 49x |
if (any(ret$node_class == "ContentRow")) {
|
| 205 | 18x |
where_to <- which(ret$node_class == "ContentRow") |
| 206 | 18x |
for (crow_i in where_to) {
|
| 207 |
# For each Content row, extract the row split that is used as analysis variable |
|
| 208 | 66x |
tmp_tbl <- ret[crow_i, , drop = FALSE] |
| 209 | 66x |
na_labels <- lapply(tmp_tbl, is.na) %>% unlist(use.names = FALSE) |
| 210 | 66x |
group_to_take <- colnames(tmp_tbl[, !na_labels]) |
| 211 | 66x |
group_to_take <- group_to_take[grep("^group[0-9]+$", group_to_take)]
|
| 212 | ||
| 213 |
# Final assignment of each Content row to its correct analysis label |
|
| 214 | 66x |
ret$avar_name[crow_i] <- ret[[group_to_take[length(group_to_take)]]][crow_i] |
| 215 |
} |
|
| 216 |
} |
|
| 217 | ||
| 218 |
# If we want to expand colnames |
|
| 219 | 49x |
if (expand_colnames) {
|
| 220 | 32x |
col_name_structure <- .get_formatted_colnames(clayout(tt)) |
| 221 | 32x |
number_of_non_data_cols <- which(colnames(ret) == "node_class") |
| 222 | 32x |
if (NCOL(ret) - number_of_non_data_cols != NCOL(col_name_structure)) {
|
| 223 | ! |
stop( |
| 224 | ! |
"When expanding colnames structure, we were not able to find the same", |
| 225 | ! |
" number of columns as in the result data frame. This is a bug. Please report it." |
| 226 | ! |
) # nocov |
| 227 |
} |
|
| 228 | ||
| 229 | 32x |
buffer_rows_for_colnames <- matrix( |
| 230 | 32x |
rep("<only_for_column_names>", number_of_non_data_cols * NROW(col_name_structure)),
|
| 231 | 32x |
nrow = NROW(col_name_structure) |
| 232 |
) |
|
| 233 | ||
| 234 | 32x |
header_colnames_matrix <- cbind(buffer_rows_for_colnames, data.frame(col_name_structure)) |
| 235 | 32x |
colnames(header_colnames_matrix) <- colnames(ret) |
| 236 | ||
| 237 | 32x |
count_row <- NULL |
| 238 | 32x |
if (disp_ccounts(tt)) {
|
| 239 | 7x |
ccounts <- col_counts(tt) |
| 240 | 7x |
if (data_format == "strings") {
|
| 241 | 2x |
ccounts <- mf_strings(mf_tt)[mf_nlheader(mf_tt), ] |
| 242 | 2x |
ccounts <- .remove_empty_elements(ccounts) |
| 243 |
} |
|
| 244 | 7x |
count_row <- c(rep("<only_for_column_counts>", number_of_non_data_cols), ccounts)
|
| 245 | 7x |
header_colnames_matrix <- rbind(header_colnames_matrix, count_row) |
| 246 |
} |
|
| 247 | 32x |
ret <- rbind(header_colnames_matrix, ret) |
| 248 |
} |
|
| 249 | ||
| 250 |
# make_ard ----------------------------------------------------------------- |
|
| 251 |
# ARD part for one stat per row |
|
| 252 | 49x |
if (make_ard) {
|
| 253 | 26x |
cinfo_df <- col_info(tt) |
| 254 | 26x |
ci_coltree <- coltree(cinfo_df) |
| 255 | 26x |
column_split_names <- .get_column_split_name(ci_coltree) # used only in make_ard |
| 256 | ||
| 257 |
# Unnecessary columns |
|
| 258 | 26x |
ret_tmp <- ret[, !colnames(ret) %in% c("row_num", "is_group_summary", "node_class")]
|
| 259 | 26x |
n_row_groups <- sapply(colnames(ret), function(x) {
|
| 260 | 314x |
if (grepl("^group", x)) {
|
| 261 |
# Extract the number after "group" using regex |
|
| 262 | 52x |
as.numeric(sub("group(\\d+).*", "\\1", x))
|
| 263 |
} else {
|
|
| 264 | 262x |
0 # Return 0 if no "group" is found |
| 265 |
} |
|
| 266 |
}) %>% |
|
| 267 | 26x |
max() |
| 268 | ||
| 269 |
# Indexes of real columns (visible in the output, but no row names) |
|
| 270 | 26x |
only_col_indexes <- seq(which(colnames(ret_tmp) == "label_name") + 1, ncol(ret_tmp)) |
| 271 | ||
| 272 |
# Core row names |
|
| 273 | 26x |
col_label_rows <- grepl("<only_for_column_*", ret_tmp$avar_name)
|
| 274 | 26x |
number_of_col_splits <- sum(grepl("<only_for_column_names>", ret_tmp$avar_name))
|
| 275 | 26x |
core_row_names <- ret_tmp[!col_label_rows, -only_col_indexes] |
| 276 | 26x |
colnames_to_rename <- colnames(core_row_names) %in% c("avar_name", "row_name", "label_name")
|
| 277 |
# instead of avar_name row_name label_name ("variable_label" is not present in ARDs)
|
|
| 278 | 26x |
colnames(core_row_names)[colnames_to_rename] <- c("variable", "variable_level", "variable_label")
|
| 279 | ||
| 280 |
# Adding stats_names if present |
|
| 281 | 26x |
raw_stat_names <- .get_stat_names_from_table(tt, add_labrows = keep_label_rows) |
| 282 | 26x |
cell_stat_names <- .make_df_from_raw_data(raw_stat_names, nr = nrow(tt), nc = ncol(tt)) |
| 283 | ||
| 284 |
# Moving colnames to rows (flattening) |
|
| 285 | 26x |
ret_w_cols <- NULL |
| 286 |
# Looping on statistical columns |
|
| 287 | 26x |
for (col_i in only_col_indexes) {
|
| 288 |
# Making row splits into row specifications (group1 group1_level) |
|
| 289 | 106x |
current_col_split_level <- unlist(ret_tmp[seq_len(number_of_col_splits), col_i], use.names = FALSE) |
| 290 | 106x |
col_split_names <- column_split_names[[1]][[1]] # cross section of the column split names (not values) |
| 291 | 106x |
more_than_one_name_in_csn <- sapply(col_split_names, length) > 1 |
| 292 | ||
| 293 |
# Correction for cases where there is split_cols_by_multivar |
|
| 294 | 106x |
if (any(more_than_one_name_in_csn)) {
|
| 295 | 8x |
col_split_names[more_than_one_name_in_csn] <- lapply( |
| 296 | 8x |
seq(sum(more_than_one_name_in_csn)), |
| 297 | 8x |
function(i) {
|
| 298 | 8x |
paste0("multivar_split", i)
|
| 299 |
} |
|
| 300 |
) |
|
| 301 |
} |
|
| 302 | ||
| 303 |
# Alternated association of split names and values (along with group split) |
|
| 304 | 106x |
flattened_cols_names <- .c_alternated(col_split_names, current_col_split_level) |
| 305 | 106x |
names(flattened_cols_names) <- .c_alternated( |
| 306 | 106x |
paste0("group", seq_along(col_split_names) + n_row_groups),
|
| 307 | 106x |
paste0("group", seq_along(current_col_split_level) + n_row_groups, "_level")
|
| 308 |
) |
|
| 309 | ||
| 310 | 106x |
if (n_row_groups > 0) {
|
| 311 | 93x |
tmp_core_ret_by_col_i <- cbind( |
| 312 | 93x |
core_row_names[, seq(n_row_groups * 2)], |
| 313 | 93x |
t(data.frame(flattened_cols_names)), |
| 314 | 93x |
core_row_names[, -seq(n_row_groups * 2)], |
| 315 | 93x |
row.names = NULL |
| 316 |
) |
|
| 317 |
} else {
|
|
| 318 | 13x |
tmp_core_ret_by_col_i <- cbind( |
| 319 | 13x |
t(data.frame(flattened_cols_names)), |
| 320 | 13x |
core_row_names, |
| 321 | 13x |
row.names = NULL |
| 322 |
) |
|
| 323 |
} |
|
| 324 | ||
| 325 |
# retrieving stat names and stats |
|
| 326 | 106x |
stat_name <- setNames(cell_stat_names[, col_i - min(only_col_indexes) + 1, drop = TRUE], NULL) |
| 327 | 106x |
stat <- setNames(ret_tmp[!col_label_rows, col_i, drop = TRUE], NULL) |
| 328 | 106x |
necessary_stat_lengths <- lapply(stat, length) |
| 329 | 106x |
stat[!lengths(stat) > 0] <- NA |
| 330 | ||
| 331 |
# Truncating or adding NA if stat names has more or less elements than stats |
|
| 332 | 106x |
stat_name <- lapply(seq_along(stat_name), function(sn_i) {
|
| 333 | 1748x |
unlist(stat_name[[sn_i]], use.names = FALSE)[seq_len(necessary_stat_lengths[[sn_i]])] |
| 334 |
}) |
|
| 335 | 106x |
stat_name[!nzchar(stat_name)] <- NA |
| 336 | 106x |
stat_name[!lengths(stat_name) > 0] <- NA |
| 337 | ||
| 338 |
# unnesting stat_name and stat |
|
| 339 | 106x |
tmp_ret_by_col_i <- NULL |
| 340 | 106x |
for (row_i in seq_along(stat)) {
|
| 341 | 1748x |
tmp_ret_by_col_i <- rbind( |
| 342 | 1748x |
tmp_ret_by_col_i, |
| 343 | 1748x |
cbind( |
| 344 | 1748x |
tmp_core_ret_by_col_i[row_i, ], |
| 345 | 1748x |
stat_name = stat_name[[row_i]], |
| 346 | 1748x |
stat = stat[[row_i]], |
| 347 | 1748x |
row.names = NULL |
| 348 |
) |
|
| 349 |
) |
|
| 350 |
} |
|
| 351 | ||
| 352 | 106x |
ret_w_cols <- rbind(ret_w_cols, tmp_ret_by_col_i) |
| 353 |
} |
|
| 354 | ||
| 355 |
# If add_tbl_str_decimals is not present, we need to call the function again to keep precision |
|
| 356 | 26x |
add_tbl_str_decimals <- list(...)$add_tbl_str_decimals |
| 357 | 26x |
if (is.null(add_tbl_str_decimals) || isTRUE(add_tbl_str_decimals)) {
|
| 358 |
# Trying to extract strings as numeric for comparison |
|
| 359 | 13x |
tryCatch( |
| 360 |
{
|
|
| 361 | 13x |
stat_string_ret <- as_result_df( |
| 362 | 13x |
tt = tt, spec = spec, data_format = "numeric", |
| 363 | 13x |
make_ard = TRUE, add_tbl_str_decimals = FALSE, verbose = verbose |
| 364 |
) |
|
| 365 | 13x |
ret_w_cols <- cbind(ret_w_cols, "stat_string" = stat_string_ret$stat) |
| 366 |
}, |
|
| 367 | 13x |
error = function(e) {
|
| 368 | ! |
warning("Could not add 'stat_string' column to the result data frame. Error: ", e$message)
|
| 369 |
} |
|
| 370 |
) |
|
| 371 |
} |
|
| 372 | ||
| 373 | 26x |
ret <- ret_w_cols |
| 374 |
} |
|
| 375 | ||
| 376 |
# Simplify the result data frame |
|
| 377 | 49x |
out <- if (simplify) {
|
| 378 | 6x |
.simplify_result_df(ret) |
| 379 |
} else {
|
|
| 380 | 43x |
ret |
| 381 |
} |
|
| 382 | ||
| 383 |
# take out rownames |
|
| 384 | 49x |
rownames(out) <- NULL |
| 385 |
} else {
|
|
| 386 |
# Applying specs |
|
| 387 | ! |
out <- spec(tt, ...) |
| 388 |
} |
|
| 389 | ||
| 390 | 49x |
out |
| 391 |
} |
|
| 392 | ||
| 393 |
.lengths_with_nulls <- function(lst) {
|
|
| 394 | 2x |
sapply(lst, function(x) if (is.null(x)) 1 else length(x)) |
| 395 |
} |
|
| 396 | ||
| 397 | ||
| 398 |
# Helper function used to structure the raw values into a dataframe |
|
| 399 |
.make_df_from_raw_data <- function(rawvals, nr, nc) {
|
|
| 400 |
## if the table has one row and multiple columns, sometimes the cell values returns a list of the cell values |
|
| 401 |
## rather than a list of length 1 representing the single row. This is bad but may not be changeable |
|
| 402 |
## at this point. |
|
| 403 | 75x |
if (nr == 1 && length(rawvals) > 1) {
|
| 404 | 6x |
rawvals <- list(rawvals) |
| 405 |
} |
|
| 406 | ||
| 407 |
# Flatten the list of lists (rows) of cell values into a data frame |
|
| 408 | 75x |
cellvals <- as.data.frame(do.call(rbind, rawvals)) |
| 409 | ||
| 410 | 75x |
if (nr == 1 && nc == 1) {
|
| 411 | 9x |
if (length(unlist(rawvals)) > 1) { # This happens only with nr = nc = 1 for raw values
|
| 412 | 4x |
cellvals <- as.data.frame(I(rawvals)) |
| 413 |
} |
|
| 414 | 9x |
colnames(cellvals) <- names(rawvals) |
| 415 |
} |
|
| 416 | ||
| 417 | 75x |
row.names(cellvals) <- NULL |
| 418 | 75x |
cellvals |
| 419 |
} |
|
| 420 | ||
| 421 |
# Is there a better alternative? |
|
| 422 |
.c_alternated <- function(v1, v2) {
|
|
| 423 | 212x |
unlist(mapply(c, v1, v2, SIMPLIFY = FALSE)) |
| 424 |
} |
|
| 425 | ||
| 426 |
# Amazing helper function to get the statistic names from row cells! |
|
| 427 |
.get_stat_names_from_table <- function(tt, add_labrows = FALSE) {
|
|
| 428 |
# omit_labrows # omit label rows |
|
| 429 | 26x |
rows <- collect_leaves(tt, incl.cont = TRUE, add.labrows = add_labrows) |
| 430 | 26x |
lapply(rows, function(ri) {
|
| 431 | 306x |
lapply(row_cells(ri), obj_stat_names) |
| 432 |
}) |
|
| 433 |
} |
|
| 434 | ||
| 435 |
# Helper function to get column split names |
|
| 436 |
.get_column_split_name <- function(ci_coltree) {
|
|
| 437 |
# ci stands for column information |
|
| 438 | 164x |
if (is(ci_coltree, "LayoutAxisTree")) {
|
| 439 | 58x |
kids <- tree_children(ci_coltree) |
| 440 | 58x |
return(lapply(kids, .get_column_split_name)) |
| 441 |
} |
|
| 442 | ||
| 443 | 106x |
lapply(pos_splits(tree_pos(ci_coltree)), function(x) {
|
| 444 | 184x |
pl <- spl_payload(x) |
| 445 | 184x |
if (!is.null(pl)) { # it is null when all obs (1 column)
|
| 446 | 182x |
pl |
| 447 |
} else {
|
|
| 448 | 2x |
x@name |
| 449 |
} |
|
| 450 |
}) |
|
| 451 |
} |
|
| 452 | ||
| 453 |
# Function that selects specific outputs from the result data frame |
|
| 454 |
.simplify_result_df <- function(df) {
|
|
| 455 | 6x |
col_df <- colnames(df) |
| 456 | 6x |
if (!all(c("label_name", "node_class") %in% col_df)) {
|
| 457 | ! |
stop("Please simplify the result data frame only when it has 'label_name' and 'node_class' columns.")
|
| 458 |
} |
|
| 459 | 6x |
label_names_col <- which(col_df == "label_name") |
| 460 | 6x |
result_cols <- seq(which(col_df == "node_class") + 1, length(col_df)) |
| 461 | ||
| 462 | 6x |
df[, c(label_names_col, result_cols)] |
| 463 |
} |
|
| 464 | ||
| 465 |
.remove_empty_elements <- function(char_df, is_not_label_rows) {
|
|
| 466 | 24x |
if (is.null(dim(char_df))) {
|
| 467 | 2x |
return(char_df[nzchar(char_df, keepNA = TRUE)]) |
| 468 |
} |
|
| 469 | 22x |
rows_to_remove <- apply(char_df, 1, function(row_i) all(!nzchar(row_i, keepNA = TRUE)), simplify = TRUE) |
| 470 | 22x |
char_df[!rows_to_remove | is_not_label_rows, , drop = FALSE] |
| 471 |
} |
|
| 472 | ||
| 473 |
# Helper function to make the character matrix numeric |
|
| 474 |
.make_numeric_char_mf <- function(char_df) {
|
|
| 475 | 22x |
if (is.null(dim(char_df))) {
|
| 476 | ! |
ret <- lapply(char_df[[1]], function(x) {
|
| 477 | ! |
as.numeric(stringi::stri_extract_all(x, regex = "\\d+.\\d+|\\d+")[[1]]) |
| 478 | ! |
}) # keeps the list (single element) for data.frame |
| 479 | ! |
return(I(ret)) |
| 480 |
} |
|
| 481 | ||
| 482 | 22x |
ret <- apply(char_df, 2, function(col_i) {
|
| 483 | 83x |
out <- lapply( |
| 484 | 83x |
stringi::stri_extract_all(col_i, regex = "\\d+.\\d+|\\d+"), |
| 485 | 83x |
as.numeric |
| 486 |
) |
|
| 487 | 83x |
if (all(dim(char_df) == c(1, 1)) && is.list(out[[1]])) {
|
| 488 | ! |
unlist(out, use.names = FALSE) |
| 489 |
} else {
|
|
| 490 | 83x |
out |
| 491 |
} |
|
| 492 | 22x |
}, simplify = FALSE) |
| 493 | ||
| 494 | 22x |
do.call(cbind, ret) |
| 495 |
} |
|
| 496 | ||
| 497 |
make_result_df_md_colnames <- function(maxlen) {
|
|
| 498 | 897x |
spllen <- floor((maxlen - 2) / 2) |
| 499 | 897x |
ret <- character() |
| 500 | 897x |
if (spllen > 0) {
|
| 501 | 831x |
ret <- paste("group", rep(seq_len(spllen), each = 2), c("", "_level"), sep = "")
|
| 502 |
} |
|
| 503 | 897x |
ret <- c(ret, c("avar_name", "row_name", "label_name", "row_num", "is_group_summary", "node_class"))
|
| 504 |
} |
|
| 505 | ||
| 506 |
do_label_row <- function(rdfrow, maxlen, add_tbl_name_split = FALSE) {
|
|
| 507 | 299x |
pth <- rdfrow$path[[1]] |
| 508 |
# Adjusting for the fact that we have two columns for each split |
|
| 509 | 299x |
extra_nas_from_splits <- floor((maxlen - length(pth)) / 2) * 2 |
| 510 | ||
| 511 |
# Special cases with hidden labels |
|
| 512 | 299x |
if (length(pth) %% 2 == 1) {
|
| 513 | 204x |
extra_nas_from_splits <- extra_nas_from_splits + 1 |
| 514 |
} else {
|
|
| 515 | 95x |
if (isTRUE(add_tbl_name_split)) {
|
| 516 | 8x |
pth <- c("<analysis_spl_tbl_name>", pth)
|
| 517 | 8x |
extra_nas_from_splits <- extra_nas_from_splits - 1 |
| 518 |
} else {
|
|
| 519 | 87x |
pth <- pth[-1] |
| 520 | 87x |
extra_nas_from_splits <- extra_nas_from_splits + 1 |
| 521 |
} |
|
| 522 |
} |
|
| 523 | ||
| 524 | 299x |
c( |
| 525 | 299x |
as.list(pth[seq_len(length(pth) - 1)]), |
| 526 | 299x |
as.list(replicate(extra_nas_from_splits, list(NA_character_))), |
| 527 | 299x |
as.list(tail(pth, 1)), |
| 528 | 299x |
list( |
| 529 | 299x |
label_name = rdfrow$label, |
| 530 | 299x |
row_num = rdfrow$abs_rownumber, |
| 531 | 299x |
content = FALSE, |
| 532 | 299x |
node_class = rdfrow$node_class |
| 533 |
) |
|
| 534 |
) |
|
| 535 |
} |
|
| 536 | ||
| 537 |
do_content_row <- function(rdfrow, maxlen) {
|
|
| 538 | 66x |
pth <- rdfrow$path[[1]] |
| 539 | 66x |
contpos <- which(pth == "@content") |
| 540 | ||
| 541 | 66x |
seq_before <- seq_len(contpos - 1) |
| 542 | ||
| 543 | 66x |
c( |
| 544 | 66x |
as.list(pth[seq_before]), |
| 545 | 66x |
as.list(replicate(maxlen - contpos, list(NA_character_))), |
| 546 | 66x |
list(tail(pth, 1)), |
| 547 | 66x |
list( |
| 548 | 66x |
label_name = rdfrow$label, |
| 549 | 66x |
row_num = rdfrow$abs_rownumber, |
| 550 | 66x |
content = TRUE, |
| 551 | 66x |
node_class = rdfrow$node_class |
| 552 |
) |
|
| 553 |
) |
|
| 554 |
} |
|
| 555 | ||
| 556 |
do_data_row <- function(rdfrow, maxlen, add_tbl_name_split = FALSE) {
|
|
| 557 | 532x |
pth <- rdfrow$path[[1]] |
| 558 | 532x |
pthlen <- length(pth) |
| 559 |
## odd means we have a multi-analsysis step in the path, we do not want this in the result |
|
| 560 | 532x |
if (pthlen %% 2 == 1 && pthlen > 1) {
|
| 561 |
# we remove the last element, as it is a fake split (tbl_name from analyse) |
|
| 562 |
# pth <- pth[-1 * (pthlen - 2)] |
|
| 563 | 31x |
if (isTRUE(add_tbl_name_split)) {
|
| 564 | 14x |
pth <- c("<analysis_spl_tbl_name>", pth)
|
| 565 |
} else {
|
|
| 566 | 17x |
pth <- pth[-1] |
| 567 |
} |
|
| 568 |
} |
|
| 569 | 532x |
pthlen_new <- length(pth) |
| 570 | 532x |
if (pthlen_new == 1) {
|
| 571 | 33x |
pthlen_new <- 3 # why? |
| 572 |
} |
|
| 573 | 532x |
c( |
| 574 | 532x |
as.list(pth[seq_len(pthlen_new - 2)]), |
| 575 | 532x |
replicate(ifelse((maxlen - pthlen_new) > 0, maxlen - pthlen_new, 0), list(NA_character_)), |
| 576 | 532x |
as.list(tail(pth, 2)), |
| 577 | 532x |
list( |
| 578 | 532x |
label_name = rdfrow$label, |
| 579 | 532x |
row_num = rdfrow$abs_rownumber, |
| 580 | 532x |
content = FALSE, |
| 581 | 532x |
node_class = rdfrow$node_class |
| 582 |
) |
|
| 583 |
) |
|
| 584 |
} |
|
| 585 | ||
| 586 |
deuniqify_path_elements <- function(path) {
|
|
| 587 | 898x |
gsub("\\[[[:digit:]]+\\]$", "", path)
|
| 588 |
} |
|
| 589 | ||
| 590 |
.fix_raw_row_path <- function(path, which_root_name = c("root", "rbind_root"), all = TRUE) {
|
|
| 591 | 898x |
path <- deuniqify_path_elements(path) |
| 592 | 898x |
any_root_paths <- path[1] %in% which_root_name |
| 593 | 898x |
if (any_root_paths) {
|
| 594 | 274x |
if (isTRUE(all)) {
|
| 595 |
# Selecting the header grouping of root and rbind_root (we do not want to remove other root labels-path later) |
|
| 596 | 274x |
root_indices <- which(path %in% which_root_name) |
| 597 | 274x |
if (any(diff(root_indices) > 1)) { # integer(0) for diff means FALSE
|
| 598 | ! |
end_point_root_headers <- which(diff(root_indices) > 1)[1] |
| 599 |
} else {
|
|
| 600 | 274x |
end_point_root_headers <- length(root_indices) |
| 601 |
} |
|
| 602 | 274x |
root_path_to_remove <- seq_len(end_point_root_headers) |
| 603 |
} else {
|
|
| 604 | ! |
root_path_to_remove <- 1 |
| 605 |
} |
|
| 606 | 274x |
path <- path[-root_path_to_remove] |
| 607 |
} |
|
| 608 | ||
| 609 |
# Fix for very edge case where we have only root elements |
|
| 610 | 898x |
if (length(path) == 0) {
|
| 611 | 1x |
path <- which_root_name[1] |
| 612 |
} |
|
| 613 | ||
| 614 | 898x |
path |
| 615 |
} |
|
| 616 | ||
| 617 |
handle_rdf_row <- function(rdfrow, maxlen, add_tbl_name_split = FALSE) {
|
|
| 618 | 897x |
nclass <- rdfrow$node_class |
| 619 | ||
| 620 | 897x |
ret <- switch(nclass, |
| 621 | 897x |
LabelRow = do_label_row(rdfrow, maxlen, add_tbl_name_split = add_tbl_name_split), |
| 622 | 897x |
ContentRow = do_content_row(rdfrow, maxlen), |
| 623 | 897x |
DataRow = do_data_row(rdfrow, maxlen, add_tbl_name_split = add_tbl_name_split), |
| 624 | 897x |
stop("Unrecognized node type in row dataframe, unable to generate result data frame")
|
| 625 |
) |
|
| 626 | 897x |
setNames(ret, make_result_df_md_colnames(maxlen)) |
| 627 |
} |
|
| 628 | ||
| 629 |
# Helper recurrent function to get the column names for the result data frame from the VTableTree |
|
| 630 |
.get_formatted_colnames <- function(clyt) {
|
|
| 631 | 205x |
ret <- obj_label(clyt) |
| 632 | 205x |
if (!nzchar(ret)) {
|
| 633 | 32x |
ret <- NULL |
| 634 |
} |
|
| 635 | 205x |
if (is.null(tree_children(clyt))) {
|
| 636 | ! |
ret |
| 637 |
} else {
|
|
| 638 | 205x |
ret <- rbind(ret, do.call(cbind, lapply(tree_children(clyt), .get_formatted_colnames))) |
| 639 | 205x |
colnames(ret) <- NULL |
| 640 | 205x |
rownames(ret) <- NULL |
| 641 | 205x |
ret |
| 642 |
} |
|
| 643 |
} |
|
| 644 | ||
| 645 |
# Function to convert all elements to character while preserving structure |
|
| 646 |
.convert_to_character <- function(df) {
|
|
| 647 |
# Apply transformation to each column |
|
| 648 | 13x |
df_converted <- lapply(df, function(col) {
|
| 649 | 53x |
if (is.list(col)) {
|
| 650 |
# For columns with vector cells, convert each vector to a character vector |
|
| 651 | 53x |
I(lapply(col, as.character)) |
| 652 |
} else {
|
|
| 653 |
# For regular columns, directly convert to character |
|
| 654 | ! |
as.character(col) |
| 655 |
} |
|
| 656 |
}) |
|
| 657 |
# Return the transformed data frame |
|
| 658 | 13x |
data.frame(df_converted, stringsAsFactors = FALSE) |
| 659 |
} |
|
| 660 | ||
| 661 |
# path_enriched_df ------------------------------------------------------------ |
|
| 662 |
# |
|
| 663 |
#' @describeIn data.frame_export Transform a `TableTree` object to a path-enriched `data.frame`. |
|
| 664 |
#' |
|
| 665 |
#' @param path_fun (`function`)\cr function to transform paths into single-string row/column names. |
|
| 666 |
#' @param value_fun (`function`)\cr function to transform cell values into cells of a `data.frame`. Defaults to |
|
| 667 |
#' `collapse_values`, which creates strings where multi-valued cells are collapsed together, separated by `|`. |
|
| 668 |
#' |
|
| 669 |
#' @return |
|
| 670 |
#' * `path_enriched_df()` returns a `data.frame` of `tt`'s cell values (processed by `value_fun`, with columns named by |
|
| 671 |
#' the full column paths (processed by `path_fun` and an additional `row_path` column with the row paths (processed |
|
| 672 |
#' by `path_fun`). |
|
| 673 |
#' |
|
| 674 |
#' @examples |
|
| 675 |
#' lyt <- basic_table() %>% |
|
| 676 |
#' split_cols_by("ARM") %>%
|
|
| 677 |
#' analyze(c("AGE", "BMRKR2"))
|
|
| 678 |
#' |
|
| 679 |
#' tbl <- build_table(lyt, ex_adsl) |
|
| 680 |
#' path_enriched_df(tbl) |
|
| 681 |
#' |
|
| 682 |
#' @export |
|
| 683 |
path_enriched_df <- function(tt, path_fun = collapse_path, value_fun = collapse_values) {
|
|
| 684 | 3x |
rdf <- make_row_df(tt) |
| 685 | 3x |
cdf <- make_col_df(tt) |
| 686 | 3x |
cvs <- as.data.frame(do.call(rbind, cell_values(tt))) |
| 687 | 3x |
cvs <- as.data.frame(lapply(cvs, value_fun)) |
| 688 | 3x |
row.names(cvs) <- NULL |
| 689 | 3x |
colnames(cvs) <- path_fun(cdf$path) |
| 690 | 3x |
preppaths <- path_fun(rdf[rdf$node_class != "LabelRow", ]$path) |
| 691 | 3x |
cbind.data.frame(row_path = preppaths, cvs) |
| 692 |
} |
|
| 693 | ||
| 694 |
.collapse_char <- "|" |
|
| 695 |
.collapse_char_esc <- "\\|" |
|
| 696 | ||
| 697 |
collapse_path <- function(paths) {
|
|
| 698 | 196x |
if (is.list(paths)) {
|
| 699 | 6x |
return(vapply(paths, collapse_path, "")) |
| 700 |
} |
|
| 701 | 190x |
paste(paths, collapse = .collapse_char) |
| 702 |
} |
|
| 703 | ||
| 704 |
collapse_values <- function(colvals) {
|
|
| 705 | 13x |
if (!is.list(colvals)) { ## || all(vapply(colvals, length, 1L) == 1))
|
| 706 | ! |
return(colvals) |
| 707 | 13x |
} else if (all(vapply(colvals, length, 1L) == 1)) {
|
| 708 | 1x |
return(unlist(colvals)) |
| 709 |
} |
|
| 710 | 12x |
vapply(colvals, paste, "", collapse = .collapse_char) |
| 711 |
} |
| 1 |
#' Trimming and pruning criteria |
|
| 2 |
#' |
|
| 3 |
#' Criteria functions (and constructors thereof) for trimming and pruning tables. |
|
| 4 |
#' |
|
| 5 |
#' @inheritParams gen_args |
|
| 6 |
#' |
|
| 7 |
#' @return A logical value indicating whether `tr` should be included (`TRUE`) or pruned (`FALSE`) during pruning. |
|
| 8 |
#' |
|
| 9 |
#' @seealso [prune_table()], [trim_rows()] |
|
| 10 |
#' |
|
| 11 |
#' @details `all_zero_or_na` returns `TRUE` (and thus indicates trimming/pruning) for any *non-`LabelRow`* |
|
| 12 |
#' `TableRow` which contain only any mix of `NA` (including `NaN`), `0`, `Inf` and `-Inf` values. |
|
| 13 |
#' |
|
| 14 |
#' @examples |
|
| 15 |
#' adsl <- ex_adsl |
|
| 16 |
#' levels(adsl$SEX) <- c(levels(ex_adsl$SEX), "OTHER") |
|
| 17 |
#' adsl$AGE[adsl$SEX == "UNDIFFERENTIATED"] <- 0 |
|
| 18 |
#' adsl$BMRKR1 <- 0 |
|
| 19 |
#' |
|
| 20 |
#' tbl_to_prune <- basic_table() %>% |
|
| 21 |
#' analyze("BMRKR1") %>%
|
|
| 22 |
#' split_cols_by("ARM") %>%
|
|
| 23 |
#' split_rows_by("SEX") %>%
|
|
| 24 |
#' summarize_row_groups() %>% |
|
| 25 |
#' split_rows_by("STRATA1") %>%
|
|
| 26 |
#' summarize_row_groups() %>% |
|
| 27 |
#' analyze("AGE") %>%
|
|
| 28 |
#' build_table(adsl) |
|
| 29 |
#' |
|
| 30 |
#' tbl_to_prune %>% prune_table(all_zero_or_na) |
|
| 31 |
#' |
|
| 32 |
#' @rdname trim_prune_funs |
|
| 33 |
#' @export |
|
| 34 |
all_zero_or_na <- function(tr) {
|
|
| 35 | 347x |
if (!is(tr, "TableRow") || is(tr, "LabelRow")) {
|
| 36 | 93x |
return(FALSE) |
| 37 |
} |
|
| 38 | 254x |
rvs <- unlist(unname(row_values(tr))) |
| 39 | 254x |
all(is.na(rvs) | rvs == 0 | !is.finite(rvs)) |
| 40 |
} |
|
| 41 | ||
| 42 |
#' @details `all_zero` returns `TRUE` for any non-`LabelRow` which contains only (non-missing) zero values. |
|
| 43 |
#' |
|
| 44 |
#' @examples |
|
| 45 |
#' tbl_to_prune %>% prune_table(all_zero) |
|
| 46 |
#' |
|
| 47 |
#' @rdname trim_prune_funs |
|
| 48 |
#' @export |
|
| 49 |
all_zero <- function(tr) {
|
|
| 50 | 8x |
if (!is(tr, "TableRow") || is(tr, "LabelRow")) {
|
| 51 | ! |
return(FALSE) |
| 52 |
} |
|
| 53 | 8x |
rvs <- unlist(unname(row_values(tr))) |
| 54 | 8x |
isTRUE(all(rvs == 0)) |
| 55 |
} |
|
| 56 | ||
| 57 |
#' Trim rows from a populated table without regard for table structure |
|
| 58 |
#' |
|
| 59 |
#' @inheritParams gen_args |
|
| 60 |
#' @param criteria (`function`)\cr function which takes a `TableRow` object and returns `TRUE` if that row |
|
| 61 |
#' should be removed. Defaults to [all_zero_or_na()]. |
|
| 62 |
#' |
|
| 63 |
#' @return The table with rows that have only `NA` or 0 cell values removed. |
|
| 64 |
#' |
|
| 65 |
#' @note |
|
| 66 |
#' Visible `LabelRow`s are including in this trimming, which can lead to either all label rows being trimmed or |
|
| 67 |
#' label rows remaining when all data rows have been trimmed, depending on what `criteria` returns when called on |
|
| 68 |
#' a `LabelRow` object. To avoid this, use the structurally-aware [prune_table()] machinery instead. |
|
| 69 |
#' |
|
| 70 |
#' @details |
|
| 71 |
#' This function will be deprecated in the future in favor of the more elegant and versatile [prune_table()] |
|
| 72 |
#' function which can perform the same function as `trim_rows()` but is more powerful as it takes table structure |
|
| 73 |
#' into account. |
|
| 74 |
#' |
|
| 75 |
#' @seealso [prune_table()] |
|
| 76 |
#' |
|
| 77 |
#' @examples |
|
| 78 |
#' adsl <- ex_adsl |
|
| 79 |
#' levels(adsl$SEX) <- c(levels(ex_adsl$SEX), "OTHER") |
|
| 80 |
#' |
|
| 81 |
#' tbl_to_trim <- basic_table() %>% |
|
| 82 |
#' analyze("BMRKR1") %>%
|
|
| 83 |
#' split_cols_by("ARM") %>%
|
|
| 84 |
#' split_rows_by("SEX") %>%
|
|
| 85 |
#' summarize_row_groups() %>% |
|
| 86 |
#' split_rows_by("STRATA1") %>%
|
|
| 87 |
#' summarize_row_groups() %>% |
|
| 88 |
#' analyze("AGE") %>%
|
|
| 89 |
#' build_table(adsl) |
|
| 90 |
#' |
|
| 91 |
#' tbl_to_trim %>% trim_rows() |
|
| 92 |
#' |
|
| 93 |
#' tbl_to_trim %>% trim_rows(all_zero) |
|
| 94 |
#' |
|
| 95 |
#' @export |
|
| 96 |
trim_rows <- function(tt, criteria = all_zero_or_na) {
|
|
| 97 | 3x |
rows <- collect_leaves(tt, TRUE, TRUE) |
| 98 | 3x |
torm <- vapply(rows, criteria, |
| 99 | 3x |
NA, |
| 100 | 3x |
USE.NAMES = FALSE |
| 101 |
) |
|
| 102 | 3x |
tt[!torm, , |
| 103 | 3x |
keep_topleft = TRUE, |
| 104 | 3x |
keep_titles = TRUE, |
| 105 | 3x |
keep_footers = TRUE, |
| 106 | 3x |
reindex_refs = TRUE |
| 107 |
] |
|
| 108 |
} |
|
| 109 | ||
| 110 |
#' @inheritParams trim_rows |
|
| 111 |
#' |
|
| 112 |
#' @details |
|
| 113 |
#' `content_all_zeros_nas` prunes a subtable if both of the following are true: |
|
| 114 |
#' |
|
| 115 |
#' * It has a content table with exactly one row in it. |
|
| 116 |
#' * `all_zero_or_na` returns `TRUE` for that single content row. In practice, when the default summary/content |
|
| 117 |
#' function is used, this represents pruning any subtable which corresponds to an empty set of the input data |
|
| 118 |
#' (e.g. because a factor variable was used in [split_rows_by()] but not all levels were present in the data). |
|
| 119 |
#' |
|
| 120 |
#' @examples |
|
| 121 |
#' tbl_to_prune %>% prune_table(content_all_zeros_nas) |
|
| 122 |
#' |
|
| 123 |
#' @rdname trim_prune_funs |
|
| 124 |
#' @export |
|
| 125 |
content_all_zeros_nas <- function(tt, criteria = all_zero_or_na) {
|
|
| 126 |
## this will be NULL if |
|
| 127 |
## tt is something that doesn't have a content table |
|
| 128 | 254x |
ct <- content_table(tt) |
| 129 |
## NROW returns 0 for NULL. |
|
| 130 | 254x |
if (NROW(ct) == 0 || nrow(ct) > 1) {
|
| 131 | 242x |
return(FALSE) |
| 132 |
} |
|
| 133 | ||
| 134 | 12x |
cr <- tree_children(ct)[[1]] |
| 135 | 12x |
criteria(cr) |
| 136 |
} |
|
| 137 | ||
| 138 |
#' @details |
|
| 139 |
#' `prune_empty_level` combines `all_zero_or_na` behavior for `TableRow` objects, `content_all_zeros_nas` on |
|
| 140 |
#' `content_table(tt)` for `TableTree` objects, and an additional check that returns `TRUE` if the `tt` has no |
|
| 141 |
#' children. |
|
| 142 |
#' |
|
| 143 |
#' @examples |
|
| 144 |
#' tbl_to_prune %>% prune_table(prune_empty_level) |
|
| 145 |
#' |
|
| 146 |
#' @rdname trim_prune_funs |
|
| 147 |
#' @export |
|
| 148 |
prune_empty_level <- function(tt) {
|
|
| 149 | 389x |
if (is(tt, "TableRow")) {
|
| 150 | 151x |
return(all_zero_or_na(tt)) |
| 151 |
} |
|
| 152 | ||
| 153 | 238x |
if (content_all_zeros_nas(tt)) {
|
| 154 | 2x |
return(TRUE) |
| 155 |
} |
|
| 156 | 236x |
kids <- tree_children(tt) |
| 157 | 236x |
length(kids) == 0 |
| 158 |
} |
|
| 159 | ||
| 160 |
#' @details `prune_zeros_only` behaves as `prune_empty_level` does, except that like `all_zero` it prunes |
|
| 161 |
#' only in the case of all non-missing zero values. |
|
| 162 |
#' |
|
| 163 |
#' @examples |
|
| 164 |
#' tbl_to_prune %>% prune_table(prune_zeros_only) |
|
| 165 |
#' |
|
| 166 |
#' @rdname trim_prune_funs |
|
| 167 |
#' @export |
|
| 168 |
prune_zeros_only <- function(tt) {
|
|
| 169 | 16x |
if (is(tt, "TableRow")) {
|
| 170 | 8x |
return(all_zero(tt)) |
| 171 |
} |
|
| 172 | ||
| 173 | 8x |
if (content_all_zeros_nas(tt, criteria = all_zero)) {
|
| 174 | ! |
return(TRUE) |
| 175 |
} |
|
| 176 | 8x |
kids <- tree_children(tt) |
| 177 | 8x |
length(kids) == 0 |
| 178 |
} |
|
| 179 | ||
| 180 |
#' @param min (`numeric(1)`)\cr (used by `low_obs_pruner` only). Minimum aggregate count value. |
|
| 181 |
#' Subtables whose combined/average count are below this threshold will be pruned. |
|
| 182 |
#' @param type (`string`)\cr how count values should be aggregated. Must be `"sum"` (the default) or `"mean"`. |
|
| 183 |
#' |
|
| 184 |
#' @details |
|
| 185 |
#' `low_obs_pruner` is a *constructor function* which, when called, returns a pruning criteria function which |
|
| 186 |
#' will prune on content rows by comparing sum or mean (dictated by `type`) of the count portions of the cell |
|
| 187 |
#' values (defined as the first value per cell regardless of how many values per cell there are) against `min`. |
|
| 188 |
#' |
|
| 189 |
#' @examples |
|
| 190 |
#' min_prune <- low_obs_pruner(70, "sum") |
|
| 191 |
#' tbl_to_prune %>% prune_table(min_prune) |
|
| 192 |
#' |
|
| 193 |
#' @rdname trim_prune_funs |
|
| 194 |
#' @export |
|
| 195 |
low_obs_pruner <- function(min, type = c("sum", "mean")) {
|
|
| 196 | 3x |
type <- match.arg(type) |
| 197 | 3x |
function(tt) {
|
| 198 | 21x |
if (is(tt, "TableRow") || NROW(ctab <- content_table(tt)) != 1) { ## note the <- in there!!!
|
| 199 | 9x |
return(FALSE) ## only trimming on count content rows |
| 200 |
} |
|
| 201 | 12x |
ctr <- tree_children(ctab)[[1]] |
| 202 | 12x |
vals <- sapply(row_values(ctr), function(v) v[[1]]) |
| 203 | 12x |
sumvals <- sum(vals) |
| 204 | 12x |
if (type == "mean") {
|
| 205 | 8x |
sumvals <- sumvals / length(vals) |
| 206 |
} |
|
| 207 | 12x |
sumvals < min |
| 208 |
} |
|
| 209 |
} |
|
| 210 | ||
| 211 |
#' Recursively prune a `TableTree` |
|
| 212 |
#' |
|
| 213 |
#' @inheritParams gen_args |
|
| 214 |
#' @param prune_func (`function`)\cr a function to be called on each subtree which returns `TRUE` if the |
|
| 215 |
#' entire subtree should be removed. |
|
| 216 |
#' @param stop_depth (`numeric(1)`)\cr the depth after which subtrees should not be checked for pruning. |
|
| 217 |
#' Defaults to `NA` which indicates pruning should happen at all levels. |
|
| 218 |
#' @param depth (`numeric(1)`)\cr used internally, not intended to be set by the end user. |
|
| 219 |
#' @param ... named arguments to optionally be passed down to `prune_func` if it |
|
| 220 |
#' accepts them (or `...`) |
|
| 221 |
#' |
|
| 222 |
#' @return A `TableTree` pruned via recursive application of `prune_func`. |
|
| 223 |
#' |
|
| 224 |
#' @seealso [prune_empty_level()] for details on this and several other basic pruning functions included |
|
| 225 |
#' in the `rtables` package. |
|
| 226 |
#' |
|
| 227 |
#' @examples |
|
| 228 |
#' adsl <- ex_adsl |
|
| 229 |
#' levels(adsl$SEX) <- c(levels(ex_adsl$SEX), "OTHER") |
|
| 230 |
#' |
|
| 231 |
#' tbl_to_prune <- basic_table() %>% |
|
| 232 |
#' split_cols_by("ARM") %>%
|
|
| 233 |
#' split_rows_by("SEX") %>%
|
|
| 234 |
#' summarize_row_groups() %>% |
|
| 235 |
#' split_rows_by("STRATA1") %>%
|
|
| 236 |
#' summarize_row_groups() %>% |
|
| 237 |
#' analyze("AGE") %>%
|
|
| 238 |
#' build_table(adsl) |
|
| 239 |
#' |
|
| 240 |
#' tbl_to_prune %>% prune_table() |
|
| 241 |
#' |
|
| 242 |
#' @export |
|
| 243 |
prune_table <- function(tt, |
|
| 244 |
prune_func = prune_empty_level, |
|
| 245 |
stop_depth = NA_real_, |
|
| 246 |
depth = 0, |
|
| 247 |
...) {
|
|
| 248 | 325x |
if (!is.na(stop_depth) && depth > stop_depth) {
|
| 249 | ! |
return(tt) |
| 250 |
} |
|
| 251 | 325x |
if (is(tt, "TableRow")) {
|
| 252 | 54x |
if (prune_func(tt)) {
|
| 253 | ! |
tt <- NULL |
| 254 |
} |
|
| 255 | 54x |
return(tt) |
| 256 |
} |
|
| 257 | ||
| 258 | 271x |
kids <- tree_children(tt) |
| 259 | ||
| 260 | 271x |
more_args <- match_fun_args(prune_func, depth = depth, ...) |
| 261 | 271x |
torm <- vapply(kids, function(tb) {
|
| 262 | 390x |
!is.null(tb) && do.call(prune_func, c(list(tb), more_args)) |
| 263 | 271x |
}, NA) |
| 264 | ||
| 265 | 270x |
keepkids <- kids[!torm] |
| 266 | 270x |
keepkids <- lapply(keepkids, prune_table, |
| 267 | 270x |
prune_func = prune_func, |
| 268 | 270x |
stop_depth = stop_depth, |
| 269 | 270x |
depth = depth + 1, |
| 270 |
... |
|
| 271 |
) |
|
| 272 | ||
| 273 | 270x |
keepkids <- keepkids[!vapply(keepkids, is.null, NA)] |
| 274 | 270x |
if (length(keepkids) > 0) {
|
| 275 | 135x |
tree_children(tt) <- keepkids |
| 276 |
} else {
|
|
| 277 | 135x |
tt <- NULL |
| 278 |
} |
|
| 279 | 270x |
tt |
| 280 |
} |
| 1 |
#' Variable associated with a split |
|
| 2 |
#' |
|
| 3 |
#' This function is intended for use when writing custom splitting logic. In cases where the split is associated with |
|
| 4 |
#' a single variable, the name of that variable will be returned. At time of writing this includes splits generated |
|
| 5 |
#' via the [split_rows_by()], [split_cols_by()], [split_rows_by_cuts()], [split_cols_by_cuts()], |
|
| 6 |
#' [split_rows_by_cutfun()], and [split_cols_by_cutfun()] layout directives. |
|
| 7 |
#' |
|
| 8 |
#' @param spl (`VarLevelSplit`)\cr the split object. |
|
| 9 |
#' |
|
| 10 |
#' @return For splits with a single variable associated with them, returns the split. Otherwise, an error is raised. |
|
| 11 |
#' |
|
| 12 |
#' @export |
|
| 13 |
#' @seealso \code{\link{make_split_fun}}
|
|
| 14 | 2x |
setGeneric("spl_variable", function(spl) standardGeneric("spl_variable"))
|
| 15 | ||
| 16 |
#' @rdname spl_variable |
|
| 17 |
#' @export |
|
| 18 | 1x |
setMethod("spl_variable", "VarLevelSplit", function(spl) spl_payload(spl))
|
| 19 | ||
| 20 |
#' @rdname spl_variable |
|
| 21 |
#' @export |
|
| 22 | ! |
setMethod("spl_variable", "VarDynCutSplit", function(spl) spl_payload(spl))
|
| 23 | ||
| 24 |
#' @rdname spl_variable |
|
| 25 |
#' @export |
|
| 26 | ! |
setMethod("spl_variable", "VarStaticCutSplit", function(spl) spl_payload(spl))
|
| 27 | ||
| 28 |
#' @rdname spl_variable |
|
| 29 |
#' @export |
|
| 30 |
setMethod( |
|
| 31 |
"spl_variable", "Split", |
|
| 32 | 1x |
function(spl) stop("Split class ", class(spl), " not associated with a single variable.")
|
| 33 |
) |
|
| 34 | ||
| 35 |
in_col_split <- function(spl_ctx) {
|
|
| 36 | ! |
identical( |
| 37 | ! |
names(spl_ctx), |
| 38 | ! |
names(context_df_row(cinfo = NULL)) |
| 39 |
) |
|
| 40 |
} |
|
| 41 | ||
| 42 |
assert_splres_element <- function(pinfo, nm, len = NULL, component = NULL) {
|
|
| 43 | 45x |
msg_2_append <- "" |
| 44 | 45x |
if (!is.null(component)) {
|
| 45 | 33x |
msg_2_append <- paste0( |
| 46 | 33x |
"Invalid split function constructed by upstream call to ", |
| 47 | 33x |
"make_split_fun. Problem source: ", |
| 48 | 33x |
component, " argument." |
| 49 |
) |
|
| 50 |
} |
|
| 51 | 45x |
if (!(nm %in% names(pinfo))) {
|
| 52 | ! |
stop( |
| 53 | ! |
"Split result does not have required element: ", nm, ".", |
| 54 | ! |
msg_2_append |
| 55 |
) |
|
| 56 |
} |
|
| 57 | 45x |
if (!is.null(len) && length(pinfo[[nm]]) != len) {
|
| 58 | ! |
stop( |
| 59 | ! |
"Split result element ", nm, " does not have required length ", len, ".", |
| 60 | ! |
msg_2_append |
| 61 |
) |
|
| 62 |
} |
|
| 63 | 45x |
TRUE |
| 64 |
} |
|
| 65 | ||
| 66 |
validate_split_result <- function(pinfo, component = NULL) {
|
|
| 67 | 15x |
assert_splres_element(pinfo, "datasplit", component = component) |
| 68 | 15x |
len <- length(pinfo$datasplit) |
| 69 | 15x |
assert_splres_element(pinfo, "values", len, component = component) |
| 70 | 15x |
assert_splres_element(pinfo, "labels", len, component = component) |
| 71 | 15x |
TRUE |
| 72 |
} |
|
| 73 | ||
| 74 |
#' Construct split result object |
|
| 75 |
#' |
|
| 76 |
#' These functions can be used to create or add to a split result in functions which implement core splitting or |
|
| 77 |
#' post-processing within a custom split function. |
|
| 78 |
#' |
|
| 79 |
#' @param values (`character` or `list(SplitValue)`)\cr the values associated with each facet. |
|
| 80 |
#' @param datasplit (`list(data.frame)`)\cr the facet data for each facet generated in the split. |
|
| 81 |
#' @param labels (`character`)\cr the labels associated with each facet. |
|
| 82 |
#' @param extras (`list` or `NULL`)\cr extra values associated with each of the facets which will be passed to |
|
| 83 |
#' analysis functions applied within the facet. |
|
| 84 |
#' @param subset_exprs (`list`)\cr A list of subsetting expressions (e.g., |
|
| 85 |
#' created with `quote()`) to be used during column subsetting. |
|
| 86 |
#' |
|
| 87 |
#' @return A named list representing the facets generated by the split with elements `values`, `datasplit`, and |
|
| 88 |
#' `labels`, which are the same length and correspond to each other element-wise. |
|
| 89 |
#' |
|
| 90 |
#' @details |
|
| 91 |
#' These functions performs various housekeeping tasks to ensure that the split result list is as the rtables |
|
| 92 |
#' internals expect it, most of which are not relevant to end users. |
|
| 93 |
#' |
|
| 94 |
#' @examples |
|
| 95 |
#' splres <- make_split_result( |
|
| 96 |
#' values = c("hi", "lo"),
|
|
| 97 |
#' datasplit = list(hi = mtcars, lo = mtcars[1:10, ]), |
|
| 98 |
#' labels = c("more data", "less data"),
|
|
| 99 |
#' subset_exprs = list(expression(TRUE), expression(seq_along(wt) <= 10)) |
|
| 100 |
#' ) |
|
| 101 |
#' |
|
| 102 |
#' splres2 <- add_to_split_result(splres, |
|
| 103 |
#' values = "med", |
|
| 104 |
#' datasplit = list(med = mtcars[1:20, ]), |
|
| 105 |
#' labels = "kinda some data", |
|
| 106 |
#' subset_exprs = quote(seq_along(wt) <= 20) |
|
| 107 |
#' ) |
|
| 108 |
#' |
|
| 109 |
#' @family make_custom_split |
|
| 110 |
#' @rdname make_split_result |
|
| 111 |
#' @export |
|
| 112 |
#' @family make_custom_split |
|
| 113 |
make_split_result <- function(values, datasplit, labels, extras = NULL, subset_exprs = vector("list", length(values))) {
|
|
| 114 | 9x |
if (length(values) == 1 && is(datasplit, "data.frame")) {
|
| 115 | ! |
datasplit <- list(datasplit) |
| 116 |
} |
|
| 117 | 9x |
ret <- list(values = values, datasplit = datasplit, labels = labels, subset_exprs = subset_exprs) |
| 118 | 9x |
if (!is.null(extras)) {
|
| 119 | ! |
ret$extras <- extras |
| 120 |
} |
|
| 121 | 9x |
.fixupvals(ret) |
| 122 |
} |
|
| 123 | ||
| 124 |
#' @param splres (`list`)\cr a list representing the result of splitting. |
|
| 125 |
#' |
|
| 126 |
#' @rdname make_split_result |
|
| 127 |
#' @export |
|
| 128 |
add_to_split_result <- function(splres, values, datasplit, labels, extras = NULL, subset_exprs = NULL) {
|
|
| 129 | 4x |
validate_split_result(splres) |
| 130 | 4x |
newstuff <- make_split_result(values, datasplit, labels, extras, subset_exprs = list(subset_exprs)) |
| 131 | 4x |
ret <- lapply( |
| 132 | 4x |
names(splres), |
| 133 | 4x |
function(nm) c(splres[[nm]], newstuff[[nm]]) |
| 134 |
) |
|
| 135 | 4x |
names(ret) <- names(splres) |
| 136 | 4x |
.fixupvals(ret) |
| 137 |
} |
|
| 138 | ||
| 139 | ||
| 140 | 13x |
.can_take_spl_context <- function(f) any(c(".spl_context", "...") %in% names(formals(f)))
|
| 141 | ||
| 142 |
#' Create a custom splitting function |
|
| 143 |
#' |
|
| 144 |
#' @param pre (`list`)\cr zero or more functions which operate on the incoming data and return a new data frame that |
|
| 145 |
#' should split via `core_split`. They will be called on the data in the order they appear in the list. |
|
| 146 |
#' @param core_split (`function` or `NULL`)\cr if non-`NULL`, a function which accepts the same arguments that |
|
| 147 |
#' `do_base_split` does, and returns the same type of named list. Custom functions which override this behavior |
|
| 148 |
#' cannot be used in column splits. |
|
| 149 |
#' @param post (`list`)\cr zero or more functions which should be called on the list output by splitting. |
|
| 150 |
#' |
|
| 151 |
#' @details |
|
| 152 |
#' Custom split functions can be thought of as (up to) 3 different types of manipulations of the splitting process: |
|
| 153 |
#' |
|
| 154 |
#' 1. Pre-processing of the incoming data to be split. |
|
| 155 |
#' 2. (Row-splitting only) Customization of the core mapping of incoming data to facets. |
|
| 156 |
#' 3. Post-processing operations on the set of facets (groups) generated by the split. |
|
| 157 |
#' |
|
| 158 |
#' This function provides an interface to create custom split functions by implementing and specifying sets of |
|
| 159 |
#' operations in each of those classes of customization independently. |
|
| 160 |
#' |
|
| 161 |
#' Pre-processing functions (1), must accept: `df`, `spl`, `vals`, and `labels`, and can optionally accept |
|
| 162 |
#' `.spl_context`. They then manipulate `df` (the incoming data for the split) and return a modified data frame. |
|
| 163 |
#' This modified data frame *must* contain all columns present in the incoming data frame, but can add columns if |
|
| 164 |
#' necessary (though we note that these new columns cannot be used in the layout as split or analysis variables, |
|
| 165 |
#' because they will not be present when validity checking is done). |
|
| 166 |
#' |
|
| 167 |
#' The preprocessing component is useful for things such as manipulating factor levels, e.g., to trim unobserved ones |
|
| 168 |
#' or to reorder levels based on observed counts, etc. |
|
| 169 |
#' |
|
| 170 |
#' Core splitting functions override the fundamental |
|
| 171 |
#' splitting procedure, and are only necessary in rare cases. These |
|
| 172 |
#' must accept `spl`, `df`, `vals`, `labels`, and can optionally |
|
| 173 |
#' accept `.spl_context`. They should return a split result object |
|
| 174 |
#' constructed via `make_split_result()`. |
|
| 175 |
#' |
|
| 176 |
#' In particular, if the custom split function will be used in |
|
| 177 |
#' column space, subsetting expressions (e.g., as returned by |
|
| 178 |
#' `quote()` or `bquote` must be provided, while they are |
|
| 179 |
#' optional (and largely ignored, currently) in row space. |
|
| 180 |
#' |
|
| 181 |
#' |
|
| 182 |
#' Post-processing functions (3) must accept the result of the core split as their first argument (which can be |
|
| 183 |
#' anything), in addition to `spl`, and `fulldf`, and can optionally accept `.spl_context`. They must each return a |
|
| 184 |
#' modified version of the same structure specified above for core splitting. |
|
| 185 |
#' |
|
| 186 |
#' In both the pre- and post-processing cases, multiple functions can be specified. When this happens, they are applied |
|
| 187 |
#' sequentially, in the order they appear in the list passed to the relevant argument (`pre` and `post`, respectively). |
|
| 188 |
#' |
|
| 189 |
#' @return A custom function that can be used as a split function. |
|
| 190 |
#' |
|
| 191 |
#' @seealso [custom_split_funs] for a more detailed discussion on what custom split functions do. |
|
| 192 |
#' |
|
| 193 |
#' @examples |
|
| 194 |
#' mysplitfun <- make_split_fun( |
|
| 195 |
#' pre = list(drop_facet_levels), |
|
| 196 |
#' post = list(add_overall_facet("ALL", "All Arms"))
|
|
| 197 |
#' ) |
|
| 198 |
#' |
|
| 199 |
#' basic_table(show_colcounts = TRUE) %>% |
|
| 200 |
#' split_cols_by("ARM", split_fun = mysplitfun) %>%
|
|
| 201 |
#' analyze("AGE") %>%
|
|
| 202 |
#' build_table(subset(DM, ARM %in% c("B: Placebo", "C: Combination")))
|
|
| 203 |
#' |
|
| 204 |
#' ## post (and pre) arguments can take multiple functions, here |
|
| 205 |
#' ## we add an overall facet and the reorder the facets |
|
| 206 |
#' reorder_facets <- function(splret, spl, fulldf, ...) {
|
|
| 207 |
#' ord <- order(names(splret$values)) |
|
| 208 |
#' make_split_result( |
|
| 209 |
#' splret$values[ord], |
|
| 210 |
#' splret$datasplit[ord], |
|
| 211 |
#' splret$labels[ord] |
|
| 212 |
#' ) |
|
| 213 |
#' } |
|
| 214 |
#' |
|
| 215 |
#' mysplitfun2 <- make_split_fun( |
|
| 216 |
#' pre = list(drop_facet_levels), |
|
| 217 |
#' post = list( |
|
| 218 |
#' add_overall_facet("ALL", "All Arms"),
|
|
| 219 |
#' reorder_facets |
|
| 220 |
#' ) |
|
| 221 |
#' ) |
|
| 222 |
#' basic_table(show_colcounts = TRUE) %>% |
|
| 223 |
#' split_cols_by("ARM", split_fun = mysplitfun2) %>%
|
|
| 224 |
#' analyze("AGE") %>%
|
|
| 225 |
#' build_table(subset(DM, ARM %in% c("B: Placebo", "C: Combination")))
|
|
| 226 |
#' |
|
| 227 |
#' very_stupid_core <- function(spl, df, vals, labels, .spl_context) {
|
|
| 228 |
#' make_split_result(c("stupid", "silly"),
|
|
| 229 |
#' datasplit = list(df[1:10, ], df[11:30, ]), |
|
| 230 |
#' labels = c("first 10", "second 20")
|
|
| 231 |
#' ) |
|
| 232 |
#' } |
|
| 233 |
#' |
|
| 234 |
#' dumb_30_facet <- add_combo_facet("dumb",
|
|
| 235 |
#' label = "thirty patients", |
|
| 236 |
#' levels = c("stupid", "silly")
|
|
| 237 |
#' ) |
|
| 238 |
#' nonsense_splfun <- make_split_fun( |
|
| 239 |
#' core_split = very_stupid_core, |
|
| 240 |
#' post = list(dumb_30_facet) |
|
| 241 |
#' ) |
|
| 242 |
#' |
|
| 243 |
#' ## recall core split overriding is not supported in column space |
|
| 244 |
#' ## currently, but we can see it in action in row space |
|
| 245 |
#' |
|
| 246 |
#' lyt_silly <- basic_table() %>% |
|
| 247 |
#' split_rows_by("ARM", split_fun = nonsense_splfun) %>%
|
|
| 248 |
#' summarize_row_groups() %>% |
|
| 249 |
#' analyze("AGE")
|
|
| 250 |
#' silly_table <- build_table(lyt_silly, DM) |
|
| 251 |
#' silly_table |
|
| 252 |
#' |
|
| 253 |
#' @family make_custom_split |
|
| 254 |
#' @export |
|
| 255 |
make_split_fun <- function(pre = list(), core_split = NULL, post = list()) {
|
|
| 256 | 7x |
function(df, |
| 257 | 7x |
spl, |
| 258 | 7x |
vals = NULL, |
| 259 | 7x |
labels = NULL, |
| 260 | 7x |
trim = FALSE, |
| 261 | 7x |
.spl_context) {
|
| 262 | 11x |
orig_columns <- names(df) |
| 263 | 11x |
for (pre_fn in pre) {
|
| 264 | 5x |
if (.can_take_spl_context(pre_fn)) {
|
| 265 | 5x |
df <- pre_fn(df = df, spl = spl, vals = vals, labels = labels, .spl_context = .spl_context) |
| 266 |
} else {
|
|
| 267 | ! |
df <- pre_fn(df = df, spl = spl, vals = vals, labels = labels) |
| 268 |
} |
|
| 269 | 3x |
if (!is(df, "data.frame")) {
|
| 270 | ! |
stop( |
| 271 | ! |
"Error in custom split function, pre-split step did not return a data.frame. ", |
| 272 | ! |
"See upstream call to make_split_fun for original source of error." |
| 273 |
) |
|
| 274 |
} |
|
| 275 |
} |
|
| 276 | ||
| 277 | 9x |
if (!all(orig_columns %in% names(df))) {
|
| 278 | ! |
stop( |
| 279 | ! |
"Preprocessing functions(s) in custom split function removed a column from the incoming data.", |
| 280 | ! |
" This is not supported. See upstread make_split_fun call (pre argument) for original source of error." |
| 281 |
) |
|
| 282 |
} |
|
| 283 | ||
| 284 | 9x |
if (is.null(core_split)) {
|
| 285 | 7x |
ret <- do_base_split(spl = spl, df = df, vals = vals, labels = labels) |
| 286 |
} else {
|
|
| 287 | 2x |
ret <- core_split(spl = spl, df = df, vals = vals, labels = labels, .spl_context) |
| 288 | 2x |
validate_split_result(ret, component = "core_split") |
| 289 |
} |
|
| 290 | ||
| 291 | 9x |
for (post_fn in post) {
|
| 292 | 8x |
if (.can_take_spl_context(post_fn)) {
|
| 293 | 8x |
ret <- post_fn(ret, spl = spl, .spl_context = .spl_context, fulldf = df) |
| 294 |
} else {
|
|
| 295 | ! |
ret <- post_fn(ret, spl = spl, fulldf = df) |
| 296 |
} |
|
| 297 |
} |
|
| 298 | 9x |
validate_split_result(ret, "post") |
| 299 | 9x |
ret |
| 300 |
} |
|
| 301 |
} |
|
| 302 | ||
| 303 |
#' Add a combination facet in post-processing |
|
| 304 |
#' |
|
| 305 |
#' Add a combination facet during the post-processing stage in a custom split fun. |
|
| 306 |
#' |
|
| 307 |
#' @param name (`string`)\cr name for the resulting facet (for use in pathing, etc.). |
|
| 308 |
#' @param label (`string`)\cr label for the resulting facet. |
|
| 309 |
#' @param levels (`character`)\cr vector of levels to combine within the resulting facet. |
|
| 310 |
#' @param extra (`list`)\cr extra arguments to be passed to analysis functions applied within the resulting facet. |
|
| 311 |
#' |
|
| 312 |
#' @details |
|
| 313 |
#' For `add_combo_facet`, the data associated with the resulting facet will be the data associated with the facets for |
|
| 314 |
#' each level in `levels`, row-bound together. In particular, this means that if those levels are overlapping, data |
|
| 315 |
#' that appears in both will be duplicated. |
|
| 316 |
#' |
|
| 317 |
#' @return A function which can be used within the `post` argument in [make_split_fun()]. |
|
| 318 |
#' |
|
| 319 |
#' @seealso [make_split_fun()] |
|
| 320 |
#' |
|
| 321 |
#' @examples |
|
| 322 |
#' mysplfun <- make_split_fun(post = list( |
|
| 323 |
#' add_combo_facet("A_B",
|
|
| 324 |
#' label = "Arms A+B", |
|
| 325 |
#' levels = c("A: Drug X", "B: Placebo")
|
|
| 326 |
#' ), |
|
| 327 |
#' add_overall_facet("ALL", label = "All Arms")
|
|
| 328 |
#' )) |
|
| 329 |
#' |
|
| 330 |
#' lyt <- basic_table(show_colcounts = TRUE) %>% |
|
| 331 |
#' split_cols_by("ARM", split_fun = mysplfun) %>%
|
|
| 332 |
#' analyze("AGE")
|
|
| 333 |
#' |
|
| 334 |
#' tbl <- build_table(lyt, DM) |
|
| 335 |
#' |
|
| 336 |
#' @family make_custom_split |
|
| 337 |
#' @export |
|
| 338 |
add_combo_facet <- function(name, label = name, levels, extra = list()) {
|
|
| 339 | 3x |
function(ret, spl, .spl_context, fulldf) {
|
| 340 | 4x |
if (is(levels, "AllLevelsSentinel")) {
|
| 341 | 1x |
subexpr <- expression(TRUE) |
| 342 | 1x |
datpart <- list(fulldf) |
| 343 |
} else {
|
|
| 344 | 3x |
subexpr <- .combine_value_exprs(ret$values[levels]) |
| 345 | 3x |
datpart <- list(do.call(rbind, ret$datasplit[levels])) |
| 346 |
} |
|
| 347 | ||
| 348 | ||
| 349 | 4x |
val <- LevelComboSplitValue( |
| 350 | 4x |
val = name, extr = extra, combolevels = levels, label = label, |
| 351 | 4x |
sub_expr = subexpr |
| 352 |
) |
|
| 353 | 4x |
add_to_split_result(ret, |
| 354 | 4x |
values = list(val), labels = label, |
| 355 | 4x |
datasplit = datpart |
| 356 |
) |
|
| 357 |
} |
|
| 358 |
} |
|
| 359 | ||
| 360 |
.combine_value_exprs <- function(val_lst, spl) {
|
|
| 361 | 3x |
exprs <- lapply(val_lst, value_expr) |
| 362 | 3x |
nulls <- vapply(exprs, is.null, TRUE) |
| 363 | 3x |
if (all(nulls)) {
|
| 364 | 1x |
return(NULL) # default behavior all the way down the line, no need to do anything. |
| 365 | 2x |
} else if (any(nulls)) {
|
| 366 | ! |
exprs[nulls] <- lapply(val_lst[nulls], function(vali) make_subset_expr(spl, vali)) |
| 367 |
} |
|
| 368 | 2x |
Reduce(.or_combine_exprs, exprs) |
| 369 |
} |
|
| 370 | ||
| 371 |
## no NULLS coming in here, everything has been populated |
|
| 372 |
## by either custom subsetting expressions or the result of make_subset_expr(spl, val) |
|
| 373 |
.or_combine_exprs <- function(ex1, ex2) {
|
|
| 374 | 2x |
if (identical(ex1, expression(FALSE))) {
|
| 375 | ! |
return(ex2) |
| 376 | 2x |
} else if (identical(ex2, expression(FALSE))) {
|
| 377 | ! |
return(ex1) |
| 378 | 2x |
} else if (identical(ex1, expression(TRUE)) || identical(ex2, expression(TRUE))) {
|
| 379 | ! |
return(TRUE) |
| 380 |
} |
|
| 381 | 2x |
as.expression(bquote((.(a)) | .(b), list(a = ex1[[1]], b = ex2[[1]]))) |
| 382 |
} |
|
| 383 | ||
| 384 |
#' @rdname add_combo_facet |
|
| 385 |
#' @export |
|
| 386 |
add_overall_facet <- function(name, label, extra = list()) {
|
|
| 387 | 1x |
add_combo_facet( |
| 388 | 1x |
name = name, label = label, levels = select_all_levels, |
| 389 | 1x |
extra = extra |
| 390 |
) |
|
| 391 |
} |
|
| 392 | ||
| 393 |
#' Trim levels of another variable from each facet (post-processing split step) |
|
| 394 |
#' |
|
| 395 |
#' @param innervar (`character`)\cr the variable(s) to trim (remove unobserved levels) independently within each facet. |
|
| 396 |
#' |
|
| 397 |
#' @return A function suitable for use in the `pre` (list) argument of `make_split_fun`. |
|
| 398 |
#' |
|
| 399 |
#' @seealso [make_split_fun()] |
|
| 400 |
#' |
|
| 401 |
#' @family make_custom_split |
|
| 402 |
#' @export |
|
| 403 |
trim_levels_in_facets <- function(innervar) {
|
|
| 404 | 1x |
function(ret, ...) {
|
| 405 | 1x |
for (var in innervar) {
|
| 406 | 1x |
ret$datasplit <- lapply(ret$datasplit, function(df) {
|
| 407 | 2x |
df[[var]] <- factor(df[[var]]) |
| 408 | 2x |
df |
| 409 |
}) |
|
| 410 |
} |
|
| 411 | 1x |
ret |
| 412 |
} |
|
| 413 |
} |
|
| 414 | ||
| 415 |
#' Pre-processing function for use in `make_split_fun` |
|
| 416 |
#' |
|
| 417 |
#' This function is intended for use as a pre-processing component in `make_split_fun`, and should not be called |
|
| 418 |
#' directly by end users. |
|
| 419 |
#' |
|
| 420 |
#' @param df (`data.frame`)\cr the incoming data corresponding with the parent facet. |
|
| 421 |
#' @param spl (`VarLevelSplit`)\cr the split. |
|
| 422 |
#' @param ... additional parameters passed internally. |
|
| 423 |
#' |
|
| 424 |
#' @seealso [make_split_fun()] |
|
| 425 |
#' |
|
| 426 |
#' @family make_custom_split |
|
| 427 |
#' @export |
|
| 428 |
drop_facet_levels <- function(df, spl, ...) {
|
|
| 429 | 2x |
if (!is(spl, "VarLevelSplit") || is.na(spl_payload(spl))) {
|
| 430 | ! |
stop("Unable to determine faceting variable in drop_facet_levels application.")
|
| 431 |
} |
|
| 432 | 2x |
var <- spl_payload(spl) |
| 433 | 2x |
df[[var]] <- factor(df[[var]]) |
| 434 | 2x |
df |
| 435 |
} |
| 1 |
# Split functions -------------------------------------------------------------- |
|
| 2 |
#' Split functions |
|
| 3 |
#' |
|
| 4 |
#' @description |
|
| 5 |
#' This is a collection of useful, default split function that can help you in dividing the data, hence the |
|
| 6 |
#' table rows or columns, into different parts or groups (splits). You can also create your own split function if you |
|
| 7 |
#' need to create a custom division as specific as you need. Please consider reading [custom_split_funs] if |
|
| 8 |
#' this is the case. Beyond this list of functions, you can also use [add_overall_level()] and [add_combo_levels()] |
|
| 9 |
#' for adding or modifying levels and [trim_levels_to_map()] to provide possible level combinations to filter the split |
|
| 10 |
#' with. |
|
| 11 |
#' |
|
| 12 |
#' @inheritParams sf_args |
|
| 13 |
#' @inheritParams gen_args |
|
| 14 |
#' @param vals (`ANY`)\cr for internal use only. |
|
| 15 |
#' @param labels (`character`)\cr labels to use for the remaining levels instead of the existing ones. |
|
| 16 |
#' |
|
| 17 |
#' @returns A function that can be used to split the data accordingly. The actual function signature |
|
| 18 |
#' is similar to the one you can define when creating a fully custom one. For more details see [custom_split_funs]. |
|
| 19 |
#' |
|
| 20 |
#' @note |
|
| 21 |
#' The following parameters are also documented here but they are only the default |
|
| 22 |
#' signature of a split function: `df` (data to be split), `spl` (split object), and `vals = NULL`, |
|
| 23 |
#' `labels = NULL`, `trim = FALSE` (last three only for internal use). See [custom_split_funs] for more details |
|
| 24 |
#' and [make_split_fun()] for a more advanced API. |
|
| 25 |
#' |
|
| 26 |
#' @seealso [custom_split_funs], [add_overall_level()], [add_combo_levels()], and [trim_levels_to_map()]. |
|
| 27 |
#' |
|
| 28 |
#' @name split_funcs |
|
| 29 |
NULL |
|
| 30 | ||
| 31 |
# helper fncs |
|
| 32 |
.get_unique_levels <- function(vec) {
|
|
| 33 | 93x |
out <- if (is.factor(vec)) {
|
| 34 | 84x |
levels(vec) |
| 35 |
} else {
|
|
| 36 | 9x |
unique(vec) |
| 37 |
} |
|
| 38 | ||
| 39 | 93x |
out |
| 40 |
} |
|
| 41 | ||
| 42 |
.print_setdiff_error <- function(provided, existing) {
|
|
| 43 | 5x |
paste(setdiff(provided, existing), collapse = ", ") |
| 44 |
} |
|
| 45 | ||
| 46 |
#' @describeIn split_funcs keeps only specified levels (`only`) in the split variable. If any of the specified |
|
| 47 |
#' levels is not present, an error is returned. `reorder = TRUE` (the default) orders the split levels |
|
| 48 |
#' according to the order of `only`. |
|
| 49 |
#' |
|
| 50 |
#' @param only (`character`)\cr levels to retain (all others will be dropped). If none of the levels is present |
|
| 51 |
#' an empty table is returned. |
|
| 52 |
#' @param reorder (`flag`)\cr whether the order of `only` should be used as the order of the children of the |
|
| 53 |
#' split. Defaults to `TRUE`. |
|
| 54 |
#' |
|
| 55 |
#' @examples |
|
| 56 |
#' # keep_split_levels keeps specified levels (reorder = TRUE by default) |
|
| 57 |
#' lyt <- basic_table() %>% |
|
| 58 |
#' split_rows_by("COUNTRY",
|
|
| 59 |
#' split_fun = keep_split_levels(c("USA", "CAN", "BRA"))
|
|
| 60 |
#' ) %>% |
|
| 61 |
#' analyze("AGE")
|
|
| 62 |
#' |
|
| 63 |
#' tbl <- build_table(lyt, DM) |
|
| 64 |
#' tbl |
|
| 65 |
#' |
|
| 66 |
#' @export |
|
| 67 |
keep_split_levels <- function(only, reorder = TRUE) {
|
|
| 68 | 56x |
function(df, spl, vals = NULL, labels = NULL, trim = FALSE) {
|
| 69 | 85x |
var <- spl_payload(spl) |
| 70 | 85x |
varvec <- df[[var]] |
| 71 | ||
| 72 |
# Unique values from the split variable |
|
| 73 | 85x |
unique_vals <- .get_unique_levels(varvec) |
| 74 | ||
| 75 |
# Error in case not all levels are present |
|
| 76 | 85x |
if (length(varvec) > 0 && !all(only %in% unique_vals)) {
|
| 77 | 4x |
what <- ifelse(is.factor(varvec), "factor level(s)", "character value(s)") |
| 78 | 4x |
stop( |
| 79 | 4x |
"Attempted to keep ", what, " in split that are not present in data: \n", |
| 80 | 4x |
.print_setdiff_error(only, unique_vals) |
| 81 |
) |
|
| 82 |
} |
|
| 83 | ||
| 84 | 81x |
df2 <- df[varvec %in% only, ] |
| 85 | 81x |
if (reorder) {
|
| 86 | 80x |
df2[[var]] <- factor(df2[[var]], levels = only) |
| 87 |
} else {
|
|
| 88 |
# Find original order of only |
|
| 89 | 1x |
only <- unique_vals[sort(match(only, unique_vals))] |
| 90 |
} |
|
| 91 | ||
| 92 | 81x |
spl_child_order(spl) <- only |
| 93 | 81x |
.apply_split_inner(spl, df2, |
| 94 | 81x |
vals = only, |
| 95 | 81x |
labels = labels, |
| 96 | 81x |
trim = trim |
| 97 |
) |
|
| 98 |
} |
|
| 99 |
} |
|
| 100 | ||
| 101 |
#' @describeIn split_funcs Removes specified levels (`excl`) from the split variable. Nothing done if not in data. |
|
| 102 |
#' |
|
| 103 |
#' @param excl (`character`)\cr levels to be excluded (they will not be reflected in the resulting table structure |
|
| 104 |
#' regardless of presence in the data). |
|
| 105 |
#' |
|
| 106 |
#' @examples |
|
| 107 |
#' # remove_split_levels removes specified split levels |
|
| 108 |
#' lyt <- basic_table() %>% |
|
| 109 |
#' split_rows_by("COUNTRY",
|
|
| 110 |
#' split_fun = remove_split_levels(c( |
|
| 111 |
#' "USA", "CAN", |
|
| 112 |
#' "CHE", "BRA" |
|
| 113 |
#' )) |
|
| 114 |
#' ) %>% |
|
| 115 |
#' analyze("AGE")
|
|
| 116 |
#' |
|
| 117 |
#' tbl <- build_table(lyt, DM) |
|
| 118 |
#' tbl |
|
| 119 |
#' |
|
| 120 |
#' @export |
|
| 121 |
remove_split_levels <- function(excl) {
|
|
| 122 | 29x |
stopifnot(is.character(excl)) |
| 123 | 29x |
function(df, spl, vals = NULL, labels = NULL, trim = FALSE) {
|
| 124 | 57x |
var <- spl_payload(spl) |
| 125 | 57x |
df2 <- df[!(df[[var]] %in% excl), ] |
| 126 | 57x |
if (is.factor(df2[[var]])) {
|
| 127 | 2x |
levels <- levels(df2[[var]]) |
| 128 | 2x |
levels <- levels[!(levels %in% excl)] |
| 129 | 2x |
df2[[var]] <- factor(df2[[var]], levels = levels) |
| 130 |
} |
|
| 131 | 57x |
.apply_split_inner(spl, df2, |
| 132 | 57x |
vals = vals, |
| 133 | 57x |
labels = labels, |
| 134 | 57x |
trim = trim |
| 135 |
) |
|
| 136 |
} |
|
| 137 |
} |
|
| 138 | ||
| 139 |
#' @describeIn split_funcs Drops levels that have no representation in the data. |
|
| 140 |
#' |
|
| 141 |
#' @examples |
|
| 142 |
#' # drop_split_levels drops levels that are not present in the data |
|
| 143 |
#' lyt <- basic_table() %>% |
|
| 144 |
#' split_rows_by("SEX", split_fun = drop_split_levels) %>%
|
|
| 145 |
#' analyze("AGE")
|
|
| 146 |
#' |
|
| 147 |
#' tbl <- build_table(lyt, DM) |
|
| 148 |
#' tbl |
|
| 149 |
#' |
|
| 150 |
#' @export |
|
| 151 |
drop_split_levels <- function(df, |
|
| 152 |
spl, |
|
| 153 |
vals = NULL, |
|
| 154 |
labels = NULL, |
|
| 155 |
trim = FALSE) {
|
|
| 156 | 169x |
var <- spl_payload(spl) |
| 157 | 169x |
df2 <- df |
| 158 | 169x |
df2[[var]] <- factor(df[[var]]) |
| 159 | 169x |
lblvar <- spl_label_var(spl) |
| 160 | 169x |
if (!is.null(lblvar)) {
|
| 161 | 169x |
df2[[lblvar]] <- factor(df[[lblvar]]) |
| 162 |
} |
|
| 163 | ||
| 164 | 169x |
.apply_split_inner(spl, df2, |
| 165 | 169x |
vals = vals, |
| 166 | 169x |
labels = labels, |
| 167 | 169x |
trim = trim |
| 168 |
) |
|
| 169 |
} |
|
| 170 | ||
| 171 |
#' @describeIn split_funcs Removes specified levels `excl` and drops all levels that are |
|
| 172 |
#' not in the data. |
|
| 173 |
#' |
|
| 174 |
#' @examples |
|
| 175 |
#' # Removing "M" and "U" directly, then "UNDIFFERENTIATED" because not in data |
|
| 176 |
#' lyt <- basic_table() %>% |
|
| 177 |
#' split_rows_by("SEX", split_fun = drop_and_remove_levels(c("M", "U"))) %>%
|
|
| 178 |
#' analyze("AGE")
|
|
| 179 |
#' |
|
| 180 |
#' tbl <- build_table(lyt, DM) |
|
| 181 |
#' tbl |
|
| 182 |
#' |
|
| 183 |
#' @export |
|
| 184 |
drop_and_remove_levels <- function(excl) {
|
|
| 185 | 4x |
stopifnot(is.character(excl)) |
| 186 | 4x |
function(df, spl, vals = NULL, labels = NULL, trim = FALSE) {
|
| 187 | 13x |
var <- spl_payload(spl) |
| 188 | 13x |
df2 <- df[!(df[[var]] %in% excl), ] |
| 189 | 13x |
df2[[var]] <- factor(df2[[var]]) |
| 190 | 13x |
.apply_split_inner( |
| 191 | 13x |
spl, |
| 192 | 13x |
df2, |
| 193 | 13x |
vals = vals, |
| 194 | 13x |
labels = labels, |
| 195 | 13x |
trim = trim |
| 196 |
) |
|
| 197 |
} |
|
| 198 |
} |
|
| 199 | ||
| 200 |
#' @describeIn split_funcs Reorders split levels following `neworder`, which needs to be of |
|
| 201 |
#' same size as the levels in data. |
|
| 202 |
#' |
|
| 203 |
#' @param neworder (`character`)\cr new order of factor levels. All need to be present in the data. |
|
| 204 |
#' To add empty levels, rely on pre-processing or create your [custom_split_funs]. |
|
| 205 |
#' @param newlabels (`character`)\cr labels for (new order of) factor levels. If named, the levels are matched. |
|
| 206 |
#' Otherwise, the order of `neworder` is used. |
|
| 207 |
#' @param drlevels (`flag`)\cr whether levels that are not in `neworder` should be dropped. |
|
| 208 |
#' Default is `TRUE`. Note: `drlevels = TRUE` does not drop levels that are not originally in the data. |
|
| 209 |
#' Rely on pre-processing or use a combination of split functions with [make_split_fun()] to also drop |
|
| 210 |
#' unused levels. |
|
| 211 |
#' |
|
| 212 |
#' @examples |
|
| 213 |
#' # Reordering levels in split variable |
|
| 214 |
#' lyt <- basic_table() %>% |
|
| 215 |
#' split_rows_by( |
|
| 216 |
#' "SEX", |
|
| 217 |
#' split_fun = reorder_split_levels( |
|
| 218 |
#' neworder = c("U", "F"),
|
|
| 219 |
#' newlabels = c(U = "Uu", `F` = "Female") |
|
| 220 |
#' ) |
|
| 221 |
#' ) %>% |
|
| 222 |
#' analyze("AGE")
|
|
| 223 |
#' |
|
| 224 |
#' tbl <- build_table(lyt, DM) |
|
| 225 |
#' tbl |
|
| 226 |
#' |
|
| 227 |
#' # Reordering levels in split variable but keeping all the levels |
|
| 228 |
#' lyt <- basic_table() %>% |
|
| 229 |
#' split_rows_by( |
|
| 230 |
#' "SEX", |
|
| 231 |
#' split_fun = reorder_split_levels( |
|
| 232 |
#' neworder = c("U", "F"),
|
|
| 233 |
#' newlabels = c("Uu", "Female"),
|
|
| 234 |
#' drlevels = FALSE |
|
| 235 |
#' ) |
|
| 236 |
#' ) %>% |
|
| 237 |
#' analyze("AGE")
|
|
| 238 |
#' |
|
| 239 |
#' tbl <- build_table(lyt, DM) |
|
| 240 |
#' tbl |
|
| 241 |
#' |
|
| 242 |
#' @export |
|
| 243 |
reorder_split_levels <- function(neworder, |
|
| 244 |
newlabels = neworder, |
|
| 245 |
drlevels = TRUE) {
|
|
| 246 | 8x |
function(df, spl, trim, ...) {
|
| 247 | 8x |
df2 <- df |
| 248 | 8x |
valvec <- df2[[spl_payload(spl)]] |
| 249 | ||
| 250 | 8x |
uni_vals <- .get_unique_levels(valvec) |
| 251 | ||
| 252 |
# No sense adding things that are not present -> creating unexpected NAs |
|
| 253 | 8x |
if (!all(neworder %in% uni_vals)) {
|
| 254 | 1x |
stop( |
| 255 | 1x |
"Attempted to reorder factor levels in split that are not present in data:\n", |
| 256 | 1x |
.print_setdiff_error(neworder, uni_vals) |
| 257 |
) |
|
| 258 |
} |
|
| 259 | ||
| 260 |
# Keeping all levels also from before if not dropped |
|
| 261 | 7x |
diff_with_uni_vals <- setdiff(uni_vals, neworder) |
| 262 | 7x |
if (!drlevels && length(diff_with_uni_vals) > 0) {
|
| 263 | 3x |
if (length(newlabels) > length(neworder)) {
|
| 264 | 1x |
stop( |
| 265 | 1x |
"When keeping levels not in neworder (drlevels = FALSE), newlabels can ", |
| 266 | 1x |
"affect only selected neworder, and not other levels.\n", |
| 267 | 1x |
"Add labels for current neworder: ", paste0(neworder, collapse = ", ") |
| 268 |
) |
|
| 269 |
} |
|
| 270 | 2x |
neworder <- c(neworder, diff_with_uni_vals) |
| 271 | 2x |
if (is.null(names(newlabels))) {
|
| 272 | ! |
newlabels <- c(newlabels, diff_with_uni_vals) |
| 273 |
} else {
|
|
| 274 | 2x |
newlabels <- c(newlabels, setNames(diff_with_uni_vals, diff_with_uni_vals)) |
| 275 |
} |
|
| 276 |
} |
|
| 277 | ||
| 278 | 6x |
valvec <- factor(valvec, levels = neworder) |
| 279 | ||
| 280 |
# Labels |
|
| 281 | 6x |
if (!is.null(names(newlabels))) {
|
| 282 | 5x |
if (any(!names(newlabels) %in% neworder)) {
|
| 283 | 2x |
stop( |
| 284 | 2x |
"Got labels' names for levels that are not present:\n", |
| 285 | 2x |
setdiff(names(newlabels), neworder) |
| 286 |
) |
|
| 287 |
} |
|
| 288 |
# To be safe: sorting by neworder |
|
| 289 | 3x |
newlabels <- newlabels[sapply(names(newlabels), function(x) which(x == neworder))] |
| 290 | 1x |
} else if (length(neworder) != length(newlabels)) {
|
| 291 | 1x |
stop( |
| 292 | 1x |
"Got unnamed newlabels with different length than neworder. ", |
| 293 | 1x |
"Please provide names or make sure they are of the same length.\n", |
| 294 | 1x |
"Current neworder: ", paste0(neworder, collapse = ", ") |
| 295 |
) |
|
| 296 |
} |
|
| 297 | ||
| 298 |
# Final values |
|
| 299 | 3x |
spl_child_order(spl) <- neworder |
| 300 | 3x |
df2[[spl_payload(spl)]] <- valvec |
| 301 | 3x |
.apply_split_inner(spl, df2, |
| 302 | 3x |
vals = neworder, |
| 303 | 3x |
labels = newlabels, |
| 304 | 3x |
trim = trim |
| 305 |
) |
|
| 306 |
} |
|
| 307 |
} |
|
| 308 | ||
| 309 |
#' @describeIn split_funcs Takes the split groups and removes levels of `innervar` if not present in |
|
| 310 |
#' those split groups. If you want to specify a filter of possible combinations, please |
|
| 311 |
#' consider using [trim_levels_to_map()]. |
|
| 312 |
#' |
|
| 313 |
#' @param innervar (`string`)\cr variable whose factor levels should be trimmed (e.g. empty levels dropped) |
|
| 314 |
#' *separately within each grouping defined at this point in the structure*. |
|
| 315 |
#' @param drop_outlevs (`flag`)\cr whether empty levels in the variable being split on (i.e. the "outer" |
|
| 316 |
#' variable, not `innervar`) should be dropped. Defaults to `TRUE`. |
|
| 317 |
#' |
|
| 318 |
#' @examples |
|
| 319 |
#' # trim_levels_in_group() trims levels within each group defined by the split variable |
|
| 320 |
#' dat <- data.frame( |
|
| 321 |
#' col1 = factor(c("A", "B", "C"), levels = c("A", "B", "C", "N")),
|
|
| 322 |
#' col2 = factor(c("a", "b", "c"), levels = c("a", "b", "c", "x"))
|
|
| 323 |
#' ) # N is removed if drop_outlevs = TRUE, x is removed always |
|
| 324 |
#' |
|
| 325 |
#' tbl <- basic_table() %>% |
|
| 326 |
#' split_rows_by("col1", split_fun = trim_levels_in_group("col2")) %>%
|
|
| 327 |
#' analyze("col2") %>%
|
|
| 328 |
#' build_table(dat) |
|
| 329 |
#' tbl |
|
| 330 |
#' |
|
| 331 |
#' @export |
|
| 332 |
trim_levels_in_group <- function(innervar, drop_outlevs = TRUE) {
|
|
| 333 | 7x |
myfun <- function(df, spl, vals = NULL, labels = NULL, trim = FALSE) {
|
| 334 | 7x |
if (!drop_outlevs) {
|
| 335 | ! |
ret <- .apply_split_inner(spl, df, |
| 336 | ! |
vals = vals, |
| 337 | ! |
labels = labels, trim = trim |
| 338 |
) |
|
| 339 |
} else {
|
|
| 340 | 7x |
ret <- drop_split_levels( |
| 341 | 7x |
df = df, spl = spl, vals = vals, |
| 342 | 7x |
labels = labels, trim = trim |
| 343 |
) |
|
| 344 |
} |
|
| 345 | ||
| 346 | 7x |
ret$datasplit <- lapply(ret$datasplit, function(x) {
|
| 347 | 17x |
coldat <- x[[innervar]] |
| 348 | 17x |
if (is(coldat, "character")) {
|
| 349 | ! |
if (!is.null(vals)) {
|
| 350 | ! |
lvs <- vals |
| 351 |
} else {
|
|
| 352 | ! |
lvs <- unique(coldat) |
| 353 |
} |
|
| 354 | ! |
coldat <- factor(coldat, levels = lvs) ## otherwise |
| 355 |
} else {
|
|
| 356 | 17x |
coldat <- droplevels(coldat) |
| 357 |
} |
|
| 358 | 17x |
x[[innervar]] <- coldat |
| 359 | 17x |
x |
| 360 |
}) |
|
| 361 | 7x |
ret$labels <- as.character(ret$labels) # TODO |
| 362 | 7x |
ret |
| 363 |
} |
|
| 364 | 7x |
myfun |
| 365 |
} |
|
| 366 | ||
| 367 |
# add_combo_levels ------------------------------------------------------------- |
|
| 368 |
# Dedicated docs are attached to default split functions |
|
| 369 |
.add_combo_part_info <- function(part, |
|
| 370 |
df, |
|
| 371 |
valuename, |
|
| 372 |
levels, |
|
| 373 |
label, |
|
| 374 |
extras, |
|
| 375 |
first = TRUE) {
|
|
| 376 | 26x |
value <- LevelComboSplitValue(valuename, extras, |
| 377 | 26x |
combolevels = levels, |
| 378 | 26x |
label = label |
| 379 |
) |
|
| 380 | 26x |
newdat <- setNames(list(df), valuename) |
| 381 | 26x |
newval <- setNames(list(value), valuename) |
| 382 | 26x |
newextra <- setNames(list(extras), valuename) |
| 383 | 26x |
if (first) {
|
| 384 | 6x |
part$datasplit <- c(newdat, part$datasplit) |
| 385 | 6x |
part$values <- c(newval, part$values) |
| 386 | 6x |
part$labels <- c(setNames(label, valuename), part$labels) |
| 387 | 6x |
part$extras <- c(newextra, part$extras) |
| 388 |
} else {
|
|
| 389 | 20x |
part$datasplit <- c(part$datasplit, newdat) |
| 390 | 20x |
part$values <- c(part$values, newval) |
| 391 | 20x |
part$labels <- c(part$labels, setNames(label, valuename)) |
| 392 | 20x |
part$extras <- c(part$extras, newextra) |
| 393 |
} |
|
| 394 |
## not needed even in custom split function case. |
|
| 395 |
## part = .fixupvals(part) |
|
| 396 | 26x |
part |
| 397 |
} |
|
| 398 | ||
| 399 |
#' Add overall or combination levels to split groups |
|
| 400 |
#' |
|
| 401 |
#' @description |
|
| 402 |
#' `add_overall_level` is a split function that adds a global level to the current levels in the split. Similarly, |
|
| 403 |
#' `add_combo_df` uses a user-provided `data.frame` to define the combine the levels to be added. If you need a |
|
| 404 |
#' single overall column, after all splits, please check [add_overall_col()]. Consider also defining |
|
| 405 |
#' your custom split function if you need more flexibility (see [custom_split_funs]). |
|
| 406 |
#' |
|
| 407 |
#' @inheritParams lyt_args |
|
| 408 |
#' @inheritParams sf_args |
|
| 409 |
#' @param valname (`string`)\cr value to be assigned to the implicit all-observations split level. Defaults to |
|
| 410 |
#' `"Overall"`. |
|
| 411 |
#' @param first (`flag`)\cr whether the implicit level should appear first (`TRUE`) or last (`FALSE`). Defaults |
|
| 412 |
#' to `TRUE`. |
|
| 413 |
#' |
|
| 414 |
#' @return A splitting function (`splfun`) that adds or changes the levels of a split. |
|
| 415 |
#' |
|
| 416 |
#' @seealso [custom_split_funs] and [split_funcs]. |
|
| 417 |
#' |
|
| 418 |
#' @examples |
|
| 419 |
#' lyt <- basic_table() %>% |
|
| 420 |
#' split_cols_by("ARM", split_fun = add_overall_level("All Patients",
|
|
| 421 |
#' first = FALSE |
|
| 422 |
#' )) %>% |
|
| 423 |
#' analyze("AGE")
|
|
| 424 |
#' |
|
| 425 |
#' tbl <- build_table(lyt, DM) |
|
| 426 |
#' tbl |
|
| 427 |
#' |
|
| 428 |
#' lyt2 <- basic_table() %>% |
|
| 429 |
#' split_cols_by("ARM") %>%
|
|
| 430 |
#' split_rows_by("RACE",
|
|
| 431 |
#' split_fun = add_overall_level("All Ethnicities")
|
|
| 432 |
#' ) %>% |
|
| 433 |
#' summarize_row_groups(label_fstr = "%s (n)") %>% |
|
| 434 |
#' analyze("AGE")
|
|
| 435 |
#' lyt2 |
|
| 436 |
#' |
|
| 437 |
#' tbl2 <- build_table(lyt2, DM) |
|
| 438 |
#' tbl2 |
|
| 439 |
#' |
|
| 440 |
#' @export |
|
| 441 |
add_overall_level <- function(valname = "Overall", |
|
| 442 |
label = valname, |
|
| 443 |
extra_args = list(), |
|
| 444 |
first = TRUE, |
|
| 445 |
trim = FALSE) {
|
|
| 446 | 6x |
combodf <- data.frame( |
| 447 | 6x |
valname = valname, |
| 448 | 6x |
label = label, |
| 449 | 6x |
levelcombo = I(list(select_all_levels)), |
| 450 | 6x |
exargs = I(list(extra_args)), |
| 451 | 6x |
stringsAsFactors = FALSE |
| 452 |
) |
|
| 453 | 6x |
add_combo_levels(combodf, |
| 454 | 6x |
trim = trim, first = first |
| 455 |
) |
|
| 456 |
} |
|
| 457 | ||
| 458 |
setClass("AllLevelsSentinel", contains = "character")
|
|
| 459 | ||
| 460 |
# nocov start |
|
| 461 |
#' @rdname add_overall_level |
|
| 462 |
#' @export |
|
| 463 |
select_all_levels <- new("AllLevelsSentinel")
|
|
| 464 |
# nocov end |
|
| 465 | ||
| 466 |
#' @param combosdf (`data.frame` or `tbl_df`)\cr a data frame with columns `valname`, `label`, `levelcombo`, and |
|
| 467 |
#' `exargs`. `levelcombo` and `exargs` should be list columns. Passing the `select_all_levels` object as a value in |
|
| 468 |
#' `comblevels` column indicates that an overall/all-observations level should be created. |
|
| 469 |
#' @param keep_levels (`character` or `NULL`)\cr if non-`NULL`, the levels to retain across both combination and |
|
| 470 |
#' individual levels. |
|
| 471 |
#' |
|
| 472 |
#' @inherit add_overall_level return |
|
| 473 |
#' |
|
| 474 |
#' @note |
|
| 475 |
#' Analysis or summary functions for which the order matters should never be used within the tabulation framework. |
|
| 476 |
#' |
|
| 477 |
#' @examplesIf require(tibble) |
|
| 478 |
#' |
|
| 479 |
#' library(tibble) |
|
| 480 |
#' combodf <- tribble( |
|
| 481 |
#' ~valname, ~label, ~levelcombo, ~exargs, |
|
| 482 |
#' "A_B", "Arms A+B", c("A: Drug X", "B: Placebo"), list(),
|
|
| 483 |
#' "A_C", "Arms A+C", c("A: Drug X", "C: Combination"), list()
|
|
| 484 |
#' ) |
|
| 485 |
#' |
|
| 486 |
#' lyt <- basic_table(show_colcounts = TRUE) %>% |
|
| 487 |
#' split_cols_by("ARM", split_fun = add_combo_levels(combodf)) %>%
|
|
| 488 |
#' analyze("AGE")
|
|
| 489 |
#' |
|
| 490 |
#' tbl <- build_table(lyt, DM) |
|
| 491 |
#' tbl |
|
| 492 |
#' |
|
| 493 |
#' lyt1 <- basic_table(show_colcounts = TRUE) %>% |
|
| 494 |
#' split_cols_by("ARM",
|
|
| 495 |
#' split_fun = add_combo_levels(combodf, |
|
| 496 |
#' keep_levels = c( |
|
| 497 |
#' "A_B", |
|
| 498 |
#' "A_C" |
|
| 499 |
#' ) |
|
| 500 |
#' ) |
|
| 501 |
#' ) %>% |
|
| 502 |
#' analyze("AGE")
|
|
| 503 |
#' |
|
| 504 |
#' tbl1 <- build_table(lyt1, DM) |
|
| 505 |
#' tbl1 |
|
| 506 |
#' |
|
| 507 |
#' smallerDM <- droplevels(subset(DM, SEX %in% c("M", "F") &
|
|
| 508 |
#' grepl("^(A|B)", ARM)))
|
|
| 509 |
#' lyt2 <- basic_table(show_colcounts = TRUE) %>% |
|
| 510 |
#' split_cols_by("ARM", split_fun = add_combo_levels(combodf[1, ])) %>%
|
|
| 511 |
#' split_cols_by("SEX",
|
|
| 512 |
#' split_fun = add_overall_level("SEX_ALL", "All Genders")
|
|
| 513 |
#' ) %>% |
|
| 514 |
#' analyze("AGE")
|
|
| 515 |
#' |
|
| 516 |
#' lyt3 <- basic_table(show_colcounts = TRUE) %>% |
|
| 517 |
#' split_cols_by("ARM", split_fun = add_combo_levels(combodf)) %>%
|
|
| 518 |
#' split_rows_by("SEX",
|
|
| 519 |
#' split_fun = add_overall_level("SEX_ALL", "All Genders")
|
|
| 520 |
#' ) %>% |
|
| 521 |
#' summarize_row_groups() %>% |
|
| 522 |
#' analyze("AGE")
|
|
| 523 |
#' |
|
| 524 |
#' tbl3 <- build_table(lyt3, smallerDM) |
|
| 525 |
#' tbl3 |
|
| 526 |
#' |
|
| 527 |
#' @rdname add_overall_level |
|
| 528 |
#' @export |
|
| 529 |
add_combo_levels <- function(combosdf, |
|
| 530 |
trim = FALSE, |
|
| 531 |
first = FALSE, |
|
| 532 |
keep_levels = NULL) {
|
|
| 533 | 15x |
myfun <- function(df, spl, vals = NULL, labels = NULL, ...) {
|
| 534 | 20x |
if (is(spl, "MultiVarSplit")) {
|
| 535 | 1x |
stop("Combining levels of a MultiVarSplit does not make sense.",
|
| 536 | 1x |
call. = FALSE |
| 537 |
) |
|
| 538 | 15x |
} # nocov |
| 539 | 19x |
ret <- .apply_split_inner(spl, df, |
| 540 | 19x |
vals = vals, |
| 541 | 19x |
labels = labels, trim = trim |
| 542 |
) |
|
| 543 | 19x |
for (i in seq_len(nrow(combosdf))) {
|
| 544 | 26x |
lcombo <- combosdf[i, "levelcombo", drop = TRUE][[1]] |
| 545 | 26x |
spld <- spl_payload(spl) |
| 546 | 26x |
if (is(lcombo, "AllLevelsSentinel")) {
|
| 547 | 6x |
subdf <- df |
| 548 | 20x |
} else if (is(spl, "VarLevelSplit")) {
|
| 549 | 20x |
subdf <- df[df[[spld]] %in% lcombo, ] |
| 550 | 15x |
} else { ## this covers non-var splits, e.g. Cut-based splits
|
| 551 | ! |
stopifnot(all(lcombo %in% c(ret$labels, ret$vals))) |
| 552 | ! |
subdf <- do.call( |
| 553 | ! |
rbind, |
| 554 | ! |
ret$datasplit[names(ret$datasplit) %in% lcombo | ret$vals %in% lcombo] |
| 555 |
) |
|
| 556 |
} |
|
| 557 | 26x |
ret <- .add_combo_part_info( |
| 558 | 26x |
ret, subdf, |
| 559 | 26x |
combosdf[i, "valname", drop = TRUE], |
| 560 | 26x |
lcombo, |
| 561 | 26x |
combosdf[i, "label", drop = TRUE], |
| 562 | 26x |
combosdf[i, "exargs", drop = TRUE][[1]], |
| 563 | 26x |
first |
| 564 |
) |
|
| 565 |
} |
|
| 566 | 19x |
if (!is.null(keep_levels)) {
|
| 567 | 4x |
keep_inds <- value_names(ret$values) %in% keep_levels |
| 568 | 4x |
ret <- lapply(ret, function(x) x[keep_inds]) |
| 569 |
} |
|
| 570 | ||
| 571 | 19x |
ret |
| 572 |
} |
|
| 573 | 15x |
myfun |
| 574 |
} |
|
| 575 | ||
| 576 |
#' Trim levels to map |
|
| 577 |
#' |
|
| 578 |
#' This split function constructor creates a split function which trims levels of a variable to reflect restrictions |
|
| 579 |
#' on the possible combinations of two or more variables which the data is split by (along the same axis) within a |
|
| 580 |
#' layout. |
|
| 581 |
#' |
|
| 582 |
#' @param map data.frame. A data.frame defining allowed combinations of |
|
| 583 |
#' variables. Any combination at the level of this split not present in the |
|
| 584 |
#' map will be removed from the data, both for the variable being split and |
|
| 585 |
#' those present in the data but not associated with this split or any parents |
|
| 586 |
#' of it. |
|
| 587 |
#' |
|
| 588 |
#' @details |
|
| 589 |
#' When splitting occurs, the map is subset to the values of all previously performed splits. The levels of the |
|
| 590 |
#' variable being split are then pruned to only those still present within this subset of the map representing the |
|
| 591 |
#' current hierarchical splitting context. |
|
| 592 |
#' |
|
| 593 |
#' Splitting is then performed via the [keep_split_levels()] split function. |
|
| 594 |
#' |
|
| 595 |
#' Each resulting element of the partition is then further trimmed by pruning values of any remaining variables |
|
| 596 |
#' specified in the map to those values allowed under the combination of the previous and current split. |
|
| 597 |
#' |
|
| 598 |
#' @return A function that can be used as a split function. |
|
| 599 |
#' |
|
| 600 |
#' @seealso [trim_levels_in_group()]. |
|
| 601 |
#' |
|
| 602 |
#' @examples |
|
| 603 |
#' map <- data.frame( |
|
| 604 |
#' LBCAT = c("CHEMISTRY", "CHEMISTRY", "CHEMISTRY", "IMMUNOLOGY"),
|
|
| 605 |
#' PARAMCD = c("ALT", "CRP", "CRP", "IGA"),
|
|
| 606 |
#' ANRIND = c("LOW", "LOW", "HIGH", "HIGH"),
|
|
| 607 |
#' stringsAsFactors = FALSE |
|
| 608 |
#' ) |
|
| 609 |
#' |
|
| 610 |
#' lyt <- basic_table() %>% |
|
| 611 |
#' split_rows_by("LBCAT") %>%
|
|
| 612 |
#' split_rows_by("PARAMCD", split_fun = trim_levels_to_map(map = map)) %>%
|
|
| 613 |
#' analyze("ANRIND")
|
|
| 614 |
#' tbl <- build_table(lyt, ex_adlb) |
|
| 615 |
#' |
|
| 616 |
#' @export |
|
| 617 |
trim_levels_to_map <- function(map = NULL) {
|
|
| 618 | 7x |
if (is.null(map) || any(sapply(map, class) != "character")) {
|
| 619 | ! |
stop( |
| 620 | ! |
"No map dataframe was provided or not all of the columns are of ", |
| 621 | ! |
"type character." |
| 622 |
) |
|
| 623 |
} |
|
| 624 | ||
| 625 | 7x |
myfun <- function(df, |
| 626 | 7x |
spl, |
| 627 | 7x |
vals = NULL, |
| 628 | 7x |
labels = NULL, |
| 629 | 7x |
trim = FALSE, |
| 630 | 7x |
.spl_context) {
|
| 631 | 12x |
allvars <- colnames(map) |
| 632 | 12x |
splvar <- spl_payload(spl) |
| 633 | ||
| 634 | 12x |
allvmatches <- match(.spl_context$split, allvars) |
| 635 | 12x |
outvars <- allvars[na.omit(allvmatches)] |
| 636 |
## invars are variables present in data, but not in |
|
| 637 |
## previous or current splits |
|
| 638 | 12x |
invars <- intersect( |
| 639 | 12x |
setdiff(allvars, c(outvars, splvar)), |
| 640 | 12x |
names(df) |
| 641 |
) |
|
| 642 |
## allvarord <- c(na.omit(allvmatches), ## appear in prior splits |
|
| 643 |
## which(allvars == splvar), ## this split |
|
| 644 |
## allvars[-1*na.omit(allvmatches)]) ## "outvars" |
|
| 645 | ||
| 646 |
## allvars <- allvars[allvarord] |
|
| 647 |
## outvars <- allvars[-(which(allvars == splvar):length(allvars))] |
|
| 648 | 12x |
if (length(outvars) > 0) {
|
| 649 | 10x |
indfilters <- vapply(outvars, function(ivar) {
|
| 650 | 12x |
obsval <- .spl_context$value[match(ivar, .spl_context$split)] |
| 651 | 12x |
sprintf("%s == '%s'", ivar, obsval)
|
| 652 |
}, "") |
|
| 653 | ||
| 654 | 10x |
allfilters <- paste(indfilters, collapse = " & ") |
| 655 | 10x |
map <- map[eval(parse(text = allfilters), envir = map), ] |
| 656 |
} |
|
| 657 | 12x |
map_splvarpos <- which(names(map) == splvar) |
| 658 | 12x |
nondup <- !duplicated(map[[splvar]]) |
| 659 | 12x |
ksl_fun <- keep_split_levels( |
| 660 | 12x |
only = map[[splvar]][nondup], |
| 661 | 12x |
reorder = TRUE |
| 662 |
) |
|
| 663 | 12x |
ret <- ksl_fun(df, spl, vals, labels, trim = trim) |
| 664 | ||
| 665 | 12x |
if (length(ret$datasplit) == 0) {
|
| 666 | 1x |
msg <- paste(sprintf("%s[%s]", .spl_context$split, .spl_context$value),
|
| 667 | 1x |
collapse = "->" |
| 668 |
) |
|
| 669 | 1x |
stop( |
| 670 | 1x |
"map does not allow any values present in data for split ", |
| 671 | 1x |
"variable ", splvar, |
| 672 | 1x |
" under the following parent splits:\n\t", msg |
| 673 |
) |
|
| 674 |
} |
|
| 675 | ||
| 676 |
## keep non-split (inner) variables levels |
|
| 677 | 11x |
ret$datasplit <- lapply(ret$values, function(splvar_lev) {
|
| 678 | 19x |
df3 <- ret$datasplit[[splvar_lev]] |
| 679 | 19x |
curmap <- map[map[[map_splvarpos]] == splvar_lev, ] |
| 680 |
## loop through inner variables |
|
| 681 | 19x |
for (iv in invars) { ## setdiff(colnames(map), splvar)) {
|
| 682 | 19x |
iv_lev <- df3[[iv]] |
| 683 | 19x |
levkeep <- as.character(unique(curmap[[iv]])) |
| 684 | 19x |
if (is.factor(iv_lev) && !all(levkeep %in% levels(iv_lev))) {
|
| 685 | ! |
stop( |
| 686 | ! |
"Attempted to keep invalid factor level(s) in split ", |
| 687 | ! |
setdiff(levkeep, levels(iv_lev)) |
| 688 |
) |
|
| 689 |
} |
|
| 690 | ||
| 691 | 19x |
df3 <- df3[iv_lev %in% levkeep, , drop = FALSE] |
| 692 | ||
| 693 | 19x |
if (is.factor(iv_lev)) {
|
| 694 | 19x |
df3[[iv]] <- factor(as.character(df3[[iv]]), |
| 695 | 19x |
levels = levkeep |
| 696 |
) |
|
| 697 |
} |
|
| 698 |
} |
|
| 699 | ||
| 700 | 19x |
df3 |
| 701 |
}) |
|
| 702 | 11x |
names(ret$datasplit) <- ret$values |
| 703 | 11x |
ret |
| 704 |
} |
|
| 705 | ||
| 706 | 7x |
myfun |
| 707 |
} |
| 1 |
#' Score functions for sorting `TableTrees` |
|
| 2 |
#' |
|
| 3 |
#' @inheritParams gen_args |
|
| 4 |
#' |
|
| 5 |
#' @return A single numeric value indicating score according to the relevant metric for `tt`, to be used when sorting. |
|
| 6 |
#' |
|
| 7 |
#' @export |
|
| 8 |
#' @rdname score_funs |
|
| 9 |
cont_n_allcols <- function(tt) {
|
|
| 10 | 6x |
ctab <- content_table(tt) |
| 11 | 6x |
if (NROW(ctab) == 0) {
|
| 12 | 2x |
stop( |
| 13 | 2x |
"cont_n_allcols score function used at subtable [", |
| 14 | 2x |
obj_name(tt), "] that has no content table." |
| 15 |
) |
|
| 16 |
} |
|
| 17 | 4x |
sum(sapply( |
| 18 | 4x |
row_values(tree_children(ctab)[[1]]), |
| 19 | 4x |
function(cv) cv[1] |
| 20 |
)) |
|
| 21 |
} |
|
| 22 | ||
| 23 |
#' @param j (`numeric(1)`)\cr index of column used for scoring. |
|
| 24 |
#' |
|
| 25 |
#' @seealso For examples and details, please read the documentation for [sort_at_path()] and the |
|
| 26 |
#' [Sorting and Pruning](https://insightsengineering.github.io/rtables/latest-tag/articles/sorting_pruning.html) |
|
| 27 |
#' vignette. |
|
| 28 |
#' |
|
| 29 |
#' @export |
|
| 30 |
#' @rdname score_funs |
|
| 31 |
cont_n_onecol <- function(j) {
|
|
| 32 | 2x |
function(tt) {
|
| 33 | 6x |
ctab <- content_table(tt) |
| 34 | 6x |
if (NROW(ctab) == 0) {
|
| 35 | 2x |
stop( |
| 36 | 2x |
"cont_n_allcols score function used at subtable [", |
| 37 | 2x |
obj_name(tt), "] that has no content table." |
| 38 |
) |
|
| 39 |
} |
|
| 40 | 4x |
row_values(tree_children(ctab)[[1]])[[j]][1] |
| 41 |
} |
|
| 42 |
} |
|
| 43 | ||
| 44 |
## used for pruning functions and scoring functions(sorting) |
|
| 45 |
match_fun_args <- function(fun, ...) {
|
|
| 46 | 309x |
dotargs <- list(...) |
| 47 | 309x |
retargs <- list() |
| 48 | 309x |
formnms <- names(formals(fun)) |
| 49 | 309x |
if ("..." %in% formnms) {
|
| 50 | ! |
retargs <- dotargs |
| 51 | 309x |
} else if (any(names(dotargs) %in% formnms)) {
|
| 52 | 11x |
retargs <- dotargs[names(dotargs) %in% formnms] |
| 53 |
} |
|
| 54 | 309x |
retargs |
| 55 |
} |
|
| 56 | ||
| 57 |
#' Sorting a table at a specific path |
|
| 58 |
#' |
|
| 59 |
#' Main sorting function to order the sub-structure of a `TableTree` at a particular path in the table tree. |
|
| 60 |
#' |
|
| 61 |
#' @inheritParams gen_args |
|
| 62 |
#' @param scorefun (`function`)\cr scoring function. Should accept the type of children directly under the position |
|
| 63 |
#' at `path` (either `VTableTree`, `VTableRow`, or `VTableNodeInfo`, which covers both) and return a numeric value |
|
| 64 |
#' to be sorted. |
|
| 65 |
#' @param decreasing (`flag`)\cr whether the scores generated by `scorefun` should be sorted in decreasing order. If |
|
| 66 |
#' unset (the default of `NA`), it is set to `TRUE` if the generated scores are numeric and `FALSE` if they are |
|
| 67 |
#' characters. |
|
| 68 |
#' @param na.pos (`string`)\cr what should be done with children (sub-trees/rows) with `NA` scores. Defaults to |
|
| 69 |
#' `"omit"`, which removes them. Other allowed values are `"last"` and `"first"`, which indicate where `NA` scores |
|
| 70 |
#' should be placed in the order. |
|
| 71 |
#' @param .prev_path (`character`)\cr internal detail, do not set manually. |
|
| 72 |
#' @param ... Additional (named) arguments that will be passed directly down to |
|
| 73 |
#' `score_fun` *if* it accepts them (or accepts `...` itself). |
|
| 74 |
#' |
|
| 75 |
#' @return A `TableTree` with the same structure as `tt` with the exception that the requested sorting has been done |
|
| 76 |
#' at `path`. |
|
| 77 |
#' |
|
| 78 |
#' @details |
|
| 79 |
#' `sort_at_path`, given a path, locates the (sub)table(s) described by the path (see below for handling of the `"*"` |
|
| 80 |
#' wildcard). For each such subtable, it then calls `scorefun` on each direct child of the table, using the resulting |
|
| 81 |
#' scores to determine their sorted order. `tt` is then modified to reflect each of these one or more sorting |
|
| 82 |
#' operations. |
|
| 83 |
#' |
|
| 84 |
#' `score_fun` can optionally accept `decreasing`, which will be passed the value passed |
|
| 85 |
#' to `sort_at_path` automatically, and other arguments which can be set via `...`. The |
|
| 86 |
#' first argument passed to `scorefun` will always be the table structure (subtable or row) |
|
| 87 |
#' it is scoring. |
|
| 88 |
#' |
|
| 89 |
#' In `path`, a leading `"root"` element will be ignored, regardless of whether this matches the object name (and thus |
|
| 90 |
#' actual root path name) of `tt`. Including `"root"` in paths where it does not match the name of `tt` may mask deeper |
|
| 91 |
#' misunderstandings of how valid paths within a `TableTree` object correspond to the layout used to originally declare |
|
| 92 |
#' it, which we encourage users to avoid. |
|
| 93 |
#' |
|
| 94 |
#' `path` can include the "wildcard" `"*"` as a step, which translates roughly to *any* node/branching element and means |
|
| 95 |
#' that each child at that step will be *separately* sorted based on `scorefun` and the remaining `path` entries. This |
|
| 96 |
#' can occur multiple times in a path. |
|
| 97 |
#' |
|
| 98 |
#' A list of valid (non-wildcard) paths can be seen in the `path` column of the `data.frame` created by |
|
| 99 |
#' [formatters::make_row_df()] with the `visible_only` argument set to `FALSE`. It can also be inferred from the |
|
| 100 |
#' summary given by [table_structure()]. |
|
| 101 |
#' |
|
| 102 |
#' Note that sorting needs a deeper understanding of table structure in `rtables`. Please consider reading the related |
|
| 103 |
#' vignette |
|
| 104 |
#' ([Sorting and Pruning](https://insightsengineering.github.io/rtables/latest-tag/articles/sorting_pruning.html)) |
|
| 105 |
#' and explore table structure with useful functions like [table_structure()] and [row_paths_summary()]. It is also |
|
| 106 |
#' very important to understand the difference between "content" rows and "data" rows. The first one analyzes and |
|
| 107 |
#' describes the split variable generally and is generated with [summarize_row_groups()], while the second one is |
|
| 108 |
#' commonly produced by calling one of the various [analyze()] instances. |
|
| 109 |
#' |
|
| 110 |
#' Built-in score functions are [cont_n_allcols()] and [cont_n_onecol()]. They are both working with content rows |
|
| 111 |
#' (coming from [summarize_row_groups()]) while a custom score function needs to be used on `DataRow`s. Here, some |
|
| 112 |
#' useful descriptor and accessor functions (coming from related vignette): |
|
| 113 |
#' - [cell_values()] - Retrieves a named list of a `TableRow` or `TableTree` object's values. |
|
| 114 |
#' - [formatters::obj_name()] - Retrieves the name of an object. Note this can differ from the label that is |
|
| 115 |
#' displayed (if any is) when printing. |
|
| 116 |
#' - [formatters::obj_label()] - Retrieves the display label of an object. Note this can differ from the name that |
|
| 117 |
#' appears in the path. |
|
| 118 |
#' - [content_table()] - Retrieves a `TableTree` object's content table (which contains its summary rows). |
|
| 119 |
#' - [tree_children()] - Retrieves a `TableTree` object's direct children (either subtables, rows or possibly a mix |
|
| 120 |
#' thereof, though that should not happen in practice). |
|
| 121 |
#' |
|
| 122 |
#' @seealso |
|
| 123 |
#' * Score functions [cont_n_allcols()] and [cont_n_onecol()]. |
|
| 124 |
#' * [formatters::make_row_df()] and [table_structure()] for pathing information. |
|
| 125 |
#' * [tt_at_path()] to select a table's (sub)structure at a given path. |
|
| 126 |
#' |
|
| 127 |
#' @examples |
|
| 128 |
#' # Creating a table to sort |
|
| 129 |
#' |
|
| 130 |
#' # Function that gives two statistics per table-tree "leaf" |
|
| 131 |
#' more_analysis_fnc <- function(x) {
|
|
| 132 |
#' in_rows( |
|
| 133 |
#' "median" = median(x), |
|
| 134 |
#' "mean" = mean(x), |
|
| 135 |
#' .formats = "xx.x" |
|
| 136 |
#' ) |
|
| 137 |
#' } |
|
| 138 |
#' |
|
| 139 |
#' # Main layout of the table |
|
| 140 |
#' raw_lyt <- basic_table() %>% |
|
| 141 |
#' split_cols_by("ARM") %>%
|
|
| 142 |
#' split_rows_by( |
|
| 143 |
#' "RACE", |
|
| 144 |
#' split_fun = drop_and_remove_levels("WHITE") # dropping WHITE levels
|
|
| 145 |
#' ) %>% |
|
| 146 |
#' summarize_row_groups() %>% |
|
| 147 |
#' split_rows_by("STRATA1") %>%
|
|
| 148 |
#' summarize_row_groups() %>% |
|
| 149 |
#' analyze("AGE", afun = more_analysis_fnc)
|
|
| 150 |
#' |
|
| 151 |
#' # Creating the table and pruning empty and NAs |
|
| 152 |
#' tbl <- build_table(raw_lyt, DM) %>% |
|
| 153 |
#' prune_table() |
|
| 154 |
#' |
|
| 155 |
#' # Peek at the table structure to understand how it is built |
|
| 156 |
#' table_structure(tbl) |
|
| 157 |
#' |
|
| 158 |
#' # Sorting only ASIAN sub-table, or, in other words, sorting STRATA elements for |
|
| 159 |
#' # the ASIAN group/row-split. This uses content_table() accessor function as it |
|
| 160 |
#' # is a "ContentRow". In this case, we also base our sorting only on the second column. |
|
| 161 |
#' sort_at_path(tbl, c("ASIAN", "STRATA1"), cont_n_onecol(2))
|
|
| 162 |
#' |
|
| 163 |
#' # Custom scoring function that is working on "DataRow"s |
|
| 164 |
#' scorefun <- function(tt) {
|
|
| 165 |
#' # Here we could use browser() |
|
| 166 |
#' sum(unlist(row_values(tt))) # Different accessor function |
|
| 167 |
#' } |
|
| 168 |
#' # Sorting mean and median for all the AGE leaves! |
|
| 169 |
#' sort_at_path(tbl, c("RACE", "*", "STRATA1", "*", "AGE"), scorefun)
|
|
| 170 |
#' |
|
| 171 |
#' last_cat_scorefun <- function(x, decreasing, lastcat) {
|
|
| 172 |
#' mycat <- obj_name(x) |
|
| 173 |
#' if (mycat == lastcat) {
|
|
| 174 |
#' ifelse(isTRUE(decreasing), -Inf, Inf) |
|
| 175 |
#' } else {
|
|
| 176 |
#' match(tolower(substr(mycat, 1, 1)), letters) |
|
| 177 |
#' } |
|
| 178 |
#' } |
|
| 179 |
#' |
|
| 180 |
#' lyt2 <- basic_table() %>% |
|
| 181 |
#' split_rows_by("SEX") %>%
|
|
| 182 |
#' analyze("AGE")
|
|
| 183 |
#' |
|
| 184 |
#' tbl2 <- build_table(lyt2, DM) |
|
| 185 |
#' sort_at_path(tbl2, "SEX", last_cat_scorefun, lastcat = "M") |
|
| 186 |
#' sort_at_path(tbl2, "SEX", last_cat_scorefun, lastcat = "M", decreasing = FALSE) |
|
| 187 |
#' |
|
| 188 |
#' @export |
|
| 189 |
sort_at_path <- function(tt, |
|
| 190 |
path, |
|
| 191 |
scorefun, |
|
| 192 |
decreasing = NA, |
|
| 193 |
na.pos = c("omit", "last", "first"),
|
|
| 194 |
.prev_path = character(), |
|
| 195 |
...) {
|
|
| 196 | 52x |
if (NROW(tt) == 0) {
|
| 197 | 1x |
return(tt) |
| 198 |
} |
|
| 199 | ||
| 200 |
## XXX hacky fix this!!! |
|
| 201 |
## tt_at_path removes root even if actual root table isn't named root, we need to match that behavior |
|
| 202 | 51x |
if (path[1] == "root") {
|
| 203 |
## always remove first root element but only add it to |
|
| 204 |
## .prev_path (used for error reporting) if it actually matched the name |
|
| 205 | 2x |
if (obj_name(tt) == "root") {
|
| 206 | 2x |
.prev_path <- c(.prev_path, path[1]) |
| 207 |
} |
|
| 208 | 2x |
path <- path[-1] |
| 209 |
} |
|
| 210 | 51x |
if (identical(obj_name(tt), path[1])) {
|
| 211 | 6x |
.prev_path <- c(.prev_path, path[1]) |
| 212 | 6x |
path <- path[-1] |
| 213 |
} |
|
| 214 | ||
| 215 | 51x |
curpath <- path |
| 216 | 51x |
subtree <- tt |
| 217 | 51x |
backpath <- c() |
| 218 | 51x |
count <- 0 |
| 219 | 51x |
while (length(curpath) > 0) {
|
| 220 | 59x |
curname <- curpath[1] |
| 221 | 59x |
oldkids <- tree_children(subtree) |
| 222 |
## we sort each child separately based on the score function |
|
| 223 |
## and the remaining path |
|
| 224 | 59x |
if (curname == "*") {
|
| 225 | 12x |
oldnames <- vapply(oldkids, obj_name, "") |
| 226 | 12x |
newkids <- lapply( |
| 227 | 12x |
seq_along(oldkids), |
| 228 | 12x |
function(i) {
|
| 229 | 38x |
sort_at_path(oldkids[[i]], |
| 230 | 38x |
path = curpath[-1], |
| 231 | 38x |
scorefun = scorefun, |
| 232 | 38x |
decreasing = decreasing, |
| 233 | 38x |
na.pos = na.pos, |
| 234 |
## its ok to modify the "path" here because its only ever used for |
|
| 235 |
## informative error reporting. |
|
| 236 | 38x |
.prev_path = c(.prev_path, backpath, paste0("* (", oldnames[i], ")")),
|
| 237 |
... |
|
| 238 |
) |
|
| 239 |
} |
|
| 240 |
) |
|
| 241 | 7x |
names(newkids) <- oldnames |
| 242 | 7x |
newtab <- subtree |
| 243 | 7x |
tree_children(newtab) <- newkids |
| 244 | 7x |
if (length(backpath) > 0) {
|
| 245 | 3x |
ret <- recursive_replace(tt, backpath, value = newtab) |
| 246 |
} else {
|
|
| 247 | 4x |
ret <- newtab |
| 248 |
} |
|
| 249 | 7x |
return(ret) |
| 250 | 47x |
} else if (!(curname %in% names(oldkids))) {
|
| 251 | 1x |
stop( |
| 252 | 1x |
"Unable to find child(ren) '", |
| 253 | 1x |
curname, "'\n\t occurred at path: ", |
| 254 | 1x |
paste(c(.prev_path, path[seq_len(count)]), collapse = " -> "), |
| 255 | 1x |
"\n Use 'make_row_df(obj, visible_only = TRUE)[, c(\"label\", \"path\", \"node_class\")]' or \n", |
| 256 | 1x |
"'table_structure(obj)' to explore valid paths." |
| 257 |
) |
|
| 258 |
} |
|
| 259 | 46x |
subtree <- tree_children(subtree)[[curname]] |
| 260 | 46x |
backpath <- c(backpath, curpath[1]) |
| 261 | 46x |
curpath <- curpath[-1] |
| 262 | 46x |
count <- count + 1 |
| 263 |
} |
|
| 264 | 38x |
real_backpath <- path[seq_len(count)] |
| 265 | ||
| 266 | 38x |
na.pos <- match.arg(na.pos) |
| 267 |
## subtree <- tt_at_path(tt, path) |
|
| 268 | 38x |
kids <- tree_children(subtree) |
| 269 |
## relax this to allow character "scores" |
|
| 270 |
## scores <- vapply(kids, scorefun, NA_real_) |
|
| 271 | 38x |
more_args <- match_fun_args(scorefun, decreasing = decreasing, ...) |
| 272 | 38x |
scores <- lapply(kids, function(x) {
|
| 273 | 100x |
tryCatch(do.call(scorefun, c(list(x), more_args)), |
| 274 | 100x |
error = function(e) e |
| 275 |
) |
|
| 276 |
}) |
|
| 277 | 38x |
errs <- which(vapply(scores, is, class2 = "error", TRUE)) |
| 278 | 38x |
if (length(errs) > 0) {
|
| 279 | 4x |
stop("Encountered at least ", length(errs), " error(s) when applying score function.\n",
|
| 280 | 4x |
"First error: ", scores[[errs[1]]]$message, |
| 281 | 4x |
"\n\toccurred at path: ", |
| 282 | 4x |
paste(c(.prev_path, real_backpath, names(kids)[errs[1]]), collapse = " -> "), |
| 283 | 4x |
call. = FALSE |
| 284 |
) |
|
| 285 |
} else {
|
|
| 286 | 34x |
scores <- unlist(scores) |
| 287 |
} |
|
| 288 | 34x |
if (!is.null(dim(scores)) || length(scores) != length(kids)) {
|
| 289 | ! |
stop( |
| 290 | ! |
"Score function does not appear to have return exactly one ", |
| 291 | ! |
"scalar value per child" |
| 292 |
) |
|
| 293 |
} |
|
| 294 | 34x |
if (is.na(decreasing)) {
|
| 295 | 18x |
decreasing <- if (is.character(scores)) FALSE else TRUE |
| 296 |
} |
|
| 297 | 34x |
ord <- order(scores, na.last = (na.pos != "first"), decreasing = decreasing) |
| 298 | 34x |
newkids <- kids[ord] |
| 299 | 34x |
if (anyNA(scores) && na.pos == "omit") { # we did na last here
|
| 300 | ! |
newkids <- head(newkids, -1 * sum(is.na(scores))) |
| 301 |
} |
|
| 302 | ||
| 303 | 34x |
newtree <- subtree |
| 304 | 34x |
tree_children(newtree) <- newkids |
| 305 | 34x |
tt_at_path(tt, path) <- newtree |
| 306 | 34x |
tt |
| 307 |
} |
| 1 |
insert_brs <- function(vec) {
|
|
| 2 | 1021x |
if (length(vec) == 1) {
|
| 3 | 1021x |
ret <- list(vec) |
| 4 |
} else {
|
|
| 5 | ! |
nout <- length(vec) * 2 - 1 |
| 6 | ! |
ret <- vector("list", nout)
|
| 7 | ! |
for (i in seq_along(vec)) {
|
| 8 | ! |
ret[[2 * i - 1]] <- vec[i] |
| 9 | ! |
if (2 * i < nout) {
|
| 10 | ! |
ret[[2 * i]] <- tags$br() |
| 11 |
} |
|
| 12 |
} |
|
| 13 |
} |
|
| 14 | 1021x |
ret |
| 15 |
} |
|
| 16 | ||
| 17 |
div_helper <- function(lst, class) {
|
|
| 18 | 72x |
do.call(tags$div, c(list(class = paste(class, "rtables-container"), lst))) |
| 19 |
} |
|
| 20 | ||
| 21 |
#' Convert an `rtable` object to a `shiny.tag` HTML object |
|
| 22 |
#' |
|
| 23 |
#' The returned HTML object can be immediately used in `shiny` and `rmarkdown`. |
|
| 24 |
#' |
|
| 25 |
#' @param x (`VTableTree`)\cr a `TableTree` object. |
|
| 26 |
#' @param class_table (`character`)\cr class for `table` tag. |
|
| 27 |
#' @param class_tr (`character`)\cr class for `tr` tag. |
|
| 28 |
#' @param class_th (`character`)\cr class for `th` tag. |
|
| 29 |
#' @param width (`character`)\cr a string to indicate the desired width of the table. Common input formats include a |
|
| 30 |
#' percentage of the viewer window width (e.g. `"100%"`) or a distance value (e.g. `"300px"`). Defaults to `NULL`. |
|
| 31 |
#' @param link_label (`character`)\cr link anchor label (not including `tab:` prefix) for the table. |
|
| 32 |
#' @param bold (`character`)\cr elements in table output that should be bold. Options are `"main_title"`, |
|
| 33 |
#' `"subtitles"`, `"header"`, `"row_names"`, `"label_rows"`, and `"content_rows"` (which includes any non-label |
|
| 34 |
#' rows). Defaults to `"header"`. |
|
| 35 |
#' @param header_sep_line (`flag`)\cr whether a black line should be printed to under the table header. Defaults |
|
| 36 |
#' to `TRUE`. |
|
| 37 |
#' @param no_spaces_between_cells (`flag`)\cr whether spaces between table cells should be collapsed. Defaults |
|
| 38 |
#' to `FALSE`. |
|
| 39 |
#' @param expand_newlines (`flag`)\cr Defaults to `FALSE`, relying on `html` output to solve newline characters (`\n`). |
|
| 40 |
#' Doing this keeps the structure of the cells but may depend on the output device. |
|
| 41 |
#' |
|
| 42 |
#' @importFrom htmltools tags |
|
| 43 |
#' |
|
| 44 |
#' @return A `shiny.tag` object representing `x` in HTML. |
|
| 45 |
#' |
|
| 46 |
#' @examples |
|
| 47 |
#' tbl <- rtable( |
|
| 48 |
#' header = LETTERS[1:3], |
|
| 49 |
#' format = "xx", |
|
| 50 |
#' rrow("r1", 1, 2, 3),
|
|
| 51 |
#' rrow("r2", 4, 3, 2, indent = 1),
|
|
| 52 |
#' rrow("r3", indent = 2)
|
|
| 53 |
#' ) |
|
| 54 |
#' |
|
| 55 |
#' as_html(tbl) |
|
| 56 |
#' |
|
| 57 |
#' as_html(tbl, class_table = "table", class_tr = "row") |
|
| 58 |
#' |
|
| 59 |
#' as_html(tbl, bold = c("header", "row_names"))
|
|
| 60 |
#' |
|
| 61 |
#' \dontrun{
|
|
| 62 |
#' Viewer(tbl) |
|
| 63 |
#' } |
|
| 64 |
#' |
|
| 65 |
#' @export |
|
| 66 |
as_html <- function(x, |
|
| 67 |
width = NULL, |
|
| 68 |
class_table = "table table-condensed table-hover", |
|
| 69 |
class_tr = NULL, |
|
| 70 |
class_th = NULL, |
|
| 71 |
link_label = NULL, |
|
| 72 |
bold = c("header"),
|
|
| 73 |
header_sep_line = TRUE, |
|
| 74 |
no_spaces_between_cells = FALSE, |
|
| 75 |
expand_newlines = FALSE) {
|
|
| 76 | 9x |
if (is.null(x)) {
|
| 77 | ! |
return(tags$p("Empty Table"))
|
| 78 |
} |
|
| 79 | ||
| 80 | 9x |
stopifnot(is(x, "VTableTree")) |
| 81 | ||
| 82 | 9x |
mat <- matrix_form(x, indent_rownames = TRUE, expand_newlines = expand_newlines) |
| 83 | ||
| 84 | 9x |
nlh <- mf_nlheader(mat) |
| 85 | 9x |
nc <- ncol(x) + 1 |
| 86 | 9x |
nr <- length(mf_lgrouping(mat)) |
| 87 | ||
| 88 |
# Structure is a list of lists with rows (one for each line grouping) and cols as dimensions |
|
| 89 | 9x |
cells <- matrix(rep(list(list()), (nr * nc)), ncol = nc) |
| 90 | ||
| 91 | 9x |
for (i in seq_len(nr)) {
|
| 92 | 173x |
for (j in seq_len(nc)) {
|
| 93 | 1021x |
curstrs <- mf_strings(mat)[i, j] |
| 94 | 1021x |
curspn <- mf_spans(mat)[i, j] |
| 95 | 1021x |
algn <- mf_aligns(mat)[i, j] |
| 96 | ||
| 97 | 1021x |
inhdr <- i <= nlh |
| 98 | 1021x |
tagfun <- if (inhdr) tags$th else tags$td |
| 99 | 1021x |
cells[i, j][[1]] <- tagfun( |
| 100 | 1021x |
class = if (inhdr) class_th else class_tr, |
| 101 | 1021x |
style = paste0("text-align: ", algn, ";"),
|
| 102 | 1021x |
style = if (inhdr && !"header" %in% bold) "font-weight: normal;", |
| 103 | 1021x |
style = if (i == nlh && header_sep_line) "border-bottom: 1px solid black;", |
| 104 | 1021x |
colspan = if (curspn != 1) curspn, |
| 105 | 1021x |
insert_brs(curstrs) |
| 106 |
) |
|
| 107 |
} |
|
| 108 |
} |
|
| 109 | ||
| 110 | 9x |
if (header_sep_line) {
|
| 111 | 9x |
cells[nlh][[1]] <- htmltools::tagAppendAttributes( |
| 112 | 9x |
cells[nlh, 1][[1]], |
| 113 | 9x |
style = "border-bottom: 1px solid black;" |
| 114 |
) |
|
| 115 |
} |
|
| 116 | ||
| 117 |
# Create a map between line numbers and line groupings, adjusting abs_rownumber with nlh |
|
| 118 | 9x |
map <- data.frame(lines = seq_len(nr), abs_rownumber = mat$line_grouping) |
| 119 | 9x |
row_info_df <- data.frame(indent = mat$row_info$indent, abs_rownumber = mat$row_info$abs_rownumber + nlh) |
| 120 | 9x |
map <- merge(map, row_info_df, by = "abs_rownumber") |
| 121 | ||
| 122 |
# add indent values for headerlines |
|
| 123 | 9x |
map <- rbind(data.frame(abs_rownumber = 1:nlh, indent = 0, lines = 0), map) |
| 124 | ||
| 125 | ||
| 126 |
# Row labels style |
|
| 127 | 9x |
for (i in seq_len(nr)) {
|
| 128 | 173x |
indent <- ifelse(any(map$lines == i), map$indent[map$lines == i][1], -1) |
| 129 | ||
| 130 |
# Apply indentation |
|
| 131 | 173x |
if (indent > 0) {
|
| 132 | 127x |
cells[i, 1][[1]] <- htmltools::tagAppendAttributes( |
| 133 | 127x |
cells[i, 1][[1]], |
| 134 | 127x |
style = paste0("padding-left: ", indent * 3, "ch;")
|
| 135 |
) |
|
| 136 |
} |
|
| 137 | ||
| 138 |
# Apply bold font weight if "row_names" is in 'bold' |
|
| 139 | 173x |
if ("row_names" %in% bold) {
|
| 140 | 4x |
cells[i, 1][[1]] <- htmltools::tagAppendAttributes( |
| 141 | 4x |
cells[i, 1][[1]], |
| 142 | 4x |
style = "font-weight: bold;" |
| 143 |
) |
|
| 144 |
} |
|
| 145 |
} |
|
| 146 | ||
| 147 |
# label rows style |
|
| 148 | 9x |
if ("label_rows" %in% bold) {
|
| 149 | ! |
which_lbl_rows <- which(mat$row_info$node_class == "LabelRow") |
| 150 | ! |
cells[which_lbl_rows + nlh, ] <- lapply( |
| 151 | ! |
cells[which_lbl_rows + nlh, ], |
| 152 | ! |
htmltools::tagAppendAttributes, |
| 153 | ! |
style = "font-weight: bold;" |
| 154 |
) |
|
| 155 |
} |
|
| 156 | ||
| 157 |
# content rows style |
|
| 158 | 9x |
if ("content_rows" %in% bold) {
|
| 159 | ! |
which_cntnt_rows <- which(mat$row_info$node_class %in% c("ContentRow", "DataRow"))
|
| 160 | ! |
cells[which_cntnt_rows + nlh, ] <- lapply( |
| 161 | ! |
cells[which_cntnt_rows + nlh, ], |
| 162 | ! |
htmltools::tagAppendAttributes, |
| 163 | ! |
style = "font-weight: bold;" |
| 164 |
) |
|
| 165 |
} |
|
| 166 | ||
| 167 | 9x |
if (any(!mat$display)) {
|
| 168 |
# Check that expansion kept the same display info |
|
| 169 | 2x |
check_expansion <- c() |
| 170 | 2x |
for (ii in unique(mat$line_grouping)) {
|
| 171 | 121x |
rows <- which(mat$line_grouping == ii) |
| 172 | 121x |
check_expansion <- c( |
| 173 | 121x |
check_expansion, |
| 174 | 121x |
apply(mat$display[rows, , drop = FALSE], 2, function(x) all(x) || all(!x)) |
| 175 |
) |
|
| 176 |
} |
|
| 177 | ||
| 178 | 2x |
if (!all(check_expansion)) {
|
| 179 | ! |
stop( |
| 180 | ! |
"Found that a group of rows have different display options even if ", |
| 181 | ! |
"they belong to the same line group. This should not happen. Please ", |
| 182 | ! |
"file an issue or report to the maintainers." |
| 183 | ! |
) # nocov |
| 184 |
} |
|
| 185 | ||
| 186 | 2x |
for (ii in unique(mat$line_grouping)) {
|
| 187 | 121x |
rows <- which(mat$line_grouping == ii) |
| 188 | 121x |
should_display_col <- apply(mat$display[rows, , drop = FALSE], 2, any) |
| 189 | 121x |
cells[ii, !should_display_col] <- NA_integer_ |
| 190 |
} |
|
| 191 |
} |
|
| 192 | ||
| 193 | 9x |
rows <- apply(cells, 1, function(row) {
|
| 194 | 173x |
tags$tr( |
| 195 | 173x |
class = class_tr, |
| 196 | 173x |
style = "white-space: pre;", |
| 197 | 173x |
Filter(function(x) !identical(x, NA_integer_), row) |
| 198 |
) |
|
| 199 |
}) |
|
| 200 | ||
| 201 | 9x |
hsep_line <- tags$hr(class = "solid") |
| 202 | ||
| 203 | 9x |
hdrtag <- div_helper( |
| 204 | 9x |
class = "rtables-titles-block", |
| 205 | 9x |
list( |
| 206 | 9x |
div_helper( |
| 207 | 9x |
class = "rtables-main-titles-block", |
| 208 | 9x |
lapply(main_title(x), if ("main_title" %in% bold) tags$b else tags$p,
|
| 209 | 9x |
class = "rtables-main-title" |
| 210 |
) |
|
| 211 |
), |
|
| 212 | 9x |
div_helper( |
| 213 | 9x |
class = "rtables-subtitles-block", |
| 214 | 9x |
lapply(subtitles(x), if ("subtitles" %in% bold) tags$b else tags$p,
|
| 215 | 9x |
class = "rtables-subtitle" |
| 216 |
) |
|
| 217 |
) |
|
| 218 |
) |
|
| 219 |
) |
|
| 220 | ||
| 221 | 9x |
tabletag <- do.call( |
| 222 | 9x |
tags$table, |
| 223 | 9x |
c( |
| 224 | 9x |
rows, |
| 225 | 9x |
list( |
| 226 | 9x |
class = class_table, |
| 227 | 9x |
style = paste( |
| 228 | 9x |
if (no_spaces_between_cells) "border-collapse: collapse;", |
| 229 | 9x |
if (!is.null(width)) paste("width:", width)
|
| 230 |
), |
|
| 231 | 9x |
tags$caption(sprintf("(\\#tag:%s)", link_label),
|
| 232 | 9x |
style = "caption-side: top;", |
| 233 | 9x |
.noWS = "after-begin" |
| 234 |
) |
|
| 235 |
) |
|
| 236 |
) |
|
| 237 |
) |
|
| 238 | ||
| 239 | 9x |
rfnotes <- div_helper( |
| 240 | 9x |
class = "rtables-ref-footnotes-block", |
| 241 | 9x |
lapply(mat$ref_footnotes, tags$p, |
| 242 | 9x |
class = "rtables-referential-footnote" |
| 243 |
) |
|
| 244 |
) |
|
| 245 | ||
| 246 | 9x |
mftr <- div_helper( |
| 247 | 9x |
class = "rtables-main-footers-block", |
| 248 | 9x |
lapply(main_footer(x), tags$p, |
| 249 | 9x |
class = "rtables-main-footer" |
| 250 |
) |
|
| 251 |
) |
|
| 252 | ||
| 253 | 9x |
pftr <- div_helper( |
| 254 | 9x |
class = "rtables-prov-footers-block", |
| 255 | 9x |
lapply(prov_footer(x), tags$p, |
| 256 | 9x |
class = "rtables-prov-footer" |
| 257 |
) |
|
| 258 |
) |
|
| 259 | ||
| 260 |
## XXX this omits the divs entirely if they are empty. Do we want that or do |
|
| 261 |
## we want them to be there but empty?? |
|
| 262 | 9x |
ftrlst <- list( |
| 263 | 9x |
if (length(mat$ref_footnotes) > 0) rfnotes, |
| 264 | 9x |
if (length(mat$ref_footnotes) > 0) hsep_line, |
| 265 | 9x |
if (length(main_footer(x)) > 0) mftr, |
| 266 | 9x |
if (length(main_footer(x)) > 0 && length(prov_footer(x)) > 0) tags$br(), # line break |
| 267 | 9x |
if (length(prov_footer(x)) > 0) pftr |
| 268 |
) |
|
| 269 | ||
| 270 | ! |
if (!is.null(unlist(ftrlst))) ftrlst <- c(list(hsep_line), ftrlst) |
| 271 | 9x |
ftrlst <- ftrlst[!vapply(ftrlst, is.null, TRUE)] |
| 272 | ||
| 273 | 9x |
ftrtag <- div_helper( |
| 274 | 9x |
class = "rtables-footers-block", |
| 275 | 9x |
ftrlst |
| 276 |
) |
|
| 277 | ||
| 278 | 9x |
div_helper( |
| 279 | 9x |
class = "rtables-all-parts-block", |
| 280 | 9x |
list( |
| 281 | 9x |
tags$head( |
| 282 | 9x |
tags$style( |
| 283 | 9x |
".rtables-all-parts-block table tr { border-top: 1px solid #ddd;}"
|
| 284 |
) |
|
| 285 |
), |
|
| 286 | 9x |
hdrtag, |
| 287 | 9x |
tabletag, |
| 288 | 9x |
ftrtag |
| 289 |
) |
|
| 290 |
) |
|
| 291 |
} |
| 1 |
treestruct <- function(obj, ind = 0L) {
|
|
| 2 | 19x |
nc <- ncol(obj) |
| 3 | 19x |
cat(rep(" ", times = ind),
|
| 4 | 19x |
sprintf("[%s] %s", class(obj), obj_name(obj)),
|
| 5 | 19x |
sep = "" |
| 6 |
) |
|
| 7 | 19x |
if (!is(obj, "ElementaryTable") && nrow(obj@content) > 0) {
|
| 8 | 6x |
crows <- nrow(content_table(obj)) |
| 9 | 6x |
ccols <- if (crows == 0) 0 else nc |
| 10 | 6x |
cat(sprintf( |
| 11 | 6x |
" [cont: %d x %d]", |
| 12 | 6x |
crows, ccols |
| 13 |
)) |
|
| 14 |
} |
|
| 15 | 19x |
if (is(obj, "VTableTree") && length(tree_children(obj))) {
|
| 16 | 19x |
kids <- tree_children(obj) |
| 17 | 19x |
if (are(kids, "TableRow")) {
|
| 18 | 9x |
cat(sprintf( |
| 19 | 9x |
" (%d x %d)\n", |
| 20 | 9x |
length(kids), nc |
| 21 |
)) |
|
| 22 |
} else {
|
|
| 23 | 10x |
cat("\n")
|
| 24 | 10x |
lapply(kids, treestruct, ind = ind + 1) |
| 25 |
} |
|
| 26 |
} |
|
| 27 | 19x |
invisible(NULL) |
| 28 |
} |
|
| 29 | ||
| 30 |
setGeneric( |
|
| 31 |
"ploads_to_str", |
|
| 32 | 103x |
function(x, collapse = ":") standardGeneric("ploads_to_str")
|
| 33 |
) |
|
| 34 | ||
| 35 |
setMethod( |
|
| 36 |
"ploads_to_str", "Split", |
|
| 37 |
function(x, collapse = ":") {
|
|
| 38 | 52x |
paste(sapply(spl_payload(x), ploads_to_str), |
| 39 | 52x |
collapse = collapse |
| 40 |
) |
|
| 41 |
} |
|
| 42 |
) |
|
| 43 | ||
| 44 |
setMethod( |
|
| 45 |
"ploads_to_str", "CompoundSplit", |
|
| 46 |
function(x, collapse = ":") {
|
|
| 47 | 6x |
paste(sapply(spl_payload(x), ploads_to_str), |
| 48 | 6x |
collapse = collapse |
| 49 |
) |
|
| 50 |
} |
|
| 51 |
) |
|
| 52 | ||
| 53 |
setMethod( |
|
| 54 |
"ploads_to_str", "list", |
|
| 55 |
function(x, collapse = ":") {
|
|
| 56 | ! |
stop("Please contact the maintainer")
|
| 57 |
} |
|
| 58 |
) |
|
| 59 | ||
| 60 |
setMethod( |
|
| 61 |
"ploads_to_str", "SplitVector", |
|
| 62 |
function(x, collapse = ":") {
|
|
| 63 | 8x |
sapply(x, ploads_to_str) |
| 64 |
} |
|
| 65 |
) |
|
| 66 | ||
| 67 |
setMethod( |
|
| 68 |
"ploads_to_str", "ANY", |
|
| 69 |
function(x, collapse = ":") {
|
|
| 70 | 37x |
paste(x) |
| 71 |
} |
|
| 72 |
) |
|
| 73 | ||
| 74 | 49x |
setGeneric("payloadmsg", function(spl) standardGeneric("payloadmsg"))
|
| 75 | ||
| 76 |
setMethod( |
|
| 77 |
"payloadmsg", "VarLevelSplit", |
|
| 78 |
function(spl) {
|
|
| 79 | 47x |
spl_payload(spl) |
| 80 |
} |
|
| 81 |
) |
|
| 82 | ||
| 83 |
setMethod( |
|
| 84 |
"payloadmsg", "MultiVarSplit", |
|
| 85 | 2x |
function(spl) "var" |
| 86 |
) |
|
| 87 | ||
| 88 |
setMethod( |
|
| 89 |
"payloadmsg", "VarLevWBaselineSplit", |
|
| 90 |
function(spl) {
|
|
| 91 | ! |
paste0( |
| 92 | ! |
spl_payload(spl), "[bsl ", |
| 93 | ! |
spl@ref_group_value, # XXX XXX |
| 94 |
"]" |
|
| 95 |
) |
|
| 96 |
} |
|
| 97 |
) |
|
| 98 | ||
| 99 |
setMethod( |
|
| 100 |
"payloadmsg", "ManualSplit", |
|
| 101 | ! |
function(spl) "mnl" |
| 102 |
) |
|
| 103 | ||
| 104 |
setMethod( |
|
| 105 |
"payloadmsg", "AllSplit", |
|
| 106 | ! |
function(spl) "all" |
| 107 |
) |
|
| 108 | ||
| 109 |
setMethod( |
|
| 110 |
"payloadmsg", "ANY", |
|
| 111 |
function(spl) {
|
|
| 112 | ! |
warning("don't know how to make payload print message for Split of class", class(spl))
|
| 113 | ! |
"XXX" |
| 114 |
} |
|
| 115 |
) |
|
| 116 | ||
| 117 |
spldesc <- function(spl, value = "") {
|
|
| 118 | 32x |
value <- rawvalues(value) |
| 119 | 32x |
payloadmsg <- payloadmsg(spl) |
| 120 | 32x |
format <- "%s (%s)" |
| 121 | 32x |
sprintf( |
| 122 | 32x |
format, |
| 123 | 32x |
value, |
| 124 | 32x |
payloadmsg |
| 125 |
) |
|
| 126 |
} |
|
| 127 | ||
| 128 |
layoutmsg <- function(obj) {
|
|
| 129 |
## if(!is(obj, "VLayoutNode")) |
|
| 130 |
## stop("how did a non layoutnode object get in docatlayout??")
|
|
| 131 | ||
| 132 | 28x |
pos <- tree_pos(obj) |
| 133 | 28x |
spllst <- pos_splits(pos) |
| 134 | 28x |
spvallst <- pos_splvals(pos) |
| 135 | 28x |
if (is(obj, "LayoutAxisTree")) {
|
| 136 | 12x |
kids <- tree_children(obj) |
| 137 | 12x |
return(unlist(lapply(kids, layoutmsg))) |
| 138 |
} |
|
| 139 | ||
| 140 | 16x |
msg <- paste( |
| 141 | 16x |
collapse = " -> ", |
| 142 | 16x |
mapply(spldesc, |
| 143 | 16x |
spl = spllst, |
| 144 | 16x |
value = spvallst |
| 145 |
) |
|
| 146 |
) |
|
| 147 | 16x |
msg |
| 148 |
} |
|
| 149 | ||
| 150 |
setMethod( |
|
| 151 |
"show", "LayoutAxisTree", |
|
| 152 |
function(object) {
|
|
| 153 | 2x |
msg <- layoutmsg(object) |
| 154 | 2x |
cat(msg, "\n") |
| 155 | 2x |
invisible(object) |
| 156 |
} |
|
| 157 |
) |
|
| 158 | ||
| 159 | ||
| 160 |
#' Display column tree structure |
|
| 161 |
#' |
|
| 162 |
#' Displays the tree structure of the columns of a |
|
| 163 |
#' table or column structure object. |
|
| 164 |
#' |
|
| 165 |
#' @inheritParams gen_args |
|
| 166 |
#' |
|
| 167 |
#' @return Nothing, called for its side effect of displaying |
|
| 168 |
#' a summary to the terminal. |
|
| 169 |
#' |
|
| 170 |
#' @examples |
|
| 171 |
#' lyt <- basic_table() %>% |
|
| 172 |
#' split_cols_by("ARM") %>%
|
|
| 173 |
#' split_cols_by("STRATA1") %>%
|
|
| 174 |
#' split_cols_by("SEX", nested = FALSE) %>%
|
|
| 175 |
#' analyze("AGE")
|
|
| 176 |
#' |
|
| 177 |
#' tbl <- build_table(lyt, ex_adsl) |
|
| 178 |
#' coltree_structure(tbl) |
|
| 179 |
#' @export |
|
| 180 |
coltree_structure <- function(obj) {
|
|
| 181 | 1x |
ctree <- coltree(obj) |
| 182 | 1x |
cat(layoutmsg2(ctree)) |
| 183 |
} |
|
| 184 | ||
| 185 |
lastposmsg <- function(pos) {
|
|
| 186 | 6x |
spls <- pos_splits(pos) |
| 187 | 6x |
splvals <- value_names(pos_splvals(pos)) |
| 188 | 6x |
indiv_msgs <- unlist(mapply(function(spl, valnm) paste(obj_name(spl), valnm, sep = ": "), |
| 189 | 6x |
spl = spls, |
| 190 | 6x |
valnm = splvals, |
| 191 | 6x |
SIMPLIFY = FALSE |
| 192 |
)) |
|
| 193 | 6x |
paste(indiv_msgs, collapse = " -> ") |
| 194 |
} |
|
| 195 | ||
| 196 |
layoutmsg2 <- function(obj, level = 1) {
|
|
| 197 | 7x |
nm <- obj_name(obj) |
| 198 | 7x |
pos <- tree_pos(obj) |
| 199 | 7x |
nopos <- identical(pos, EmptyTreePos) |
| 200 | ||
| 201 | 7x |
msg <- paste0(strrep(" ", times = 2 * (level - 1)), "[", nm, "] (", if (nopos) "no pos" else lastposmsg(pos), ")\n")
|
| 202 | 7x |
if (is(obj, "LayoutAxisTree")) {
|
| 203 | 3x |
kids <- tree_children(obj) |
| 204 | 3x |
msg <- c(msg, unlist(lapply(kids, layoutmsg2, level = level + 1))) |
| 205 |
} |
|
| 206 | 7x |
msg |
| 207 |
} |
|
| 208 | ||
| 209 | 46x |
setGeneric("spltype_abbrev", function(obj) standardGeneric("spltype_abbrev"))
|
| 210 | ||
| 211 |
setMethod( |
|
| 212 |
"spltype_abbrev", "VarLevelSplit", |
|
| 213 | 4x |
function(obj) "lvls" |
| 214 |
) |
|
| 215 | ||
| 216 |
setMethod( |
|
| 217 |
"spltype_abbrev", "VarLevWBaselineSplit", |
|
| 218 | 5x |
function(obj) paste("ref_group", obj@ref_group_value)
|
| 219 |
) |
|
| 220 | ||
| 221 |
setMethod( |
|
| 222 |
"spltype_abbrev", "MultiVarSplit", |
|
| 223 | ! |
function(obj) "vars" |
| 224 |
) |
|
| 225 | ||
| 226 |
setMethod( |
|
| 227 |
"spltype_abbrev", "VarStaticCutSplit", |
|
| 228 | 10x |
function(obj) "scut" |
| 229 |
) |
|
| 230 | ||
| 231 |
setMethod( |
|
| 232 |
"spltype_abbrev", "VarDynCutSplit", |
|
| 233 | 5x |
function(obj) "dcut" |
| 234 |
) |
|
| 235 |
setMethod( |
|
| 236 |
"spltype_abbrev", "AllSplit", |
|
| 237 | 15x |
function(obj) "all obs" |
| 238 |
) |
|
| 239 |
## setMethod("spltype_abbrev", "NULLSplit",
|
|
| 240 |
## function(obj) "no obs") |
|
| 241 | ||
| 242 |
setMethod( |
|
| 243 |
"spltype_abbrev", "AnalyzeVarSplit", |
|
| 244 | 1x |
function(obj) "** analysis **" |
| 245 |
) |
|
| 246 | ||
| 247 |
setMethod( |
|
| 248 |
"spltype_abbrev", "CompoundSplit", |
|
| 249 | ! |
function(obj) paste("compound", paste(sapply(spl_payload(obj), spltype_abbrev), collapse = " "))
|
| 250 |
) |
|
| 251 | ||
| 252 |
setMethod( |
|
| 253 |
"spltype_abbrev", "AnalyzeMultiVars", |
|
| 254 | 6x |
function(obj) "** multivar analysis **" |
| 255 |
) |
|
| 256 |
setMethod( |
|
| 257 |
"spltype_abbrev", "AnalyzeColVarSplit", |
|
| 258 | ! |
function(obj) "** col-var analysis **" |
| 259 |
) |
|
| 260 | ||
| 261 |
docat_splitvec <- function(object, indent = 0) {
|
|
| 262 | 8x |
if (indent > 0) {
|
| 263 | ! |
cat(rep(" ", times = indent), sep = "")
|
| 264 |
} |
|
| 265 | 8x |
if (length(object) == 1L && is(object[[1]], "VTableNodeInfo")) {
|
| 266 | ! |
tab <- object[[1]] |
| 267 | ! |
msg <- sprintf( |
| 268 | ! |
"A Pre-Existing Table [%d x %d]", |
| 269 | ! |
nrow(tab), ncol(tab) |
| 270 |
) |
|
| 271 |
} else {
|
|
| 272 | 8x |
plds <- ploads_to_str(object) ## lapply(object, spl_payload)) |
| 273 | ||
| 274 | 8x |
tabbrev <- sapply(object, spltype_abbrev) |
| 275 | 8x |
msg <- paste( |
| 276 | 8x |
collapse = " -> ", |
| 277 | 8x |
paste0(plds, " (", tabbrev, ")")
|
| 278 |
) |
|
| 279 |
} |
|
| 280 | 8x |
cat(msg, "\n") |
| 281 |
} |
|
| 282 | ||
| 283 |
setMethod( |
|
| 284 |
"show", "SplitVector", |
|
| 285 |
function(object) {
|
|
| 286 | 1x |
cat("A SplitVector Pre-defining a Tree Structure\n\n")
|
| 287 | 1x |
docat_splitvec(object) |
| 288 | 1x |
cat("\n")
|
| 289 | 1x |
invisible(object) |
| 290 |
} |
|
| 291 |
) |
|
| 292 | ||
| 293 |
docat_predataxis <- function(object, indent = 0) {
|
|
| 294 | 6x |
lapply(object, docat_splitvec) |
| 295 |
} |
|
| 296 | ||
| 297 |
setMethod( |
|
| 298 |
"show", "PreDataColLayout", |
|
| 299 |
function(object) {
|
|
| 300 | 1x |
cat("A Pre-data Column Layout Object\n\n")
|
| 301 | 1x |
docat_predataxis(object) |
| 302 | 1x |
invisible(object) |
| 303 |
} |
|
| 304 |
) |
|
| 305 | ||
| 306 |
setMethod( |
|
| 307 |
"show", "PreDataRowLayout", |
|
| 308 |
function(object) {
|
|
| 309 | 1x |
cat("A Pre-data Row Layout Object\n\n")
|
| 310 | 1x |
docat_predataxis(object) |
| 311 | 1x |
invisible(object) |
| 312 |
} |
|
| 313 |
) |
|
| 314 | ||
| 315 |
setMethod( |
|
| 316 |
"show", "PreDataTableLayouts", |
|
| 317 |
function(object) {
|
|
| 318 | 2x |
cat("A Pre-data Table Layout\n")
|
| 319 | 2x |
cat("\nColumn-Split Structure:\n")
|
| 320 | 2x |
docat_predataxis(object@col_layout) |
| 321 | 2x |
cat("\nRow-Split Structure:\n")
|
| 322 | 2x |
docat_predataxis(object@row_layout) |
| 323 | 2x |
cat("\n")
|
| 324 | 2x |
invisible(object) |
| 325 |
} |
|
| 326 |
) |
|
| 327 | ||
| 328 |
setMethod( |
|
| 329 |
"show", "InstantiatedColumnInfo", |
|
| 330 |
function(object) {
|
|
| 331 | 2x |
layoutmsg <- layoutmsg(coltree(object)) |
| 332 | 2x |
cat("An InstantiatedColumnInfo object",
|
| 333 | 2x |
"Columns:", |
| 334 | 2x |
layoutmsg, |
| 335 | 2x |
if (disp_ccounts(object)) {
|
| 336 | 2x |
paste( |
| 337 | 2x |
"ColumnCounts:\n", |
| 338 | 2x |
paste(col_counts(object), |
| 339 | 2x |
collapse = ", " |
| 340 |
) |
|
| 341 |
) |
|
| 342 |
}, |
|
| 343 |
"", |
|
| 344 | 2x |
sep = "\n" |
| 345 |
) |
|
| 346 | 2x |
invisible(object) |
| 347 |
} |
|
| 348 |
) |
|
| 349 | ||
| 350 |
#' @rdname int_methods |
|
| 351 |
setMethod("print", "VTableTree", function(x, ...) {
|
|
| 352 | 10x |
msg <- toString(x, ...) |
| 353 | 9x |
cat(msg) |
| 354 | 9x |
invisible(x) |
| 355 |
}) |
|
| 356 | ||
| 357 |
#' @rdname int_methods |
|
| 358 |
setMethod("show", "VTableTree", function(object) {
|
|
| 359 | ! |
cat(toString(object)) |
| 360 | ! |
invisible(object) |
| 361 |
}) |
|
| 362 | ||
| 363 |
setMethod("show", "TableRow", function(object) {
|
|
| 364 | 1x |
cat(sprintf( |
| 365 | 1x |
"[%s indent_mod %d]: %s %s\n", |
| 366 | 1x |
class(object), |
| 367 | 1x |
indent_mod(object), |
| 368 | 1x |
obj_label(object), |
| 369 | 1x |
paste(as.vector(get_formatted_cells(object)), |
| 370 | 1x |
collapse = " " |
| 371 |
) |
|
| 372 |
)) |
|
| 373 | 1x |
invisible(object) |
| 374 |
}) |
| 1 |
.reindex_one_pos <- function(refs, cur_idx_fun) {
|
|
| 2 | 2451x |
if (length(refs) == 0) {
|
| 3 | 2385x |
return(refs) |
| 4 |
} |
|
| 5 | ||
| 6 | 66x |
lapply(refs, function(refi) {
|
| 7 |
## these can be symbols, e.g. ^, †now, those are |
|
| 8 |
## special and don't get reindexed cause they're not numbered |
|
| 9 |
## to begin with |
|
| 10 | 71x |
idx <- ref_index(refi) |
| 11 | 71x |
if (is.na(idx) || !is.na(as.integer(idx))) {
|
| 12 | 71x |
ref_index(refi) <- cur_idx_fun(refi) |
| 13 |
} |
|
| 14 | 71x |
refi |
| 15 |
}) |
|
| 16 |
} |
|
| 17 | ||
| 18 | 56x |
setGeneric(".idx_helper", function(tr, cur_idx_fun) standardGeneric(".idx_helper"))
|
| 19 | ||
| 20 |
setMethod( |
|
| 21 |
".idx_helper", "TableRow", |
|
| 22 |
function(tr, cur_idx_fun) {
|
|
| 23 | 54x |
row_footnotes(tr) <- .reindex_one_pos( |
| 24 | 54x |
row_footnotes(tr), |
| 25 | 54x |
cur_idx_fun |
| 26 |
) |
|
| 27 | ||
| 28 | 54x |
cell_footnotes(tr) <- lapply(cell_footnotes(tr), ## crfs, |
| 29 | 54x |
.reindex_one_pos, |
| 30 | 54x |
cur_idx_fun = cur_idx_fun |
| 31 |
) |
|
| 32 | 54x |
tr |
| 33 |
} |
|
| 34 |
) |
|
| 35 | ||
| 36 |
setMethod( |
|
| 37 |
".idx_helper", "VTableTree", |
|
| 38 |
function(tr, cur_idx_fun) {
|
|
| 39 | 2x |
if (!labelrow_visible(tr)) {
|
| 40 |
stop("got a row footnote on a non-visible label row. this should never happen") # nocov
|
|
| 41 |
} |
|
| 42 | 2x |
lr <- tt_labelrow(tr) |
| 43 | ||
| 44 | 2x |
row_footnotes(lr) <- .reindex_one_pos( |
| 45 | 2x |
row_footnotes(lr), |
| 46 | 2x |
cur_idx_fun |
| 47 |
) |
|
| 48 | ||
| 49 | 2x |
tt_labelrow(tr) <- lr |
| 50 | ||
| 51 | 2x |
tr |
| 52 |
} |
|
| 53 |
) |
|
| 54 | ||
| 55 |
index_col_refs <- function(tt, cur_idx_fun) {
|
|
| 56 | 475x |
ctree <- coltree(tt) |
| 57 | 475x |
ctree <- .index_col_refs_inner(ctree, cur_idx_fun) |
| 58 | 475x |
coltree(tt) <- ctree |
| 59 | 475x |
tt |
| 60 |
} |
|
| 61 | ||
| 62 |
.index_col_refs_inner <- function(ctree, cur_idx_fun) {
|
|
| 63 | 2226x |
col_footnotes(ctree) <- .reindex_one_pos( |
| 64 | 2226x |
col_footnotes(ctree), |
| 65 | 2226x |
cur_idx_fun |
| 66 |
) |
|
| 67 | ||
| 68 | 2226x |
if (is(ctree, "LayoutColTree")) {
|
| 69 | 837x |
tree_children(ctree) <- lapply(tree_children(ctree), |
| 70 | 837x |
.index_col_refs_inner, |
| 71 | 837x |
cur_idx_fun = cur_idx_fun |
| 72 |
) |
|
| 73 |
} |
|
| 74 | 2226x |
ctree |
| 75 |
## cfs <- col_footnotes(ctree) |
|
| 76 |
## if(length(unlist(cfs)) > 0) {
|
|
| 77 |
## col_footnotes(ctree) <- .reindex_one_pos(lapply(cfs, |
|
| 78 |
## function(refs) lapply(refs, function(refi) {
|
|
| 79 |
} |
|
| 80 | ||
| 81 |
#' Update footnote indices on a built table |
|
| 82 |
#' |
|
| 83 |
#' Re-indexes footnotes within a built table. |
|
| 84 |
#' |
|
| 85 |
#' @inheritParams gen_args |
|
| 86 |
#' |
|
| 87 |
#' @details |
|
| 88 |
#' After adding or removing referential footnotes manually, or after subsetting a table, the reference indexes |
|
| 89 |
#' (i.e. the number associated with specific footnotes) may be incorrect. This function recalculates these based |
|
| 90 |
#' on the full table. |
|
| 91 |
#' |
|
| 92 |
#' @note In the future this should not generally need to be called manually. |
|
| 93 |
#' |
|
| 94 |
#' @export |
|
| 95 |
update_ref_indexing <- function(tt) {
|
|
| 96 | 475x |
col_fnotes <- c(list(row_fnotes = list()), col_footnotes(tt)) |
| 97 | 475x |
row_fnotes <- row_footnotes(tt) |
| 98 | 475x |
cell_fnotes <- cell_footnotes(tt) |
| 99 | 475x |
all_fns <- rbind(col_fnotes, cbind(row_fnotes, cell_fnotes)) |
| 100 | 475x |
all_fns <- unlist(t(all_fns)) |
| 101 | 475x |
unique_fnotes <- unique(sapply(all_fns, ref_msg)) |
| 102 | ||
| 103 | 475x |
cur_index <- function(ref_fn) {
|
| 104 | 71x |
match(ref_msg(ref_fn), unique_fnotes) |
| 105 |
} |
|
| 106 | ||
| 107 | 475x |
if (ncol(tt) > 0) {
|
| 108 | 475x |
tt <- index_col_refs(tt, cur_index) |
| 109 |
} ## col_info(tt) <- index_col_refs(col_info(tt), cur_index) |
|
| 110 |
## TODO when column refs are a thing we will |
|
| 111 |
## still need to do those here before returning!!! |
|
| 112 | 475x |
if (nrow(tt) == 0) {
|
| 113 | 19x |
return(tt) |
| 114 |
} |
|
| 115 | ||
| 116 | 456x |
rdf <- make_row_df(tt) |
| 117 | ||
| 118 | 456x |
rdf <- rdf[rdf$nreflines > 0, ] |
| 119 | 456x |
if (nrow(rdf) == 0) {
|
| 120 | 424x |
return(tt) |
| 121 |
} |
|
| 122 | ||
| 123 | 32x |
for (i in seq_len(nrow(rdf))) {
|
| 124 | 56x |
path <- unname(rdf$path[[i]]) |
| 125 | 56x |
tt_at_path(tt, path) <- |
| 126 | 56x |
.idx_helper( |
| 127 | 56x |
tt_at_path(tt, path), |
| 128 | 56x |
cur_index |
| 129 |
) |
|
| 130 |
} |
|
| 131 | 32x |
tt |
| 132 |
} |
| 1 |
#' @import formatters |
|
| 2 |
#' @importMethodsFrom formatters toString matrix_form nlines |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
# toString ---- |
|
| 6 | ||
| 7 |
## #' @export |
|
| 8 |
## setGeneric("toString", function(x,...) standardGeneric("toString"))
|
|
| 9 | ||
| 10 |
## ## preserve S3 behavior |
|
| 11 |
## setMethod("toString", "ANY", base::toString)
|
|
| 12 | ||
| 13 |
## #' @export |
|
| 14 |
## setMethod("print", "ANY", base::print)
|
|
| 15 | ||
| 16 |
#' Convert an `rtable` object to a string |
|
| 17 |
#' |
|
| 18 |
#' @inheritParams formatters::toString |
|
| 19 |
#' @inheritParams gen_args |
|
| 20 |
#' @inherit formatters::toString |
|
| 21 |
#' |
|
| 22 |
#' @return A string representation of `x` as it appears when printed. |
|
| 23 |
#' |
|
| 24 |
#' @examplesIf require(dplyr) |
|
| 25 |
#' library(dplyr) |
|
| 26 |
#' |
|
| 27 |
#' iris2 <- iris %>% |
|
| 28 |
#' group_by(Species) %>% |
|
| 29 |
#' mutate(group = as.factor(rep_len(c("a", "b"), length.out = n()))) %>%
|
|
| 30 |
#' ungroup() |
|
| 31 |
#' |
|
| 32 |
#' lyt <- basic_table() %>% |
|
| 33 |
#' split_cols_by("Species") %>%
|
|
| 34 |
#' split_cols_by("group") %>%
|
|
| 35 |
#' analyze(c("Sepal.Length", "Petal.Width"), afun = list_wrap_x(summary), format = "xx.xx")
|
|
| 36 |
#' |
|
| 37 |
#' tbl <- build_table(lyt, iris2) |
|
| 38 |
#' |
|
| 39 |
#' cat(toString(tbl, col_gap = 3)) |
|
| 40 |
#' |
|
| 41 |
#' @rdname tostring |
|
| 42 |
#' @aliases tostring toString,VTableTree-method |
|
| 43 |
#' @exportMethod toString |
|
| 44 |
setMethod("toString", "VTableTree", function(x,
|
|
| 45 |
widths = NULL, |
|
| 46 |
col_gap = 3, |
|
| 47 |
hsep = horizontal_sep(x), |
|
| 48 |
indent_size = 2, |
|
| 49 |
tf_wrap = FALSE, |
|
| 50 |
max_width = NULL, |
|
| 51 |
fontspec = font_spec(), |
|
| 52 |
ttype_ok = FALSE, |
|
| 53 |
round_type = c("iec", "sas")) {
|
|
| 54 | 53x |
toString( |
| 55 | 53x |
matrix_form(x, |
| 56 | 53x |
indent_rownames = TRUE, |
| 57 | 53x |
indent_size = indent_size, |
| 58 | 53x |
fontspec = fontspec, |
| 59 | 53x |
col_gap = col_gap, |
| 60 | 53x |
round_type = round_type |
| 61 |
), |
|
| 62 | 53x |
widths = widths, col_gap = col_gap, |
| 63 | 53x |
hsep = hsep, |
| 64 | 53x |
tf_wrap = tf_wrap, |
| 65 | 53x |
max_width = max_width, |
| 66 | 53x |
fontspec = fontspec, |
| 67 | 53x |
ttype_ok = ttype_ok, |
| 68 | 53x |
round_type = round_type |
| 69 |
) |
|
| 70 |
}) |
|
| 71 | ||
| 72 |
#' Table shells |
|
| 73 |
#' |
|
| 74 |
#' A table shell is a rendering of the table which maintains the structure, but does not display the values, rather |
|
| 75 |
#' displaying the formatting instructions for each cell. |
|
| 76 |
#' |
|
| 77 |
#' @inheritParams formatters::toString |
|
| 78 |
#' @inheritParams gen_args |
|
| 79 |
#' |
|
| 80 |
#' @return |
|
| 81 |
#' * `table_shell` returns `NULL`, as the function is called for the side effect of printing the shell to the console. |
|
| 82 |
#' * `table_shell_str` returns the string representing the table shell. |
|
| 83 |
#' |
|
| 84 |
#' @seealso [value_formats()] for a matrix of formats for each cell in a table. |
|
| 85 |
#' |
|
| 86 |
#' @examplesIf require(dplyr) |
|
| 87 |
#' library(dplyr) |
|
| 88 |
#' |
|
| 89 |
#' iris2 <- iris %>% |
|
| 90 |
#' group_by(Species) %>% |
|
| 91 |
#' mutate(group = as.factor(rep_len(c("a", "b"), length.out = n()))) %>%
|
|
| 92 |
#' ungroup() |
|
| 93 |
#' |
|
| 94 |
#' lyt <- basic_table() %>% |
|
| 95 |
#' split_cols_by("Species") %>%
|
|
| 96 |
#' split_cols_by("group") %>%
|
|
| 97 |
#' analyze(c("Sepal.Length", "Petal.Width"), afun = list_wrap_x(summary), format = "xx.xx")
|
|
| 98 |
#' |
|
| 99 |
#' tbl <- build_table(lyt, iris2) |
|
| 100 |
#' table_shell(tbl) |
|
| 101 |
#' |
|
| 102 |
#' @export |
|
| 103 |
table_shell <- function(tt, widths = NULL, col_gap = 3, hsep = default_hsep(), |
|
| 104 |
tf_wrap = FALSE, max_width = NULL) {
|
|
| 105 | 2x |
cat(table_shell_str( |
| 106 | 2x |
tt = tt, widths = widths, col_gap = col_gap, hsep = hsep, |
| 107 | 2x |
tf_wrap = tf_wrap, max_width = max_width |
| 108 |
)) |
|
| 109 |
} |
|
| 110 | ||
| 111 |
## XXX consider moving to formatters, its really just a function |
|
| 112 |
## of the MatrixPrintForm |
|
| 113 |
#' @rdname table_shell |
|
| 114 |
#' @export |
|
| 115 |
table_shell_str <- function(tt, widths = NULL, col_gap = 3, hsep = default_hsep(), |
|
| 116 |
tf_wrap = FALSE, max_width = NULL) {
|
|
| 117 | 2x |
matform <- matrix_form(tt, indent_rownames = TRUE) |
| 118 | 2x |
format_strs <- vapply( |
| 119 | 2x |
as.vector(matform$formats), |
| 120 | 2x |
function(x) {
|
| 121 | 18x |
if (inherits(x, "function")) {
|
| 122 | 1x |
"<fnc>" |
| 123 | 17x |
} else if (inherits(x, "character")) {
|
| 124 | 17x |
x |
| 125 |
} else {
|
|
| 126 | ! |
stop("Don't know how to make a shell with formats of class: ", class(x))
|
| 127 |
} |
|
| 128 |
}, "" |
|
| 129 |
) |
|
| 130 | ||
| 131 | 2x |
format_strs_mat <- matrix(format_strs, ncol = ncol(matform$strings)) |
| 132 | 2x |
format_strs_mat[, 1] <- matform$strings[, 1] |
| 133 | 2x |
nlh <- mf_nlheader(matform) |
| 134 | 2x |
format_strs_mat[seq_len(nlh), ] <- matform$strings[seq_len(nlh), ] |
| 135 | ||
| 136 | 2x |
matform$strings <- format_strs_mat |
| 137 | 2x |
if (is.null(widths)) {
|
| 138 | 2x |
widths <- propose_column_widths(matform) |
| 139 |
} |
|
| 140 | 2x |
toString(matform, |
| 141 | 2x |
widths = widths, col_gap = col_gap, hsep = hsep, |
| 142 | 2x |
tf_wrap = tf_wrap, max_width = max_width |
| 143 |
) |
|
| 144 |
} |
|
| 145 | ||
| 146 |
#' Transform an `rtable` to a list of matrices which can be used for outputting |
|
| 147 |
#' |
|
| 148 |
#' Although `rtables` are represented as a tree data structure when outputting the table to ASCII or HTML |
|
| 149 |
#' it is useful to map the `rtable` to an in-between state with the formatted cells in a matrix form. |
|
| 150 |
#' |
|
| 151 |
#' @inheritParams gen_args |
|
| 152 |
#' @inheritParams formatters::format_value |
|
| 153 |
#' @param indent_rownames (`flag`)\cr if `TRUE`, the column with the row names in the `strings` matrix of the output |
|
| 154 |
#' has indented row names (strings pre-fixed). |
|
| 155 |
#' @param expand_newlines (`flag`)\cr whether the matrix form generated should expand rows whose values contain |
|
| 156 |
#' newlines into multiple 'physical' rows (as they will appear when rendered into ASCII). Defaults to `TRUE`. |
|
| 157 |
#' @param fontspec (`font_spec`)\cr The font that should be used by default when |
|
| 158 |
#' rendering this `MatrixPrintForm` object, or NULL (the default). |
|
| 159 |
#' @param col_gap (`numeric(1)`)]\cr The number of spaces (in the font specified |
|
| 160 |
#' by `fontspec`) that should be placed between columns when the table |
|
| 161 |
#' is rendered directly to text (e.g., by `toString` or `export_as_txt`). Defaults |
|
| 162 |
#' to `3`. |
|
| 163 |
#' |
|
| 164 |
#' @details |
|
| 165 |
#' The strings in the return object are defined as follows: row labels are those determined by `make_row_df` and cell |
|
| 166 |
#' values are determined using `get_formatted_cells`. (Column labels are calculated using a non-exported internal |
|
| 167 |
#' function. |
|
| 168 |
#' |
|
| 169 |
#' @return A list with the following elements: |
|
| 170 |
#' \describe{
|
|
| 171 |
#' \item{`strings`}{The content, as it should be printed, of the top-left material, column headers, row labels,
|
|
| 172 |
#' and cell values of `tt`.} |
|
| 173 |
#' \item{`spans`}{The column-span information for each print-string in the `strings` matrix.}
|
|
| 174 |
#' \item{`aligns`}{The text alignment for each print-string in the `strings` matrix.}
|
|
| 175 |
#' \item{`display`}{Whether each print-string in the strings matrix should be printed.}
|
|
| 176 |
#' \item{`row_info`}{The `data.frame` generated by `make_row_df`.}
|
|
| 177 |
#' } |
|
| 178 |
#' |
|
| 179 |
#' With an additional `nrow_header` attribute indicating the number of pseudo "rows" that the column structure defines. |
|
| 180 |
#' |
|
| 181 |
#' @examplesIf require(dplyr) |
|
| 182 |
#' library(dplyr) |
|
| 183 |
#' |
|
| 184 |
#' iris2 <- iris %>% |
|
| 185 |
#' group_by(Species) %>% |
|
| 186 |
#' mutate(group = as.factor(rep_len(c("a", "b"), length.out = n()))) %>%
|
|
| 187 |
#' ungroup() |
|
| 188 |
#' |
|
| 189 |
#' lyt <- basic_table() %>% |
|
| 190 |
#' split_cols_by("Species") %>%
|
|
| 191 |
#' split_cols_by("group") %>%
|
|
| 192 |
#' analyze(c("Sepal.Length", "Petal.Width"),
|
|
| 193 |
#' afun = list_wrap_x(summary), format = "xx.xx" |
|
| 194 |
#' ) |
|
| 195 |
#' |
|
| 196 |
#' lyt |
|
| 197 |
#' |
|
| 198 |
#' tbl <- build_table(lyt, iris2) |
|
| 199 |
#' |
|
| 200 |
#' matrix_form(tbl) |
|
| 201 |
#' |
|
| 202 |
#' @export |
|
| 203 |
setMethod( |
|
| 204 |
"matrix_form", "VTableTree", |
|
| 205 |
function(obj, |
|
| 206 |
indent_rownames = FALSE, |
|
| 207 |
expand_newlines = TRUE, |
|
| 208 |
indent_size = 2, |
|
| 209 |
fontspec = NULL, |
|
| 210 |
col_gap = 3L, |
|
| 211 |
round_type = c("iec", "sas")) {
|
|
| 212 | 340x |
stopifnot(is(obj, "VTableTree")) |
| 213 | 340x |
check_ccount_vis_ok(obj) |
| 214 | 339x |
header_content <- .tbl_header_mat(obj) # first col are for row.names |
| 215 | ||
| 216 | 337x |
sr <- make_row_df(obj, fontspec = fontspec) |
| 217 | ||
| 218 | 337x |
body_content_strings <- if (NROW(sr) == 0) {
|
| 219 | 5x |
character() |
| 220 |
} else {
|
|
| 221 | 332x |
cbind(as.character(sr$label), get_formatted_cells(obj, round_type = round_type)) |
| 222 |
} |
|
| 223 | ||
| 224 | 337x |
formats_strings <- if (NROW(sr) == 0) {
|
| 225 | 5x |
character() |
| 226 |
} else {
|
|
| 227 | 332x |
cbind("", get_formatted_cells(obj, shell = TRUE))
|
| 228 |
} |
|
| 229 | ||
| 230 | 337x |
tsptmp <- lapply(collect_leaves(obj, TRUE, TRUE), function(rr) {
|
| 231 | 7185x |
sp <- row_cspans(rr) |
| 232 | 7185x |
rep(sp, times = sp) |
| 233 |
}) |
|
| 234 | ||
| 235 |
## the 1 is for row labels |
|
| 236 | 337x |
body_spans <- if (nrow(obj) > 0) {
|
| 237 | 332x |
cbind(1L, do.call(rbind, tsptmp)) |
| 238 |
} else {
|
|
| 239 | 5x |
matrix(1, nrow = 0, ncol = ncol(obj) + 1) |
| 240 |
} |
|
| 241 | ||
| 242 | 337x |
body_aligns <- if (NROW(sr) == 0) {
|
| 243 | 5x |
character() |
| 244 |
} else {
|
|
| 245 | 332x |
cbind("left", get_cell_aligns(obj))
|
| 246 |
} |
|
| 247 | ||
| 248 | 337x |
body <- rbind(header_content$body, body_content_strings) |
| 249 | ||
| 250 | 337x |
hdr_fmt_blank <- matrix("",
|
| 251 | 337x |
nrow = nrow(header_content$body), |
| 252 | 337x |
ncol = ncol(header_content$body) |
| 253 |
) |
|
| 254 | 337x |
if (disp_ccounts(obj)) {
|
| 255 | 40x |
hdr_fmt_blank[nrow(hdr_fmt_blank), ] <- c("", rep(colcount_format(obj), ncol(obj)))
|
| 256 |
} |
|
| 257 | ||
| 258 | 337x |
formats <- rbind(hdr_fmt_blank, formats_strings) |
| 259 | ||
| 260 | 337x |
spans <- rbind(header_content$span, body_spans) |
| 261 | 337x |
row.names(spans) <- NULL |
| 262 | ||
| 263 | 337x |
aligns <- rbind( |
| 264 | 337x |
matrix(rep("center", length(header_content$body)),
|
| 265 | 337x |
nrow = nrow(header_content$body) |
| 266 |
), |
|
| 267 | 337x |
body_aligns |
| 268 |
) |
|
| 269 | ||
| 270 | 337x |
aligns[, 1] <- "left" # row names and topleft (still needed for topleft) |
| 271 | ||
| 272 | 337x |
nr_header <- nrow(header_content$body) |
| 273 | 337x |
if (indent_rownames) {
|
| 274 | 248x |
body[, 1] <- indent_string(body[, 1], c(rep(0, nr_header), sr$indent), |
| 275 | 248x |
incr = indent_size |
| 276 |
) |
|
| 277 |
# why also formats? |
|
| 278 | 248x |
formats[, 1] <- indent_string(formats[, 1], c(rep(0, nr_header), sr$indent), |
| 279 | 248x |
incr = indent_size |
| 280 |
) |
|
| 281 | 89x |
} else if (NROW(sr) > 0) {
|
| 282 | 85x |
sr$indent <- rep(0, NROW(sr)) |
| 283 |
} |
|
| 284 | ||
| 285 | 337x |
col_ref_strs <- matrix(vapply(header_content$footnotes, function(x) {
|
| 286 | 2969x |
if (length(x) == 0) {
|
| 287 |
"" |
|
| 288 |
} else {
|
|
| 289 | 5x |
paste(vapply(x, format_fnote_ref, ""), collapse = " ") |
| 290 |
} |
|
| 291 | 337x |
}, ""), ncol = ncol(body)) |
| 292 | 337x |
body_ref_strs <- get_ref_matrix(obj) |
| 293 | ||
| 294 | 337x |
body <- matrix( |
| 295 | 337x |
paste0( |
| 296 | 337x |
body, |
| 297 | 337x |
rbind( |
| 298 | 337x |
col_ref_strs, |
| 299 | 337x |
body_ref_strs |
| 300 |
) |
|
| 301 |
), |
|
| 302 | 337x |
nrow = nrow(body), |
| 303 | 337x |
ncol = ncol(body) |
| 304 |
) |
|
| 305 | ||
| 306 | 337x |
ref_fnotes <- get_formatted_fnotes(obj) # pagination will not count extra lines coming from here |
| 307 | 337x |
pag_titles <- page_titles(obj) |
| 308 | ||
| 309 | 337x |
MatrixPrintForm( |
| 310 | 337x |
strings = body, |
| 311 | 337x |
spans = spans, |
| 312 | 337x |
aligns = aligns, |
| 313 | 337x |
formats = formats, |
| 314 |
## display = display, purely a function of spans, handled in constructor now |
|
| 315 | 337x |
row_info = sr, |
| 316 | 337x |
colpaths = make_col_df(obj)[["path"]], |
| 317 |
## line_grouping handled internally now line_grouping = 1:nrow(body), |
|
| 318 | 337x |
ref_fnotes = ref_fnotes, |
| 319 | 337x |
nlines_header = nr_header, ## this is fixed internally |
| 320 | 337x |
nrow_header = nr_header, |
| 321 | 337x |
expand_newlines = expand_newlines, |
| 322 | 337x |
has_rowlabs = TRUE, |
| 323 | 337x |
has_topleft = TRUE, |
| 324 | 337x |
main_title = main_title(obj), |
| 325 | 337x |
subtitles = subtitles(obj), |
| 326 | 337x |
page_titles = pag_titles, |
| 327 | 337x |
main_footer = main_footer(obj), |
| 328 | 337x |
prov_footer = prov_footer(obj), |
| 329 | 337x |
table_inset = table_inset(obj), |
| 330 | 337x |
header_section_div = header_section_div(obj), |
| 331 | 337x |
horizontal_sep = horizontal_sep(obj), |
| 332 | 337x |
indent_size = indent_size, |
| 333 | 337x |
fontspec = fontspec, |
| 334 | 337x |
col_gap = col_gap |
| 335 |
) |
|
| 336 |
} |
|
| 337 |
) |
|
| 338 | ||
| 339 | ||
| 340 |
check_ccount_vis_ok <- function(tt) {
|
|
| 341 | 340x |
ctree <- coltree(tt) |
| 342 | 340x |
tlkids <- tree_children(ctree) |
| 343 | 340x |
lapply(tlkids, ccvis_check_subtree) |
| 344 | 339x |
invisible(NULL) |
| 345 |
} |
|
| 346 | ||
| 347 |
ccvis_check_subtree <- function(ctree) {
|
|
| 348 | 1615x |
kids <- tree_children(ctree) |
| 349 | 1615x |
if (is.null(kids)) {
|
| 350 | ! |
return(invisible(NULL)) |
| 351 |
} |
|
| 352 | 1615x |
vals <- vapply(kids, disp_ccounts, TRUE) |
| 353 | 1615x |
if (length(unique(vals)) > 1) {
|
| 354 | 1x |
unmatch <- which(!duplicated(vals))[1:2] |
| 355 | 1x |
stop( |
| 356 | 1x |
"Detected different colcount visibility among sibling facets (those ", |
| 357 | 1x |
"arising from the same split_cols_by* layout instruction). This is ", |
| 358 | 1x |
"not supported.\n", |
| 359 | 1x |
"Set count values to NA if you want a blank space to appear as the ", |
| 360 | 1x |
"displayed count for particular facets.\n", |
| 361 | 1x |
"First disagreement occured at paths:\n", |
| 362 | 1x |
.path_to_disp(pos_to_path(tree_pos(kids[[unmatch[1]]]))), "\n", |
| 363 | 1x |
.path_to_disp(pos_to_path(tree_pos(kids[[unmatch[2]]]))) |
| 364 |
) |
|
| 365 |
} |
|
| 366 | 1614x |
lapply(kids, ccvis_check_subtree) |
| 367 | 1614x |
invisible(NULL) |
| 368 |
} |
|
| 369 | ||
| 370 |
.resolve_fn_symbol <- function(fn) {
|
|
| 371 | 448x |
if (!is(fn, "RefFootnote")) {
|
| 372 | ! |
return(NULL) |
| 373 |
} |
|
| 374 | 448x |
ret <- ref_symbol(fn) |
| 375 | 448x |
if (is.na(ret)) {
|
| 376 | 448x |
ret <- as.character(ref_index(fn)) |
| 377 |
} |
|
| 378 | 448x |
ret |
| 379 |
} |
|
| 380 | ||
| 381 |
format_fnote_ref <- function(fn) {
|
|
| 382 | 42699x |
if (length(fn) == 0 || (is.list(fn) && all(vapply(fn, function(x) length(x) == 0, TRUE)))) {
|
| 383 | 42635x |
return("")
|
| 384 | 64x |
} else if (is.list(fn) && all(vapply(fn, is.list, TRUE))) {
|
| 385 | ! |
return(vapply(fn, format_fnote_ref, "")) |
| 386 |
} |
|
| 387 | 64x |
if (is.list(fn)) {
|
| 388 | 59x |
inds <- unlist(lapply(unlist(fn), .resolve_fn_symbol)) |
| 389 |
} else {
|
|
| 390 | 5x |
inds <- .resolve_fn_symbol(fn) |
| 391 |
} |
|
| 392 | 64x |
if (length(inds) > 0) {
|
| 393 | 64x |
paste0(" {", paste(unique(inds), collapse = ", "), "}")
|
| 394 |
} else {
|
|
| 395 |
"" |
|
| 396 |
} |
|
| 397 |
} |
|
| 398 | ||
| 399 |
format_fnote_note <- function(fn) {
|
|
| 400 | 373x |
if (length(fn) == 0 || (is.list(fn) && all(vapply(fn, function(x) length(x) == 0, TRUE)))) {
|
| 401 | ! |
return(character()) |
| 402 |
} |
|
| 403 | 373x |
if (is.list(fn)) {
|
| 404 | ! |
return(unlist(lapply(unlist(fn), format_fnote_note))) |
| 405 |
} |
|
| 406 | ||
| 407 | 373x |
if (is(fn, "RefFootnote")) {
|
| 408 | 373x |
paste0("{", .resolve_fn_symbol(fn), "} - ", ref_msg(fn))
|
| 409 |
} else {
|
|
| 410 | ! |
NULL |
| 411 |
} |
|
| 412 |
} |
|
| 413 | ||
| 414 |
.fn_ind_extractor <- function(strs) {
|
|
| 415 | ! |
res <- suppressWarnings(as.numeric(gsub("\\{([[:digit:]]+)\\}.*", "\\1", strs)))
|
| 416 | ! |
res[res == "NA"] <- NA_character_ |
| 417 |
## these mixing is allowed now with symbols |
|
| 418 |
## if(!(sum(is.na(res)) %in% c(0L, length(res)))) |
|
| 419 |
## stop("Got NAs mixed with non-NAS for extracted footnote indices. This should not happen")
|
|
| 420 | ! |
res |
| 421 |
} |
|
| 422 | ||
| 423 |
get_ref_matrix <- function(tt) {
|
|
| 424 | 337x |
if (ncol(tt) == 0 || nrow(tt) == 0) {
|
| 425 | 5x |
return(matrix("", nrow = nrow(tt), ncol = ncol(tt) + 1L))
|
| 426 |
} |
|
| 427 | 332x |
rows <- collect_leaves(tt, incl.cont = TRUE, add.labrows = TRUE) |
| 428 | 332x |
lst <- unlist(lapply(rows, cell_footnotes), recursive = FALSE) |
| 429 | 332x |
cstrs <- unlist(lapply(lst, format_fnote_ref)) |
| 430 | 332x |
bodymat <- matrix(cstrs, |
| 431 | 332x |
byrow = TRUE, |
| 432 | 332x |
nrow = nrow(tt), |
| 433 | 332x |
ncol = ncol(tt) |
| 434 |
) |
|
| 435 | 332x |
cbind(vapply(rows, function(rw) format_fnote_ref(row_footnotes(rw)), ""), bodymat) |
| 436 |
} |
|
| 437 | ||
| 438 |
get_formatted_fnotes <- function(tt) {
|
|
| 439 | 337x |
colresfs <- unlist(make_col_df(tt, visible_only = FALSE)$col_fnotes) |
| 440 | 337x |
rows <- collect_leaves(tt, incl.cont = TRUE, add.labrows = TRUE) |
| 441 | 337x |
lst <- c( |
| 442 | 337x |
colresfs, |
| 443 | 337x |
unlist( |
| 444 | 337x |
lapply(rows, function(r) unlist(c(row_footnotes(r), cell_footnotes(r)), recursive = FALSE)), |
| 445 | 337x |
recursive = FALSE |
| 446 |
) |
|
| 447 |
) |
|
| 448 | ||
| 449 | 337x |
inds <- vapply(lst, ref_index, 1L) |
| 450 | 337x |
ord <- order(inds) |
| 451 | 337x |
lst <- lst[ord] |
| 452 | 337x |
syms <- vapply(lst, ref_symbol, "") |
| 453 | 337x |
keep <- is.na(syms) | !duplicated(syms) |
| 454 | 337x |
lst <- lst[keep] |
| 455 | 337x |
unique(vapply(lst, format_fnote_note, "")) |
| 456 | ||
| 457 |
## , recursive = FALSE) |
|
| 458 |
## rlst <- unlist(lapply(rows, row_footnotes)) |
|
| 459 |
## lst <- |
|
| 460 |
## syms <- vapply(lst, ref_symbol, "") |
|
| 461 |
## keep <- is.na(syms) | !duplicated(syms) |
|
| 462 |
## lst <- lst[keep] |
|
| 463 |
## inds <- vapply(lst, ref_index, 1L) |
|
| 464 |
## cellstrs <- unlist(lapply(lst, format_fnote_note)) |
|
| 465 |
## rstrs <- unlist(lapply(rows, function(rw) format_fnote_note(row_footnotes(rw)))) |
|
| 466 |
## allstrs <- c(colstrs, rstrs, cellstrs) |
|
| 467 |
## inds <- .fn_ind_extractor(allstrs) |
|
| 468 |
## allstrs[order(inds)] |
|
| 469 |
} |
|
| 470 | ||
| 471 |
.do_tbl_h_piece2 <- function(tt) {
|
|
| 472 | 345x |
coldf <- make_col_df(tt, visible_only = FALSE) |
| 473 | 345x |
remain <- seq_len(nrow(coldf)) |
| 474 | 345x |
chunks <- list() |
| 475 | 345x |
cur <- 1 |
| 476 | 345x |
na_str <- colcount_na_str(tt) |
| 477 | ||
| 478 |
## XXX this would be better as the facet-associated |
|
| 479 |
## format but I don't know that we need to |
|
| 480 |
## support that level of differentiation anyway... |
|
| 481 | 345x |
cc_format <- colcount_format(tt) |
| 482 |
## each iteration of this loop identifies |
|
| 483 |
## all rows corresponding to one top-level column |
|
| 484 |
## label and its children, then processes those |
|
| 485 |
## with .do_header_chunk |
|
| 486 | 345x |
while (length(remain) > 0) {
|
| 487 | 883x |
rw <- remain[1] |
| 488 | 883x |
inds <- coldf$leaf_indices[[rw]] |
| 489 | 883x |
endblock <- which(coldf$abs_pos == max(inds)) |
| 490 | ||
| 491 | 883x |
stopifnot(endblock >= rw) |
| 492 | 883x |
chunk_res <- .do_header_chunk(coldf[rw:endblock, ], cc_format, na_str = na_str) |
| 493 | 881x |
chunk_res <- unlist(chunk_res, recursive = FALSE) |
| 494 | 881x |
chunks[[cur]] <- chunk_res |
| 495 | 881x |
remain <- remain[remain > endblock] |
| 496 | 881x |
cur <- cur + 1 |
| 497 |
} |
|
| 498 | 343x |
chunks <- .pad_tops(chunks) |
| 499 | 343x |
lapply( |
| 500 | 343x |
seq_len(length(chunks[[1]])), |
| 501 | 343x |
function(i) {
|
| 502 | 518x |
DataRow(unlist(lapply(chunks, `[[`, i), recursive = FALSE)) |
| 503 |
} |
|
| 504 |
) |
|
| 505 |
} |
|
| 506 | ||
| 507 |
.pad_end <- function(lst, padto, ncols) {
|
|
| 508 | 1347x |
curcov <- sum(vapply(lst, cell_cspan, 0L)) |
| 509 | 1347x |
if (curcov == padto) {
|
| 510 | 1347x |
return(lst) |
| 511 |
} |
|
| 512 | ||
| 513 | ! |
c(lst, list(rcell("", colspan = padto - curcov)))
|
| 514 |
} |
|
| 515 | ||
| 516 |
.pad_tops <- function(chunks) {
|
|
| 517 | 343x |
lens <- vapply(chunks, length, 1L) |
| 518 | 343x |
padto <- max(lens) |
| 519 | 343x |
needpad <- lens != padto |
| 520 | 343x |
if (all(!needpad)) {
|
| 521 | 337x |
return(chunks) |
| 522 |
} |
|
| 523 | ||
| 524 | 6x |
for (i in seq_along(lens)) {
|
| 525 | 25x |
if (lens[i] < padto) {
|
| 526 | 10x |
chk <- chunks[[i]] |
| 527 | 10x |
span <- sum(vapply(chk[[length(chk)]], cell_cspan, 1L)) |
| 528 | 10x |
chunks[[i]] <- c( |
| 529 | 10x |
replicate(list(list(rcell("", colspan = span))),
|
| 530 | 10x |
n = padto - lens[i] |
| 531 |
), |
|
| 532 | 10x |
chk |
| 533 |
) |
|
| 534 |
} |
|
| 535 |
} |
|
| 536 | 6x |
chunks |
| 537 |
} |
|
| 538 | ||
| 539 |
.do_header_chunk <- function(coldf, cc_format, na_str) {
|
|
| 540 |
## hard assumption that coldf is a section |
|
| 541 |
## of a column dataframe summary that was |
|
| 542 |
## created with visible_only=FALSE |
|
| 543 | 883x |
nleafcols <- length(coldf$leaf_indices[[1]]) |
| 544 | ||
| 545 | 883x |
spldfs <- split(coldf, lengths(coldf$path)) |
| 546 | 883x |
toret <- lapply( |
| 547 | 883x |
seq_along(spldfs), |
| 548 | 883x |
function(i) {
|
| 549 | 1202x |
rws <- spldfs[[i]] |
| 550 | 1202x |
thisbit_vals <- lapply( |
| 551 | 1202x |
seq_len(nrow(rws)), |
| 552 | 1202x |
function(ri) {
|
| 553 | 1627x |
cellii <- rcell(rws[ri, "label", drop = TRUE], |
| 554 | 1627x |
colspan = rws$total_span[ri], |
| 555 | 1627x |
footnotes = rws[ri, "col_fnotes", drop = TRUE][[1]] |
| 556 |
) |
|
| 557 | 1627x |
cellii |
| 558 |
} |
|
| 559 |
) |
|
| 560 | 1202x |
ret <- list(.pad_end(thisbit_vals, padto = nleafcols)) |
| 561 | 1202x |
anycounts <- any(rws$ccount_visible) |
| 562 | 1202x |
if (anycounts) {
|
| 563 | 147x |
thisbit_ns <- lapply( |
| 564 | 147x |
seq_len(nrow(rws)), |
| 565 | 147x |
function(ri) {
|
| 566 | 299x |
vis_ri <- rws$ccount_visible[ri] |
| 567 | 299x |
val <- if (vis_ri) rws$col_count[ri] else NULL |
| 568 | 299x |
fmt <- rws$ccount_format[ri] |
| 569 | 299x |
if (is.character(fmt)) {
|
| 570 | 299x |
cfmt_dim <- names(which(sapply(formatters::list_valid_format_labels(), function(x) any(x == fmt)))) |
| 571 | 299x |
if (cfmt_dim == "2d") {
|
| 572 | 7x |
if (grepl("%", fmt)) {
|
| 573 | 6x |
val <- c(val, 1) ## XXX This is the old behavior but it doesn't take into account parent counts... |
| 574 |
} else {
|
|
| 575 | 1x |
stop( |
| 576 | 1x |
"This 2d format is not supported for column counts. ", |
| 577 | 1x |
"Please choose a 1d format or a 2d format that includes a % value." |
| 578 |
) |
|
| 579 |
} |
|
| 580 | 292x |
} else if (cfmt_dim == "3d") {
|
| 581 | 1x |
stop("3d formats are not supported for column counts.")
|
| 582 |
} |
|
| 583 |
} |
|
| 584 | 297x |
cellii <- rcell( |
| 585 | 297x |
val, |
| 586 | 297x |
colspan = rws$total_span[ri], |
| 587 | 297x |
format = fmt, # cc_format, |
| 588 | 297x |
format_na_str = na_str |
| 589 |
) |
|
| 590 | 297x |
cellii |
| 591 |
} |
|
| 592 |
) |
|
| 593 | 145x |
ret <- c(ret, list(.pad_end(thisbit_ns, padto = nleafcols))) |
| 594 |
} |
|
| 595 | 1200x |
ret |
| 596 |
} |
|
| 597 |
) |
|
| 598 | 881x |
toret |
| 599 |
} |
|
| 600 | ||
| 601 |
.tbl_header_mat <- function(tt) {
|
|
| 602 | 339x |
rows <- .do_tbl_h_piece2(tt) ## (clyt) |
| 603 | 337x |
cinfo <- col_info(tt) |
| 604 | ||
| 605 | 337x |
nc <- ncol(tt) |
| 606 | 337x |
body <- matrix(rapply(rows, function(x) {
|
| 607 | 508x |
cs <- row_cspans(x) |
| 608 | 508x |
strs <- get_formatted_cells(x) |
| 609 | 508x |
strs |
| 610 | 337x |
}), ncol = nc, byrow = TRUE) |
| 611 | ||
| 612 | 337x |
span <- matrix(rapply(rows, function(x) {
|
| 613 | 508x |
cs <- row_cspans(x) |
| 614 | ! |
if (is.null(cs)) cs <- rep(1, ncol(x)) |
| 615 | 508x |
rep(cs, cs) |
| 616 | 337x |
}), ncol = nc, byrow = TRUE) |
| 617 | ||
| 618 | 337x |
fnote <- do.call( |
| 619 | 337x |
rbind, |
| 620 | 337x |
lapply(rows, function(x) {
|
| 621 | 508x |
cell_footnotes(x) |
| 622 |
}) |
|
| 623 |
) |
|
| 624 | ||
| 625 | 337x |
tl <- top_left(cinfo) |
| 626 | 337x |
lentl <- length(tl) |
| 627 | 337x |
nli <- nrow(body) |
| 628 | 337x |
if (lentl == 0) {
|
| 629 | 301x |
tl <- rep("", nli)
|
| 630 | 36x |
} else if (lentl > nli) {
|
| 631 | 20x |
tl_tmp <- paste0(tl, collapse = "\n") |
| 632 | 20x |
tl <- rep("", nli)
|
| 633 | 20x |
tl[length(tl)] <- tl_tmp |
| 634 | 16x |
} else if (lentl < nli) {
|
| 635 |
# We want topleft alignment that goes to the bottom! |
|
| 636 | 7x |
tl <- c(rep("", nli - lentl), tl)
|
| 637 |
} |
|
| 638 | 337x |
list( |
| 639 | 337x |
body = cbind(tl, body, deparse.level = 0), span = cbind(1, span), |
| 640 | 337x |
footnotes = cbind(list(list()), fnote) |
| 641 |
) |
|
| 642 |
} |
|
| 643 | ||
| 644 |
# get formatted cells ---- |
|
| 645 | ||
| 646 |
#' Get formatted cells |
|
| 647 |
#' |
|
| 648 |
#' @inheritParams gen_args |
|
| 649 |
#' @inheritParams formatters::format_value |
|
| 650 |
#' @param shell (`flag`)\cr whether the formats themselves should be returned instead of the values with formats |
|
| 651 |
#' applied. Defaults to `FALSE`. |
|
| 652 |
#' |
|
| 653 |
#' @return The formatted print-strings for all (body) cells in `obj`. |
|
| 654 |
#' |
|
| 655 |
#' @examplesIf require(dplyr) |
|
| 656 |
#' library(dplyr) |
|
| 657 |
#' |
|
| 658 |
#' iris2 <- iris %>% |
|
| 659 |
#' group_by(Species) %>% |
|
| 660 |
#' mutate(group = as.factor(rep_len(c("a", "b"), length.out = n()))) %>%
|
|
| 661 |
#' ungroup() |
|
| 662 |
#' |
|
| 663 |
#' tbl <- basic_table() %>% |
|
| 664 |
#' split_cols_by("Species") %>%
|
|
| 665 |
#' split_cols_by("group") %>%
|
|
| 666 |
#' analyze(c("Sepal.Length", "Petal.Width"), afun = list_wrap_x(summary), format = "xx.xx") %>%
|
|
| 667 |
#' build_table(iris2) |
|
| 668 |
#' |
|
| 669 |
#' get_formatted_cells(tbl) |
|
| 670 |
#' |
|
| 671 |
#' @export |
|
| 672 |
#' @rdname gfc |
|
| 673 |
setGeneric( |
|
| 674 |
"get_formatted_cells", |
|
| 675 | 42143x |
function(obj, shell = FALSE, round_type = c("iec", "sas")) standardGeneric("get_formatted_cells")
|
| 676 |
) |
|
| 677 | ||
| 678 |
#' @rdname gfc |
|
| 679 |
setMethod( |
|
| 680 |
"get_formatted_cells", "TableTree", |
|
| 681 |
function(obj, shell = FALSE, round_type = c("iec", "sas")) {
|
|
| 682 | 3026x |
lr <- get_formatted_cells(tt_labelrow(obj), shell = shell, round_type = round_type) |
| 683 | ||
| 684 | 3026x |
ct <- get_formatted_cells(content_table(obj), shell = shell, round_type = round_type) |
| 685 | ||
| 686 | 3026x |
els <- lapply(tree_children(obj), get_formatted_cells, shell = shell, round_type = round_type) |
| 687 | ||
| 688 |
## TODO fix ncol problem for rrow() |
|
| 689 | 3026x |
if (ncol(ct) == 0 && ncol(lr) != ncol(ct)) {
|
| 690 | 825x |
ct <- lr[NULL, ] |
| 691 |
} |
|
| 692 | ||
| 693 | 3026x |
do.call(rbind, c(list(lr), list(ct), els)) |
| 694 |
} |
|
| 695 |
) |
|
| 696 | ||
| 697 |
#' @rdname gfc |
|
| 698 |
setMethod( |
|
| 699 |
"get_formatted_cells", "ElementaryTable", |
|
| 700 |
function(obj, shell = FALSE, round_type = c("iec", "sas")) {
|
|
| 701 | 6015x |
lr <- get_formatted_cells(tt_labelrow(obj), shell = shell, round_type = round_type) |
| 702 | 6015x |
els <- lapply(tree_children(obj), get_formatted_cells, shell = shell, round_type = round_type) |
| 703 | 6015x |
do.call(rbind, c(list(lr), els)) |
| 704 |
} |
|
| 705 |
) |
|
| 706 | ||
| 707 |
#' @rdname gfc |
|
| 708 |
setMethod( |
|
| 709 |
"get_formatted_cells", "TableRow", |
|
| 710 |
function(obj, shell = FALSE, round_type = c("iec", "sas")) {
|
|
| 711 |
# Parent row format and na_str |
|
| 712 | 24033x |
pr_row_format <- if (is.null(obj_format(obj))) "xx" else obj_format(obj) |
| 713 | 24033x |
pr_row_na_str <- obj_na_str(obj) %||% "NA" |
| 714 | ||
| 715 | 24033x |
matrix( |
| 716 | 24033x |
unlist(Map(function(val, spn, shelli) {
|
| 717 | 110495x |
stopifnot(is(spn, "integer")) |
| 718 | ||
| 719 | 110495x |
out <- format_rcell(val, |
| 720 | 110495x |
pr_row_format = pr_row_format, |
| 721 | 110495x |
pr_row_na_str = pr_row_na_str, |
| 722 | 110495x |
shell = shelli, |
| 723 | 110495x |
round_type = round_type |
| 724 |
) |
|
| 725 | 110495x |
if (!is.function(out) && is.character(out)) {
|
| 726 | 110483x |
out <- paste(out, collapse = ", ") |
| 727 |
} |
|
| 728 | ||
| 729 | 110495x |
rep(list(out), spn) |
| 730 | 24033x |
}, val = row_cells(obj), spn = row_cspans(obj), shelli = shell)), |
| 731 | 24033x |
ncol = ncol(obj) |
| 732 |
) |
|
| 733 |
} |
|
| 734 |
) |
|
| 735 | ||
| 736 |
#' @rdname gfc |
|
| 737 |
setMethod( |
|
| 738 |
"get_formatted_cells", "LabelRow", |
|
| 739 |
function(obj, shell = FALSE, round_type = c("iec", "sas")) {
|
|
| 740 | 9069x |
nc <- ncol(obj) # TODO note rrow() or rrow("label") has the wrong ncol
|
| 741 | 9069x |
vstr <- if (shell) "-" else "" |
| 742 | 9069x |
if (labelrow_visible(obj)) {
|
| 743 | 3298x |
matrix(rep(vstr, nc), ncol = nc) |
| 744 |
} else {
|
|
| 745 | 5771x |
matrix(character(0), ncol = nc) |
| 746 |
} |
|
| 747 |
} |
|
| 748 |
) |
|
| 749 | ||
| 750 |
#' @rdname gfc |
|
| 751 | 14578x |
setGeneric("get_cell_aligns", function(obj) standardGeneric("get_cell_aligns"))
|
| 752 | ||
| 753 |
#' @rdname gfc |
|
| 754 |
setMethod( |
|
| 755 |
"get_cell_aligns", "TableTree", |
|
| 756 |
function(obj) {
|
|
| 757 | 1511x |
lr <- get_cell_aligns(tt_labelrow(obj)) |
| 758 | ||
| 759 | 1511x |
ct <- get_cell_aligns(content_table(obj)) |
| 760 | ||
| 761 | 1511x |
els <- lapply(tree_children(obj), get_cell_aligns) |
| 762 | ||
| 763 |
## TODO fix ncol problem for rrow() |
|
| 764 | 1511x |
if (ncol(ct) == 0 && ncol(lr) != ncol(ct)) {
|
| 765 | 412x |
ct <- lr[NULL, ] |
| 766 |
} |
|
| 767 | ||
| 768 | 1511x |
do.call(rbind, c(list(lr), list(ct), els)) |
| 769 |
} |
|
| 770 |
) |
|
| 771 | ||
| 772 |
#' @rdname gfc |
|
| 773 |
setMethod( |
|
| 774 |
"get_cell_aligns", "ElementaryTable", |
|
| 775 |
function(obj) {
|
|
| 776 | 3003x |
lr <- get_cell_aligns(tt_labelrow(obj)) |
| 777 | 3003x |
els <- lapply(tree_children(obj), get_cell_aligns) |
| 778 | 3003x |
do.call(rbind, c(list(lr), els)) |
| 779 |
} |
|
| 780 |
) |
|
| 781 | ||
| 782 |
#' @rdname gfc |
|
| 783 |
setMethod( |
|
| 784 |
"get_cell_aligns", "TableRow", |
|
| 785 |
function(obj) {
|
|
| 786 | 5536x |
als <- vapply(row_cells(obj), cell_align, "") |
| 787 | 5536x |
spns <- row_cspans(obj) |
| 788 | ||
| 789 | 5536x |
matrix(rep(als, times = spns), |
| 790 | 5536x |
ncol = ncol(obj) |
| 791 |
) |
|
| 792 |
} |
|
| 793 |
) |
|
| 794 | ||
| 795 |
#' @rdname gfc |
|
| 796 |
setMethod( |
|
| 797 |
"get_cell_aligns", "LabelRow", |
|
| 798 |
function(obj) {
|
|
| 799 | 4528x |
nc <- ncol(obj) # TODO note rrow() or rrow("label") has the wrong ncol
|
| 800 | 4528x |
if (labelrow_visible(obj)) {
|
| 801 | 1649x |
matrix(rep("center", nc), ncol = nc)
|
| 802 |
} else {
|
|
| 803 | 2879x |
matrix(character(0), ncol = nc) |
| 804 |
} |
|
| 805 |
} |
|
| 806 |
) |
|
| 807 | ||
| 808 |
# utility functions ---- |
|
| 809 | ||
| 810 |
#' From a sorted sequence of numbers, remove numbers where diff == 1 |
|
| 811 |
#' |
|
| 812 |
#' @examples |
|
| 813 |
#' remove_consecutive_numbers(x = c(2, 4, 9)) |
|
| 814 |
#' remove_consecutive_numbers(x = c(2, 4, 5, 9)) |
|
| 815 |
#' remove_consecutive_numbers(x = c(2, 4, 5, 6, 9)) |
|
| 816 |
#' remove_consecutive_numbers(x = 4:9) |
|
| 817 |
#' |
|
| 818 |
#' @noRd |
|
| 819 |
remove_consecutive_numbers <- function(x) {
|
|
| 820 |
# actually should be integer |
|
| 821 | ! |
stopifnot(is.wholenumber(x), is.numeric(x), !is.unsorted(x)) |
| 822 | ||
| 823 | ! |
if (length(x) == 0) {
|
| 824 | ! |
return(integer(0)) |
| 825 |
} |
|
| 826 | ! |
if (!is.integer(x)) x <- as.integer(x) |
| 827 | ||
| 828 | ! |
x[c(TRUE, diff(x) != 1)] |
| 829 |
} |
|
| 830 | ||
| 831 |
#' Insert an empty string |
|
| 832 |
#' |
|
| 833 |
#' @examples |
|
| 834 |
#' empty_string_after(letters[1:5], 2) |
|
| 835 |
#' empty_string_after(letters[1:5], c(2, 4)) |
|
| 836 |
#' |
|
| 837 |
#' @noRd |
|
| 838 |
empty_string_after <- function(x, indices) {
|
|
| 839 | ! |
if (length(indices) > 0) {
|
| 840 | ! |
offset <- 0 |
| 841 | ! |
for (i in sort(indices)) {
|
| 842 | ! |
x <- append(x, "", i + offset) |
| 843 | ! |
offset <- offset + 1 |
| 844 |
} |
|
| 845 |
} |
|
| 846 | ! |
x |
| 847 |
} |
|
| 848 | ||
| 849 |
#' Indent strings |
|
| 850 |
#' |
|
| 851 |
#' Used in rtables to indent row names for the ASCII output. |
|
| 852 |
#' |
|
| 853 |
#' @param x (`character`)\cr a character vector. |
|
| 854 |
#' @param indent (`numeric`)\cr a vector of non-negative integers of length `length(x)`. |
|
| 855 |
#' @param incr (`integer(1)`)\cr a non-negative number of spaces per indent level. |
|
| 856 |
#' @param including_newline (`flag`)\cr whether newlines should also be indented. |
|
| 857 |
#' |
|
| 858 |
#' @return `x`, indented with left-padding with `indent * incr` white-spaces. |
|
| 859 |
#' |
|
| 860 |
#' @examples |
|
| 861 |
#' indent_string("a", 0)
|
|
| 862 |
#' indent_string("a", 1)
|
|
| 863 |
#' indent_string(letters[1:3], 0:2) |
|
| 864 |
#' indent_string(paste0(letters[1:3], "\n", LETTERS[1:3]), 0:2) |
|
| 865 |
#' |
|
| 866 |
#' @export |
|
| 867 |
indent_string <- function(x, indent = 0, incr = 2, including_newline = TRUE) {
|
|
| 868 | 648x |
if (length(x) > 0) {
|
| 869 | 648x |
indent <- rep_len(indent, length.out = length(x)) |
| 870 | 648x |
incr <- rep_len(incr, length.out = length(x)) |
| 871 |
} |
|
| 872 | ||
| 873 | 648x |
indent_str <- strrep(" ", (indent > 0) * indent * incr)
|
| 874 | ||
| 875 | 648x |
if (including_newline) {
|
| 876 | 648x |
x <- unlist(mapply(function(xi, stri) {
|
| 877 | 13580x |
gsub("\n", stri, xi, fixed = TRUE)
|
| 878 | 648x |
}, x, paste0("\n", indent_str)))
|
| 879 |
} |
|
| 880 | ||
| 881 | 648x |
paste0(indent_str, x) |
| 882 |
} |
|
| 883 | ||
| 884 |
## .paste_no_na <- function(x, ...) {
|
|
| 885 |
## paste(na.omit(x), ...) |
|
| 886 |
## } |
|
| 887 | ||
| 888 |
## #' Pad a string and align within string |
|
| 889 |
## #' |
|
| 890 |
## #' @param x string |
|
| 891 |
## #' @param n number of character of the output string, if `n < nchar(x)` an error is thrown |
|
| 892 |
## #' |
|
| 893 |
## #' @noRd |
|
| 894 |
## #' |
|
| 895 |
## #' @examples |
|
| 896 |
## #' |
|
| 897 |
## #' padstr("abc", 3)
|
|
| 898 |
## #' padstr("abc", 4)
|
|
| 899 |
## #' padstr("abc", 5)
|
|
| 900 |
## #' padstr("abc", 5, "left")
|
|
| 901 |
## #' padstr("abc", 5, "right")
|
|
| 902 |
## #' |
|
| 903 |
## #' if(interactive()){
|
|
| 904 |
## #' padstr("abc", 1)
|
|
| 905 |
## #' } |
|
| 906 |
## #' |
|
| 907 |
## padstr <- function(x, n, just = c("center", "left", "right")) {
|
|
| 908 | ||
| 909 |
## just <- match.arg(just) |
|
| 910 | ||
| 911 |
## if (length(x) != 1) stop("length of x needs to be 1 and not", length(x))
|
|
| 912 |
## if (is.na(n) || !is.numeric(n) || n < 0) stop("n needs to be numeric and > 0")
|
|
| 913 | ||
| 914 |
## if (is.na(x)) x <- "<NA>" |
|
| 915 | ||
| 916 |
## nc <- nchar(x) |
|
| 917 | ||
| 918 |
## if (n < nc) stop("\"", x, "\" has more than ", n, " characters")
|
|
| 919 | ||
| 920 |
## switch( |
|
| 921 |
## just, |
|
| 922 |
## center = {
|
|
| 923 |
## pad <- (n - nc)/2 |
|
| 924 |
## paste0(spaces(floor(pad)), x, spaces(ceiling(pad))) |
|
| 925 |
## }, |
|
| 926 |
## left = paste0(x, spaces(n - nc)), |
|
| 927 |
## right = paste0(spaces(n - nc), x) |
|
| 928 |
## ) |
|
| 929 |
## } |
|
| 930 | ||
| 931 |
## spaces <- function(n) {
|
|
| 932 |
## strrep(" ", n)
|
|
| 933 |
## } |
|
| 934 | ||
| 935 |
#' Convert matrix of strings into a string with aligned columns |
|
| 936 |
#' |
|
| 937 |
#' Note that this function is intended to print simple rectangular matrices and not `rtable`s. |
|
| 938 |
#' |
|
| 939 |
#' @param mat (`matrix`)\cr a matrix of strings. |
|
| 940 |
#' @param nheader (`integer(1)`)\cr number of header rows. |
|
| 941 |
#' @param colsep (`string`)\cr a string that separates the columns. |
|
| 942 |
#' @param hsep (`character(1)`)\cr character to build line separator. |
|
| 943 |
#' |
|
| 944 |
#' @return A string. |
|
| 945 |
#' |
|
| 946 |
#' @examples |
|
| 947 |
#' mat <- matrix(c("A", "B", "C", "a", "b", "c"), nrow = 2, byrow = TRUE)
|
|
| 948 |
#' cat(mat_as_string(mat)) |
|
| 949 |
#' cat("\n")
|
|
| 950 |
#' |
|
| 951 |
#' @noRd |
|
| 952 |
mat_as_string <- function(mat, nheader = 1, colsep = " ", hsep = default_hsep()) {
|
|
| 953 | 2x |
colwidths <- apply(apply(mat, c(1, 2), nchar), 2, max) |
| 954 | ||
| 955 | 2x |
rows_formatted <- apply(mat, 1, function(row) {
|
| 956 | 36x |
paste(unlist(mapply(padstr, row, colwidths, "left")), collapse = colsep) |
| 957 |
}) |
|
| 958 | ||
| 959 | 2x |
header_rows <- seq_len(nheader) |
| 960 | 2x |
nchwidth <- nchar(rows_formatted[1]) |
| 961 | 2x |
paste(c( |
| 962 | 2x |
rows_formatted[header_rows], |
| 963 | 2x |
substr(strrep(hsep, nchwidth), 1, nchwidth), |
| 964 | 2x |
rows_formatted[-header_rows] |
| 965 | 2x |
), collapse = "\n") |
| 966 |
} |
| 1 |
#' Default tabulation |
|
| 2 |
#' |
|
| 3 |
#' This function is used when [analyze()] is invoked. |
|
| 4 |
#' |
|
| 5 |
#' @param x (`vector`)\cr the *already split* data being tabulated for a particular cell/set of cells. |
|
| 6 |
#' @param ... additional parameters to pass on. |
|
| 7 |
#' |
|
| 8 |
#' @details This function has the following behavior given particular types of inputs: |
|
| 9 |
#' \describe{
|
|
| 10 |
#' \item{numeric}{calls [mean()] on `x`.}
|
|
| 11 |
#' \item{logical}{calls [sum()] on `x`.}
|
|
| 12 |
#' \item{factor}{calls [length()] on `x`.}
|
|
| 13 |
#' } |
|
| 14 |
#' |
|
| 15 |
#' The [in_rows()] function is called on the resulting value(s). All other classes of input currently lead to an error. |
|
| 16 |
#' |
|
| 17 |
#' @inherit in_rows return |
|
| 18 |
#' |
|
| 19 |
#' @author Gabriel Becker and Adrian Waddell |
|
| 20 |
#' |
|
| 21 |
#' @examples |
|
| 22 |
#' simple_analysis(1:3) |
|
| 23 |
#' simple_analysis(iris$Species) |
|
| 24 |
#' simple_analysis(iris$Species == "setosa") |
|
| 25 |
#' |
|
| 26 |
#' @rdname rtinner |
|
| 27 |
#' @export |
|
| 28 | 1817x |
setGeneric("simple_analysis", function(x, ...) standardGeneric("simple_analysis"))
|
| 29 | ||
| 30 |
#' @rdname rtinner |
|
| 31 |
#' @exportMethod simple_analysis |
|
| 32 |
setMethod( |
|
| 33 |
"simple_analysis", "numeric", |
|
| 34 | 1244x |
function(x, ...) in_rows("Mean" = rcell(mean(x, ...), stat_names = "mean", format = "xx.xx"))
|
| 35 |
) |
|
| 36 | ||
| 37 |
#' @rdname rtinner |
|
| 38 |
#' @exportMethod simple_analysis |
|
| 39 |
setMethod( |
|
| 40 |
"simple_analysis", "logical", |
|
| 41 | 4x |
function(x, ...) in_rows("Count" = rcell(sum(x, ...), stat_names = "n", format = "xx"))
|
| 42 |
) |
|
| 43 | ||
| 44 |
#' @rdname rtinner |
|
| 45 |
#' @exportMethod simple_analysis |
|
| 46 |
setMethod( |
|
| 47 |
"simple_analysis", "factor", |
|
| 48 | 569x |
function(x, ...) in_rows(.list = as.list(table(x)), .stat_names = "n") |
| 49 |
) |
|
| 50 | ||
| 51 |
#' @rdname rtinner |
|
| 52 |
#' @exportMethod simple_analysis |
|
| 53 |
setMethod( |
|
| 54 |
"simple_analysis", "ANY", |
|
| 55 |
function(x, ...) {
|
|
| 56 | ! |
stop("No default simple_analysis behavior for class ", class(x), " please specify FUN explicitly.")
|
| 57 |
} |
|
| 58 |
) |
|
| 59 | ||
| 60 |
#' Check if an object is a valid `rtable` |
|
| 61 |
#' |
|
| 62 |
#' @param x (`ANY`)\cr an object. |
|
| 63 |
#' |
|
| 64 |
#' @return `TRUE` if `x` is a formal `TableTree` object, `FALSE` otherwise. |
|
| 65 |
#' |
|
| 66 |
#' @examples |
|
| 67 |
#' is_rtable(build_table(basic_table(), iris)) |
|
| 68 |
#' |
|
| 69 |
#' @export |
|
| 70 |
is_rtable <- function(x) {
|
|
| 71 | 48x |
is(x, "VTableTree") |
| 72 |
} |
|
| 73 | ||
| 74 |
# nocov start |
|
| 75 |
## is each object in a collection from a class |
|
| 76 |
are <- function(object_collection, class2) {
|
|
| 77 |
all(vapply(object_collection, is, logical(1), class2)) |
|
| 78 |
} |
|
| 79 | ||
| 80 |
num_all_equal <- function(x, tol = .Machine$double.eps^0.5) {
|
|
| 81 |
stopifnot(is.numeric(x)) |
|
| 82 | ||
| 83 |
if (length(x) == 1) {
|
|
| 84 |
return(TRUE) |
|
| 85 |
} |
|
| 86 | ||
| 87 |
y <- range(x) / mean(x) |
|
| 88 |
isTRUE(all.equal(y[1], y[2], tolerance = tol)) |
|
| 89 |
} |
|
| 90 | ||
| 91 |
# copied over from utils.nest which is not open-source |
|
| 92 |
all_true <- function(lst, fcn, ...) {
|
|
| 93 |
all(vapply(lst, fcn, logical(1), ...)) |
|
| 94 |
} |
|
| 95 | ||
| 96 |
is_logical_single <- function(x) {
|
|
| 97 |
!is.null(x) && |
|
| 98 |
is.logical(x) && |
|
| 99 |
length(x) == 1 && |
|
| 100 |
!is.na(x) |
|
| 101 |
} |
|
| 102 | ||
| 103 |
is_logical_vector_modif <- function(x, min_length = 1) {
|
|
| 104 |
!is.null(x) && |
|
| 105 |
is.logical(x) && |
|
| 106 |
is.atomic(x) && |
|
| 107 |
!anyNA(x) && |
|
| 108 |
ifelse(min_length > 0, length(x) >= min_length, TRUE) |
|
| 109 |
} |
|
| 110 |
# nocov end |
|
| 111 | ||
| 112 |
# Shorthand for functions that take df as first parameter |
|
| 113 |
.takes_df <- function(f) {
|
|
| 114 | 1831x |
func_takes(f, "df", is_first = TRUE) |
| 115 |
} |
|
| 116 | ||
| 117 |
# Checking if function takes parameters |
|
| 118 |
func_takes <- function(func, params, is_first = FALSE) {
|
|
| 119 | 12412x |
if (is.list(func)) {
|
| 120 | 2564x |
return(lapply(func, func_takes, params = params, is_first = is_first)) |
| 121 |
} |
|
| 122 | 9848x |
if (is.null(func) || !is(func, "function")) {
|
| 123 |
# safe-net: should this fail instead? |
|
| 124 | 2096x |
return(setNames(rep(FALSE, length(params)), params)) |
| 125 |
} |
|
| 126 | 7752x |
f_params <- formals(func) |
| 127 | 7752x |
if (!is_first) {
|
| 128 | 2516x |
return(setNames(params %in% names(f_params), params)) |
| 129 |
} else {
|
|
| 130 | 5236x |
if (length(params) > 1L) {
|
| 131 | 1x |
stop("is_first works only with one parameters.")
|
| 132 |
} |
|
| 133 | 5235x |
return(!is.null(f_params) && names(f_params)[1] == params) |
| 134 |
} |
|
| 135 |
} |
|
| 136 | ||
| 137 |
#' Translate spl_context to a path to display in error messages |
|
| 138 |
#' |
|
| 139 |
#' @param ctx (`data.frame`)\cr the `spl_context` data frame where the error occurred. |
|
| 140 |
#' |
|
| 141 |
#' @return A character string containing a description of the row path corresponding to `ctx`. |
|
| 142 |
#' |
|
| 143 |
#' @export |
|
| 144 |
spl_context_to_disp_path <- function(ctx) {
|
|
| 145 |
## this can happen in the first split in column space, but |
|
| 146 |
## should never happen in row space |
|
| 147 | 23x |
if (length(ctx$split) == 0) {
|
| 148 | 2x |
return("root")
|
| 149 |
} |
|
| 150 | 21x |
if (ctx$split[1] == "root" && ctx$value[1] == "root") {
|
| 151 | 20x |
ctx <- ctx[-1, ] |
| 152 |
} |
|
| 153 | 21x |
ret <- paste(sprintf("%s[%s]", ctx[["split"]], ctx[["value"]]),
|
| 154 | 21x |
collapse = "->" |
| 155 |
) |
|
| 156 | 21x |
if (length(ret) == 0 || nchar(ret) == 0) {
|
| 157 | 14x |
ret <- "root" |
| 158 |
} |
|
| 159 | 21x |
ret |
| 160 |
} |
|
| 161 | ||
| 162 |
# Utility function to paste vector of values in a nice way |
|
| 163 |
paste_vec <- function(vec) {
|
|
| 164 | 7x |
paste0('c("', paste(vec, collapse = '", "'), '")')
|
| 165 |
} |
|
| 166 | ||
| 167 |
# Utility for checking if a package is installed |
|
| 168 |
check_required_packages <- function(pkgs) {
|
|
| 169 | ! |
for (pkgi in pkgs) {
|
| 170 | ! |
if (!requireNamespace(pkgi, quietly = TRUE)) {
|
| 171 | ! |
stop( |
| 172 | ! |
"This function requires the ", pkgi, " package. ", |
| 173 | ! |
"Please install it if you wish to use it" |
| 174 |
) |
|
| 175 |
} |
|
| 176 |
} |
|
| 177 |
} |
| 1 |
## NB handling the case where there are no values is done during tabulation |
|
| 2 |
## which is the only reason expression(TRUE) is ok, because otherwise |
|
| 3 |
## we (sometimes) run into |
|
| 4 |
## factor()[TRUE] giving <NA> (i.e. length 1) |
|
| 5 | 4748x |
setGeneric("make_subset_expr", function(spl, val) standardGeneric("make_subset_expr"))
|
| 6 | ||
| 7 |
setMethod( |
|
| 8 |
"make_subset_expr", "VarLevelSplit", |
|
| 9 |
function(spl, val) {
|
|
| 10 |
## this is how custom split functions will communicate the correct expression |
|
| 11 |
## to the column modeling code |
|
| 12 | 3533x |
if (length(value_expr(val)) > 0) {
|
| 13 | 12x |
return(value_expr(val)) |
| 14 |
} |
|
| 15 | ||
| 16 | 3521x |
v <- unlist(rawvalues(val)) |
| 17 |
## XXX if we're including all levels should even missing be included? |
|
| 18 | 3521x |
if (is(v, "AllLevelsSentinel")) {
|
| 19 | 9x |
as.expression(bquote((!is.na(.(a))), list(a = as.name(spl_payload(spl))))) |
| 20 |
} else {
|
|
| 21 | 3512x |
as.expression(bquote((!is.na(.(a)) & .(a) %in% .(b)), list( |
| 22 | 3512x |
a = as.name(spl_payload(spl)), |
| 23 | 3512x |
b = v |
| 24 |
))) |
|
| 25 |
} |
|
| 26 |
} |
|
| 27 |
) |
|
| 28 | ||
| 29 |
setMethod( |
|
| 30 |
"make_subset_expr", "MultiVarSplit", |
|
| 31 |
function(spl, val) {
|
|
| 32 |
## this is how custom split functions will communicate the correct expression |
|
| 33 |
## to the column modeling code |
|
| 34 | 312x |
if (length(value_expr(val)) > 0) {
|
| 35 | ! |
return(value_expr(val)) |
| 36 |
} |
|
| 37 | ||
| 38 |
## v = rawvalues(val) |
|
| 39 |
## as.expression(bquote(!is.na(.(a)), list(a = v))) |
|
| 40 | 312x |
expression(TRUE) |
| 41 |
} |
|
| 42 |
) |
|
| 43 | ||
| 44 |
setMethod( |
|
| 45 |
"make_subset_expr", "AnalyzeVarSplit", |
|
| 46 |
function(spl, val) {
|
|
| 47 | ! |
if (avar_inclNAs(spl)) {
|
| 48 | ! |
expression(TRUE) |
| 49 |
} else {
|
|
| 50 | ! |
as.expression(bquote( |
| 51 | ! |
!is.na(.(a)), |
| 52 | ! |
list(a = as.name(spl_payload(spl))) |
| 53 |
)) |
|
| 54 |
} |
|
| 55 |
} |
|
| 56 |
) |
|
| 57 | ||
| 58 |
setMethod( |
|
| 59 |
"make_subset_expr", "AnalyzeColVarSplit", |
|
| 60 |
function(spl, val) {
|
|
| 61 | ! |
expression(TRUE) |
| 62 |
} |
|
| 63 |
) |
|
| 64 | ||
| 65 |
## XXX these are going to be ridiculously slow |
|
| 66 |
## FIXME |
|
| 67 | ||
| 68 |
setMethod( |
|
| 69 |
"make_subset_expr", "VarStaticCutSplit", |
|
| 70 |
function(spl, val) {
|
|
| 71 | 135x |
v <- rawvalues(val) |
| 72 |
## as.expression(bquote(which(cut(.(a), breaks=.(brk), labels = .(labels), |
|
| 73 | 135x |
as.expression(bquote( |
| 74 | 135x |
cut(.(a), |
| 75 | 135x |
breaks = .(brk), labels = .(labels), |
| 76 | 135x |
include.lowest = TRUE |
| 77 | 135x |
) == .(b), |
| 78 | 135x |
list( |
| 79 | 135x |
a = as.name(spl_payload(spl)), |
| 80 | 135x |
b = v, |
| 81 | 135x |
brk = spl_cuts(spl), |
| 82 | 135x |
labels = spl_cutlabels(spl) |
| 83 |
) |
|
| 84 |
)) |
|
| 85 |
} |
|
| 86 |
) |
|
| 87 | ||
| 88 |
## NB this assumes spl_cutlabels(spl) is in order!!!!!! |
|
| 89 |
setMethod( |
|
| 90 |
"make_subset_expr", "CumulativeCutSplit", |
|
| 91 |
function(spl, val) {
|
|
| 92 | 63x |
v <- rawvalues(val) |
| 93 |
## as.expression(bquote(which(as.integer(cut(.(a), breaks=.(brk), |
|
| 94 | 63x |
as.expression(bquote( |
| 95 | 63x |
as.integer(cut(.(a), |
| 96 | 63x |
breaks = .(brk), |
| 97 | 63x |
labels = .(labels), |
| 98 | 63x |
include.lowest = TRUE |
| 99 |
)) <= |
|
| 100 | 63x |
as.integer(factor(.(b), levels = .(labels))), |
| 101 | 63x |
list( |
| 102 | 63x |
a = as.name(spl_payload(spl)), |
| 103 | 63x |
b = v, |
| 104 | 63x |
brk = spl_cuts(spl), |
| 105 | 63x |
labels = spl_cutlabels(spl) |
| 106 |
) |
|
| 107 |
)) |
|
| 108 |
} |
|
| 109 |
) |
|
| 110 | ||
| 111 |
## I think this one is unnecessary, |
|
| 112 |
## build_table collapses DynCutSplits into |
|
| 113 |
## static ones. |
|
| 114 |
## |
|
| 115 |
## XXX TODO fixme |
|
| 116 |
## setMethod("make_subset_expr", "VarDynCutSplit",
|
|
| 117 |
## function(spl, val) {
|
|
| 118 |
## v = rawvalues(val) |
|
| 119 |
## ## as.expression(bquote(which(.(fun)(.(a)) == .(b)), |
|
| 120 |
## as.expression(bquote(.(fun)(.(a)) == .(b)), |
|
| 121 |
## list(a = as.name(spl_payload(spl)), |
|
| 122 |
## b = v, |
|
| 123 |
## fun = spl@cut_fun)) |
|
| 124 |
## }) |
|
| 125 | ||
| 126 |
setMethod( |
|
| 127 |
"make_subset_expr", "AllSplit", |
|
| 128 | 411x |
function(spl, val) expression(TRUE) |
| 129 |
) |
|
| 130 | ||
| 131 |
## probably don't need this |
|
| 132 | ||
| 133 |
setMethod( |
|
| 134 |
"make_subset_expr", "expression", |
|
| 135 | ! |
function(spl, val) spl |
| 136 |
) |
|
| 137 | ||
| 138 |
setMethod( |
|
| 139 |
"make_subset_expr", "character", |
|
| 140 |
function(spl, val) {
|
|
| 141 | ! |
newspl <- VarLevelSplit(spl, spl) |
| 142 | ! |
make_subset_expr(newspl, val) |
| 143 |
} |
|
| 144 |
) |
|
| 145 | ||
| 146 |
.combine_subset_exprs <- function(ex1, ex2) {
|
|
| 147 | 3238x |
if (is.null(ex1) || identical(ex1, expression(TRUE))) {
|
| 148 | 2038x |
if (is.expression(ex2) && !identical(ex2, expression(TRUE))) {
|
| 149 | 1537x |
return(ex2) |
| 150 |
} else {
|
|
| 151 | 501x |
return(expression(TRUE)) |
| 152 |
} |
|
| 153 |
} |
|
| 154 | ||
| 155 |
## if(is.null(ex2)) |
|
| 156 |
## ex2 <- expression(TRUE) |
|
| 157 | 1200x |
stopifnot(is.expression(ex1), is.expression(ex2)) |
| 158 | 1200x |
as.expression(bquote((.(a)) & .(b), list(a = ex1[[1]], b = ex2[[1]]))) |
| 159 |
} |
|
| 160 | ||
| 161 |
make_pos_subset <- function(spls = pos_splits(pos), |
|
| 162 |
svals = pos_splvals(pos), |
|
| 163 |
pos) {
|
|
| 164 | 1111x |
expr <- NULL |
| 165 | 1111x |
for (i in seq_along(spls)) {
|
| 166 | 1728x |
newexpr <- make_subset_expr(spls[[i]], svals[[i]]) |
| 167 | 1728x |
expr <- .combine_subset_exprs(expr, newexpr) |
| 168 |
} |
|
| 169 | 1111x |
expr |
| 170 |
} |
|
| 171 | ||
| 172 |
get_pos_extra <- function(svals = pos_splvals(pos), |
|
| 173 |
pos) {
|
|
| 174 | 1117x |
ret <- list() |
| 175 | 1117x |
for (i in seq_along(svals)) {
|
| 176 | 1740x |
extrs <- splv_extra(svals[[i]]) |
| 177 | 1740x |
if (any(names(ret) %in% names(extrs))) {
|
| 178 | ! |
stop("same extra argument specified at multiple levels of nesting. Not currently supported")
|
| 179 |
} |
|
| 180 | 1740x |
ret <- c(ret, extrs) |
| 181 |
} |
|
| 182 | 1117x |
ret |
| 183 |
} |
|
| 184 | ||
| 185 |
get_col_extras <- function(ctree) {
|
|
| 186 | 361x |
leaves <- collect_leaves(ctree) |
| 187 | 361x |
lapply( |
| 188 | 361x |
leaves, |
| 189 | 361x |
function(x) get_pos_extra(pos = tree_pos(x)) |
| 190 |
) |
|
| 191 |
} |
|
| 192 | ||
| 193 |
setGeneric( |
|
| 194 |
"make_col_subsets", |
|
| 195 | 1471x |
function(lyt, df) standardGeneric("make_col_subsets")
|
| 196 |
) |
|
| 197 | ||
| 198 |
setMethod( |
|
| 199 |
"make_col_subsets", "LayoutColTree", |
|
| 200 |
function(lyt, df) {
|
|
| 201 | 360x |
leaves <- collect_leaves(lyt) |
| 202 | 360x |
lapply(leaves, make_col_subsets) |
| 203 |
} |
|
| 204 |
) |
|
| 205 | ||
| 206 |
setMethod( |
|
| 207 |
"make_col_subsets", "LayoutColLeaf", |
|
| 208 |
function(lyt, df) {
|
|
| 209 | 1111x |
make_pos_subset(pos = tree_pos(lyt)) |
| 210 |
} |
|
| 211 |
) |
|
| 212 | ||
| 213 |
create_colinfo <- function(lyt, df, rtpos = TreePos(), |
|
| 214 |
counts = NULL, |
|
| 215 |
alt_counts_df = NULL, |
|
| 216 |
total = NULL, |
|
| 217 |
topleft = NULL) {
|
|
| 218 |
## this will work whether clayout is pre or post |
|
| 219 |
## data |
|
| 220 | 366x |
clayout <- clayout(lyt) |
| 221 | 366x |
if (is.null(topleft)) {
|
| 222 | 366x |
topleft <- top_left(lyt) |
| 223 |
} |
|
| 224 | 366x |
cc_format <- colcount_format(lyt) %||% "(N=xx)" |
| 225 | ||
| 226 |
## do it this way for full backwards compatibility |
|
| 227 | 366x |
if (is.null(alt_counts_df)) {
|
| 228 | 346x |
alt_counts_df <- df |
| 229 |
} |
|
| 230 | 366x |
ctree <- coltree(clayout, df = df, rtpos = rtpos, alt_counts_df = alt_counts_df, ccount_format = cc_format) |
| 231 | 359x |
if (!is.na(disp_ccounts(lyt))) {
|
| 232 | 85x |
leaf_pths <- make_col_df(ctree, visible_only = TRUE, na_str = "", ccount_format = cc_format)$path |
| 233 | 85x |
for (path in leaf_pths) {
|
| 234 | 333x |
colcount_visible(ctree, path) <- disp_ccounts(lyt) |
| 235 |
} |
|
| 236 |
} |
|
| 237 | ||
| 238 | 359x |
cexprs <- make_col_subsets(ctree, df) |
| 239 | 359x |
colextras <- col_extra_args(ctree) |
| 240 | ||
| 241 |
## calculate the counts based on the df |
|
| 242 |
## This presumes that it is called on the WHOLE dataset, |
|
| 243 |
## NOT after any splitting has occurred. Otherwise |
|
| 244 |
## the counts will obviously be wrong. |
|
| 245 | 359x |
if (is.null(counts)) {
|
| 246 | 355x |
counts <- rep(NA_integer_, length(cexprs)) |
| 247 | 4x |
} else if (length(counts) != length(cexprs)) {
|
| 248 | 1x |
stop( |
| 249 | 1x |
"Length of overriding counts must equal number of columns. Got ", |
| 250 | 1x |
length(counts), " values for ", length(cexprs), " columns. ", |
| 251 | 1x |
"Use NAs to specify that the default counting machinery should be ", |
| 252 | 1x |
"used for that position." |
| 253 |
) |
|
| 254 |
} |
|
| 255 | ||
| 256 | 358x |
counts_df_name <- "alt_counts_df" |
| 257 | 358x |
if (identical(alt_counts_df, df)) { # is.null(alt_counts_df)) {
|
| 258 | 342x |
alt_counts_df <- df |
| 259 | 342x |
counts_df_name <- "df" |
| 260 |
} |
|
| 261 | 358x |
calcpos <- is.na(counts) |
| 262 | ||
| 263 | 358x |
calccounts <- sapply(cexprs, function(ex) {
|
| 264 | 1102x |
if (identical(ex, expression(TRUE))) {
|
| 265 | 177x |
nrow(alt_counts_df) |
| 266 | 925x |
} else if (identical(ex, expression(FALSE))) {
|
| 267 | ! |
0L |
| 268 |
} else {
|
|
| 269 | 925x |
vec <- try(eval(ex, envir = alt_counts_df), silent = TRUE) |
| 270 | 925x |
if (is(vec, "numeric")) {
|
| 271 | ! |
length(vec) |
| 272 | 925x |
} else if (is(vec, "logical")) { ## sum(is.na(.)) ????
|
| 273 | 925x |
sum(vec, na.rm = TRUE) |
| 274 |
} |
|
| 275 |
} |
|
| 276 |
}) |
|
| 277 | 358x |
counts[calcpos] <- calccounts[calcpos] |
| 278 | 358x |
counts <- as.integer(counts) |
| 279 | 358x |
if (is.null(total)) {
|
| 280 | ! |
total <- sum(counts) |
| 281 |
} |
|
| 282 | ||
| 283 | 358x |
cpths <- col_paths(ctree) |
| 284 | 358x |
for (i in seq_along(cpths)) {
|
| 285 | 1102x |
facet_colcount(ctree, cpths[[i]]) <- counts[i] |
| 286 |
} |
|
| 287 | 358x |
InstantiatedColumnInfo( |
| 288 | 358x |
treelyt = ctree, |
| 289 | 358x |
csubs = cexprs, |
| 290 | 358x |
extras = colextras, |
| 291 | 358x |
cnts = counts, |
| 292 | 358x |
dispcounts = disp_ccounts(lyt), |
| 293 | 358x |
countformat = cc_format, |
| 294 | 358x |
total_cnt = total, |
| 295 | 358x |
topleft = topleft |
| 296 |
) |
|
| 297 |
} |
| 1 |
#' Create an `ElementaryTable` from a `data.frame` |
|
| 2 |
#' |
|
| 3 |
#' @param df (`data.frame`)\cr a data frame. |
|
| 4 |
#' |
|
| 5 |
#' @details |
|
| 6 |
#' If row names are not defined in `df` (or they are simple numbers), then the row names are taken from the column |
|
| 7 |
#' `label_name`, if it exists. If `label_name` exists, then it is also removed from the original data. This behavior |
|
| 8 |
#' is compatible with [as_result_df()], when `as_is = TRUE` and the row names are not unique. |
|
| 9 |
#' |
|
| 10 |
#' @seealso [as_result_df()] for the inverse operation. |
|
| 11 |
#' |
|
| 12 |
#' @examples |
|
| 13 |
#' df_to_tt(mtcars) |
|
| 14 |
#' |
|
| 15 |
#' @export |
|
| 16 |
df_to_tt <- function(df) {
|
|
| 17 | 4x |
colnms <- colnames(df) |
| 18 | 4x |
cinfo <- manual_cols(colnms) |
| 19 | 4x |
rnames <- rownames(df) |
| 20 | 4x |
havern <- !is.null(rnames) |
| 21 | ||
| 22 | 4x |
if ((!havern || all(grepl("[0-9]+", rnames))) && "label_name" %in% colnms) {
|
| 23 | 2x |
rnames <- df$label_name |
| 24 | 2x |
df <- df[, -match("label_name", colnms)]
|
| 25 | 2x |
colnms <- colnames(df) |
| 26 | 2x |
cinfo <- manual_cols(colnms) |
| 27 | 2x |
havern <- TRUE |
| 28 |
} |
|
| 29 | ||
| 30 | 4x |
kids <- lapply(seq_len(nrow(df)), function(i) {
|
| 31 | 124x |
rni <- if (havern) rnames[i] else "" |
| 32 | 124x |
do.call(rrow, c(list(row.name = rni), unclass(df[i, ]))) |
| 33 |
}) |
|
| 34 | ||
| 35 | 4x |
ElementaryTable(kids = kids, cinfo = cinfo) |
| 36 |
} |
| 1 |
#' Format `rcell` objects |
|
| 2 |
#' |
|
| 3 |
#' This is a wrapper for [formatters::format_value()] for use with `CellValue` objects |
|
| 4 |
#' |
|
| 5 |
#' @inheritParams lyt_args |
|
| 6 |
#' @inheritParams formatters::format_value |
|
| 7 |
#' @param x (`CellValue` or `ANY`)\cr an object of class `CellValue`, or a raw value. |
|
| 8 |
#' @param format (`string` or `function`)\cr the format label or formatter function to |
|
| 9 |
#' apply to `x`. |
|
| 10 |
#' @param output (`string`)\cr output type. |
|
| 11 |
#' @param pr_row_format (`list`)\cr list of default formats coming from the general row. |
|
| 12 |
#' @param pr_row_na_str (`list`)\cr list of default `"NA"` strings coming from the general row. |
|
| 13 |
#' @param shell (`flag`)\cr whether the formats themselves should be returned instead of the |
|
| 14 |
#' values with formats applied. Defaults to `FALSE`. |
|
| 15 |
#' |
|
| 16 |
#' @return Formatted text. |
|
| 17 |
#' |
|
| 18 |
#' @examples |
|
| 19 |
#' cll <- CellValue(pi, format = "xx.xxx") |
|
| 20 |
#' format_rcell(cll) |
|
| 21 |
#' |
|
| 22 |
#' # Cell values precedes the row values |
|
| 23 |
#' cll <- CellValue(pi, format = "xx.xxx") |
|
| 24 |
#' format_rcell(cll, pr_row_format = "xx.x") |
|
| 25 |
#' |
|
| 26 |
#' # Similarly for NA values |
|
| 27 |
#' cll <- CellValue(NA, format = "xx.xxx", format_na_str = "This is THE NA") |
|
| 28 |
#' format_rcell(cll, pr_row_na_str = "This is NA") |
|
| 29 |
#' |
|
| 30 |
#' @export |
|
| 31 |
format_rcell <- function(x, format, |
|
| 32 |
output = c("ascii", "html"),
|
|
| 33 |
na_str = obj_na_str(x) %||% "NA", |
|
| 34 |
pr_row_format = NULL, |
|
| 35 |
pr_row_na_str = NULL, |
|
| 36 |
round_type = c("iec", "sas"),
|
|
| 37 |
shell = FALSE) {
|
|
| 38 |
# Check for format and parent row format |
|
| 39 | 110512x |
format <- if (missing(format)) obj_format(x) else format |
| 40 | 110512x |
if (is.null(format) && !is.null(pr_row_format)) {
|
| 41 | 77755x |
format <- pr_row_format |
| 42 |
} |
|
| 43 |
# Check for na_str from parent |
|
| 44 | 110512x |
if (is.null(obj_na_str(x)) && !is.null(pr_row_na_str)) {
|
| 45 | 93969x |
na_str <- pr_row_na_str |
| 46 |
} |
|
| 47 | ||
| 48 |
# Main call to external function or shell |
|
| 49 | 110512x |
if (shell) {
|
| 50 | 27945x |
return(format) |
| 51 |
} |
|
| 52 | 82567x |
format_value(rawvalues(x), |
| 53 | 82567x |
format = format, |
| 54 | 82567x |
output = output, |
| 55 | 82567x |
na_str = na_str, |
| 56 | 82567x |
round_type = round_type |
| 57 |
) |
|
| 58 |
} |
| 1 |
#' @importFrom utils browseURL |
|
| 2 |
NULL |
|
| 3 | ||
| 4 |
#' Display an `rtable` object in the Viewer pane in RStudio or in a browser |
|
| 5 |
#' |
|
| 6 |
#' The table will be displayed using bootstrap styling. |
|
| 7 |
#' |
|
| 8 |
#' @param x (`rtable` or `shiny.tag`)\cr an object of class `rtable` or `shiny.tag` (defined in `htmltools` package). |
|
| 9 |
#' @param y (`rtable` or `shiny.tag`)\cr optional second argument of same type as `x`. |
|
| 10 |
#' @param ... arguments passed to [as_html()]. |
|
| 11 |
#' |
|
| 12 |
#' @return Not meaningful. Called for the side effect of opening a browser or viewer pane. |
|
| 13 |
#' |
|
| 14 |
#' @examples |
|
| 15 |
#' if (interactive()) {
|
|
| 16 |
#' sl5 <- factor(iris$Sepal.Length > 5, |
|
| 17 |
#' levels = c(TRUE, FALSE), |
|
| 18 |
#' labels = c("S.L > 5", "S.L <= 5")
|
|
| 19 |
#' ) |
|
| 20 |
#' |
|
| 21 |
#' df <- cbind(iris, sl5 = sl5) |
|
| 22 |
#' |
|
| 23 |
#' lyt <- basic_table() %>% |
|
| 24 |
#' split_cols_by("sl5") %>%
|
|
| 25 |
#' analyze("Sepal.Length")
|
|
| 26 |
#' |
|
| 27 |
#' tbl <- build_table(lyt, df) |
|
| 28 |
#' |
|
| 29 |
#' Viewer(tbl) |
|
| 30 |
#' Viewer(tbl, tbl) |
|
| 31 |
#' |
|
| 32 |
#' |
|
| 33 |
#' tbl2 <- htmltools::tags$div( |
|
| 34 |
#' class = "table-responsive", |
|
| 35 |
#' as_html(tbl, class_table = "table") |
|
| 36 |
#' ) |
|
| 37 |
#' |
|
| 38 |
#' Viewer(tbl, tbl2) |
|
| 39 |
#' } |
|
| 40 |
#' @export |
|
| 41 |
Viewer <- function(x, y = NULL, ...) {
|
|
| 42 | 3x |
check_convert <- function(x, name, accept_NULL = FALSE) {
|
| 43 | 6x |
if (accept_NULL && is.null(x)) {
|
| 44 | 3x |
NULL |
| 45 | 3x |
} else if (is(x, "shiny.tag")) {
|
| 46 | ! |
x |
| 47 | 3x |
} else if (is(x, "VTableTree")) {
|
| 48 | 3x |
as_html(x, ...) |
| 49 |
} else {
|
|
| 50 | ! |
stop("object of class rtable or shiny tag excepted for ", name)
|
| 51 |
} |
|
| 52 |
} |
|
| 53 | ||
| 54 | 3x |
x_tag <- check_convert(x, "x", FALSE) |
| 55 | 3x |
y_tag <- check_convert(y, "y", TRUE) |
| 56 | ||
| 57 | 3x |
html_output <- if (is.null(y)) {
|
| 58 | 3x |
x_tag |
| 59 |
} else {
|
|
| 60 | ! |
tags$div(class = "container-fluid", htmltools::tags$div( |
| 61 | ! |
class = "row", |
| 62 | ! |
tags$div(class = "col-xs-6", x_tag), |
| 63 | ! |
tags$div(class = "col-xs-6", y_tag) |
| 64 |
)) |
|
| 65 |
} |
|
| 66 | ||
| 67 | 3x |
sandbox_folder <- file.path(tempdir(), "rtable") |
| 68 | ||
| 69 | 3x |
if (!dir.exists(sandbox_folder)) {
|
| 70 | 1x |
dir.create(sandbox_folder, recursive = TRUE) |
| 71 | 1x |
pbs <- file.path(path.package(package = "rtables"), "bootstrap/") |
| 72 | 1x |
file.copy(list.files(pbs, full.names = TRUE, recursive = FALSE), sandbox_folder, recursive = TRUE) |
| 73 |
# list.files(sandbox_folder) |
|
| 74 |
} |
|
| 75 | ||
| 76 |
# get html name |
|
| 77 | 3x |
n_try <- 10000 |
| 78 | 3x |
for (i in seq_len(n_try)) {
|
| 79 | 6x |
htmlFile <- file.path(sandbox_folder, paste0("table", i, ".html"))
|
| 80 | ||
| 81 | 6x |
if (!file.exists(htmlFile)) {
|
| 82 | 3x |
break |
| 83 | 3x |
} else if (i == n_try) {
|
| 84 | ! |
stop("too many html rtables created, restart your session")
|
| 85 |
} |
|
| 86 |
} |
|
| 87 | ||
| 88 | 3x |
html_bs <- tags$html( |
| 89 | 3x |
lang = "en", |
| 90 | 3x |
tags$head( |
| 91 | 3x |
tags$meta(charset = "utf-8"), |
| 92 | 3x |
tags$meta("http-equiv" = "X-UA-Compatible", content = "IE=edge"),
|
| 93 | 3x |
tags$meta( |
| 94 | 3x |
name = "viewport", |
| 95 | 3x |
content = "width=device-width, initial-scale=1" |
| 96 |
), |
|
| 97 | 3x |
tags$title("rtable"),
|
| 98 | 3x |
tags$link( |
| 99 | 3x |
href = "css/bootstrap.min.css", |
| 100 | 3x |
rel = "stylesheet" |
| 101 |
) |
|
| 102 |
), |
|
| 103 | 3x |
tags$body( |
| 104 | 3x |
html_output |
| 105 |
) |
|
| 106 |
) |
|
| 107 | ||
| 108 | 3x |
cat( |
| 109 | 3x |
paste("<!DOCTYPE html>\n", htmltools::doRenderTags(html_bs)),
|
| 110 | 3x |
file = htmlFile, append = FALSE |
| 111 |
) |
|
| 112 | ||
| 113 | 3x |
viewer <- getOption("viewer")
|
| 114 | ||
| 115 | 3x |
if (!is.null(viewer)) {
|
| 116 | 3x |
viewer(htmlFile) |
| 117 |
} else {
|
|
| 118 | ! |
browseURL(htmlFile) |
| 119 |
} |
|
| 120 |
} |
| 1 |
#' Change indentation of all `rrows` in an `rtable` |
|
| 2 |
#' |
|
| 3 |
#' Change indentation of all `rrows` in an `rtable` |
|
| 4 |
#' |
|
| 5 |
#' @param x (`VTableTree`)\cr an `rtable` object. |
|
| 6 |
#' @param by (`integer`)\cr number to increase indentation of rows by. Can be negative. If final indentation is |
|
| 7 |
#' less than 0, the indentation is set to 0. |
|
| 8 |
#' |
|
| 9 |
#' @return `x` with its indent modifier incremented by `by`. |
|
| 10 |
#' |
|
| 11 |
#' @examples |
|
| 12 |
#' is_setosa <- iris$Species == "setosa" |
|
| 13 |
#' m_tbl <- rtable( |
|
| 14 |
#' header = rheader( |
|
| 15 |
#' rrow(row.name = NULL, rcell("Sepal.Length", colspan = 2), rcell("Petal.Length", colspan = 2)),
|
|
| 16 |
#' rrow(NULL, "mean", "median", "mean", "median") |
|
| 17 |
#' ), |
|
| 18 |
#' rrow( |
|
| 19 |
#' row.name = "All Species", |
|
| 20 |
#' mean(iris$Sepal.Length), median(iris$Sepal.Length), |
|
| 21 |
#' mean(iris$Petal.Length), median(iris$Petal.Length), |
|
| 22 |
#' format = "xx.xx" |
|
| 23 |
#' ), |
|
| 24 |
#' rrow( |
|
| 25 |
#' row.name = "Setosa", |
|
| 26 |
#' mean(iris$Sepal.Length[is_setosa]), median(iris$Sepal.Length[is_setosa]), |
|
| 27 |
#' mean(iris$Petal.Length[is_setosa]), median(iris$Petal.Length[is_setosa]), |
|
| 28 |
#' format = "xx.xx" |
|
| 29 |
#' ) |
|
| 30 |
#' ) |
|
| 31 |
#' indent(m_tbl) |
|
| 32 |
#' indent(m_tbl, 2) |
|
| 33 |
#' |
|
| 34 |
#' @export |
|
| 35 |
indent <- function(x, by = 1) {
|
|
| 36 | 9x |
if (nrow(x) == 0 || by == 0) {
|
| 37 | 9x |
return(x) |
| 38 |
} |
|
| 39 | ||
| 40 | ! |
indent_mod(x) <- indent_mod(x) + by |
| 41 | ! |
x |
| 42 |
} |
|
| 43 | ||
| 44 |
#' Clear all indent modifiers from a table |
|
| 45 |
#' |
|
| 46 |
#' @inheritParams gen_args |
|
| 47 |
#' |
|
| 48 |
#' @return The same class as `tt`, with all indent modifiers set to zero. |
|
| 49 |
#' |
|
| 50 |
#' @examples |
|
| 51 |
#' lyt1 <- basic_table() %>% |
|
| 52 |
#' summarize_row_groups("STUDYID", label_fstr = "overall summary") %>%
|
|
| 53 |
#' split_rows_by("AEBODSYS", child_labels = "visible") %>%
|
|
| 54 |
#' summarize_row_groups("STUDYID", label_fstr = "subgroup summary") %>%
|
|
| 55 |
#' analyze("AGE", indent_mod = -1L)
|
|
| 56 |
#' |
|
| 57 |
#' tbl1 <- build_table(lyt1, ex_adae) |
|
| 58 |
#' tbl1 |
|
| 59 |
#' clear_indent_mods(tbl1) |
|
| 60 |
#' |
|
| 61 |
#' @export |
|
| 62 |
#' @rdname clear_imods |
|
| 63 | 40x |
setGeneric("clear_indent_mods", function(tt) standardGeneric("clear_indent_mods"))
|
| 64 | ||
| 65 |
#' @export |
|
| 66 |
#' @rdname clear_imods |
|
| 67 |
setMethod( |
|
| 68 |
"clear_indent_mods", "VTableTree", |
|
| 69 |
function(tt) {
|
|
| 70 | 25x |
ct <- content_table(tt) |
| 71 | 25x |
if (!is.null(ct)) {
|
| 72 | 9x |
content_table(tt) <- clear_indent_mods(ct) |
| 73 |
} |
|
| 74 | 25x |
tree_children(tt) <- lapply(tree_children(tt), clear_indent_mods) |
| 75 | 25x |
indent_mod(tt) <- 0L |
| 76 | 25x |
tt |
| 77 |
} |
|
| 78 |
) |
|
| 79 | ||
| 80 |
#' @export |
|
| 81 |
#' @rdname clear_imods |
|
| 82 |
setMethod( |
|
| 83 |
"clear_indent_mods", "TableRow", |
|
| 84 |
function(tt) {
|
|
| 85 | 15x |
indent_mod(tt) <- 0L |
| 86 | 15x |
tt |
| 87 |
} |
|
| 88 |
) |
| 1 |
#' @importFrom tools file_ext |
|
| 2 |
NULL |
|
| 3 | ||
| 4 |
#' Create enriched flat value table with paths |
|
| 5 |
#' |
|
| 6 |
#' This function creates a flat tabular file of cell values and corresponding paths via [path_enriched_df()]. It then |
|
| 7 |
#' writes that data frame out as a `tsv` file. |
|
| 8 |
#' |
|
| 9 |
#' By default (i.e. when `value_func` is not specified, list columns where at least one value has length > 1 are |
|
| 10 |
#' collapsed to character vectors by collapsing the list element with `"|"`. |
|
| 11 |
#' |
|
| 12 |
#' @note |
|
| 13 |
#' There is currently no round-trip capability for this type of export. You can read values exported this way back in |
|
| 14 |
#' via `import_from_tsv` but you will receive only the `data.frame` version back, NOT a `TableTree`. |
|
| 15 |
#' |
|
| 16 |
#' @inheritParams gen_args |
|
| 17 |
#' @inheritParams data.frame_export |
|
| 18 |
#' @param file (`string`)\cr the path of the file to written to or read from. |
|
| 19 |
#' @param sep (`string`)\cr defaults to `\t`. See [utils::write.table()] for more details. |
|
| 20 |
#' @param ... (`any`)\cr additional arguments to be passed to [utils::write.table()]. |
|
| 21 |
#' |
|
| 22 |
#' @return |
|
| 23 |
#' * `export_as_tsv` returns `NULL` silently. |
|
| 24 |
#' * `import_from_tsv` returns a `data.frame` with re-constituted list values. |
|
| 25 |
#' |
|
| 26 |
#' @seealso [path_enriched_df()] for the underlying function that does the work. |
|
| 27 |
#' |
|
| 28 |
#' @importFrom utils write.table read.table |
|
| 29 |
#' @rdname tsv_io |
|
| 30 |
#' @export |
|
| 31 |
export_as_tsv <- function(tt, file = NULL, path_fun = collapse_path, |
|
| 32 |
value_fun = collapse_values, sep = "\t", ...) {
|
|
| 33 | 1x |
df <- path_enriched_df(tt, path_fun = path_fun, value_fun = value_fun) |
| 34 | 1x |
write.table(df, file, sep = sep, ...) |
| 35 |
} |
|
| 36 | ||
| 37 |
#' @rdname tsv_io |
|
| 38 |
#' @export |
|
| 39 |
import_from_tsv <- function(file) {
|
|
| 40 | 1x |
rawdf <- read.table(file, header = TRUE, sep = "\t") |
| 41 | 1x |
as.data.frame(lapply( |
| 42 | 1x |
rawdf, |
| 43 | 1x |
function(col) {
|
| 44 | 7x |
if (!any(grepl(.collapse_char, col, fixed = TRUE))) {
|
| 45 | ! |
col |
| 46 |
} else {
|
|
| 47 | 7x |
I(strsplit(col, split = .collapse_char_esc)) |
| 48 |
} |
|
| 49 |
} |
|
| 50 |
)) |
|
| 51 |
} |
|
| 52 |
# txt (formatters) -------------------------------------------------------------------- |
|
| 53 |
#' @importFrom formatters export_as_txt |
|
| 54 |
#' |
|
| 55 |
#' @examples |
|
| 56 |
#' lyt <- basic_table() %>% |
|
| 57 |
#' split_cols_by("ARM") %>%
|
|
| 58 |
#' analyze(c("AGE", "BMRKR2", "COUNTRY"))
|
|
| 59 |
#' |
|
| 60 |
#' tbl <- build_table(lyt, ex_adsl) |
|
| 61 |
#' |
|
| 62 |
#' cat(export_as_txt(tbl, file = NULL, paginate = TRUE, lpp = 8)) |
|
| 63 |
#' |
|
| 64 |
#' \dontrun{
|
|
| 65 |
#' tf <- tempfile(fileext = ".txt") |
|
| 66 |
#' export_as_txt(tbl, file = tf) |
|
| 67 |
#' system2("cat", tf)
|
|
| 68 |
#' } |
|
| 69 |
#' |
|
| 70 |
#' @export |
|
| 71 |
formatters::export_as_txt |
|
| 72 | ||
| 73 |
# pdf (formatters) ---------------------------------------------------------- |
|
| 74 |
#' @importFrom formatters export_as_pdf |
|
| 75 |
#' |
|
| 76 |
#' @examples |
|
| 77 |
#' lyt <- basic_table() %>% |
|
| 78 |
#' split_cols_by("ARM") %>%
|
|
| 79 |
#' analyze(c("AGE", "BMRKR2", "COUNTRY"))
|
|
| 80 |
#' |
|
| 81 |
#' tbl <- build_table(lyt, ex_adsl) |
|
| 82 |
#' |
|
| 83 |
#' \dontrun{
|
|
| 84 |
#' tf <- tempfile(fileext = ".pdf") |
|
| 85 |
#' export_as_pdf(tbl, file = tf, pg_height = 4) |
|
| 86 |
#' tf <- tempfile(fileext = ".pdf") |
|
| 87 |
#' export_as_pdf(tbl, file = tf, lpp = 8) |
|
| 88 |
#' } |
|
| 89 |
#' |
|
| 90 |
#' @export |
|
| 91 |
formatters::export_as_pdf |