Skip to contents

This vignette provides catalog of endpoint examples, derived endpoint specifications and associated ADaM functions and statistical functions.

The endpoints are grouped into three categories:

  • Binary outcomes
  • Demographics
  • Continuous outcomes

Endpoint types

For the endpoints above, we note that the endpoint specification must contain statistics both by strata and treatment arm (N, n, %), by strata across treatment arms (RR, OR, P-value) and across strata and treatment arms (P-value interaction) which must be calculated on two strata (Age, Sex). The statistical functions will come from the library of statistical functions in {chefStats}.

We assume the endpoint concerns adverse events and must be applied to the full safety population set (SAFFL="Y").

We also need the functions to produce the clinical data to pass to the data_prepare. Since this examlple analyses adverse events, we will need some version of the ADAE dataset. As these exact nature of the clinical dataset will differ from project-to-project, there is no library of data prep functions we can source from. Instead we define our own example functions here:

# Merge ADSL and ADAE from {pharmaverseadam}
mk_adae <- function(study_metadata) {

  # Read ADSL
  adsl <- data.table::as.data.table(pharmaverseadam::adsl)

  # Filter treatment arms
  adsl <- adsl[adsl$TRT01A %in% c("Placebo", "Xanomeline High Dose")]

  # New derived ADSL age group variable
  adsl[, AGEGR2 := data.table::fcase(AGE < 65, "AGE < 65",
    AGE >= 65, "AGE >= 65",
    default = NA
  )]

  # Read ADAE
  adae <- data.table::as.data.table(pharmaverseadam::adae)

  # Merge ADSL and ADAE
  adam_out <-
    merge(adsl, adae[, c(setdiff(names(adae), names(adsl)), "USUBJID"),
      with =
        F
    ], by = "USUBJID", all = TRUE)

  return(adam_out)
}

Let us consider different variations of this endpoint specification in the cases below. In the examples we demonstrate how to apply the statistics either on the full population, by grouping on system organ class (SOCs) and severity, and filtered on severity.

Note that the set of SOCs is trimmed according to an endpoint criterion function (ep_crit_min_subev_by_trt), so that only SOCs with at least 5 subjects with events in at least one treatment arm are included. The criteria function will come from the library of criteria functions found in {chefCriteria}.

Case 1: Adverse events (base form)
ep_spec <- chef::mk_endpoint_str(
  data_prepare = mk_adae,
  treatment_var = "TRT01A",
  treatment_refval = "Xanomeline High Dose",
  pop_var = "SAFFL",
  pop_value = "Y",
  stratify_by = list(c("AGEGR2", "SEX")),
  stat_by_strata_by_trt = list(
    chefStats::n_subj,
    chefStats::n_subj_event,
    cheStats::p_subj_event
  ),
  stat_by_strata_across_trt = list(
    chefStats::RR,
    chefStats::OR,
    chefStats::p_val
  ),
  stat_across_strata_across_trt = list(chefStats::pval_interaction),
  endpoint_label = "Adverse events - <treatment_var>"
)
Case 2: Adverse events grouped by system organ class
ep_spec <- chef::mk_endpoint_str(
  data_prepare = mk_adae,
  treatment_var = "TRT01A",
  treatment_refval = "Xanomeline High Dose",
  pop_var = "SAFFL",
  pop_value = "Y",
  group_by = list(list(AESOC = c())),
  stratify_by = list(c("AGEGR2", "SEX")),
  stat_by_strata_by_trt = list(
    chefStats::n_subj,
    chefStats::n_subj_event,
    cheStats::p_subj_event
  ),
  stat_by_strata_across_trt = list(
    chefStats::RR,
    chefStats::OR,
    chefStats::p_val
  ),
  stat_across_strata_across_trt = list(chefStats::pval_interaction),
  crit_endpoint = list(
    c(
      ep_crit_min_subev_by_trt,
      min_n_subev = 5,
      requirement_type = "any"
    )
  ),
  endpoint_label = "Adverse events - <AESOC> - <treatment_var>"
)
Case 3: Mild adverse events grouped by system organ class
ep_spec <- chef::mk_endpoint_str(
  data_prepare = mk_adae,
  treatment_var = "TRT01A",
  treatment_refval = "Xanomeline High Dose",
  pop_var = "SAFFL",
  pop_value = "Y",
  endpoint_filter = 'ASEV == "MILD"',
  group_by = list(list(AESOC = c())),
  stratify_by = list(c("AGEGR2", "SEX")),
  stat_by_strata_by_trt = list(
    chefStats::n_subj,
    chefStats::n_subj_event,
    cheStats::p_subj_event
  ),
  stat_by_strata_across_trt = list(
    chefStats::RR,
    chefStats::OR,
    chefStats::p_val
  ),
  stat_across_strata_across_trt = list(chefStats::pval_interaction),
  crit_endpoint = list(
    c(
      ep_crit_min_subev_by_trt,
      min_n_subev = 5,
      requirement_type = "any"
    )
  ),
  endpoint_label = "Mild adverse events - <treatment_var>"
)
Case 4: Adverse events grouped by severity and system organ class
ep_spec <- chef::mk_endpoint_str(
  data_prepare = mk_adae,
  treatment_var = "TRT01A",
  treatment_refval = "Xanomeline High Dose",
  pop_var = "SAFFL",
  pop_value = "Y",
  group_by = list(list(ASEV = c(), AESOC = c())),
  stratify_by = list(c("AGEGR2", "SEX")),
  stat_by_strata_by_trt = list(
    chefStats::n_subj,
    chefStats::n_subj_event,
    cheStats::p_subj_event
  ),
  stat_by_strata_across_trt = list(
    chefStats::RR,
    chefStats::OR,
    chefStats::p_val
  ),
  stat_across_strata_across_trt = list(chefStats::pval_interaction),
  crit_endpoint = list(
    c(
      ep_crit_min_subev_by_trt,
      min_n_subev = 5,
      requirement_type = "any"
    )
  ),
  endpoint_label = "<ASEV> adverse events - <AESOC> - <treatment_var>"
)

To get results that match the table above, we can group the endpoints by AESOC, and then within each AESOC grouping, stratify by AEDECOD. By framing it as such, we can see that we need a statistic (n number of subjects with an event and E number of events) for every combination endpoint group (AESOC) and stratification level (AEDECOD). The statistical functions will come from chefStats.

We assume the endpoint concerns adverse events and must be applied to the full safety population set (SAFFL="Y").

We also need the functions to produce the clinical data to pass to the data_prepare. Since this examlple analyses adverse events, we will need some version of the ADAE dataset. As these exact nature of the clinical dataset will differ from project-to-project, there is no library of data prep functions we can source from. Instead we define our own example functions here:

# Merge ADSL and ADAE from {pharmaverseadam}
mk_adae <- function(study_metadata) {

  # Read ADSL
  adsl <- data.table::as.data.table(pharmaverseadam::adsl)

  # Filter treatment arms
  adsl <- adsl[adsl$TRT01A %in% c("Placebo", "Xanomeline High Dose")]

  # New derived ADSL age group variable
  adsl[, AGEGR2 := data.table::fcase(AGE < 65, "AGE < 65",
    AGE >= 65, "AGE >= 65",
    default = NA
  )]

  # Read ADAE
  adae <- data.table::as.data.table(pharmaverseadam::adae)

  # Merge ADSL and ADAE
  adam_out <-
    merge(adsl, adae[, c(setdiff(names(adae), names(adsl)), "USUBJID"),
      with =
        F
    ], by = "USUBJID", all = TRUE)

  return(adam_out)
}

ep_spec <- chef::mk_endpoint_str(
    data_prepare = mk_adae,
    pop_var = "SAFFL",
    pop_value = "Y",
    treatment_var = "TRT01A",
    treatment_refval = "Xanomeline High Dose",
    group_by = list(list(AESOC = c())),
    stratify_by = list(c("AEDECOD")),
    stat_by_strata_by_trt = list(chefStats::n_subj_event,
                                 chefStats::n_event)
  )

This table contains both continuous variables (Age, Body Weight at Baseline) and categorical variables (Age Group, Sex) for which different statistics are applied. So the endpoint will be split up in two endpoint specifications, one for the continuous variables (case 5), and one for the categorical variables (case 6).

For the categorical variables (Age Group and Sex), we see that we need a statistic for each stratification level of the category (e.g. AGEGR1 < 65 and AGEGR1 >= 65). So it is clear that to get the results we need, we need one statistic per stratification level and treatment level. Thus we will supply our statistical function to the stat_by_strata_by_trt argument of our endpoint specification.

For the continuous variables it is less obvious. However, if we consider TOTAL to be a stratification just like age or sex, albeit with only a single level, then we can see that here we also need one statistic per strata level and treatment level, so will use the stat_by_strata_by_trt

In both cases a single statistical function is applied, (chefStats::demographics_continuous() and chefStats::demographics_categorical() respectively) which compactly supplies all required statistics in one function call. Alternatively, we can define separate functions for each statistics and apply the individually in the endpoint specification.

We also need the functions to produce the clinical data to pass to the data_prepare. Since this example analyzes body weight, we will need some version of the ADVS dataset. As these exact nature of the clinical dataset will differ from project-to-project, there is no library of data prep functions we can source from. Instead we define our own example that matches the needs of our analysis:

# Merge ADSL and ADVS from {pharmaverseadam}
mk_advs <- function(study_metadata) {

  # Read ADSL
  adsl <- data.table::as.data.table(pharmaverseadam::adsl)

  # Filter treatment arms
  adsl <- adsl[adsl$TRT01A %in% c("Placebo", "Xanomeline High Dose")]

  # New derived ADSL age group variable
  adsl[, AGEGR2 := data.table::fcase(AGE < 65, "AGE < 65",
    AGE >= 65, "AGE >= 65",
    default = NA
  )]
  # Read ADVS
  advs <- data.table::as.data.table(pharmaverseadam::advs)

  # Identify baseline body weight
  advs_bw <- advs[advs$PARAMCD == "WEIGHT" & advs$VISIT == "BASELINE"]

  # Create new variable BW_BASELINE
  advs_bw[["BW_BASELINE"]] <- advs_bw[["AVAL"]]

  # Merge ADSL, ADAE and baseline body weight from ADVS
  adam_out <-
    merge(adsl, advs_bw[, c("BW_BASELINE", "USUBJID")], by = "USUBJID", all.x = TRUE)

  return(adam_out)
}

We show how to define demographic endpoints for both continuous variables and categorical variables

Case 1: Baseline characteristics of analysis population (continuous variables)
chef::mk_endpoint_str(
  data_prepare = mk_advs,
  treatment_var = "TRT01A",
  treatment_refval = "Xanomeline High Dose",
  pop_var = "SAFFL",
  pop_value = "Y",
  stat_by_strata_by_trt = list(
    c(demographics_continuous,
      var = "AGE"
    ),
    c(demographics_continuous,
      var = "BW_BASELINE"
    )
  ),
  endpoint_label = "Demographics endpoint (categorical measures)"
)
Case 2: Baseline characteristics of analysis population (categorical variables)
chef::mk_endpoint_str(
  data_prepare = mk_advs,
  treatment_var = "TRT01A",
  treatment_refval = "Xanomeline High Dose",
  pop_var = "SAFFL",
  pop_value = "Y",
  stratify_by = list(c("AGEGR2", "SEX")),
  stat_by_strata_by_trt = list(c(demographics_counts)),
  endpoint_label = "Demographics endpoint (categorical measures)"
)

We then collect the two endpoint specifications to cover the complete endpoint:

ep_spec <- rbind(ep_spec_pt1, ep_spec_pt2)

The table above contains statistics on baseline characteristics, as well as change-from-baseline. We observe that mean and SD are calculated on each combination of treatment arm, strata (total and age), time (baseline, week 8 and 16). By defining time (AVISIT) as the endpoint grouping we can obtain all the statistics in one endpoint specification. This implies that:

  • The mean and SD are calculated by treatment and strata levels (stat_by_strata_by_trt) on both the analysis data columns containing the baseline (VALUE_BASELINE) and change from baseline column (VALUE_CHANGE).
  • Number of subjects (N) is also calculated by treatment and strata level (stat_by_strata_by_trt).
  • For each endpoint grouping (AVISIT) Hedge’s G is calculated by each strata level and across treatment arms (stat_by_strata_across_trt).

We also need the functions to produce the clinical data to pass to the data_prepare. Since this example analyses laboratory data, we will need some version of the ADLB dataset. As these exact nature of the clinical dataset will differ from project-to-project, there is no library of data prep functions we can source from. Instead we define our own example that matches the needs of our analysis:

# Merge ADSL and ADLB from {pharmaverseadam}
mk_adlb <- function(study_metadata) {
  # Read ADSL
  adsl <- data.table::as.data.table(pharmaverseadam::adsl)

  # Filter treatment arms
  adsl <-
    adsl[adsl$TRT01A %in% c("Placebo", "Xanomeline High Dose")]

  # New derived ADSL age group variable
  adsl[, AGEGR2 := data.table::fcase(AGE < 65, "AGE < 65",
    AGE >= 65, "AGE >= 65",
    default = NA
  )]

  # Read ADLB
  adlb_0 <- data.table::as.data.table(pharmaverseadam::adlb)
  adlb_1 <- adlb_0[adlb_0[["PARAMCD"]] == "SODIUM" &
      adlb_0[["AVISIT"]] %in% c("Baseline", "Week 8", "Week 16"), ]

  adlb2 <-
    merge(adlb_1,
      adlb_1[adlb_1$AVISIT == "Baseline", c("USUBJID", "AVAL")],
      by = "USUBJID", all.x = TRUE
    )

  adlb2[["VALUE_BASELINE"]] <- adlb2[["AVAL.y"]]
  adlb2[["VALUE_CHANGE"]] <- adlb2[["AVAL.x"]] - adlb2[["AVAL.y"]]

  # Merge ADSL and ADLB
  adam_out <-
    merge(adsl, adlb2[, c(
      "USUBJID",
      "PARAMCD",
      "AVISIT",
      "VALUE_BASELINE",
      "VALUE_CHANGE",
      "ANL01FL"
    )], by = "USUBJID", all.x = TRUE)

  return(adam_out)
}

The endpoint specification showing summary statistics on baseline and change from baseline is shown below

chef::mk_endpoint_str(
  data_prepare = mk_adlb,
  treatment_var = "TRT01A",
  treatment_refval = "Xanomeline High Dose",
  pop_value = "Y",
  group_by = list(list(AVISIT = c())),
  stratify_by = list(c("AGEGR2")),
  stat_by_strata_by_trt = list(
    "n_sub" = chefStats::n_subj,
    "mean: VALUE_BASELINE" = c(chefStats::mean_value, var = "VALUE_BASELINE"),
    "mean: VALUE_CHANGE" = c(chefStats::mean_value, var = "VALUE_CHANGE"),
    "sd: VALUE_BASELINE" = c(chefStats::sd_value, var = "VALUE_BASELINE"),
    "sd: VALUE_CHANGE" = c(chefStats::sd_value, var = "VALUE_CHANGE")
  ),
  stat_by_strata_across_trt = list(hedgesg),
  endpoint_label = "Baseline and change from baseline on SODIUM - <AVISIT>"
)