Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tbl_heirarchical() #1872

Closed
ddsjoberg opened this issue Jul 25, 2024 · 5 comments · Fixed by #2013
Closed

Add tbl_heirarchical() #1872

ddsjoberg opened this issue Jul 25, 2024 · 5 comments · Fixed by #2013
Assignees
Milestone

Comments

@ddsjoberg
Copy link
Owner

ddsjoberg commented Jul 25, 2024

We need an ARD-based function for items like AE summaries. This still needs some thought.

First proposed API....

tbl_hierarchical(
  data = ADAE, 
  heirarchy = c(SOC, AE),
  by, # can we use a single argument for stratifying tables by treatment and AE grade, for example?
  statistic = "{n} ({p})",
  denominator = ADSL, 
  id = USUBJID,
  include = everything(), # this would be the variables from `heirarchy` that we would include summary stats for (some of the nested drug class tables don't need stats on the class level)
  overall_row = FALSE
)

Would it work if the the first by variable listed was calculated as is, then any subsequent by variables are processed with tbl_strata()? e.g. if by=ARM, then we would have two columns of statistics (treatment A, treatment B). If by=c(ARM, AEGRADE), then the first header row would be the 5 grade levels, then the header row above that would be for treatments A and B... 🤔

@ddsjoberg ddsjoberg added this to the v2.1.0 milestone Jul 25, 2024
@ddsjoberg
Copy link
Owner Author

Aiming to use this function to build tables from the FDA specs

image
image

@edelarua
Copy link
Collaborator

edelarua commented Oct 3, 2024

FDA Table 9:

# Load Libraries & Data

library(cardinal)
library(gtsummary)

adsl <- random.cdisc.data::cadsl
adae <- random.cdisc.data::cadae

# cardinal

make_table_09(adae = adae, alt_counts_df = adsl, pref_var = "AEDECOD")
#> System Organ Class           A: Drug X    B: Placebo    C: Combination
#>   Dictionary-Derived Term     (N=134)       (N=134)        (N=132)    
#> ——————————————————————————————————————————————————————————————————————
#> Any SAE                     104 (77.6%)   101 (75.4%)     99 (75.0%)  
#> cl A                        48 (35.8%)    48 (35.8%)      50 (37.9%)  
#>   dcd A.1.1.1.2             48 (35.8%)    48 (35.8%)      50 (37.9%)  
#> cl B                        79 (59.0%)    78 (58.2%)      76 (57.6%)  
#>   dcd B.1.1.1.1             47 (35.1%)    49 (36.6%)      43 (32.6%)  
#>   dcd B.2.2.3.1             48 (35.8%)    54 (40.3%)      51 (38.6%)  
#> cl D                        50 (37.3%)    42 (31.3%)      51 (38.6%)  
#>   dcd D.1.1.1.1             50 (37.3%)    42 (31.3%)      51 (38.6%)

# gtsummary

adae <- adae %>%
  dplyr::filter(SAFFL == "Y", AESER == "Y")

tbl_hierarchical(
  adae,
  by = ARM,
  variables = c(AESOC, AEDECOD),
  id = USUBJID,
  denominator = adsl,
  overall_row = TRUE,
  label = list(AESOC = "System Organ Class", overall = "Any SAE")
)

image

Created on 2024-10-03 with reprex v2.1.1

@edelarua
Copy link
Collaborator

edelarua commented Oct 3, 2024

FDA Table 10:

# Load Libraries & Data

library(dplyr)
library(cardinal)
library(gtsummary)

adsl <- random.cdisc.data::cadsl
adae <- random.cdisc.data::cadae

# Pre-Processing - Ensure required variables fmqsc_var and fmqnam_var exist in adae
set.seed(1)
adae <- adae %>%
  rename(FMQ01SC = SMQ01SC) %>%
  mutate(
    AESER = sample(c("Y", "N"), size = nrow(adae), replace = TRUE),
    FMQ01NAM = sample(c("FMQ1", "FMQ2", "FMQ3"), size = nrow(adae), replace = TRUE)
  )
adae$FMQ01SC[is.na(adae$FMQ01SC)] <- "NARROW"

# cardinal

make_table_10(adae = adae, alt_counts_df = adsl)
#> Body System or Organ Class   A: Drug X    B: Placebo   C: Combination
#>   FMQ (Narrow)                (N=134)      (N=134)        (N=132)    
#> —————————————————————————————————————————————————————————————————————
#> cl A.1                                                               
#>   FMQ1                       17 (12.7%)   14 (10.4%)     29 (22.0%)  
#>   FMQ2                       23 (17.2%)   20 (14.9%)     20 (15.2%)  
#>   FMQ3                       20 (14.9%)   19 (14.2%)     23 (17.4%)  
#> cl B.1                                                               
#>   FMQ1                        8 (6.0%)    11 (8.2%)       7 (5.3%)   
#>   FMQ2                        5 (3.7%)    12 (9.0%)      16 (12.1%)  
#>   FMQ3                       10 (7.5%)     7 (5.2%)       5 (3.8%)   
#> cl B.2                                                               
#>   FMQ1                       13 (9.7%)    15 (11.2%)      9 (6.8%)   
#>   FMQ2                       12 (9.0%)     9 (6.7%)      10 (7.6%)   
#>   FMQ3                        6 (4.5%)     7 (5.2%)       9 (6.8%)   
#> cl C.2                                                               
#>   FMQ1                        9 (6.7%)     9 (6.7%)      12 (9.1%)   
#>   FMQ2                        6 (4.5%)     8 (6.0%)       8 (6.1%)   
#>   FMQ3                        6 (4.5%)     9 (6.7%)      10 (7.6%)   
#> cl D.1                                                               
#>   FMQ1                       23 (17.2%)   17 (12.7%)     27 (20.5%)  
#>   FMQ2                       22 (16.4%)   20 (14.9%)     25 (18.9%)  
#>   FMQ3                       15 (11.2%)   19 (14.2%)     21 (15.9%)  
#> cl D.2                                                               
#>   FMQ1                        8 (6.0%)    11 (8.2%)       9 (6.8%)   
#>   FMQ2                       14 (10.4%)   15 (11.2%)     14 (10.6%)  
#>   FMQ3                       11 (8.2%)     9 (6.7%)      11 (8.3%)

# gtsummary

adae <- adae %>%
  filter(SAFFL == "Y", AESER == "Y", FMQ01SC == "NARROW")

tbl_hierarchical(
  adae,
  by = ARM,
  variables = c(AEBODSYS, FMQ01NAM),
  include = FMQ01NAM,
  id = USUBJID,
  denominator = adsl,
  label = list(FMQ01NAM = "FMQ (Narrow)")
)

image

Created on 2024-10-03 with reprex v2.1.1

@edelarua
Copy link
Collaborator

edelarua commented Oct 3, 2024

AET02 (simplified):

# Load Libraries & Data

library(dplyr)
library(tern)
library(gtsummary)

adsl <- random.cdisc.data::cadsl
adae <- random.cdisc.data::cadae

adsl <- df_explicit_na(adsl)
adae <- df_explicit_na(adae) %>%
  var_relabel(
    AEBODSYS = "MedDRA System Organ Class",
    AEDECOD = "MedDRA Preferred Term"
  ) %>%
  filter(ANL01FL == "Y")

# rtables/tern

split_fun <- drop_split_levels

lyt <- basic_table(show_colcounts = TRUE) %>%
  split_cols_by(var = "ACTARM") %>%
  analyze_num_patients(
    vars = "USUBJID",
    .stats = "unique",
    .labels = c(
      unique = "Total number of patients with at least one adverse event"
    )
  ) %>%
  split_rows_by(
    "AEBODSYS",
    child_labels = "visible",
    nested = FALSE,
    split_fun = split_fun,
    label_pos = "topleft",
    split_label = obj_label(adae$AEBODSYS)
  ) %>%
  count_occurrences(
    vars = "AEDECOD"
  ) %>%
  append_varlabels(adae, "AEDECOD", indent = 1L)

result <- build_table(lyt, df = adae, alt_counts_df = adsl)
result
#> MedDRA System Organ Class                                   A: Drug X    B: Placebo   C: Combination
#>   MedDRA Preferred Term                                      (N=134)      (N=134)        (N=132)    
#> ————————————————————————————————————————————————————————————————————————————————————————————————————
#> Total number of patients with at least one adverse event   100 (74.6%)   98 (73.1%)    103 (78.0%)  
#> cl A.1                                                                                              
#>   dcd A.1.1.1.1                                            45 (33.6%)    31 (23.1%)     52 (39.4%)  
#>   dcd A.1.1.1.2                                            41 (30.6%)    39 (29.1%)     42 (31.8%)  
#> cl B.1                                                                                              
#>   dcd B.1.1.1.1                                            38 (28.4%)    37 (27.6%)     36 (27.3%)  
#> cl B.2                                                                                              
#>   dcd B.2.1.2.1                                            39 (29.1%)    34 (25.4%)     46 (34.8%)  
#>   dcd B.2.2.3.1                                            38 (28.4%)    40 (29.9%)     45 (34.1%)  
#> cl C.1                                                                                              
#>   dcd C.1.1.1.3                                            36 (26.9%)    34 (25.4%)     36 (27.3%)  
#> cl C.2                                                                                              
#>   dcd C.2.1.2.1                                            28 (20.9%)    36 (26.9%)     48 (36.4%)  
#> cl D.1                                                                                              
#>   dcd D.1.1.1.1                                            42 (31.3%)    32 (23.9%)     46 (34.8%)  
#>   dcd D.1.1.4.2                                            38 (28.4%)    34 (25.4%)     40 (30.3%)  
#> cl D.2                                                                                              
#>   dcd D.2.1.5.3                                            37 (27.6%)    46 (34.3%)     50 (37.9%)

# gtsummary

tbl_hierarchical(
  adae,
  by = ACTARM,
  variables = c(AEBODSYS, AEDECOD),
  id = USUBJID,
  denominator = adsl,
  include = AEDECOD,
  overall_row = TRUE,
  label = list(overall = "Total number of patients with at least one adverse event")
)

image

Created on 2024-10-03 with reprex v2.1.1

@edelarua
Copy link
Collaborator

edelarua commented Oct 3, 2024

AET04 (simplified):

# Load Libraries & Data

library(dplyr)
library(tern)
library(gtsummary)

adsl <- random.cdisc.data::cadsl
adae <- random.cdisc.data::cadae

adsl <- df_explicit_na(adsl) %>% filter(TRT01A != "<Missing>")
adae <- df_explicit_na(adae) %>%
  var_relabel(
    AEBODSYS = "MedDRA System Organ Class",
    AEDECOD = "MedDRA Preferred Term"
  ) %>%
  filter(
    ANL01FL == "Y",
    AETOXGR != "<Missing>"
  )

# rtables/tern

lyt <- basic_table(show_colcounts = TRUE) %>%
  split_cols_by("ACTARM") %>%
  split_rows_by(
    "AEBODSYS",
    child_labels = "visible",
    nested = FALSE,
    split_fun = drop_split_levels,
    split_label = var_labels(adae)[["AEBODSYS"]],
    label_pos = "topleft"
  ) %>%
  count_occurrences_by_grade(
    var = "AETOXGR"
  ) %>%
  append_topleft("  Highest Grade")

result <- lyt %>%
  build_table(adae, alt_counts_df = adsl) %>%
  prune_table()

result
#> MedDRA System Organ Class   A: Drug X    B: Placebo   C: Combination
#>   Highest Grade              (N=134)      (N=134)        (N=132)    
#> ————————————————————————————————————————————————————————————————————
#> cl A.1                                                              
#>   1                         27 (20.1%)   19 (14.2%)     34 (25.8%)  
#>   2                         41 (30.6%)   39 (29.1%)     42 (31.8%)  
#> cl B.1                                                              
#>   5                         38 (28.4%)   37 (27.6%)     36 (27.3%)  
#> cl B.2                                                              
#>   1                         23 (17.2%)   22 (16.4%)     28 (21.2%)  
#>   3                         39 (29.1%)   34 (25.4%)     46 (34.8%)  
#> cl C.1                                                              
#>   4                         36 (26.9%)   34 (25.4%)     36 (27.3%)  
#> cl C.2                                                              
#>   2                         28 (20.9%)   36 (26.9%)     48 (36.4%)  
#> cl D.1                                                              
#>   3                         22 (16.4%)   22 (16.4%)     22 (16.7%)  
#>   5                         42 (31.3%)   32 (23.9%)     46 (34.8%)  
#> cl D.2                                                              
#>   1                         37 (27.6%)   46 (34.3%)     50 (37.9%)

# gtsummary

tbl_hierarchical(
  adae |> mutate(AETOXGR = factor(AETOXGR, ordered = TRUE)),
  by = ACTARM,
  variables = c(AEBODSYS, AETOXGR),
  id = USUBJID,
  denominator = adsl,
  include = AETOXGR,
  label = AETOXGR ~ "Highest Grade"
) |>
  # prune empty rows
  modify_table_body(
    ~ .x |>
      filter(
        rowSums(.x[c("stat_1", "stat_2", "stat_3")] != "0 (0.0)") > 0 |
          is.na(.x$stat_1)
      )
  )
#> ℹ Denominator set by "ACTARM" column in `denominator` data frame.

image

Created on 2024-10-03 with reprex v2.1.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants