ard_hierarchical_stack() is a function similar to
ard_stack() that simplifies the process of creating an ARD
(analysis ready dataset) for nested/hierarchical tables. The expected
function call will look something like:
ard_hierarchal_stack(
.data = adae,
.by = ARM,
.overall = TRUE,
.id = USUBJID,
.overallrow = TRUE,
ard_hierarchical(
variables = AEBODSYS,
denominator = adsl
),
...
)
The function will take a single “common” dataset to pass over the
various ard_*() calls. One quality-of-life improvement this
function allows for is the ability to easily calculate the various
statistics stratified by treatment types and over all treatment
types using the .overall argument. Of course, one could
achieve the same result by using the functions in the cards
package by themselves. The process is outlined below.
.overall argument is used to indicate whether or
not the user wishes to include the “total”/“overall” column’s data. This
argument will ignore the .by argument when performing
calculations.overallrow is used to indicate whether or not top
level calculations should be performed. e.g. total AE per ARM or number
of subjects with at least one AE.id is an argument that will be passed to
ard_hierarchical() and is used to check whether or not
there are any duplicates in the variable providedFor this example, the data will come from the chevron
package.
# Load data
adsl <- chevron::syn_data$adsl
adae <- chevron::syn_data$adae
We will be recreating this AET02 table as an ARD.
# Ensure character variables are converted to factors and empty strings and NAs are explicit missing levels.
adsl <- df_explicit_na(adsl)
adae <- df_explicit_na(adae) %>%
var_relabel(
AEBODSYS = "MedDRA System Organ Class",
AEDECOD = "MedDRA Preferred Term"
) %>%
filter(ANL01FL == "Y")
# Define the split function
split_fun <- drop_split_levels
lyt <- basic_table(show_colcounts = TRUE) %>%
split_cols_by(var = "ACTARM") %>%
add_overall_col(label = "All Patients") %>%
analyze_num_patients(
vars = "USUBJID",
.stats = c("unique", "nonunique"),
.labels = c(
unique = "Total number of patients with at least one adverse event",
nonunique = "Overall total number of events"
)
) %>%
split_rows_by(
"AEBODSYS",
child_labels = "visible",
nested = FALSE,
split_fun = split_fun,
label_pos = "topleft",
split_label = obj_label(adae$AEBODSYS)
) %>%
summarize_num_patients(
var = "USUBJID",
.stats = c("unique", "nonunique"),
.labels = c(
unique = "Total number of patients with at least one adverse event",
nonunique = "Total number of events"
)
) %>%
count_occurrences(
vars = "AEDECOD",
.indent_mods = -1L
) %>%
append_varlabels(adae, "AEDECOD", indent = 1L)
result <- build_table(lyt, df = adae, alt_counts_df = adsl)
#saved result as tt_export.html
includeHTML("tt_export.html")
| MedDRA System Organ Class | A: Drug X | B: Placebo | C: Combination | All Patients |
|---|---|---|---|---|
| MedDRA Preferred Term | (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%) |
| dcd B.2.2.3.1 | 8 (53.3%) | 6 (40.0%) | 7 (46.7%) | 21 (46.7%) |
| cl C.1 | ||||
| Total number of patients with at least one adverse event | 4 (26.7%) | 4 (26.7%) | 5 (33.3%) | 13 (28.9%) |
| Total number of events | 4 | 9 | 10 | 23 |
| dcd C.1.1.1.3 | 4 (26.7%) | 4 (26.7%) | 5 (33.3%) | 13 (28.9%) |
| cl C.2 | ||||
| Total number of patients with at least one adverse event | 6 (40.0%) | 4 (26.7%) | 8 (53.3%) | 18 (40.0%) |
| Total number of events | 6 | 4 | 12 | 22 |
| dcd C.2.1.2.1 | 6 (40.0%) | 4 (26.7%) | 8 (53.3%) | 18 (40.0%) |
| cl D.1 | ||||
| Total number of patients with at least one adverse event | 9 (60.0%) | 5 (33.3%) | 11 (73.3%) | 25 (55.6%) |
| Total number of events | 13 | 9 | 19 | 41 |
| dcd D.1.1.1.1 | 4 (26.7%) | 4 (26.7%) | 7 (46.7%) | 15 (33.3%) |
| dcd D.1.1.4.2 | 6 (40.0%) | 2 (13.3%) | 7 (46.7%) | 15 (33.3%) |
| cl D.2 | ||||
| Total number of patients with at least one adverse event | 2 (13.3%) | 5 (33.3%) | 7 (46.7%) | 14 (31.1%) |
| Total number of events | 3 | 5 | 10 | 18 |
| dcd D.2.1.5.3 | 2 (13.3%) | 5 (33.3%) | 7 (46.7%) | 14 (31.1%) |
We’ll start from the top of the table. To retreive the total number
of patients by the treatment arm and the overall number of patients, all
we need is the adsl dataset. The statistic n
is chosen because that’s all we really need for this top row in this
example. By default the function will give you the number per category
(n), the proportion of observations in the category
(p), and the total number of observations in the dataset
(N).
To calculate the total number of patients in the dataset, I opted to transform the arm to be the same for all patients. You’re welcome to choose another method or function if it’s more intuitive another way.
# By ARM
card_patients_by_arm <- adsl |>
cards::ard_categorical(variables = ARM, statistic = everything() ~ c("n"))
## display
card_patients_by_arm %>%
kable(format = "html") %>%
kable_styling() %>%
kableExtra::scroll_box(width = "100%")
| variable | variable_level | context | stat_name | stat_label | stat | fmt_fn | warning | error |
|---|---|---|---|---|---|---|---|---|
| ARM | 1 | categorical | n | n | 15 | 0 | NULL | NULL |
| ARM | 2 | categorical | n | n | 15 | 0 | NULL | NULL |
| ARM | 3 | categorical | n | n | 15 | 0 | NULL | NULL |
# Overall
card_patient_total <- adsl |>
mutate(ARM = "total") |>
cards::ard_categorical(variables = ARM, statistic = everything() ~ c("n"))
## display
card_patient_total %>%
kable(format = "html") %>%
kable_styling() %>%
kableExtra::scroll_box(width = "100%")
| variable | variable_level | context | stat_name | stat_label | stat | fmt_fn | warning | error |
|---|---|---|---|---|---|---|---|---|
| ARM | total | categorical | n | n | 45 | 0 | NULL | NULL |
Moving on, we’ll be calculating the values for the second row. In order to return the correct values, we’ll have to do a minor transformation on the data so we only have one observation per patient per AE. In the first place, if they are included in this dataset, they already have at least one AE.
Similarly to last time, ARM was changed to only have one
level for the calculation over all treatment levels.
# By ARM
card_any_ae <- adae |>
dplyr::slice(1L, .by = USUBJID) |>
dplyr::mutate(any_ae = TRUE) |>
cards::ard_dichotomous(by = ARM, variable = any_ae, denominator = adsl)
## display
card_any_ae %>%
kable(format = "html") %>%
kable_styling() %>%
kableExtra::scroll_box(width = "100%", height = "200px")
| group1 | group1_level | variable | variable_level | context | stat_name | stat_label | stat | fmt_fn | warning | error |
|---|---|---|---|---|---|---|---|---|---|---|
| ARM | 1 | any_ae | TRUE | dichotomous | n | n | 13 | 0 | NULL | NULL |
| ARM | 1 | any_ae | TRUE | dichotomous | N | N | 15 | 0 | NULL | NULL |
| ARM | 1 | any_ae | TRUE | dichotomous | p | % | 0.8666667 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 2 | any_ae | TRUE | dichotomous | n | n | 14 | 0 | NULL | NULL |
| ARM | 2 | any_ae | TRUE | dichotomous | N | N | 15 | 0 | NULL | NULL |
| ARM | 2 | any_ae | TRUE | dichotomous | p | % | 0.9333333 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 3 | any_ae | TRUE | dichotomous | n | n | 15 | 0 | NULL | NULL |
| ARM | 3 | any_ae | TRUE | dichotomous | N | N | 15 | 0 | NULL | NULL |
| ARM | 3 | any_ae | TRUE | dichotomous | p | % | 1 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
# Overall
adsl_total <- adsl |>
dplyr::mutate(ARM = "total")
card_any_ae_all <- adae |>
dplyr::slice(1L, .by = USUBJID) |>
dplyr:: mutate(any_ae = TRUE, ARM = "total") |>
cards::ard_dichotomous(by = ARM, variable = any_ae, denominator = adsl_total)
## display
card_any_ae_all %>%
kable(format = "html") %>%
kable_styling() %>%
kableExtra::scroll_box(width = "100%", height = "200px")
| group1 | group1_level | variable | variable_level | context | stat_name | stat_label | stat | fmt_fn | warning | error |
|---|---|---|---|---|---|---|---|---|---|---|
| ARM | total | any_ae | TRUE | dichotomous | n | n | 42 | 0 | NULL | NULL |
| ARM | total | any_ae | TRUE | dichotomous | N | N | 45 | 0 | NULL | NULL |
| ARM | total | any_ae | TRUE | dichotomous | p | % | 0.9333333 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
For the total number of events in the data, we’ll be using the
ard_hierarchical_count() function over the ARM
variable.
# By ARM
card_ae_count <-adae |>
cards::ard_hierarchical_count(variables = ARM)
## display
card_ae_count %>%
kable(format = "html") %>%
kable_styling() %>%
kableExtra::scroll_box(width = "100%", height = "200px")
| variable | variable_level | context | stat_name | stat_label | stat | fmt_fn | warning | error |
|---|---|---|---|---|---|---|---|---|
| ARM | 1 | hierarchical_count | n | n | 58 | 0 | NULL | NULL |
| ARM | 2 | hierarchical_count | n | n | 59 | 0 | NULL | NULL |
| ARM | 3 | hierarchical_count | n | n | 99 | 0 | NULL | NULL |
# Overall
card_ae_count_all <- adae |>
mutate(ARM = "total") |>
cards::ard_hierarchical_count(variables = ARM)
## display
card_ae_count_all %>%
kable(format = "html") %>%
kable_styling() %>%
kableExtra::scroll_box(width = "100%")
| variable | variable_level | context | stat_name | stat_label | stat | fmt_fn | warning | error |
|---|---|---|---|---|---|---|---|---|
| ARM | total | hierarchical_count | n | n | 216 | 0 | NULL | NULL |
Now we’re moving into the actual hierarchical portion of the table; the part split into summaries by SOC. For the total number of patients with at least one AE, the process is similar to before: modify the data so we only have one observation per subject per treatment arm and body system.
For the calculation done over all treatment arms, we just need to
remove the by argument in
ard_hierarchical().
# By ARM
card_by_soc_1ae <- adae |>
# keep one AE per SOC
slice(1L, .by = c(USUBJID, ARM, AEBODSYS)) |>
cards::ard_hierarchical(
by = ARM,
variables = AEBODSYS,
denominator = adsl
) |>
filter(stat_name %in% c("n", "p"))
## display
head(card_by_soc_1ae, 10) %>%
kable(format = "html") %>%
kable_styling() %>%
kableExtra::scroll_box(width = "100%", height = "300px")
| group1 | group1_level | variable | variable_level | context | stat_name | stat_label | stat | fmt_fn | warning | error |
|---|---|---|---|---|---|---|---|---|---|---|
| ARM | 1 | AEBODSYS | 1 | hierarchical | n | n | 7 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 1 | hierarchical | p | % | 0.4666667 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 2 | AEBODSYS | 1 | hierarchical | n | n | 6 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 1 | hierarchical | p | % | 0.4 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 3 | AEBODSYS | 1 | hierarchical | n | n | 10 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 1 | hierarchical | p | % | 0.6666667 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 1 | AEBODSYS | 2 | hierarchical | n | n | 5 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 2 | hierarchical | p | % | 0.3333333 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 2 | AEBODSYS | 2 | hierarchical | n | n | 6 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 2 | hierarchical | p | % | 0.4 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
# Overall
card_by_soc_1ae_all <- adae |>
# keep one AE per SOC
slice(1L, .by = c(USUBJID, ARM, AEBODSYS)) |>
cards::ard_hierarchical(
variables = AEBODSYS,
denominator = adsl
) |>
filter(stat_name %in% c("n", "p"))
## display
head(card_by_soc_1ae_all, 10) %>%
kable(format = "html") %>%
kable_styling() %>%
kableExtra::scroll_box(width = "100%", height = "300px")
| variable | variable_level | context | stat_name | stat_label | stat | fmt_fn | warning | error |
|---|---|---|---|---|---|---|---|---|
| AEBODSYS | 1 | hierarchical | n | n | 23 | 0 | NULL | NULL |
| AEBODSYS | 1 | hierarchical | p | % | 0.5111111 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| AEBODSYS | 2 | hierarchical | n | n | 19 | 0 | NULL | NULL |
| AEBODSYS | 2 | hierarchical | p | % | 0.4222222 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| AEBODSYS | 3 | hierarchical | n | n | 29 | 0 | NULL | NULL |
| AEBODSYS | 3 | hierarchical | p | % | 0.6444444 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| AEBODSYS | 4 | hierarchical | n | n | 13 | 0 | NULL | NULL |
| AEBODSYS | 4 | hierarchical | p | % | 0.2888889 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| AEBODSYS | 5 | hierarchical | n | n | 18 | 0 | NULL | NULL |
| AEBODSYS | 5 | hierarchical | p | % | 0.4 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
To find the counts of the total number of events by SOC, we’ll be
using the ard_hierarchical_count() function but this time
by AEBODSYS.
# By ARM
card_count_soc_all_event <- adae |>
cards::ard_hierarchical_count(
by = ARM,
variables = AEBODSYS
)
## display
card_count_soc_all_event %>%
kable(format = "html") %>%
kable_styling() %>%
kableExtra::scroll_box(width = "100%", height = "300px")
| group1 | group1_level | variable | variable_level | context | stat_name | stat_label | stat | fmt_fn | warning | error |
|---|---|---|---|---|---|---|---|---|---|---|
| ARM | 1 | AEBODSYS | 1 | hierarchical_count | n | n | 8 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 1 | hierarchical_count | n | n | 11 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 1 | hierarchical_count | n | n | 16 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 2 | hierarchical_count | n | n | 6 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 2 | hierarchical_count | n | n | 6 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 2 | hierarchical_count | n | n | 12 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 3 | hierarchical_count | n | n | 18 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 3 | hierarchical_count | n | n | 15 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 3 | hierarchical_count | n | n | 20 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 4 | hierarchical_count | n | n | 4 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 4 | hierarchical_count | n | n | 9 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 4 | hierarchical_count | n | n | 10 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 5 | hierarchical_count | n | n | 6 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 5 | hierarchical_count | n | n | 4 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 5 | hierarchical_count | n | n | 12 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 6 | hierarchical_count | n | n | 13 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 6 | hierarchical_count | n | n | 9 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 6 | hierarchical_count | n | n | 19 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 7 | hierarchical_count | n | n | 3 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 7 | hierarchical_count | n | n | 5 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 7 | hierarchical_count | n | n | 10 | 0 | NULL | NULL |
# Overall
card_count_soc_all_event_total <- adae |>
cards::ard_hierarchical_count(
variables = AEBODSYS
)
## display
card_count_soc_all_event_total %>%
kable(format = "html") %>%
kable_styling() %>%
kableExtra::scroll_box(width = "100%", height = "300px")
| variable | variable_level | context | stat_name | stat_label | stat | fmt_fn | warning | error |
|---|---|---|---|---|---|---|---|---|
| AEBODSYS | 1 | hierarchical_count | n | n | 35 | 0 | NULL | NULL |
| AEBODSYS | 2 | hierarchical_count | n | n | 24 | 0 | NULL | NULL |
| AEBODSYS | 3 | hierarchical_count | n | n | 53 | 0 | NULL | NULL |
| AEBODSYS | 4 | hierarchical_count | n | n | 23 | 0 | NULL | NULL |
| AEBODSYS | 5 | hierarchical_count | n | n | 22 | 0 | NULL | NULL |
| AEBODSYS | 6 | hierarchical_count | n | n | 41 | 0 | NULL | NULL |
| AEBODSYS | 7 | hierarchical_count | n | n | 18 | 0 | NULL | NULL |
Finally, breaking the categories down further by AE types, we perform
a similar operation to when we calculated the total number of patients
with at least one adverse event by body system, just adding an extra
nesting factor (AEDECOD) to the mix.
# By ARM
card_ae_rate <-adae |>
# keep one AE per subject
slice(1L, .by = c(USUBJID, ARM, AEBODSYS, AEDECOD)) |>
cards::ard_hierarchical(
by = ARM,
variables = c(AEBODSYS, AEDECOD),
denominator = adsl
) |>
filter(stat_name %in% c("n", "p"))
## display
card_ae_rate %>%
kable(format = "html") %>%
kable_styling() %>%
kableExtra::scroll_box(width = "100%", height = "300px")
| group1 | group1_level | group2 | group2_level | variable | variable_level | context | stat_name | stat_label | stat | fmt_fn | warning | error |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ARM | 1 | AEBODSYS | 1 | AEDECOD | 1 | hierarchical | n | n | 3 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 1 | AEDECOD | 1 | hierarchical | p | % | 0.2 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 2 | AEBODSYS | 1 | AEDECOD | 1 | hierarchical | n | n | 1 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 1 | AEDECOD | 1 | hierarchical | p | % | 0.06666667 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 3 | AEBODSYS | 1 | AEDECOD | 1 | hierarchical | n | n | 6 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 1 | AEDECOD | 1 | hierarchical | p | % | 0.4 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 1 | AEBODSYS | 1 | AEDECOD | 2 | hierarchical | n | n | 5 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 1 | AEDECOD | 2 | hierarchical | p | % | 0.3333333 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 2 | AEBODSYS | 1 | AEDECOD | 2 | hierarchical | n | n | 6 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 1 | AEDECOD | 2 | hierarchical | p | % | 0.4 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 3 | AEBODSYS | 1 | AEDECOD | 2 | hierarchical | n | n | 6 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 1 | AEDECOD | 2 | hierarchical | p | % | 0.4 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 1 | AEBODSYS | 2 | AEDECOD | 3 | hierarchical | n | n | 5 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 2 | AEDECOD | 3 | hierarchical | p | % | 0.3333333 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 2 | AEBODSYS | 2 | AEDECOD | 3 | hierarchical | n | n | 6 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 2 | AEDECOD | 3 | hierarchical | p | % | 0.4 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 3 | AEBODSYS | 2 | AEDECOD | 3 | hierarchical | n | n | 8 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 2 | AEDECOD | 3 | hierarchical | p | % | 0.5333333 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 1 | AEBODSYS | 3 | AEDECOD | 4 | hierarchical | n | n | 5 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 3 | AEDECOD | 4 | hierarchical | p | % | 0.3333333 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 2 | AEBODSYS | 3 | AEDECOD | 4 | hierarchical | n | n | 6 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 3 | AEDECOD | 4 | hierarchical | p | % | 0.4 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 3 | AEBODSYS | 3 | AEDECOD | 4 | hierarchical | n | n | 5 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 3 | AEDECOD | 4 | hierarchical | p | % | 0.3333333 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 1 | AEBODSYS | 3 | AEDECOD | 5 | hierarchical | n | n | 8 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 3 | AEDECOD | 5 | hierarchical | p | % | 0.5333333 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 2 | AEBODSYS | 3 | AEDECOD | 5 | hierarchical | n | n | 6 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 3 | AEDECOD | 5 | hierarchical | p | % | 0.4 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 3 | AEBODSYS | 3 | AEDECOD | 5 | hierarchical | n | n | 7 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 3 | AEDECOD | 5 | hierarchical | p | % | 0.4666667 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 1 | AEBODSYS | 4 | AEDECOD | 6 | hierarchical | n | n | 4 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 4 | AEDECOD | 6 | hierarchical | p | % | 0.2666667 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 2 | AEBODSYS | 4 | AEDECOD | 6 | hierarchical | n | n | 4 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 4 | AEDECOD | 6 | hierarchical | p | % | 0.2666667 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 3 | AEBODSYS | 4 | AEDECOD | 6 | hierarchical | n | n | 5 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 4 | AEDECOD | 6 | hierarchical | p | % | 0.3333333 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 1 | AEBODSYS | 5 | AEDECOD | 7 | hierarchical | n | n | 6 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 5 | AEDECOD | 7 | hierarchical | p | % | 0.4 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 2 | AEBODSYS | 5 | AEDECOD | 7 | hierarchical | n | n | 4 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 5 | AEDECOD | 7 | hierarchical | p | % | 0.2666667 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 3 | AEBODSYS | 5 | AEDECOD | 7 | hierarchical | n | n | 8 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 5 | AEDECOD | 7 | hierarchical | p | % | 0.5333333 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 1 | AEBODSYS | 6 | AEDECOD | 8 | hierarchical | n | n | 4 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 6 | AEDECOD | 8 | hierarchical | p | % | 0.2666667 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 2 | AEBODSYS | 6 | AEDECOD | 8 | hierarchical | n | n | 4 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 6 | AEDECOD | 8 | hierarchical | p | % | 0.2666667 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 3 | AEBODSYS | 6 | AEDECOD | 8 | hierarchical | n | n | 7 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 6 | AEDECOD | 8 | hierarchical | p | % | 0.4666667 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 1 | AEBODSYS | 6 | AEDECOD | 9 | hierarchical | n | n | 6 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 6 | AEDECOD | 9 | hierarchical | p | % | 0.4 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 2 | AEBODSYS | 6 | AEDECOD | 9 | hierarchical | n | n | 2 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 6 | AEDECOD | 9 | hierarchical | p | % | 0.1333333 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 3 | AEBODSYS | 6 | AEDECOD | 9 | hierarchical | n | n | 7 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 6 | AEDECOD | 9 | hierarchical | p | % | 0.4666667 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 1 | AEBODSYS | 7 | AEDECOD | 10 | hierarchical | n | n | 2 | 0 | NULL | NULL |
| ARM | 1 | AEBODSYS | 7 | AEDECOD | 10 | hierarchical | p | % | 0.1333333 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 2 | AEBODSYS | 7 | AEDECOD | 10 | hierarchical | n | n | 5 | 0 | NULL | NULL |
| ARM | 2 | AEBODSYS | 7 | AEDECOD | 10 | hierarchical | p | % | 0.3333333 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| ARM | 3 | AEBODSYS | 7 | AEDECOD | 10 | hierarchical | n | n | 7 | 0 | NULL | NULL |
| ARM | 3 | AEBODSYS | 7 | AEDECOD | 10 | hierarchical | p | % | 0.4666667 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
# Overall
card_ae_rate_total <- adae |>
# keep one AE per subject
slice(1L, .by = c(USUBJID, ARM, AEBODSYS, AEDECOD)) |>
cards::ard_hierarchical(
variables = c(AEBODSYS, AEDECOD),
denominator = adsl
) |>
filter(stat_name %in% c("n", "p"))
## display
card_ae_rate_total %>%
kable(format = "html") %>%
kable_styling() %>%
kableExtra::scroll_box(width = "100%", height = "300px")
| group1 | group1_level | variable | variable_level | context | stat_name | stat_label | stat | fmt_fn | warning | error |
|---|---|---|---|---|---|---|---|---|---|---|
| AEBODSYS | 1 | AEDECOD | 1 | hierarchical | n | n | 10 | 0 | NULL | NULL |
| AEBODSYS | 1 | AEDECOD | 1 | hierarchical | p | % | 0.2222222 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| AEBODSYS | 1 | AEDECOD | 2 | hierarchical | n | n | 17 | 0 | NULL | NULL |
| AEBODSYS | 1 | AEDECOD | 2 | hierarchical | p | % | 0.3777778 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| AEBODSYS | 2 | AEDECOD | 3 | hierarchical | n | n | 19 | 0 | NULL | NULL |
| AEBODSYS | 2 | AEDECOD | 3 | hierarchical | p | % | 0.4222222 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| AEBODSYS | 3 | AEDECOD | 4 | hierarchical | n | n | 16 | 0 | NULL | NULL |
| AEBODSYS | 3 | AEDECOD | 4 | hierarchical | p | % | 0.3555556 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| AEBODSYS | 3 | AEDECOD | 5 | hierarchical | n | n | 21 | 0 | NULL | NULL |
| AEBODSYS | 3 | AEDECOD | 5 | hierarchical | p | % | 0.4666667 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| AEBODSYS | 4 | AEDECOD | 6 | hierarchical | n | n | 13 | 0 | NULL | NULL |
| AEBODSYS | 4 | AEDECOD | 6 | hierarchical | p | % | 0.2888889 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| AEBODSYS | 5 | AEDECOD | 7 | hierarchical | n | n | 18 | 0 | NULL | NULL |
| AEBODSYS | 5 | AEDECOD | 7 | hierarchical | p | % | 0.4 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| AEBODSYS | 6 | AEDECOD | 8 | hierarchical | n | n | 15 | 0 | NULL | NULL |
| AEBODSYS | 6 | AEDECOD | 8 | hierarchical | p | % | 0.3333333 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| AEBODSYS | 6 | AEDECOD | 9 | hierarchical | n | n | 15 | 0 | NULL | NULL |
| AEBODSYS | 6 | AEDECOD | 9 | hierarchical | p | % | 0.3333333 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
| AEBODSYS | 7 | AEDECOD | 10 | hierarchical | n | n | 14 | 0 | NULL | NULL |
| AEBODSYS | 7 | AEDECOD | 10 | hierarchical | p | % | 0.3111111 | function (x) , {, res <- ifelse(is.na(x), NA_character_, str_trim(format(round5(x * , scale, digits = digits), nsmall = digits))), if (!is.null(width)) {, res <- ifelse(nchar(res) >= width | is.na(res), res, , paste0(strrep(” “, width - nchar(res)), res)), }, res, } | NULL | NULL |
Finally, once you’re done, you’re welcome to stick them all together
(or leave them as-is) and you have your complete ARD. As you can see,
for most of these operations, while calculating the “overall”/“total”
areas, we’d have to calculate everything a second time just with very
minor modifications. While the hassle isn’t the end of the world,
ard_hierarchal_stack() aims to make the process more
streamlined and much simpler. All-in-all, the cards package
offers an easy-to-use and intuitive way to chunk out tlg’s, which may
come in handy for applications such as in QC pipelines.