library(tidyverse)
library(lubridate)
library(sdtm.oak)Clinical Data Journey (Phase 2): Metadata-Assisted SDTM DM Programming in R using sdtm.oak
Objective
In the previous article, we transformed raw demographics data into an SDTM DM domain using manual R programming.
While manual programming is useful for understanding SDTM concepts, clinical programming workflows often use metadata specifications to improve consistency, traceability, and maintainability.
In this article, we will introduce a metadata-assisted approach where SDTM variable definitions are stored separately from programming logic. We will also explore how sdtm.oak utilities can support SDTM development.
Load Required Packages
Import the Raw Data
raw_dm <- read_csv("RAW_Demo.csv",show_col_types = FALSE)Review the Raw Data
head(raw_dm)# A tibble: 6 × 15
STUDYID SITEID Patient_ID Age Gender RACE ETHNICITY HEIGHT_CM WEIGHT_KG
<chr> <chr> <chr> <dbl> <chr> <chr> <chr> <dbl> <dbl>
1 ONCO-2026 SITE004 SUBJ0001 62 Male WHITE NOT HISP… 167. 78.2
2 ONCO-2026 SITE014 SUBJ0002 84 Female BLACK… NOT HISP… 164. 100.
3 ONCO-2026 SITE010 SUBJ0003 71 Female WHITE NOT HISP… 168 79.6
4 ONCO-2026 SITE007 SUBJ0004 49 Female ASIAN NOT HISP… 174 79.8
5 ONCO-2026 SITE002 SUBJ0005 45 Female ASIAN NOT HISP… 154. 73.2
6 ONCO-2026 SITE002 SUBJ0006 64 Female BLACK… NOT HISP… 145 92.4
# ℹ 6 more variables: BMI <dbl>, BIRTH_DATE <chr>, COUNTRY <chr>,
# RAND_DATE <chr>, ARM <chr>, TREATMENT_START_DATE <chr>
Import Metadata Specification
dm_metadata <- read_csv("DM_Metadata.csv",show_col_types = FALSE)Review the Metadata
head(dm_metadata)# A tibble: 6 × 7
SDTM_VAR RAW_VAR LABEL TYPE LENGTH ORIGIN ROLE
<chr> <chr> <chr> <chr> <dbl> <chr> <chr>
1 STUDYID STUDYID Study Identifier char… 20 CRF Iden…
2 DOMAIN <NA> Domain Abbreviat… char… 2 Assig… Iden…
3 USUBJID STUDYID+SITEID+Patient_ID Unique Subject I… char… 40 Deriv… Iden…
4 SUBJID Patient_ID Subject Identifi… char… 20 CRF Iden…
5 SITEID SITEID Study Site Ident… char… 10 CRF Iden…
6 AGE Age Age nume… 8 CRF Topic
The metadata file acts as a specification layer between raw data and the final SDTM dataset. It documents SDTM variables, source variables, labels, data types, and derivation information.
Instead of embedding dataset specifications directly in the R script, metadata stores them separately, making the workflow easier to understand, maintain, and reuse.
Use Metadata to Define SDTM Structure
Extract SDTM variables:
sdtm_vars <- dm_metadata$SDTM_VAR
sdtm_vars [1] "STUDYID" "DOMAIN" "USUBJID" "SUBJID" "SITEID" "AGE"
[7] "SEX" "RACE" "ETHNIC" "BRTHDTC" "COUNTRY" "ARMCD"
[13] "ARM" "RFXSTDTC"
The metadata now defines the expected SDTM variable structure, reducing the need to hard-code variable names in the R script.
Perform SDTM derivations
DM <- raw_dm %>%
mutate(
DOMAIN = "DM",
USUBJID = paste(
STUDYID,
SITEID,
Patient_ID,
sep="-"
),
SUBJID = Patient_ID,
SEX = case_when(
Gender=="Male" ~ "M",
Gender=="Female" ~ "F",
TRUE ~ NA_character_
),
AGE = Age,
ETHNIC = ETHNICITY,
ARMCD = case_when(
ARM=="ARM A" ~ "A",
ARM=="ARM B" ~ "B",
TRUE ~ NA_character_
),
BRTHDTC =
format(
dmy(BIRTH_DATE),
"%Y-%m-%d"
),
RFXSTDTC =
format(
dmy(TREATMENT_START_DATE),
"%Y-%m-%d"
)
)Apply Metadata-Defined Structure
Instead of manually selecting variables:
DM <- DM %>%
select(all_of(sdtm_vars))The metadata now controls the final SDTM variable structure.
Introducing sdtm.oak
Add OAK Traceability Identifiers
sdtm.oak provides reusable functions that support common SDTM programming activities.
One useful utility function is generate_oak_id_vars(), which adds standardized traceability variables to the raw dataset.
Generate OAK identifiers
DM_oak <- generate_oak_id_vars(
raw_dat = raw_dm,
pat_var = "Patient_ID",
raw_src = "RAW_Demo"
)
names(DM_oak) [1] "oak_id" "raw_source" "patient_number"
[4] "STUDYID" "SITEID" "Patient_ID"
[7] "Age" "Gender" "RACE"
[10] "ETHNICITY" "HEIGHT_CM" "WEIGHT_KG"
[13] "BMI" "BIRTH_DATE" "COUNTRY"
[16] "RAND_DATE" "ARM" "TREATMENT_START_DATE"
The function adds three traceability variables:
oak_id: A unique identifier generated by OAK for traceability.
raw_source: Captures the origin of the raw dataset.
patient_number: Provides a standardized patient-level identifier.
These variables support reproducibility and data lineage during SDTM development.
Validation
Check that all metadata-defined variables are present
The metadata specification defines the expected SDTM DM variables. This check verifies that every variable listed in the metadata exists in the final SDTM dataset.
setdiff(
dm_metadata$SDTM_VAR,
names(DM)
)character(0)
Check for one record per subject
The SDTM DM domain should contain one record for each unique subject. This check verifies that no duplicate USUBJID values exist.
DM %>%
count(USUBJID) %>%
filter(n > 1)# A tibble: 0 × 2
# ℹ 2 variables: USUBJID <chr>, n <int>
No duplicate USUBJID values were found, confirming that the dataset contains one record per subject.
Compare Phase 1 and Phase 2
| Phase 1 | Phase 2 |
|---|---|
| Variables defined inside R code | Variable structure stored in metadata |
| Manual variable selection | Metadata-controlled variable selection |
| Programming logic only | Metadata + programming logic |
| No traceability layer | OAK traceability variables |
Conclusion
In this phase, we introduced a metadata-assisted approach to SDTM programming by separating dataset specifications from transformation logic. We also explored how sdtm.oak can improve traceability through standardized identifier utilities. While study-specific derivations still require programming logic, this approach improves organization and lays the foundation for more advanced metadata-driven workflows.
In the next phase, we’ll introduce the metacore package and learn how structured SDTM specification workbooks can be converted into metadata objects, providing the foundation for standardized and scalable SDTM programming.