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 | 188x |
if (is.character(path) && length(path) > 1) { |
6 | 163x |
path <- as.list(path) |
7 |
} |
|
8 | 188x |
if (length(path) > 0 && path[[1]] == obj_name(tab)) { |
9 | 162x |
path <- path[-1] |
10 |
} |
|
11 | 188x |
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 | 771x |
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 | 191x |
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 | 191x |
return(newkid) |
54 | 580x |
} else if (path[[1]] == "@content") { |
55 | 27x |
ctb <- content_table(tab) |
56 | 27x |
ctb <- recursive_replace(ctb, |
57 | 27x |
path = path[-1], |
58 |
## rows = rows, |
|
59 |
## cols = cols, |
|
60 | 27x |
value = value |
61 |
) |
|
62 | 27x |
content_table(tab) <- ctb |
63 | 27x |
tab |
64 |
} else { ## length(path) > 1, more recursing to do |
|
65 | 553x |
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 | 553x |
stopifnot( |
71 | 553x |
length(kidel) == 1, |
72 | 553x |
is.character(kidel) || is.factor(kidel) |
73 |
) |
|
74 | 553x |
knms <- names(tree_children(tab)) |
75 | 553x |
if (!(kidel %in% knms)) { |
76 | ! |
stop(sprintf("position element %s not in names of next level children", kidel)) |
77 | 553x |
} 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 | 553x |
newkid <- recursive_replace( |
82 | 553x |
tree_children(tab)[[kidel]], |
83 | 553x |
path[-1], |
84 |
## incontent = incontent, |
|
85 |
## rows = rows, |
|
86 |
## cols = cols, |
|
87 | 553x |
value |
88 |
) |
|
89 | 553x |
tree_children(tab)[[kidel]] <- newkid |
90 | 553x |
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 |
#' @rdname insert_row_at_path |
|
209 |
setMethod( |
|
210 |
"insert_row_at_path", c("VTableTree", "ANY"), |
|
211 |
function(tt, path, value) { |
|
212 | ! |
stop( |
213 | ! |
"Currently only insertion of DataRow objects is supported. Got ", |
214 | ! |
"object of class ", class(value), ". Please use rrow() or DataRow() ", |
215 | ! |
"to construct your row before insertion." |
216 |
) |
|
217 |
} |
|
218 |
) |
|
219 | ||
220 |
#' Label at path |
|
221 |
#' |
|
222 |
#' Accesses or sets the label at a path. |
|
223 |
#' |
|
224 |
#' @inheritParams gen_args |
|
225 |
#' |
|
226 |
#' @details |
|
227 |
#' If `path` resolves to a single row, the label for that row is retrieved or set. If, instead, `path` resolves to a |
|
228 |
#' subtable, the text for the row-label associated with that path is retrieved or set. In the subtable case, if the |
|
229 |
#' label text is set to a non-`NA` value, the `labelrow` will be set to visible, even if it was not before. Similarly, |
|
230 |
#' 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 |
|
231 |
#' appear at all when the table is printed. |
|
232 |
#' |
|
233 |
#' @note When changing the row labels for content rows, it is important to path all the way to the *row*. Paths |
|
234 |
#' ending in `"@content"` will not exhibit the behavior you want, and are thus an error. See [row_paths()] for help |
|
235 |
#' determining the full paths to content rows. |
|
236 |
#' |
|
237 |
#' @examples |
|
238 |
#' lyt <- basic_table() %>% |
|
239 |
#' split_rows_by("COUNTRY", split_fun = keep_split_levels(c("CHN", "USA"))) %>% |
|
240 |
#' analyze("AGE") |
|
241 |
#' |
|
242 |
#' tbl <- build_table(lyt, DM) |
|
243 |
#' |
|
244 |
#' label_at_path(tbl, c("COUNTRY", "CHN")) |
|
245 |
#' |
|
246 |
#' label_at_path(tbl, c("COUNTRY", "USA")) <- "United States" |
|
247 |
#' tbl |
|
248 |
#' |
|
249 |
#' @export |
|
250 |
label_at_path <- function(tt, path) { |
|
251 | 29x |
obj_label(tt_at_path(tt, path)) |
252 |
} |
|
253 | ||
254 |
#' @export |
|
255 |
#' @rdname label_at_path |
|
256 |
`label_at_path<-` <- function(tt, path, value) { |
|
257 | 32x |
if (!is(tt, "VTableTree")) { |
258 | ! |
stop("tt must be a TableTree or ElementaryTable object") |
259 |
} |
|
260 | 32x |
if (is.null(value) || is.na(value)) { |
261 | 1x |
value <- NA_character_ |
262 |
} |
|
263 | 32x |
subt <- tt_at_path(tt, path) |
264 | 32x |
obj_label(subt) <- value |
265 | 32x |
tt_at_path(tt, path) <- subt |
266 | 32x |
tt |
267 |
} |
|
268 | ||
269 |
#' Access or set table elements at specified path |
|
270 |
#' |
|
271 |
#' @inheritParams gen_args |
|
272 |
#' @param ... unused. |
|
273 |
#' |
|
274 |
#' @export |
|
275 |
#' @rdname ttap |
|
276 | 386x |
setGeneric("tt_at_path", function(tt, path, ...) standardGeneric("tt_at_path")) |
277 | ||
278 |
#' @inheritParams tt_at_path |
|
279 |
#' |
|
280 |
#' @export |
|
281 |
#' @rdname int_methods |
|
282 |
setMethod( |
|
283 |
"tt_at_path", "VTableTree", |
|
284 |
function(tt, path, ...) { |
|
285 | 386x |
stopifnot( |
286 | 386x |
is(path, "character"), |
287 | 386x |
length(path) > 0, |
288 | 386x |
!anyNA(path) |
289 |
) |
|
290 | ||
291 | 386x |
if (path[1] == "root" && obj_name(tt) != "root") { |
292 | 3x |
path <- path[-1] |
293 |
} |
|
294 |
## handle pathing that hits the root split by name |
|
295 | 386x |
if (obj_name(tt) == path[1]) { |
296 | 354x |
path <- path[-1] |
297 |
} |
|
298 | 386x |
cur <- tt |
299 | 386x |
curpath <- path |
300 | 386x |
while (length(curpath > 0)) { |
301 | 1311x |
kids <- tree_children(cur) |
302 | 1311x |
curname <- curpath[1] |
303 | 1311x |
if (curname == "@content") { |
304 | 69x |
cur <- content_table(cur) |
305 | 1242x |
} else if (curname %in% names(kids)) { |
306 | 1241x |
cur <- kids[[curname]] |
307 |
} else { |
|
308 | 1x |
stop("Path appears invalid for this tree at step ", curname) |
309 |
} |
|
310 | 1310x |
curpath <- curpath[-1] |
311 |
} |
|
312 | 385x |
cur |
313 |
} |
|
314 |
) |
|
315 | ||
316 |
#' @note Setting `NULL` at a defined path removes the corresponding sub-table. |
|
317 |
#' |
|
318 |
#' @examples |
|
319 |
#' # Accessing sub table. |
|
320 |
#' lyt <- basic_table() %>% |
|
321 |
#' split_cols_by("ARM") %>% |
|
322 |
#' split_rows_by("SEX") %>% |
|
323 |
#' split_rows_by("BMRKR2") %>% |
|
324 |
#' analyze("AGE") |
|
325 |
#' |
|
326 |
#' tbl <- build_table(lyt, ex_adsl) %>% prune_table() |
|
327 |
#' sub_tbl <- tt_at_path(tbl, path = c("SEX", "F", "BMRKR2")) |
|
328 |
#' |
|
329 |
#' # Removing sub table. |
|
330 |
#' tbl2 <- tbl |
|
331 |
#' tt_at_path(tbl2, path = c("SEX", "F")) <- NULL |
|
332 |
#' tbl2 |
|
333 |
#' |
|
334 |
#' # Setting sub table. |
|
335 |
#' lyt3 <- basic_table() %>% |
|
336 |
#' split_cols_by("ARM") %>% |
|
337 |
#' split_rows_by("SEX") %>% |
|
338 |
#' analyze("BMRKR2") |
|
339 |
#' |
|
340 |
#' tbl3 <- build_table(lyt3, ex_adsl) %>% prune_table() |
|
341 |
#' |
|
342 |
#' tt_at_path(tbl3, path = c("SEX", "F", "BMRKR2")) <- sub_tbl |
|
343 |
#' tbl3 |
|
344 |
#' |
|
345 |
#' @export |
|
346 |
#' @rdname ttap |
|
347 |
setGeneric( |
|
348 |
"tt_at_path<-", |
|
349 | 188x |
function(tt, path, ..., value) standardGeneric("tt_at_path<-") |
350 |
) |
|
351 | ||
352 |
#' @export |
|
353 |
#' @keywords internal |
|
354 |
#' @rdname int_methods |
|
355 |
setMethod( |
|
356 |
"tt_at_path<-", c(tt = "VTableTree", value = "VTableTree"), |
|
357 |
function(tt, path, ..., value) { |
|
358 | 80x |
do_recursive_replace(tt, path = path, value = value) |
359 |
} |
|
360 |
) |
|
361 | ||
362 |
## this one removes the child at path from the parents list of children, |
|
363 |
## because that is how lists behave. |
|
364 |
#' @export |
|
365 |
#' @keywords internal |
|
366 |
#' @rdname int_methods |
|
367 |
setMethod( |
|
368 |
"tt_at_path<-", c(tt = "VTableTree", value = "NULL"), |
|
369 |
function(tt, path, ..., value) { |
|
370 | 2x |
do_recursive_replace(tt, path = path, value = value) |
371 |
} |
|
372 |
) |
|
373 | ||
374 |
#' @export |
|
375 |
#' @keywords internal |
|
376 |
#' @rdname int_methods |
|
377 |
setMethod( |
|
378 |
"tt_at_path<-", c(tt = "VTableTree", value = "TableRow"), |
|
379 |
function(tt, path, ..., value) { |
|
380 | 106x |
stopifnot(is(tt_at_path(tt = tt, path = path), "TableRow")) |
381 | 106x |
do_recursive_replace(tt, path = path, value = value) |
382 | ||
383 |
## ##i <- .path_to_pos(path = path, seq_len(nrow(tt)), tt, NROW) |
|
384 |
## i <- .path_to_pos(path = path, tt = tt) |
|
385 | ||
386 |
## replace_rows(tt, i = i, value = list(value)) |
|
387 |
} |
|
388 |
) |
|
389 | ||
390 |
#' Retrieve and assign elements of a `TableTree` |
|
391 |
#' |
|
392 |
#' @param x (`TableTree`)\cr a `TableTree` object. |
|
393 |
#' @param i (`numeric(1)`)\cr index. |
|
394 |
#' @param j (`numeric(1)`)\cr index. |
|
395 |
#' @param drop (`flag`)\cr whether the value in the cell should be returned if one cell is selected by the |
|
396 |
#' combination of `i` and `j`. It is not possible to return a vector of values. To do so please consider using |
|
397 |
#' [cell_values()]. Defaults to `FALSE`. |
|
398 |
#' @param ... additional arguments. Includes: |
|
399 |
#' \describe{ |
|
400 |
#' \item{`keep_topleft`}{(`flag`) (`[` only) whether the top-left material for the table should be retained after |
|
401 |
#' subsetting. Defaults to `TRUE` if all rows are included (i.e. subsetting was by column), and drops it |
|
402 |
#' otherwise.} |
|
403 |
#' \item{`keep_titles`}{(`flag`) whether title information should be retained. Defaults to `FALSE`.} |
|
404 |
#' \item{`keep_footers`}{(`flag`) whether non-referential footer information should be retained. Defaults to |
|
405 |
#' `keep_titles`.} |
|
406 |
#' \item{`reindex_refs`}{(`flag`) whether referential footnotes should be re-indexed as if the resulting subset is |
|
407 |
#' the entire table. Defaults to `TRUE`.} |
|
408 |
#' } |
|
409 |
#' @param value (`list`, `TableRow`, or `TableTree`)\cr replacement value. |
|
410 |
#' |
|
411 |
#' @details |
|
412 |
#' By default, subsetting drops the information about title, subtitle, main footer, provenance footer, and `topleft`. |
|
413 |
#' If only a column is selected and all rows are kept, the `topleft` information remains as default. Any referential |
|
414 |
#' footnote is kept whenever the subset table contains the referenced element. |
|
415 |
#' |
|
416 |
#' @return A `TableTree` (or `ElementaryTable`) object, unless a single cell was selected with `drop = TRUE`, in which |
|
417 |
#' case the (possibly multi-valued) fully stripped raw value of the selected cell. |
|
418 |
#' |
|
419 |
#' @note |
|
420 |
#' Subsetting always preserve the original order, even if provided indexes do not preserve it. If sorting is needed, |
|
421 |
#' please consider using `sort_at_path()`. Also note that `character` indices are treated as paths, not vectors of |
|
422 |
#' names in both `[` and `[<-`. |
|
423 |
#' |
|
424 |
#' @seealso |
|
425 |
#' * [sort_at_path()] to understand sorting. |
|
426 |
#' * [summarize_row_groups()] to understand path structure. |
|
427 |
#' |
|
428 |
#' @examples |
|
429 |
#' lyt <- basic_table( |
|
430 |
#' title = "Title", |
|
431 |
#' subtitles = c("Sub", "titles"), |
|
432 |
#' prov_footer = "prov footer", |
|
433 |
#' main_footer = "main footer" |
|
434 |
#' ) %>% |
|
435 |
#' split_cols_by("ARM") %>% |
|
436 |
#' split_rows_by("SEX") %>% |
|
437 |
#' analyze(c("AGE")) |
|
438 |
#' |
|
439 |
#' tbl <- build_table(lyt, DM) |
|
440 |
#' top_left(tbl) <- "Info" |
|
441 |
#' tbl |
|
442 |
#' |
|
443 |
#' # As default header, footer, and topleft information is lost |
|
444 |
#' tbl[1, ] |
|
445 |
#' tbl[1:2, 2] |
|
446 |
#' |
|
447 |
#' # Also boolean filters can work |
|
448 |
#' tbl[, c(FALSE, TRUE, FALSE)] |
|
449 |
#' |
|
450 |
#' # If drop = TRUE, the content values are directly retrieved |
|
451 |
#' tbl[2, 1] |
|
452 |
#' tbl[2, 1, drop = TRUE] |
|
453 |
#' |
|
454 |
#' # Drop works also if vectors are selected, but not matrices |
|
455 |
#' tbl[, 1, drop = TRUE] |
|
456 |
#' tbl[2, , drop = TRUE] |
|
457 |
#' tbl[1, 1, drop = TRUE] # NULL because it is a label row |
|
458 |
#' tbl[2, 1:2, drop = TRUE] # vectors can be returned only with cell_values() |
|
459 |
#' tbl[1:2, 1:2, drop = TRUE] # no dropping because it is a matrix |
|
460 |
#' |
|
461 |
#' # If all rows are selected, topleft is kept by default |
|
462 |
#' tbl[, 2] |
|
463 |
#' tbl[, 1] |
|
464 |
#' |
|
465 |
#' # It is possible to deselect values |
|
466 |
#' tbl[-2, ] |
|
467 |
#' tbl[, -1] |
|
468 |
#' |
|
469 |
#' # Values can be reassigned |
|
470 |
#' tbl[2, 1] <- rcell(999) |
|
471 |
#' tbl[2, ] <- list(rrow("FFF", 888, 666, 777)) |
|
472 |
#' tbl[6, ] <- list(-111, -222, -333) |
|
473 |
#' tbl |
|
474 |
#' |
|
475 |
#' # We can keep some information from the original table if we need |
|
476 |
#' tbl[1, 2, keep_titles = TRUE] |
|
477 |
#' tbl[1, 2, keep_footers = TRUE, keep_titles = FALSE] |
|
478 |
#' tbl[1, 2, keep_footers = FALSE, keep_titles = TRUE] |
|
479 |
#' tbl[1, 2, keep_footers = TRUE] |
|
480 |
#' tbl[1, 2, keep_topleft = TRUE] |
|
481 |
#' |
|
482 |
#' # Keeps the referential footnotes when subset contains them |
|
483 |
#' fnotes_at_path(tbl, rowpath = c("SEX", "M", "AGE", "Mean")) <- "important" |
|
484 |
#' tbl[4, 1] |
|
485 |
#' tbl[2, 1] # None present |
|
486 |
#' |
|
487 |
#' # We can reindex referential footnotes, so that the new table does not depend |
|
488 |
#' # on the original one |
|
489 |
#' fnotes_at_path(tbl, rowpath = c("SEX", "U", "AGE", "Mean")) <- "important" |
|
490 |
#' tbl[, 1] # both present |
|
491 |
#' tbl[5:6, 1] # {1} because it has been indexed again |
|
492 |
#' tbl[5:6, 1, reindex_refs = FALSE] # {2} -> not reindexed |
|
493 |
#' |
|
494 |
#' # Note that order can not be changed with subsetting |
|
495 |
#' tbl[c(4, 3, 1), c(3, 1)] # It preserves order and wanted selection |
|
496 |
#' |
|
497 |
#' @name brackets |
|
498 |
NULL |
|
499 | ||
500 |
#' @exportMethod [<- |
|
501 |
#' @rdname brackets |
|
502 |
setMethod( |
|
503 |
"[<-", c("VTableTree", value = "list"), |
|
504 |
function(x, i, j, ..., value) { |
|
505 | 3x |
nr <- nrow(x) |
506 | 3x |
if (missing(i)) { |
507 | ! |
i <- seq_len(NROW(x)) |
508 | 3x |
} else if (is(i, "character")) { |
509 | ! |
i <- .path_to_pos(i, x) |
510 |
} else { |
|
511 | 3x |
i <- .j_to_posj(i, nr) |
512 |
} |
|
513 | ||
514 | 3x |
if (missing(j)) { |
515 | 1x |
j <- seq_along(col_exprs(col_info(x))) |
516 | 2x |
} else if (is(j, "character")) { |
517 | ! |
j <- .path_to_pos(j, x, cols = TRUE) |
518 |
} else { |
|
519 | 2x |
j <- .j_to_posj(j, ncol(x)) |
520 |
} |
|
521 | ||
522 | 3x |
if (length(i) > 1 && length(j) < ncol(x)) { |
523 | ! |
stop("cannot modify multiple rows in not all columns.") |
524 |
} |
|
525 | ||
526 | 3x |
if (are(value, "TableRow")) { |
527 | 1x |
value <- rep(value, length.out = length(i)) |
528 |
} else { |
|
529 | 2x |
value <- rep(value, length.out = length(i) * length(j)) |
530 |
} |
|
531 | ||
532 | 3x |
counter <- 0 |
533 |
## this has access to value, i, and j by scoping |
|
534 | 3x |
replace_rowsbynum <- function(x, i, valifnone = NULL) { |
535 | 16x |
maxi <- max(i) |
536 | 16x |
if (counter >= maxi) { |
537 | ! |
return(valifnone) |
538 |
} |
|
539 | ||
540 | 16x |
if (labelrow_visible(x)) { |
541 | 3x |
counter <<- counter + 1 |
542 | 3x |
if (counter %in% i) { |
543 | 1x |
nxtval <- value[[1]] |
544 | 1x |
if (is(nxtval, "LabelRow")) { |
545 | 1x |
tt_labelrow(x) <- nxtval |
546 |
} else { |
|
547 | ! |
stop( |
548 | ! |
"can't replace label with value of class", |
549 | ! |
class(nxtval) |
550 |
) |
|
551 |
} |
|
552 |
## we're done with this one move to |
|
553 |
## the next |
|
554 | 1x |
value <<- value[-1] |
555 |
} |
|
556 |
} |
|
557 | 16x |
if (is(x, "TableTree") && nrow(content_table(x)) > 0) { |
558 | 3x |
ctab <- content_table(x) |
559 | ||
560 | 3x |
content_table(x) <- replace_rowsbynum(ctab, i) |
561 |
} |
|
562 | 16x |
if (counter >= maxi) { # already done |
563 | 2x |
return(x) |
564 |
} |
|
565 | 14x |
kids <- tree_children(x) |
566 | ||
567 | 14x |
if (length(kids) > 0) { |
568 | 14x |
for (pos in seq_along(kids)) { |
569 | 17x |
curkid <- kids[[pos]] |
570 | 17x |
if (is(curkid, "TableRow")) { |
571 | 7x |
counter <<- counter + 1 |
572 | 7x |
if (counter %in% i) { |
573 | 3x |
nxtval <- value[[1]] |
574 | 3x |
if (is(nxtval, class(curkid))) { |
575 | 1x |
if (no_colinfo(nxtval) && length(row_values(nxtval)) == ncol(x)) { |
576 | 1x |
col_info(nxtval) <- col_info(x) |
577 |
} |
|
578 | 1x |
stopifnot(identical(col_info(x), col_info(nxtval))) |
579 | 1x |
curkid <- nxtval |
580 | 1x |
value <- value[-1] |
581 |
} else { |
|
582 | 2x |
rvs <- row_values(curkid) |
583 | 2x |
rvs[j] <- value[seq_along(j)] |
584 | 2x |
row_values(curkid) <- rvs |
585 | 2x |
value <- value[-(seq_along(j))] |
586 |
} |
|
587 | 3x |
kids[[pos]] <- curkid |
588 |
} |
|
589 |
} else { |
|
590 | 10x |
kids[[pos]] <- replace_rowsbynum(curkid, i) |
591 |
} |
|
592 | 17x |
if (counter >= maxi) { |
593 | 7x |
break |
594 |
} |
|
595 |
} |
|
596 |
} |
|
597 | 14x |
tree_children(x) <- kids |
598 | 14x |
x |
599 |
} |
|
600 | 3x |
replace_rowsbynum(x, i, ...) |
601 |
} |
|
602 |
) |
|
603 | ||
604 |
#' @inheritParams brackets |
|
605 |
#' |
|
606 |
#' @exportMethod [<- |
|
607 |
#' @rdname int_methods |
|
608 |
#' @keywords internal |
|
609 |
setMethod( |
|
610 |
"[<-", c("VTableTree", value = "CellValue"), |
|
611 |
function(x, i, j, ..., value) { |
|
612 | 1x |
x[i = i, j = j, ...] <- list(value) |
613 | 1x |
x |
614 |
} |
|
615 |
) |
|
616 | ||
617 |
## this is going to be hard :( :( :( |
|
618 | ||
619 |
### selecting/removing columns |
|
620 | ||
621 |
## we have two options here: path like we do with rows and positional |
|
622 |
## in leaf space. |
|
623 | ||
624 |
setGeneric( |
|
625 |
"subset_cols", |
|
626 |
function(tt, |
|
627 |
j, |
|
628 |
newcinfo = NULL, |
|
629 |
keep_topleft = TRUE, |
|
630 |
keep_titles = TRUE, |
|
631 |
keep_footers = keep_titles, |
|
632 |
...) { |
|
633 | 9970x |
standardGeneric("subset_cols") |
634 |
} |
|
635 |
) |
|
636 | ||
637 |
setMethod( |
|
638 |
"subset_cols", c("TableTree", "numeric"), |
|
639 |
function(tt, j, newcinfo = NULL, |
|
640 |
keep_topleft, keep_titles, keep_footers, ...) { |
|
641 | 867x |
j <- .j_to_posj(j, ncol(tt)) |
642 | 867x |
if (is.null(newcinfo)) { |
643 | 161x |
cinfo <- col_info(tt) |
644 | 161x |
newcinfo <- subset_cols(cinfo, j, |
645 | 161x |
keep_topleft = keep_topleft, ... |
646 |
) |
|
647 |
} |
|
648 |
## topleft taken care of in creation of newcinfo |
|
649 | 867x |
kids <- tree_children(tt) |
650 | 867x |
newkids <- lapply(kids, subset_cols, j = j, newcinfo = newcinfo, ...) |
651 | 867x |
cont <- content_table(tt) |
652 | 867x |
newcont <- subset_cols(cont, j, newcinfo = newcinfo, ...) |
653 | 867x |
tt2 <- tt |
654 | 867x |
col_info(tt2) <- newcinfo |
655 | 867x |
content_table(tt2) <- newcont |
656 | 867x |
tree_children(tt2) <- newkids |
657 | 867x |
tt_labelrow(tt2) <- subset_cols(tt_labelrow(tt2), j, newcinfo, ...) |
658 | ||
659 | 867x |
tt2 <- .h_copy_titles_footers_topleft( |
660 | 867x |
tt2, tt, |
661 | 867x |
keep_titles, |
662 | 867x |
keep_footers, |
663 | 867x |
keep_topleft |
664 |
) |
|
665 | 867x |
tt2 |
666 |
} |
|
667 |
) |
|
668 | ||
669 |
setMethod( |
|
670 |
"subset_cols", c("ElementaryTable", "numeric"), |
|
671 |
function(tt, j, newcinfo = NULL, |
|
672 |
keep_topleft, keep_titles, keep_footers, ...) { |
|
673 | 1829x |
j <- .j_to_posj(j, ncol(tt)) |
674 | 1829x |
if (is.null(newcinfo)) { |
675 | 97x |
cinfo <- col_info(tt) |
676 | 97x |
newcinfo <- subset_cols(cinfo, j, |
677 | 97x |
keep_topleft = keep_topleft, |
678 | 97x |
keep_titles = keep_titles, |
679 | 97x |
keep_footers = keep_footers, ... |
680 |
) |
|
681 |
} |
|
682 |
## topleft handled in creation of newcinfo |
|
683 | 1829x |
kids <- tree_children(tt) |
684 | 1829x |
newkids <- lapply(kids, subset_cols, j = j, newcinfo = newcinfo, ...) |
685 | 1829x |
tt2 <- tt |
686 | 1829x |
col_info(tt2) <- newcinfo |
687 | 1829x |
tree_children(tt2) <- newkids |
688 | 1829x |
tt_labelrow(tt2) <- subset_cols(tt_labelrow(tt2), j, newcinfo, ...) |
689 | 1829x |
tt2 <- .h_copy_titles_footers_topleft( |
690 | 1829x |
tt2, tt, |
691 | 1829x |
keep_titles, |
692 | 1829x |
keep_footers, |
693 | 1829x |
keep_topleft |
694 |
) |
|
695 | 1829x |
tt2 |
696 |
} |
|
697 |
) |
|
698 | ||
699 |
## small utility to transform any negative |
|
700 |
## indices into positive ones, given j |
|
701 |
## and total length |
|
702 | ||
703 |
.j_to_posj <- function(j, n) { |
|
704 |
## This will work for logicals, numerics, integers |
|
705 | 15052x |
j <- seq_len(n)[j] |
706 | 15052x |
j |
707 |
} |
|
708 | ||
709 |
path_collapse_sep <- "`" |
|
710 |
escape_name_padding <- function(x) { |
|
711 | 145x |
ret <- gsub("._[[", "\\._\\[\\[", x, fixed = TRUE) |
712 | 145x |
ret <- gsub("]]_.", "\\]\\]_\\.", ret, fixed = TRUE) |
713 | 145x |
ret |
714 |
} |
|
715 |
path_to_regex <- function(path) { |
|
716 | 52x |
paste(vapply(path, function(x) { |
717 | 146x |
if (identical(x, "*")) { |
718 | 1x |
paste0("[^", path_collapse_sep, "]+") |
719 |
} else { |
|
720 | 145x |
escape_name_padding(x) |
721 |
} |
|
722 | 52x |
}, ""), collapse = path_collapse_sep) |
723 |
} |
|
724 | ||
725 |
.path_to_pos <- function(path, tt, distinct_ok = TRUE, cols = FALSE) { |
|
726 | 52x |
path <- path[!grepl("^(|root)$", path)] |
727 | 52x |
if (cols) { |
728 | 52x |
rowdf <- make_col_df(tt) |
729 |
} else { |
|
730 | ! |
rowdf <- make_row_df(tt) |
731 |
} |
|
732 | 52x |
if (length(path) == 0 || identical(path, "*") || identical(path, "root")) { |
733 | ! |
return(seq(1, nrow(rowdf))) |
734 |
} |
|
735 | ||
736 | 52x |
paths <- rowdf$path |
737 | 52x |
pathregex <- path_to_regex(path) |
738 | 52x |
pathstrs <- vapply(paths, paste, "", collapse = path_collapse_sep) |
739 | 52x |
allmatchs <- grep(pathregex, pathstrs) |
740 | 52x |
if (length(allmatchs) == 0) { |
741 | ! |
stop( |
742 | ! |
if (cols) "column path [" else "row path [", |
743 | ! |
paste(path, collapse = "->"), |
744 | ! |
"] does not appear valid for this table" |
745 |
) |
|
746 |
} |
|
747 | ||
748 | 52x |
idxdiffs <- diff(allmatchs) |
749 | 52x |
if (!distinct_ok && length(idxdiffs) > 0 && any(idxdiffs > 1)) { |
750 | ! |
firstnon <- min(which(idxdiffs > 1)) |
751 |
## its firstnon here because we would want firstnon-1 but |
|
752 |
## the diffs are actually shifted 1 so they cancel out |
|
753 | ! |
allmatchs <- allmatchs[seq(1, firstnon)] |
754 |
} |
|
755 | 52x |
allmatchs |
756 |
} |
|
757 | ||
758 |
## fix column spans that would be invalid |
|
759 |
## after some columns are no longer there |
|
760 |
.fix_rowcspans <- function(rw, j) { |
|
761 | 3974x |
cspans <- row_cspans(rw) |
762 | 3974x |
nc <- sum(cspans) |
763 | 3974x |
j <- .j_to_posj(j, nc) |
764 |
## this is overly complicated |
|
765 |
## we need the starting indices |
|
766 |
## but the first span might not be 1, so |
|
767 |
## we pad with 1 and then take off the last |
|
768 | 3974x |
start <- cumsum(c(1, head(cspans, -1))) |
769 | 3974x |
ends <- c(tail(start, -1) - 1, nc) |
770 | 3974x |
res <- mapply(function(st, en) { |
771 | 22905x |
sum(j >= st & j <= en) |
772 | 3974x |
}, st = start, en = ends) |
773 | 3974x |
res <- res[res > 0] |
774 | 3974x |
stopifnot(sum(res) == length(j)) |
775 | 3974x |
res |
776 |
} |
|
777 | ||
778 |
select_cells_j <- function(cells, j) { |
|
779 | 3974x |
if (length(j) != length(unique(j))) { |
780 | ! |
stop("duplicate column selections is not currently supported") |
781 |
} |
|
782 | 3974x |
spans <- vapply( |
783 | 3974x |
cells, function(x) cell_cspan(x), |
784 | 3974x |
integer(1) |
785 |
) |
|
786 | 3974x |
inds <- rep(seq_along(cells), times = spans) |
787 | 3974x |
selinds <- inds[j] |
788 | 3974x |
retcells <- cells[selinds[!duplicated(selinds)]] |
789 | 3974x |
newspans <- vapply( |
790 | 3974x |
split(selinds, selinds), |
791 | 3974x |
length, |
792 | 3974x |
integer(1) |
793 |
) |
|
794 | ||
795 | 3974x |
mapply(function(cl, sp) { |
796 | 6891x |
cell_cspan(cl) <- sp |
797 | 6891x |
cl |
798 | 3974x |
}, cl = retcells, sp = newspans, SIMPLIFY = FALSE) |
799 |
} |
|
800 | ||
801 |
setMethod( |
|
802 |
"subset_cols", c("ANY", "character"), |
|
803 |
function(tt, j, newcinfo = NULL, keep_topleft = TRUE, ...) { |
|
804 | 42x |
j <- .path_to_pos(path = j, tt = tt, cols = TRUE) |
805 | 42x |
subset_cols(tt, j, newcinfo = newcinfo, keep_topleft = keep_topleft, ...) |
806 |
} |
|
807 |
) |
|
808 | ||
809 |
setMethod( |
|
810 |
"subset_cols", c("TableRow", "numeric"), |
|
811 |
function(tt, j, newcinfo = NULL, keep_topleft = TRUE, ...) { |
|
812 | 3974x |
j <- .j_to_posj(j, ncol(tt)) |
813 | 3974x |
if (is.null(newcinfo)) { |
814 | 16x |
cinfo <- col_info(tt) |
815 | 16x |
newcinfo <- subset_cols(cinfo, j, keep_topleft = keep_topleft, ...) |
816 |
} |
|
817 | 3974x |
tt2 <- tt |
818 | 3974x |
row_cells(tt2) <- select_cells_j(row_cells(tt2), j) |
819 | ||
820 | 3974x |
if (length(row_cspans(tt2)) > 0) { |
821 | 3974x |
row_cspans(tt2) <- .fix_rowcspans(tt2, j) |
822 |
} |
|
823 | 3974x |
col_info(tt2) <- newcinfo |
824 | 3974x |
tt2 |
825 |
} |
|
826 |
) |
|
827 | ||
828 |
setMethod( |
|
829 |
"subset_cols", c("LabelRow", "numeric"), |
|
830 |
function(tt, j, newcinfo = NULL, keep_topleft = TRUE, ...) { |
|
831 | 2702x |
j <- .j_to_posj(j, ncol(tt)) |
832 | 2702x |
if (is.null(newcinfo)) { |
833 | ! |
cinfo <- col_info(tt) |
834 | ! |
newcinfo <- subset_cols(cinfo, j, keep_topleft = keep_topleft, ...) |
835 |
} |
|
836 | 2702x |
col_info(tt) <- newcinfo |
837 | 2702x |
tt |
838 |
} |
|
839 |
) |
|
840 | ||
841 |
setMethod( |
|
842 |
"subset_cols", c("InstantiatedColumnInfo", "numeric"), |
|
843 |
function(tt, j, newcinfo = NULL, keep_topleft = TRUE, ...) { |
|
844 | 278x |
if (!is.null(newcinfo)) { |
845 | ! |
return(newcinfo) |
846 |
} |
|
847 | 278x |
j <- .j_to_posj(j, length(col_exprs(tt))) |
848 | 278x |
newctree <- subset_cols(coltree(tt), j, NULL) |
849 | 278x |
newcextra <- col_extra_args(tt)[j] |
850 | 278x |
newcsubs <- col_exprs(tt)[j] |
851 | 278x |
newcounts <- col_counts(tt)[j] |
852 | 278x |
tl <- if (keep_topleft) top_left(tt) else character() |
853 | 278x |
InstantiatedColumnInfo( |
854 | 278x |
treelyt = newctree, |
855 | 278x |
csubs = newcsubs, |
856 | 278x |
extras = newcextra, |
857 | 278x |
cnts = newcounts, |
858 | 278x |
dispcounts = disp_ccounts(tt), |
859 | 278x |
countformat = colcount_format(tt), |
860 | 278x |
topleft = tl |
861 |
) |
|
862 |
} |
|
863 |
) |
|
864 | ||
865 |
setMethod( |
|
866 |
"subset_cols", c("LayoutColTree", "numeric"), |
|
867 |
function(tt, j, newcinfo = NULL, ...) { |
|
868 | 278x |
lst <- collect_leaves(tt) |
869 | 278x |
j <- .j_to_posj(j, length(lst)) |
870 | ||
871 |
## j has only non-negative values from |
|
872 |
## this point on |
|
873 | 278x |
counter <- 0 |
874 | 278x |
prune_children <- function(x, j) { |
875 | 674x |
kids <- tree_children(x) |
876 | 674x |
newkids <- kids |
877 | 674x |
for (i in seq_along(newkids)) { |
878 | 1813x |
if (is(newkids[[i]], "LayoutColLeaf")) { |
879 | 1417x |
counter <<- counter + 1 |
880 | 1417x |
if (!(counter %in% j)) { |
881 | 1013x |
newkids[[i]] <- list() |
882 | 278x |
} ## NULL removes the position entirely |
883 |
} else { |
|
884 | 396x |
newkids[[i]] <- prune_children(newkids[[i]], j) |
885 |
} |
|
886 |
} |
|
887 | ||
888 | 674x |
newkids <- newkids[sapply(newkids, function(thing) length(thing) > 0)] |
889 | 674x |
if (length(newkids) > 0) { |
890 | 474x |
tree_children(x) <- newkids |
891 | 474x |
x |
892 |
} else { |
|
893 | 200x |
list() |
894 |
} |
|
895 |
} |
|
896 | 278x |
prune_children(tt, j) |
897 |
} |
|
898 |
) |
|
899 | ||
900 |
## label rows ARE included in the count |
|
901 |
subset_by_rownum <- function(tt, |
|
902 |
i, |
|
903 |
keep_topleft = FALSE, |
|
904 |
keep_titles = TRUE, |
|
905 |
keep_footers = keep_titles, |
|
906 |
...) { |
|
907 | 184x |
stopifnot(is(tt, "VTableNodeInfo")) |
908 | 184x |
counter <- 0 |
909 | 184x |
nr <- nrow(tt) |
910 | 184x |
i <- .j_to_posj(i, nr) |
911 | 184x |
if (length(i) == 0) { |
912 | 3x |
ret <- TableTree(cinfo = col_info(tt)) |
913 | 3x |
if (isTRUE(keep_topleft)) { |
914 | 1x |
top_left(ret) <- top_left(tt) |
915 |
} |
|
916 | 3x |
return(ret) |
917 |
} |
|
918 | ||
919 | 181x |
prune_rowsbynum <- function(x, i, valifnone = NULL) { |
920 | 1321x |
maxi <- max(i) |
921 | 1321x |
if (counter > maxi) { |
922 | 137x |
return(valifnone) |
923 |
} |
|
924 | ||
925 | 1184x |
if (labelrow_visible(x)) { |
926 | 489x |
counter <<- counter + 1 |
927 | 489x |
if (!(counter %in% i)) { |
928 |
## XXX this should do whatever |
|
929 |
## is required to 'remove' the Label Row |
|
930 |
## (currently implicit based on |
|
931 |
## the value of the label but |
|
932 |
## that shold really probably change) |
|
933 | 177x |
labelrow_visible(x) <- FALSE |
934 |
} |
|
935 |
} |
|
936 | 1184x |
if (is(x, "TableTree") && nrow(content_table(x)) > 0) { |
937 | 90x |
ctab <- content_table(x) |
938 | ||
939 | 90x |
content_table(x) <- prune_rowsbynum(ctab, i, |
940 | 90x |
valifnone = ElementaryTable( |
941 | 90x |
cinfo = col_info(ctab), |
942 | 90x |
iscontent = TRUE |
943 |
) |
|
944 |
) |
|
945 |
} |
|
946 | 1184x |
kids <- tree_children(x) |
947 | 1184x |
if (counter > maxi) { # already done |
948 | 49x |
kids <- list() |
949 | 1135x |
} else if (length(kids) > 0) { |
950 | 1133x |
for (pos in seq_along(kids)) { |
951 | 4102x |
if (is(kids[[pos]], "TableRow")) { |
952 | 3052x |
counter <<- counter + 1 |
953 | 3052x |
if (!(counter %in% i)) { |
954 | 2144x |
kids[[pos]] <- list() |
955 |
} |
|
956 |
} else { |
|
957 | 1050x |
kids[[pos]] <- prune_rowsbynum(kids[[pos]], i, list()) |
958 |
} |
|
959 |
} |
|
960 | 1133x |
kids <- kids[sapply(kids, function(x) NROW(x) > 0)] |
961 |
} |
|
962 | 1184x |
if (length(kids) == 0 && NROW(content_table(x)) == 0 && !labelrow_visible(x)) { |
963 | 359x |
return(valifnone) |
964 |
} else { |
|
965 | 825x |
tree_children(x) <- kids |
966 | 825x |
x |
967 |
} |
|
968 |
## ## if(length(kids) == 0) { |
|
969 |
## ## if(!is(x, "TableTree")) |
|
970 |
## ## return(valifnone) |
|
971 |
## ## } |
|
972 |
## if(is(x, "VTableTree") && nrow(x) > 0) { |
|
973 |
## x |
|
974 |
## } else { |
|
975 |
## valifnone |
|
976 |
## } |
|
977 |
} |
|
978 | 181x |
ret <- prune_rowsbynum(tt, i) |
979 | ||
980 | 181x |
ret <- .h_copy_titles_footers_topleft( |
981 | 181x |
ret, tt, |
982 | 181x |
keep_titles, |
983 | 181x |
keep_footers, |
984 | 181x |
keep_topleft |
985 |
) |
|
986 | ||
987 | 181x |
ret |
988 |
} |
|
989 | ||
990 |
#' @exportMethod [ |
|
991 |
#' @rdname brackets |
|
992 |
setMethod( |
|
993 |
"[", c("VTableTree", "logical", "logical"), |
|
994 |
function(x, i, j, ..., drop = FALSE) { |
|
995 | 1x |
i <- .j_to_posj(i, nrow(x)) |
996 | 1x |
j <- .j_to_posj(j, ncol(x)) |
997 | 1x |
x[i, j, ..., drop = drop] |
998 |
} |
|
999 |
) |
|
1000 | ||
1001 |
#' @exportMethod [ |
|
1002 |
#' @rdname int_methods |
|
1003 |
#' @keywords internal |
|
1004 |
setMethod( |
|
1005 |
"[", c("VTableTree", "logical", "ANY"), |
|
1006 |
function(x, i, j, ..., drop = FALSE) { |
|
1007 | ! |
i <- .j_to_posj(i, nrow(x)) |
1008 | ! |
x[i, j, ..., drop = drop] |
1009 |
} |
|
1010 |
) |
|
1011 | ||
1012 |
#' @exportMethod [ |
|
1013 |
#' @rdname int_methods |
|
1014 |
#' @keywords internal |
|
1015 |
setMethod( |
|
1016 |
"[", c("VTableTree", "logical", "missing"), |
|
1017 |
function(x, i, j, ..., drop = FALSE) { |
|
1018 | 4x |
j <- seq_len(ncol(x)) |
1019 | 4x |
i <- .j_to_posj(i, nrow(x)) |
1020 | 4x |
x[i, j, ..., drop = drop] |
1021 |
} |
|
1022 |
) |
|
1023 | ||
1024 |
#' @exportMethod [ |
|
1025 |
#' @rdname int_methods |
|
1026 |
#' @keywords internal |
|
1027 |
setMethod( |
|
1028 |
"[", c("VTableTree", "ANY", "logical"), |
|
1029 |
function(x, i, j, ..., drop = FALSE) { |
|
1030 | 1x |
j <- .j_to_posj(j, ncol(x)) |
1031 | 1x |
x[i, j, ..., drop = drop] |
1032 |
} |
|
1033 |
) |
|
1034 | ||
1035 |
#' @exportMethod [ |
|
1036 |
#' @rdname int_methods |
|
1037 |
#' @keywords internal |
|
1038 |
setMethod( |
|
1039 |
"[", c("VTableTree", "ANY", "missing"), |
|
1040 |
function(x, i, j, ..., drop = FALSE) { |
|
1041 | 149x |
j <- seq_len(ncol(x)) |
1042 | 149x |
x[i = i, j = j, ..., drop = drop] |
1043 |
} |
|
1044 |
) |
|
1045 | ||
1046 |
#' @exportMethod [ |
|
1047 |
#' @rdname int_methods |
|
1048 |
#' @keywords internal |
|
1049 |
setMethod( |
|
1050 |
"[", c("VTableTree", "missing", "ANY"), |
|
1051 |
function(x, i, j, ..., drop = FALSE) { |
|
1052 | 4x |
i <- seq_len(nrow(x)) |
1053 | 4x |
x[i = i, j = j, ..., drop = drop] |
1054 |
} |
|
1055 |
) |
|
1056 | ||
1057 |
#' @exportMethod [ |
|
1058 |
#' @rdname int_methods |
|
1059 |
#' @keywords internal |
|
1060 |
setMethod( |
|
1061 |
"[", c("VTableTree", "ANY", "character"), |
|
1062 |
function(x, i, j, ..., drop = FALSE) { |
|
1063 |
## j <- .colpath_to_j(j, coltree(x)) |
|
1064 | 3x |
j <- .path_to_pos(path = j, tt = x, cols = TRUE) |
1065 | 3x |
x[i = i, j = j, ..., drop = drop] |
1066 |
} |
|
1067 |
) |
|
1068 | ||
1069 |
#' @exportMethod [ |
|
1070 |
#' @rdname int_methods |
|
1071 |
#' @keywords internal |
|
1072 |
setMethod( |
|
1073 |
"[", c("VTableTree", "character", "ANY"), |
|
1074 |
function(x, i, j, ..., drop = FALSE) { |
|
1075 |
## i <- .path_to_pos(i, seq_len(nrow(x)), x, NROW) |
|
1076 | ! |
i <- .path_to_pos(i, x) |
1077 | ! |
x[i = i, j = j, ..., drop = drop] |
1078 |
} |
|
1079 |
) |
|
1080 | ||
1081 |
## to avoid dispatch ambiguity. Not necessary, possibly not a good idea at all |
|
1082 |
#' @exportMethod [ |
|
1083 |
#' @rdname int_methods |
|
1084 |
#' @keywords internal |
|
1085 |
setMethod( |
|
1086 |
"[", c("VTableTree", "character", "character"), |
|
1087 |
function(x, i, j, ..., drop = FALSE) { |
|
1088 |
## i <- .path_to_pos(i, seq_len(nrow(x)), x, NROW) |
|
1089 | ! |
i <- .path_to_pos(i, x) |
1090 |
## j <- .colpath_to_j(j, coltree(x)) |
|
1091 | ! |
j <- .path_to_pos(path = j, tt = x, cols = TRUE) |
1092 | ! |
x[i = i, j = j, ..., drop = drop] |
1093 |
} |
|
1094 |
) |
|
1095 | ||
1096 |
#' @exportMethod [ |
|
1097 |
#' @rdname int_methods |
|
1098 |
#' @keywords internal |
|
1099 |
setMethod( |
|
1100 |
"[", c("VTableTree", "missing", "numeric"), |
|
1101 |
function(x, i, j, ..., drop = FALSE) { |
|
1102 | 241x |
i <- seq_len(nrow(x)) |
1103 | 241x |
x[i, j, ..., drop = drop] |
1104 |
} |
|
1105 |
) |
|
1106 | ||
1107 |
#' @exportMethod [ |
|
1108 |
#' @rdname int_methods |
|
1109 |
#' @keywords internal |
|
1110 |
setMethod( |
|
1111 |
"[", c("VTableTree", "numeric", "numeric"), |
|
1112 |
function(x, i, j, ..., drop = FALSE) { |
|
1113 |
## have to do it this way because we can't add an argument since we don't |
|
1114 |
## own the generic declaration |
|
1115 | 477x |
keep_topleft <- list(...)[["keep_topleft"]] %||% NA |
1116 | 477x |
keep_titles <- list(...)[["keep_titles"]] %||% FALSE |
1117 | 477x |
keep_footers <- list(...)[["keep_footers"]] %||% keep_titles |
1118 | 477x |
reindex_refs <- list(...)[["reindex_refs"]] %||% TRUE |
1119 | ||
1120 | 477x |
nr <- nrow(x) |
1121 | 477x |
nc <- ncol(x) |
1122 | 477x |
i <- .j_to_posj(i, nr) |
1123 | 477x |
j <- .j_to_posj(j, nc) |
1124 | ||
1125 |
## if(!missing(i) && length(i) < nr) { |
|
1126 | 477x |
if (length(i) < nr) { ## already populated by .j_to_posj |
1127 | 184x |
keep_topleft <- isTRUE(keep_topleft) |
1128 | 184x |
x <- subset_by_rownum(x, i, |
1129 | 184x |
keep_topleft = keep_topleft, |
1130 | 184x |
keep_titles = keep_titles, |
1131 | 184x |
keep_footers = keep_footers |
1132 |
) |
|
1133 | 293x |
} else if (is.na(keep_topleft)) { |
1134 | 49x |
keep_topleft <- TRUE |
1135 |
} |
|
1136 | ||
1137 |
## if(!missing(j) && length(j) < nc) |
|
1138 | 477x |
if (length(j) < nc) { |
1139 | 232x |
x <- subset_cols(x, j, |
1140 | 232x |
keep_topleft = keep_topleft, |
1141 | 232x |
keep_titles = keep_titles, |
1142 | 232x |
keep_footers = keep_footers |
1143 |
) |
|
1144 |
} |
|
1145 | ||
1146 |
# Dropping everything |
|
1147 | 477x |
if (drop) { |
1148 | 35x |
if (length(j) == 1L && length(i) == 1L) { |
1149 | 30x |
rw <- collect_leaves(x, TRUE, TRUE)[[1]] |
1150 | 30x |
if (is(rw, "LabelRow")) { |
1151 | 2x |
warning( |
1152 | 2x |
"The value selected with drop = TRUE belongs ", |
1153 | 2x |
"to a label row. NULL will be returned" |
1154 |
) |
|
1155 | 2x |
x <- NULL |
1156 |
} else { |
|
1157 | 28x |
x <- row_values(rw)[[1]] |
1158 |
} |
|
1159 |
} else { |
|
1160 | 5x |
warning( |
1161 | 5x |
"Trying to drop more than one subsetted value. ", |
1162 | 5x |
"We support this only with accessor function `cell_values()`. ", |
1163 | 5x |
"No drop will be done at this time." |
1164 |
) |
|
1165 | 5x |
drop <- FALSE |
1166 |
} |
|
1167 |
} |
|
1168 | 477x |
if (!drop) { |
1169 | 447x |
if (!keep_topleft) { |
1170 | 61x |
top_left(x) <- character() |
1171 |
} |
|
1172 | 447x |
if (reindex_refs) { |
1173 | 105x |
x <- update_ref_indexing(x) |
1174 |
} |
|
1175 |
} |
|
1176 | 477x |
x |
1177 |
} |
|
1178 |
) |
|
1179 | ||
1180 |
#' @importFrom utils compareVersion |
|
1181 | ||
1182 |
setGeneric("tail", tail) |
|
1183 | ||
1184 |
setMethod( |
|
1185 |
"tail", "VTableTree", |
|
1186 |
function(x, n = 6L, ...) { |
|
1187 |
if (compareVersion("4.0.0", as.character(getRversion())) <= 0) { |
|
1188 |
tail.matrix(x, n, keepnums = FALSE) |
|
1189 |
} else { |
|
1190 |
tail.matrix(x, n, addrownums = FALSE) |
|
1191 |
} |
|
1192 |
} |
|
1193 |
) |
|
1194 | ||
1195 |
setGeneric("head", head) |
|
1196 | ||
1197 |
setMethod( |
|
1198 |
"head", "VTableTree", |
|
1199 |
function(x, n = 6L, ...) { |
|
1200 |
head.matrix(x, n) |
|
1201 |
} |
|
1202 |
) |
|
1203 | ||
1204 |
#' Retrieve cell values by row and column path |
|
1205 |
#' |
|
1206 |
#' @inheritParams gen_args |
|
1207 |
#' @param rowpath (`character`)\cr path in row-split space to the desired row(s). Can include `"@content"`. |
|
1208 |
#' @param colpath (`character`)\cr path in column-split space to the desired column(s). Can include `"*"`. |
|
1209 |
#' @param omit_labrows (`flag`)\cr whether label rows underneath `rowpath` should be omitted (`TRUE`, the default), |
|
1210 |
#' or return empty lists of cell "values" (`FALSE`). |
|
1211 |
#' |
|
1212 |
#' @return |
|
1213 |
#' * `cell_values` returns a `list` (regardless of the type of value the cells hold). If `rowpath` defines a path to |
|
1214 |
#' a single row, `cell_values` returns the list of cell values for that row, otherwise a list of such lists, one for |
|
1215 |
#' each row captured underneath `rowpath`. This occurs after subsetting to `colpath` has occurred. |
|
1216 |
#' * `value_at` returns the "unwrapped" value of a single cell, or an error, if the combination of `rowpath` and |
|
1217 |
#' `colpath` do not define the location of a single cell in `tt`. |
|
1218 |
#' |
|
1219 |
#' @note `cell_values` will return a single cell's value wrapped in a list. Use `value_at` to receive the "bare" cell |
|
1220 |
#' value. |
|
1221 |
#' |
|
1222 |
#' @examples |
|
1223 |
#' lyt <- basic_table() %>% |
|
1224 |
#' split_cols_by("ARM") %>% |
|
1225 |
#' split_cols_by("SEX") %>% |
|
1226 |
#' split_rows_by("RACE") %>% |
|
1227 |
#' summarize_row_groups() %>% |
|
1228 |
#' split_rows_by("STRATA1") %>% |
|
1229 |
#' analyze("AGE") |
|
1230 |
#' |
|
1231 |
#' @examplesIf require(dplyr) |
|
1232 |
#' library(dplyr) ## for mutate |
|
1233 |
#' tbl <- build_table(lyt, DM %>% |
|
1234 |
#' mutate(SEX = droplevels(SEX), RACE = droplevels(RACE))) |
|
1235 |
#' |
|
1236 |
#' row_paths_summary(tbl) |
|
1237 |
#' col_paths_summary(tbl) |
|
1238 |
#' |
|
1239 |
#' cell_values( |
|
1240 |
#' tbl, c("RACE", "ASIAN", "STRATA1", "B"), |
|
1241 |
#' c("ARM", "A: Drug X", "SEX", "F") |
|
1242 |
#' ) |
|
1243 |
#' |
|
1244 |
#' # it's also possible to access multiple values by being less specific |
|
1245 |
#' cell_values( |
|
1246 |
#' tbl, c("RACE", "ASIAN", "STRATA1"), |
|
1247 |
#' c("ARM", "A: Drug X", "SEX", "F") |
|
1248 |
#' ) |
|
1249 |
#' cell_values(tbl, c("RACE", "ASIAN"), c("ARM", "A: Drug X", "SEX", "M")) |
|
1250 |
#' |
|
1251 |
#' ## any arm, male columns from the ASIAN content (i.e. summary) row |
|
1252 |
#' cell_values( |
|
1253 |
#' tbl, c("RACE", "ASIAN", "@content"), |
|
1254 |
#' c("ARM", "B: Placebo", "SEX", "M") |
|
1255 |
#' ) |
|
1256 |
#' cell_values( |
|
1257 |
#' tbl, c("RACE", "ASIAN", "@content"), |
|
1258 |
#' c("ARM", "*", "SEX", "M") |
|
1259 |
#' ) |
|
1260 |
#' |
|
1261 |
#' ## all columns |
|
1262 |
#' cell_values(tbl, c("RACE", "ASIAN", "STRATA1", "B")) |
|
1263 |
#' |
|
1264 |
#' ## all columns for the Combination arm |
|
1265 |
#' cell_values( |
|
1266 |
#' tbl, c("RACE", "ASIAN", "STRATA1", "B"), |
|
1267 |
#' c("ARM", "C: Combination") |
|
1268 |
#' ) |
|
1269 |
#' |
|
1270 |
#' cvlist <- cell_values( |
|
1271 |
#' tbl, c("RACE", "ASIAN", "STRATA1", "B", "AGE", "Mean"), |
|
1272 |
#' c("ARM", "B: Placebo", "SEX", "M") |
|
1273 |
#' ) |
|
1274 |
#' cvnolist <- value_at( |
|
1275 |
#' tbl, c("RACE", "ASIAN", "STRATA1", "B", "AGE", "Mean"), |
|
1276 |
#' c("ARM", "B: Placebo", "SEX", "M") |
|
1277 |
#' ) |
|
1278 |
#' stopifnot(identical(cvlist[[1]], cvnolist)) |
|
1279 |
#' |
|
1280 |
#' @rdname cell_values |
|
1281 |
#' @export |
|
1282 |
setGeneric("cell_values", function(tt, rowpath = NULL, colpath = NULL, omit_labrows = TRUE) { |
|
1283 | 163x |
standardGeneric("cell_values") |
1284 |
}) |
|
1285 | ||
1286 |
#' @rdname int_methods |
|
1287 |
#' @keywords internal |
|
1288 |
#' @exportMethod cell_values |
|
1289 |
setMethod( |
|
1290 |
"cell_values", "VTableTree", |
|
1291 |
function(tt, rowpath, colpath = NULL, omit_labrows = TRUE) { |
|
1292 | 160x |
.inner_cell_value(tt, |
1293 | 160x |
rowpath = rowpath, colpath = colpath, |
1294 | 160x |
omit_labrows = omit_labrows, value_at = FALSE |
1295 |
) |
|
1296 |
} |
|
1297 |
) |
|
1298 | ||
1299 |
#' @rdname int_methods |
|
1300 |
#' @keywords internal |
|
1301 |
#' @exportMethod cell_values |
|
1302 |
setMethod( |
|
1303 |
"cell_values", "TableRow", |
|
1304 |
function(tt, rowpath, colpath = NULL, omit_labrows = TRUE) { |
|
1305 | 2x |
if (!is.null(rowpath)) { |
1306 | 1x |
stop("cell_values on TableRow objects must have NULL rowpath") |
1307 |
} |
|
1308 | 1x |
.inner_cell_value(tt, |
1309 | 1x |
rowpath = rowpath, colpath = colpath, |
1310 | 1x |
omit_labrows = omit_labrows, value_at = FALSE |
1311 |
) |
|
1312 |
} |
|
1313 |
) |
|
1314 | ||
1315 |
#' @rdname int_methods |
|
1316 |
#' @keywords internal |
|
1317 |
#' @exportMethod cell_values |
|
1318 |
setMethod( |
|
1319 |
"cell_values", "LabelRow", |
|
1320 |
function(tt, rowpath, colpath = NULL, omit_labrows = TRUE) { |
|
1321 | 1x |
stop("calling cell_values on LabelRow is not meaningful") |
1322 |
} |
|
1323 |
) |
|
1324 | ||
1325 |
#' @rdname cell_values |
|
1326 |
#' @export |
|
1327 |
setGeneric("value_at", function(tt, rowpath = NULL, colpath = NULL) { |
|
1328 | 8x |
standardGeneric("value_at") |
1329 |
}) |
|
1330 | ||
1331 |
#' @rdname cell_values |
|
1332 |
#' @exportMethod value_at |
|
1333 |
setMethod( |
|
1334 |
"value_at", "VTableTree", |
|
1335 |
function(tt, rowpath, colpath = NULL) { |
|
1336 | 7x |
.inner_cell_value(tt, |
1337 | 7x |
rowpath = rowpath, colpath = colpath, |
1338 | 7x |
omit_labrows = FALSE, value_at = TRUE |
1339 |
) |
|
1340 |
} |
|
1341 |
) |
|
1342 | ||
1343 |
#' @rdname int_methods |
|
1344 |
#' @keywords internal |
|
1345 |
#' @exportMethod value_at |
|
1346 |
setMethod( |
|
1347 |
"value_at", "TableRow", |
|
1348 |
function(tt, rowpath, colpath = NULL) { |
|
1349 | 1x |
.inner_cell_value(tt, |
1350 | 1x |
rowpath = rowpath, colpath = colpath, |
1351 | 1x |
omit_labrows = FALSE, value_at = TRUE |
1352 |
) |
|
1353 |
} |
|
1354 |
) |
|
1355 | ||
1356 |
#' @rdname int_methods |
|
1357 |
#' @keywords internal |
|
1358 |
#' @exportMethod value_at |
|
1359 |
setMethod( |
|
1360 |
"value_at", "LabelRow", |
|
1361 |
function(tt, rowpath, colpath = NULL) { |
|
1362 | ! |
stop("calling value_at for LabelRow objects is not meaningful") |
1363 |
} |
|
1364 |
) |
|
1365 | ||
1366 |
.inner_cell_value <- function(tt, |
|
1367 |
rowpath, |
|
1368 |
colpath = NULL, |
|
1369 |
omit_labrows = TRUE, |
|
1370 |
value_at = FALSE) { |
|
1371 | 169x |
if (is.null(rowpath)) { |
1372 | 90x |
subtree <- tt |
1373 |
} else { |
|
1374 | 79x |
subtree <- tt_at_path(tt, rowpath) |
1375 |
} |
|
1376 | 168x |
if (!is.null(colpath)) { |
1377 | 28x |
subtree <- subset_cols(subtree, colpath) |
1378 |
} |
|
1379 | ||
1380 | 168x |
rows <- collect_leaves(subtree, TRUE, !omit_labrows) |
1381 | 168x |
if (value_at && (ncol(subtree) != 1 || length(rows) != 1)) { |
1382 | 3x |
stop("Combination of rowpath and colpath does not select individual cell.\n", |
1383 | 3x |
" To retrieve more than one cell value at a time use cell_values().", |
1384 | 3x |
call. = FALSE |
1385 |
) |
|
1386 |
} |
|
1387 | 165x |
if (length(rows) == 1) { |
1388 | 92x |
ret <- row_values(rows[[1]]) |
1389 | 92x |
if (value_at && ncol(subtree) == 1) { |
1390 | 5x |
ret <- ret[[1]] |
1391 |
} |
|
1392 | 92x |
ret |
1393 |
} else { |
|
1394 | 73x |
lapply(rows, row_values) |
1395 |
} |
|
1396 |
} |
|
1397 | ||
1398 |
## empty_table is created in onLoad because it depends on other things there. |
|
1399 | ||
1400 |
# Helper function to copy or not header, footer, and topleft information |
|
1401 |
.h_copy_titles_footers_topleft <- function(new, |
|
1402 |
old, |
|
1403 |
keep_titles, |
|
1404 |
keep_footers, |
|
1405 |
keep_topleft, |
|
1406 |
reindex_refs = FALSE, |
|
1407 |
empt_tbl = empty_table) { |
|
1408 |
## Please note that the standard adopted come from an empty table |
|
1409 | ||
1410 |
# titles |
|
1411 | 2886x |
if (isTRUE(keep_titles)) { |
1412 | 2712x |
main_title(new) <- main_title(old) |
1413 | 2712x |
subtitles(new) <- subtitles(old) |
1414 |
} else { |
|
1415 | 174x |
main_title(new) <- main_title(empt_tbl) |
1416 | 174x |
subtitles(new) <- subtitles(empt_tbl) |
1417 |
} |
|
1418 | ||
1419 |
# fnotes |
|
1420 | 2886x |
if (isTRUE(keep_footers)) { |
1421 | 2718x |
main_footer(new) <- main_footer(old) |
1422 | 2718x |
prov_footer(new) <- prov_footer(old) |
1423 |
} else { |
|
1424 | 168x |
main_footer(new) <- main_footer(empt_tbl) |
1425 | 168x |
prov_footer(new) <- prov_footer(empt_tbl) |
1426 |
} |
|
1427 | ||
1428 |
# topleft |
|
1429 | 2886x |
if (isTRUE(keep_topleft)) { |
1430 | 2738x |
top_left(new) <- top_left(old) |
1431 |
} else { |
|
1432 | 148x |
top_left(new) <- top_left(empt_tbl) |
1433 |
} |
|
1434 | ||
1435 |
# reindex references |
|
1436 | 2886x |
if (reindex_refs) { |
1437 | ! |
new <- update_ref_indexing(new) |
1438 |
} |
|
1439 | ||
1440 | 2886x |
new |
1441 |
} |
|
1442 | ||
1443 |
#' Head and tail methods |
|
1444 |
#' |
|
1445 |
#' @inheritParams utils::head |
|
1446 |
#' @param keep_topleft (`flag`)\cr if `TRUE` (the default), top_left material for the table will be carried over to the |
|
1447 |
#' subset. |
|
1448 |
#' @param keep_titles (`flag`)\cr if `TRUE` (the default), all title material for the table will be carried over to the |
|
1449 |
#' subset. |
|
1450 |
#' @param keep_footers (`flag`)\cr if `TRUE`, all footer material for the table will be carried over to the subset. It |
|
1451 |
#' defaults to `keep_titles`. |
|
1452 |
#' @param reindex_refs (`flag`)\cr defaults to `FALSE`. If `TRUE`, referential footnotes will be reindexed for the |
|
1453 |
#' subset. |
|
1454 |
#' |
|
1455 |
#' @docType methods |
|
1456 |
#' @export |
|
1457 |
#' @rdname head_tail |
|
1458 |
setGeneric("head") |
|
1459 | ||
1460 |
#' @docType methods |
|
1461 |
#' @export |
|
1462 |
#' @rdname head_tail |
|
1463 |
setMethod( |
|
1464 |
"head", "VTableTree", |
|
1465 |
function(x, n = 6, ..., keep_topleft = TRUE, |
|
1466 |
keep_titles = TRUE, |
|
1467 |
keep_footers = keep_titles, |
|
1468 |
## FALSE because this is a glance |
|
1469 |
## more often than a subset op |
|
1470 |
reindex_refs = FALSE) { |
|
1471 |
## default |
|
1472 | 5x |
res <- callNextMethod() |
1473 | 5x |
res <- .h_copy_titles_footers_topleft( |
1474 | 5x |
old = x, new = res, |
1475 | 5x |
keep_topleft = keep_topleft, |
1476 | 5x |
keep_titles = keep_titles, |
1477 | 5x |
keep_footers = keep_footers, |
1478 | 5x |
reindex_refs = reindex_refs |
1479 |
) |
|
1480 | 5x |
res |
1481 |
} |
|
1482 |
) |
|
1483 | ||
1484 |
#' @docType methods |
|
1485 |
#' @export |
|
1486 |
#' @rdname head_tail |
|
1487 |
setGeneric("tail") |
|
1488 | ||
1489 |
#' @docType methods |
|
1490 |
#' @export |
|
1491 |
#' @rdname head_tail |
|
1492 |
setMethod( |
|
1493 |
"tail", "VTableTree", |
|
1494 |
function(x, n = 6, ..., keep_topleft = TRUE, |
|
1495 |
keep_titles = TRUE, |
|
1496 |
keep_footers = keep_titles, |
|
1497 |
## FALSE because this is a glance |
|
1498 |
## more often than a subset op |
|
1499 |
reindex_refs = FALSE) { |
|
1500 | 4x |
res <- callNextMethod() |
1501 | 4x |
res <- .h_copy_titles_footers_topleft( |
1502 | 4x |
old = x, new = res, |
1503 | 4x |
keep_topleft = keep_topleft, |
1504 | 4x |
keep_titles = keep_titles, |
1505 | 4x |
keep_footers = keep_footers, |
1506 | 4x |
reindex_refs = reindex_refs |
1507 |
) |
|
1508 | 4x |
res |
1509 |
} |
|
1510 |
) |
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 |
.ref_full = NULL, |
|
11 |
.in_ref_col = NULL, |
|
12 |
.spl_context = NULL, |
|
13 |
.N_row, |
|
14 |
.df_row, |
|
15 |
extras) { |
|
16 |
# This list is always present |
|
17 | 5968x |
possargs <- c( |
18 | 5968x |
list( |
19 | 5968x |
.N_col = .N_col, |
20 | 5968x |
.N_total = .N_total, |
21 | 5968x |
.N_row = .N_row, |
22 | 5968x |
.df_row = .df_row, |
23 | 5968x |
.all_col_exprs = .all_col_exprs, |
24 | 5968x |
.all_col_counts = .all_col_counts |
25 |
), |
|
26 | 5968x |
extras |
27 |
) |
|
28 | ||
29 |
## specialized arguments that must be named in formals, cannot go |
|
30 |
## anonymously into ... |
|
31 | 5968x |
if (!is.null(.var) && nzchar(.var)) { |
32 | 4718x |
possargs <- c(possargs, list(.var = .var)) |
33 |
} |
|
34 | 5968x |
if (!is.null(.ref_group)) { |
35 | 1839x |
possargs <- c(possargs, list(.ref_group = .ref_group)) |
36 |
} |
|
37 | 5968x |
if (!is.null(.alt_df_row)) { |
38 | 105x |
possargs <- c(possargs, list(.alt_df_row = .alt_df_row)) |
39 |
} |
|
40 | 5968x |
if (!is.null(.alt_df)) { |
41 | 105x |
possargs <- c(possargs, list(.alt_df = .alt_df)) |
42 |
} |
|
43 | 5968x |
if (!is.null(.ref_full)) { |
44 | 141x |
possargs <- c(possargs, list(.ref_full = .ref_full)) |
45 |
} |
|
46 | 5968x |
if (!is.null(.in_ref_col)) { |
47 | 141x |
possargs <- c(possargs, list(.in_ref_col = .in_ref_col)) |
48 |
} |
|
49 | ||
50 |
# Special case: .spl_context |
|
51 | 5968x |
if (!is.null(.spl_context) && !(".spl_context" %in% names(possargs))) { |
52 | 5968x |
possargs <- c(possargs, list(.spl_context = .spl_context)) |
53 |
} else { |
|
54 | ! |
possargs$.spl_context <- NULL |
55 |
} |
|
56 | ||
57 |
# Extra args handling |
|
58 | 5968x |
formargs <- formals(f) |
59 | 5968x |
formnms <- names(formargs) |
60 | 5968x |
exnms <- names(extras) |
61 | 5968x |
if (is.null(formargs)) { |
62 | 206x |
return(NULL) |
63 | 5762x |
} else if ("..." %in% names(formargs)) { |
64 | 5044x |
formnms <- c(formnms, exnms[nzchar(exnms)]) |
65 |
} |
|
66 | 5762x |
possargs[names(possargs) %in% formnms] |
67 |
} |
|
68 | ||
69 |
#' @noRd |
|
70 |
#' @return A `RowsVerticalSection` object representing the `k x 1` section of the |
|
71 |
#' table being generated, with `k` the number of rows the analysis function |
|
72 |
#' generates. |
|
73 |
gen_onerv <- function(csub, col, count, cextr, cpath, |
|
74 |
dfpart, func, totcount, splextra, |
|
75 |
all_col_exprs, |
|
76 |
all_col_counts, |
|
77 |
takesdf = .takes_df(func), |
|
78 |
baselinedf, |
|
79 |
alt_dfpart, |
|
80 |
inclNAs, |
|
81 |
col_parent_inds, |
|
82 |
spl_context) { |
|
83 | 5968x |
if (NROW(spl_context) > 0) { |
84 | 5947x |
spl_context$cur_col_id <- paste(cpath[seq(2, length(cpath), 2)], collapse = ".") |
85 | 5947x |
spl_context$cur_col_subset <- col_parent_inds |
86 | 5947x |
spl_context$cur_col_expr <- list(csub) |
87 | 5947x |
spl_context$cur_col_n <- vapply(col_parent_inds, sum, 1L) |
88 | 5947x |
spl_context$cur_col_split <- list(cpath[seq(1, length(cpath), 2)]) |
89 | 5947x |
spl_context$cur_col_split_val <- list(cpath[seq(2, length(cpath), 2)]) |
90 |
} |
|
91 | ||
92 |
# Making .alt_df from alt_dfpart (i.e. .alt_df_row) |
|
93 | 5968x |
if (NROW(alt_dfpart) > 0) { |
94 | 105x |
alt_dfpart_fil <- alt_dfpart[eval(csub, envir = alt_dfpart), , drop = FALSE] |
95 | 105x |
if (!is.null(col) && col %in% names(alt_dfpart_fil) && !inclNAs) { |
96 | 99x |
alt_dfpart_fil <- alt_dfpart_fil[!is.na(alt_dfpart_fil[[col]]), , |
97 | 99x |
drop = FALSE |
98 |
] |
|
99 |
} |
|
100 |
} else { |
|
101 | 5863x |
alt_dfpart_fil <- alt_dfpart |
102 |
} |
|
103 | ||
104 |
## workaround for https://github.com/insightsengineering/rtables/issues/159 |
|
105 | 5968x |
if (NROW(dfpart) > 0) { |
106 | 5098x |
inds <- eval(csub, envir = dfpart) |
107 | 5098x |
dat <- dfpart[inds, , drop = FALSE] |
108 |
} else { |
|
109 | 870x |
dat <- dfpart |
110 |
} |
|
111 | 5968x |
if (!is.null(col) && !inclNAs) { |
112 | 4692x |
dat <- dat[!is.na(dat[[col]]), , drop = FALSE] |
113 |
} |
|
114 | ||
115 | 5968x |
fullrefcoldat <- cextr$.ref_full |
116 | 5968x |
if (!is.null(fullrefcoldat)) { |
117 | 141x |
cextr$.ref_full <- NULL |
118 |
} |
|
119 | 5968x |
inrefcol <- cextr$.in_ref_col |
120 | 5968x |
if (!is.null(fullrefcoldat)) { |
121 | 141x |
cextr$.in_ref_col <- NULL |
122 |
} |
|
123 | ||
124 | 5968x |
exargs <- c(cextr, splextra) |
125 | ||
126 |
## behavior for x/df and ref-data (full and group) |
|
127 |
## match |
|
128 | 5968x |
if (!is.null(col) && !takesdf) { |
129 | 3775x |
dat <- dat[[col]] |
130 | 3775x |
fullrefcoldat <- fullrefcoldat[[col]] |
131 | 3775x |
baselinedf <- baselinedf[[col]] |
132 |
} |
|
133 | 5968x |
args <- list(dat) |
134 | ||
135 | 5968x |
names(all_col_counts) <- names(all_col_exprs) |
136 | ||
137 | 5968x |
exargs <- match_extra_args(func, |
138 | 5968x |
.N_col = count, |
139 | 5968x |
.N_total = totcount, |
140 | 5968x |
.all_col_exprs = all_col_exprs, |
141 | 5968x |
.all_col_counts = all_col_counts, |
142 | 5968x |
.var = col, |
143 | 5968x |
.ref_group = baselinedf, |
144 | 5968x |
.alt_df_row = alt_dfpart, |
145 | 5968x |
.alt_df = alt_dfpart_fil, |
146 | 5968x |
.ref_full = fullrefcoldat, |
147 | 5968x |
.in_ref_col = inrefcol, |
148 | 5968x |
.N_row = NROW(dfpart), |
149 | 5968x |
.df_row = dfpart, |
150 | 5968x |
.spl_context = spl_context, |
151 | 5968x |
extras = c( |
152 | 5968x |
cextr, |
153 | 5968x |
splextra |
154 |
) |
|
155 |
) |
|
156 | ||
157 | 5968x |
args <- c(args, exargs) |
158 | ||
159 | 5968x |
val <- do.call(func, args) |
160 | 5965x |
if (!is(val, "RowsVerticalSection")) { |
161 | 3810x |
if (!is(val, "list")) { |
162 | 3312x |
val <- list(val) |
163 |
} |
|
164 | 3810x |
ret <- in_rows( |
165 | 3810x |
.list = val, |
166 | 3810x |
.labels = unlist(value_labels(val)), |
167 | 3810x |
.names = names(val) |
168 |
) |
|
169 |
} else { |
|
170 | 2155x |
ret <- val |
171 |
} |
|
172 | 5965x |
ret |
173 |
} |
|
174 | ||
175 |
strip_multivar_suffix <- function(x) { |
|
176 | 228x |
gsub("\\._\\[\\[[0-9]\\]\\]_\\.$", "", x) |
177 |
} |
|
178 | ||
179 |
## Generate all values (one for each column) for one or more rows |
|
180 |
## by calling func once per column (as defined by cinfo) |
|
181 |
#' @noRd |
|
182 |
#' @return A list of `m` `RowsVerticalSection` objects, one for each (leaf) column in the table. |
|
183 |
gen_rowvalues <- function(dfpart, |
|
184 |
datcol, |
|
185 |
cinfo, |
|
186 |
func, |
|
187 |
splextra, |
|
188 |
takesdf = NULL, |
|
189 |
baselines, |
|
190 |
alt_dfpart, |
|
191 |
inclNAs, |
|
192 |
spl_context = spl_context) { |
|
193 | 1649x |
colexprs <- col_exprs(cinfo) |
194 | 1649x |
colcounts <- col_counts(cinfo) |
195 | 1649x |
colextras <- col_extra_args(cinfo, NULL) |
196 | 1649x |
cpaths <- col_paths(cinfo) |
197 |
## XXX I don't think this is used anywhere??? |
|
198 |
## splextra = c(splextra, list(.spl_context = spl_context)) |
|
199 | 1649x |
totcount <- col_total(cinfo) |
200 | ||
201 | 1649x |
colleaves <- collect_leaves(cinfo@tree_layout) |
202 | ||
203 | 1649x |
gotflist <- is.list(func) |
204 | ||
205 |
## one set of named args to be applied to all columns |
|
206 | 1649x |
if (!is.null(names(splextra))) { |
207 | 25x |
splextra <- list(splextra) |
208 |
} else { |
|
209 | 1624x |
length(splextra) <- ncol(cinfo) |
210 |
} |
|
211 | ||
212 | 1649x |
if (!gotflist) { |
213 | 1117x |
func <- list(func) |
214 | 532x |
} else if (length(splextra) == 1) { |
215 | 114x |
splextra <- rep(splextra, length.out = length(func)) |
216 |
} |
|
217 |
## if(length(func)) == 1 && names(spl) |
|
218 |
## splextra = list(splextra) |
|
219 | ||
220 |
## we are in analyze_colvars, so we have to match |
|
221 |
## the exargs value by position for each column repeatedly |
|
222 |
## across the higher level col splits. |
|
223 | 1649x |
if (!is.null(datcol) && is.na(datcol)) { |
224 | 54x |
datcol <- character(length(colleaves)) |
225 | 54x |
exargs <- vector("list", length(colleaves)) |
226 | 54x |
for (i in seq_along(colleaves)) { |
227 | 228x |
x <- colleaves[[i]] |
228 | ||
229 | 228x |
pos <- tree_pos(x) |
230 | 228x |
spls <- pos_splits(pos) |
231 |
## values have the suffix but we are populating datacol |
|
232 |
## so it has to match var numbers so strip the suffixes back off |
|
233 | 228x |
splvals <- strip_multivar_suffix(rawvalues(pos)) |
234 | 228x |
n <- length(spls) |
235 | 228x |
datcol[i] <- if (is(spls[[n]], "MultiVarSplit")) { |
236 | 228x |
splvals[n] |
237 |
} else { |
|
238 | 228x |
NA_character_ |
239 |
} |
|
240 | 228x |
argpos <- match(datcol[i], spl_payload(spls[[n]])) |
241 |
## single bracket here because assigning NULL into a list removes |
|
242 |
## the position entirely |
|
243 | 228x |
exargs[i] <- if (argpos <= length(splextra)) { |
244 | 228x |
splextra[argpos] |
245 |
} else { |
|
246 | ! |
list(NULL) |
247 |
} |
|
248 |
} |
|
249 |
## }) |
|
250 | 54x |
if (all(is.na(datcol))) { |
251 | ! |
datcol <- list(NULL) |
252 | 54x |
} else if (any(is.na(datcol))) { |
253 | ! |
stop("mix of var and non-var columns with NA analysis rowvara") |
254 |
} |
|
255 |
} else { |
|
256 | 1595x |
exargs <- splextra |
257 | 1595x |
if (is.null(datcol)) { |
258 | 335x |
datcol <- list(NULL) |
259 |
} |
|
260 | 1595x |
datcol <- rep(datcol, length(colexprs)) |
261 |
## if(gotflist) |
|
262 |
## length(exargs) <- length(func) ## func is a list |
|
263 | 1595x |
exargs <- rep(exargs, length.out = length(colexprs)) |
264 |
} |
|
265 | 1649x |
allfuncs <- rep(func, length.out = length(colexprs)) |
266 | ||
267 | 1649x |
if (is.null(takesdf)) { |
268 | 1171x |
takesdf <- .takes_df(allfuncs) |
269 |
} |
|
270 | ||
271 | 1649x |
rawvals <- mapply(gen_onerv, |
272 | 1649x |
csub = colexprs, |
273 | 1649x |
col = datcol, |
274 | 1649x |
count = colcounts, |
275 | 1649x |
cextr = colextras, |
276 | 1649x |
cpath = cpaths, |
277 | 1649x |
baselinedf = baselines, |
278 | 1649x |
alt_dfpart = list(alt_dfpart), |
279 | 1649x |
func = allfuncs, |
280 | 1649x |
takesdf = takesdf, |
281 | 1649x |
col_parent_inds = spl_context[, names(colexprs), |
282 | 1649x |
drop = FALSE |
283 |
], |
|
284 | 1649x |
all_col_exprs = list(colexprs), |
285 | 1649x |
all_col_counts = list(colcounts), |
286 | 1649x |
splextra = exargs, |
287 | 1649x |
MoreArgs = list( |
288 | 1649x |
dfpart = dfpart, |
289 | 1649x |
totcount = totcount, |
290 | 1649x |
inclNAs = inclNAs, |
291 | 1649x |
spl_context = spl_context |
292 |
), |
|
293 | 1649x |
SIMPLIFY = FALSE |
294 |
) |
|
295 | ||
296 | 1646x |
names(rawvals) <- names(colexprs) |
297 | 1646x |
rawvals |
298 |
} |
|
299 | ||
300 |
.strip_lst_rvals <- function(lst) { |
|
301 | ! |
lapply(lst, rawvalues) |
302 |
} |
|
303 | ||
304 |
#' @noRd |
|
305 |
#' @return A list of table rows, even when only one is generated. |
|
306 |
.make_tablerows <- function(dfpart, |
|
307 |
alt_dfpart, |
|
308 |
func, |
|
309 |
cinfo, |
|
310 |
datcol = NULL, |
|
311 |
lev = 1L, |
|
312 |
rvlab = NA_character_, |
|
313 |
format = NULL, |
|
314 |
defrowlabs = NULL, |
|
315 |
rowconstr = DataRow, |
|
316 |
splextra = list(), |
|
317 |
takesdf = NULL, |
|
318 |
baselines = replicate( |
|
319 |
length(col_exprs(cinfo)), |
|
320 |
list(dfpart[0, ]) |
|
321 |
), |
|
322 |
inclNAs, |
|
323 |
spl_context = context_df_row(cinfo = cinfo)) { |
|
324 | 1649x |
if (is.null(datcol) && !is.na(rvlab)) { |
325 | ! |
stop("NULL datcol but non-na rowvar label") |
326 |
} |
|
327 | 1649x |
if (!is.null(datcol) && !is.na(datcol)) { |
328 | 1260x |
if (!all(datcol %in% names(dfpart))) { |
329 | ! |
stop( |
330 | ! |
"specified analysis variable (", datcol, |
331 | ! |
") not present in data" |
332 |
) |
|
333 |
} |
|
334 | ||
335 | 1260x |
rowvar <- datcol |
336 |
} else { |
|
337 | 389x |
rowvar <- NA_character_ |
338 |
} |
|
339 | ||
340 | 1649x |
rawvals <- gen_rowvalues(dfpart, |
341 | 1649x |
alt_dfpart = alt_dfpart, |
342 | 1649x |
datcol = datcol, |
343 | 1649x |
cinfo = cinfo, |
344 | 1649x |
func = func, |
345 | 1649x |
splextra = splextra, |
346 | 1649x |
takesdf = takesdf, |
347 | 1649x |
baselines = baselines, |
348 | 1649x |
inclNAs = inclNAs, |
349 | 1649x |
spl_context = spl_context |
350 |
) |
|
351 | ||
352 |
## if(is.null(rvtypes)) |
|
353 |
## rvtypes = rep(NA_character_, length(rawvals)) |
|
354 | 1646x |
lens <- vapply(rawvals, length, NA_integer_) |
355 | 1646x |
unqlens <- unique(lens) |
356 |
## length 0 returns are ok to not match cause they are |
|
357 |
## just empty space we can fill in as needed. |
|
358 | 1646x |
if (length(unqlens[unqlens > 0]) != 1L) { ## length(unqlens) != 1 && |
359 |
## (0 %in% unqlens && length(unqlens) != 2)) { |
|
360 | 1x |
stop( |
361 | 1x |
"Number of rows generated by analysis function do not match ", |
362 | 1x |
"across all columns. ", |
363 | 1x |
if (!is.na(datcol) && is.character(dfpart[[datcol]])) { |
364 | ! |
paste( |
365 | ! |
"\nPerhaps convert analysis variable", datcol, |
366 | ! |
"to a factor?" |
367 |
) |
|
368 |
} |
|
369 |
) |
|
370 |
} |
|
371 | 1645x |
maxind <- match(max(unqlens), lens) |
372 | ||
373 |
## look if we got labels, if not apply the |
|
374 |
## default row labels |
|
375 |
## this is guaranteed to be a RowsVerticalSection object. |
|
376 | 1645x |
rv1col <- rawvals[[maxind]] |
377 |
## nocov start |
|
378 |
if (!is(rv1col, "RowsVerticalSection")) { |
|
379 |
stop( |
|
380 |
"gen_rowvalues appears to have generated something that was not ", |
|
381 |
"a RowsVerticalSection object. Please contact the maintainer." |
|
382 |
) |
|
383 |
} |
|
384 |
# nocov end |
|
385 | ||
386 | 1645x |
labels <- value_labels(rv1col) |
387 | ||
388 | 1645x |
ncrows <- max(unqlens) |
389 | 1645x |
if (ncrows == 0) { |
390 | ! |
return(list()) |
391 |
} |
|
392 | 1645x |
stopifnot(ncrows > 0) |
393 | ||
394 | 1645x |
if (is.null(labels)) { |
395 | 211x |
if (length(rawvals[[maxind]]) == length(defrowlabs)) { |
396 | 203x |
labels <- defrowlabs |
397 |
} else { |
|
398 | 8x |
labels <- rep("", ncrows) |
399 |
} |
|
400 |
} |
|
401 | ||
402 | 1645x |
rfootnotes <- rep(list(list(), length(rv1col))) |
403 | 1645x |
nms <- value_names(rv1col) |
404 | 1645x |
rfootnotes <- row_footnotes(rv1col) |
405 | ||
406 | 1645x |
imods <- indent_mod(rv1col) ## rv1col@indent_mods |
407 | 1645x |
unwrapped_vals <- lapply(rawvals, as, Class = "list", strict = TRUE) |
408 | ||
409 | 1645x |
formatvec <- NULL |
410 | 1645x |
if (!is.null(format)) { |
411 | 208x |
if (is.function(format)) { |
412 | 1x |
format <- list(format) |
413 |
} |
|
414 | 208x |
formatvec <- rep(format, length.out = ncrows) |
415 |
} |
|
416 | ||
417 | 1645x |
trows <- lapply(1:ncrows, function(i) { |
418 | 2621x |
rowvals <- lapply(unwrapped_vals, function(colvals) { |
419 | 9336x |
colvals[[i]] |
420 |
}) |
|
421 | 2621x |
imod <- unique(vapply(rowvals, indent_mod, 0L)) |
422 | 2621x |
if (length(imod) != 1) { |
423 | ! |
stop( |
424 | ! |
"Different cells in the same row appear to have been given ", |
425 | ! |
"different indent_mod values" |
426 |
) |
|
427 |
} |
|
428 | 2621x |
rowconstr( |
429 | 2621x |
vals = rowvals, |
430 | 2621x |
cinfo = cinfo, |
431 | 2621x |
lev = lev, |
432 | 2621x |
label = labels[i], |
433 | 2621x |
name = nms[i], ## labels[i], ## XXX this is probably wrong?! |
434 | 2621x |
var = rowvar, |
435 | 2621x |
format = formatvec[[i]], |
436 | 2621x |
indent_mod = imods[[i]] %||% 0L, |
437 | 2621x |
footnotes = rfootnotes[[i]] ## one bracket so list |
438 |
) |
|
439 |
}) |
|
440 | 1645x |
trows |
441 |
} |
|
442 | ||
443 |
.make_caller <- function(parent_cfun, clabelstr = "") { |
|
444 | 489x |
formalnms <- names(formals(parent_cfun)) |
445 |
## note the <- here |
|
446 | 489x |
if (!is.na(dotspos <- match("...", formalnms))) { |
447 | 1x |
toremove <- dotspos |
448 |
} else { |
|
449 | 488x |
toremove <- NULL |
450 |
} |
|
451 | ||
452 | 489x |
labelstrpos <- match("labelstr", names(formals(parent_cfun))) |
453 | 489x |
if (is.na(labelstrpos)) { |
454 | ! |
stop( |
455 | ! |
"content function does not appear to accept the labelstr", |
456 | ! |
"arguent" |
457 |
) |
|
458 |
} |
|
459 | 489x |
toremove <- c(toremove, labelstrpos) |
460 | 489x |
formalnms <- formalnms[-1 * toremove] |
461 | ||
462 | 489x |
caller <- eval(parser_helper(text = paste( |
463 | 489x |
"function() { parent_cfun(", |
464 | 489x |
paste(formalnms, "=", |
465 | 489x |
formalnms, |
466 | 489x |
collapse = ", " |
467 |
), |
|
468 | 489x |
", labelstr = clabelstr, ...)}" |
469 |
))) |
|
470 | 489x |
formals(caller) <- c( |
471 | 489x |
formals(parent_cfun)[-labelstrpos], |
472 | 489x |
alist("..." = ) |
473 | 489x |
) # nolint |
474 | 489x |
caller |
475 |
} |
|
476 | ||
477 |
# Makes content table xxx renaming |
|
478 |
.make_ctab <- function(df, |
|
479 |
lvl, ## treepos, |
|
480 |
name, |
|
481 |
label, |
|
482 |
cinfo, |
|
483 |
parent_cfun = NULL, |
|
484 |
format = NULL, |
|
485 |
na_str = NA_character_, |
|
486 |
indent_mod = 0L, |
|
487 |
cvar = NULL, |
|
488 |
inclNAs, |
|
489 |
alt_df, |
|
490 |
extra_args, |
|
491 |
spl_context = context_df_row(cinfo = cinfo)) { |
|
492 | 1937x |
if (length(cvar) == 0 || is.na(cvar) || identical(nchar(cvar), 0L)) { |
493 | 1761x |
cvar <- NULL |
494 |
} |
|
495 | 1937x |
if (!is.null(parent_cfun)) { |
496 |
## cfunc <- .make_caller(parent_cfun, label) |
|
497 | 478x |
cfunc <- lapply(parent_cfun, .make_caller, clabelstr = label) |
498 | 478x |
contkids <- tryCatch( |
499 | 478x |
.make_tablerows(df, |
500 | 478x |
lev = lvl, |
501 | 478x |
func = cfunc, |
502 | 478x |
cinfo = cinfo, |
503 | 478x |
rowconstr = ContentRow, |
504 | 478x |
datcol = cvar, |
505 | 478x |
takesdf = rep(.takes_df(cfunc), |
506 | 478x |
length.out = ncol(cinfo) |
507 |
), |
|
508 | 478x |
inclNAs = FALSE, |
509 | 478x |
alt_dfpart = alt_df, |
510 | 478x |
splextra = extra_args, |
511 | 478x |
spl_context = spl_context |
512 |
), |
|
513 | 478x |
error = function(e) e |
514 |
) |
|
515 | 478x |
if (is(contkids, "error")) { |
516 | 1x |
stop("Error in content (summary) function: ", contkids$message, |
517 | 1x |
"\n\toccured at path: ", |
518 | 1x |
spl_context_to_disp_path(spl_context), |
519 | 1x |
call. = FALSE |
520 |
) |
|
521 |
} |
|
522 |
} else { |
|
523 | 1459x |
contkids <- list() |
524 |
} |
|
525 | 1936x |
ctab <- ElementaryTable( |
526 | 1936x |
kids = contkids, |
527 | 1936x |
name = paste0(name, "@content"), |
528 | 1936x |
lev = lvl, |
529 | 1936x |
labelrow = LabelRow(), |
530 | 1936x |
cinfo = cinfo, |
531 | 1936x |
iscontent = TRUE, |
532 | 1936x |
format = format, |
533 | 1936x |
indent_mod = indent_mod, |
534 | 1936x |
na_str = na_str |
535 |
) |
|
536 | 1936x |
ctab |
537 |
} |
|
538 | ||
539 |
.make_analyzed_tab <- function(df, |
|
540 |
alt_df, |
|
541 |
spl, |
|
542 |
cinfo, |
|
543 |
partlabel = "", |
|
544 |
dolab = TRUE, |
|
545 |
lvl, |
|
546 |
baselines, |
|
547 |
spl_context) { |
|
548 | 1172x |
stopifnot(is(spl, "VAnalyzeSplit")) |
549 | 1172x |
check_validsplit(spl, df) |
550 | 1171x |
defrlabel <- spl@default_rowlabel |
551 | 1171x |
if (nchar(defrlabel) == 0 && !missing(partlabel) && nchar(partlabel) > 0) { |
552 | ! |
defrlabel <- partlabel |
553 |
} |
|
554 | 1171x |
kids <- tryCatch( |
555 | 1171x |
.make_tablerows(df, |
556 | 1171x |
func = analysis_fun(spl), |
557 | 1171x |
defrowlabs = defrlabel, # XXX |
558 | 1171x |
cinfo = cinfo, |
559 | 1171x |
datcol = spl_payload(spl), |
560 | 1171x |
lev = lvl + 1L, |
561 | 1171x |
format = obj_format(spl), |
562 | 1171x |
splextra = split_exargs(spl), |
563 | 1171x |
baselines = baselines, |
564 | 1171x |
alt_dfpart = alt_df, |
565 | 1171x |
inclNAs = avar_inclNAs(spl), |
566 | 1171x |
spl_context = spl_context |
567 |
), |
|
568 | 1171x |
error = function(e) e |
569 |
) |
|
570 | ||
571 |
# Adding section_div for DataRows (analyze leaves) |
|
572 | 1171x |
kids <- .set_kids_section_div(kids, spl_section_div(spl), "DataRow") |
573 | ||
574 | 1171x |
if (is(kids, "error")) { |
575 | 3x |
stop("Error applying analysis function (var - ", |
576 | 3x |
spl_payload(spl) %||% "colvars", "): ", kids$message, |
577 | 3x |
"\n\toccured at (row) path: ", |
578 | 3x |
spl_context_to_disp_path(spl_context), |
579 | 3x |
call. = FALSE |
580 |
) |
|
581 |
} |
|
582 | 1168x |
lab <- obj_label(spl) |
583 | 1168x |
ret <- TableTree( |
584 | 1168x |
kids = kids, |
585 | 1168x |
name = obj_name(spl), |
586 | 1168x |
label = lab, |
587 | 1168x |
lev = lvl, |
588 | 1168x |
cinfo = cinfo, |
589 | 1168x |
format = obj_format(spl), |
590 | 1168x |
na_str = obj_na_str(spl), |
591 | 1168x |
indent_mod = indent_mod(spl) |
592 |
) |
|
593 | ||
594 | 1168x |
labelrow_visible(ret) <- dolab |
595 | 1168x |
ret |
596 |
} |
|
597 | ||
598 |
#' @param ... all arguments to `recurse_applysplit`, methods may only use some of them. |
|
599 |
#' @return A `list` of children to place at this level. |
|
600 |
#' |
|
601 |
#' @noRd |
|
602 |
setGeneric(".make_split_kids", function(spl, have_controws, make_lrow, ...) { |
|
603 | 1728x |
standardGeneric(".make_split_kids") |
604 |
}) |
|
605 | ||
606 |
## single AnalyzeSplit |
|
607 |
setMethod( |
|
608 |
".make_split_kids", "VAnalyzeSplit", |
|
609 |
function(spl, |
|
610 |
have_controws, ## unused here |
|
611 |
make_lrow, ## unused here |
|
612 |
..., |
|
613 |
df, |
|
614 |
alt_df, |
|
615 |
lvl, |
|
616 |
name, |
|
617 |
cinfo, |
|
618 |
baselines, |
|
619 |
spl_context, |
|
620 |
nsibs = 0) { |
|
621 | 1172x |
spvis <- labelrow_visible(spl) |
622 | 1172x |
if (is.na(spvis)) { |
623 | 190x |
spvis <- nsibs > 0 |
624 |
} |
|
625 | ||
626 | 1172x |
ret <- .make_analyzed_tab( |
627 | 1172x |
df = df, |
628 | 1172x |
alt_df, |
629 | 1172x |
spl = spl, |
630 | 1172x |
cinfo = cinfo, |
631 | 1172x |
lvl = lvl + 1L, |
632 | 1172x |
dolab = spvis, |
633 | 1172x |
partlabel = obj_label(spl), |
634 | 1172x |
baselines = baselines, |
635 | 1172x |
spl_context = spl_context |
636 |
) |
|
637 | 1168x |
indent_mod(ret) <- indent_mod(spl) |
638 | ||
639 | 1168x |
kids <- list(ret) |
640 | 1168x |
names(kids) <- obj_name(ret) |
641 | 1168x |
kids |
642 |
} |
|
643 |
) |
|
644 | ||
645 |
# Adding section_divisors to TableRow |
|
646 |
.set_kids_section_div <- function(lst, trailing_section_div_char, allowed_class = "VTableTree") { |
|
647 | 1701x |
if (!is.na(trailing_section_div_char)) { |
648 | 29x |
lst <- lapply( |
649 | 29x |
lst, |
650 | 29x |
function(k) { |
651 | 70x |
if (is(k, allowed_class)) { |
652 | 70x |
trailing_section_div(k) <- trailing_section_div_char |
653 |
} |
|
654 | 70x |
k |
655 |
} |
|
656 |
) |
|
657 |
} |
|
658 | 1701x |
lst |
659 |
} |
|
660 | ||
661 |
## 1 or more AnalyzeSplits |
|
662 |
setMethod( |
|
663 |
".make_split_kids", "AnalyzeMultiVars", |
|
664 |
function(spl, |
|
665 |
have_controws, |
|
666 |
make_lrow, ## used here |
|
667 |
spl_context, |
|
668 |
...) { ## all passed directly down to VAnalyzeSplit method |
|
669 | 102x |
avspls <- spl_payload(spl) |
670 | ||
671 | 102x |
nspl <- length(avspls) |
672 | ||
673 | 102x |
kids <- unlist(lapply(avspls, |
674 | 102x |
.make_split_kids, |
675 | 102x |
nsibs = nspl - 1, |
676 | 102x |
have_controws = have_controws, |
677 | 102x |
make_lrow = make_lrow, |
678 | 102x |
spl_context = spl_context, |
679 |
... |
|
680 |
)) |
|
681 | ||
682 | 102x |
kids <- .set_kids_section_div(kids, spl_section_div(spl), "VTableTree") |
683 | ||
684 |
## XXX this seems like it should be identical not !identical |
|
685 |
## TODO FIXME |
|
686 | 102x |
if (!identical(make_lrow, FALSE) && !have_controws && length(kids) == 1) { |
687 |
## we only analyzed one var so |
|
688 |
## we don't need an extra wrapper table |
|
689 |
## in the structure |
|
690 | ! |
stopifnot(identical( |
691 | ! |
obj_name(kids[[1]]), |
692 | ! |
spl_payload(spl) |
693 |
)) |
|
694 | ! |
return(kids[[1]]) |
695 |
} |
|
696 |
## this will be the variables |
|
697 |
## nms = sapply(spl_payload(spl), spl_payload) |
|
698 | ||
699 | 102x |
nms <- vapply(kids, obj_name, "") |
700 | 102x |
labs <- vapply(kids, obj_label, "") |
701 | 102x |
if (length(unique(nms)) != length(nms) && length(unique(nms)) != length(nms)) { |
702 | 1x |
warning("Non-unique sibling analysis table names. Using Labels ", |
703 | 1x |
"instead. Use the table_names argument to analyze to avoid ", |
704 | 1x |
"this when analyzing the same variable multiple times.", |
705 | 1x |
"\n\toccured at (row) path: ", |
706 | 1x |
spl_context_to_disp_path(spl_context), |
707 | 1x |
call. = FALSE |
708 |
) |
|
709 | 1x |
kids <- mapply(function(k, nm) { |
710 | 2x |
obj_name(k) <- nm |
711 | 2x |
k |
712 | 1x |
}, k = kids, nm = labs, SIMPLIFY = FALSE) |
713 | 1x |
nms <- labs |
714 |
} |
|
715 | ||
716 | 102x |
nms[is.na(nms)] <- "" |
717 | ||
718 | 102x |
names(kids) <- nms |
719 | 102x |
kids |
720 |
} |
|
721 |
) |
|
722 | ||
723 |
setMethod( |
|
724 |
".make_split_kids", "Split", |
|
725 |
function(spl, |
|
726 |
have_controws, |
|
727 |
make_lrow, |
|
728 |
..., |
|
729 |
splvec, ## passed to recursive_applysplit |
|
730 |
df, ## used to apply split |
|
731 |
alt_df, ## used to apply split for alternative df |
|
732 |
lvl, ## used to calculate innerlev |
|
733 |
cinfo, ## used for sanity check |
|
734 |
baselines, ## used to calc new baselines |
|
735 |
spl_context) { |
|
736 |
## do the core splitting of data into children for this split |
|
737 | 454x |
rawpart <- do_split(spl, df, spl_context = spl_context) |
738 | 443x |
dataspl <- rawpart[["datasplit"]] |
739 |
## these are SplitValue objects |
|
740 | 443x |
splvals <- rawpart[["values"]] |
741 | 443x |
partlabels <- rawpart[["labels"]] |
742 | 443x |
if (is.factor(partlabels)) { |
743 | ! |
partlabels <- as.character(partlabels) |
744 |
} |
|
745 | 443x |
nms <- unlist(value_names(splvals)) |
746 | 443x |
if (is.factor(nms)) { |
747 | ! |
nms <- as.character(nms) |
748 |
} |
|
749 | ||
750 |
## Get new baseline values |
|
751 |
## |
|
752 |
## XXX this is a lot of data churn, if it proves too slow |
|
753 |
## we can |
|
754 |
## a) check if any of the analyses (i.e. the afuns) need the baseline in this |
|
755 |
## splitvec and not do any of this if not, or |
|
756 |
## b) refactor row splitting to behave like column splitting |
|
757 |
## |
|
758 |
## (b) seems the better design but is a major reworking of the guts of how |
|
759 |
## rtables tabulation works |
|
760 |
## (a) will only help if analyses that use baseline |
|
761 |
## info are mixed with those who don't. |
|
762 | 443x |
newbl_raw <- lapply(baselines, function(dat) { |
763 |
# If no ref_group is specified |
|
764 | 1565x |
if (is.null(dat)) { |
765 | 1545x |
return(NULL) |
766 |
} |
|
767 | ||
768 |
## apply the same splitting on the |
|
769 | 20x |
bldataspl <- tryCatch(do_split(spl, dat, spl_context = spl_context)[["datasplit"]], |
770 | 20x |
error = function(e) e |
771 |
) |
|
772 | ||
773 |
# Error localization |
|
774 | 20x |
if (is(bldataspl, "error")) { |
775 | ! |
stop("Following error encountered in splitting .ref_group (baselines): ", |
776 | ! |
bldataspl$message, |
777 | ! |
call. = FALSE |
778 |
) |
|
779 |
} |
|
780 | ||
781 |
## we only keep the ones corresponding with actual data splits |
|
782 | 20x |
res <- lapply( |
783 | 20x |
names(dataspl), |
784 | 20x |
function(nm) { |
785 | 52x |
if (nm %in% names(bldataspl)) { |
786 | 52x |
bldataspl[[nm]] |
787 |
} else { |
|
788 | ! |
dataspl[[1]][0, ] |
789 |
} |
|
790 |
} |
|
791 |
) |
|
792 | ||
793 | 20x |
names(res) <- names(dataspl) |
794 | 20x |
res |
795 |
}) |
|
796 | ||
797 | 443x |
newbaselines <- lapply(names(dataspl), function(nm) { |
798 | 1318x |
lapply(newbl_raw, function(rawdat) { |
799 | 4615x |
if (nm %in% names(rawdat)) { |
800 | 52x |
rawdat[[nm]] |
801 |
} else { |
|
802 | 4563x |
rawdat[[1]][0, ] |
803 |
} |
|
804 |
}) |
|
805 |
}) |
|
806 | ||
807 | 443x |
if (length(newbaselines) != length(dataspl)) { |
808 | ! |
stop( |
809 | ! |
"Baselines (ref_group) after row split does not have", |
810 | ! |
" the same number of levels of input data split. ", |
811 | ! |
"Contact the maintainer." |
812 | ! |
) # nocov |
813 |
} |
|
814 | 443x |
if (!(length(newbaselines) == 0 || |
815 | 443x |
identical( |
816 | 443x |
unique(sapply(newbaselines, length)), |
817 | 443x |
length(col_exprs(cinfo)) |
818 |
))) { |
|
819 | ! |
stop( |
820 | ! |
"Baselines (ref_group) do not have the same number of columns", |
821 | ! |
" in each split. Contact the maintainer." |
822 | ! |
) # nocov |
823 |
} |
|
824 | ||
825 |
# If params are not present do not do the calculation |
|
826 | 443x |
acdf_param <- check_afun_cfun_params( |
827 | 443x |
SplitVector(spl, splvec), |
828 | 443x |
c(".alt_df", ".alt_df_row") |
829 |
) |
|
830 | ||
831 |
# Apply same split for alt_counts_df |
|
832 | 443x |
if (!is.null(alt_df) && any(acdf_param)) { |
833 | 17x |
alt_dfpart <- tryCatch( |
834 | 17x |
do_split(spl, alt_df, |
835 | 17x |
spl_context = spl_context |
836 | 17x |
)[["datasplit"]], |
837 | 17x |
error = function(e) e |
838 |
) |
|
839 | ||
840 |
# Removing NA rows - to explore why this happens at all in a split |
|
841 |
# This would be a fix but it is done in post-processing instead of pre-proc -> xxx |
|
842 |
# x alt_dfpart <- lapply(alt_dfpart, function(data) { |
|
843 |
# x data[!apply(is.na(data), 1, all), ] |
|
844 |
# x }) |
|
845 | ||
846 |
# Error localization |
|
847 | 17x |
if (is(alt_dfpart, "error")) { |
848 | 2x |
stop("Following error encountered in splitting alt_counts_df: ", |
849 | 2x |
alt_dfpart$message, |
850 | 2x |
call. = FALSE |
851 |
) |
|
852 |
} |
|
853 |
# Error if split does not have the same values in the alt_df (and order) |
|
854 |
# The following breaks if there are different levels (do_split returns empty list) |
|
855 |
# or if there are different number of the same levels. Added handling of NAs |
|
856 |
# in the values of the factor when is all only NAs |
|
857 | 15x |
is_all_na <- all(is.na(alt_df[[spl_payload(spl)]])) |
858 | ||
859 | 15x |
if (!all(names(dataspl) %in% names(alt_dfpart)) || length(alt_dfpart) != length(dataspl) || is_all_na) { |
860 | 5x |
alt_df_spl_vals <- unique(alt_df[[spl_payload(spl)]]) |
861 | 5x |
end_part <- "" |
862 | ||
863 | 5x |
if (!all(alt_df_spl_vals %in% levels(alt_df_spl_vals))) { |
864 | 2x |
end_part <- paste0( |
865 | 2x |
" and following levels: ", |
866 | 2x |
paste_vec(levels(alt_df_spl_vals)) |
867 |
) |
|
868 |
} |
|
869 | ||
870 | 5x |
if (is_all_na) { |
871 | 2x |
end_part <- ". Found only NAs in alt_counts_df split" |
872 |
} |
|
873 | ||
874 | 5x |
stop( |
875 | 5x |
"alt_counts_df split variable(s) [", spl_payload(spl), |
876 | 5x |
"] (in split ", as.character(class(spl)), |
877 | 5x |
") does not have the same factor levels of df.\ndf has c(", '"', |
878 | 5x |
paste(names(dataspl), collapse = '", "'), '"', ") levels while alt_counts_df has ", |
879 | 5x |
ifelse(length(alt_df_spl_vals) > 0, paste_vec(alt_df_spl_vals), ""), |
880 | 5x |
" unique values", end_part |
881 |
) |
|
882 |
} |
|
883 |
} else { |
|
884 | 426x |
alt_dfpart <- setNames(rep(list(NULL), length(dataspl)), names(dataspl)) |
885 |
} |
|
886 | ||
887 | ||
888 | 436x |
innerlev <- lvl + (have_controws || is.na(make_lrow) || make_lrow) |
889 |
## do full recursive_applysplit on each part of the split defined by spl |
|
890 | 436x |
inner <- unlist(mapply( |
891 | 436x |
function(dfpart, alt_dfpart, nm, label, baselines, splval) { |
892 | 1276x |
rsplval <- context_df_row( |
893 | 1276x |
split = obj_name(spl), |
894 | 1276x |
value = value_names(splval), |
895 | 1276x |
full_parent_df = list(dfpart), |
896 | 1276x |
cinfo = cinfo |
897 |
) |
|
898 | ||
899 |
## if(length(rsplval) > 0) |
|
900 |
## rsplval <- setNames(rsplval, obj_name(spl)) |
|
901 | 1276x |
recursive_applysplit( |
902 | 1276x |
df = dfpart, |
903 | 1276x |
alt_df = alt_dfpart, |
904 | 1276x |
name = nm, |
905 | 1276x |
lvl = innerlev, |
906 | 1276x |
splvec = splvec, |
907 | 1276x |
cinfo = cinfo, |
908 | 1276x |
make_lrow = label_kids(spl), |
909 | 1276x |
parent_cfun = content_fun(spl), |
910 | 1276x |
cformat = content_format(spl), |
911 | 1276x |
cna_str = content_na_str(spl), |
912 | 1276x |
partlabel = label, |
913 | 1276x |
cindent_mod = content_indent_mod(spl), |
914 | 1276x |
cvar = content_var(spl), |
915 | 1276x |
baselines = baselines, |
916 | 1276x |
cextra_args = content_extra_args(spl), |
917 |
## splval should still be retaining its name |
|
918 | 1276x |
spl_context = rbind(spl_context, rsplval) |
919 |
) |
|
920 |
}, |
|
921 | 436x |
dfpart = dataspl, |
922 | 436x |
alt_dfpart = alt_dfpart, |
923 | 436x |
label = partlabels, |
924 | 436x |
nm = nms, |
925 | 436x |
baselines = newbaselines, |
926 | 436x |
splval = splvals, |
927 | 436x |
SIMPLIFY = FALSE |
928 |
)) |
|
929 | ||
930 |
# Setting the kids section separator if they inherits VTableTree |
|
931 | 428x |
inner <- .set_kids_section_div( |
932 | 428x |
inner, |
933 | 428x |
trailing_section_div_char = spl_section_div(spl), |
934 | 428x |
allowed_class = "VTableTree" |
935 |
) |
|
936 | ||
937 |
## This is where we need to build the structural tables |
|
938 |
## even if they are invisible because their labels are not |
|
939 |
## not shown. |
|
940 | 428x |
innertab <- TableTree( |
941 | 428x |
kids = inner, |
942 | 428x |
name = obj_name(spl), |
943 | 428x |
labelrow = LabelRow( |
944 | 428x |
label = obj_label(spl), |
945 | 428x |
vis = isTRUE(vis_label(spl)) |
946 |
), |
|
947 | 428x |
cinfo = cinfo, |
948 | 428x |
iscontent = FALSE, |
949 | 428x |
indent_mod = indent_mod(spl), |
950 | 428x |
page_title = ptitle_prefix(spl) |
951 |
) |
|
952 |
## kids = inner |
|
953 | 428x |
kids <- list(innertab) |
954 | 428x |
kids |
955 |
} |
|
956 |
) |
|
957 | ||
958 |
context_df_row <- function(split = character(), |
|
959 |
value = character(), |
|
960 |
full_parent_df = list(), |
|
961 |
cinfo = NULL) { |
|
962 | 3025x |
ret <- data.frame( |
963 | 3025x |
split = split, |
964 | 3025x |
value = value, |
965 | 3025x |
full_parent_df = I(full_parent_df), |
966 |
# parent_cold_inds = I(parent_col_inds), |
|
967 | 3025x |
stringsAsFactors = FALSE |
968 |
) |
|
969 | 3025x |
if (nrow(ret) > 0) { |
970 | 3012x |
ret$all_cols_n <- nrow(full_parent_df[[1]]) |
971 |
} else { |
|
972 | 13x |
ret$all_cols_n <- integer() ## should this be numeric??? This never happens |
973 |
} |
|
974 | ||
975 | 3025x |
if (!is.null(cinfo)) { |
976 | 1612x |
if (nrow(ret) > 0) { |
977 | 1603x |
colcols <- as.data.frame(lapply(col_exprs(cinfo), function(e) { |
978 | 5614x |
vals <- eval(e, envir = full_parent_df[[1]]) |
979 | 5614x |
if (identical(vals, TRUE)) { |
980 | 549x |
vals <- rep(vals, length.out = nrow(full_parent_df[[1]])) |
981 |
} |
|
982 | 5614x |
I(list(vals)) |
983 |
})) |
|
984 |
} else { |
|
985 | 9x |
colcols <- as.data.frame(rep(list(logical()), ncol(cinfo))) |
986 |
} |
|
987 | 1612x |
names(colcols) <- names(col_exprs(cinfo)) |
988 | 1612x |
ret <- cbind(ret, colcols) |
989 |
} |
|
990 | 3025x |
ret |
991 |
} |
|
992 | ||
993 |
recursive_applysplit <- function(df, |
|
994 |
lvl = 0L, |
|
995 |
alt_df, |
|
996 |
splvec, |
|
997 |
name, |
|
998 |
# label, |
|
999 |
make_lrow = NA, |
|
1000 |
partlabel = "", |
|
1001 |
cinfo, |
|
1002 |
parent_cfun = NULL, |
|
1003 |
cformat = NULL, |
|
1004 |
cna_str = NA_character_, |
|
1005 |
cindent_mod = 0L, |
|
1006 |
cextra_args = list(), |
|
1007 |
cvar = NULL, |
|
1008 |
baselines = lapply( |
|
1009 |
col_extra_args(cinfo), |
|
1010 |
function(x) x$.ref_full |
|
1011 |
), |
|
1012 |
spl_context = context_df_row(cinfo = cinfo), |
|
1013 |
no_outer_tbl = FALSE, |
|
1014 |
parent_sect_split = NA_character_) { |
|
1015 |
## pre-existing table was added to the layout |
|
1016 | 1612x |
if (length(splvec) == 1L && is(splvec[[1]], "VTableNodeInfo")) { |
1017 | 1x |
return(splvec[[1]]) |
1018 |
} |
|
1019 | ||
1020 |
## the content function is the one from the PREVIOUS |
|
1021 |
## split, i.e. the one whose children we are now constructing |
|
1022 |
## this is a bit annoying but makes the semantics for |
|
1023 |
## declaring layouts much more sane. |
|
1024 | 1611x |
ctab <- .make_ctab(df, |
1025 | 1611x |
lvl = lvl, |
1026 | 1611x |
name = name, |
1027 | 1611x |
label = partlabel, |
1028 | 1611x |
cinfo = cinfo, |
1029 | 1611x |
parent_cfun = parent_cfun, |
1030 | 1611x |
format = cformat, |
1031 | 1611x |
na_str = cna_str, |
1032 | 1611x |
indent_mod = cindent_mod, |
1033 | 1611x |
cvar = cvar, |
1034 | 1611x |
alt_df = alt_df, |
1035 | 1611x |
extra_args = cextra_args, |
1036 | 1611x |
spl_context = spl_context |
1037 |
) |
|
1038 | ||
1039 | 1610x |
nonroot <- lvl != 0L |
1040 | ||
1041 | 1610x |
if (is.na(make_lrow)) { |
1042 | 1304x |
make_lrow <- if (nrow(ctab) > 0 || !nzchar(partlabel)) FALSE else TRUE |
1043 |
} |
|
1044 |
## never print an empty row label for root. |
|
1045 | 1610x |
if (make_lrow && partlabel == "" && !nonroot) { |
1046 | 6x |
make_lrow <- FALSE |
1047 |
} |
|
1048 | ||
1049 | 1610x |
if (length(splvec) == 0L) { |
1050 | 99x |
kids <- list() |
1051 | 99x |
imod <- 0L |
1052 | 99x |
spl <- NULL |
1053 |
} else { |
|
1054 | 1511x |
spl <- splvec[[1]] |
1055 | 1511x |
splvec <- splvec[-1] |
1056 | ||
1057 |
## we pass this everything recursive_applysplit received and |
|
1058 |
## it all gets passed around through ... as needed |
|
1059 |
## to the various methods of .make_split_kids |
|
1060 | 1511x |
kids <- .make_split_kids( |
1061 | 1511x |
spl = spl, |
1062 | 1511x |
df = df, |
1063 | 1511x |
alt_df = alt_df, |
1064 | 1511x |
lvl = lvl, |
1065 | 1511x |
splvec = splvec, |
1066 | 1511x |
name = name, |
1067 | 1511x |
make_lrow = make_lrow, |
1068 | 1511x |
partlabel = partlabel, |
1069 | 1511x |
cinfo = cinfo, |
1070 | 1511x |
parent_cfun = parent_cfun, |
1071 | 1511x |
cformat = cformat, |
1072 | 1511x |
cindent_mod = cindent_mod, |
1073 | 1511x |
cextra_args = cextra_args, cvar = cvar, |
1074 | 1511x |
baselines = baselines, |
1075 | 1511x |
spl_context = spl_context, |
1076 | 1511x |
have_controws = nrow(ctab) > 0 |
1077 |
) |
|
1078 | 1481x |
imod <- 0L |
1079 |
} ## end length(splvec) |
|
1080 | ||
1081 | 1580x |
if (is.na(make_lrow)) { |
1082 | ! |
make_lrow <- if (nrow(ctab) > 0 || !nzchar(partlabel)) FALSE else TRUE |
1083 |
} |
|
1084 |
## never print an empty row label for root. |
|
1085 | 1580x |
if (make_lrow && partlabel == "" && !nonroot) { |
1086 | ! |
make_lrow <- FALSE |
1087 |
} |
|
1088 | ||
1089 |
## this is only true when called from build_table and the first split |
|
1090 |
## in (one of the) SplitVector is NOT an AnalyzeMultiVars split. |
|
1091 |
## in that case we would be "double creating" the structural |
|
1092 |
## subtable |
|
1093 | 1580x |
if (no_outer_tbl) { |
1094 | 287x |
ret <- kids[[1]] |
1095 | 287x |
indent_mod(ret) <- indent_mod(spl) |
1096 | 1293x |
} else if (nrow(ctab) > 0L || length(kids) > 0L) { |
1097 |
## previously we checked if the child had an identical label |
|
1098 |
## but I don't think thats needed anymore. |
|
1099 | 1293x |
tlabel <- partlabel |
1100 | 1293x |
ret <- TableTree( |
1101 | 1293x |
cont = ctab, |
1102 | 1293x |
kids = kids, |
1103 | 1293x |
name = name, |
1104 | 1293x |
label = tlabel, # partlabel, |
1105 | 1293x |
lev = lvl, |
1106 | 1293x |
iscontent = FALSE, |
1107 | 1293x |
labelrow = LabelRow( |
1108 | 1293x |
lev = lvl, |
1109 | 1293x |
label = tlabel, |
1110 | 1293x |
cinfo = cinfo, |
1111 | 1293x |
vis = make_lrow |
1112 |
), |
|
1113 | 1293x |
cinfo = cinfo, |
1114 | 1293x |
indent_mod = imod |
1115 |
) |
|
1116 |
} else { |
|
1117 | ! |
ret <- NULL |
1118 |
} |
|
1119 | ||
1120 |
## if(!is.null(spl) && !is.na(spl_section_sep(spl))) |
|
1121 |
## ret <- apply_kids_section_sep(ret, spl_section_sep(spl)) |
|
1122 |
## ## message(sprintf("indent modifier: %d", indentmod)) |
|
1123 |
## if(!is.null(ret)) |
|
1124 |
## indent_mod(ret) = indentmod |
|
1125 | 1580x |
ret |
1126 |
} |
|
1127 | ||
1128 |
#' Create a table from a layout and data |
|
1129 |
#' |
|
1130 |
#' Layouts are used to describe a table pre-data. `build_table` is used to create a table |
|
1131 |
#' using a layout and a dataset. |
|
1132 |
#' |
|
1133 |
#' @inheritParams gen_args |
|
1134 |
#' @inheritParams lyt_args |
|
1135 |
#' @param col_counts (`numeric` or `NULL`)\cr `r lifecycle::badge("deprecated")` if non-`NULL`, column counts |
|
1136 |
#' *for leaf-columns only* which override those calculated automatically during tabulation. Must specify |
|
1137 |
#' "counts" for *all* leaf-columns if non-`NULL`. `NA` elements will be replaced with the automatically |
|
1138 |
#' calculated counts. Turns on display of leaf-column counts when non-`NULL`. |
|
1139 |
#' @param col_total (`integer(1)`)\cr the total observations across all columns. Defaults to `nrow(df)`. |
|
1140 |
#' @param ... ignored. |
|
1141 |
#' |
|
1142 |
#' @details |
|
1143 |
#' When `alt_counts_df` is specified, column counts are calculated by applying the exact column subsetting |
|
1144 |
#' expressions determined when applying column splitting to the main data (`df`) to `alt_counts_df` and |
|
1145 |
#' counting the observations in each resulting subset. |
|
1146 |
#' |
|
1147 |
#' In particular, this means that in the case of splitting based on cuts of the data, any dynamic cuts will have |
|
1148 |
#' been calculated based on `df` and simply re-used for the count calculation. |
|
1149 |
#' |
|
1150 |
#' @note |
|
1151 |
#' When overriding the column counts or totals care must be taken that, e.g., `length()` or `nrow()` are not called |
|
1152 |
#' within tabulation functions, because those will NOT give the overridden counts. Writing/using tabulation |
|
1153 |
#' functions which accept `.N_col` and `.N_total` or do not rely on column counts at all (even implicitly) is the |
|
1154 |
#' only way to ensure overridden counts are fully respected. |
|
1155 |
#' |
|
1156 |
#' @return A `TableTree` or `ElementaryTable` object representing the table created by performing the tabulations |
|
1157 |
#' declared in `lyt` to the data `df`. |
|
1158 |
#' |
|
1159 |
#' @examples |
|
1160 |
#' lyt <- basic_table() %>% |
|
1161 |
#' split_cols_by("Species") %>% |
|
1162 |
#' analyze("Sepal.Length", afun = function(x) { |
|
1163 |
#' list( |
|
1164 |
#' "mean (sd)" = rcell(c(mean(x), sd(x)), format = "xx.xx (xx.xx)"), |
|
1165 |
#' "range" = diff(range(x)) |
|
1166 |
#' ) |
|
1167 |
#' }) |
|
1168 |
#' lyt |
|
1169 |
#' |
|
1170 |
#' tbl <- build_table(lyt, iris) |
|
1171 |
#' tbl |
|
1172 |
#' |
|
1173 |
#' # analyze multiple variables |
|
1174 |
#' lyt2 <- basic_table() %>% |
|
1175 |
#' split_cols_by("Species") %>% |
|
1176 |
#' analyze(c("Sepal.Length", "Petal.Width"), afun = function(x) { |
|
1177 |
#' list( |
|
1178 |
#' "mean (sd)" = rcell(c(mean(x), sd(x)), format = "xx.xx (xx.xx)"), |
|
1179 |
#' "range" = diff(range(x)) |
|
1180 |
#' ) |
|
1181 |
#' }) |
|
1182 |
#' |
|
1183 |
#' tbl2 <- build_table(lyt2, iris) |
|
1184 |
#' tbl2 |
|
1185 |
#' |
|
1186 |
#' # an example more relevant for clinical trials with column counts |
|
1187 |
#' lyt3 <- basic_table(show_colcounts = TRUE) %>% |
|
1188 |
#' split_cols_by("ARM") %>% |
|
1189 |
#' analyze("AGE", afun = function(x) { |
|
1190 |
#' setNames(as.list(fivenum(x)), c( |
|
1191 |
#' "minimum", "lower-hinge", "median", |
|
1192 |
#' "upper-hinge", "maximum" |
|
1193 |
#' )) |
|
1194 |
#' }) |
|
1195 |
#' |
|
1196 |
#' tbl3 <- build_table(lyt3, DM) |
|
1197 |
#' tbl3 |
|
1198 |
#' |
|
1199 |
#' tbl4 <- build_table(lyt3, subset(DM, AGE > 40)) |
|
1200 |
#' tbl4 |
|
1201 |
#' |
|
1202 |
#' # with column counts calculated based on different data |
|
1203 |
#' miniDM <- DM[sample(1:NROW(DM), 100), ] |
|
1204 |
#' tbl5 <- build_table(lyt3, DM, alt_counts_df = miniDM) |
|
1205 |
#' tbl5 |
|
1206 |
#' |
|
1207 |
#' tbl6 <- build_table(lyt3, DM, col_counts = 1:3) |
|
1208 |
#' tbl6 |
|
1209 |
#' |
|
1210 |
#' @author Gabriel Becker |
|
1211 |
#' @export |
|
1212 |
build_table <- function(lyt, df, |
|
1213 |
alt_counts_df = NULL, |
|
1214 |
col_counts = NULL, |
|
1215 |
col_total = if (is.null(alt_counts_df)) nrow(df) else nrow(alt_counts_df), |
|
1216 |
topleft = NULL, |
|
1217 |
hsep = default_hsep(), |
|
1218 |
...) { |
|
1219 | 346x |
if (!is(lyt, "PreDataTableLayouts")) { |
1220 | ! |
stop( |
1221 | ! |
"lyt must be a PreDataTableLayouts object. Got object of class ", |
1222 | ! |
class(lyt) |
1223 |
) |
|
1224 |
} |
|
1225 | ||
1226 |
## if no columns are defined (e.g. because lyt is NULL) |
|
1227 |
## add a single overall column as the "most basic" |
|
1228 |
## table column structure that makes sense |
|
1229 | 346x |
clyt <- clayout(lyt) |
1230 | 346x |
if (length(clyt) == 1 && length(clyt[[1]]) == 0) { |
1231 | 106x |
clyt[[1]] <- add_overall_col(clyt[[1]], "") |
1232 | 106x |
clayout(lyt) <- clyt |
1233 |
} |
|
1234 | ||
1235 |
## do checks and defensive programming now that we have the data |
|
1236 | 346x |
lyt <- fix_dyncuts(lyt, df) |
1237 | 346x |
lyt <- set_def_child_ord(lyt, df) |
1238 | 345x |
lyt <- fix_analyze_vis(lyt) |
1239 | 345x |
df <- fix_split_vars(lyt, df, char_ok = is.null(col_counts)) |
1240 | 336x |
alt_params <- check_afun_cfun_params(lyt, c(".alt_df", ".alt_df_row")) |
1241 | 336x |
if (any(alt_params) && is.null(alt_counts_df)) { |
1242 | 2x |
stop( |
1243 | 2x |
"Layout contains afun/cfun functions that have optional parameters ", |
1244 | 2x |
".alt_df and/or .alt_df_row, but no alt_counts_df was provided in ", |
1245 | 2x |
"build_table()." |
1246 |
) |
|
1247 |
} |
|
1248 | ||
1249 | 334x |
rtpos <- TreePos() |
1250 | 334x |
cinfo <- create_colinfo(lyt, df, rtpos, |
1251 | 334x |
counts = col_counts, |
1252 | 334x |
alt_counts_df = alt_counts_df, |
1253 | 334x |
total = col_total, |
1254 | 334x |
topleft |
1255 |
) |
|
1256 | 326x |
if (!is.null(col_counts)) { |
1257 | 3x |
toreplace <- !is.na(col_counts) |
1258 | 3x |
newccs <- col_counts(cinfo) ## old actual counts |
1259 | 3x |
newccs[toreplace] <- col_counts[toreplace] |
1260 | 3x |
col_counts(cinfo) <- newccs |
1261 | 3x |
leaf_paths <- col_paths(cinfo) |
1262 | 3x |
for (pth in leaf_paths) { |
1263 | 21x |
colcount_visible(cinfo, pth) <- TRUE |
1264 |
} |
|
1265 |
} |
|
1266 | 326x |
rlyt <- rlayout(lyt) |
1267 | 326x |
rtspl <- root_spl(rlyt) |
1268 | 326x |
ctab <- .make_ctab(df, 0L, |
1269 | 326x |
alt_df = NULL, |
1270 | 326x |
name = "root", |
1271 | 326x |
label = "", |
1272 | 326x |
cinfo = cinfo, ## cexprs, ctree, |
1273 | 326x |
parent_cfun = content_fun(rtspl), |
1274 | 326x |
format = content_format(rtspl), |
1275 | 326x |
na_str = content_na_str(rtspl), |
1276 | 326x |
indent_mod = 0L, |
1277 | 326x |
cvar = content_var(rtspl), |
1278 | 326x |
extra_args = content_extra_args(rtspl) |
1279 |
) |
|
1280 | ||
1281 | 326x |
kids <- lapply(seq_along(rlyt), function(i) { |
1282 | 350x |
splvec <- rlyt[[i]] |
1283 | 350x |
if (length(splvec) == 0) { |
1284 | 14x |
return(NULL) |
1285 |
} |
|
1286 | 336x |
firstspl <- splvec[[1]] |
1287 | 336x |
nm <- obj_name(firstspl) |
1288 |
## XXX unused, probably shouldn't be? |
|
1289 |
## this seems to be covered by grabbing the partlabel |
|
1290 |
## TODO confirm this |
|
1291 |
## lab <- obj_label(firstspl) |
|
1292 | 336x |
recursive_applysplit( |
1293 | 336x |
df = df, lvl = 0L, |
1294 | 336x |
alt_df = alt_counts_df, |
1295 | 336x |
name = nm, |
1296 | 336x |
splvec = splvec, |
1297 | 336x |
cinfo = cinfo, |
1298 |
## XXX are these ALWAYS right? |
|
1299 | 336x |
make_lrow = label_kids(firstspl), |
1300 | 336x |
parent_cfun = NULL, |
1301 | 336x |
cformat = content_format(firstspl), |
1302 | 336x |
cna_str = content_na_str(firstspl), |
1303 | 336x |
cvar = content_var(firstspl), |
1304 | 336x |
cextra_args = content_extra_args(firstspl), |
1305 | 336x |
spl_context = context_df_row( |
1306 | 336x |
split = "root", value = "root", |
1307 | 336x |
full_parent_df = list(df), |
1308 | 336x |
cinfo = cinfo |
1309 |
), |
|
1310 |
## we DO want the 'outer table' if the first |
|
1311 |
## one is a multi-analyze |
|
1312 | 336x |
no_outer_tbl = !is(firstspl, "AnalyzeMultiVars") |
1313 |
) |
|
1314 |
}) |
|
1315 | 303x |
kids <- kids[!sapply(kids, is.null)] |
1316 | 289x |
if (length(kids) > 0) names(kids) <- sapply(kids, obj_name) |
1317 | ||
1318 |
# top level divisor |
|
1319 | 303x |
if (!is.na(top_level_section_div(lyt))) { |
1320 | 2x |
kids <- lapply(kids, function(first_level_kids) { |
1321 | 4x |
trailing_section_div(first_level_kids) <- top_level_section_div(lyt) |
1322 | 4x |
first_level_kids |
1323 |
}) |
|
1324 |
} |
|
1325 | ||
1326 | 303x |
if (nrow(ctab) == 0L && length(kids) == 1L && is(kids[[1]], "VTableTree")) { |
1327 | 259x |
tab <- kids[[1]] |
1328 | 259x |
main_title(tab) <- main_title(lyt) |
1329 | 259x |
subtitles(tab) <- subtitles(lyt) |
1330 | 259x |
main_footer(tab) <- main_footer(lyt) |
1331 | 259x |
prov_footer(tab) <- prov_footer(lyt) |
1332 | 259x |
header_section_div(tab) <- header_section_div(lyt) |
1333 |
} else { |
|
1334 | 44x |
tab <- TableTree( |
1335 | 44x |
cont = ctab, |
1336 | 44x |
kids = kids, |
1337 | 44x |
lev = 0L, |
1338 | 44x |
name = "root", |
1339 | 44x |
label = "", |
1340 | 44x |
iscontent = FALSE, |
1341 | 44x |
cinfo = cinfo, |
1342 | 44x |
format = obj_format(rtspl), |
1343 | 44x |
na_str = obj_na_str(rtspl), |
1344 | 44x |
title = main_title(lyt), |
1345 | 44x |
subtitles = subtitles(lyt), |
1346 | 44x |
main_footer = main_footer(lyt), |
1347 | 44x |
prov_footer = prov_footer(lyt), |
1348 | 44x |
header_section_div = header_section_div(lyt) |
1349 |
) |
|
1350 |
} |
|
1351 | ||
1352 |
## This seems to be unneeded, not clear what 'top_left' check it refers to |
|
1353 |
## but both top_left taller than column headers and very long topleft are now |
|
1354 |
## allowed, so this is just wasted computation. |
|
1355 | ||
1356 |
## ## this is where the top_left check lives right now. refactor later maybe |
|
1357 |
## ## but now just call it so the error gets thrown when I want it to |
|
1358 |
## unused <- matrix_form(tab) |
|
1359 | 303x |
tab <- update_ref_indexing(tab) |
1360 | 303x |
horizontal_sep(tab) <- hsep |
1361 | 303x |
if (table_inset(lyt) > 0) { |
1362 | 1x |
table_inset(tab) <- table_inset(lyt) |
1363 |
} |
|
1364 | 303x |
tab |
1365 |
} |
|
1366 | ||
1367 |
# fix_split_vars ---- |
|
1368 |
# These checks guarantee that all the split variables are present in the data. |
|
1369 |
# No generic is needed because it is not dependent on the input layout but |
|
1370 |
# on the df. |
|
1371 |
fix_one_split_var <- function(spl, df, char_ok = TRUE) { |
|
1372 | 581x |
var <- spl_payload(spl) |
1373 | 581x |
if (!(var %in% names(df))) { |
1374 | 2x |
stop("Split variable [", var, "] not found in data being tabulated.") |
1375 |
} |
|
1376 | 579x |
varvec <- df[[var]] |
1377 | 579x |
if (!is(varvec, "character") && !is.factor(varvec)) { |
1378 | 1x |
message(sprintf( |
1379 | 1x |
paste( |
1380 | 1x |
"Split var [%s] was not character or factor.", |
1381 | 1x |
"Converting to factor" |
1382 |
), |
|
1383 | 1x |
var |
1384 |
)) |
|
1385 | 1x |
varvec <- factor(varvec) |
1386 | 1x |
df[[var]] <- varvec |
1387 | 578x |
} else if (is(varvec, "character") && !char_ok) { |
1388 | 1x |
stop( |
1389 | 1x |
"Overriding column counts is not supported when splitting on ", |
1390 | 1x |
"character variables.\n Please convert all column split variables to ", |
1391 | 1x |
"factors." |
1392 |
) |
|
1393 |
} |
|
1394 | ||
1395 | 578x |
if (is.factor(varvec)) { |
1396 | 416x |
levs <- levels(varvec) |
1397 |
} else { |
|
1398 | 162x |
levs <- unique(varvec) |
1399 |
} |
|
1400 | 578x |
if (!all(nzchar(levs))) { |
1401 | 4x |
stop( |
1402 | 4x |
"Got empty string level in splitting variable ", var, |
1403 | 4x |
" This is not supported.\nIf display as an empty level is ", |
1404 | 4x |
"desired use a value-labeling variable." |
1405 |
) |
|
1406 |
} |
|
1407 | ||
1408 |
## handle label var |
|
1409 | 574x |
lblvar <- spl_label_var(spl) |
1410 | 574x |
have_lblvar <- !identical(var, lblvar) |
1411 | 574x |
if (have_lblvar) { |
1412 | 88x |
if (!(lblvar %in% names(df))) { |
1413 | 1x |
stop( |
1414 | 1x |
"Value label variable [", lblvar, |
1415 | 1x |
"] not found in data being tabulated." |
1416 |
) |
|
1417 |
} |
|
1418 | 87x |
lblvec <- df[[lblvar]] |
1419 | 87x |
tab <- table(varvec, lblvec) |
1420 | ||
1421 | 87x |
if (any(rowSums(tab > 0) > 1) || any(colSums(tab > 0) > 1)) { |
1422 | 1x |
stop(sprintf( |
1423 | 1x |
paste( |
1424 | 1x |
"There does not appear to be a 1-1", |
1425 | 1x |
"correspondence between values in split var", |
1426 | 1x |
"[%s] and label var [%s]" |
1427 |
), |
|
1428 | 1x |
var, lblvar |
1429 |
)) |
|
1430 |
} |
|
1431 | ||
1432 | 86x |
if (!is(lblvec, "character") && !is.factor(lblvec)) { |
1433 | ! |
message(sprintf( |
1434 | ! |
paste( |
1435 | ! |
"Split label var [%s] was not character or", |
1436 | ! |
"factor. Converting to factor" |
1437 |
), |
|
1438 | ! |
var |
1439 |
)) |
|
1440 | ! |
lblvec <- factor(lblvec) |
1441 | ! |
df[[lblvar]] <- lblvec |
1442 |
} |
|
1443 |
} |
|
1444 | ||
1445 | 572x |
df |
1446 |
} |
|
1447 | ||
1448 |
fix_split_vars <- function(lyt, df, char_ok) { |
|
1449 | 345x |
df <- fix_split_vars_inner(clayout(lyt), df, char_ok = char_ok) |
1450 | 341x |
df <- fix_split_vars_inner(rlayout(lyt), df, char_ok = TRUE) |
1451 | 336x |
df |
1452 | ||
1453 |
## clyt <- clayout(lyt) |
|
1454 |
## rlyt <- rlayout(lyt) |
|
1455 | ||
1456 |
## allspls <- unlist(list(clyt, rlyt)) |
|
1457 |
## VarLevelSplit includes sublclass VarLevWBaselineSplit |
|
1458 |
} |
|
1459 | ||
1460 |
fix_split_vars_inner <- function(lyt, df, char_ok) { |
|
1461 | 686x |
stopifnot(is(lyt, "PreDataAxisLayout")) |
1462 | 686x |
allspls <- unlist(lyt) |
1463 | 686x |
varspls <- allspls[sapply(allspls, is, "VarLevelSplit")] |
1464 | 686x |
unqvarinds <- !duplicated(sapply(varspls, spl_payload)) |
1465 | 686x |
unqvarspls <- varspls[unqvarinds] |
1466 | 581x |
for (spl in unqvarspls) df <- fix_one_split_var(spl, df, char_ok = char_ok) |
1467 | ||
1468 | 677x |
df |
1469 |
} |
|
1470 | ||
1471 |
# set_def_child_ord ---- |
|
1472 |
## the table is built by recursively splitting the data and doing things to each |
|
1473 |
## piece. The order (or even values) of unique(df[[col]]) is not guaranteed to |
|
1474 |
## be the same in all the different partitions. This addresses that. |
|
1475 |
setGeneric( |
|
1476 |
"set_def_child_ord", |
|
1477 | 3946x |
function(lyt, df) standardGeneric("set_def_child_ord") |
1478 |
) |
|
1479 | ||
1480 |
setMethod( |
|
1481 |
"set_def_child_ord", "PreDataTableLayouts", |
|
1482 |
function(lyt, df) { |
|
1483 | 346x |
clayout(lyt) <- set_def_child_ord(clayout(lyt), df) |
1484 | 345x |
rlayout(lyt) <- set_def_child_ord(rlayout(lyt), df) |
1485 | 345x |
lyt |
1486 |
} |
|
1487 |
) |
|
1488 | ||
1489 |
setMethod( |
|
1490 |
"set_def_child_ord", "PreDataAxisLayout", |
|
1491 |
function(lyt, df) { |
|
1492 | 1027x |
lyt@.Data <- lapply(lyt, set_def_child_ord, df = df) |
1493 | 1026x |
lyt |
1494 |
} |
|
1495 |
) |
|
1496 | ||
1497 |
setMethod( |
|
1498 |
"set_def_child_ord", "SplitVector", |
|
1499 |
function(lyt, df) { |
|
1500 | 1068x |
lyt[] <- lapply(lyt, set_def_child_ord, df = df) |
1501 | 1067x |
lyt |
1502 |
} |
|
1503 |
) |
|
1504 | ||
1505 |
## for most split types, don't do anything |
|
1506 |
## becuause their ordering already isn't data-based |
|
1507 |
setMethod( |
|
1508 |
"set_def_child_ord", "ANY", |
|
1509 | 622x |
function(lyt, df) lyt |
1510 |
) |
|
1511 | ||
1512 |
setMethod( |
|
1513 |
"set_def_child_ord", "VarLevelSplit", |
|
1514 |
function(lyt, df) { |
|
1515 | 866x |
if (!is.null(spl_child_order(lyt))) { |
1516 | 285x |
return(lyt) |
1517 |
} |
|
1518 | ||
1519 | 581x |
vec <- df[[spl_payload(lyt)]] |
1520 | 581x |
vals <- if (is.factor(vec)) { |
1521 | 417x |
levels(vec) |
1522 |
} else { |
|
1523 | 164x |
unique(vec) |
1524 |
} |
|
1525 | 581x |
spl_child_order(lyt) <- vals |
1526 | 581x |
lyt |
1527 |
} |
|
1528 |
) |
|
1529 | ||
1530 |
setMethod( |
|
1531 |
"set_def_child_ord", "VarLevWBaselineSplit", |
|
1532 |
function(lyt, df) { |
|
1533 | 17x |
bline <- spl_ref_group(lyt) |
1534 | 17x |
if (!is.null(spl_child_order(lyt)) && match(bline, spl_child_order(lyt), nomatch = -1) == 1L) { |
1535 | 6x |
return(lyt) |
1536 |
} |
|
1537 | ||
1538 | 11x |
if (!is.null(split_fun(lyt))) { |
1539 |
## expensive but sadly necessary, I think |
|
1540 | 3x |
pinfo <- do_split(lyt, df, spl_context = context_df_row()) |
1541 | 3x |
vals <- sort(unlist(value_names(pinfo$values))) |
1542 |
} else { |
|
1543 | 8x |
vec <- df[[spl_payload(lyt)]] |
1544 | 8x |
vals <- if (is.factor(vec)) { |
1545 | 5x |
levels(vec) |
1546 |
} else { |
|
1547 | 3x |
unique(vec) |
1548 |
} |
|
1549 |
} |
|
1550 | 11x |
if (!bline %in% vals) { |
1551 | 1x |
stop(paste0( |
1552 | 1x |
'Reference group "', bline, '"', " was not present in the levels of ", spl_payload(lyt), " in the data." |
1553 |
)) |
|
1554 |
} |
|
1555 | 10x |
spl_child_order(lyt) <- vals |
1556 | 10x |
lyt |
1557 |
} |
|
1558 |
) |
|
1559 | ||
1560 |
splitvec_to_coltree <- function(df, splvec, pos = NULL, |
|
1561 |
lvl = 1L, label = "", |
|
1562 |
spl_context = context_df_row(cinfo = NULL), |
|
1563 |
alt_counts_df = df, |
|
1564 |
global_cc_format) { |
|
1565 | 1789x |
stopifnot( |
1566 | 1789x |
lvl <= length(splvec) + 1L, |
1567 | 1789x |
is(splvec, "SplitVector") |
1568 |
) |
|
1569 | ||
1570 | ||
1571 | 1789x |
if (lvl == length(splvec) + 1L) { |
1572 |
## XXX this should be a LayoutColree I Think. |
|
1573 | 1160x |
nm <- unlist(tail(value_names(pos), 1)) %||% "" |
1574 | 1160x |
spl <- tail(pos_splits(pos), 1)[[1]] |
1575 | 1160x |
fmt <- colcount_format(spl) %||% global_cc_format |
1576 | 1160x |
LayoutColLeaf( |
1577 | 1160x |
lev = lvl - 1L, |
1578 | 1160x |
label = label, |
1579 | 1160x |
tpos = pos, |
1580 | 1160x |
name = nm, |
1581 | 1160x |
colcount = NROW(alt_counts_df), |
1582 | 1160x |
disp_ccounts = disp_ccounts(spl), |
1583 | 1160x |
colcount_format = fmt |
1584 |
) |
|
1585 |
} else { |
|
1586 | 629x |
spl <- splvec[[lvl]] |
1587 | 629x |
nm <- if (is.null(pos) || length(pos_splits(pos)) == 0) { |
1588 | 384x |
obj_name(spl) |
1589 |
} else { |
|
1590 | 245x |
unlist(tail( |
1591 | 245x |
value_names(pos), |
1592 | 245x |
1 |
1593 |
)) |
|
1594 |
} |
|
1595 | 629x |
rawpart <- do_split(spl, df, |
1596 | 629x |
trim = FALSE, |
1597 | 629x |
spl_context = spl_context |
1598 |
) |
|
1599 | 626x |
datparts <- rawpart[["datasplit"]] |
1600 | 626x |
vals <- rawpart[["values"]] |
1601 | 626x |
labs <- rawpart[["labels"]] |
1602 | ||
1603 | 626x |
force(alt_counts_df) |
1604 | 626x |
kids <- mapply( |
1605 | 626x |
function(dfpart, value, partlab) { |
1606 |
## we could pass subset expression in here but the spec |
|
1607 |
## currently doesn't call for it in column space |
|
1608 | 1409x |
newprev <- context_df_row( |
1609 | 1409x |
split = obj_name(spl), |
1610 | 1409x |
value = value_names(value), |
1611 | 1409x |
full_parent_df = list(dfpart), |
1612 | 1409x |
cinfo = NULL |
1613 |
) |
|
1614 |
## subset expressions handled inside make_child_pos, |
|
1615 |
## value is (optionally, for the moment) carrying it around |
|
1616 | 1409x |
newpos <- make_child_pos(pos, spl, value, partlab) |
1617 | 1409x |
acdf_subset_expr <- make_subset_expr(spl, value) |
1618 | 1409x |
new_acdf_subset <- try(eval(acdf_subset_expr, alt_counts_df), silent = TRUE) |
1619 | 1409x |
if (is(new_acdf_subset, "try-error")) { |
1620 | 4x |
stop(sprintf( |
1621 | 4x |
paste( |
1622 | 4x |
ifelse(identical(df, alt_counts_df), "df", "alt_counts_df"), |
1623 | 4x |
"appears incompatible with column-split", |
1624 | 4x |
"structure. Offending column subset", |
1625 | 4x |
"expression: %s\nOriginal error", |
1626 | 4x |
"message: %s" |
1627 | 4x |
), deparse(acdf_subset_expr[[1]]), |
1628 | 4x |
conditionMessage(attr(new_acdf_subset, "condition")) |
1629 |
)) |
|
1630 |
} |
|
1631 | ||
1632 | 1405x |
splitvec_to_coltree(dfpart, splvec, newpos, |
1633 | 1405x |
lvl + 1L, partlab, |
1634 | 1405x |
spl_context = rbind(spl_context, newprev), |
1635 | 1405x |
alt_counts_df = alt_counts_df[new_acdf_subset, , drop = FALSE], |
1636 | 1405x |
global_cc_format = global_cc_format |
1637 |
) |
|
1638 |
}, |
|
1639 | 626x |
dfpart = datparts, value = vals, |
1640 | 626x |
partlab = labs, SIMPLIFY = FALSE |
1641 |
) |
|
1642 | 620x |
disp_cc <- FALSE |
1643 | 620x |
cc_format <- global_cc_format # this doesn't matter probably, but its technically more correct |
1644 | 620x |
if (lvl > 1) { |
1645 | 243x |
disp_cc <- disp_ccounts(splvec[[lvl - 1]]) |
1646 | 243x |
cc_format <- colcount_format(splvec[[lvl - 1]]) %||% global_cc_format |
1647 |
} |
|
1648 | ||
1649 | 620x |
names(kids) <- value_names(vals) |
1650 | 620x |
LayoutColTree( |
1651 | 620x |
lev = lvl, label = label, |
1652 | 620x |
spl = spl, |
1653 | 620x |
kids = kids, tpos = pos, |
1654 | 620x |
name = nm, |
1655 | 620x |
summary_function = content_fun(spl), |
1656 | 620x |
colcount = NROW(alt_counts_df), |
1657 | 620x |
disp_ccounts = disp_cc, |
1658 | 620x |
colcount_format = cc_format |
1659 |
) |
|
1660 |
} |
|
1661 |
} |
|
1662 | ||
1663 |
# fix_analyze_vis ---- |
|
1664 |
## now that we know for sure the number of siblings |
|
1665 |
## collaplse NAs to TRUE/FALSE for whether |
|
1666 |
## labelrows should be visible for ElementaryTables |
|
1667 |
## generatead from analyzing a single variable |
|
1668 | 1059x |
setGeneric("fix_analyze_vis", function(lyt) standardGeneric("fix_analyze_vis")) |
1669 | ||
1670 |
setMethod( |
|
1671 |
"fix_analyze_vis", "PreDataTableLayouts", |
|
1672 |
function(lyt) { |
|
1673 | 345x |
rlayout(lyt) <- fix_analyze_vis(rlayout(lyt)) |
1674 | 345x |
lyt |
1675 |
} |
|
1676 |
) |
|
1677 | ||
1678 |
setMethod( |
|
1679 |
"fix_analyze_vis", "PreDataRowLayout", |
|
1680 |
function(lyt) { |
|
1681 | 345x |
splvecs <- lapply(lyt, fix_analyze_vis) |
1682 | 345x |
PreDataRowLayout( |
1683 | 345x |
root = root_spl(lyt), |
1684 | 345x |
lst = splvecs |
1685 |
) |
|
1686 |
} |
|
1687 |
) |
|
1688 | ||
1689 |
setMethod( |
|
1690 |
"fix_analyze_vis", "SplitVector", |
|
1691 |
function(lyt) { |
|
1692 | 369x |
len <- length(lyt) |
1693 | 369x |
if (len == 0) { |
1694 | 14x |
return(lyt) |
1695 |
} |
|
1696 | 355x |
lastspl <- lyt[[len]] |
1697 | 355x |
if (!(is(lastspl, "VAnalyzeSplit") || is(lastspl, "AnalyzeMultivar"))) { |
1698 | 74x |
return(lyt) |
1699 |
} |
|
1700 | ||
1701 | 281x |
if (is(lastspl, "VAnalyzeSplit") && is.na(labelrow_visible(lastspl))) { |
1702 |
## labelrow_visible(lastspl) = FALSE |
|
1703 | 275x |
labelrow_visible(lastspl) <- "hidden" |
1704 | 6x |
} else if (is(lastspl, "AnalyzeMultiVar")) { |
1705 | ! |
pld <- spl_payload(lastspl) |
1706 | ! |
newpld <- lapply(pld, function(sp, havesibs) { |
1707 | ! |
if (is.na(labelrow_visible(sp))) { |
1708 | ! |
labelrow_visible(sp) <- havesibs |
1709 |
} |
|
1710 | ! |
}, havesibs = len > 1) |
1711 | ! |
spl_payload(lastspl) <- newpld |
1712 |
## pretty sure this isn't needed... |
|
1713 | ! |
if (is.na(label_kids(lastspl))) { |
1714 | ! |
label_kids(lastspl) <- len > 1 |
1715 |
} |
|
1716 |
} |
|
1717 | 281x |
lyt[[len]] <- lastspl |
1718 | 281x |
lyt |
1719 |
} |
|
1720 |
) |
|
1721 | ||
1722 |
# check_afun_cfun_params ---- |
|
1723 | ||
1724 |
# This checks if the input params are used anywhere in cfun/afun |
|
1725 |
setGeneric("check_afun_cfun_params", function(lyt, params) { |
|
1726 | 3345x |
standardGeneric("check_afun_cfun_params") |
1727 |
}) |
|
1728 | ||
1729 |
setMethod( |
|
1730 |
"check_afun_cfun_params", "PreDataTableLayouts", |
|
1731 |
function(lyt, params) { |
|
1732 |
# clayout does not have analysis functions |
|
1733 | 336x |
check_afun_cfun_params(rlayout(lyt), params) |
1734 |
} |
|
1735 |
) |
|
1736 | ||
1737 |
setMethod( |
|
1738 |
"check_afun_cfun_params", "PreDataRowLayout", |
|
1739 |
function(lyt, params) { |
|
1740 | 336x |
ro_spl_parm_l <- check_afun_cfun_params(root_spl(lyt), params) |
1741 | 336x |
r_spl_parm_l <- lapply(lyt, check_afun_cfun_params, params = params) |
1742 | 336x |
Reduce(`|`, c(list(ro_spl_parm_l), r_spl_parm_l)) |
1743 |
} |
|
1744 |
) |
|
1745 | ||
1746 |
# Main function for checking parameters |
|
1747 |
setMethod( |
|
1748 |
"check_afun_cfun_params", "SplitVector", |
|
1749 |
function(lyt, params) { |
|
1750 | 803x |
param_l <- lapply(lyt, check_afun_cfun_params, params = params) |
1751 | 803x |
Reduce(`|`, param_l) |
1752 |
} |
|
1753 |
) |
|
1754 | ||
1755 |
# Helper function for check_afun_cfun_params |
|
1756 |
.afun_cfun_switch <- function(spl_i) { |
|
1757 | 1869x |
if (is(spl_i, "VAnalyzeSplit")) { |
1758 | 628x |
analysis_fun(spl_i) |
1759 |
} else { |
|
1760 | 1241x |
content_fun(spl_i) |
1761 |
} |
|
1762 |
} |
|
1763 | ||
1764 |
# Extreme case that happens only when using add_existing_table |
|
1765 |
setMethod( |
|
1766 |
"check_afun_cfun_params", "VTableTree", |
|
1767 |
function(lyt, params) { |
|
1768 | 1x |
setNames(logical(length(params)), params) # All FALSE |
1769 |
} |
|
1770 |
) |
|
1771 | ||
1772 |
setMethod( |
|
1773 |
"check_afun_cfun_params", "Split", |
|
1774 |
function(lyt, params) { |
|
1775 |
# Extract function in the split |
|
1776 | 1869x |
fnc <- .afun_cfun_switch(lyt) |
1777 | ||
1778 |
# For each parameter, check if it is called |
|
1779 | 1869x |
sapply(params, function(pai) any(unlist(func_takes(fnc, pai)))) |
1780 |
} |
|
1781 |
) |
|
1782 | ||
1783 |
# Helper functions ---- |
|
1784 | ||
1785 | 231x |
count <- function(df, ...) NROW(df) |
1786 | ||
1787 |
guess_format <- function(val) { |
|
1788 | 1054x |
if (length(val) == 1) { |
1789 | 1042x |
if (is.integer(val) || !is.numeric(val)) { |
1790 | 226x |
"xx" |
1791 |
} else { |
|
1792 | 816x |
"xx.xx" |
1793 |
} |
|
1794 | 12x |
} else if (length(val) == 2) { |
1795 | 12x |
"xx.x / xx.x" |
1796 | ! |
} else if (length(val) == 3) { |
1797 | ! |
"xx.x (xx.x - xx.x)" |
1798 |
} else { |
|
1799 | ! |
stop("got value of length > 3") |
1800 |
} |
|
1801 |
} |
|
1802 | ||
1803 |
.quick_afun <- function(afun, lbls) { |
|
1804 | 14x |
if (.takes_df(afun)) { |
1805 | 5x |
function(df, .spl_context, ...) { |
1806 | 226x |
if (!is.null(lbls) && length(lbls) == 1 && is.na(lbls)) { |
1807 | 222x |
lbls <- tail(.spl_context$value, 1) |
1808 |
} |
|
1809 | 226x |
if (".spl_context" %in% names(formals(afun))) { |
1810 | ! |
res <- afun(df = df, .spl_context = .spl_context, ...) |
1811 |
} else { |
|
1812 | 226x |
res <- afun(df = df, ...) |
1813 |
} |
|
1814 | 226x |
if (is(res, "RowsVerticalSection")) { |
1815 | ! |
ret <- res |
1816 |
} else { |
|
1817 | 226x |
if (!is.list(res)) { |
1818 | 226x |
ret <- rcell(res, label = lbls, format = guess_format(res)) |
1819 |
} else { |
|
1820 | ! |
if (!is.null(lbls) && length(lbls) == length(res) && all(!is.na(lbls))) { |
1821 | ! |
names(res) <- lbls |
1822 |
} |
|
1823 | ! |
ret <- in_rows(.list = res, .labels = names(res), .formats = vapply(res, guess_format, "")) |
1824 |
} |
|
1825 |
} |
|
1826 | 226x |
ret |
1827 |
} |
|
1828 |
} else { |
|
1829 | 9x |
function(x, .spl_context, ...) { |
1830 | 387x |
if (!is.null(lbls) && length(lbls) == 1 && is.na(lbls)) { |
1831 | 225x |
lbls <- tail(.spl_context$value, 1) |
1832 |
} |
|
1833 | 387x |
if (".spl_context" %in% names(formals(afun))) { |
1834 | ! |
res <- afun(x = x, .spl_context = .spl_context, ...) |
1835 |
} else { |
|
1836 | 387x |
res <- afun(x = x, ...) |
1837 |
} |
|
1838 | 387x |
if (is(res, "RowsVerticalSection")) { |
1839 | ! |
ret <- res |
1840 |
} else { |
|
1841 | 387x |
if (!is.list(res)) { |
1842 | 297x |
ret <- rcell(res, label = lbls, format = guess_format(res)) |
1843 |
} else { |
|
1844 | 90x |
if (!is.null(lbls) && length(lbls) == length(res) && all(!is.na(lbls))) { |
1845 | 9x |
names(res) <- lbls |
1846 |