Exporting Clinical Tables with derrick

Overview

derrick converts analysis-ready clinical tables into files written by reporter. It accepts either a gtsummary object or an already assembled plain data frame, then applies report metadata, column labels, spanning headers, widths, pagination controls, and output-format selection in one call to derrick::gtsummary_reporter().

Relationship to reporter

derrick uses reporter as the rendering engine. The examples in this vignette continue to rely on reporter for:

The main difference is the level of abstraction. With reporter, the caller usually builds the report and table specification directly. derrick builds that specification from a gtsummary object or a plain data frame, then passes it to reporter.

derrick adds a clinical-table workflow layer around reporter:

A typical workflow is:

  1. Build or assemble a table.
  2. Define report metadata such as title1, footnote1, and progname.
  3. Call derrick::gtsummary_reporter() with the table, output path, layout arguments, and requested output_types.
  4. Inspect the generated files and add manual widths or pagination only when needed.

The examples below write TXT and HTML outputs so the results can be displayed inside this HTML vignette. Use the same table specification with output_types = c("RTF", "DOCX", "PDF"), or any supported subset, when final delivery files are needed.

Setup

library(dplyr)
library(gtsummary)
library(derrick)

Example 1: gtsummary summary input

The most common input is a gtsummary object. In this example, the analysis layer creates a demographic summary with a p-value column and a spanning header. derrick then exports the displayed table structure.

demog_tbl <- gtsummary::trial |>
  dplyr::select(trt, age, grade) |>
  gtsummary::tbl_summary(by = trt) |>
  gtsummary::add_p() |>
  gtsummary::modify_spanning_header(
    gtsummary::all_stat_cols() ~ "**Treatment Group**"
  )

demog_tbl
Characteristic
Treatment Group
p-value2
Drug A
N = 98
1
Drug B
N = 102
1
Age 46 (37, 60) 48 (39, 56) 0.7
    Unknown 7 4
Grade

0.9
    I 35 (36%) 33 (32%)
    II 32 (33%) 36 (35%)
    III 31 (32%) 33 (32%)
1 Median (Q1, Q3); n (%)
2 Wilcoxon rank sum test; Pearson’s Chi-squared test

For gtsummary inputs, derrick reads displayed columns, column labels, indentation rules, p-values, and spanning headers from the object.

preview_cols <- intersect(
  c("label", "stat_1", "stat_2", "p.value"),
  names(demog_tbl$table_body)
)

utils::head(demog_tbl$table_body[preview_cols])
#> # A tibble: 6 × 4
#>   label   stat_1      stat_2      p.value
#>   <chr>   <chr>       <chr>         <dbl>
#> 1 Age     46 (37, 60) 48 (39, 56)   0.718
#> 2 Unknown 7           4            NA    
#> 3 Grade   <NA>        <NA>          0.871
#> 4 I       35 (36%)    33 (32%)     NA    
#> 5 II      32 (33%)    36 (35%)     NA    
#> 6 III     31 (32%)    33 (32%)     NA
title1 <- "Table 14.1.1 Summary of Demographics"
title2 <- "Safety Population"
footnote1 <- "Percentages are based on subjects in each treatment group."
progname <- "programs/t-demog.R"

The file_path argument supplies the output stem. The actual formats written are controlled by output_types, and the function returns the generated file paths.

demog_paths <- gtsummary_reporter(
  gts_obj = demog_tbl,
  file_path = file.path(output_dir, "t-demog.rtf"),
  output_types = c("TXT", "HTML"),
  save_rds = TRUE
)

output_manifest(demog_paths)
#>   format         file bytes
#> 1    TXT  t-demog.txt   856
#> 2   HTML t-demog.html  3984

TXT preview

TXT output is useful for checking fixed-width alignment, indentation, page titles, footnotes, and line wrapping.

Table 14.1.1 Summary of Demographics
Safety Population

______________________________________________
                   Treatment Group
               _______________________
                 Drug A      Drug B
                 N = 98      N = 102
Characteristic    N (%)       N (%)    p-value
______________________________________________

Age            46 (37, 60) 48 (39, 56) 0.7183
    Unknown         7           4
Grade                                  0.8708
    I           35 (36%)    33 (32%)
    II          32 (33%)    36 (35%)
    III         31 (32%)    33 (32%)
______________________________________________
Percentages are based on subjects in each tr...

                                                                          programs/t-demog.R 03JUL2026 19:03

HTML preview

HTML output is useful for a quick browser review of proportional-width table layout and header styling.

Example 2: gtsummary cross-table input

Different gtsummary table types use the same export function. This example uses tbl_cross() to create a treatment-by-grade frequency table.

cross_tbl <- gtsummary::trial |>
  dplyr::select(trt, grade) |>
  gtsummary::tbl_cross(
    row = grade,
    col = trt,
    percent = "row"
  ) |>
  gtsummary::modify_spanning_header(
    gtsummary::all_stat_cols() ~ "**Treatment Group**"
  )

cross_tbl
Treatment Group
Drug A Drug B Total
Grade


    I 35 (51%) 33 (49%) 68 (100%)
    II 32 (47%) 36 (53%) 68 (100%)
    III 31 (48%) 33 (52%) 64 (100%)
Total 98 (49%) 102 (51%) 200 (100%)
title1 <- "Table 14.1.2 Grade by Treatment"
title2 <- "Safety Population"
footnote1 <- "Percentages are row percentages."
progname <- "programs/t-grade-cross.R"

cross_paths <- gtsummary_reporter(
  gts_obj = cross_tbl,
  file_path = file.path(output_dir, "t-grade-cross.rtf"),
  output_types = c("TXT", "HTML"),
  save_rds = FALSE
)

output_manifest(cross_paths)
#>   format               file bytes
#> 1    TXT  t-grade-cross.txt   674
#> 2   HTML t-grade-cross.html  3640

TXT preview

Table 14.1.2 Grade by Treatment
Safety Population

_____________________________________
               Treatment Group
        _____________________________
         Drug A   Drug B     Total
         N (%)     N (%)     N (%)
_____________________________________

Grade
    I   35 (51%) 33 (49%)  68 (100%)
    II  32 (47%) 36 (53%)  68 (100%)
    III 31 (48%) 33 (52%)  64 (100%)
Total   98 (49%) 102 (51%) 200 (100%)
_____________________________________
Percentages are row percentages.

                                                                    programs/t-grade-cross.R 03JUL2026 19:03

HTML preview

Example 3: plain data frame input

Use a plain data frame when the table has already been assembled outside gtsummary, for example from a validated analysis data set or a custom summary program. In this mode, provide report labels and spanning headers explicitly, because there is no gtsummary styling metadata to read.

ae_tbl <- data.frame(
  label = c(
    "Serious adverse events",
    "  Subjects with at least one event",
    "  Events leading to study drug interruption",
    "Treatment-emergent adverse events",
    "  Headache",
    "  Nausea",
    "  Alanine aminotransferase increased"
  ),
  placebo = c("", "2 (10.0%)", "1 (5.0%)", "", "6 (30.0%)", "4 (20.0%)", "1 (5.0%)"),
  active_low = c("", "1 (5.0%)", "0", "", "5 (25.0%)", "3 (15.0%)", "2 (10.0%)"),
  active_high = c("", "3 (15.0%)", "2 (10.0%)", "", "7 (35.0%)", "5 (25.0%)", "3 (15.0%)"),
  total = c("", "6 (10.0%)", "3 (5.0%)", "", "18 (30.0%)", "12 (20.0%)", "6 (10.0%)"),
  stringsAsFactors = FALSE
)

ae_tbl
#>                                         label   placebo active_low active_high
#> 1                      Serious adverse events                                 
#> 2            Subjects with at least one event 2 (10.0%)   1 (5.0%)   3 (15.0%)
#> 3   Events leading to study drug interruption  1 (5.0%)          0   2 (10.0%)
#> 4           Treatment-emergent adverse events                                 
#> 5                                    Headache 6 (30.0%)  5 (25.0%)   7 (35.0%)
#> 6                                      Nausea 4 (20.0%)  3 (15.0%)   5 (25.0%)
#> 7          Alanine aminotransferase increased  1 (5.0%)  2 (10.0%)   3 (15.0%)
#>        total
#> 1           
#> 2  6 (10.0%)
#> 3   3 (5.0%)
#> 4           
#> 5 18 (30.0%)
#> 6 12 (20.0%)
#> 7  6 (10.0%)
title1 <- "Table 14.3.1 Overview of Adverse Events"
title2 <- "Safety Population"
footnote1 <- "Events are counted once per subject within each row."
progname <- "programs/t-ae-overview.R"

ae_paths <- gtsummary_reporter(
  gts_obj = ae_tbl,
  file_path = file.path(output_dir, "t-ae-overview.rtf"),
  column_labels = c(
    label = "System Organ Class / Preferred Term",
    placebo = "Placebo",
    active_low = "Active Low",
    active_high = "Active High",
    total = "Total"
  ),
  spanning_headers = data.frame(
    from = "placebo",
    to = "total",
    label = "Treatment Group"
  ),
  output_types = c("TXT", "HTML"),
  save_rds = FALSE
)

output_manifest(ae_paths)
#>   format               file bytes
#> 1    TXT  t-ae-overview.txt  1338
#> 2   HTML t-ae-overview.html  4612

TXT preview

Table 14.3.1 Overview of Adverse Events
Safety Population

____________________________________________________________________________________
                                                         Treatment Group
                                            ________________________________________
                                                       Active    Active
System Organ Class / Preferred Term          Placebo     Low      High      Total
____________________________________________________________________________________

Serious adverse events
  Subjects with at least one event          2 (10.0%) 1 (5.0%)  3 (15.0%) 6 (10.0%)
  Events leading to study drug interruption 1 (5.0%)      0     2 (10.0%)  3 (5.0%)
Treatment-emergent adverse events
  Headache                                  6 (30.0%) 5 (25.0%) 7 (35.0%) 18 (30.0%)
  Nausea                                    4 (20.0%) 3 (15.0%) 5 (25.0%) 12 (20.0%)
  Alanine aminotransferase increased        1 (5.0%)  2 (10.0%) 3 (15.0%) 6 (10.0%)
____________________________________________________________________________________
Events are counted once per subject within each row.

                                                                    programs/t-ae-overview.R 03JUL2026 19:03

HTML preview

Example 4: controlling column widths

The same table can look very different depending on the width controls. Start with reporter-managed automatic widths, then use max_table_width or column_widths when the report shell requires a specific layout.

Automatic widths

With no width overrides, derrick passes no column widths to reporter. reporter then estimates the display columns from the target output format, font settings, headers, table content, and available page width.

title1 <- "Table 14.3.1 Overview of Adverse Events"
title2 <- "Automatic column widths"
footnote1 <- "This version lets reporter estimate the width of each column."
progname <- "programs/t-ae-overview.R"

ae_auto_paths <- gtsummary_reporter(
  gts_obj = ae_tbl,
  file_path = file.path(output_dir, "t-ae-auto.rtf"),
  column_labels = c(
    label = "System Organ Class / Preferred Term",
    placebo = "Placebo",
    active_low = "Active Low",
    active_high = "Active High",
    total = "Total"
  ),
  spanning_headers = data.frame(
    from = "placebo",
    to = "total",
    label = "Treatment Group"
  ),
  output_types = "TXT",
  save_rds = FALSE
)

basename(ae_auto_paths)
#> [1] "t-ae-auto.txt"
Table 14.3.1 Overview of Adverse Events
Automatic column widths

____________________________________________________________________________________
                                                         Treatment Group
                                            ________________________________________
                                                       Active    Active
System Organ Class / Preferred Term          Placebo     Low      High      Total
____________________________________________________________________________________

Serious adverse events
  Subjects with at least one event          2 (10.0%) 1 (5.0%)  3 (15.0%) 6 (10.0%)
  Events leading to study drug interruption 1 (5.0%)      0     2 (10.0%)  3 (5.0%)
Treatment-emergent adverse events
  Headache                                  6 (30.0%) 5 (25.0%) 7 (35.0%) 18 (30.0%)
  Nausea                                    4 (20.0%) 3 (15.0%) 5 (25.0%) 12 (20.0%)
  Alanine aminotransferase increased        1 (5.0%)  2 (10.0%) 3 (15.0%) 6 (10.0%)
____________________________________________________________________________________
This version lets reporter estimate the width of each column.

                                                                    programs/t-ae-overview.R 03JUL2026 19:03

Narrow max_table_width

max_table_width caps the whole table by passing a table-width constraint to reporter. A narrower width forces long labels to wrap sooner and leaves less room for treatment columns.

title1 <- "Table 14.3.1 Overview of Adverse Events"
title2 <- "Narrow table width"
footnote1 <- "This version caps the total table width at 5.5 inches."
progname <- "programs/t-ae-overview.R"

ae_narrow_paths <- gtsummary_reporter(
  gts_obj = ae_tbl,
  file_path = file.path(output_dir, "t-ae-narrow.rtf"),
  column_labels = c(
    label = "System Organ Class / Preferred Term",
    placebo = "Placebo",
    active_low = "Active Low",
    active_high = "Active High",
    total = "Total"
  ),
  spanning_headers = data.frame(
    from = "placebo",
    to = "total",
    label = "Treatment Group"
  ),
  max_table_width = 5.5,
  output_types = "TXT",
  save_rds = FALSE
)

basename(ae_narrow_paths)
#> [1] "t-ae-narrow.txt"
Table 14.3.1 Overview of Adverse Events
Narrow table width

____________________________________________________________________________________
                                                         Treatment Group
                                            ________________________________________
                                                       Active    Active
System Organ Class / Preferred Term          Placebo     Low      High      Total
____________________________________________________________________________________

Serious adverse events
  Subjects with at least one event          2 (10.0%) 1 (5.0%)  3 (15.0%) 6 (10.0%)
  Events leading to study drug interruption 1 (5.0%)      0     2 (10.0%)  3 (5.0%)
Treatment-emergent adverse events
  Headache                                  6 (30.0%) 5 (25.0%) 7 (35.0%) 18 (30.0%)
  Nausea                                    4 (20.0%) 3 (15.0%) 5 (25.0%) 12 (20.0%)
  Alanine aminotransferase increased        1 (5.0%)  2 (10.0%) 3 (15.0%) 6 (10.0%)
____________________________________________________________________________________
This version caps the total table width at 5.5 inches.

                                                                    programs/t-ae-overview.R 03JUL2026 19:03

Manual column_widths

column_widths assigns widths in display-column order. For this data frame, the order is label, placebo, active_low, active_high, and total. Manual widths are useful when the label column needs more space than reporter’s automatic estimate provides.

title1 <- "Table 14.3.1 Overview of Adverse Events"
title2 <- "Manual column widths"
footnote1 <- "This version gives more room to the label column."
progname <- "programs/t-ae-overview.R"

ae_manual_paths <- gtsummary_reporter(
  gts_obj = ae_tbl,
  file_path = file.path(output_dir, "t-ae-manual.rtf"),
  column_labels = c(
    label = "System Organ Class / Preferred Term",
    placebo = "Placebo",
    active_low = "Active Low",
    active_high = "Active High",
    total = "Total"
  ),
  spanning_headers = data.frame(
    from = "placebo",
    to = "total",
    label = "Treatment Group"
  ),
  column_widths = "3.8|1.1|1.3|1.3|1.1",
  output_types = "TXT",
  save_rds = FALSE
)

basename(ae_manual_paths)
#> [1] "t-ae-manual.txt"
Table 14.3.1 Overview of Adverse Events
Manual column widths

_______________________________________________________________________________________________________
                                                                   Treatment Group
                                              _________________________________________________________
System Organ Class / Preferred Term             Placebo      Active Low      Active High      Total
_______________________________________________________________________________________________________

Serious adverse events
  Subjects with at least one event             2 (10.0%)      1 (5.0%)        3 (15.0%)     6 (10.0%)
  Events leading to study drug interruption     1 (5.0%)          0           2 (10.0%)      3 (5.0%)
Treatment-emergent adverse events
  Headache                                     6 (30.0%)      5 (25.0%)       7 (35.0%)     18 (30.0%)
  Nausea                                       4 (20.0%)      3 (15.0%)       5 (25.0%)     12 (20.0%)
  Alanine aminotransferase increased            1 (5.0%)      2 (10.0%)       3 (15.0%)     6 (10.0%)
_______________________________________________________________________________________________________
This version gives more room to the label column.

                                                                    programs/t-ae-overview.R 03JUL2026 19:03

Example 5: manual pagination

By default, rows_per_page = NULL leaves pagination to reporter. Set rows_per_page when a table shell requires predictable manual chunks before the report is written. The example below expands the adverse event table and starts a new page after every seven data rows.

paged_ae_tbl <- rbind(
  ae_tbl,
  data.frame(
    label = c(
      "Skin and subcutaneous tissue disorders",
      "  Rash",
      "  Pruritus",
      "Respiratory, thoracic and mediastinal disorders",
      "  Cough",
      "  Oropharyngeal pain",
      "Gastrointestinal disorders",
      "  Diarrhea",
      "  Abdominal pain",
      "Investigations",
      "  Blood creatine phosphokinase increased"
    ),
    placebo = c(
      "", "2 (10.0%)", "1 (5.0%)", "", "3 (15.0%)", "1 (5.0%)",
      "", "2 (10.0%)", "1 (5.0%)", "", "1 (5.0%)"
    ),
    active_low = c(
      "", "3 (15.0%)", "1 (5.0%)", "", "2 (10.0%)", "2 (10.0%)",
      "", "4 (20.0%)", "2 (10.0%)", "", "2 (10.0%)"
    ),
    active_high = c(
      "", "4 (20.0%)", "2 (10.0%)", "", "5 (25.0%)", "2 (10.0%)",
      "", "5 (25.0%)", "3 (15.0%)", "", "4 (20.0%)"
    ),
    total = c(
      "", "9 (15.0%)", "4 (6.7%)", "", "10 (16.7%)", "5 (8.3%)",
      "", "11 (18.3%)", "6 (10.0%)", "", "7 (11.7%)"
    ),
    stringsAsFactors = FALSE
  )
)

nrow(paged_ae_tbl)
#> [1] 18
title1 <- "Table 14.3.2 Adverse Events by Body System"
title2 <- "Manual pagination"
footnote1 <- "This version starts a new report page after every seven data rows."
progname <- "programs/t-ae-paged.R"

paged_paths <- gtsummary_reporter(
  gts_obj = paged_ae_tbl,
  file_path = file.path(output_dir, "t-ae-paged.rtf"),
  column_labels = c(
    label = "System Organ Class / Preferred Term",
    placebo = "Placebo",
    active_low = "Active Low",
    active_high = "Active High",
    total = "Total"
  ),
  spanning_headers = data.frame(
    from = "placebo",
    to = "total",
    label = "Treatment Group"
  ),
  rows_per_page = 7,
  output_types = c("TXT", "HTML"),
  save_rds = FALSE
)

output_manifest(paged_paths)
#>   format            file bytes
#> 1    TXT  t-ae-paged.txt  3845
#> 2   HTML t-ae-paged.html 12019

TXT preview

TXT output repeats the title, table header, footnote, and footer after each manual page break.

Table 14.3.2 Adverse Events by Body System
Manual pagination

____________________________________________________________________________________
                                                         Treatment Group
                                            ________________________________________
                                                       Active    Active
System Organ Class / Preferred Term          Placebo     Low      High      Total
____________________________________________________________________________________

Serious adverse events
  Subjects with at least one event          2 (10.0%) 1 (5.0%)  3 (15.0%) 6 (10.0%)
  Events leading to study drug interruption 1 (5.0%)      0     2 (10.0%)  3 (5.0%)
Treatment-emergent adverse events
  Headache                                  6 (30.0%) 5 (25.0%) 7 (35.0%) 18 (30.0%)
  Nausea                                    4 (20.0%) 3 (15.0%) 5 (25.0%) 12 (20.0%)
  Alanine aminotransferase increased        1 (5.0%)  2 (10.0%) 3 (15.0%) 6 (10.0%)
____________________________________________________________________________________
This version starts a new report page after every seven data rows.

                                                                       programs/t-ae-paged.R 03JUL2026 19:03
Table 14.3.2 Adverse Events by Body System
Manual pagination

________________________________________________________________________________________
                                                             Treatment Group
                                                ________________________________________
                                                           Active    Active
System Organ Class / Preferred Term              Placebo     Low      High      Total
________________________________________________________________________________________

Skin and subcutaneous tissue disorders
  Rash                                          2 (10.0%) 3 (15.0%) 4 (20.0%) 9 (15.0%)
  Pruritus                                      1 (5.0%)  1 (5.0%)  2 (10.0%)  4 (6.7%)
Respiratory, thoracic and mediastinal disorders
  Cough                                         3 (15.0%) 2 (10.0%) 5 (25.0%) 10 (16.7%)
  Oropharyngeal pain                            1 (5.0%)  2 (10.0%) 2 (10.0%)  5 (8.3%)
Gastrointestinal disorders
________________________________________________________________________________________
This version starts a new report page after every seven data rows.

                                                                       programs/t-ae-paged.R 03JUL2026 19:03
Table 14.3.2 Adverse Events by Body System
Manual pagination

_________________________________________________________________________________
                                                      Treatment Group
                                         ________________________________________
                                                    Active    Active
System Organ Class / Preferred Term       Placebo     Low      High      Total
_________________________________________________________________________________

  Diarrhea                               2 (10.0%) 4 (20.0%) 5 (25.0%) 11 (18.3%)
  Abdominal pain                         1 (5.0%)  2 (10.0%) 3 (15.0%) 6 (10.0%)
Investigations
  Blood creatine phosphokinase increased 1 (5.0%)  2 (10.0%) 4 (20.0%) 7 (11.7%)
_________________________________________________________________________________
This version starts a new report page after every seven data rows.

                                                                       programs/t-ae-paged.R 03JUL2026 19:03

HTML preview

Common layout controls

These arguments are the main controls to review when moving from a draft table to a production report shell:

gtsummary_reporter(
  gts_obj = ae_tbl,
  file_path = file.path(output_dir, "t-ae-final.rtf"),
  column_labels = c(
    label = "System Organ Class / Preferred Term",
    placebo = "Placebo",
    active_low = "Active Low",
    active_high = "Active High",
    total = "Total"
  ),
  spanning_headers = data.frame(
    from = "placebo",
    to = "total",
    label = "Treatment Group"
  ),
  column_widths = "3.8|1.1|1.3|1.3|1.1",
  max_chars_per_line = 132,
  rows_per_page = 24,
  output_types = c("RTF", "TXT", "HTML")
)

Output checklist

Before treating generated files as final, check that: