read_shell.RdReads a YAML display shell – the mocked-up table a statistician signs off
on: titles, footnotes, population, column structure (including a spanning
header), row structure with the statistics required at each level, and
pagination – into a validated shell2tlf_shell object.
read_shell(path)An object of class shell2tlf_shell: a list with elements meta,
display, title, subtitles, footnotes, source, population,
columns, rows, listing and pagination.
The shell, not the program, is the source of truth. Everything downstream
(shell_code(), build_tlf(), render_tlf()) is derived from this object,
so a footnote or a statistic can only be changed in one place.
meta: { id, study, display }
display: table # or: listing
title: "Table 14.1.1"
subtitles: [ "Summary of Demographics", "Safety Population" ]
footnotes: [ "Percentages use ..." ]
source: "Source: adsl.csv"
population: { label, dataset, filter, denominator, denominator_filter }
columns: { var, levels, total, show_counts, spanning: { var, levels } }
rows: [ { var, label, type, stats, levels, unique_by, sort, sub,
baseline_var, denominator } ]
listing: { vars, labels, sort_by } # display: listing only
pagination: { rows_per_page, orientation, col_rel_width }type is one of:
"summary" – descriptive statistics for a numeric variable.
"count" – n and percent per level of a categorical variable. A count
row may carry sub, a list of nested rows, which produces the usual
system-organ-class / preferred-term hierarchy; a row in such a hierarchy
may also carry sort: descending to order levels by overall frequency.
"shift" – a cross-tabulation of baseline_var (rows) against var
(rows within them), the shape a lab shift table takes. It needs levels,
the category set shared by both axes, and it must be the last row at its
level because a row split nests everything that follows it. See
Shift denominators below.
unique_by: USUBJID counts distinct subjects rather than records, which is
what an adverse-event table means by "n". It applies to count and shift.
Summary statistics: n, mean, sd, mean_sd, median, q1_q3,
min_max (range), missing. Count and shift statistics: n_pct, n.
A shift row states its denominator; it is never inferred.
denominator: baseline_row (the default) – the percentage is of the
subjects (or records) in the same baseline category and the same
column, excluding any whose post-baseline category is missing. Each
baseline block then sums to 100%. This denominator is printed on the
baseline group row, so a reviewer can check it against the page.
denominator: column – the percentage is of the column N, so the
whole table sums to 100%. With unique_by, set population.denominator
as well, or the column N counts records rather than subjects.
Under either choice, a record whose baseline or post-baseline value is not
one of the declared levels is excluded from the numerator and the
denominator. validate_shell() reports how many such records exist; that
number is the difference between a shift table that reconciles and one that
does not.
display: listing produces one row per record, in the order sort_by gives,
with one column per entry of listing.vars labelled by listing.labels. A
listing is deliberately not de-duplicated: a subject with three adverse
events occupies three rows. columns: and rows: describe a summarised
display and are refused in a listing shell rather than silently ignored.
Values are reproduced with as.character(); rounding and formatting of
derived variables belong in the ADaM step, not the display.
This replaces the SAS idiom of a PROC REPORT program hand-transcribed from
a shell document, where the titles and footnotes are retyped into TITLE
and FOOTNOTE statements and drift from the shell on the first amendment;
a listing shell replaces the PROC REPORT listing whose BY and ORDER
variables silently collapse repeated records.
Figures and inferential columns (p-values, treatment differences, confidence intervals for a difference) are out of scope, and there is no partial support for them: a shell that asks for one fails validation rather than producing a display that looks right and is not.
validate_shell() to check the shell against real data.
path <- system.file("extdata", "demographics.yaml", package = "shell2tlf")
shell <- read_shell(path)
shell
#>
#> ── shell2tlf display shell ─────────────────────────────────────────────────────
#> ℹ Table 14.1.1
#> ℹ Summary of Demographic and Baseline Characteristics
#> ℹ Safety Population
#> ℹ Dataset: adsl | population: Safety Population (SAFFL == "Y")
#> ℹ Columns: TRTGRP > TRT01A + All Subjects
#>
#> ── Rows ──
#>
#> ✔ [summary] AGE - Age (years) (n, mean_sd, median, q1_q3, min_max)
#> ✔ [count] AGEGR1 - Age group (years) (n_pct)
#> ✔ [count] SEX - Sex (n_pct)
#> ✔ [count] RACE - Race (n_pct)
#> ✔ [summary] BMIBL - Baseline BMI (kg/m2) (n, mean_sd, median, min_max)
#> ℹ Footnotes: 2 | rows per page: 26 (portrait)
# a shift shell: baseline normal-range category against post-baseline
read_shell(system.file("extdata", "lab_shift.yaml", package = "shell2tlf"))
#>
#> ── shell2tlf display shell ─────────────────────────────────────────────────────
#> ℹ Table 14.3.4.1
#> ℹ Shift from Baseline to Maximum Post-Baseline Value by Normal Range
#> ℹ Alkaline Phosphatase (U/L), Safety Population
#> ℹ Dataset: adlb | population: Safety Population (SAFFL == "Y" & PARAMCD == "ALKPH" & AVISIT == "POST-BASELINE MAXIMUM")
#> ℹ Columns: TRTA + All Subjects
#>
#> ── Rows ──
#>
#> ✔ [shift] BNRIND x ANRIND - Baseline (n_pct)
#> ℹ denominator: baseline_row
#> ℹ Footnotes: 3 | rows per page: 24 (portrait)
# a listing shell
read_shell(system.file("extdata", "ae_listing.yaml", package = "shell2tlf"))
#>
#> ── shell2tlf display shell ─────────────────────────────────────────────────────
#> ℹ Listing 16.2.7.1
#> ℹ Listing of Treatment-Emergent Adverse Events
#> ℹ Safety Population
#> ℹ Dataset: adae | population: Treatment-Emergent Adverse Events (SAFFL == "Y" & TRTEMFL == "Y")
#>
#> ── Listing columns ──
#>
#> ✔ [listing] USUBJID - Subject
#> ✔ [listing] TRT01A - Treatment
#> ✔ [listing] AEBODSYS - System Organ Class
#> ✔ [listing] AEDECOD - Preferred Term
#> ✔ [listing] AESEV - Severity
#> ℹ Sorted by: USUBJID, AEBODSYS, AEDECOD (records are never combined)
#> ℹ Footnotes: 2 | rows per page: 20 (landscape)