if (!requireNamespace("dplyr", quietly = TRUE)) suppressMessages(install.packages("dplyr"))
if (!requireNamespace("lubridate", quietly = TRUE)) suppressMessages(install.packages("lubridate"))
if (!requireNamespace("tidyr", quietly = TRUE)) suppressMessages(install.packages("tidyr"))
if (!requireNamespace("pharmaversesdtm", quietly = TRUE)) suppressMessages(install.packages("pharmaversesdtm"))
library(dplyr)
library(lubridate)
library(tidyr)
library(pharmaversesdtm)Day 11: Disposition (DS) & Trial Design Domains
Screen Failures, Completers, and Study Structure
1 Learning Objectives
By the end of Day 11, you will be able to:
- Build the DS (Disposition) domain - tracking every subject’s journey through the trial
- Understand EPOCH and how it relates to study periods (SCREENING, TREATMENT, FOLLOW-UP)
- Derive key disposition variables: DSDECOD, DSSTDTC, DSCAT, and completion flags
- Survey the Trial Design domains (TA, TE, TV, TI, TS) and understand what each contains
- Create a subject disposition flow - the essential “patient accounting” table
- Understand how DS feeds into the ADaM ADSL dataset
2 Why Disposition Matters
The DS domain answers the most fundamental question in a clinical trial: What happened to each subject?
The FDA requires a complete accounting of every subject who entered the study:
- Did they complete the study? →
COMPLETED - Did they withdraw early? → Why? (adverse event, lost to follow-up, withdrawal by subject, etc.)
- Were they a screen failure? → Why? (did not meet inclusion criteria, etc.)
This information feeds into CONSORT diagrams, disposition tables, and the ADaM ADSL dataset - all critical for regulatory submissions.
3 Package Installation & Loading
4 The DS Domain Structure
4.1 Key Variables
┌──────────────────────────────────────────────────────────────────────────────┐
│ DS DOMAIN - KEY VARIABLES │
├──────────────────────────────────────────────────────────────────────────────┤
│ IDENTIFIER VARIABLES │
│ STUDYID = Study identifier │
│ USUBJID = Unique subject identifier │
│ DSSEQ = Sequence number within subject │
├──────────────────────────────────────────────────────────────────────────────┤
│ TOPIC / RESULT VARIABLES │
│ DSTERM = Reported disposition term (as collected) │
│ DSDECOD = Standardized disposition term (CT) │
│ DSCAT = Category: "DISPOSITION EVENT" or "PROTOCOL MILESTONE" │
│ DSSCAT = Subcategory: e.g., "STUDY PARTICIPATION", "TREATMENT" │
├──────────────────────────────────────────────────────────────────────────────┤
│ TIMING VARIABLES │
│ DSSTDTC = Date/time of disposition event (ISO 8601) │
│ DSDY = Study day of disposition event │
│ EPOCH = Epoch (SCREENING, TREATMENT, FOLLOW-UP) │
│ VISITNUM = Visit number │
│ VISIT = Visit name │
└──────────────────────────────────────────────────────────────────────────────┘
4.2 Understanding DSCAT and DSSCAT
These categorization variables are critical:
| DSCAT | DSSCAT | DSDECOD Examples |
|---|---|---|
DISPOSITION EVENT |
STUDY PARTICIPATION |
COMPLETED, SCREEN FAILURE |
DISPOSITION EVENT |
TREATMENT |
COMPLETED, ADVERSE EVENT, WITHDRAWAL BY SUBJECT |
PROTOCOL MILESTONE |
INFORMED CONSENT |
INFORMED CONSENT OBTAINED |
PROTOCOL MILESTONE |
RANDOMIZATION |
RANDOMIZED |
Think of it this way:
- DSCAT = The broad type of event (disposition event vs. protocol milestone)
- DSSCAT = Which part of the study this relates to (study participation vs. treatment)
A subject can have multiple DS records: one for each protocol milestone and each disposition decision point.
5 Exploring DS Data from pharmaversesdtm
# Load DS domain
data("ds", package = "pharmaversesdtm")
data("dm", package = "pharmaversesdtm")
cat("DS domain dimensions:", nrow(ds), "rows x", ncol(ds), "columns\n")DS domain dimensions: 850 rows x 13 columns
cat("Number of unique subjects:", n_distinct(ds$USUBJID), "\n\n")Number of unique subjects: 306
cat("Variables available:\n")Variables available:
cat(paste(names(ds), collapse = ", "), "\n")STUDYID, DOMAIN, USUBJID, DSSEQ, DSSPID, DSTERM, DSDECOD, DSCAT, VISITNUM, VISIT, DSDTC, DSSTDTC, DSSTDY
dplyr::glimpse(ds)Rows: 850
Columns: 13
$ STUDYID <chr> "CDISCPILOT01", "CDISCPILOT01", "CDISCPILOT01", "CDISCPILOT01…
$ DOMAIN <chr> "DS", "DS", "DS", "DS", "DS", "DS", "DS", "DS", "DS", "DS", "…
$ USUBJID <chr> "01-701-1015", "01-701-1015", "01-701-1015", "01-701-1023", "…
$ DSSEQ <int> 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 4…
$ DSSPID <chr> NA, NA, NA, NA, "24", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ DSTERM <chr> "RANDOMIZED", "PROTOCOL COMPLETED", "FINAL LAB VISIT", "RANDO…
$ DSDECOD <chr> "RANDOMIZED", "COMPLETED", "FINAL LAB VISIT", "RANDOMIZED", "…
$ DSCAT <chr> "PROTOCOL MILESTONE", "DISPOSITION EVENT", "OTHER EVENT", "PR…
$ VISITNUM <dbl> 3.0, 13.0, 13.0, 3.0, 5.0, 5.0, 201.0, 3.0, 13.0, 13.0, 3.0, …
$ VISIT <chr> "BASELINE", "WEEK 26", "WEEK 26", "BASELINE", "WEEK 4", "WEEK…
$ DSDTC <chr> "2014-01-02", "2014-07-02", "2014-07-02T11:45", "2012-08-05",…
$ DSSTDTC <chr> "2014-01-02", "2014-07-02", "2014-07-02", "2012-08-05", "2012…
$ DSSTDY <dbl> 1, 182, 182, 1, 29, 29, 198, 1, 180, 180, 1, 28, 28, 182, 1, …
5.1 Understanding the DS Records
# What categories exist?
cat("DS Categories:\n")DS Categories:
# Use only columns that exist in the dataset
cat_vars <- intersect(c("DSCAT", "DSSCAT"), names(ds))
ds %>%
count(across(all_of(cat_vars)), name = "Count") %>%
arrange(across(all_of(cat_vars)))# A tibble: 3 × 2
DSCAT Count
<chr> <int>
1 DISPOSITION EVENT 306
2 OTHER EVENT 290
3 PROTOCOL MILESTONE 254
# What disposition terms exist?
cat("Disposition terms (DSDECOD):\n")Disposition terms (DSDECOD):
term_vars <- intersect(c("DSCAT", "DSSCAT", "DSDECOD"), names(ds))
ds %>%
count(across(all_of(term_vars)), name = "Count") %>%
arrange(across(all_of(term_vars[term_vars != "DSDECOD"])), desc(Count))# A tibble: 13 × 3
DSCAT DSDECOD Count
<chr> <chr> <int>
1 DISPOSITION EVENT COMPLETED 110
2 DISPOSITION EVENT ADVERSE EVENT 92
3 DISPOSITION EVENT SCREEN FAILURE 52
4 DISPOSITION EVENT WITHDRAWAL BY SUBJECT 27
5 DISPOSITION EVENT STUDY TERMINATED BY SPONSOR 7
6 DISPOSITION EVENT PROTOCOL VIOLATION 6
7 DISPOSITION EVENT LACK OF EFFICACY 4
8 DISPOSITION EVENT DEATH 3
9 DISPOSITION EVENT PHYSICIAN DECISION 3
10 DISPOSITION EVENT LOST TO FOLLOW-UP 2
11 OTHER EVENT FINAL LAB VISIT 254
12 OTHER EVENT FINAL RETRIEVAL VISIT 36
13 PROTOCOL MILESTONE RANDOMIZED 254
Each subject typically has multiple records in DS:
- Informed consent record - When they signed the ICF
- Randomization record - When they were randomized (if applicable)
- Treatment disposition - Did they complete treatment? If not, why?
- Study disposition - Did they complete the study? If not, why?
These records tell the full story of each subject’s journey.
6 Understanding EPOCH
6.1 What is EPOCH?
EPOCH is a variable that indicates which period of the study the data belongs to. It divides the trial into distinct phases:
Timeline:
────────────────────────────────────────────────────────────────────
SCREENING │ TREATMENT (Run-in) │ TREATMENT │ FOLLOW-UP
(Pre-study) │ (Placebo lead-in) │ (Active) │ (Post-treatment)
────────────────────────────────────────────────────────────────────
# Check EPOCH values in DM (EPOCH is often in DM as well)
if ("EPOCH" %in% names(ds)) {
cat("EPOCH values in DS:\n")
ds %>%
count(EPOCH, name = "Count") %>%
arrange(desc(Count))
} else {
cat("EPOCH not found in DS. Checking DM...\n")
if ("EPOCH" %in% names(dm)) {
cat("EPOCH values in DM:\n")
dm %>% count(EPOCH) %>% print()
}
}EPOCH not found in DS. Checking DM...
6.2 EPOCH in the DM Domain
The DM domain has EPOCH-related variables that indicate the last known state of the subject:
# Key epoch/disposition-related variables in DM
dm_disp_vars <- intersect(
c("USUBJID", "ARM", "ACTARM", "RFSTDTC", "RFENDTC", "DTHDTC", "DTHFL"),
names(dm)
)
dm %>%
select(all_of(dm_disp_vars)) %>%
head(10)# A tibble: 10 × 7
USUBJID ARM ACTARM RFSTDTC RFENDTC DTHDTC DTHFL
<chr> <chr> <chr> <chr> <chr> <chr> <chr>
1 01-701-1015 Placebo Placebo 2014-0… 2014-0… <NA> <NA>
2 01-701-1023 Placebo Placebo 2012-0… 2012-0… <NA> <NA>
3 01-701-1028 Xanomeline High Dose Xanomeline Hig… 2013-0… 2014-0… <NA> <NA>
4 01-701-1033 Xanomeline Low Dose Xanomeline Low… 2014-0… 2014-0… <NA> <NA>
5 01-701-1034 Xanomeline High Dose Xanomeline Hig… 2014-0… 2014-1… <NA> <NA>
6 01-701-1047 Placebo Placebo 2013-0… 2013-0… <NA> <NA>
7 01-701-1057 Screen Failure Screen Failure <NA> <NA> <NA> <NA>
8 01-701-1097 Xanomeline Low Dose Xanomeline Low… 2014-0… 2014-0… <NA> <NA>
9 01-701-1111 Xanomeline Low Dose Xanomeline Low… 2012-0… 2012-0… <NA> <NA>
10 01-701-1115 Xanomeline Low Dose Xanomeline Low… 2012-1… 2013-0… <NA> <NA>
| Variable | Description |
|---|---|
RFSTDTC |
Reference start date (first exposure) |
RFENDTC |
Reference end date (last exposure) |
RFXSTDTC |
Date/time of first study treatment |
RFXENDTC |
Date/time of last study treatment |
DTHDTC |
Date of death |
DTHFL |
Death flag (Y/N) |
DSDECOD |
Disposition decoded term (in some implementations) |
These DM variables are derived from the DS and EX domain data.
7 Building a DS Domain from Scratch
Let’s simulate raw disposition data and build a complete DS domain:
7.1 Step 1: Create Raw Disposition Data
set.seed(42)
n_subjects <- 30
# Generate subject disposition decisions
raw_disp <- tibble(
STUDYID = "CDISC01",
USUBJID = paste0("CDISC01-001-", sprintf("%03d", 1:n_subjects)),
# Informed consent date
CONSENT_DATE = as.Date("2024-01-01") + sample(0:30, n_subjects, replace = TRUE),
# Screening result
SCREEN_RESULT = sample(
c("PASSED", "FAILED"),
n_subjects, replace = TRUE, prob = c(0.85, 0.15)
),
SCREEN_REASON = if_else(
SCREEN_RESULT == "FAILED",
sample(c("DID NOT MEET INCLUSION CRITERIA",
"EXCLUSION CRITERIA MET",
"WITHDREW CONSENT"),
n_subjects, replace = TRUE, prob = c(0.5, 0.3, 0.2)),
NA_character_
),
# Randomization date (only for those who passed screening)
RAND_DATE = if_else(
SCREEN_RESULT == "PASSED",
CONSENT_DATE + sample(7:21, n_subjects, replace = TRUE),
as.Date(NA)
),
# Treatment arm (only for randomized subjects)
ARMCD = if_else(
SCREEN_RESULT == "PASSED",
sample(c("PBO", "ACT10", "ACT20"), n_subjects, replace = TRUE),
NA_character_
)
) %>%
# Add first dose date
mutate(
FIRST_DOSE = if_else(!is.na(RAND_DATE), RAND_DATE + sample(0:3, n(), replace = TRUE), as.Date(NA))
)
# Determine treatment disposition for randomized subjects
raw_disp <- raw_disp %>%
mutate(
TRT_RESULT = case_when(
SCREEN_RESULT == "FAILED" ~ NA_character_,
TRUE ~ sample(c("COMPLETED", "ADVERSE EVENT", "WITHDRAWAL BY SUBJECT",
"LOST TO FOLLOW-UP", "PHYSICIAN DECISION"),
n(), replace = TRUE,
prob = c(0.65, 0.12, 0.10, 0.08, 0.05))
),
# End of treatment date
TRT_END_DATE = case_when(
is.na(FIRST_DOSE) ~ as.Date(NA),
TRT_RESULT == "COMPLETED" ~ FIRST_DOSE + 84, # 12-week study
TRUE ~ FIRST_DOSE + sample(7:60, n(), replace = TRUE) # Early termination
)
)
cat("Raw disposition data:\n")Raw disposition data:
cat("Total subjects:", nrow(raw_disp), "\n")Total subjects: 30
cat("Screen failures:", sum(raw_disp$SCREEN_RESULT == "FAILED"), "\n")Screen failures: 5
cat("Randomized:", sum(raw_disp$SCREEN_RESULT == "PASSED"), "\n\n")Randomized: 25
raw_disp %>%
select(USUBJID, SCREEN_RESULT, TRT_RESULT) %>%
head(10)# A tibble: 10 × 3
USUBJID SCREEN_RESULT TRT_RESULT
<chr> <chr> <chr>
1 CDISC01-001-001 PASSED WITHDRAWAL BY SUBJECT
2 CDISC01-001-002 PASSED COMPLETED
3 CDISC01-001-003 PASSED COMPLETED
4 CDISC01-001-004 PASSED COMPLETED
5 CDISC01-001-005 PASSED COMPLETED
6 CDISC01-001-006 PASSED COMPLETED
7 CDISC01-001-007 PASSED COMPLETED
8 CDISC01-001-008 FAILED <NA>
9 CDISC01-001-009 PASSED ADVERSE EVENT
10 CDISC01-001-010 PASSED LOST TO FOLLOW-UP
7.2 Step 2: Transform to SDTM DS
# Create DS records for each disposition event
# A subject may have MULTIPLE DS records
# Record 1: Informed Consent
ds_consent <- raw_disp %>%
mutate(
DOMAIN = "DS",
DSSEQ = 1L,
DSTERM = "INFORMED CONSENT OBTAINED",
DSDECOD = "INFORMED CONSENT OBTAINED",
DSCAT = "PROTOCOL MILESTONE",
DSSCAT = "INFORMED CONSENT",
DSSTDTC = as.character(CONSENT_DATE),
EPOCH = "SCREENING"
) %>%
select(STUDYID, DOMAIN, USUBJID, DSSEQ, DSTERM, DSDECOD, DSCAT, DSSCAT, DSSTDTC, EPOCH)
# Record 2: Randomization (only for those who passed screening)
ds_random <- raw_disp %>%
filter(SCREEN_RESULT == "PASSED") %>%
mutate(
DOMAIN = "DS",
DSSEQ = 2L,
DSTERM = "RANDOMIZED",
DSDECOD = "RANDOMIZED",
DSCAT = "PROTOCOL MILESTONE",
DSSCAT = "RANDOMIZATION",
DSSTDTC = as.character(RAND_DATE),
EPOCH = "SCREENING"
) %>%
select(STUDYID, DOMAIN, USUBJID, DSSEQ, DSTERM, DSDECOD, DSCAT, DSSCAT, DSSTDTC, EPOCH)
# Record 3: Treatment Disposition
ds_treatment <- raw_disp %>%
filter(!is.na(TRT_RESULT)) %>%
mutate(
DOMAIN = "DS",
DSSEQ = 3L,
DSTERM = TRT_RESULT,
DSDECOD = TRT_RESULT,
DSCAT = "DISPOSITION EVENT",
DSSCAT = "TREATMENT",
DSSTDTC = as.character(TRT_END_DATE),
EPOCH = "TREATMENT"
) %>%
select(STUDYID, DOMAIN, USUBJID, DSSEQ, DSTERM, DSDECOD, DSCAT, DSSCAT, DSSTDTC, EPOCH)
# Record 4: Screen Failure disposition
ds_screenfail <- raw_disp %>%
filter(SCREEN_RESULT == "FAILED") %>%
mutate(
DOMAIN = "DS",
DSSEQ = 2L,
DSTERM = SCREEN_REASON,
DSDECOD = "SCREEN FAILURE",
DSCAT = "DISPOSITION EVENT",
DSSCAT = "STUDY PARTICIPATION",
DSSTDTC = as.character(CONSENT_DATE + sample(1:14, n(), replace = TRUE)),
EPOCH = "SCREENING"
) %>%
select(STUDYID, DOMAIN, USUBJID, DSSEQ, DSTERM, DSDECOD, DSCAT, DSSCAT, DSSTDTC, EPOCH)
# Combine all DS records
sdtm_ds <- bind_rows(ds_consent, ds_random, ds_treatment, ds_screenfail) %>%
# Re-sequence within each subject
arrange(STUDYID, USUBJID, DSSTDTC) %>%
group_by(USUBJID) %>%
mutate(DSSEQ = row_number()) %>%
ungroup()
cat("SDTM DS domain created:\n")SDTM DS domain created:
cat("Total records:", nrow(sdtm_ds), "\n")Total records: 85
cat("Unique subjects:", n_distinct(sdtm_ds$USUBJID), "\n\n")Unique subjects: 30
sdtm_ds %>%
filter(USUBJID == first(sdtm_ds$USUBJID)) %>%
print()# A tibble: 3 × 10
STUDYID DOMAIN USUBJID DSSEQ DSTERM DSDECOD DSCAT DSSCAT DSSTDTC EPOCH
<chr> <chr> <chr> <int> <chr> <chr> <chr> <chr> <chr> <chr>
1 CDISC01 DS CDISC01-001-001 1 INFOR… INFORM… PROT… INFOR… 2024-0… SCRE…
2 CDISC01 DS CDISC01-001-001 2 RANDO… RANDOM… PROT… RANDO… 2024-0… SCRE…
3 CDISC01 DS CDISC01-001-001 3 WITHD… WITHDR… DISP… TREAT… 2024-0… TREA…
7.3 Step 3: Validate DS Domain
cat("=== DS Domain Validation ===\n\n")=== DS Domain Validation ===
# Check 1: Every subject has at least one record
cat("Subjects with DS records:", n_distinct(sdtm_ds$USUBJID), "\n")Subjects with DS records: 30
# Check 2: Every subject has informed consent
consent_check <- sdtm_ds %>%
filter(DSDECOD == "INFORMED CONSENT OBTAINED") %>%
n_distinct(.$USUBJID)
cat("Subjects with informed consent:", consent_check, "\n\n")Subjects with informed consent: 30
# Check 3: Disposition summary
cat("Treatment Disposition Summary:\n")Treatment Disposition Summary:
sdtm_ds %>%
filter(DSCAT == "DISPOSITION EVENT", DSSCAT == "TREATMENT") %>%
count(DSDECOD, name = "Count") %>%
mutate(Percent = round(100 * Count / sum(Count), 1)) %>%
arrange(desc(Count))# A tibble: 4 × 3
DSDECOD Count Percent
<chr> <int> <dbl>
1 COMPLETED 19 76
2 WITHDRAWAL BY SUBJECT 3 12
3 LOST TO FOLLOW-UP 2 8
4 ADVERSE EVENT 1 4
8 Subject Disposition Flow
This is one of the most important outputs in a clinical study report:
# Create a CONSORT-style flow summary
cat("=== SUBJECT DISPOSITION FLOW ===\n\n")=== SUBJECT DISPOSITION FLOW ===
n_consented <- n_distinct(sdtm_ds$USUBJID)
cat("Subjects consented: ", n_consented, "\n")Subjects consented: 30
n_screened_out <- sdtm_ds %>%
filter(DSDECOD == "SCREEN FAILURE") %>%
n_distinct(.$USUBJID)
cat(" Screen failures: ", n_screened_out, "\n") Screen failures: 5
# Screen failure reasons
if (n_screened_out > 0) {
sdtm_ds %>%
filter(DSDECOD == "SCREEN FAILURE") %>%
count(DSTERM, name = "N") %>%
arrange(desc(N)) %>%
mutate(line = paste0(" - ", DSTERM, ": ", N)) %>%
pull(line) %>%
cat(sep = "\n")
cat("\n")
} - EXCLUSION CRITERIA MET: 3
- DID NOT MEET INCLUSION CRITERIA: 2
n_randomized <- sdtm_ds %>%
filter(DSDECOD == "RANDOMIZED") %>%
n_distinct(.$USUBJID)
cat("Subjects randomized: ", n_randomized, "\n")Subjects randomized: 25
n_completed <- sdtm_ds %>%
filter(DSCAT == "DISPOSITION EVENT", DSSCAT == "TREATMENT", DSDECOD == "COMPLETED") %>%
n_distinct(.$USUBJID)
cat(" Completed treatment: ", n_completed, "\n") Completed treatment: 19
n_discontinued <- n_randomized - n_completed
cat(" Discontinued treatment: ", n_discontinued, "\n") Discontinued treatment: 6
# Discontinuation reasons
if (n_discontinued > 0) {
sdtm_ds %>%
filter(DSCAT == "DISPOSITION EVENT", DSSCAT == "TREATMENT", DSDECOD != "COMPLETED") %>%
count(DSDECOD, name = "N") %>%
arrange(desc(N)) %>%
mutate(line = paste0(" - ", DSDECOD, ": ", N)) %>%
pull(line) %>%
cat(sep = "\n")
cat("\n")
} - WITHDRAWAL BY SUBJECT: 3
- LOST TO FOLLOW-UP: 2
- ADVERSE EVENT: 1
cat("\nCompletion rate:", round(100 * n_completed / n_randomized, 1), "%\n")
Completion rate: 76 %
9 Trial Design Domains Overview
9.1 What Are Trial Design Domains?
Trial Design domains describe the structure of the study itself, not individual subject data. Think of them as the blueprint of the clinical trial.
| Domain | Name | Purpose |
|---|---|---|
| TA | Trial Arms | Describes each treatment arm and its sequence of elements |
| TE | Trial Elements | Describes the building blocks of each arm (e.g., washout, treatment) |
| TV | Trial Visits | Lists all planned visits and their timing windows |
| TI | Trial Inclusion/Exclusion | Documents the inclusion/exclusion criteria |
| TS | Trial Summary | Key parameters about the study (phase, indication, design) |
Trial Design domains are required for FDA submissions. They provide the context for understanding the subject-level data. An FDA reviewer needs to know:
- What arms existed? (TA)
- What was the study schedule? (TV)
- What were the eligibility criteria? (TI)
- What type of study was this? (TS)
9.2 TA - Trial Arms
# Create a Trial Arms (TA) domain example
ta <- tibble::tribble(
~STUDYID, ~DOMAIN, ~ARMCD, ~ARM, ~TAESSION, ~TATRANS, ~EPOCH, ~ELEMENT,
"CDISC01", "TA", "PBO", "Placebo", 1L, "No criteria", "SCREENING", "Screen",
"CDISC01", "TA", "PBO", "Placebo", 2L, "Randomized", "TREATMENT", "Placebo",
"CDISC01", "TA", "PBO", "Placebo", 3L, "End treatment","FOLLOW-UP", "Follow-up",
"CDISC01", "TA", "ACT10", "Active 10mg", 1L, "No criteria", "SCREENING", "Screen",
"CDISC01", "TA", "ACT10", "Active 10mg", 2L, "Randomized", "TREATMENT", "Active 10mg",
"CDISC01", "TA", "ACT10", "Active 10mg", 3L, "End treatment","FOLLOW-UP", "Follow-up",
"CDISC01", "TA", "ACT20", "Active 20mg", 1L, "No criteria", "SCREENING", "Screen",
"CDISC01", "TA", "ACT20", "Active 20mg", 2L, "Randomized", "TREATMENT", "Active 20mg",
"CDISC01", "TA", "ACT20", "Active 20mg", 3L, "End treatment","FOLLOW-UP", "Follow-up"
)
cat("Trial Arms (TA):\n")Trial Arms (TA):
print(ta)# A tibble: 9 × 8
STUDYID DOMAIN ARMCD ARM TAESSION TATRANS EPOCH ELEMENT
<chr> <chr> <chr> <chr> <int> <chr> <chr> <chr>
1 CDISC01 TA PBO Placebo 1 No criteria SCREENING Screen
2 CDISC01 TA PBO Placebo 2 Randomized TREATMENT Placebo
3 CDISC01 TA PBO Placebo 3 End treatment FOLLOW-UP Follow-up
4 CDISC01 TA ACT10 Active 10mg 1 No criteria SCREENING Screen
5 CDISC01 TA ACT10 Active 10mg 2 Randomized TREATMENT Active 10mg
6 CDISC01 TA ACT10 Active 10mg 3 End treatment FOLLOW-UP Follow-up
7 CDISC01 TA ACT20 Active 20mg 1 No criteria SCREENING Screen
8 CDISC01 TA ACT20 Active 20mg 2 Randomized TREATMENT Active 20mg
9 CDISC01 TA ACT20 Active 20mg 3 End treatment FOLLOW-UP Follow-up
9.3 TV - Trial Visits
# Create a Trial Visits (TV) domain example
tv <- tibble::tribble(
~STUDYID, ~DOMAIN, ~VISITNUM, ~VISIT, ~TVSTRL, ~TVENRL,
"CDISC01", "TV", -1, "SCREENING", "Day -21", "Day -1",
"CDISC01", "TV", 1, "BASELINE", "Day 1", "Day 1",
"CDISC01", "TV", 2, "WEEK 2", "Day 8", "Day 18",
"CDISC01", "TV", 3, "WEEK 4", "Day 22", "Day 32",
"CDISC01", "TV", 4, "WEEK 8", "Day 50", "Day 64",
"CDISC01", "TV", 5, "WEEK 12", "Day 78", "Day 92",
"CDISC01", "TV", 6, "END OF STUDY","Day 92", "Day 106"
)
cat("Trial Visits (TV) - Study Schedule:\n")Trial Visits (TV) - Study Schedule:
print(tv)# A tibble: 7 × 6
STUDYID DOMAIN VISITNUM VISIT TVSTRL TVENRL
<chr> <chr> <dbl> <chr> <chr> <chr>
1 CDISC01 TV -1 SCREENING Day -21 Day -1
2 CDISC01 TV 1 BASELINE Day 1 Day 1
3 CDISC01 TV 2 WEEK 2 Day 8 Day 18
4 CDISC01 TV 3 WEEK 4 Day 22 Day 32
5 CDISC01 TV 4 WEEK 8 Day 50 Day 64
6 CDISC01 TV 5 WEEK 12 Day 78 Day 92
7 CDISC01 TV 6 END OF STUDY Day 92 Day 106
Notice TVSTRL and TVENRL - these define the visit window. In practice:
- A “WEEK 2” visit might be acceptable if it occurs between Day 8 and Day 18
- Programming logic must map actual visit dates to planned visits based on these windows
- Visits outside windows may be flagged as unscheduled visits
9.4 TS - Trial Summary
# Create a Trial Summary (TS) domain example
ts <- tibble::tribble(
~STUDYID, ~DOMAIN, ~TSSEQ, ~TSPARMCD, ~TSPARM, ~TSVAL,
"CDISC01", "TS", 1L, "ADDON", "Added on to Existing Treatments", "N",
"CDISC01", "TS", 2L, "SSTDTC", "Study Start Date", "2024-01-01",
"CDISC01", "TS", 3L, "SENDTC", "Study End Date", "2024-12-31",
"CDISC01", "TS", 4L, "INDIC", "Trial Indication", "Type 2 Diabetes Mellitus",
"CDISC01", "TS", 5L, "TRT", "Investigational Therapy", "Study Drug XYZ",
"CDISC01", "TS", 6L, "TPHASE", "Trial Phase", "PHASE III",
"CDISC01", "TS", 7L, "STYPE", "Study Type", "INTERVENTIONAL",
"CDISC01", "TS", 8L, "RANDOM", "Trial is Randomized", "Y",
"CDISC01", "TS", 9L, "STOTEFFC", "Study to Evaluate", "EFFICACY AND SAFETY",
"CDISC01", "TS", 10L, "PCLAS", "Pharmacological Class", "SGLT2 INHIBITOR"
)
cat("Trial Summary (TS) - Key Study Parameters:\n")Trial Summary (TS) - Key Study Parameters:
print(ts)# A tibble: 10 × 6
STUDYID DOMAIN TSSEQ TSPARMCD TSPARM TSVAL
<chr> <chr> <int> <chr> <chr> <chr>
1 CDISC01 TS 1 ADDON Added on to Existing Treatments N
2 CDISC01 TS 2 SSTDTC Study Start Date 2024-01-01
3 CDISC01 TS 3 SENDTC Study End Date 2024-12-31
4 CDISC01 TS 4 INDIC Trial Indication Type 2 Diabete…
5 CDISC01 TS 5 TRT Investigational Therapy Study Drug XYZ
6 CDISC01 TS 6 TPHASE Trial Phase PHASE III
7 CDISC01 TS 7 STYPE Study Type INTERVENTIONAL
8 CDISC01 TS 8 RANDOM Trial is Randomized Y
9 CDISC01 TS 9 STOTEFFC Study to Evaluate EFFICACY AND S…
10 CDISC01 TS 10 PCLAS Pharmacological Class SGLT2 INHIBITOR
The TS domain uses a parameter-value structure where each row is one study-level parameter. Common parameters include:
- TPHASE - Phase I, II, III, IV
- INDIC - What disease is being studied
- RANDOM - Is this a randomized trial
- STYPE - Interventional vs. Observational
- REGID - Regulatory registration number (e.g., IND number)
The full list of valid TS parameters is in the CDISC CT Package.
10 How DS Feeds Into ADSL
The DS domain is a primary source for the ADaM ADSL (Subject-Level Analysis Dataset). Here’s the connection:
# Preview: Deriving ADSL disposition variables from DS
adsl_disp <- raw_disp %>%
mutate(
# Core ADSL variables derived from DS logic
RANDFL = if_else(SCREEN_RESULT == "PASSED", "Y", "N"),
RANDDT = RAND_DATE,
# Treatment completion
EOSSTT = case_when(
SCREEN_RESULT == "FAILED" ~ "SCREEN FAILURE",
TRT_RESULT == "COMPLETED" ~ "COMPLETED",
TRUE ~ "DISCONTINUED"
),
# Reason for discontinuation
DCSREAS = case_when(
EOSSTT == "COMPLETED" ~ NA_character_,
EOSSTT == "SCREEN FAILURE" ~ SCREEN_REASON,
TRUE ~ TRT_RESULT
),
# Safety population flag (received at least one dose)
SAFFL = if_else(!is.na(FIRST_DOSE), "Y", "N"),
# ITT population flag (randomized)
ITTFL = RANDFL,
# Treatment duration (days)
TRTDURD = if_else(
!is.na(FIRST_DOSE) & !is.na(TRT_END_DATE),
as.numeric(TRT_END_DATE - FIRST_DOSE) + 1,
NA_real_
)
)
cat("ADSL Disposition Variables Preview:\n")ADSL Disposition Variables Preview:
adsl_disp %>%
select(USUBJID, RANDFL, EOSSTT, DCSREAS, SAFFL, ITTFL, TRTDURD) %>%
head(10)# A tibble: 10 × 7
USUBJID RANDFL EOSSTT DCSREAS SAFFL ITTFL TRTDURD
<chr> <chr> <chr> <chr> <chr> <chr> <dbl>
1 CDISC01-001-001 Y DISCONTINUED WITHDRAWAL BY SUBJ… Y Y 56
2 CDISC01-001-002 Y COMPLETED <NA> Y Y 85
3 CDISC01-001-003 Y COMPLETED <NA> Y Y 85
4 CDISC01-001-004 Y COMPLETED <NA> Y Y 85
5 CDISC01-001-005 Y COMPLETED <NA> Y Y 85
6 CDISC01-001-006 Y COMPLETED <NA> Y Y 85
7 CDISC01-001-007 Y COMPLETED <NA> Y Y 85
8 CDISC01-001-008 N SCREEN FAILURE EXCLUSION CRITERIA… N N NA
9 CDISC01-001-009 Y DISCONTINUED ADVERSE EVENT Y Y 42
10 CDISC01-001-010 Y DISCONTINUED LOST TO FOLLOW-UP Y Y 61
# Population flags summary
cat("\n=== Analysis Population Summary ===\n")
=== Analysis Population Summary ===
cat("Screened:", nrow(adsl_disp), "\n")Screened: 30
cat("Randomized (ITT):", sum(adsl_disp$ITTFL == "Y"), "\n")Randomized (ITT): 25
cat("Safety Population:", sum(adsl_disp$SAFFL == "Y"), "\n\n")Safety Population: 25
cat("End of Study Status:\n")End of Study Status:
adsl_disp %>%
count(EOSSTT, name = "N") %>%
mutate(Percent = round(100 * N / sum(N), 1)) %>%
arrange(desc(N))# A tibble: 3 × 3
EOSSTT N Percent
<chr> <int> <dbl>
1 COMPLETED 19 63.3
2 DISCONTINUED 6 20
3 SCREEN FAILURE 5 16.7
| ADSL Variable | Source | Description |
|---|---|---|
| RANDFL | DS (RANDOMIZED record) | Randomized flag (Y/N) |
| RANDDT | DS (DSSTDTC where DSDECOD = RANDOMIZED) | Randomization date |
| EOSSTT | DS (treatment disposition) | End of study status |
| DCSREAS | DS (DSTERM for discontinuation) | Reason for discontinuation |
| SAFFL | EX (received at least one dose) | Safety population flag |
| ITTFL | DS (randomized) | ITT population flag |
| TRTDURD | Derived from EX dates | Treatment duration |
Understanding these mappings is essential for ADaM work in Week 3.
11 Deliverable Summary
Today you completed the following:
| Task | Status |
|---|---|
| Understood DS domain structure and purpose | ✓ Done |
| Built a DS domain with consent, randomization, and treatment disposition | ✓ Done |
| Explored EPOCH and study period concepts | ✓ Done |
| Surveyed Trial Design domains (TA, TV, TS) | ✓ Done |
| Created subject disposition flow (CONSORT-style) | ✓ Done |
| Previewed how DS feeds into ADSL | ✓ Done |
12 Key Takeaways
- DS is an Events class domain - Each record is a milestone or disposition event
- Multiple records per subject - Consent, randomization, treatment disposition, study disposition
- DSCAT/DSSCAT distinguish event types - Milestones vs. disposition decisions
- EPOCH defines study periods - Screening, Treatment, Follow-up
- Trial Design domains are required - TA, TE, TV, TI, TS describe the study blueprint
- DS drives ADSL - Randomization, completion, discontinuation all come from DS
13 Resources
- CDISC SDTM Implementation Guide - DS Domain - Official DS specification
- Trial Design Domains Guide - TA, TE, TV, TI, TS specifications
- CONSORT Statement - Reporting standards for clinical trials
- Admiral ADSL Vignette - Building ADSL from DS
- Pharmaverse.org - R packages for clinical data
14 What’s Next?
In Day 12, we will focus on Data Cuts with datacutr:
- What clinical data cuts are and why they matter for interim analyses
- Using the
datacutrpackage to apply cutoff dates across domains - Handling ongoing AEs, partial dates, and records at the cutoff boundary
- Patient-level vs. record-level cut logic