build_tlf.RdEvaluates the code from shell_code() against real data and returns the
built rtables table. The table is produced by running the generated
source, not by a parallel implementation, so what shell_code() prints and
what build_tlf() returns cannot drift apart.
build_tlf(shell, data)A shell2tlf_shell from read_shell() or new_shell().
Either a single data frame – bound to the shell's
population.dataset – or a named list of data frames, which is required
when the shell also names a population.denominator dataset.
An rtables TableTree.
The return value is a plain rtables TableTree: pass it to
rtables::export_as_txt(), tt_to_flextable(), render_tlf(), or anything
else that already accepts one.
A listing shell returns a TableTree too, so nothing downstream needs a
listing branch.
build_tlf() runs eval(parse(text = shell_code(shell))). That is the
design, not an oversight. Generating the program and then evaluating a
different implementation is how a table and its reviewed code drift apart;
evaluating the printed text is what makes reviewing the code equivalent to
reviewing the table.
The trust boundary sits at the shell, and it sits exactly where it sits for
any R program: a shell contains expressions (population.filter,
population.denominator_filter) that are evaluated, so a shell is as
trusted as an R script the same person would otherwise have written by
hand. Read a shell you did not write before you build it, and do not build
one received from an untrusted source – the same rule you already apply to
a .R file from the same source. Nothing in the package sandboxes it,
because a sandbox that blocked dplyr::filter() would block the shell.
shell <- read_shell(system.file("extdata", "demographics.yaml",
package = "shell2tlf"))
adsl <- utils::read.csv(system.file("extdata", "adsl_demo.csv",
package = "shell2tlf"))
tbl <- build_tlf(shell, adsl)
tbl
#> Placebo Xanomeline
#> Placebo Xanomeline Low Dose Xanomeline High Dose All Subjects
#> (N=59) (N=60) (N=59) (N=178)
#> ————————————————————————————————————————————————————————————————————————————————————————————————————————————
#> Age (years)
#> n 59 60 59 178
#> Mean (SD) 75.5 (8.14) 74.1 (9.61) 75.0 (7.81) 74.9 (8.53)
#> Median 75.0 74.0 75.0 75.0
#> Q1, Q3 69.0, 81.0 68.0, 80.0 70.0, 79.5 69.0, 80.0
#> Min, Max 60.0, 92.0 52.0, 92.0 57.0, 92.0 52.0, 92.0
#> Age group (years)
#> <65 7 (11.9%) 8 (13.3%) 4 (6.8%) 19 (10.7%)
#> 65-80 37 (62.7%) 38 (63.3%) 41 (69.5%) 116 (65.2%)
#> >80 15 (25.4%) 14 (23.3%) 14 (23.7%) 43 (24.2%)
#> Sex
#> F 34 (57.6%) 26 (43.3%) 28 (47.5%) 88 (49.4%)
#> M 25 (42.4%) 34 (56.7%) 31 (52.5%) 90 (50.6%)
#> Race
#> AMERICAN INDIAN OR ALASKA NATIVE 4 (6.8%) 1 (1.7%) 2 (3.4%) 7 (3.9%)
#> BLACK OR AFRICAN AMERICAN 7 (11.9%) 6 (10.0%) 7 (11.9%) 20 (11.2%)
#> WHITE 48 (81.4%) 53 (88.3%) 50 (84.7%) 151 (84.8%)
#> Baseline BMI (kg/m2)
#> n 59 60 59 178
#> Mean (SD) 26.4 (4.43) 25.3 (5.71) 26.8 (4.72) 26.2 (5.00)
#> Median 25.6 24.2 26.4 25.4
#> Min, Max 15.0, 36.9 13.5, 40.0 17.0, 37.5 13.5, 40.0
# a listing: one row per record, never de-duplicated
lst <- read_shell(system.file("extdata", "ae_listing.yaml",
package = "shell2tlf"))
adae <- utils::read.csv(system.file("extdata", "adae_demo.csv",
package = "shell2tlf"))
nrow(build_tlf(lst, adae)) == sum(adae$SAFFL == "Y" & adae$TRTEMFL == "Y")
#> [1] TRUE