Suppose you need to create the table below, and need an ARD representation of the results to get started. Here, we will review an examples for creating a basic demographics table.
To get started, load the {cards} package.
Demographics
Characteristic |
Placebo N = 86 |
Xanomeline Low Dose N = 84 |
Xanomeline High Dose N = 84 |
---|---|---|---|
Age | |||
Median (IQR) | 76 (69, 82) | 78 (71, 82) | 76 (71, 80) |
Mean (SD) | 75 (9) | 76 (8) | 74 (8) |
Range | 52 - 89 | 51 - 88 | 56 - 88 |
Age Group, n (%) | |||
<65 | 14 (16%) | 8 (10%) | 11 (13%) |
65-80 | 42 (49%) | 47 (56%) | 55 (65%) |
>80 | 30 (35%) | 29 (35%) | 18 (21%) |
Female, n (%) | 53 (62%) | 50 (60%) | 40 (48%) |
The table above has three types of data summaries: a
continuous variable summary for AGE
, a
categorical variable summary for AGEGR1
,
and a dichotomous variable summary for
SEX
.
Continuous Summaries
To get a continuous variable summary, we will use the
ard_continuous()
function from the {cards}
package.
df_continuous_ard <-
ard_continuous(
ADSL,
by = ARM,
variables = AGE,
statistic = ~ continuous_summary_fns(c("median", "p25", "p75", "mean", "sd", "min", "max"))
)
df_continuous_ard |> head(5)
#> {cards} data frame: 5 x 10
#> group1 group1_level variable stat_name stat_label stat
#> 1 ARM Placebo AGE median Median 76
#> 2 ARM Placebo AGE p25 Q1 69
#> 3 ARM Placebo AGE p75 Q3 82
#> 4 ARM Placebo AGE mean Mean 75.209
#> 5 ARM Placebo AGE sd SD 8.59
#> ℹ 4 more variables: context, fmt_fn, warning, error
Categorical Summaries
To get the categorical variable summary, we will use the
ard_categorical()
function.
df_categorical_ard <-
ard_categorical(
ADSL,
by = ARM,
variables = AGEGR1
)
df_categorical_ard |> head(5)
#> {cards} data frame: 5 x 11
#> group1 group1_level variable variable_level stat_name stat_label stat
#> 1 ARM Placebo AGEGR1 <65 n n 14
#> 2 ARM Placebo AGEGR1 <65 N N 86
#> 3 ARM Placebo AGEGR1 <65 p % 0.163
#> 4 ARM Xanomeli… AGEGR1 <65 n n 11
#> 5 ARM Xanomeli… AGEGR1 <65 N N 84
#> ℹ 4 more variables: context, fmt_fn, warning, error
Dichotomous Summaries
To get the dichotomous variable summary, we will use
ard_dichotomous()
. In this case, we want to show the Female
("F"
) level of the SEX
variable and specify
this with the values
argument.
df_dichotomous_ard <-
ard_dichotomous(
ADSL,
by = ARM,
variables = SEX,
value = list(SEX = "F")
)
df_dichotomous_ard |> head(5)
#> {cards} data frame: 5 x 11
#> group1 group1_level variable variable_level stat_name stat_label stat
#> 1 ARM Placebo SEX F n n 53
#> 2 ARM Placebo SEX F N N 86
#> 3 ARM Placebo SEX F p % 0.616
#> 4 ARM Xanomeli… SEX F n n 40
#> 5 ARM Xanomeli… SEX F N N 84
#> ℹ 4 more variables: context, fmt_fn, warning, error
Combine Results
As a last step, you can combine all of these objects into a single
object using bind_ard()
, which is similar to
dplyr::bind_rows()
and includes additional structural
checks for our results.
bind_ard(
df_continuous_ard,
df_categorical_ard,
df_dichotomous_ard
)
#> {cards} data frame: 57 x 11
#> group1 group1_level variable variable_level stat_name stat_label stat
#> 1 ARM Placebo AGE median Median 76
#> 2 ARM Placebo AGE p25 Q1 69
#> 3 ARM Placebo AGE p75 Q3 82
#> 4 ARM Placebo AGE mean Mean 75.209
#> 5 ARM Placebo AGE sd SD 8.59
#> 6 ARM Placebo AGE min Min 52
#> 7 ARM Placebo AGE max Max 89
#> 8 ARM Xanomeli… AGE median Median 76
#> 9 ARM Xanomeli… AGE p25 Q1 70.5
#> 10 ARM Xanomeli… AGE p75 Q3 80
#> ℹ 47 more rows
#> ℹ Use `print(n = ...)` to see more rows
#> ℹ 4 more variables: context, fmt_fn, warning, error
Shortcut
The ard_stack()
function provides a shortcut to perform
the calculations above in a single step.
In the example below, the data
and by
arguments are passed to each subsequent ard_*()
function
call. Moreover, we will also be returned the univariate tabulation of
the by
variable, which would be used to add counts to the
header row of the table.
ard_stack(
data = ADSL,
.by = ARM,
ard_continuous(
variables = AGE,
statistic = ~ continuous_summary_fns(c("median", "p25", "p75", "mean", "sd", "min", "max"))
),
ard_categorical(variables = AGEGR1),
ard_dichotomous(variables = SEX, value = list(SEX = "F"))
)
#> {cards} data frame: 66 x 11
#> group1 group1_level variable variable_level stat_name stat_label stat
#> 1 ARM Placebo AGE median Median 76
#> 2 ARM Placebo AGE p25 Q1 69
#> 3 ARM Placebo AGE p75 Q3 82
#> 4 ARM Placebo AGE mean Mean 75.209
#> 5 ARM Placebo AGE sd SD 8.59
#> 6 ARM Placebo AGE min Min 52
#> 7 ARM Placebo AGE max Max 89
#> 8 ARM Xanomeli… AGE median Median 76
#> 9 ARM Xanomeli… AGE p25 Q1 70.5
#> 10 ARM Xanomeli… AGE p75 Q3 80
#> ℹ 56 more rows
#> ℹ Use `print(n = ...)` to see more rows
#> ℹ 4 more variables: context, fmt_fn, warning, error
Adverse Events
Next, we will review several examples for creating basic adverse events (AE) tables.
Participant-level summaries
A common type of AE table contains participant-level summaries. Here, we are reporting the number and percentage of subjects with at least one AE by system organ class and preferred term.
Placebo | Xanomeline Low Dose | Xanomeline High Dose | |
---|---|---|---|
ANY BODY SYSTEM | 65 (76%) | 77 (92%) | 76 (90%) |
CARDIAC DISORDERS | 12 (14%) | 13 (15%) | 15 (18%) |
GASTROINTESTINAL DISORDERS | 17 (20%) | 14 (17%) | 20 (24%) |
GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS | 21 (24%) | 47 (56%) | 40 (48%) |
APPLICATION SITE PRURITUS | 6 ( 7%) | 22 (26%) | 22 (26%) |
APPLICATION SITE ERYTHEMA | 3 ( 3%) | 12 (14%) | 15 (18%) |
APPLICATION SITE IRRITATION | 3 ( 3%) | 9 (11%) | 9 (11%) |
INFECTIONS AND INFESTATIONS | 16 (19%) | 9 (11%) | 13 (15%) |
NERVOUS SYSTEM DISORDERS | 8 ( 9%) | 20 (24%) | 25 (30%) |
DIZZINESS | 2 ( 2%) | 8 (10%) | 11 (13%) |
RESPIRATORY, THORACIC AND MEDIASTINAL DISORDERS | 8 ( 9%) | 9 (11%) | 10 (12%) |
SKIN AND SUBCUTANEOUS TISSUE DISORDERS | 20 (23%) | 39 (46%) | 40 (48%) |
PRURITUS | 8 ( 9%) | 21 (25%) | 26 (31%) |
ERYTHEMA | 8 ( 9%) | 14 (17%) | 14 (17%) |
RASH | 5 ( 6%) | 13 (15%) | 9 (11%) |
For the computations below, we will not only make use of the
ADAE
dataset, but we will also rely on ADSL
for the full study population. To match between them, we need to do a
small data manipulation on the naming of the treatment variable.
# rename trt variable
adsl <- ADSL |>
dplyr::rename("TRTA" = "TRT01A")
# subset to Treatment emergent AES
adae <- ADAE |>
dplyr::filter(TRTEMFL == "Y")
To get these participant level summaries, we will take a multi-step approach by calculating number and percent of subjects with: (1) at least one AE, (2) at least one AE by system organ class, and (3) at least one AE by system organ class and preferred term.
Because ADAE
potentially contains multiple records per
subject for a given AE, we must collapse the data so that it is
subject-level. For the first step, we must reduce the data down to 1
record per subject with any AE (note that your data may contain derived
flags to assist with this step). If it doesn’t exist, we can create a
flag variable to compute over:
We then pass this dataset to ard_categorical()
to
compute the number and percentage of subjects by treatment arm. To
ensure our denominator is the full study population, we pass our
adsl
data to the denominator
argument:
# any ae
ard_subj_any <- ard_categorical(
data = adae_subj,
by = TRTA,
variables = any_ae,
statistic = ~ c("n", "p"),
denominator = adsl
)
For the second step, we create a new dataset of 1 record per subject with any AE by system organ class:
We then pass this dataset to ard_categorical()
to
compute the number and percentage of subjects within each system organ
class by treatment arm.
ard_subj_soc <- ard_categorical(
data = adae_soc,
by = TRTA,
variables = AEBODSYS,
statistic = ~ c("n", "p"),
denominator = adsl
)
Finally, we create another dataset of 1 record per subject with any AE by system organ class and preferred term.
This time, we will use ard_hierarchical()
to compute the
number and percentage of subjects within each preferred term observed
within each system organ class by treatment arm. The use of
ard_hierarchical()
ensures we keep the nested structure of
preferred terms within system organ classes.
ard_subj_pt <- ard_hierarchical(
data = adae_pt,
by = TRTA,
variables = c(AEBODSYS, AETERM),
statistic = ~ c("n", "p"),
denominator = adsl
)
All participant level summaries can be combined using
bind_ard()
.
bind_ard(ard_subj_any, ard_subj_soc, ard_subj_pt)
#> {cards} data frame: 1524 x 13
#> group1 group1_level group2 group2_level variable variable_level stat_name
#> 1 TRTA Placebo <NA> any_ae 1 n
#> 2 TRTA Placebo <NA> any_ae 1 p
#> 3 TRTA Xanomeli… <NA> any_ae 1 n
#> 4 TRTA Xanomeli… <NA> any_ae 1 p
#> 5 TRTA Xanomeli… <NA> any_ae 1 n
#> 6 TRTA Xanomeli… <NA> any_ae 1 p
#> 7 TRTA Placebo <NA> AEBODSYS CARDIAC … n
#> 8 TRTA Placebo <NA> AEBODSYS CARDIAC … p
#> 9 TRTA Xanomeli… <NA> AEBODSYS CARDIAC … n
#> 10 TRTA Xanomeli… <NA> AEBODSYS CARDIAC … p
#> stat_label stat
#> 1 n 65
#> 2 % 0.756
#> 3 n 76
#> 4 % 0.905
#> 5 n 77
#> 6 % 0.917
#> 7 n 12
#> 8 % 0.14
#> 9 n 15
#> 10 % 0.179
#> ℹ 1514 more rows
#> ℹ Use `print(n = ...)` to see more rows
#> ℹ 4 more variables: context, fmt_fn, warning, error
Event-level Summaries
In addition to participant-level summaries, event-level summaries are
often needed. For these types of tables, we report total counts of AEs ,
and therefore we can use the ADAE
data directly. We will
need to count AEs overall, by system organ class, and by preferred term
(within system organ class).
Placebo | Xanomeline Low Dose | Xanomeline High Dose | |
---|---|---|---|
ANY BODY SYSTEM | 281 | 412 | 433 |
CARDIAC DISORDERS | 26 | 30 | 30 |
GASTROINTESTINAL DISORDERS | 26 | 22 | 36 |
GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS | 46 | 118 | 124 |
APPLICATION SITE PRURITUS | 10 | 32 | 35 |
APPLICATION SITE ERYTHEMA | 3 | 20 | 23 |
APPLICATION SITE IRRITATION | 7 | 18 | 16 |
INFECTIONS AND INFESTATIONS | 35 | 16 | 20 |
NERVOUS SYSTEM DISORDERS | 11 | 40 | 41 |
DIZZINESS | 3 | 13 | 15 |
RESPIRATORY, THORACIC AND MEDIASTINAL DISORDERS | 12 | 14 | 22 |
SKIN AND SUBCUTANEOUS TISSUE DISORDERS | 45 | 111 | 104 |
PRURITUS | 11 | 31 | 38 |
ERYTHEMA | 12 | 22 | 22 |
RASH | 9 | 18 | 15 |
For overall AE counts by treatment group, we can use
ard_hierarchical_count()
as such. As we don’t have a
hierarchy, this is equivalent to ard_categorical()
with
statistic = ~ "n"
. We can rename the counts so they are
distinguishable when stacked with the participate level counts.
# overall
ard_event_any <- ard_hierarchical_count(
data = adae,
variables = TRTA
)
For AE counts within preferred term and system organ class by
treatment group, we can use ard_hierarchical()
specifying
our nesting as such:
ard_event_socpt <- ard_hierarchical_count(
data = adae,
by = TRTA,
variables = c(AEBODSYS, AETERM)
)
These event-level summaries can be combined using
bind_ard()
.
bind_ard(ard_event_any, ard_event_socpt)
#> {cards} data frame: 762 x 13
#> group1 group1_level group2 group2_level variable variable_level stat_name
#> 1 <NA> <NA> TRTA Placebo n
#> 2 <NA> <NA> TRTA Xanomeli… n
#> 3 <NA> <NA> TRTA Xanomeli… n
#> 4 TRTA Placebo AEBODSYS GASTROIN… AETERM ABDOMINA… n
#> 5 TRTA Xanomeli… AEBODSYS GASTROIN… AETERM ABDOMINA… n
#> 6 TRTA Xanomeli… AEBODSYS GASTROIN… AETERM ABDOMINA… n
#> 7 TRTA Placebo AEBODSYS GASTROIN… AETERM ABDOMINA… n
#> 8 TRTA Xanomeli… AEBODSYS GASTROIN… AETERM ABDOMINA… n
#> 9 TRTA Xanomeli… AEBODSYS GASTROIN… AETERM ABDOMINA… n
#> 10 TRTA Placebo AEBODSYS SURGICAL… AETERM ACROCHOR… n
#> stat_label stat
#> 1 n 281
#> 2 n 433
#> 3 n 412
#> 4 n 0
#> 5 n 1
#> 6 n 0
#> 7 n 1
#> 8 n 2
#> 9 n 3
#> 10 n 0
#> ℹ 752 more rows
#> ℹ Use `print(n = ...)` to see more rows
#> ℹ 4 more variables: context, fmt_fn, warning, error