30-Day Course
Clinical SAS → R
Day 25 of 30·SDTM Implementation

SDTM Case Study: Raw to Submission-Ready Domain

An end-to-end build tying together the SDTM module.

Learning objectives

  • 1Assemble a complete domain from raw data through to a valid .xpt file.
  • 2Combine dplyr, admiral, and xportr in a single, reviewable script.
  • 3Apply controlled terminology, proper sequencing, and metadata.
  • 4Perform automated conformance-style checks and double-programming validation before export.

Key SAS® → R comparison

TaskSAS®R
Domain pipelineLarge macro library per domainOne clear dplyr + admiral + xportr script
Metadata handlingPROC CONTENTS + manual labelsxportr_label() + xportr_type() from spec
ConformanceOpenCDISC / Pinnacle 21sdtmchecks + custom rules + xportr
Dataset comparePROC COMPAREdiffdf (structured report)

Code examples

Full DM build pipeline
SAS®
/* %build_dm; %qc_dm; %export_dm; */
R
dm <- raw_dm |>
  transmute(
    STUDYID = "ABC",
    DOMAIN  = "DM",
    USUBJID = str_c("ABC", siteid, subjid, sep = "-"),
    ...
  ) |>
  derive_vars_dt(new_vars_prefix = "RF", ...) |>
  arrange(USUBJID, SITEID, SUBJID) |>
  mutate(DMSEQ = row_number())

dm |>
  xportr_type(spec, domain = "DM") |>
  xportr_label(spec, domain = "DM") |>
  xportr_write("dm.xpt", label = "Demographics")

Note: xportr enforces the exact data types and labels from your specification document, dramatically reducing define.xml mismatches.

Controlled terminology application
SAS®
sex = put(rawsex, $sex.);
        race = put(rawrace, $race.);
R
dm <- dm |>
  mutate(
    SEX  = recode(SEX, "1"="M", "2"="F", .default = "U"),
    RACE = case_when(
      RACE == "WHITE" ~ "WHITE",
      RACE == "BLACK" ~ "BLACK OR AFRICAN AMERICAN",
      ...
    )
  )

Note: Store your controlled terminology in a small metadata table or YAML and join/recode — far easier to maintain and audit than dozens of formats.

Pre-export validation + double programming
SAS®
proc compare base=dm_prod compare=dm_qc;
  id usubjid;
run;
R
# In production script
dm |> xportr_write(...)

# In independent QC script (different programmer)
qc_dm <- ...  # same logic, independently coded

diffdf(dm, qc_dm, keys = "USUBJID")   # zero differences = pass

Note: diffdf gives you a clean, machine-readable discrepancy list that can be automatically checked in CI.

Explanation

The real power of the modern open-source stack appears when you assemble the full pipeline in one script: raw data → mapping + CT → date derivations (admiral) → sequencing → metadata application (xportr) → transport file. The result is dramatically shorter and more transparent than the equivalent SAS macro suite.

Validation remains essential. You still double-program key domains, but the comparison tool (diffdf) is more structured than PROC COMPARE output, and you can add automated sdtmchecks-style rules directly in the same R session.

The combination of readable code + automated metadata + repeatable environment (renv) + structured QC gives you a credible, regulator-friendly SDTM process without the traditional SAS macro maintenance burden.

Practice exercise

Using the supplied raw data, build one full SDTM domain (DM or a findings domain) from raw through to a validated .xpt. Include an independent QC program by a second 'programmer', run diffdf until it reports zero differences, and generate the xpt with correct labels and types via xportr.