Initial development version.

  • Shell: read_shell() parses a YAML display shell – titles, subtitles, footnotes, source line, population filter, column structure with spanning headers, row structure with per-row statistics, and pagination – into a validated shell2tlf_shell. new_shell() does the same from a list. Every structural problem is reported in one error, not one at a time. YAML’s y/n/yes/no booleans are kept as text so the statistic n and the flag value "Y" survive parsing.
  • Code: shell_code() emits readable, runnable {rtables} source for the display – population filter, factor set-up, column layout, one layout step per shell row, build_table() and any post-build sorting. print() renders it as a code block; writeLines() saves it as a program.
  • Build: build_tlf() evaluates exactly the code shell_code() prints and returns a plain {rtables} TableTree, so the reviewed code and the delivered table cannot drift apart.
  • Render: render_tlf() writes RTF with {r2rtf}, taking titles, footnotes, source, orientation and rows-per-page from the shell. Spanning headers are merged into single wide RTF cells; row nesting becomes a real left indent.
  • Validate: validate_shell() checks a shell against real data and returns a tibble of problems – absent variables, statistics asked of the wrong type, empty by-groups, missing denominator datasets, levels declared but not present.
  • Two worked examples in inst/extdata: a demographics summary with a spanning header, and a system-organ-class / preferred-term adverse-event table with subject-level counts and frequency sorting.

Added since the initial version

  • Listings (display: listing). One row per record, in the order listing.sort_by gives, with one column per listing.vars entry labelled by listing.labels. A listing is deliberately not de-duplicated: the emitted program has no aggregation step, so nothing can collapse a repeated key. It is built with rtables::df_to_tt() and comes back as an ordinary TableTree, so render_tlf() needed no listing branch. columns: and rows: are refused in a listing shell rather than silently ignored. Worked example: inst/extdata/ae_listing.yaml.
  • Shift tables (type: shift). baseline_var cross-tabulated against var over a shared levels set that is required, not inferred, so the table stays square. The denominator is stated in the shell, never guessed: denominator: baseline_row (subjects or records in the same baseline category and the same column, each block summing to 100%) or denominator: column (the column N). The baseline_row denominator is printed on the baseline group row, so the number the percentages were divided by is on the page. The comment above denom in the generated code names the population as well. Worked example: inst/extdata/lab_shift.yaml, built on pharmaverseadam::adlb.
  • validate_shell() counts the records whose baseline or post-baseline value falls outside the shell’s levels. They leave both the numerator and the denominator, and an unnoticed handful is how a shift table stops reconciling with its own column header. The shipped fixture has one such record, and the example shows it.
  • Snapshot tests pin the generated code and built table for the v1 demographics and adverse-event shells, so the new display types cannot alter an unchanged one.

Known limits in this version

  • Display types are summary, count, shift and listing (see README “Scope”).
  • Figures are out of scope. Nothing here produces or renders a plot.
  • Inferential columns are out of scope: no p-values, treatment differences, confidence intervals for a difference, or model-based statistics. The statistic set is closed and a shell asking for one fails validation rather than producing a display that looks right and is not.
  • One column split plus one optional spanning header.
  • A shift row must be the last row at its level, because rtables nests everything that follows a row split; several parameters means several shells.
  • Listing values are reproduced with as.character(); rounding and formatting of derived variables belong in the ADaM step.
  • build_tlf() evaluates the generated program. That is the design – see the “Trust boundary” section of ?build_tlf. A shell is as trusted as an R script the same person would otherwise have written: read one you did not write before you build it.