Specific AE

Following the ICH E3 guidance, we need to summarize which patients were included in each efficacy analysis in Section 12.2, Adverse Events (AEs).

library(esubdemo)
## Warning in eval(ei, envir): The current R version is not the same with the
## current project in 4.1.0
library(haven) # Read SAS data
library(dplyr) # Manipulate data
library(tidyr) # Manipulate data
library(r2rtf) # Reporting in RTF format
library(tools)

The data used to summarize AE information is in adsl and adae datasets.

adsl <- read_sas("data-adam/adsl.sas7bdat")
adae <- read_sas("data-adam/adae.sas7bdat")

For illustration purpose, we only provide count in the simplified table. The percentage of participants for each AE criteria can be calculated as in the AE summary vignette.

In this way, let’s focus on the analysis script for advanced feature for table layout.

  • group content: AE can be summarized in multiple nested layer. (e.g., by system organ class (SOC, AESOC) and specific AE term (AEDECOD))
  • pagination: there are many AE terms that can not be covered in one page. Column headers and SOC information needs to be repeated in every page.

In the code below, we count the number of subjects in each AE term by SOC and treatment group, and we create a new variable order and set it as 0 for these counted numbers. The variable order will help with the data manipulation later.

ana <- adae %>%
  mutate(
    AESOC = toTitleCase(tolower(AESOC)),
    AEDECOD = toTitleCase(tolower(AEDECOD))
  )

t1 <- ana %>%
  group_by(TRTAN, AESOC) %>%
  summarise(n = fmt_num(n_distinct(USUBJID), digits = 0)) %>%
  mutate(AEDECOD = AESOC, order = 0)
## `summarise()` has grouped output by 'TRTAN'. You can override using the
## `.groups` argument.
t1 %>% head(4)
## # A tibble: 4 × 5
## # Groups:   TRTAN [1]
##   TRTAN AESOC                       n      AEDECOD                     order
##   <dbl> <chr>                       <chr>  <chr>                       <dbl>
## 1     0 Cardiac Disorders           "  13" Cardiac Disorders               0
## 2     0 Ear and Labyrinth Disorders "   1" Ear and Labyrinth Disorders     0
## 3     0 Eye Disorders               "   4" Eye Disorders                   0
## 4     0 Gastrointestinal Disorders  "  17" Gastrointestinal Disorders      0

In the code below, we count number of subjects in each AE term by SOC, AE term, and treatment group. Here we also create a new variable order and set it as 1 for these counted numbers.

t2 <- ana %>%
  group_by(TRTAN, AESOC, AEDECOD) %>%
  summarise(n = fmt_num(n_distinct(USUBJID), digits = 0)) %>%
  mutate(order = 1)
## `summarise()` has grouped output by 'TRTAN', 'AESOC'. You can override using
## the `.groups` argument.
t2 %>% head(4)
## # A tibble: 4 × 5
## # Groups:   TRTAN, AESOC [1]
##   TRTAN AESOC             AEDECOD                              n      order
##   <dbl> <chr>             <chr>                                <chr>  <dbl>
## 1     0 Cardiac Disorders Atrial Fibrillation                  "   1"     1
## 2     0 Cardiac Disorders Atrial Hypertrophy                   "   1"     1
## 3     0 Cardiac Disorders Atrioventricular Block First Degree  "   1"     1
## 4     0 Cardiac Disorders Atrioventricular Block Second Degree "   2"     1

We prepare reporting data for AE information.

t_ae <- bind_rows(t1, t2) %>%
  pivot_wider(
    id_cols = c(AESOC, order, AEDECOD),
    names_from = TRTAN,
    names_prefix = "n_",
    values_from = n,
    values_fill = fmt_num(0, digits = 0)
  ) %>%
  arrange(AESOC, order, AEDECOD) %>%
  select(AESOC, AEDECOD, starts_with("n"))

t_ae %>% head(4)
## # A tibble: 4 × 5
##   AESOC             AEDECOD             n_0    n_54   n_81  
##   <chr>             <chr>               <chr>  <chr>  <chr> 
## 1 Cardiac Disorders Cardiac Disorders   "  13" "  13" "  18"
## 2 Cardiac Disorders Atrial Fibrillation "   1" "   1" "   3"
## 3 Cardiac Disorders Atrial Flutter      "   0" "   1" "   1"
## 4 Cardiac Disorders Atrial Hypertrophy  "   1" "   0" "   0"

We prepare reporting data for analysis population.

t_pop <- adsl %>%
  filter(SAFFL == "Y") %>%
  count_by("TRT01AN", "SAFFL",
    var_label = "Participants in population"
  ) %>%
  mutate(
    AESOC = "pop",
    AEDECOD = var_label
  ) %>%
  select(AESOC, AEDECOD, starts_with("n_"))

t_pop
## # A tibble: 1 × 5
##   AESOC AEDECOD                    n_0    n_54   n_81  
##   <chr> <chr>                      <chr>  <chr>  <chr> 
## 1 pop   Participants in population "  86" "  84" "  84"

The final report data is saved in tbl_ae_spec. We also add a blank row between population and AE information in the reporting table.

tbl_ae_spec <- bind_rows(
  t_pop,
  data.frame(AESOC = "pop"),
  t_ae
) %>%
  mutate(AEDECOD = ifelse(AEDECOD == AESOC,
    AEDECOD, paste0("  ", AEDECOD)
  ))

tbl_ae_spec %>% head(4)
## # A tibble: 4 × 5
##   AESOC             AEDECOD                        n_0    n_54   n_81  
##   <chr>             <chr>                          <chr>  <chr>  <chr> 
## 1 pop               "  Participants in population" "  86" "  84" "  84"
## 2 pop                NA                             NA     NA     NA   
## 3 Cardiac Disorders "Cardiac Disorders"            "  13" "  13" "  18"
## 4 Cardiac Disorders "  Atrial Fibrillation"        "   1" "   1" "   3"

We start to define the format of the output.

To obtain the nested layout, we use page_by argument in rtf_body function. By defining page_by="AESOC", r2rtf recognize the variable as a group indicator.

After setting pageby_row = "first_row", the first row is displayed as group header. If a group of information is breaked into multiple page, the group header row is repeated in each page by default.

We can also customize the text format by providing a matrix that have same dimension of the input dataset (i.e. tbl_ae_spec). In the code below, we illustrate how to display bold text for group headers to highlight the nested structure of the table layout.

n_row <- nrow(tbl_ae_spec)
n_col <- ncol(tbl_ae_spec)
id <- tbl_ae_spec$AESOC == tbl_ae_spec$AEDECOD
id <- ifelse(is.na(id), FALSE, id)

text_format <- ifelse(id, "b", "")

More discussion on page_by, group_by and subline_by features can be found in the [r2rtf package website](https://merck.github.io/r2rtf/articles/example-sublineby-pageby-groupby.html.

tbl_ae_spec %>%
  rtf_title(
    "Analysis of Participants With Specific Adverse Events",
    "(Safety Analysis Population)"
  ) %>%
  rtf_colheader(" | Placebo | Xanomeline Low Dose| Xanomeline High Dose",
    col_rel_width = c(3, rep(1, 3))
  ) %>%
  rtf_colheader(" | n |  n | n ",
    border_top = "",
    border_bottom = "single",
    col_rel_width = c(3, rep(1, 3))
  ) %>%
  rtf_body(
    col_rel_width = c(1, 3, rep(1, 3)),
    text_justification = c("l", "l", rep("c", 3)),
    text_format = matrix(text_format, nrow = n_row, ncol = n_col),
    page_by = "AESOC",
    pageby_row = "first_row"
  ) %>%
  rtf_footnote("Every subject is counted a single time for each applicable row and column.") %>%
  rtf_encode() %>%
  write_rtf("tlf/tlf_spec_ae.rtf")

In conclusion, the procedure to generate a specific AE summary table as shown in the above example is listed as follows:

  • Step 1: Read data into R, i.e., adae and adsl.
  • Step 2: Count the number of subjects by SOC and treatment group (rows with bold text) and store it in a data frame t1.
  • Step 3: Count the number of subjects in each AE term by SOC, AE term, and treatment group (rows without bold text) and store it in a data frame t2.
  • Step 4: Rowly bind t1 and t2 into t_ae.
  • Step 5: Count the number of subjects in each arm as t_pop.
  • Step 6: Rowly bind t_pop with t_ae as tbl_ae_spec.
  • Step 7: Format the output by r2rtf.