Clinical Data Journey (Phase 5): Moving Toward Metadata-Driven SDTM Programming using sdtm.oak and metacore

Objective

In the previous article, we explored how metacore metadata objects can be queried and used during SDTM dataset development. We retrieved dataset and variable metadata, compared programmed datasets against specifications, and used metadata as a validation layer. However, metadata-driven programming goes beyond validation. The next step is to integrate metadata with SDTM development workflows, where metadata can guide dataset creation, standardize common derivations, and improve traceability. In this article, we will explore how sdtm.oak, metacore, and metatools can work together to support a more metadata-driven approach for SDTM dataset development.

Load Required Packages

library(tidyverse)      #Data manipulation
library(lubridate)      #Date-time handling

library(metacore)       #Metadata management
library(sdtm.oak)       #SDTM development

library(haven)          #File export

Import raw data

raw_vs <- read_csv("RAW_VS.csv",show_col_types = FALSE)

Review the raw data:

head(raw_vs)
# A tibble: 6 × 9
  STUDYID   SITEID  Patient_ID VISIT    VISITNUM VS_DATE VSTEST VSORRES VSORRESU
  <chr>     <chr>   <chr>      <chr>       <dbl> <chr>   <chr>    <dbl> <chr>   
1 ONCO-2026 SITE001 SUBJ0001   SCREENI…        1 01-Jan… Systo…     120 mmHg    
2 ONCO-2026 SITE001 SUBJ0001   SCREENI…        1 01-Jan… Diast…      80 mmHg    
3 ONCO-2026 SITE001 SUBJ0001   SCREENI…        1 01-Jan… Heart…      72 beats/m…
4 ONCO-2026 SITE001 SUBJ0001   VISIT 1         2 15-Jan… Systo…     118 mmHg    
5 ONCO-2026 SITE001 SUBJ0001   VISIT 1         2 15-Jan… Diast…      79 mmHg    
6 ONCO-2026 SITE001 SUBJ0001   VISIT 1         2 15-Jan… Heart…      70 beats/m…

Load SDTM Metadata

The SDTM specification introduced in previous phases is converted into a metacore object.

spec_file <- system.file(
  "extdata",
  "SDTM_spec_CDISC_pilot.xlsx",
  package = "metacore"
)


meta <- spec_to_metacore(
  spec_file
)

Retrieve VS Metadata

vs_spec <- select_dataset(
  meta,
  "VS"
)

The metadata object now provides the expected structure of the VS domain.

Retrieve Metadata-Defined Variables

Instead of manually maintaining the SDTM variable list:

vs_vars <- vs_spec$ds_vars$variable

vs_vars
 [1] "STUDYID"  "DOMAIN"   "USUBJID"  "VSSEQ"    "VSTESTCD" "VSTEST"  
 [7] "VSPOS"    "VSORRES"  "VSORRESU" "VSSTRESC" "VSSTRESN" "VSSTRESU"
[13] "VSSTAT"   "VSLOC"    "VSBLFL"   "VISITNUM" "VISIT"    "VISITDY" 
[19] "EPOCH"    "VSDTC"    "VSDY"     "VSTPT"    "VSTPTNUM" "VSELTM"  
[25] "VSTPTREF"
attr(,"label")
[1] "Variable Name"

The variable structure is obtained directly from metadata. This allows the programming workflow to remain aligned with the specification when metadata changes.

Add OAK Traceability Variables

During SDTM development, maintaining traceability between raw data and SDTM datasets is important. sdtm.oak provides utilities to support standardized traceability.

raw_vs_oak <- generate_oak_id_vars(
  raw_dat = raw_vs,
  pat_var = "Patient_ID",
  raw_src = "RAW_VS"
)

Review generated identifiers:

head(
  raw_vs_oak %>%
  select(
    oak_id,
    raw_source,
    patient_number
  )
)
# A tibble: 6 × 3
  oak_id raw_source patient_number
   <int> <chr>      <chr>         
1      1 RAW_VS     SUBJ0001      
2      2 RAW_VS     SUBJ0001      
3      3 RAW_VS     SUBJ0001      
4      4 RAW_VS     SUBJ0001      
5      5 RAW_VS     SUBJ0001      
6      6 RAW_VS     SUBJ0001      

These variables provide additional lineage information during dataset development.

Derive the SDTM VS Dataset

Metadata defines what the dataset should contain. Programming logic defines how values are derived.

VS <- raw_vs_oak %>%
  mutate(
    DOMAIN = "VS",
    USUBJID = paste(STUDYID, SITEID, Patient_ID, sep = "-"),
    VSTESTCD = case_when(
      VSTEST == "Systolic Blood Pressure" ~ "SYSBP",
      VSTEST == "Diastolic Blood Pressure" ~ "DIABP",
      VSTEST == "Heart Rate" ~ "HR",
       TRUE ~ NA_character_),
      VSDTC = format(dmy(VS_DATE),"%Y-%m-%d")
    )
Apply Metadata-Based Structure

Use metadata to maintain SDTM variable order:

VS <- VS %>%
select(
any_of(vs_vars)
)

The final dataset structure now follows the metadata specification.

Generate SDTM Sequence Variables

SDTM domains with multiple records per subject require sequence variables. sdtm.oak provides reusable utilities for common SDTM derivations. Example:

VS <- VS %>%
  arrange(
    USUBJID,
    VISITNUM,
    VSTESTCD
  )

VS <- derive_seq(
  tgt_dat = VS,
  tgt_var = "VSSEQ",
  rec_vars = c("VSTESTCD","VSDTC"),
  sbj_vars = "USUBJID"
)

VSSEQ provides a unique sequence number for each record within a subject, allowing individual observations to be uniquely identified.

Validate Against Metadata

Check expected variables:

setdiff(
vs_vars,
names(VS)
)
 [1] "VSPOS"    "VSSTRESC" "VSSTRESN" "VSSTRESU" "VSSTAT"   "VSLOC"   
 [7] "VSBLFL"   "VISITDY"  "EPOCH"    "VSDY"     "VSTPT"    "VSTPTNUM"
[13] "VSELTM"   "VSTPTREF"

The comparison identifies variables defined in the metadata specification that have not yet been derived in the current SDTM programming step. These variables may require additional derivations or may remain blank depending on study requirements.

Check unexpected variables:

setdiff(
names(VS),
vs_vars
)
character(0)

No unexpected variables remain after applying the metadata-defined structure.

Export SDTM Dataset

Clinical submissions commonly require SAS transport files. Using haven:

write_xpt(
VS,
"VS.xpt"
)

The SDTM dataset can then be prepared for submission workflows.

Phase 4 vs Phase 5

Phase 4 Phase 5
Query metadata Use metadata during development
Validate SDTM structure Build SDTM workflow
Explore metacore objects Integrate metacore + metatools + sdtm.oak
Metadata as reference Metadata as development framework

Key Takeaways

In this phase, we moved from metadata-assisted programming toward a metadata-driven workflow. We learned how to:

- Use metacore metadata objects during SDTM development

- Retrieve SDTM structures programmatically

- Use sdtm.oak utilities for traceability and common SDTM tasks

- Maintain alignment between metadata and programmed datasets

- Prepare datasets for downstream submission workflows

In the next phase, we will move from SDTM datasets to ADaM datasets and explore how admiral supports analysis dataset development.