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

Building an SDTM DM Domain with dplyr

Mapping raw demographics to the SDTM DM structure.

Learning objectives

  • 1Map raw demographics to SDTM DM variables using controlled terminology.
  • 2Apply required identifiers (STUDYID, USUBJID, DOMAIN) correctly.
  • 3Derive RFSTDTC, RFENDTC and age in the standard SDTM form.
  • 4Order variables and attach labels exactly to the SDTM IG specification.

Key SAS® → R comparison

TaskSAS®R
Build domainDATA step + keep/retain orderdplyr::transmute() in target order
USUBJID constructionCATX(studyid, site, subj)str_c() or paste with sep
Date variablesISO or numeric dates + formatscharacter ISO 8601 strings (no formats)
Controlled termsPROC FORMAT + put()case_when() or metadata join

Code examples

Core DM mapping with identifiers
SAS®
data dm (keep=studyid domain usubjid siteid ...);
  set raw_dm;
  studyid = 'ABC';
  domain  = 'DM';
  usubjid = catx('-', studyid, siteid, subjid);
  ...
run;
R
dm <- raw_dm |>
  transmute(
    STUDYID = "ABC",
    DOMAIN  = "DM",
    USUBJID = str_c("ABC", siteid, subjid, sep = "-"),
    SITEID  = siteid,
    SUBJID  = subjid
  )

Note: transmute() is the R equivalent of a keep list in the DATA step — it guarantees the exact variable set and order you need for the domain.

Deriving RFSTDTC / RFENDTC and AGE
SAS®
rfstdtc = put(min(exstdt), yymmdd10.);
        age = floor((rfstdt - birthdt)/365.25);
R
dm <- dm |>
  derive_vars_dt(
    new_vars_prefix = "RF",
    dtc = RFSTDTC  # from first exposure or enrollment
  ) |>
  mutate(
    AGE = as.integer(floor(as.numeric(difftime(RFSTDTC, BRTHDTC, units="days")) / 365.25)),
    AGEU = "YEARS"
  )

Note: admiral has dedicated functions for the common RFSTDTC/RFENDTC derivations used across almost every SDTM domain.

Applying controlled terminology + labels
SAS®
sex = put(sex, $sex.);
        label age = 'Age';
        label ageu = 'Age Units';
R
dm <- dm |>
  mutate(
    SEX = case_when(SEX == "M" ~ "M", SEX == "F" ~ "F", TRUE ~ "U")
  ) |>
  xportr_label(spec)   # or manual labelled::var_label()

Note: xportr (or the labelled package) makes attaching the exact SDTM labels and formats mechanical and auditable.

Explanation

The Demographics (DM) domain is usually the first SDTM domain a programmer builds because almost every other domain references its identifiers and timing variables. The work is mostly deterministic mapping plus careful handling of dates and controlled terminology.

In R the combination of `transmute()` for structure + admiral helpers for dates + xportr for metadata produces very readable, self-documenting code. Every derivation is explicit, which makes double programming and regulatory review easier than deeply nested SAS macros.

The most common pitfalls are incorrect USUBJID construction, wrong variable order (which breaks define.xml), and non-ISO date formats. The R approach forces you to be explicit about each of these.

Practice exercise

Take a raw demographics file and produce a complete, labeled, correctly ordered DM domain containing at least STUDYID, DOMAIN, USUBJID, SITEID, SUBJID, RFSTDTC, RFENDTC, AGE, AGEU, SEX, RACE, ETHNIC, and ARM. Validate variable order and labels against the SDTM IG spec.