Reads 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)

Arguments

path

Path to a YAML shell file.

Value

An object of class shell2tlf_shell: a list with elements meta, display, title, subtitles, footnotes, source, population, columns, rows, listing and pagination.

Details

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.

Shell structure

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 }

Row types

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.

Shift denominators

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.

Listings

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.

Limits

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.

See also

validate_shell() to check the shell against real data.

Examples

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)