AET02 - Adverse Events

Adverse Events (AET02) QC Workflow

# load libraries
library(cards)

1. Generate a table using {chevron}

Show the code
tlg_aet02 <- chevron::aet02_main(chevron::syn_data, arm_var = "ARM", lbl_overall = "Overall ARM")
head(tlg_aet02, n = 15)
                                                             A: Drug X    B: Placebo   C: Combination   Overall ARM
                                                               (N=15)       (N=15)         (N=15)         (N=45)   
———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
Total number of patients with at least one adverse event     13 (86.7%)   14 (93.3%)     15 (100%)      42 (93.3%) 
Overall total number of events                                   58           59             99             216    
cl A.1                                                                                                             
  Total number of patients with at least one adverse event   7 (46.7%)    6 (40.0%)      10 (66.7%)     23 (51.1%) 
  Total number of events                                         8            11             16             35     
  dcd A.1.1.1.1                                              3 (20.0%)     1 (6.7%)      6 (40.0%)      10 (22.2%) 
  dcd A.1.1.1.2                                              5 (33.3%)    6 (40.0%)      6 (40.0%)      17 (37.8%) 
cl B.1                                                                                                             
  Total number of patients with at least one adverse event   5 (33.3%)    6 (40.0%)      8 (53.3%)      19 (42.2%) 
  Total number of events                                         6            6              12             24     
  dcd B.1.1.1.1                                              5 (33.3%)    6 (40.0%)      8 (53.3%)      19 (42.2%) 
cl B.2                                                                                                             
  Total number of patients with at least one adverse event   11 (73.3%)   8 (53.3%)      10 (66.7%)     29 (64.4%) 
  Total number of events                                         18           15             20             53     
  dcd B.2.1.2.1                                              5 (33.3%)    6 (40.0%)      5 (33.3%)      16 (35.6%) 

2. Flatten the table into a data.frame

A {rtables} based output can be flattened into a data.frame using the as_results_df() function from the {rtables} package. The make_ard argument set to TRUE, will format the data similar to the output generated by the {cards} package. Setting the add_tbl_str_decimals to FALSE will not return a column with the statistic as a formatted string. We also include a step to remove the “label” attribute for the statistics. The diffdf() function is sensitive to attribute mismatch, so we will remove them (set to NULL) to match the ARD results exactly.

rtables_result <- rtables::as_result_df(tlg_aet02, make_ard = TRUE, add_tbl_str_decimals = FALSE)
attr(rtables_result$stat, "label") <- NULL

rtables_result[1:6, c("group2", "group2_level", "variable", "variable_level", "stat")]
group2 group2_level variable variable_level stat
ARM A: Drug X USUBJID unique 13.0000000
ARM A: Drug X USUBJID unique 0.8666667
ARM A: Drug X USUBJID nonunique 58.0000000
ARM A: Drug X AEBODSYS unique 7.0000000
ARM A: Drug X AEBODSYS unique 0.4666667
ARM A: Drug X AEBODSYS nonunique 8.0000000

3. Create a comparable ARD

In the code below, we perform different data pre-processing for the different ARDs we want to create. Then, we generate separate ARDs using the {cards} package for the different sections of the AET02 table and compare them as subsets. Note: If your table does not have an “overall” column, you can use the ARD creation steps at the bottom of the page using primarily ard_categorical(). ard_hierarchical() is useful here to calculate the statistics for the overall column and allows for better filtering using the “context” column.

# data pre-processing. filter observations with ANL01FL = "Y"
adae <- chevron::syn_data$adae |>
  dplyr::filter(ANL01FL == "Y")
adsl <- chevron::syn_data$adsl |>
  dplyr::filter(ANL01FL == "Y")

# ----- ARDS -----
ard_result <- ard_stack_hierarchical(
  data = adae,
  variables = c(ANL01FL, AEBODSYS, AEDECOD),
  by = c(ARM),
  denominator = adsl,
  overall = TRUE,
  id = USUBJID,
  statistic = ~ c("n", "p")
) |>
  apply_fmt_fn() |>
  unlist_ard_columns() |>
  dplyr::filter(context == "hierarchical")

ard_count_result <-
  ard_stack_hierarchical_count(
    data = adae,
    variables = c(ANL01FL, AEBODSYS),
    by = ARM,
    overall = TRUE,
    denominator = ADSL
  ) |>
  apply_fmt_fn() |>
  unlist_ard_columns() |>
  dplyr::filter(context == "hierarchical_count")

4. Statistics Comparison

We’ll modify the rtables result to closely match the ARD result:

  1. Rename “group2_level” to “ARM” to match the ARD.
  2. Remove any columns that aren’t in the ARD result (stat_name is being removed as it is a blank column)
rtables_result <- rtables_result |>
  dplyr::rename(
    group1_level = group2_level,
    group1 = group2,
    group2 = group1,
    group2_level = group1_level
  )

head(rtables_result, n = 10)
group2 group2_level group1 group1_level variable variable_level variable_label stat_name stat
NA NA ARM A: Drug X USUBJID unique Total number of patients with at least one adverse event NA 13.0000000
NA NA ARM A: Drug X USUBJID unique Total number of patients with at least one adverse event NA 0.8666667
NA NA ARM A: Drug X USUBJID nonunique Overall total number of events NA 58.0000000
AEBODSYS cl A.1 ARM A: Drug X AEBODSYS unique Total number of patients with at least one adverse event NA 7.0000000
AEBODSYS cl A.1 ARM A: Drug X AEBODSYS unique Total number of patients with at least one adverse event NA 0.4666667
AEBODSYS cl A.1 ARM A: Drug X AEBODSYS nonunique Total number of events NA 8.0000000
AEBODSYS cl A.1 ARM A: Drug X AEDECOD dcd A.1.1.1.1 dcd A.1.1.1.1 NA 3.0000000
AEBODSYS cl A.1 ARM A: Drug X AEDECOD dcd A.1.1.1.1 dcd A.1.1.1.1 NA 0.2000000
AEBODSYS cl A.1 ARM A: Drug X AEDECOD dcd A.1.1.1.2 dcd A.1.1.1.2 NA 5.0000000
AEBODSYS cl A.1 ARM A: Drug X AEDECOD dcd A.1.1.1.2 dcd A.1.1.1.2 NA 0.3333333

We can compare the data in subsets to minimize reformatting steps

# subgroup analysis
rtables_result_sub1 <- rtables_result |>
  dplyr::filter(variable == "AEDECOD") |>
  dplyr::select(c("group1", "group1_level", "variable", "variable_level", "stat"))

ard_sub1 <- ard_result |>
  dplyr::filter(variable == "AEDECOD") |>
  dplyr::mutate(
    group1 = dplyr::case_when(
      group1 == "ANL01FL" ~ "ARM",
      TRUE ~ group1
    ),
    group1_level = dplyr::case_when(
      group1_level == "Y" ~ "Overall ARM",
      TRUE ~ group1_level
    )
  ) |>
  dplyr::select(c("group1", "group1_level", "variable", "variable_level", "stat"))


diffdf::diffdf(rtables_result_sub1, ard_sub1, keys = c("group1", "group1_level", "variable", "variable_level", "stat"))
No issues were found!

Compare the second subset

rtables_result_sub2 <- rtables_result |>
  dplyr::filter(variable %in% c("USUBJID", "AEBODSYS") & variable_level != "nonunique") |>
  dplyr::select(c("group1", "group1_level", "group2", "group2_level", "stat")) |>
  dplyr::rename(
    variable = group2,
    variable_level = group2_level
  ) |>
  dplyr::mutate(
    variable = dplyr::coalesce(variable, "ANL01FL"),
    variable_level = dplyr::coalesce(variable_level, "Y")
  )

ard_sub2 <- ard_result |>
  dplyr::filter(variable %in% c("ANL01FL", "AEBODSYS")) |>
  dplyr::mutate(
    group1 = dplyr::recode(group1, "ANL01FL" = "ARM"),
    group1_level = dplyr::recode(group1_level, "Y" = "Overall ARM"),
    group1 = dplyr::coalesce(group1, "ARM"),
    group1_level = dplyr::coalesce(group1_level, "Overall ARM")
  ) |>
  dplyr::select(c("group1", "group1_level", "variable", "variable_level", "stat"))

diffdf::diffdf(rtables_result_sub2, ard_sub2, keys = c("group1", "group1_level", "variable", "variable_level", "stat"))
No issues were found!

Total Number of Events (ARD counts data.frame)

# reformat rtables
rtables_count <- rtables_result |>
  dplyr::filter(variable_label %in% c("Total number of events", "Overall total number of events")) |>
  dplyr::mutate(
    group2 = dplyr::case_when(
      variable_label == "Overall total number of events" ~ "ANL01FL",
      TRUE ~ group2
    ),
    group2_level = dplyr::case_when(
      variable_label == "Overall total number of events" ~ "Y",
      TRUE ~ group2_level
    )
  ) |>
  dplyr::select("group1", "group1_level", "group2", "group2_level", "stat")

# reformat ARD result
ard_count_result <- ard_count_result |>
  dplyr::mutate(
    group1 = dplyr::coalesce(group1, "ARM"),
    group1_level = dplyr::coalesce(group1_level, "Overall ARM"),
    group1 = dplyr::case_when(
      group1 == "ANL01FL" ~ "ARM",
      TRUE ~ group1
    ),
    group1_level = dplyr::case_when(
      group1_level == "Y" ~ "Overall ARM",
      TRUE ~ group1_level
    )
  ) |>
  dplyr::select("group1", "group1_level", "variable", "variable_level", "stat") |>
  dplyr::rename(
    group2 = variable,
    group2_level = variable_level
  )

diffdf::diffdf(rtables_count, ard_count_result, keys = c("group1", "group1_level", "group2", "group2_level", "stat"))
No issues were found!