The fourth example produces an Adverse Events table by severity, with treatment groups on separate pages. The report shows statistics for All Adverse Events and System Organ Class/Preferred Term.

Program

Note the following about this example:

  • The libname() function from the libr package makes it easy to load the entire data library.
  • Functions from the sassy system integrate nicely with those from the tidyverse.
  • The reporter package is able to wrap treatment groups to separate pages using the page_wrap property.
  • The reporter package is also able to easily handle page breaks, while still respecting the established margins.
library(tidyverse)
library(sassy)

options("logr.autolog" = TRUE, 
        "logr.notes" = FALSE)


# Get Data ----------------------------------------------------------------


# Get path to temp directory
tmp <- tempdir() 

# Get path to sample data
pkg <- system.file("extdata", package = "sassy")

# Open log
lgpth <- log_open(file.path(tmp, "example4.log"))

sep("Prepare Data")

# Create libname for csv data
libname(sdtm, pkg, "csv") 

# Load data into workspace
lib_load(sdtm) 


sep("Prepare table data")
dat <- sdtm.AE |> 
  inner_join(select(sdtm.DM, USUBJID, ARM, ARMCD),  
             c("USUBJID" = "USUBJID"), 
             keep = FALSE) |> 
  filter(ARM != "SCREEN FAILURE")

put("Get population counts")
arm_pop <- dat |> 
  select(USUBJID, ARM) |>  
  distinct() |> 
  count(ARM)  |> 
  deframe() |> put()

put ("Create lookup for AE severity")
sevn <- c(MILD = 1, MODERATE = 2, SEVERE = 3) |> put()

put("Subset ADAE for needed rows and columns")
df_sub <- dat |> 
  mutate(AESEVN = sevn[AESEV]) |> 
  select(USUBJID, ARM, AESEV, AESEVN, AESOC, AEDECOD) |> 
  distinct() |> 
  put()


# Perform Calculations ----------------------------------------------------


put("Create template for needed ARM columns")
col_template <- paste0(c(rep("ARM A_", 3), rep("ARM B_", 3), rep("ARM C_", 3),
                         rep("ARM D_", 3)), rep(c(1, 2, 3), 3))



put("Identify and count highest severity grade for each subject")
df1 <- df_sub |> arrange(USUBJID, AESOC, AEDECOD, AESEVN) |> 
  group_by(USUBJID, ARM, AESOC, AEDECOD) |> 
  mutate(max_sev = ifelse(row_number() == n(), TRUE, FALSE)) |> 
  filter(max_sev == TRUE) |> 
  group_by(ARM, AESOC, AEDECOD, AESEVN) |> 
  summarize(cnt = n()) |> 
  pivot_wider(names_from = c(ARM, AESEVN),
              values_from = cnt, 
              values_fill = 0) |> 
  put()


put("Fill in missing columns where there were no events.")
for (nm in col_template) {
  if (!nm %in% names(df1))
    df1[[nm]] <- 0
}
put(df1)

put("Format counts and percents for each column")
df_events <- df1 |> 
  transmute(AESOC = AESOC, 
            AEDECOD = str_to_title(AEDECOD),
            `ARM A_1` = fmt_cnt_pct(`ARM A_1`, arm_pop["ARM A"]),
            `ARM A_2` = fmt_cnt_pct(`ARM A_2`, arm_pop["ARM A"]),
            `ARM A_3` = fmt_cnt_pct(`ARM A_3`, arm_pop["ARM A"]),
            `ARM B_1` = fmt_cnt_pct(`ARM B_1`, arm_pop["ARM B"]),
            `ARM B_2` = fmt_cnt_pct(`ARM B_2`, arm_pop["ARM B"]),
            `ARM B_3` = fmt_cnt_pct(`ARM B_3`, arm_pop["ARM B"]),
            `ARM C_1` = fmt_cnt_pct(`ARM C_1`, arm_pop["ARM C"]),
            `ARM C_2` = fmt_cnt_pct(`ARM C_2`, arm_pop["ARM C"]),
            `ARM C_3` = fmt_cnt_pct(`ARM C_3`, arm_pop["ARM C"]),
            `ARM D_1` = fmt_cnt_pct(`ARM D_1`, arm_pop["ARM D"]), 
            `ARM D_2` = fmt_cnt_pct(`ARM D_2`, arm_pop["ARM D"]), 
            `ARM D_3` = fmt_cnt_pct(`ARM D_3`, arm_pop["ARM D"])) |> 
  arrange(AESOC, AEDECOD) |> 
  ungroup() |> 
  put()


put("Get counts for All Adverse Events")
df2 <- df_sub |> 
  select(USUBJID, ARM, AESEVN) |> 
  arrange(USUBJID, AESEVN) |> 
  group_by(USUBJID) |> 
  mutate(max_sev = ifelse(row_number() == n(), TRUE, FALSE)) |> 
  filter(max_sev == TRUE) |> 
  group_by(ARM, AESEVN) |> 
  summarize(cnt = n()) |> 
  pivot_wider(names_from = c(ARM, AESEVN),
              values_from = cnt, 
              values_fill = 0) |> 
  ungroup() |> 
  put()

put("Fill in missing columns where there were no events.")
for (nm in col_template) {
  if (!nm %in% names(df2))
    df2[[nm]] <- 0
}
put(df2)

put("Format counts and percents for all adverse events.")
df_all <- df2 |> 
  transmute(AESOC = "All System Organ Classes",
            AEDECOD = "All Adverse Events", 
            `ARM A_1` = fmt_cnt_pct(`ARM A_1`, arm_pop["ARM A"]),
            `ARM A_2` = fmt_cnt_pct(`ARM A_2`, arm_pop["ARM A"]),
            `ARM A_3` = fmt_cnt_pct(`ARM A_3`, arm_pop["ARM A"]),
            `ARM B_1` = fmt_cnt_pct(`ARM B_1`, arm_pop["ARM B"]),
            `ARM B_2` = fmt_cnt_pct(`ARM B_2`, arm_pop["ARM B"]),
            `ARM B_3` = fmt_cnt_pct(`ARM B_3`, arm_pop["ARM B"]),
            `ARM C_1` = fmt_cnt_pct(`ARM C_1`, arm_pop["ARM C"]),
            `ARM C_2` = fmt_cnt_pct(`ARM C_2`, arm_pop["ARM C"]),
            `ARM C_3` = fmt_cnt_pct(`ARM C_3`, arm_pop["ARM C"]),
            `ARM D_1` = fmt_cnt_pct(`ARM D_1`, arm_pop["ARM D"]), 
            `ARM D_2` = fmt_cnt_pct(`ARM D_2`, arm_pop["ARM D"]), 
            `ARM D_3` = fmt_cnt_pct(`ARM D_3`, arm_pop["ARM D"])) |> 
  put()


# Final Data --------------------------------------------------------------

sep("Create final data frame")

final <- bind_rows(df_all, df_events) |> put()


# Print Report ----------------------------------------------------------

sep("Create and print report")

put("Create table object")
tbl <- create_table(final, first_row_blank = TRUE, width = 9) |> 
  column_defaults(from = `ARM A_1`, to = `ARM D_3`, width = 1) |> 
  spanning_header("ARM A_1", "ARM A_3", label = "ARM A", n = arm_pop["ARM A"]) |>
  spanning_header("ARM B_1", "ARM B_3", label = "ARM B", n = arm_pop["ARM B"]) |>
  spanning_header("ARM C_1", "ARM C_3", label = "ARM C", n = arm_pop["ARM C"]) |>
  spanning_header("ARM D_1", "ARM D_3", label = "ARM D", n = arm_pop["ARM D"]) |>
  stub(vars = c("AESOC", "AEDECOD"), label = "System Organ Class\n   Preferred Term", width = 5) |> 
  define(AESOC, blank_after = TRUE, label_row = TRUE) |> 
  define(AEDECOD, indent = .25) |> 
  define(`ARM A_1`, align = "center", label = "Mild") |> 
  define(`ARM A_2`, align = "center", label = "Moderate") |> 
  define(`ARM A_3`, align = "center", label = "Severe") |> 
  define(`ARM B_1`, align = "center", label = "Mild", page_wrap = TRUE) |> 
  define(`ARM B_2`, align = "center", label = "Moderate") |> 
  define(`ARM B_3`, align = "center", label = "Severe") |> 
  define(`ARM C_1`, align = "center", label = "Mild", page_wrap = TRUE) |> 
  define(`ARM C_2`, align = "center", label = "Moderate") |> 
  define(`ARM C_3`, align = "center", label = "Severe") |> 
  define(`ARM D_1`, align = "center", label = "Mild", page_wrap = TRUE) |> 
  define(`ARM D_2`, align = "center", label = "Moderate") |> 
  define(`ARM D_3`, align = "center", label = "Severe") 


pth <- file.path(tmp, "output/example4.rtf")

put("Create report object")
rpt <- create_report(pth, output_type = "RTF",
                     font = "Arial") |> 
  options_fixed(font_size = 10) |> 
  page_header("Sponsor: Company", "Study: ABC") |> 
  titles("Table 5.0", "Adverse Events by Maximum Severity",
         bold = TRUE, font_size = 12) |> 
  add_content(tbl) |> 
  footnotes("Program: AE_Table.R",
            "Note: Adverse events were coded using MedDRA Version 9.1") |> 
  page_footer(Sys.time(), "Confidential", "Page [pg] of [tpg]") 

put("Print report")
write_report(rpt) |> put()



# Clean Up ----------------------------------------------------------------

# Remove library from workspace
lib_unload(sdtm)

# Close log
log_close()

# View files
# file.show(pth)
# file.show(lgpth)

Output

Here are the first three pages of the output report:

Log

Here is part of the log from the above example:

========================================================================= 
Log Path: C:/Users/dbosa/AppData/Local/Temp/RtmpKC93lu/log/example4.log 
Working Directory: C:/packages/Testing 
User Name: dbosa 
R Version: 4.0.5 (2021-03-31) 
Machine: SOCRATES x86-64 
Operating System: Windows 10 x64 build 19041 
Log Start Time: 2021-06-27 11:30:01 
========================================================================= 

========================================================================= 
Prepare Data 
========================================================================= 

# library 'sdtm': 4 items
- attributes: csv not loaded
- path: C:/Users/dbosa/Documents/R/win-library/4.0/sassy/extdata
- items:
  Name Extension Rows Cols    Size        LastModified
1   AE       csv  150   27 88.1 Kb 2021-04-04 11:55:58
2   DM       csv   87   24 45.2 Kb 2021-04-04 11:55:58
3   SV       csv  685   10 69.9 Kb 2021-04-04 11:55:58
4   VS       csv 3358   17  467 Kb 2021-04-04 11:55:58

lib_load: library 'sdtm' loaded 

========================================================================= 
Prepare table data 
========================================================================= 

select: dropped 21 variables (STUDYID, DOMAIN, SUBJID, RFSTDTC, RFENDTC, …)

inner_join: added 2 columns (ARM, ARMCD)

            > rows only in x  (  0)

            > rows only in y  ( 30)

            > matched rows     150

            >                 =====

            > rows total       150

filter: removed 5 rows (3%), 145 rows remaining

Get population counts 

select: dropped 27 variables (STUDYID, DOMAIN, AESEQ, AETERM, AELLT, …)

distinct: removed 90 rows (62%), 55 rows remaining

count: now 4 rows and 2 columns, ungrouped

ARM A ARM B ARM C ARM D 
   14    14    14    13 

Create lookup for AE severity 

    MILD MODERATE   SEVERE 
       1        2        3 

Subset ADAE for needed rows and columns 

mutate: new variable 'AESEVN' (double) with 3 unique values and 0% NA

select: dropped 24 variables (STUDYID, DOMAIN, AESEQ, AETERM, AELLT, …)

distinct: removed 18 rows (12%), 127 rows remaining

# A tibble: 127 x 6
   USUBJID    ARM   AESEV    AESEVN AESOC                                           AEDECOD                         
   <chr>      <chr> <chr>     <dbl> <chr>                                           <chr>                           
 1 ABC-01-049 ARM D MODERATE      2 Investigations                                  BLOOD GLUCOSE INCREASED         
 2 ABC-01-049 ARM D MODERATE      2 Investigations                                  BLOOD TRIGLYCERIDES INCREASED   
 3 ABC-01-049 ARM D MILD          1 Nervous system disorders                        HEADACHE                        
 4 ABC-01-049 ARM D MODERATE      2 Investigations                                  LABORATORY TEST ABNORMAL        
 5 ABC-01-049 ARM D MILD          1 Musculoskeletal and connective tissue disorders MUSCULOSKELETAL DISCOMFORT      
 6 ABC-01-050 ARM B MILD          1 Skin and subcutaneous tissue disorders          RASH                            
 7 ABC-01-050 ARM B MILD          1 Respiratory, thoracic and mediastinal disorders UPPER RESPIRATORY TRACT CONGEST~
 8 ABC-01-051 ARM A MILD          1 Nervous system disorders                        HEADACHE                        
 9 ABC-01-051 ARM A MILD          1 General disorders and administration site cond~ INFLUENZA LIKE ILLNESS          
10 ABC-01-051 ARM A MILD          1 Respiratory, thoracic and mediastinal disorders LOWER RESPIRATORY TRACT INFLAMM~
# ... with 117 more rows

Create template for needed ARM columns 

Identify and count highest severity grade for each subject 

group_by: 4 grouping variables (USUBJID, ARM, AESOC, AEDECOD)

mutate (grouped): new variable 'max_sev' (logical) with 2 unique values and 0% NA

filter (grouped): removed one row (1%), 126 rows remaining

group_by: 4 grouping variables (ARM, AESOC, AEDECOD, AESEVN)

summarize: now 101 rows and 5 columns, 3 group variables remaining (ARM, AESOC, AEDECOD)

pivot_wider: reorganized (ARM, AESEVN, cnt) into (ARM A_2, ARM A_1, ARM B_1, ARM B_2, ARM B_3, …) [was 101x5, now 73x11]

...


========================================================================= 
Create final data frame 
========================================================================= 

# A tibble: 74 x 14
   AESOC AEDECOD `ARM A_1` `ARM A_2` `ARM A_3` `ARM B_1` `ARM B_2` `ARM B_3` `ARM C_1` `ARM C_2` `ARM C_3` `ARM D_1`
   <chr> <chr>   <chr>     <chr>     <chr>     <chr>     <chr>     <chr>     <chr>     <chr>     <chr>     <chr>    
 1 All ~ All Ad~ 6 ( 42.9~ 8 ( 57.1~ 0 (  0.0~ 8 ( 57.1~ 4 ( 28.6~ 2 ( 14.3~ 8 ( 57.1~ 6 ( 42.9~ 0 (  0.0~ 7 ( 53.8~
 2 Bloo~ Neutro~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 1 (  7.1~ 0 (  0.0~ 0 (  0.0~
 3 Card~ Palpit~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 1 (  7.7~
 4 Card~ Sinus ~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 1 (  7.7~
 5 Cong~ Dermoi~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 1 (  7.1~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~
 6 Ear ~ Vertigo 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 1 (  7.1~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~
 7 Endo~ Parath~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 1 (  7.1~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~
 8 Gast~ Diarrh~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 1 (  7.1~ 1 (  7.1~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~
 9 Gast~ Food P~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 1 (  7.1~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~
10 Gast~ Tootha~ 0 (  0.0~ 1 (  7.1~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 0 (  0.0~ 2 ( 15.4~
# ... with 64 more rows, and 2 more variables: ARM D_2 <chr>, ARM D_3 <chr>

========================================================================= 
Create and print report 
========================================================================= 

Create table object 

Create report object 

Print report 

# A report specification: 16 pages
- file_path: 'C:\Users\dbosa\AppData\Local\Temp\RtmpKC93lu/output/example4.rtf'
- output_type: RTF
- units: inches
- orientation: landscape
- margins: top 0.5 bottom 0.5 left 1 right 1
- line size/count: 107/47
- page_header: left=Sponsor: Company right=Study: ABC
- title 1: 'Table 5.0'
- title 2: 'Adverse Events by Maximum Severity'
- footnote 1: 'Program: AE_Table.R'
- footnote 2: 'Note: Adverse events were coded using MedDRA Version 9.1'
- page_footer: left=2021-06-27 11:30:02 center=Confidential right=Page [pg] of [tpg]
- content: 
# A table specification:
- data: tibble 'final' 74 rows 14 cols
- show_cols: all
- use_attributes: all
- width: 9
- spanning_header: from='ARM A_1' to='ARM A_3' 'ARM A' level=1 
- spanning_header: from='ARM B_1' to='ARM B_3' 'ARM B' level=1 
- spanning_header: from='ARM C_1' to='ARM C_3' 'ARM C' level=1 
- spanning_header: from='ARM D_1' to='ARM D_3' 'ARM D' level=1 
- stub: AESOC AEDECOD 'System Organ Class
   Preferred Term' width=5 align='left' 
- define: AESOC dedupe='TRUE' 
- define: AEDECOD 
- define: ARM A_1 'Mild' align='center' 
- define: ARM A_2 'Moderate' align='center' 
- define: ARM A_3 'Severe' align='center' 
- define: ARM B_1 'Mild' align='center' page_wrap='TRUE' 
- define: ARM B_2 'Moderate' align='center' 
- define: ARM B_3 'Severe' align='center' 
- define: ARM C_1 'Mild' align='center' page_wrap='TRUE' 
- define: ARM C_2 'Moderate' align='center' 
- define: ARM C_3 'Severe' align='center' 
- define: ARM D_1 'Mild' align='center' page_wrap='TRUE' 
- define: ARM D_2 'Moderate' align='center' 
- define: ARM D_3 'Severe' align='center' 

# A report specification: 16 pages
- file_path: 'C:\Users\dbosa\AppData\Local\Temp\RtmpKC93lu/output/example4.rtf'
- output_type: RTF
- units: inches
- orientation: landscape
- margins: top 0.5 bottom 0.5 left 1 right 1
- line size/count: 107/47
- page_header: left=Sponsor: Company right=Study: ABC
- title 1: 'Table 5.0'
- title 2: 'Adverse Events by Maximum Severity'
- footnote 1: 'Program: AE_Table.R'
- footnote 2: 'Note: Adverse events were coded using MedDRA Version 9.1'
- page_footer: left=2021-06-27 11:30:02 center=Confidential right=Page [pg] of [tpg]
- content: 
# A table specification:
- data: tibble 'final' 74 rows 14 cols
- show_cols: all
- use_attributes: all
- width: 9
- spanning_header: from='ARM A_1' to='ARM A_3' 'ARM A' level=1 
- spanning_header: from='ARM B_1' to='ARM B_3' 'ARM B' level=1 
- spanning_header: from='ARM C_1' to='ARM C_3' 'ARM C' level=1 
- spanning_header: from='ARM D_1' to='ARM D_3' 'ARM D' level=1 
- stub: AESOC AEDECOD 'System Organ Class
   Preferred Term' width=5 align='left' 
- define: AESOC dedupe='TRUE' 
- define: AEDECOD 
- define: ARM A_1 'Mild' align='center' 
- define: ARM A_2 'Moderate' align='center' 
- define: ARM A_3 'Severe' align='center' 
- define: ARM B_1 'Mild' align='center' page_wrap='TRUE' 
- define: ARM B_2 'Moderate' align='center' 
- define: ARM B_3 'Severe' align='center' 
- define: ARM C_1 'Mild' align='center' page_wrap='TRUE' 
- define: ARM C_2 'Moderate' align='center' 
- define: ARM C_3 'Severe' align='center' 
- define: ARM D_1 'Mild' align='center' page_wrap='TRUE' 
- define: ARM D_2 'Moderate' align='center' 
- define: ARM D_3 'Severe' align='center' 

lib_sync: synchronized data in library 'sdtm' 

lib_unload: library 'sdtm' unloaded 

========================================================================= 
Log End Time: 2021-06-27 11:30:04 
Log Elapsed Time: 0 00:00:03 
========================================================================= 

Next: Example 5: Vital Signs Table