A plan is built from small declarations. Tables from an ARD walks through one table from start to finish. This article is the reference to keep open while you write your own: which verb says what, grouped by the job it does, with a short example of each.
Two rules hold for every verb:
-
Each verb has one job. Its arguments have the names
of the function that does that job:
widen_ard()for the cells,as_rtftables()for the pages,set_col_header()for the header. If you know those functions, you already know the arguments. - A later layer wins. A verb can be called more than once. For the same key, the last call decides, so a house style is written once and one table changes it by adding a line.
A table made with these verbs: the vital signs example under Pages.

The code is folded under Show code below each step.
Show code
library(rtfreporter)
library(cards)
arms <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")
adsl <- pharmaverseadam::adsl
adsl <- adsl[adsl$SAFFL == "Y", ]
adsl$TRT01A <- factor(adsl$TRT01A, levels = arms)
ard <- ard_stack(
adsl, .by = TRT01A,
ard_summary(variables = c(AGE, TRTDURD),
statistic = ~ continuous_summary_fns(
c("N", "mean", "sd", "median", "min", "max"))),
ard_tabulate(variables = c(SEX, RACE)))
base <- ard |>
normalize_ard() |>
table_plan(cols = "TRT01A", rows = c(group = "variable"))base has the roles and nothing else. Each section below
adds to it.
At a glance
The verbs in three groups, by how often a clinical table uses them.
Start here: the verbs of every table
Learn these first. Every table of a study writes them.
| Job | Verb |
|---|---|
| The roles: what goes across, what goes down | table_plan(x, cols, rows, label, stat, show_keys) |
| How a cell is made | plan_cells(..., stats, value, na, notes) |
| The digits | plan_digits(..., rounding) |
| The text and order of values |
plan_labels(), plan_levels()
|
| The column header | plan_col_header(header, values) |
| Run it, look inside |
plan_apply(plan, stage), plan_layers(),
print()
|
The study’s house style is in every table too, but written once for the study and rarely changed for one table:
| Job | Verb |
|---|---|
| The row headings | plan_stub(vars, name, indent, group_summary, before) |
| Blank rows | plan_blanks(where, first, last, counted) |
| How many rows on a page, BY pages | plan_paginate_rows(max_rows, split, break_before, min_group_rows, cont_label, page_by, heading) |
| The columns: widths, decimal alignment | plan_columns(widths, decimal, row_title, auto_width, sep) |
| The whole table’s look | plan_style(border, align_count_pct, font, ..., border_header, ...) |
Sometimes: what some tables add
| Job | Verb |
|---|---|
| A Total column | plan_total(label, position) |
| A test or a difference as a column | plan_compare(..., by, position) |
| The row order (adverse events by frequency) | plan_sort(..., stat, keep) |
| Which rows stay (common terms only) | plan_filter(..., any, parents, by) |
| A column from a group of statistics | plan_stat_groups(..., .variables, .others) |
| A page per value (a lab parameter) | plan_paginate_group(col, keep, heading) |
| Titles and footnotes, when the plan carries them |
plan_titles(), plan_footnotes()
|
| A listing | plan_listing(..., type, sep, spacer, spacer_rel_width, layout, wrap) |
Rarely: there when a table needs it
| Job | Verb |
|---|---|
| Columns not printed | plan_hide(...) |
| Groups down the body | plan_row_group(mode, collapse) |
| Some cells’ look | plan_cell_style(cols, header, where, bold, italic, align, color, background, border) |
| A table too wide for the page | plan_paginate_cols(at, cut_by, every, keep, col_header, fit, allow_span_break, order) |
| Anything else | plan_after(...) |
The order to write them in
A plan is a list of declarations, so the verbs can come in any order and make the same table (a verb said twice: the later one wins). One order reads best, though: the order a table is designed in. First the mapping, what goes where; then the finishing, how it looks.
-
The mapping:
normalize_ard(),table_plan(),plan_cells(),plan_stat_groups(),plan_total(),plan_compare(),plan_filter(). -
The finishing:
plan_labels(),plan_levels(),plan_nest(),plan_digits(),plan_sort(),plan_col_header(),plan_stub(),plan_blanks(),plan_paginate_*(),plan_columns(),plan_style(),plan_cell_style().
A table writes only the verbs it needs, in this order, and skips the rest. The three groups above say how often a verb is used; this order says where it goes. The examples of Get started and Tables from an ARD follow it, and so does the code tflspec writes from a specification.
The cells
plan_cells(): templates
A template is text with {statistic} tokens.
{mean:.1f} has one decimal and {p:.1f%} is a
percentage. The shape of the value says how many rows it makes:
- one string: one row per record, labelled by the record (a category level);
- a named vector: one row per name, and the name is the row label;
- a vector with a guard,
condition ~ template: the first element that applies wins.
Entries are keyed by an analysis variable, by the kind of variable
(continuous, categorical) or left unnamed as
the default. The most specific key wins:
Show code
p <- base |>
plan_cells(
continuous = c("n" = "{N:.0f}",
"Mean (SD)" = "{mean:.1f} ({sd:.2f})",
"Median" = "{median:.1f}",
"Min, Max" = "{min:.1f}, {max:.1f}"),
categorical = c(n == 0 ~ "0", "{n:.0f} ({p:.1f%})"),
AGE = c("n" = "{N:.0f}", "Mean (SD)" = "{mean:.1f} ({sd:.2f})"),
notes = FALSE)
plan_apply(p, "table")[1:7, ]#> group label Placebo Xanomeline Low Dose Xanomeline High Dose
#> 1 AGE n 86 96 72
#> 2 AGE Mean (SD) 75.2 (8.59) 76.0 (8.11) 73.8 (7.94)
#> 3 TRTDURD n 85 95 72
#> 4 TRTDURD Mean (SD) 149.5 (60.35) 86.8 (70.47) 112.2 (65.52)
#> 5 TRTDURD Median 182.0 63.0 96.5
#> 6 TRTDURD Min, Max 7.0, 210.0 1.0, 212.0 15.0, 200.0
#> 7 SEX F 53 (61.6) 55 (57.3) 35 (48.6)

AGE has its own entry, so it shows two rows.
TRTDURD (treatment duration) uses the
continuous one.
stats = "rows" gives each statistic a row of its own,
labelled by the statistic, without templates. This is the shape of a PK
concentration table. na is the text for a cell with no
value. notes = FALSE turns off the message listing
statistics that no template used.
plan_digits(): the digits, as a house rule
A token that leaves its format open ({mean},
{p:%}) takes its digits from plan_digits().
The digits can be keyed by statistic and by variable, and a later call
wins, so a house rule and its exceptions are separate lines:
Show code
p <- base |>
plan_cells(continuous = c("Mean (SD)" = "{mean} ({sd})", "Median" = "{median}"),
categorical = "{n} ({p:%})", notes = FALSE) |>
plan_digits(continuous = c(mean = 1, sd = 2, median = 1),
categorical = c(p = 1)) |> # the house rule
plan_digits(TRTDURD = c(mean = 0, sd = 1)) # one variable
plan_apply(p, "table")[1:6, ]#> group label Placebo Xanomeline Low Dose Xanomeline High Dose
#> 1 AGE Mean (SD) 75.2 (8.59) 76.0 (8.11) 73.8 (7.94)
#> 2 AGE Median 76.0 78.0 75.5
#> 3 TRTDURD Mean (SD) 150 (60.4) 87 (70.5) 112 (65.5)
#> 4 TRTDURD Median 182.0 63.0 96.5
#> 5 SEX F 53 (61.6) 55 (57.3) 35 (48.6)
#> 6 SEX M 33 (38.4) 41 (42.7) 37 (51.4)

A token that sets its own format ({mean:.2f}) keeps it.
plan_digits(rounding = "sas") rounds halves away from zero
as SAS does. The default rounds as R does – or as
options(rtfreporter.rounding = ) says, the one rule every
formatter in the package shares. round_num() is that rule
on its own, for numbers you round yourself:
plan_levels() and plan_labels()
plan_levels() sets the order of a key’s values, or of a
variable’s levels. plan_labels() sets how a key’s values
print. Here that is the analysis variables’ names in the
group column. Both merge one key at a time, so a later call
adds an entry without repeating the others:
Show code
p <- base |>
plan_cells(continuous = c("Mean (SD)" = "{mean:.1f} ({sd:.2f})"),
categorical = "{n:.0f} ({p:.1f%})", notes = FALSE) |>
plan_labels(c(AGE = "Age (years)", TRTDURD = "Treatment duration (days)")) |>
plan_labels(c(SEX = "Sex", RACE = "Race")) |>
plan_levels(SEX = c("M", "F"))
plan_apply(p, "table")[, 1:3]#> group label Placebo
#> 1 Age (years) Mean (SD) 75.2 (8.59)
#> 2 Treatment duration (days) Mean (SD) 149.5 (60.35)
#> 3 Sex M 33 (38.4)
#> 4 Sex F 53 (61.6)
#> 5 Race AMERICAN INDIAN OR ALASKA NATIVE 0 (0.0)
#> 6 Race BLACK OR AFRICAN AMERICAN 8 (9.3)
#> 7 Race WHITE 78 (90.7)

plan_labels() sets only the text. The order of the
variables is the ARD’s, or
plan_levels(variable = c("AGE", "SEX", "RACE")) – the order
of values, of variables and of columns is always
plan_levels()’s or the data’s.
plan_levels(.drop_empty = ) names variables whose levels
no subject has – n 0 in every column – and leaves those
rows out; a level some column counts stays. An ARD made after a code
list’s full set of levels was applied counts an unused value as 0, which
a table shows by default.
The text of a category level (F,
M) is the data’s. Recode it where the ARD is made, for
example as a factor’s labels, and the table prints what the ARD
holds.
plan_sort(): the row order
Rows keep the order the data and plan_levels() give
them. plan_sort() names keys to sort by, most important
first. A minus sign means descending, and a statistic name sorts by that
statistic summed across the columns. .overall is the “any
event” row and .depth a level’s own row before the rows
under it. The usual adverse-event order is “any event first, then each
SOC with its own row first and its PTs most frequent first”:
plan_sort(".overall", "SOC", ".depth", "-n", "PT")Keep a hierarchy’s outer key before the statistic. -n
alone orders every row by its count, and the PTs lose their SOC.
Two more forms say whose count and which column:
-
-n@SOCis the count of the SOC’s own row, carried to its PTs, so the SOCs go in order of frequency and keep their PTs:plan_sort(".overall", "-n@SOC", "SOC", ".depth", "-n", "PT"). -
-n[Total](or-n[Placebo]) is the count in one column only, not the sum across the columns.-n@SOC[Total]is both.
keep = FALSE also hides the columns used only for
sorting.
Row orders by table: a cheat sheet
A sort key is a row key or the label column (by its name in the result), a statistic, or one of the forms above; write them most important first.
| The table wants | Write |
|---|---|
| a code list’s order (demographics categories, best overall response, grades) | nothing: make the column a factor in that order when the ARD is made
(cards keeps it), or plan_levels()
|
| visits in visit order | nothing: make AVISIT a factor in AVISITN
order when the ARD is made |
| an order of your own |
plan_levels(PT = c(...)), its values in the order to
show |
| the most frequent first, by the total |
plan_sort("-n[Total]") (with
plan_total(label = "Total")) |
| the most frequent first in one arm | plan_sort("-n[Drug X]") |
| the most frequent in any arm first |
plan_sort("-max(n)") ("-max(n)@SOC" for
the SOCs) |
| the fewest first | the same keys without the minus: plan_sort("n"),
"min(n)"
|
| by a rate when the arms’ denominators differ | plan_sort("-p[Total]") |
| SOCs by name, PTs by count | plan_sort(".overall", "SOC", ".depth", "-n") |
PTs by the arms’ difference (from plan_compare()) |
plan_sort("SOC", ".depth", "-estimate") (example
below) |
| SOCs by count, each with its PTs by count | plan_sort(".overall", "-n@SOC", "SOC", ".depth", "-n") |
| SOCs by count, PTs by name | plan_sort(".overall", "-n@SOC", "SOC", ".depth", "PT") |
| PTs by count, across SOCs (a PT-only table) | plan_sort("-n") |
| a listing by subject, then date | plan_sort("USUBJID", "ASTDT") |
The difference between two arms: make it at the ARD step, show it
with plan_compare(), and order the rows by its statistic
("-estimate", the largest difference first; without the
minus, the smallest):
ard <- cards::ard_stack_hierarchical(adae, variables = c(AESOC, AEDECOD), by = TRTA,
denominator = adsl, id = USUBJID)
diff <- cards::diff_ard_hierarchical(ard) # one arm minus the other
table_plan(normalize_ard(cards::bind_ard(ard, diff, .update = TRUE),
hierarchy = c("AESOC", "AEDECOD")),
cols = "TRTA", rows = c(SOC = "AESOC"), label = c(PT = "AEDECOD")) |>
plan_cells(hierarchical = "{n} ({p:.1f%})") |>
plan_compare(diff = "{estimate:.3f}") |>
plan_sort("SOC", ".depth", "-estimate") # or "-estimate@SOC" for the SOCs too
plan_filter(): which rows stay
plan_filter() keeps the rows whose records meet the
conditions. The conditions are R over the ARD’s statistics and keys,
like the guards of plan_cells():
plan_filter(p >= 0.05) # PTs with 5% or more in any arm
plan_filter(n >= 3, TRT01A != "Placebo") # 3 or more in an active arm-
any = TRUE(default): a row stays when any column meets them;FALSE: every column must. -
parents = "keep"(default): a SOC stays exactly when one of its PTs does. An overall row (“Any TEAE”) always stays. - It runs before the cells are made and the rows sorted, and nothing
is computed:
pis the ARD’s.
The rows
plan_stub(): the row headings
A demographics table usually prints the variable and its levels in
one indented column, the stub. plan_stub()
folds the row keys into it: name is the column’s name, and
indent the number of spaces for each level.
before = TRUE folds the stub before the table is
paginated, so that plan_cell_style(where = ) can see the
printed rows. Leave it FALSE (the default) when a page
split must still read a row key.
plan_row_group(), plan_blanks(),
plan_hide()
-
plan_row_group(mode = "indent")treats each heading and the rows under it as a group, so pagination keeps them together.collapse = c("grp1", "grp2")prints a repeated value once in those columns. -
plan_blanks(where = "between_groups", first = TRUE, last = TRUE)adds a blank row between groups and at the top and bottom of the body. -
plan_hide("COL")keeps a column in the data but does not print it.
The columns and the header
plan_columns()
widths are relative. When they are named, a reordered
table keeps them, and .values stands for every value
column. decimal lines numbers up on the decimal point:
plan_columns(widths = c(row_label = 4, .values = 2), decimal = ".values")row_title (the row-heading columns) and
plan_row_group(collapse = ) take names the same way. A
column can also be given by position: positions count the
printed columns, so a column hidden with
plan_hide() or taken out by page_by is not
counted.
plan_col_header()
The header is an ordinary rtf_col_header(), and its
cells can use tokens:
-
{col}: the column’s key value (the arm); with severalcolskeys,{col1},{col2}, … are the levels; -
{n}: the column’s population.values = list(n = TRUE)reads it from the ARD. Here that is the arm countsard_stack(.by = )wrote; -
{n:sum}: the total over the columns a cell spans, for a spanner.
A header row shorter than the table has its last cell repeated across the value columns, so the header does not need to know how many arms there are:
Show code
p_hdr <- base |>
plan_cells(continuous = c("Mean (SD)" = "{mean:.1f} ({sd:.2f})"),
categorical = "{n:.0f} ({p:.1f%})", notes = FALSE) |>
plan_stub(name = "row_label") |>
plan_col_header(values = list(n = TRUE), rtf_col_header(
list(col_cell(1, ""), col_cell(c(2, 4), "All arms (N={n:sum})")),
c("", "{col}"),
c("Characteristic", "(N={n})")))
p_hdr#> <table_plan> from a normalized frame, 4 layers -> RTF pages
#> cols "TRT01A"
#> rows group = "variable"
#> 1. cells continuous, categorical
#> 2. cell_options notes
#> 3. stub name, before
#> 4. header header, values
#> in -- what cols / rows / label may name:
#> TRT01A, variable, variable_level, context, stat_name, stat_label,
#> stat, stat_fmt, .kind, .depth, .label, .label_order, group1,
#> group1_level, fmt_fun, warning, error
#> after widen -- not computed yet; run it once and this print fills in
#> header tokens -- what a plan_col_header() cell may carry:
#> {n} = c(Placebo = 86, Xanomeline Low Dose = 96, Xanomeline High Dose = 72)
#> {n:sum} = 254 over every column (less over a spanner: its own columns)
#> {n:Placebo} = 86
#> {n:Xanomeline Low Dose} = 96
#> {n:Xanomeline High Dose} = 72
#> rtf_tables(doc, x) renders it; plan_apply(x, "args") shows the call

Printing the plan lists every token and the value it will take, so
you can check a header before anything is run. If a population is not in
the ARD, the header prints NA and a warning says which
cells. Give the numbers yourself with
values = list(n = c(Placebo = 86, ...)).
More columns
plan_total() and plan_compare()
-
plan_total(label = "Total")makes cards’ overall rows (fromard_stack(.overall = TRUE), or the same analysis without itsby) a column after the arms. Its(N=xx)comes from the same ARD. -
plan_compare()makes a column from the comparison records: cardx’s tests (ard_stats_chisq_test(),ard_stats_kruskal_test(), …) and differences, the records with no arm. Each sits on its variable’s block:
plan_compare(p = c(p.value < 0.001 ~ "<0.001", "{p.value:.3f}"))
plan_compare(diff = "{estimate:.2f} ({conf.low:.2f}, {conf.high:.2f})",
p = "{p.value:.3f}")
plan_compare(p = c(stats_chisq_test = "{p.value:.3f}", # each test its own way
stats_fisher_test = "{p.value:.4f}"))
plan_compare(diff = cell_rows("1" = "{estimate:.3f}", # two lines, beside
"2" = "({conf.low:.3f}, {conf.high:.3f})")) # the body'sThe names are the columns’ own. A named vector picks the test by its
context, and cell_rows() gives a cell lines that sit beside
the body’s lines. by = "COMPARISON" makes one column a
comparison, for each active arm against placebo.
plan_stat_groups(): several statistics, one column
When the columns are statistics rather than arms,
plan_stat_groups() names the groups of statistics, and
cols = ".stat_group" makes one column of each:
table_plan(cols = ".stat_group", rows = c(grp1 = "subgroup", grp2 = "subgroup_level")) |>
plan_stat_groups(n = "N", orr_ci = c("n", "estimate", "conf.low", "conf.high")) |>
plan_cells(n = c("1" = "{N:.0f}"),
orr_ci = cell_rows("1" = "{n:.0f} ({estimate:.1f%})",
"2" = "{conf.low:.1f%}, {conf.high:.1f%}")).variables limits the split to some variables, and
.others names the group the other variables go to whole.
table_plan(show_keys = "SEX") also prints a subgroup key’s
own counts as rows of their own, beside the analysis of that subgroup.
The whole table, with normalize_ard(stack = ) for the
subgroups, is in Tables from an
ARD.
The look
plan_style(): the whole table
plan_style() takes the whole-table settings
rtftable() and as_rtftables() have:
border, font_size_half_points,
row_height_twips, align_count_pct, and the
rules of each kind of row (border_header,
border_body, border_first_row, …).
plan_cell_style(): some cells
plan_cell_style() chooses cells with cols,
header or where, and says how they look. A
value applies to every chosen cell. A one-sided formula
is computed row by row, and NA leaves a cell as it is:
Show code
p_style <- base |>
plan_cells(continuous = c("Mean (SD)" = "{mean:.1f} ({sd:.2f})"),
categorical = "{n:.0f} ({p:.1f%})", notes = FALSE) |>
plan_labels(c(AGE = "Age (years)", TRTDURD = "Treatment duration (days)",
SEX = "Sex, n (%)",
RACE = "Race, n (%)")) |>
plan_stub(name = "row_label", before = TRUE) |>
plan_blanks(where = "between_groups", first = TRUE, last = TRUE) |>
plan_columns(widths = c(row_label = 4, .values = 2)) |>
plan_style(border = "tfl", align_count_pct = TRUE) |>
plan_cell_style(header = TRUE, bold = TRUE) |>
plan_cell_style(cols = "row_label", bold = ~ is.na(Placebo)) |> # the headings
plan_col_header(values = list(n = TRUE), rtf_col_header(
c("", "{col}"),
c("Characteristic", "(N={n})")))
doc <- rtf_document() |>
rtf_section(header = rtf_header(list(
c(c = "Table 14.1.2 Demographics and Treatment Duration"),
c(c = "Safety Analysis Set"), c(c = "")))) |>
rtf_tables(p_style)
generate_rtfreport(doc, "t_style.rtf", overwrite = TRUE)
The heading rows are the ones with no numbers, so
is.na(Placebo) picks them. The formula can see the heading
rows only because the stub is folded first (before = TRUE).
Without that, they do not exist yet when the styles are worked out.
Pages
Three verbs cover the three directions in which a table can split into pages:
-
by value:
plan_paginate_group(col)starts a new page for each value of a column (a lab parameter, a visit). Left out,colis the outermost row key. The page is named after the value, which is whatrtf_tables(auto_section = TRUE)starts a section on.heading = "Treatment: {value} (N={n})"names it from a template, with{n}the value’s own count in the ARD, andheading_at = "section"(or"title") says where the name prints, sortf_tables(plan)needs noauto_section = TRUE. -
by rows:
plan_paginate_rows(max_rows, split = "group_safe")sets how many rows fit on a page and keeps groups whole. Withpage_by = "arm"it first makes a page for each arm, then applies the row budget inside it, and it takes the sameheading =andheading_at =. -
by columns:
plan_paginate_cols(every = 4, keep = "row_label")splits a table too wide for the page into blocks. Each block repeats thekeepcolumns.
The key a page is cut on (col, page_by) is
a page key. It is the same on every row of a page, so
it is never a row group: the blank rows between groups,
collapse, row_title, the stub and column
positions start at the first printed row key, and first /
last apply on every page.
A vital signs table with a page per parameter, and two columns (value and change from baseline) under each arm:
Show code
visits <- c("Baseline", "Week 8", "Week 16", "Week 24")
advs <- pharmaverseadam::advs
advs <- advs[advs$PARAMCD %in% c("SYSBP", "DIABP") &
advs$ATPT %in% "AFTER LYING DOWN FOR 5 MINUTES" &
(advs$ABLFL %in% "Y" | advs$ANL01FL %in% "Y") &
advs$AVISIT %in% visits & advs$USUBJID %in% adsl$USUBJID, ]
advs$TRTA <- factor(advs$TRT01A, levels = arms)
advs$AVISIT <- factor(advs$AVISIT, levels = visits)
stats <- ~ continuous_summary_fns(c("N", "mean", "sd"))
ard_vs <- bind_ard(
ard_summary(advs, by = c(PARAM, AVISIT, TRTA), variables = AVAL,
statistic = stats),
ard_summary(droplevels(advs[advs$AVISIT != "Baseline", ]),
by = c(PARAM, AVISIT, TRTA), variables = CHG,
statistic = stats))
p_vs <- ard_vs |>
normalize_ard() |>
table_plan(cols = c("TRTA", "variable"),
rows = c(PARAM = "PARAM", Visit = "AVISIT")) |>
plan_levels(variable = c("AVAL", "CHG")) |>
plan_cells(continuous = c("n" = "{N:.0f}", "Mean (SD)" = "{mean:.1f} ({sd:.2f})"),
na = "", notes = FALSE) |>
plan_stub(name = "Visit") |>
plan_paginate_group() |> # a page per parameter
plan_blanks(where = "between_groups", first = TRUE, last = TRUE) |>
plan_columns(widths = c(5, rep(2, 6))) |>
plan_style(border = "tfl") |>
plan_col_header(values = list(n = c(table(adsl$TRT01A))), rtf_col_header(
c(list(col_cell(1, "")),
lapply(1:3, function(i) col_cell(c(2 * i, 2 * i + 1), "{col1} (N={n})"))),
c("Visit", rep(c("Value", "Change from\nbaseline"), 3))))
doc <- rtf_document(page = rtf_page(orientation = "landscape")) |>
rtf_section(header = rtf_header(list(
c(c = "Table 14.3.5.1 Vital Signs by Visit"),
c(c = "Safety Analysis Set"), c(c = "")))) |>
rtf_tables(p_vs)
generate_rtfreport(doc, "t_vs.rtf", overwrite = TRUE)
length(plan_apply(p_vs))#> [1] 2
Its first page is the picture at the top of this article.
Here the arm counts come from ADSL
(values = list(n = ...)), because the columns are arm ×
variable and the vital-signs ARD does not state the arms’
populations.
Titles, footnotes, and the way out
plan_titles() and plan_footnotes() put the
same block on every page. With pages =, you give one block
for each page instead. Titles are often shared by a whole report and set
in the running header, as in the examples above. These verbs are for the
text that belongs to one table.
plan_after(function(pages) ...) runs your own functions
on the finished pages. It is for what no verb declares. Prefer a verb
when there is one: a declaration names columns and so survives a
reordered table, while a function written against positions does not. A
plan written with verbs alone can also be saved to a table spec by
tflspec; a plan_after() step cannot.
Looking inside
-
print(plan): the layers, the column names the roles may use, and the header tokens with their values. -
plan_apply(plan, stage = ):"input"is the frame going in,"table"the table data frame,"args"the calls the plan resolves to (without running them),"pages"the pages. -
plan_layers(plan): the plan read back, layer by layer.
stage = "args" answers “which of my lines decided
this?”. It shows the argument lists after every layer has been
merged:
a <- plan_apply(p_vs, "args")
names(a)
#> [1] "widen" "rtf"
str(a$rtf[c("split", "blank_rows", "blank_row_first", "blank_row_end")])
#> List of 4
#> $ split : chr "by_value"
#> $ blank_rows : chr "between_groups"
#> $ blank_row_first: logi TRUE
#> $ blank_row_end : logi TRUE