From b773d35954ba5c3a7dddc0ec3c113d25e104c2ca Mon Sep 17 00:00:00 2001 From: Gabe Becker Date: Thu, 23 Oct 2025 14:13:55 -0700 Subject: [PATCH 1/7] vignette on translating shells to layouts --- ...guided_intermediate_translating_shells.Rmd | 974 ++++++++++++++++++ 1 file changed, 974 insertions(+) create mode 100644 vignettes/guided_intermediate_translating_shells.Rmd diff --git a/vignettes/guided_intermediate_translating_shells.Rmd b/vignettes/guided_intermediate_translating_shells.Rmd new file mode 100644 index 0000000000..27104994f1 --- /dev/null +++ b/vignettes/guided_intermediate_translating_shells.Rmd @@ -0,0 +1,974 @@ +--- +title: "Intermediate rtables - Translating Shells To Layouts" +subtitle: Contributed by Johnson & Johnson Innovative Medicine +date: "2025-06-17" +author: +- Gabriel Becker +- Dan Hofstaedter +output: + rmarkdown::html_document: + theme: "spacelab" + highlight: "kate" + toc: true + toc_float: true +vignette: > + %\VignetteIndexEntry{Intermediate rtables - Translating Shells To Layouts} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +editor_options: + markdown: + wrap: 72 + chunk_output_type: console +--- + +# Introduction + +The first - and often largest - hurdle to creating a table via +`rtables` is translating the desired table structure (typically in the +form of a *table shell*) into an `rtables` *layout*. We will cover +this translation step here. + +## A Table Shell + +Table shells can come in various forms. We will begin with a table +shell which is essentially the entire table with desired formatting +indicated instead of values: + +```{r init, echo = FALSE, results = "hidden"} +library(rtables) +library(dplyr) + +fixed_shell <- function(tt) { + mystr <- table_shell_str(tt) + regex_hits <- gregexpr("[(]N=[[:digit:]]+[)]", mystr)[[1]] + hit_lens <- attr(regex_hits, "match.length") + if (regex_hits[1] > 0) { + for (i in seq_along(regex_hits)) { + start <- regex_hits[i] + len <- hit_lens[i] + substr(mystr, start, start + len - 1) <- padstr("(N=xx)", len, just = "center") + } + } + cat(mystr) +} + +knitr::opts_chunk$set(comment = "") +``` + + +```{r, eval = TRUE, echo = FALSE, result = "asis"} +adsl <- ex_adsl %>% + filter(SEX %in% c("M", "F") & RACE %in% c("ASIAN", "BLACK OR AFRICAN AMERICAN", "WHITE")) %>% + mutate( + BMEASIFL = factor(as.character(BMEASIFL), + levels = c("Y", "N"), + labels = c("Yes", "No") + ), + SEX = factor(as.character(SEX), + levels = c("M", "F", "UNDIFFERENTIATED", "U"), + labels = c("Male", "Female", "Undifferentiated", "Unknown") + ), + RACE = factor(as.character(RACE), + levels = c("ASIAN", "BLACK OR AFRICAN AMERICAN", "WHITE"), + labels = c("Asian", "Black", "White") + ) + ) + + +lyt <- basic_table( + title = "Subject Response by Race and Sex; Treated Subjects", + show_colcounts = TRUE +) %>% + split_cols_by("STRATA1", split_fun = keep_split_levels(only = c("A", "B"))) %>% + split_cols_by("ARM", split_fun = keep_split_levels(only = c("A: Drug X", "B: Placebo"))) %>% + analyze("BMEASIFL", afun = counts_wpcts, var_labels = "All Patients", show_labels = "visible") %>% + split_rows_by( + var = "RACE", + label_pos = "topleft", + split_fun = keep_split_levels(only = c("Asian", "Black", "White")) + ) %>% + split_rows_by( + var = "SEX", + label_pos = "topleft", + split_fun = keep_split_levels(only = c("Male", "Female")) + ) %>% + summarize_row_groups( + var = "SEX", + format = "xx" + ) %>% + analyze( + vars = "BMEASIFL", + afun = counts_wpcts + ) + +result <- build_table(lyt, adsl) +fixed_shell(result) +``` + +We will use this shell to illustrate the translation process to an +rtables layout, and thus ultimately a table output. + +## A *Brief* Review Of `rtables` Layouts + +For an in-depth discussion of how constructing a layout works we refer +the reader to other documentation. That said, there are a couple +things to remember as we consider translating shells into layouts: + +1. Individual rows are declared by `analyze*` calls +2. Individual columns are the result of column faceting +3. New faceting will be nested within existing faceting in the same dimension (row/col) by default +4. All row faceting structures must be terminated with at least one analysis (`analyze`) +5. Row faceting which occurs directly after an `analyze` will *not* be nested + +With those in mind, we will now discuss how to translate shells into layouts. + +# Translating + +There are three aspects to a shell that we must translate: + +1. Column faceting structure +2. Row faceting structure +3. Cell contents + - Marginal content for row facet structure + - Individual facet content + +We will explore each portion of the translation process separately. + +## Translating Column Structure + +Our first task, translating column structure, revolves around +identifying faceting in the column dimension of a shell or desired +table. + +Our shell gives us the following to indicate column structure: + +```{r, eval = TRUE, echo = FALSE, result="asis"} +fixed_shell(result[0, ]) +``` + +The easiest way to identify faceting is to look at column- or +row-labels and determine the scope (i.e., the set of individual +columns or rows) they apply to. + +For example, we see that the `"A"` column label applies to a group of +multiple columns each of which represent an individual arm: + +```{r} +fixed_shell(result[0, c("A", "*")]) +``` + +Thus we have strata faceting with arm faceting nested within it. + +Faceting most commonly represents partitioning the data being +tabulated by the values of a categorical variable, though `rtables` +supports a generalized concept of faceting where the data group can +overlap and need not be exhaustive. + +For our table, the faceting is nested faceting by the `"STRATA1"`, and +`"ARM"` variables. We achieve this by repeated calls to +`split_cols_by` (with the default `nested = TRUE`), with the call declaring +the outermost faceting first: + + +```{r} +lyt_cols <- basic_table() |> + split_cols_by("STRATA1", split_fun = keep_split_levels(only = c("A", "B"))) %>% + split_cols_by("ARM", split_fun = keep_split_levels(only = c("A: Drug X", "B: Placebo"))) + +build_table(lyt_cols, adsl) +``` + +This is almost correct. To fully achieve our shell we need the column +counts to show up, which we do via the `show_colcounts` argument in +the relevant `split_cols_by` call: + +```{r} +lyt_cols <- basic_table() |> + split_cols_by("STRATA1", split_fun = keep_split_levels(only = c("A", "B"))) %>% + split_cols_by("ARM", + split_fun = keep_split_levels(only = c("A: Drug X", "B: Placebo")), + show_colcounts = TRUE + ) + +build_table(lyt_cols, adsl) +``` + + +This is a relatively straightforward column structure. We will cover +more complex ones later. Nevertheless we have translated our shell's +column space into `rtables` layouting instructions. + + +## Translating Row Structure + +Moving to the second aspect of translation, we will now translate the +row structure of our shell. Interpreting row structure is similar to +interpreting column structure with the caveat that individual rows do +not come from faceting, but rather from analysis (which is in charge +of populating the contents of the table's primary, non-marginal +cells). + +Our row structure is slightly less trivial than our column +structure. We can see two sections in our shell, one that displays the +response (`"BMEASIFL"`) of all patients collectively (by the column +structure): + + +```{r, echo = FALSE} +fixed_shell(tt_at_path(result, "BMEASIFL")) +``` + +and one that subsets the patients before displaying the response +within each subset, and with some marginal rows for context. + +```{r, echo = FALSE} +fixed_shell(tt_at_path(result, "RACE")) +``` + +Because none of the labels or cell-values from the all patients +portion of the table apply directly to the subset analysis portion - +and vice versa - we can treat these separately. + +In point of fact, the first portion does not require any structure +beyond an analysis of the `"BMEASIFL" variable with a label, so we can +leave that for the third translation step. + +We can illustrate this using a dummy analyze as follows: + +```{r} +dummy_afun <- function(x, ...) in_rows("Analysis" = "-") +lyt_a <- basic_table() |> + analyze("BMEASIFL", + afun = dummy_afun, + var_labels = "All Patients", + show_labels = "visible" + ) + +build_table(lyt_a, adsl) +``` + +While we do not have the individual rows we desired, as that is left +to step 3 of translation, we can see that we have successfully created +the first portion of the row *structure*. + +Note that in most tables the column and row structure are orthogonal +and so we do not need to worry about columns when we are translating +the row structure. + +Also note we *could* say that there is a facet there which contains all the +patients and has the name/label `"All Patients"`; this would result in +an equivalent table from an output perspective but there isn't really +any benefit to the added layouting instructions that would be +required, so we will not do so here. + +The second portion of the table contains labels and rows which *do* +apply to multiple individual rows. + +We see that the `"Asian"` label, for example, applies across the +corresponding `"Male"` and `"Female"` labels/marginal rows, each of +which in turn applies to a group of individual rows (`"Yes"`, and +`"No"`). + +Thus we can recreate this section via nested faceting, this time with + +```{r} +lyt_b <- basic_table() |> + split_rows_by("RACE") |> + split_rows_by("SEX") |> + analyze("BMEASIFL", afun = dummy_afun) + +head(build_table(lyt_b, adsl), 30) +``` + +We are almost there, but we see extra `"SEX"` values that weren't in +our shell. We can prevent this with the `keep_split_levels` function +provided by `rtables`: + +```{r} +lyt_b2 <- basic_table() |> + split_rows_by("RACE") |> + split_rows_by("SEX", split_fun = keep_split_levels(only = c("Male", "Female"))) |> + analyze("BMEASIFL", afun = dummy_afun) + + +build_table(lyt_b2, adsl) +``` + +Finally, we can combine the two sections by simply combining the relevant layout instructions: + + +```{r} +lyt_b3 <- basic_table() |> + analyze("BMEASIFL", + afun = dummy_afun, + var_labels = "All Patients", + show_labels = "visible" + ) |> + split_rows_by("RACE") |> + split_rows_by("SEX", split_fun = keep_split_levels(only = c("Male", "Female"))) |> + analyze("BMEASIFL", afun = dummy_afun) + +build_table(lyt_b3, adsl) +``` + +Note here that row split instructions which directly follow an +`analyze` call will automatically be non-nested, so we do not need to +specify `nested = FALSE` in the `"RACE"` split, though doing so would +not harm anything. + +We can convince ourselves that treating the column and row structure +separately by combining the layouting instructions for both to receive +something equivalent in structure (i.e., up individual rows and marginal cell contents) to our +shell: + + +```{r} +lyt_struct <- basic_table() |> + split_cols_by("STRATA1", split_fun = keep_split_levels(only = c("A", "B"))) |> + split_cols_by("ARM", + split_fun = keep_split_levels(only = c("A: Drug X", "B: Placebo")), + show_colcounts = TRUE + ) |> + analyze("BMEASIFL", + afun = dummy_afun, + var_labels = "All Patients", + show_labels = "visible" + ) |> + split_rows_by("RACE") |> + split_rows_by("SEX", split_fun = keep_split_levels(only = c("Male", "Female"))) |> + analyze("BMEASIFL", afun = dummy_afun) + +build_table(lyt_struct, adsl) +``` + +We can see that the marginal cells for `"Male"` and `"Female"` within +each race are not present, but we will handle those in the third +translation step. + + +## Translating Cell Contents + +Finally, we will finish our translation with the third step: +translating cell contents. + +Tables can contain up to two types of rows with non-empty cells as +reckoned by the `rtables` conceptual model: individual analysis rows, +and marginal group summary rows (called *content* rows by the +`rtables` internals). + +Analysis rows are declared via `analyze` during layout construction; +an analysis function (the `afun` argument) specifying how *all cells +within a single facet pane should be simultaneously created*. + +We see in our shell that we want two rows whenever we analyze +`BMEASIFL` response: one for `"Yes"` and one for `"No"`. + +Most analysis functions provided by `rtables` or extensions like +`tern` or `junco` will automatically generate multiple rows when +analyzing a categorical variable (i.e., factor): + + +```{r} +rw_lyt <- basic_table() |> + analyze("BMEASIFL", + var_labels = "All Patients", + show_labels = "visible" + ) + +build_table(rw_lyt, adsl) +``` + +Further, recall that the faceting does the work of identifying subsets +and applying our analyses within those facets/subsets +automatically. Thus by applying the *structural* layout instructions +we translated above, we get something that is getting pretty close to +our desired table: + + +```{r} +rw_lyt_struct <- basic_table() |> + split_cols_by("STRATA1", split_fun = keep_split_levels(only = c("A", "B"))) |> + split_cols_by("ARM", + split_fun = keep_split_levels(only = c("A: Drug X", "B: Placebo")), + show_colcounts = TRUE + ) |> + analyze("BMEASIFL", + var_labels = "All Patients", + show_labels = "visible" + ) |> + split_rows_by("RACE") |> + split_rows_by("SEX", split_fun = keep_split_levels(only = c("Male", "Female"))) |> + analyze("BMEASIFL") + +build_table(rw_lyt_struct, adsl) +``` + +Two aspects remain before we have matched our desired shell: our +marginal counts in the the individual gender rows within each race are +missing, and our analysis rows contain only counts rather than +matching the desired `"xx (xx.x%)"` format of count and percent. + +`rtables` provides a (*very*) simple afun to calculate count percent +values (`counts_wpcts`) which we can use for illustration purposes +here. We will see later that it is not flexible enough to meet a +study team's full set of needs and more complex afuns will be used in +practice in production. + +```{r} +rw_lyt_structb <- basic_table() |> + split_cols_by("STRATA1", split_fun = keep_split_levels(only = c("A", "B"))) |> + split_cols_by("ARM", + split_fun = keep_split_levels(only = c("A: Drug X", "B: Placebo")), + show_colcounts = TRUE + ) |> + analyze("BMEASIFL", + afun = counts_wpcts, + var_labels = "All Patients", + show_labels = "visible" + ) |> + split_rows_by("RACE") |> + split_rows_by("SEX", split_fun = keep_split_levels(only = c("Male", "Female"))) |> + analyze("BMEASIFL", afun = counts_wpcts) + +build_table(rw_lyt_structb, adsl) +``` + +Now, all we need is the marginal gender counts. We do this by adding +`summarize_row_groups` *directly after* the relevant row faceting +(`split_rows_by`) instruction in the layout. This function can accept +a fully custom function (the `cfun` argument), but for our purposes, +we can control whether the percent is included in the default group +summary with the `format` argument. + +```{r} +lyt_final <- basic_table() |> + split_cols_by("STRATA1", split_fun = keep_split_levels(only = c("A", "B"))) |> + split_cols_by("ARM", + split_fun = keep_split_levels(only = c("A: Drug X", "B: Placebo")), + show_colcounts = TRUE + ) |> + analyze("BMEASIFL", + afun = counts_wpcts, + var_labels = "All Patients", + show_labels = "visible" + ) |> + split_rows_by("RACE") |> + split_rows_by("SEX", split_fun = keep_split_levels(only = c("Male", "Female"))) |> + summarize_row_groups(format = "xx") |> + analyze("BMEASIFL", afun = counts_wpcts) + +build_table(lyt_final, adsl) +``` + +Thus, we have fully translated our shell into an `rtables` declarative +layout and realized our desired table output. + +In the remainder of this vignette we will walk through a number of +shells with more complex structural elements and how to translate them +into `rtables` layouts. + +# Spanning Column Headers + +Some shells will call for spanning labels in column space which do not +directly reflect a categorical variable in the raw data, but rather +represent groups of levels in a variable, e.g., trial arms. + +For example, we might have the following column structure in a shell: + + +```{r, echo = FALSE} +span_map <- data.frame( + ARM = c("A: Drug X", "B: Placebo", "C: Combination"), + span_label = c("Active Treatment", " ", "Active Treatment") +) + +lyt_span <- basic_table() |> + split_cols_by("span_label", split_fun = trim_levels_to_map(span_map)) |> + split_cols_by("ARM", show_colcounts = TRUE) + +adsl2 <- adsl +adsl2$span_label <- "Active Treatment" +adsl2$span_label[adsl2$ARM == "B: Placebo"] <- " " + +tbl_colspans <- build_table(lyt_span, adsl2) +fixed_shell(tbl_colspans) +``` + +Here we see the "Active Treatment" label spanning arms A and C, while +no label appears above the column for arm B. There are a couple things +to decode here that will collapse this column structure into a nested +faceting structure as we saw above. + +Most importantly, while uneven splitting *is* possible with `rtables`, +including in column space, we can get our desired output by allowing +the B arm to have an invisible spanning label which is simply a single +space (`" "`). Viewing the structure this way, we can see that we have +two levels of faceting, one which splits between so called active +treatments and the remaining arms, and within that, we facet on +individual arm. + +This brings us to our second issue: we don't have a variable for +active vs non-active treatments. There are a few ways to address this, +but the most user-friendly way is simply to create one as a +preprocessing step on the data before we make our table: + +```{r} +adsl_forspans <- adsl +adsl_forspans$span_label <- "Active Treatment" +adsl_forspans$span_label[adsl_forspans$ARM == "B: Placebo"] <- " " + +qtable(adsl_forspans, "ARM", "span_label") +``` + +With that we can build a table with the desired nested splitting: + +```{r} +lyt_cspan <- basic_table() |> + split_cols_by("span_label") |> + split_cols_by("ARM", show_colcounts = TRUE) + +build_table(lyt_cspan, adsl_forspans) +``` + +So we are getting close, but our individual arm columns are not only +showing up under their correct spanning label (though we see that the +*data* are being siphoned under the correct labels by the column +counts). + +This type of non-full-factorial nesting is common; we often only want +facets that *make logical sense* within a nested faceting structure, +while wanting to omit any that don't (e.g., in our table, the Active +Treatment - Placebo facet). + +`rtables` provides multiple ways to declare this behavior in the form +of both full *split functions* and *split function behavior building +blocks*, the latter being for use within `make_split_fun`. For now, we +will use a built-in full split function as we will be covering +`make_split_fun` in a different vignette. + +Our two options for split functions are `trim_levels_in_group` and +`trim_levels_to_map`; the former is empirical and will keep all +combinations which are *observed in the data*, omitting any that +aren't. The latter requires us to provide a map of all combinations to +be displayed, but is more robust to sparse data (e.g., a data snapshot +from an in-flight trial) and allows for displaying zero counts for +unobserved but desired combinations. + +Other than being empirical and declarative, respectively, +`trim_levels_in_group` and `trim_levels_to_map` behave similarly: when +used while splitting on a variable (the "outer variable"), the +observations and factor levels of of another ("inner") variable are +restricted independently within each facet for the outer variable. + +In our case, our outer variable is `"span_label"`, while our inner +variable would be `"ARM"`. Thus we want to restrict the levels of +`"ARM"` within each facet of `"span_label"`. For our toy example here, +the two split functions will be equivalent, but we will use +`trim_levels_to_map` as it is more robust and appropriate for more +cases of production use. + +Thus we need to create our map, a `data.frame` that contains the two +variables with each desired combination as a separate row: + +```{R} +span_label_map <- tribble( + ~span_label, ~ARM, + "Active Treatment", "A: Drug X", + "Active Treatment", "C: Combination", + " ", "B: Placebo", +) + +lyt_cspan_final <- basic_table() |> + split_cols_by("span_label", + split_fun = trim_levels_to_map(span_label_map) + ) |> + split_cols_by("ARM", show_colcounts = TRUE) + +build_table(lyt_cspan_final, adsl_forspans) +``` + +Thus we have again achieved a "table" matching our desired shell. We +can consider only the column structure because in this case as +previously the column structure, row structure, and analysis are all +orthogonal. We will see an example where that isn't fully the case below + +**Note:** in the general case, the level map used in +`trim_levels_to_map` will be a function of the data dictionaries for +the relevant variables within your study, thus for combinations of +actual variables these maps should not require manual construction as +we did above. + +# Heterogeneous Column Structures (e.g., Risk Difference Columns) + +In our previous examples, the column structure was simple nested +faceting, both in the case of faceting on two variables from the data, +and in the case we wanted spanning labels. + +While this simple nesting structure is relatively common, +particularly for column structure, it does not fit the shells for all +tables we might need to create. One example of this is risk difference +columns, as found in modern FDA guidance for Adverse Event (AE) +tables. + +In this section we will translate a shell with both spanning headers +and risk difference columns into a layout. To avoid subtleties about +counting we will analyze the `BMRKR2` variable in our synthetic +`ADSL` dataset rather than going for a realistic AE table. These +counting issues and realistic AE tables will be addressed elsewhere in +this series of vignettes. + +## Risk Difference Columns + +Many tables call for "risk difference", or comparison columns, in +addition to those used for the primary counts. When combined with +spanning labels, the column structure of our shell would look +something like: + +```{r, echo = FALSE} +adsl2$rr_header <- "Risk Differences" +adsl2$rr_label <- paste(adsl2$ARM, "vs B: Placebo") +lyt_rr <- basic_table() |> + split_cols_by("span_label", split_fun = trim_levels_to_map(span_map)) |> + split_cols_by("ARM", show_colcounts = TRUE) |> + split_cols_by("rr_header", nested = FALSE) |> + split_cols_by("ARM", labels_var = "rr_label", split_fun = remove_split_levels("B: Placebo")) + + +tbl_rr_shell <- build_table(lyt_rr, adsl2) +fixed_shell(tbl_rr_shell) +``` + +We see that the first portion of the column structure is the same, but +we now have the risk difference structure in addition. There are a +number of different ways to model risk difference columns but we will +do so as a separate nested substructure. Thus as we did with the +"Active Treatment" spanning label, we will create and then facet on a +variable that gives us the "Risk Differences" label. + +We can build up this substructure separately and then combine it with +the structure we created above to match the full shell. + + +```{r} +adsl_rr <- adsl_forspans +adsl_rr$rr_header <- "Risk Differences" + +lyt_only_rr <- basic_table() |> + split_cols_by("rr_header") |> + split_cols_by("ARM") + +build_table(lyt_only_rr, adsl_rr) +``` + +This is getting close there are two issues: first, we don't want a +placebo column (which would nonsensically compare placebo against +itself), and the labels are simply the individual arms rather than the +pair of arms being compared as in our shell. + +We can restrict the facets generated using the `remove_split_levels` +(or sibling `keep_split_levels`) split function provided by +`rtables`. In addition the `split_*_by` functions accept the +`labels_var` argument which specifies an additional variable which +should be used for the *labels* (not names) of the facets +generated. With preprocessing to create such a variable, and combining +these two approaches, we can achieve the risk difference structure: + +```{r} +adsl_rr$rr_label <- paste(adsl_rr$ARM, "vs B: Placebo") + +lyt_only_rr2 <- basic_table() |> + split_cols_by("rr_header") |> + split_cols_by("ARM", + split_fun = remove_split_levels("B: Placebo"), + labels_var = "rr_label" + ) + +build_table(lyt_only_rr2, adsl_rr) +``` + +To combine our two sections of column structure, we simply combine the +sets of layouting instructions and add `nested = FALSE` to our split +on `"rr_header"`: + + +```{r} +lyt_rr_cols <- basic_table() |> + split_cols_by("span_label", + split_fun = trim_levels_to_map(span_label_map) + ) |> + split_cols_by("ARM", show_colcounts = TRUE) |> + split_cols_by("rr_header", nested = FALSE) |> + split_cols_by("ARM", + split_fun = remove_split_levels("B: Placebo"), + labels_var = "rr_label" + ) + +build_table(lyt_rr_cols, adsl_rr) +``` + +Note that because we used `show_colcounts` in our `split_cols_by` call +for `"ARM"`, rather than in `build_table`, we have counts for our main +arm columns but not for our comparison columns, as desired. + +One caveat here, however, is that we will need a more sophisticated +analysis function because its behavior is no longer independent of +which facet it is in: it might generate e.g., counts for the primary +arm columns and then confidence intervals for our risk difference +columns. + +Typically trial teams will be using pre-existing analysis functions +for this, but we will illustrate these can be constructed now. + +## Column-structure Aware Analysis Functions + +Our analysis function needs two "modes": the primary arm column mode +and the risk difference mode, and it needs to be able to distinguish +between them. + +Analysis (and content, i.e., row group summary) functions can accept +the optional `.spl_context` argument to receive information where in +the faceting structure the facet they are currently populating is. We +will leave a detailed discussion of the full contents of the split +context to other documentation and simply use the portions we need +here. + +In particular, we will use the `cur_col_id` column of `.spl_context` +to determine which section of the column structure we are under. Note +that due to the vagaries of the current implementation, this is +constructed of the *labels* for the column facets rather than their +names. This is the split/value pairs of each column split in order +concatenated together, so it suffices to define + +```{r} +in_risk_diff <- function(spl_context) grepl("Risk Differences", spl_context$cur_col_id[1]) +``` + + +For simplicity, we will not worry about *calculating* risk differences +here, and simply write an analysis function that emits something +different to show that it can tell it is in "risk difference mode". + +Thus a very simplistic afun is as follows: + +```{r} +rr_afun <- function(x, .N_col, .spl_context) { + xtbl <- table(x) + if (in_risk_diff(.spl_context)) { + armlabel <- tail(.spl_context$cur_col_split_val[[1]], 1) # last split value, ie arm + armletter <- substr(armlabel, 1, 1) + vals <- as.list(rep(paste(armletter, "vs B"), length(xtbl))) + fmts <- rep("xx", length(xtbl)) + } else { + vals <- lapply(xtbl, function(x) x * c(1, 1 / .N_col)) ## count and pct + fmts <- rep("xx.x (xx.x%)", length(xtbl)) + } + names(vals) <- names(xtbl) + names(fmts) <- names(vals) + in_rows(.list = vals, .formats = fmts) +} +``` + +With this we can create a table. We will analyze `BMRKR2` (biomarker +2) for the sake of brevity. This is an oversimplifaction, as typically +this would be, e.g., `AEDECOD` in an `adae` dataset, but this requires +more sophisticated calculation of counts and/or percents that is +important but not germane to this specific issue. + +```{r} +lyt_rr_full <- basic_table() |> + split_cols_by("span_label", + split_fun = trim_levels_to_map(span_label_map) + ) |> + split_cols_by("ARM", show_colcounts = TRUE) |> + split_cols_by("rr_header", nested = FALSE) |> + split_cols_by("ARM", + split_fun = remove_split_levels("B: Placebo"), + labels_var = "rr_label" + ) |> + analyze("BMRKR2", afun = rr_afun) + +build_table(lyt_rr_full, adsl_rr) +``` + +The blank space above the column counts is a known issue which we +expect to be resolved in a future release due to the fact that the +header construction/wrapping behavior is not accounting for the fact +that the two sections of the column structure are independent. + +Note that while our analysis function was dependent on where in the +*column* structure we are, it remains independent of where in the +*row* faceting structure we are. Thus we can use our analysis function +within row faceting without changes: + + +```{r} +lyt_rr_full2 <- basic_table() |> + split_cols_by("span_label", + split_fun = trim_levels_to_map(span_label_map) + ) |> + split_cols_by("ARM", show_colcounts = TRUE) |> + split_cols_by("rr_header", nested = FALSE) |> + split_cols_by("ARM", + split_fun = remove_split_levels("B: Placebo"), + labels_var = "rr_label" + ) |> + split_rows_by("STRATA1") |> + split_rows_by("SEX", split_fun = keep_split_levels(c("Female", "Male"))) |> + analyze("BMRKR2", afun = rr_afun) + +tbl <- build_table(lyt_rr_full2, adsl_rr) + +cwidths <- propose_column_widths(tbl) +cwidths[cwidths > 15] <- 15 +cat(export_as_txt(tbl, colwidths = cwidths)) ## for wrapping +``` + +A more complete exploration of creating production ready analysis +functions will be presented elsewhere in this vignette series. + +# Mixed Nesting Levels + +In practice, the row structure in most shells can be translated to a +layout using combinations of the methods shown above. Some shells, +however, essentially call for group summaries for all levels of a +categorical variable, but additionally call for analysis within those +groups *for only some levels of the variable*. + +In clinical trial outputs we have seen this most commonly in +disposition tables, the shells of which might look something like: + +```{r, echo = FALSE} +simple_two_tier_init <- function(df, .var, .N_col, inner_var, drill_down_levs) { + outer_tbl <- table(df[[.var]]) + + cells <- lapply( + names(outer_tbl), + function(nm) { + cont_cell <- rcell(outer_tbl[nm] * c(1, 1 / .N_col), format = "xx (xx.x%)") + if (nm %in% drill_down_levs) { + inner_tbl <- table(df[[inner_var]]) + detail_cells <- lapply( + names(inner_tbl), + function(innm) rcell(inner_tbl[innm] * c(1, 1 / .N_col), format = "xx (xx.x%)", indent_mod = 1L) + ) + names(detail_cells) <- names(inner_tbl) + } else { + detail_cells <- NULL + } + c(setNames(list(cont_cell), nm), detail_cells) + } + ) + + in_rows(.list = unlist(cells, recursive = FALSE)) +} + +two_tier_shell_lyt <- basic_table() |> + split_cols_by("span_label", + split_fun = trim_levels_to_map(span_label_map) + ) |> + split_cols_by("ARM", show_colcounts = TRUE) |> + split_rows_by("RACE", split_fun = keep_split_levels(c("Asian", "Black"))) |> + analyze("EOSSTT", + afun = simple_two_tier_init, + extra_args = list( + inner_var = "DCSREAS", + drill_down_levs = "DISCONTINUED" + ) + ) + +two_tier_shell <- build_table(two_tier_shell_lyt, adsl_rr) ## don't need the rr bits +fixed_shell(two_tier_shell) +``` + +In this shell, the `COMPLETED`, `DISCONTINUED` and `ONGOING` rows are +siblings (derived from the `EOSSTT` variable), however only the +`DISCONTINUED` row acts as a group summary row for a facet containing +further analysis; the other two essentially act as individual rows. + +This type of structure where individual analysis rows and facets/group +summary rows are direct siblings is not currently supported by the +`rtables` layouting and tabulation engines, and is *somewhat* +supported when created via, e.g., trimming rows of a created table. + +The above said, we can arrive at a table which renders as desired +using the *two-tier analysis function* strategy. A +[vignette](variable_nesting_depth.html) discussing this is in detail +is included with `rtables`; for completeness of the training +curriculum we will briefly reiterate here. + +The key to the two-tier analysis function strategy is to generate both +levels of row in the same analysis function and simply use indent +modifiers to differentiate them. + +Below is a simple `afun` that implements this strategy. For the +purposes of this lesson readers can ignore the details of what this +function does if desired; analysis function design and implementation +will be covered in another vignette in the advanced section. + + +```{R} +simple_two_tier <- function(df, .var, .N_col, inner_var, drill_down_levs) { + ## group EOSSTT counts + outer_tbl <- table(df[[.var]]) + + cells <- lapply( + names(outer_tbl), + function(nm) { + ## simulated group summary rows + cont_cell <- rcell(outer_tbl[nm] * c(1, 1 / .N_col), + format = "xx (xx.x%)" + ) + if (nm %in% drill_down_levs) { + ## detail (DCSREAS) counts + inner_tbl <- table(df[[inner_var]]) + ## note indent_mod + detail_cells <- lapply( + names(inner_tbl), + function(innm) { + rcell(inner_tbl[innm] * c(1, 1 / .N_col), + format = "xx (xx.x%)", + ## appearance of "detail drill-down" + indent_mod = 1L + ) + } + ) + names(detail_cells) <- names(inner_tbl) + } else { + detail_cells <- NULL + } + c(setNames(list(cont_cell), nm), detail_cells) + } + ) + + in_rows(.list = unlist(cells, recursive = FALSE)) +} + +lyt_two_tier <- basic_table() |> + analyze("EOSSTT", + afun = simple_two_tier, + extra_args = list(inner_var = "DCSREAS", drill_down_levs = "DISCONTINUED") + ) + +build_table(lyt_two_tier, adsl_rr) +``` + +As in other cases, we can add the row- and column- structure +orthogonally (provided the analysis behavior is truly orthogonal to +the faceting, as it is in this shell): + +```{r} +lyt_two_tier_full <- basic_table() |> + split_cols_by("span_label", + split_fun = trim_levels_to_map(span_label_map) + ) |> + split_cols_by("ARM", show_colcounts = TRUE) |> + split_rows_by("RACE", split_fun = keep_split_levels(c("Asian", "Black"))) |> + analyze("EOSSTT", + afun = simple_two_tier, + extra_args = list(inner_var = "DCSREAS", drill_down_levs = "DISCONTINUED") + ) + +build_table(lyt_two_tier_full, adsl_rr) +``` + +Thus we have created our desired output. From f98cad94d24519fec166e31e638d1c8a74145085 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 23 Oct 2025 21:17:56 +0000 Subject: [PATCH 2/7] [skip roxygen] [skip vbump] Roxygen Man Pages Auto Update --- DESCRIPTION | 2 +- man/add_overall_level.Rd | 2 +- man/analyze_colvars.Rd | 2 +- man/append_topleft.Rd | 2 +- man/cell_values.Rd | 2 +- man/gfc.Rd | 2 +- man/int_methods.Rd | 2 +- man/matrix_form-VTableTree-method.Rd | 2 +- man/row_paths_summary.Rd | 2 +- man/split_cols_by.Rd | 2 +- man/split_cols_by_multivar.Rd | 2 +- man/split_rows_by.Rd | 2 +- man/table_shell.Rd | 2 +- man/table_structure.Rd | 2 +- man/tostring.Rd | 2 +- man/varcuts.Rd | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index d289f6d5c7..99ec219a4b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -71,7 +71,7 @@ Encoding: UTF-8 Language: en-US LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.2 +RoxygenNote: 7.3.3 Collate: '00tabletrees.R' 'Viewer.R' diff --git a/man/add_overall_level.Rd b/man/add_overall_level.Rd index ce22160cab..a388e24988 100644 --- a/man/add_overall_level.Rd +++ b/man/add_overall_level.Rd @@ -78,7 +78,7 @@ lyt2 tbl2 <- build_table(lyt2, DM) tbl2 -\dontshow{if (require(tibble)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(tibble)) withAutoprint(\{ # examplesIf} library(tibble) combodf <- tribble( diff --git a/man/analyze_colvars.Rd b/man/analyze_colvars.Rd index d30bf48390..af9a73de94 100644 --- a/man/analyze_colvars.Rd +++ b/man/analyze_colvars.Rd @@ -57,7 +57,7 @@ A \code{PreDataTableLayouts} object suitable for passing to further layouting fu Generate rows analyzing different variables across columns } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) ANL <- DM \%>\% mutate(value = rnorm(n()), pctdiff = runif(n())) diff --git a/man/append_topleft.Rd b/man/append_topleft.Rd index 0db6195067..90f61aa060 100644 --- a/man/append_topleft.Rd +++ b/man/append_topleft.Rd @@ -31,7 +31,7 @@ the actual splitting keywords. This may change in the future. This function is experimental, its name and the details of its behavior are subject to change in future versions. } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) DM2 <- DM \%>\% mutate(RACE = factor(RACE), SEX = factor(SEX)) diff --git a/man/cell_values.Rd b/man/cell_values.Rd index 1082f52f3f..55151dceb5 100644 --- a/man/cell_values.Rd +++ b/man/cell_values.Rd @@ -47,7 +47,7 @@ lyt <- basic_table() \%>\% split_rows_by("STRATA1") \%>\% analyze("AGE") -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) ## for mutate tbl <- build_table(lyt, DM \%>\% mutate(SEX = droplevels(SEX), RACE = droplevels(RACE))) diff --git a/man/gfc.Rd b/man/gfc.Rd index 19c01c1155..70b2dea16d 100644 --- a/man/gfc.Rd +++ b/man/gfc.Rd @@ -50,7 +50,7 @@ The formatted print-strings for all (body) cells in \code{obj}. Get formatted cells } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/int_methods.Rd b/man/int_methods.Rd index e19c04cbfd..0a662b3de7 100644 --- a/man/int_methods.Rd +++ b/man/int_methods.Rd @@ -1302,7 +1302,7 @@ These are internal methods that are documented only to satisfy \verb{R CMD check attention to this documentation. } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/matrix_form-VTableTree-method.Rd b/man/matrix_form-VTableTree-method.Rd index 27a488bb84..20bfd790ab 100644 --- a/man/matrix_form-VTableTree-method.Rd +++ b/man/matrix_form-VTableTree-method.Rd @@ -60,7 +60,7 @@ values are determined using \code{get_formatted_cells}. (Column labels are calcu function. } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/row_paths_summary.Rd b/man/row_paths_summary.Rd index 5a2ec27fc3..1835aea04a 100644 --- a/man/row_paths_summary.Rd +++ b/man/row_paths_summary.Rd @@ -19,7 +19,7 @@ A data frame summarizing the row- or column-structure of \code{x}. Print row/column paths summary } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} ex_adsl_MF <- ex_adsl \%>\% dplyr::filter(SEX \%in\% c("M", "F")) lyt <- basic_table() \%>\% diff --git a/man/split_cols_by.Rd b/man/split_cols_by.Rd index b36e31bfb2..30c2c97e25 100644 --- a/man/split_cols_by.Rd +++ b/man/split_cols_by.Rd @@ -120,7 +120,7 @@ lyt2 tbl2 <- build_table(lyt2, DM) tbl2 -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} # By default sequentially adding layouts results in nesting library(dplyr) diff --git a/man/split_cols_by_multivar.Rd b/man/split_cols_by_multivar.Rd index 75fd2a752e..7aca062757 100644 --- a/man/split_cols_by_multivar.Rd +++ b/man/split_cols_by_multivar.Rd @@ -53,7 +53,7 @@ When we need columns to reflect different variables entirely, rather than differ variable, we use \code{split_cols_by_multivar}. } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) ANL <- DM \%>\% mutate(value = rnorm(n()), pctdiff = runif(n())) diff --git a/man/split_rows_by.Rd b/man/split_rows_by.Rd index 232c46bdbd..70919aee62 100644 --- a/man/split_rows_by.Rd +++ b/man/split_rows_by.Rd @@ -146,7 +146,7 @@ lyt3 <- basic_table() \%>\% lyt3 -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) DM2 <- DM \%>\% diff --git a/man/table_shell.Rd b/man/table_shell.Rd index 17734b69c7..bd310700c5 100644 --- a/man/table_shell.Rd +++ b/man/table_shell.Rd @@ -54,7 +54,7 @@ A table shell is a rendering of the table which maintains the structure, but doe displaying the formatting instructions for each cell. } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/table_structure.Rd b/man/table_structure.Rd index f241b36e04..1940cd0371 100644 --- a/man/table_structure.Rd +++ b/man/table_structure.Rd @@ -18,7 +18,7 @@ No return value. Called for the side-effect of printing a row- or subtable-struc Summarize table } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/tostring.Rd b/man/tostring.Rd index 9d9e517f49..0e567e3370 100644 --- a/man/tostring.Rd +++ b/man/tostring.Rd @@ -69,7 +69,7 @@ undefined wrapping behavior. Passing vectors of already split strings remains su case each string is word-wrapped separately with the behavior described above. } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/varcuts.Rd b/man/varcuts.Rd index 877401bb26..360bdd549f 100644 --- a/man/varcuts.Rd +++ b/man/varcuts.Rd @@ -168,7 +168,7 @@ before proceeding. Thus even when nested within another split in column/row spac the overall values (e.g., quartiles) in the dataset, NOT the values for subset it is nested under. } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) # split_cols_by_cuts From 09fe32b36a901e76e6a6a73e408f858ae47ab6e8 Mon Sep 17 00:00:00 2001 From: Gabe Becker Date: Fri, 24 Oct 2025 14:22:29 -0700 Subject: [PATCH 3/7] fix roxygen version mismatch issue --- DESCRIPTION | 2 +- man/add_overall_level.Rd | 2 +- man/analyze_colvars.Rd | 2 +- man/append_topleft.Rd | 2 +- man/cell_values.Rd | 2 +- man/gfc.Rd | 2 +- man/int_methods.Rd | 2 +- man/matrix_form-VTableTree-method.Rd | 2 +- man/row_paths_summary.Rd | 2 +- man/split_cols_by.Rd | 2 +- man/split_cols_by_multivar.Rd | 2 +- man/split_rows_by.Rd | 2 +- man/table_shell.Rd | 2 +- man/table_structure.Rd | 2 +- man/tostring.Rd | 2 +- man/varcuts.Rd | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 99ec219a4b..d289f6d5c7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -71,7 +71,7 @@ Encoding: UTF-8 Language: en-US LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.3 +RoxygenNote: 7.3.2 Collate: '00tabletrees.R' 'Viewer.R' diff --git a/man/add_overall_level.Rd b/man/add_overall_level.Rd index a388e24988..ce22160cab 100644 --- a/man/add_overall_level.Rd +++ b/man/add_overall_level.Rd @@ -78,7 +78,7 @@ lyt2 tbl2 <- build_table(lyt2, DM) tbl2 -\dontshow{if (require(tibble)) withAutoprint(\{ # examplesIf} +\dontshow{if (require(tibble)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} library(tibble) combodf <- tribble( diff --git a/man/analyze_colvars.Rd b/man/analyze_colvars.Rd index af9a73de94..d30bf48390 100644 --- a/man/analyze_colvars.Rd +++ b/man/analyze_colvars.Rd @@ -57,7 +57,7 @@ A \code{PreDataTableLayouts} object suitable for passing to further layouting fu Generate rows analyzing different variables across columns } \examples{ -\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} +\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} library(dplyr) ANL <- DM \%>\% mutate(value = rnorm(n()), pctdiff = runif(n())) diff --git a/man/append_topleft.Rd b/man/append_topleft.Rd index 90f61aa060..0db6195067 100644 --- a/man/append_topleft.Rd +++ b/man/append_topleft.Rd @@ -31,7 +31,7 @@ the actual splitting keywords. This may change in the future. This function is experimental, its name and the details of its behavior are subject to change in future versions. } \examples{ -\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} +\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} library(dplyr) DM2 <- DM \%>\% mutate(RACE = factor(RACE), SEX = factor(SEX)) diff --git a/man/cell_values.Rd b/man/cell_values.Rd index 55151dceb5..1082f52f3f 100644 --- a/man/cell_values.Rd +++ b/man/cell_values.Rd @@ -47,7 +47,7 @@ lyt <- basic_table() \%>\% split_rows_by("STRATA1") \%>\% analyze("AGE") -\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} +\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} library(dplyr) ## for mutate tbl <- build_table(lyt, DM \%>\% mutate(SEX = droplevels(SEX), RACE = droplevels(RACE))) diff --git a/man/gfc.Rd b/man/gfc.Rd index 70b2dea16d..19c01c1155 100644 --- a/man/gfc.Rd +++ b/man/gfc.Rd @@ -50,7 +50,7 @@ The formatted print-strings for all (body) cells in \code{obj}. Get formatted cells } \examples{ -\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} +\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/int_methods.Rd b/man/int_methods.Rd index 0a662b3de7..e19c04cbfd 100644 --- a/man/int_methods.Rd +++ b/man/int_methods.Rd @@ -1302,7 +1302,7 @@ These are internal methods that are documented only to satisfy \verb{R CMD check attention to this documentation. } \examples{ -\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} +\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/matrix_form-VTableTree-method.Rd b/man/matrix_form-VTableTree-method.Rd index 20bfd790ab..27a488bb84 100644 --- a/man/matrix_form-VTableTree-method.Rd +++ b/man/matrix_form-VTableTree-method.Rd @@ -60,7 +60,7 @@ values are determined using \code{get_formatted_cells}. (Column labels are calcu function. } \examples{ -\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} +\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/row_paths_summary.Rd b/man/row_paths_summary.Rd index 1835aea04a..5a2ec27fc3 100644 --- a/man/row_paths_summary.Rd +++ b/man/row_paths_summary.Rd @@ -19,7 +19,7 @@ A data frame summarizing the row- or column-structure of \code{x}. Print row/column paths summary } \examples{ -\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} +\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} ex_adsl_MF <- ex_adsl \%>\% dplyr::filter(SEX \%in\% c("M", "F")) lyt <- basic_table() \%>\% diff --git a/man/split_cols_by.Rd b/man/split_cols_by.Rd index 30c2c97e25..b36e31bfb2 100644 --- a/man/split_cols_by.Rd +++ b/man/split_cols_by.Rd @@ -120,7 +120,7 @@ lyt2 tbl2 <- build_table(lyt2, DM) tbl2 -\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} +\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} # By default sequentially adding layouts results in nesting library(dplyr) diff --git a/man/split_cols_by_multivar.Rd b/man/split_cols_by_multivar.Rd index 7aca062757..75fd2a752e 100644 --- a/man/split_cols_by_multivar.Rd +++ b/man/split_cols_by_multivar.Rd @@ -53,7 +53,7 @@ When we need columns to reflect different variables entirely, rather than differ variable, we use \code{split_cols_by_multivar}. } \examples{ -\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} +\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} library(dplyr) ANL <- DM \%>\% mutate(value = rnorm(n()), pctdiff = runif(n())) diff --git a/man/split_rows_by.Rd b/man/split_rows_by.Rd index 70919aee62..232c46bdbd 100644 --- a/man/split_rows_by.Rd +++ b/man/split_rows_by.Rd @@ -146,7 +146,7 @@ lyt3 <- basic_table() \%>\% lyt3 -\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} +\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} library(dplyr) DM2 <- DM \%>\% diff --git a/man/table_shell.Rd b/man/table_shell.Rd index bd310700c5..17734b69c7 100644 --- a/man/table_shell.Rd +++ b/man/table_shell.Rd @@ -54,7 +54,7 @@ A table shell is a rendering of the table which maintains the structure, but doe displaying the formatting instructions for each cell. } \examples{ -\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} +\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/table_structure.Rd b/man/table_structure.Rd index 1940cd0371..f241b36e04 100644 --- a/man/table_structure.Rd +++ b/man/table_structure.Rd @@ -18,7 +18,7 @@ No return value. Called for the side-effect of printing a row- or subtable-struc Summarize table } \examples{ -\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} +\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/tostring.Rd b/man/tostring.Rd index 0e567e3370..9d9e517f49 100644 --- a/man/tostring.Rd +++ b/man/tostring.Rd @@ -69,7 +69,7 @@ undefined wrapping behavior. Passing vectors of already split strings remains su case each string is word-wrapped separately with the behavior described above. } \examples{ -\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} +\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/varcuts.Rd b/man/varcuts.Rd index 360bdd549f..877401bb26 100644 --- a/man/varcuts.Rd +++ b/man/varcuts.Rd @@ -168,7 +168,7 @@ before proceeding. Thus even when nested within another split in column/row spac the overall values (e.g., quartiles) in the dataset, NOT the values for subset it is nested under. } \examples{ -\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} +\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} library(dplyr) # split_cols_by_cuts From 969f43926fd381e31173dd3f37890516caf7e046 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:25:55 +0000 Subject: [PATCH 4/7] [skip roxygen] [skip vbump] Roxygen Man Pages Auto Update --- DESCRIPTION | 2 +- man/add_overall_level.Rd | 2 +- man/analyze_colvars.Rd | 2 +- man/append_topleft.Rd | 2 +- man/cell_values.Rd | 2 +- man/gfc.Rd | 2 +- man/int_methods.Rd | 2 +- man/matrix_form-VTableTree-method.Rd | 2 +- man/row_paths_summary.Rd | 2 +- man/split_cols_by.Rd | 2 +- man/split_cols_by_multivar.Rd | 2 +- man/split_rows_by.Rd | 2 +- man/table_shell.Rd | 2 +- man/table_structure.Rd | 2 +- man/tostring.Rd | 2 +- man/varcuts.Rd | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index d289f6d5c7..99ec219a4b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -71,7 +71,7 @@ Encoding: UTF-8 Language: en-US LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.2 +RoxygenNote: 7.3.3 Collate: '00tabletrees.R' 'Viewer.R' diff --git a/man/add_overall_level.Rd b/man/add_overall_level.Rd index ce22160cab..a388e24988 100644 --- a/man/add_overall_level.Rd +++ b/man/add_overall_level.Rd @@ -78,7 +78,7 @@ lyt2 tbl2 <- build_table(lyt2, DM) tbl2 -\dontshow{if (require(tibble)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(tibble)) withAutoprint(\{ # examplesIf} library(tibble) combodf <- tribble( diff --git a/man/analyze_colvars.Rd b/man/analyze_colvars.Rd index d30bf48390..af9a73de94 100644 --- a/man/analyze_colvars.Rd +++ b/man/analyze_colvars.Rd @@ -57,7 +57,7 @@ A \code{PreDataTableLayouts} object suitable for passing to further layouting fu Generate rows analyzing different variables across columns } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) ANL <- DM \%>\% mutate(value = rnorm(n()), pctdiff = runif(n())) diff --git a/man/append_topleft.Rd b/man/append_topleft.Rd index 0db6195067..90f61aa060 100644 --- a/man/append_topleft.Rd +++ b/man/append_topleft.Rd @@ -31,7 +31,7 @@ the actual splitting keywords. This may change in the future. This function is experimental, its name and the details of its behavior are subject to change in future versions. } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) DM2 <- DM \%>\% mutate(RACE = factor(RACE), SEX = factor(SEX)) diff --git a/man/cell_values.Rd b/man/cell_values.Rd index 1082f52f3f..55151dceb5 100644 --- a/man/cell_values.Rd +++ b/man/cell_values.Rd @@ -47,7 +47,7 @@ lyt <- basic_table() \%>\% split_rows_by("STRATA1") \%>\% analyze("AGE") -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) ## for mutate tbl <- build_table(lyt, DM \%>\% mutate(SEX = droplevels(SEX), RACE = droplevels(RACE))) diff --git a/man/gfc.Rd b/man/gfc.Rd index 19c01c1155..70b2dea16d 100644 --- a/man/gfc.Rd +++ b/man/gfc.Rd @@ -50,7 +50,7 @@ The formatted print-strings for all (body) cells in \code{obj}. Get formatted cells } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/int_methods.Rd b/man/int_methods.Rd index e19c04cbfd..0a662b3de7 100644 --- a/man/int_methods.Rd +++ b/man/int_methods.Rd @@ -1302,7 +1302,7 @@ These are internal methods that are documented only to satisfy \verb{R CMD check attention to this documentation. } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/matrix_form-VTableTree-method.Rd b/man/matrix_form-VTableTree-method.Rd index 27a488bb84..20bfd790ab 100644 --- a/man/matrix_form-VTableTree-method.Rd +++ b/man/matrix_form-VTableTree-method.Rd @@ -60,7 +60,7 @@ values are determined using \code{get_formatted_cells}. (Column labels are calcu function. } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/row_paths_summary.Rd b/man/row_paths_summary.Rd index 5a2ec27fc3..1835aea04a 100644 --- a/man/row_paths_summary.Rd +++ b/man/row_paths_summary.Rd @@ -19,7 +19,7 @@ A data frame summarizing the row- or column-structure of \code{x}. Print row/column paths summary } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} ex_adsl_MF <- ex_adsl \%>\% dplyr::filter(SEX \%in\% c("M", "F")) lyt <- basic_table() \%>\% diff --git a/man/split_cols_by.Rd b/man/split_cols_by.Rd index b36e31bfb2..30c2c97e25 100644 --- a/man/split_cols_by.Rd +++ b/man/split_cols_by.Rd @@ -120,7 +120,7 @@ lyt2 tbl2 <- build_table(lyt2, DM) tbl2 -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} # By default sequentially adding layouts results in nesting library(dplyr) diff --git a/man/split_cols_by_multivar.Rd b/man/split_cols_by_multivar.Rd index 75fd2a752e..7aca062757 100644 --- a/man/split_cols_by_multivar.Rd +++ b/man/split_cols_by_multivar.Rd @@ -53,7 +53,7 @@ When we need columns to reflect different variables entirely, rather than differ variable, we use \code{split_cols_by_multivar}. } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) ANL <- DM \%>\% mutate(value = rnorm(n()), pctdiff = runif(n())) diff --git a/man/split_rows_by.Rd b/man/split_rows_by.Rd index 232c46bdbd..70919aee62 100644 --- a/man/split_rows_by.Rd +++ b/man/split_rows_by.Rd @@ -146,7 +146,7 @@ lyt3 <- basic_table() \%>\% lyt3 -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) DM2 <- DM \%>\% diff --git a/man/table_shell.Rd b/man/table_shell.Rd index 17734b69c7..bd310700c5 100644 --- a/man/table_shell.Rd +++ b/man/table_shell.Rd @@ -54,7 +54,7 @@ A table shell is a rendering of the table which maintains the structure, but doe displaying the formatting instructions for each cell. } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/table_structure.Rd b/man/table_structure.Rd index f241b36e04..1940cd0371 100644 --- a/man/table_structure.Rd +++ b/man/table_structure.Rd @@ -18,7 +18,7 @@ No return value. Called for the side-effect of printing a row- or subtable-struc Summarize table } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/tostring.Rd b/man/tostring.Rd index 9d9e517f49..0e567e3370 100644 --- a/man/tostring.Rd +++ b/man/tostring.Rd @@ -69,7 +69,7 @@ undefined wrapping behavior. Passing vectors of already split strings remains su case each string is word-wrapped separately with the behavior described above. } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) iris2 <- iris \%>\% diff --git a/man/varcuts.Rd b/man/varcuts.Rd index 877401bb26..360bdd549f 100644 --- a/man/varcuts.Rd +++ b/man/varcuts.Rd @@ -168,7 +168,7 @@ before proceeding. Thus even when nested within another split in column/row spac the overall values (e.g., quartiles) in the dataset, NOT the values for subset it is nested under. } \examples{ -\dontshow{if (require(dplyr)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (require(dplyr)) withAutoprint(\{ # examplesIf} library(dplyr) # split_cols_by_cuts From 9d5e19f814321e6ff2a2b3a749caefaed72ac15c Mon Sep 17 00:00:00 2001 From: Gabe Becker Date: Fri, 24 Oct 2025 14:41:21 -0700 Subject: [PATCH 5/7] hopefully fix spellcheck --- inst/WORDLIST | 80 ++++++++++--------- ...guided_intermediate_translating_shells.Rmd | 2 +- 2 files changed, 44 insertions(+), 38 deletions(-) diff --git a/inst/WORDLIST b/inst/WORDLIST index c9635f358d..8fbd5cdb80 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -1,47 +1,19 @@ +AE +afun +amongst ARD +ard ARDs -Bové -CRAN's +biomarker +BMEASIFL +Bov Carreras +charset Cheatsheet Chohan -FFFL -Godwin -Heng -Hoffmann -IEC -Kelkhoff -Layouting -Lewandowski -Maximo -Modelling -NSE -ORCID -Paszty -Pathing -Pharma -Phuse -Pre -Qi -RStudio -Resync -Rua -STUDYID -Sabanés -Saibah -Stoilova -Subtable -Subtables -Tadeusz -Unstratified -ValueWrapper -Yung -afun -amongst -ard -charset colcount combinatorial +CRAN's customizations de decrementing @@ -53,45 +25,76 @@ elemtable emph facetted facetting +FFFL formatter forseeable funder getter getters +Godwin +Heng +Hoffmann ie +IEC iec indicies ing initializer +Kelkhoff labelled +Layouting layouting +Lewandowski mandatorily +Maximo modelled +Modelling monospace multivariable +NSE +ORCID orderable +orthogonally +oversimplifaction +Paszty pathability pathable pathed +Pathing pathing peforms +Pharma +Phuse postfix postprocessing +Pre pre +Qi reindexed repped responder +Resync reusability roadmap +RStudio +rtables +Rua +Sabans +Saibah sas sortable spl +Stoilova +STUDYID subsplits +Subtable subtable subtable's +Subtables subtables summarization tableone +Tadeusz todo traversable truetype @@ -102,8 +105,11 @@ uniquification univariable unnested unpruned +Unstratified unstratified useR +ValueWrapper visibilities visibilty xtable +Yung diff --git a/vignettes/guided_intermediate_translating_shells.Rmd b/vignettes/guided_intermediate_translating_shells.Rmd index 27104994f1..4d47080fee 100644 --- a/vignettes/guided_intermediate_translating_shells.Rmd +++ b/vignettes/guided_intermediate_translating_shells.Rmd @@ -26,7 +26,7 @@ editor_options: The first - and often largest - hurdle to creating a table via `rtables` is translating the desired table structure (typically in the form of a *table shell*) into an `rtables` *layout*. We will cover -this translation step here. +that translation process in this vignette. ## A Table Shell From 760aafe950385c3e3e52e6483153a199bed8a8fb Mon Sep 17 00:00:00 2001 From: Gabe Becker Date: Fri, 24 Oct 2025 15:44:18 -0700 Subject: [PATCH 6/7] add vignette to pkgdown [skip spelling] --- _pkgdown.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index db013a22f9..0c25ef8ac8 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -60,6 +60,7 @@ articles: contents: - rtables - exploratory_analysis + - guided_intermediate_translating_shells - title: Clinical Trials navbar: Clinical Trials From da8c6d07584b1dfbf9136406134d105befda446a Mon Sep 17 00:00:00 2001 From: Joe Zhu Date: Mon, 27 Oct 2025 14:53:29 +0800 Subject: [PATCH 7/7] update wordlist --- inst/WORDLIST | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inst/WORDLIST b/inst/WORDLIST index 8fbd5cdb80..1f8e55bff4 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -7,6 +7,7 @@ ARDs biomarker BMEASIFL Bov +Bové Carreras charset Cheatsheet @@ -80,6 +81,7 @@ RStudio rtables Rua Sabans +Sabanés Saibah sas sortable