A wrapper function for gtsummary::tbl_hierarchical() to calculate exposure-adjusted incidence rates of adverse events (or other clinical events) across a hierarchy.

The function calculates the incidence rate per specified person-time dynamically. For subjects experiencing an event, Person-Years is calculated from start_date to event_date. For subjects without an event, it is calculated from start_date to end_date.

tbl_hierarchical_incidence_rate(
  data,
  denominator,
  variables,
  by = NULL,
  id = "USUBJID",
  start_date = "TRTSDT",
  end_date = "TRTEDT",
  event_date = "AESTDTC",
  event_type = c("first_event", "all"),
  n_person_time = 100,
  unit_label = "years",
  conf.level = 0.95,
  conf.type = "normal",
  digits = 2,
  overall_row = TRUE,
  spanning_label = "{level}  \n(N = {n})",
  label = NULL
)

# S3 method for class 'tbl_hierarchical_incidence_rate'
add_overall(
  x,
  last = FALSE,
  col_label = "All Participants  \n(N = {style_roche_number(N)})",
  ...
)

Arguments

data

(data.frame)
a data frame.

denominator

(data.frame, integer)
used to define the denominator and enhance the output. The argument is required for tbl_hierarchical() and optional for tbl_hierarchical_count(). The denominator argument must be specified when id is used to calculate event rates.

variables

(tidy-select)
A character vector or tidy-selector of hierarchical columns in data (e.g., system organ class and preferred term).

by

(tidy-select)
A single column name in data to stratify the summary table by (e.g., treatment arm).

id

(tidy-select)
argument used to subset data to identify rows in data to calculate event rates in tbl_hierarchical().

start_date

(tidy-select)
A column name in denominator specifying the treatment start date.

end_date

(tidy-select)
A column name in denominator specifying the treatment end date or follow-up cutoff.

event_date

(tidy-select)
A column name in data specifying the onset date of the event.

event_type

(string)
Type of the events to be counted. Can be "first_event" or "all". Default is "first_event".

n_person_time

(numeric(1))
A numeric scalar multiplier to scale the incidence rate. Defaults to 100.

unit_label

(string)
Label for the unit of estimated person-time output. Defaults to "years". Known abbreviations ("years", "months", "weeks", "days") are parsed into standard acronyms (e.g., "PY"). Custom strings will be formatted to Title Case and prefixed with "Person-" (e.g., "decades" becomes "Person-Decades").

conf.level

(numeric(1))
Confidence level for the calculated interval. Default is 0.95. Passed directly to cardx::ard_incidence_rate().

conf.type

(string)
Confidence interval type. Default is "normal". Passed directly to cardx::ard_incidence_rate().

digits

(numeric(1))
An integer specifying the number of decimal places to round the incidence estimates, confidence intervals, and person-years to. Defaults to 2.

overall_row

(logical(1))
Whether to include an overall summary row aggregating across all hierarchical levels. Default is TRUE. The overall row label can be customized via label = list("..ard_hierarchical_overall.." = "Custom Label").

spanning_label

(string)
A glue-style template for the per-arm spanning headers when by is supplied. Available substitutions are {level} (the arm level) and {n} (the arm participant count). Default is "{level} \\n(N = {n})", matching the column-header convention used elsewhere in the package.

label

(formula-list-selector)
used to override default labels in hierarchical table, e.g. list(AESOC = "System Organ Class"). The default for each variable is the column label attribute, attr(., 'label'). If no label has been set, the column name is used.

x

(tbl_hierarchical_incidence_rate)
A table created with tbl_hierarchical_incidence_rate().

last

(logical(1))
When add_overall() is used, whether to place the overall columns after (TRUE) or before (FALSE) the stratified columns. Default is FALSE.

col_label

(string)
A glue-style template for the overall spanning header added by add_overall(). Resolved against the column header context (e.g. {N}, {n}). Default is "All Participants \\n(N = {style_roche_number(N)})".

...

These dots are for future extensions and must be empty.

Value

a gtsummary table of class "tbl_hierarchical_incidence_rate".

Examples

# Dummy denominator dataset with treatment start and end dates
adsl <- data.frame(
  USUBJID = paste0("PT", sprintf("%02d", 1:5)),
  ARM = c("Treatment", "Treatment", "Placebo", "Placebo", "Placebo"),
  TRTSDT = as.Date(rep("2023-01-01", 5)),
  TRTEDT = as.Date(c(
    "2023-12-31", "2023-06-30", "2023-12-31", "2023-08-15", "2023-10-31"
  ))
)

# Dummy AE dataset with onset dates (subset to first occurrences)
adae <- data.frame(
  USUBJID = c("PT02", "PT05", "PT05"),
  AESOC = c("Cardiac", "Nervous", "Cardiac"),
  ARM = c("Treatment", "Placebo", "Placebo"),
  AEDECOD = c("Tachycardia", "Headache", "Palpitations"),
  AESTDTC = c("2023-04-15", "2023-09-10", "2023-10-01")
)

# Build the hierarchical incidence rate table
tbl_hierarchical_incidence_rate(
  data = adae,
  denominator = adsl,
  variables = c(AESOC, AEDECOD),
  by = ARM,
  start_date = TRTSDT,
  end_date = TRTEDT,
  event_date = AESTDTC,
  n_person_time = 100,
  unit_label = "years",
  label = list(
    AESOC = "MedDRA System Organ Class",
    AEDECOD = "MedDRA Preferred Term",
    "..ard_hierarchical_overall.." = "All Adverse Events"
  )
)
MedDRA System Organ Class
    MedDRA Preferred Term
Placebo
(N = 3)
Treatment
(N = 2)
No. of Participants with AE (%) No. of AEs PY AE Rate per 100 PY 95% CI No. of Participants with AE (%) No. of AEs PY AE Rate per 100 PY 95% CI
All Adverse Events 1 (33.3%) 2 2.31 86.45 (-33.36, 206.26) 1 (50.0%) 1 1.29 77.71 (-74.60, 230.02)
Cardiac 1 (33.3%) 1 2.37 42.18 (-40.49, 124.84) 1 (50.0%) 1 1.29 77.71 (-74.60, 230.02)
    Palpitations 1 (33.3%) 1 2.37 42.18 (-40.49, 124.84) 0 0 1.49 0.00 (0.00, 0.00)
    Tachycardia 0 0 2.45 0.00 (0.00, 0.00) 1 (50.0%) 1 1.29 77.71 (-74.60, 230.02)
Nervous 1 (33.3%) 1 2.31 43.22 (-41.49, 127.94) 0 0 1.49 0.00 (0.00, 0.00)
    Headache 1 (33.3%) 1 2.31 43.22 (-41.49, 127.94) 0 0 1.49 0.00 (0.00, 0.00)
# Custom overall row label tbl_hierarchical_incidence_rate( data = adae, denominator = adsl, variables = c(AESOC, AEDECOD), by = ARM, start_date = TRTSDT, end_date = TRTEDT, event_date = AESTDTC, label = list("..ard_hierarchical_overall.." = "Any Adverse Event") )
AESOC
    AEDECOD
Placebo
(N = 3)
Treatment
(N = 2)
No. of Participants with AE (%) No. of AEs PY AE Rate per 100 PY 95% CI No. of Participants with AE (%) No. of AEs PY AE Rate per 100 PY 95% CI
Any Adverse Event 1 (33.3%) 2 2.31 86.45 (-33.36, 206.26) 1 (50.0%) 1 1.29 77.71 (-74.60, 230.02)
Cardiac 1 (33.3%) 1 2.37 42.18 (-40.49, 124.84) 1 (50.0%) 1 1.29 77.71 (-74.60, 230.02)
    Palpitations 1 (33.3%) 1 2.37 42.18 (-40.49, 124.84) 0 0 1.49 0.00 (0.00, 0.00)
    Tachycardia 0 0 2.45 0.00 (0.00, 0.00) 1 (50.0%) 1 1.29 77.71 (-74.60, 230.02)
Nervous 1 (33.3%) 1 2.31 43.22 (-41.49, 127.94) 0 0 1.49 0.00 (0.00, 0.00)
    Headache 1 (33.3%) 1 2.31 43.22 (-41.49, 127.94) 0 0 1.49 0.00 (0.00, 0.00)
# Without the overall row tbl_hierarchical_incidence_rate( data = adae, denominator = adsl, variables = c(AESOC, AEDECOD), by = ARM, start_date = TRTSDT, end_date = TRTEDT, event_date = AESTDTC, overall_row = FALSE )
AESOC
    AEDECOD
Placebo
(N = 3)
Treatment
(N = 2)
No. of Participants with AE (%) No. of AEs PY AE Rate per 100 PY 95% CI No. of Participants with AE (%) No. of AEs PY AE Rate per 100 PY 95% CI
Cardiac 1 (33.3%) 1 2.37 42.18 (-40.49, 124.84) 1 (50.0%) 1 1.29 77.71 (-74.60, 230.02)
    Palpitations 1 (33.3%) 1 2.37 42.18 (-40.49, 124.84) 0 0 1.49 0.00 (0.00, 0.00)
    Tachycardia 0 0 2.45 0.00 (0.00, 0.00) 1 (50.0%) 1 1.29 77.71 (-74.60, 230.02)
Nervous 1 (33.3%) 1 2.31 43.22 (-41.49, 127.94) 0 0 1.49 0.00 (0.00, 0.00)
    Headache 1 (33.3%) 1 2.31 43.22 (-41.49, 127.94) 0 0 1.49 0.00 (0.00, 0.00)
# Add an unstratified "Overall" column pooling all treatment arms tbl_hierarchical_incidence_rate( data = adae, denominator = adsl, variables = c(AESOC, AEDECOD), by = ARM, start_date = TRTSDT, end_date = TRTEDT, event_date = AESTDTC ) |> add_overall()
AESOC
    AEDECOD
All Participants
(N = 5)
Placebo
(N = 3)
Treatment
(N = 2)
No. of Participants with AE (%) No. of AEs PY AE Rate per 100 PY 95% CI No. of Participants with AE (%) No. of AEs PY AE Rate per 100 PY 95% CI No. of Participants with AE (%) No. of AEs PY AE Rate per 100 PY 95% CI
All Adverse Events 2 (40.0%) 3 3.60 83.32 (-10.96, 177.61) 1 (33.3%) 2 2.31 86.45 (-33.36, 206.26) 1 (50.0%) 1 1.29 77.71 (-74.60, 230.02)
Cardiac 2 (40.0%) 2 3.66 54.68 (-21.10, 130.45) 1 (33.3%) 1 2.37 42.18 (-40.49, 124.84) 1 (50.0%) 1 1.29 77.71 (-74.60, 230.02)
    Palpitations 1 (20.0%) 1 3.87 25.87 (-24.83, 76.56) 1 (33.3%) 1 2.37 42.18 (-40.49, 124.84) 0 0 1.49 0.00 (0.00, 0.00)
    Tachycardia 1 (20.0%) 1 3.74 26.74 (-25.67, 79.14) 0 0 2.45 0.00 (0.00, 0.00) 1 (50.0%) 1 1.29 77.71 (-74.60, 230.02)
Nervous 1 (20.0%) 1 3.81 26.26 (-25.21, 77.72) 1 (33.3%) 1 2.31 43.22 (-41.49, 127.94) 0 0 1.49 0.00 (0.00, 0.00)
    Headache 1 (20.0%) 1 3.81 26.26 (-25.21, 77.72) 1 (33.3%) 1 2.31 43.22 (-41.49, 127.94) 0 0 1.49 0.00 (0.00, 0.00)
# Customize the per-arm spanning headers with a glue template tbl_hierarchical_incidence_rate( data = adae, denominator = adsl, variables = c(AESOC, AEDECOD), by = ARM, start_date = TRTSDT, end_date = TRTEDT, event_date = AESTDTC, spanning_label = "{level} (N = {n})" )
AESOC
    AEDECOD
Placebo (N = 3)
Treatment (N = 2)
No. of Participants with AE (%) No. of AEs PY AE Rate per 100 PY 95% CI No. of Participants with AE (%) No. of AEs PY AE Rate per 100 PY 95% CI
All Adverse Events 1 (33.3%) 2 2.31 86.45 (-33.36, 206.26) 1 (50.0%) 1 1.29 77.71 (-74.60, 230.02)
Cardiac 1 (33.3%) 1 2.37 42.18 (-40.49, 124.84) 1 (50.0%) 1 1.29 77.71 (-74.60, 230.02)
    Palpitations 1 (33.3%) 1 2.37 42.18 (-40.49, 124.84) 0 0 1.49 0.00 (0.00, 0.00)
    Tachycardia 0 0 2.45 0.00 (0.00, 0.00) 1 (50.0%) 1 1.29 77.71 (-74.60, 230.02)
Nervous 1 (33.3%) 1 2.31 43.22 (-41.49, 127.94) 0 0 1.49 0.00 (0.00, 0.00)
    Headache 1 (33.3%) 1 2.31 43.22 (-41.49, 127.94) 0 0 1.49 0.00 (0.00, 0.00)