Tutorials › Biostatistics › admiralonco Part 2: Advanced Oncology ADaM Programming

Oncology ADaM Programming

admiralonco Part 2: Advanced Oncology ADaM Programming

A production-oriented tutorial for statistical programmers who already know ADaM and admiral and want to build robust oncology datasets with admiralonco—including ADRS, ADTR, ADTTE, confirmation logic, nadir-based endpoints, event definitions, non-standard response criteria, and validation strategies.

Advanced ~45 min read

What You'll Learn

Introduction

If Part 1 introduced the philosophy of admiral and the basic mechanics of creating ADaM datasets in R, Part 2 is where the programming becomes substantially more like real oncology production work.

The difficult part of an oncology ADaM program is rarely the final mutate(). The difficult part is translating a clinical endpoint definition into an auditable sequence of record selection, reference-date logic, analysis flags, event precedence, confirmation rules, and parameter metadata. That is exactly where admiralonco becomes useful.

Version note: The current admiralonco documentation describes the package as an oncology extension to admiral, with a primary focus on reusable oncology efficacy endpoints and solid-tumor response programming. The current documentation also provides templates for ADRS, ADTR, ADTTE and disease-area extensions. The examples below are written as advanced programming patterns and should always be reconciled with the study-specific SAP, ADaM specifications, response criteria, and locked package versions used for a submission.

1. Where admiralonco Fits in the ADaM Architecture

A useful mental model is to treat admiralonco as an oncology-specific layer on top of the general-purpose derivation machinery in admiral.

LayerTypical responsibilityExamples
SDTMCollected clinical observationsRS, TU, TR, CE, DS
ADSLSubject-level analysis variablesTRTSDT, RANDDT, SAFFL, ITTFL
ADTRLongitudinal tumor measurementsTarget-lesion sum, nadir, CHG, PCHG
ADRSResponse assessments and derived response endpointsOVR, BOR, CBOR, confirmed response
ADTTETime-to-event endpointsOS, PFS, DoR
Programming principle: Do not force every oncology endpoint into a single dataset. Build analysis datasets around the analytical grain and endpoint family. Reuse carefully derived intermediate variables rather than duplicating complex clinical logic in multiple programs.

2. Start With the Template, Not With a Blank Script

A production programmer should generally begin by examining the package template and then adapting it to the study specification. The admiralonco documentation provides ADaM templates that can be generated through admiral::use_ad_template().

library(admiral)
library(admiralonco)

use_ad_template(
  adam_name = "ADRS",
  save_path = "./programs/ad_adrs.R",
  package = "admiralonco"
)

The exact template name should be checked against the version of admiralonco being used.

This is more than a convenience. Templates encode a recommended programming workflow and make the resulting program easier for another programmer to audit. They also reduce the risk of silently reinventing package patterns that already exist.

3. Establish a Reproducible Programming Environment

For production work, package versions should be controlled. A derivation that works today because of an unpinned development version is not necessarily a reproducible submission program.

library(admiral)
library(admiralonco)
library(dplyr)
library(lubridate)

packageVersion("admiral")
packageVersion("admiralonco")

get_admiral_option("subject_keys")

One particularly useful admiral convention is to use the package's subject-key configuration rather than repeatedly hard-coding subject identifiers. This becomes important when code is reused across studies or organizations with different subject-key conventions.

4. Build a Study-Specific Derivation Contract

Before writing derivation code, translate the SAP and ADaM specifications into a small programming contract. For every parameter, explicitly record:

1
Source: identify exactly which clinical records can contribute.
2
Eligibility: remove records outside the analysis population or time window.
3
Normalization: harmonize dates, response values, categories, and analysis flags.
4
Endpoint logic: derive the clinical event or response parameter.
5
Metadata: assign PARAMCD, PARAM, AVAL/AVALC and analysis sequence.
6
QC: compare against independent subject-level expectations.

5. Advanced ADRS Programming

ADRS is where oncology programming becomes most clinically specific. A typical workflow starts with response assessments and builds increasingly derived parameters: progression, response, clinical benefit, best overall response, confirmed response, death, last assessment, and measurable disease status.

5.1 Normalize the Response Input

library(dplyr)
library(admiral)
library(admiralonco)

rs_adsl <- rs %>%
  convert_blanks_to_na() %>%
  derive_vars_merged(
    dataset_add = adsl,
    new_vars = exprs(TRTSDT, RANDDT),
    by_vars = get_admiral_option("subject_keys")
  )

The important design choice is to merge only variables required for the derivation. Avoid blindly joining all of ADSL onto every record; doing so makes lineage harder to understand and can introduce accidental variable collisions.

5.2 Analysis Flags Are Part of the Clinical Definition

An oncology endpoint is often not simply "take the maximum response." It is "take the maximum response among records satisfying a defined analysis flag, reference-date rule, and assessment eligibility condition."

# Example pattern: derive a study-specific analysis flag.
adrs <- adrs %>%
  mutate(
    ANL01FL = if_else(
      !is.na(ADT) &
        ADT >= TRTSDT &
        ADT <= as.Date(cutoffdt),
      "Y",
      NA_character_
    )
  )
QC warning: Never assume that ANL01FL means the same thing in every study. Its exact definition is specification-driven. The code above is a pattern, not a universal oncology rule.

5.3 Deriving Progressive Disease as an Event

The modern admiralonco workflow favors reusable event machinery rather than hard-coding a separate custom algorithm for every endpoint. In recent versions, oncology event definitions are designed to work with admiral's extreme-event framework.

# Illustrative event-object pattern.
pd_event <- event(
  description = "Progressive disease",
  dataset_name = "adrs",
  filter = PARAMCD == "PD" & ANL01FL == "Y",
  set_values_to = exprs(EVNTDESC = "Progressive disease")
)

The exact helper and argument structure should be taken from the version-specific admiralonco/admiral vignette. The architectural point is more important: define events declaratively and then reuse them in multiple endpoint derivations.

5.4 Best Overall Response

Best overall response is a classic example of why a programmer should separate clinical rules from data manipulation. The response hierarchy, analysis period, confirmation requirements, and special handling of CR/PR/SD/PD/NE must come from the protocol and SAP.

# Illustrative structure; use the version-specific function signature
# supplied by the admiralonco template for production code.

adrs_bor <- adrs %>%
  derive_param_bor(
    by_vars = exprs(USUBJID),
    set_values_to = exprs(
      PARAMCD = "BOR",
      PARAM = "Best Overall Response"
    )
  )

The key is not memorizing a function call. The key is understanding what records the function is allowed to see. If a pre-processing step accidentally retains an ineligible PD record, the final BOR can be wrong even though the function itself runs without error.

6. Confirmed Response: Where Advanced QC Matters

Confirmation is one of the most error-prone parts of oncology ADaM programming. A confirmed response is a longitudinal rule, not a simple lookup of the patient's best observed response.

Consider the following simplified sequence:

VisitOVRPotential interpretation
Week 8PRFirst response
Week 12PRResponse confirmed
Week 16PDLater progression

The programming question is not merely whether PR exists. It is whether a subsequent eligible assessment confirms it under the study's specified timing and response rules.

Advanced QC strategy: Create a subject-level audit table containing every response assessment, the derived analysis flag, the confirmation candidate, the confirming record, and the final confirmed-response result. This table is often more valuable for debugging than inspecting the final ADRS dataset alone.

7. Non-Standard Response Criteria

One of the strongest reasons to use admiralonco is that oncology trials do not all use the same response framework. The package documentation includes extensions and examples for criteria such as iRECIST, IMWG, GCIG, PCWG3 and Lugano 2014.

For non-standard endpoints, resist the temptation to overload a standard RECIST function with undocumented assumptions. Instead, use the flexible event/record derivation patterns provided by admiral and build the disease-specific rule in small, testable components.

7.1 Example: Separating Clinical Rules From Dataset Construction

# Step 1: create an eligibility flag.
eligible <- rs %>%
  filter(ANL01FL == "Y") %>%
  mutate(
    response_eligible = case_when(
      !is.na(AVALC) ~ TRUE,
      TRUE ~ FALSE
    )
  )

# Step 2: isolate the event-driving records.
response_events <- eligible %>%
  filter(response_eligible) %>%
  select(USUBJID, ADT, AVALC, PARAMCD, ANL01FL)

This decomposition makes the logic inspectable. A reviewer can ask "Which records were eligible?" independently of "How was the endpoint selected?"

8. ADTR: The Measurement Layer Behind Response

ADTR is useful when the study requires analysis of tumor measurements themselves. The key distinction is between measurement-level variables and response-category variables.

ConceptTypical ADTR representationWhy it matters
Absolute tumor burdenAVALSupports longitudinal measurement displays
BaselineBASEReference for CHG/PCHG
ChangeCHGAbsolute change from baseline
Percent changePCHGWaterfall/spider displays
NadirDerived minimumSupports progression logic

8.1 Percent Change Must Be Traceable

mutate(
  CHG = AVAL - BASE,
  PCHG = if_else(
    is.na(BASE) | BASE == 0,
    NA_real_,
    100 * (AVAL - BASE) / BASE
  )
)
Do not silently replace a zero denominator. A zero or invalid baseline requires a specification-driven decision. Converting the result to zero because "there is no change" is not mathematically valid.

9. Nadir Logic: A Frequent Source of Subtle Errors

The nadir is a measurement concept, while progression is a clinical endpoint rule. Those concepts must not be conflated.

nadir <- adtr %>%
  filter(ANL01FL == "Y") %>%
  group_by(USUBJID) %>%
  summarise(
    NADIR = min(AVAL, na.rm = TRUE),
    .groups = "drop"
  )

In a real study, the record set used for the nadir must match the prespecified analysis rules. For example, post-progression measurements, unscheduled scans, assessments after a new anticancer therapy, or records outside a cutoff may need special treatment.

10. ADTTE: Turning Clinical Events Into Time-to-Event Endpoints

ADTTE is conceptually different from ADRS. Instead of asking "what response did the patient achieve?", ADTTE asks "when did the endpoint occur, and if it did not occur, when and why was the patient censored?"

1
Define the event hierarchy.
2
Define censoring sources and precedence.
3
Apply cutoff and post-treatment rules.
4
Derive ADT/STARTDT and event status.
5
Calculate AVAL using the prespecified time-unit convention.

10.1 Overall Survival

# Illustrative OS event construction.
os_event <- event(
  description = "Death",
  dataset_name = "adsl",
  filter = !is.na(DTHDT),
  date = DTHDT
)

The exact event helper syntax depends on the admiral version and template in use. The important point is to define the clinical event separately from the downstream ADTTE construction.

10.2 Progression-Free Survival

PFS commonly combines progression and death. A robust implementation should make that precedence explicit rather than relying on whichever dataset happens to be sorted first.

# Conceptual event precedence:
# 1. PD event
# 2. Death event
# 3. If neither occurs, apply the SAP-defined censoring rule.

pfs_sources <- list(
  pd_event,
  os_event
)
Production rule: If two events occur on the same date, the endpoint specification must determine whether they are equivalent, whether one takes precedence, or whether an event hierarchy is needed. Never let row ordering decide the result.

11. Duration of Response

Duration of response introduces an additional dependency: the patient must first be a responder according to the prespecified response definition. The response start date and progression/death end date must therefore be derived consistently.

ComponentQuestion
EligibilityWho qualifies as a responder?
StartWhat date is the response considered to begin?
End eventPD, death, or another prespecified event?
CensoringWhat happens when no end event is observed?
CutoffHow are observations after the data cutoff treated?

12. New Anti-Cancer Therapy Is Not Just Another Date

Many oncology endpoints have special rules concerning the start of subsequent anticancer therapy. The date can affect whether an event is eligible, whether an observation is censored, and whether an assessment contributes to the endpoint.

# Example pattern for deriving an eligible assessment period.
adrs <- adrs %>%
  mutate(
    after_new_therapy = case_when(
      is.na(NEWTRTSDT) ~ FALSE,
      ADT >= NEWTRTSDT ~ TRUE,
      TRUE ~ FALSE
    )
  )

This is intentionally shown as a pattern rather than a universal implementation. The SAP determines whether the event or censoring date is before or at the new therapy date and whether the new therapy itself defines an analysis boundary.

13. Cutoff Dates and Data Snapshots

A clinical analysis is a snapshot. The program must therefore distinguish between "the latest record in the database" and "the latest record available at the analysis cutoff."

# Always make the cutoff explicit.
analysis_cutoff <- as.Date("2026-06-30")

eligible <- source %>%
  filter(
    is.na(ADT) | ADT <= analysis_cutoff
  )
Validation question: Can you reproduce the same endpoint if the source dataset contains later records that were not part of the locked analysis snapshot? If not, the cutoff logic is not sufficiently isolated.

14. Reusable Functions Beat Copy-and-Paste

Advanced admiral programming is most maintainable when study-specific rules are encapsulated in small functions or configuration objects.

derive_pct_change <- function(data) {
  data %>%
    mutate(
      CHG = AVAL - BASE,
      PCHG = 100 * CHG / BASE
    )
}

derive_pct_change(adtr)

For production work, consider adding explicit input checks, documented assumptions, and controlled handling of missing or zero baselines.

15. Parameter Metadata Should Be Programmatic

Avoid scattered assignments such as repeatedly typing PARAMCD and PARAM in unrelated sections. Centralizing parameter metadata makes multi-parameter ADRS and ADTTE programs easier to review.

param_meta <- tibble::tribble(
  ~PARAMCD, ~PARAM,
  "BOR",    "Best Overall Response",
  "CBOR",   "Best Overall Clinical Benefit",
  "PD",     "Progressive Disease"
)

16. Advanced Data-Lineage Pattern

A strong production program should make it possible to trace a final parameter back to the source assessment. One practical pattern is to retain source identifiers until the endpoint is fully derived.

adrs_work <- rs %>%
  select(
    STUDYID, USUBJID, RSSEQ, RSDTC, RSTESTCD,
    RSORRES, RSSTRESC, ANL01FL
  )

adrs_final <- adrs_work %>%
  # derive endpoint...
  mutate(
    PARAMCD = "OVR",
    PARAM = "Overall Response"
  )

Keeping RSSEQ or another source identifier during development makes targeted QC much easier. It can then be retained or removed according to the final ADaM specification.

17. Validation: Test the Algorithm, Not Just the Dataset

A common mistake is to validate only variable-level outputs. Oncology programming needs scenario-based validation because the difficult bugs occur at boundaries.

Boundary dates
Assessment exactly on, one day before, and one day after the analysis boundary.
Confirmation
Response followed by response, NE, PD, or missing assessment.
Nadir
Deepest measurement before and after progression.
Same-day events
PD and death occurring on the same date.
New therapy
Assessments immediately before and after new anticancer therapy.
Missingness
Missing baseline, missing assessment, and incomplete dates.

18. Manual Validation Example

Suppose a patient has the following simplified target-lesion measurements:

DaySum of DiametersChange from BaselinePercent Change
010000%
2880−20−20%
5665−35−35%
8470−30−30%
11290−10−10%

The nadir is 65. The final observation is still below baseline, but it represents regrowth from the nadir. A reviewer should therefore verify the progression logic using the actual tumor-burden scale and the complete response assessment rather than interpreting the −10% value in isolation.

19. Programmatic Assertions

Assertions can catch structural errors before a dataset reaches downstream TLG programs.

stopifnot(
  all(duplicated(
    adsl %>% select(USUBJID)
  ) == FALSE)
)

stopifnot(
  all(adrs$ADT <= analysis_cutoff | is.na(adrs$ADT))
)

In a regulated environment, assertions should complement—not replace—formal independent QC.

20. Independent QC Should Be Algorithmically Different

If the production program uses the same function calls and the same derivation strategy as QC, both programs can reproduce the same mistake. A stronger QC program uses a simpler, independently structured calculation.

# Production: complex event derivation.
# QC: independently calculate the expected subject-level result.

qc_bor <- response_records %>%
  group_by(USUBJID) %>%
  summarise(
    expected_bor = case_when(
      any(AVALC == "CR") ~ "CR",
      any(AVALC == "PR") ~ "PR",
      any(AVALC == "SD") ~ "SD",
      TRUE ~ "NE"
    ),
    .groups = "drop"
  )

The example is deliberately simplified and is not a replacement for the complete response algorithm. Its purpose is to demonstrate the independence principle.

21. Handling Post-Progression Measurements

Post-progression measurements are a classic source of accidental endpoint contamination. Whether they are retained depends on the endpoint and the SAP. For some derivations they are excluded from the event-driving record set; for others they remain useful for descriptive displays.

analysis_records <- adtr %>%
  filter(
    ANL01FL == "Y",
    is.na(PD_DATE) | ADT <= PD_DATE
  )
Important: This is a study-specific pattern, not a universal rule. Post-PD handling must follow the endpoint definition and the package/template approach applicable to the study.

22. Immuno-Oncology and iRECIST

Immuno-oncology trials can require response frameworks that distinguish conventional progression from immune-confirmed progression. This is precisely where a modular event architecture becomes valuable.

Do not rename standard RECIST variables and assume that the resulting dataset is iRECIST-compliant. The response state machine itself changes, and the timing and confirmation logic must be implemented explicitly.

23. Disease-Specific Extensions

The current admiralonco documentation includes examples beyond basic RECIST, including IMWG, GCIG, PCWG3 and Lugano 2014 workflows. These examples illustrate an important engineering principle: disease-specific endpoints should be layered on top of general ADaM derivation infrastructure rather than implemented as unreviewable monolithic scripts.

24. A Production ADRS Program Skeleton

# ============================================================
# ADRS - production-oriented skeleton
# ============================================================

library(admiral)
library(admiralonco)
library(dplyr)
library(lubridate)

# 1. Read source data
adsl <- read_adsl()
rs   <- read_rs()
tu   <- read_tu()

# 2. Normalize / validate input
rs <- rs %>% convert_blanks_to_na()

# 3. Merge only required ADSL variables
adrs <- rs %>%
  derive_vars_merged(
    dataset_add = adsl,
    new_vars = exprs(TRTSDT, RANDDT),
    by_vars = get_admiral_option("subject_keys")
  )

# 4. Apply analysis eligibility
adrs <- adrs %>%
  filter(ADT <= analysis_cutoff)

# 5. Derive oncology endpoints using version-specific functions
adrs <- derive_pd(adrs)
adrs <- derive_response(adrs)
adrs <- derive_bor(adrs)
adrs <- derive_confirmed_response(adrs)

# 6. Add ADSL variables
adrs <- add_adsl_variables(adrs, adsl)

# 7. Derive ASEQ last
adrs <- adrs %>%
  group_by(USUBJID) %>%
  mutate(ASEQ = row_number()) %>%
  ungroup()

# 8. QC and export
validate_adrs(adrs)
write_adam(adrs)

The functions read_adsl(), read_rs(), derive_pd(), and similar calls above are intentionally illustrative study-level wrappers. In production, the corresponding package functions and project utilities should be taken from the validated study template.

25. A Production ADTTE Program Skeleton

# ============================================================
# ADTTE - production-oriented skeleton
# ============================================================

# 1. Define source events independently
events <- list(
  pd_event,
  death_event
)

# 2. Define censoring sources
censoring <- list(
  last_assessment_censor,
  cutoff_censor
)

# 3. Build each endpoint from the same event architecture
adtte_pfs <- derive_pfs(
  events = events,
  censoring = censoring,
  start_date = TRTSDT
)

adtte_os <- derive_os(
  events = list(death_event),
  censoring = censoring,
  start_date = TRTSDT
)

26. Common Advanced Mistakes

MistakeWhy It Is DangerousBetter Approach
Hard-coding response hierarchyClinical rules can change by protocolUse specification-driven logic
Using all RS recordsIneligible assessments can alter BOR/PDCreate explicit eligible record sets
Ignoring new therapy datesCan change endpoint event/censoring statusMake the boundary explicit
Using baseline instead of nadir for PDCan materially misclassify progressionSeparate measurement and event logic
QC with identical codeShared defects remain invisibleUse an independent algorithm
Deriving ASEQ too earlyLater appends can create duplicatesAssign sequence after final assembly
Blind joins to ADSLVariable collisions and lineage problemsMerge only required variables
Uncontrolled package versionsFunction behavior can changeLock versions and record session information

27. Package-Version and Reproducibility QC

sessionInfo()

installed.packages()[c(
  "admiral",
  "admiralonco",
  "dplyr"
), "Version"]

For submission-oriented programming, preserve the package versions used to generate the final datasets and figures. If the project uses a lockfile, validate that the locked environment reproduces the same results.

28. Practical Figure and Dataset QC

ADRS and ADTR should not be validated only by examining the dataset. Downstream visualizations are excellent secondary checks.

29. An Advanced Subject-Level QC Extract

qc_subject <- adrs %>%
  group_by(USUBJID) %>%
  summarise(
    n_assessments = n(),
    first_assessment = min(ADT, na.rm = TRUE),
    last_assessment  = max(ADT, na.rm = TRUE),
    n_pr = sum(AVALC == "PR", na.rm = TRUE),
    n_cr = sum(AVALC == "CR", na.rm = TRUE),
    n_pd = sum(AVALC == "PD", na.rm = TRUE),
    .groups = "drop"
  )

This kind of compact extract is extremely useful when reviewing edge cases with clinical statisticians and medical reviewers.

30. How to Think Like an Oncology ADaM Programmer

The most important shift from intermediate to advanced programming is to stop thinking of an endpoint as a single transformation. Treat it as a chain:

1
Clinical concept — what does the endpoint mean?
2
Eligible evidence — which records can support it?
3
Temporal rules — which dates define eligibility?
4
Derivation — how is the endpoint selected?
5
Metadata — how is it represented in ADaM?
6
Independent QC — how do we prove the result?

31. Practical Checklist for a Production Oncology ADaM Program

☐ Package versions recorded
☐ SAP rules translated into explicit conditions
☐ Analysis cutoff implemented
☐ Reference dates validated
☐ Analysis flags independently reviewed
☐ New-therapy rules validated
☐ Response confirmation scenarios tested
☐ Nadir logic independently checked
☐ Event/censoring precedence tested
☐ Source lineage retained during development
☐ Independent QC algorithm completed
☐ Dataset reconciled to downstream TLGs

32. Summary

Advanced admiralonco programming is less about knowing a large collection of function names and more about designing an auditable derivation architecture. The package provides oncology-specific building blocks, while admiral provides the general ADaM derivation machinery.

The most robust implementations separate source-record eligibility, temporal rules, clinical endpoint logic, parameter metadata, and QC. This makes difficult oncology endpoints easier to review and substantially reduces the risk that a small change in one derivation silently changes several downstream analyses.

The key concept: A production oncology ADaM dataset should be reproducible from the clinical source records, the analysis specification, the program version, and a clearly documented sequence of derivation rules. If a reviewer cannot follow that chain, the program is not yet as robust as it could be.

References

Pharmaverse. admiralonco: Oncology extension package for ADaM in R Asset Library admiral. Package documentation and vignettes.

Pharmaverse. admiral: ADaM in R Asset Library. Package documentation and derivation framework.

Clinical response criteria and endpoint definitions should be interpreted according to the protocol, statistical analysis plan, and applicable disease-specific guidelines for the study.

← Back to All Tutorials