SDTM to ADaM: A Practical Mapping Walkthrough

By the TECHWORKSLAB clinical programming team

SDTM tells you what happened to a subject. ADaM tells you what it means for the analysis. The distance between those two statements is where most of the effort in clinical SAS programming actually lives, and it is a distance that has to be crossed with derivations a reviewer can follow backward, not just code that produces the right numbers.

This walkthrough is not a substitute for the ADaM Implementation Guide. It is the sequence of decisions a programmer works through when a mapping spec lands, from subject-level scaffolding to the flags that make a BDS dataset analysis-ready.

Start with ADSL, because everything else depends on it

ADSL is the subject-level backbone. Every other ADaM dataset merges against it, borrows treatment and population variables from it, and inherits any error in it. Getting ADSL right before touching a single BDS dataset is not a stylistic preference, it is what keeps twenty downstream datasets from needing the same fix twenty times.

The core work in ADSL is deriving the variables that describe who a subject is for analysis purposes: treatment assignment as randomized and as treated, population flags such as SAFFL and ITTFL, and the dates that anchor everything else, including treatment start, treatment end, and study day zero. Population flags in particular deserve scrutiny, because they encode inclusion and exclusion logic straight out of the SAP, and a flag derived slightly wrong silently changes the denominator for every subsequent analysis.

BDS versus OCCDS: picking the right shape

Once ADSL is stable, the next decision is structural. Basic Data Structure, one record per subject per parameter per analysis timepoint, fits most continuous and categorical measurements: labs, vital signs, questionnaire scores. Occurrence Data Structure fits data that is fundamentally about events happening or not happening: adverse events, concomitant medications, medical history.

The two structures answer different questions. BDS asks "what was the value of this parameter at this point," which is why it carries AVAL, AVALC, PARAM, and PARAMCD as its backbone. OCCDS asks "did this occurrence happen, and what were its attributes," which is why it looks closer to the source SDTM domain with analysis variables layered on top rather than a value-per-timepoint model imposed on it. Picking BDS for occurrence data, or trying to force AE data into a parameter model, tends to produce a dataset that technically validates but never quite answers the question the TLF needs.

Derivation and traceability

Every analysis variable in ADaM should trace to something: a source variable, a set of source records, or an explicit derivation rule applied to both. This is not a bureaucratic nicety, it is what lets a reviewer or an internal auditor reconstruct how a p-value came to exist without re-running the entire pipeline and hoping the answer matches.

In practice, traceability comes from a small set of habits. Keep DTYPE populated when a record is derived rather than copied, so it is obvious at a glance which rows came from where. Populate SRCDOM, SRCVAR, and SRCSEQ, or their study-specific equivalents, so a derived record points back to the SDTM domain, variable, and sequence number it came from. Avoid collapsing a derivation into a single opaque line of code when it can instead be built as a named step with a clear input and output, even in base SAS. The goal is that someone who did not write the program can still answer "where did this number come from" in a few minutes.

A number a reviewer can trace back to its source in five minutes causes far fewer questions than one that takes an afternoon to reconstruct.

Parameters and analysis flags

In a BDS dataset, PARAM and PARAMCD define what is being measured, and they should be stable and unambiguous across the whole dataset, not reused for slightly different things in different analyses. Analysis flags layered on top, such as ANL01FL for the record selected for a given analysis, or a change-from-baseline flag pair, encode the analysis logic itself. Getting these flags wrong is a quieter failure mode than a wrong AVAL, because the dataset still looks reasonable, it just feeds the wrong subset of records into a table.

BASE and CHG, when relevant, should be derived with a documented, consistent rule for which record counts as baseline, applied the same way across every parameter that uses it. Inconsistent baseline logic between parameters is a common source of numbers that do not reconcile between two outputs that should agree.

Where define.xml fits

Define.xml is the place all of this gets written down formally. For each ADaM dataset, it documents the variables, their origin, and, critically, the derivation or comment text explaining how a derived variable was computed. A well-written define.xml does not just list "Derived" as the origin, it gives the actual rule, in terms specific enough that a reviewer could apply it independently to the source data and get the same result.

Treating define.xml as documentation written after the fact tends to produce vague, unhelpful derivation text. Treating it as part of the mapping spec from the start, updated as the mapping evolves, keeps the document and the code from drifting apart, and it means the traceability work described above has somewhere concrete to land.

Putting it together

None of these pieces stand alone. A clean ADSL makes BDS and OCCDS derivations simpler. Choosing the right structure for each dataset keeps the analysis logic legible. Consistent traceability conventions turn a pile of derivations into something a reviewer can actually audit. And define.xml turns all of that into a document that outlives the programmer who wrote the code. Skipping any one of these steps does not usually break the immediate deliverable, it shows up later, in the query that takes a full day to answer instead of five minutes.

If your team is refining how SDTM to ADaM mapping is specified, derived, and documented, our write-up on treating define.xml as a contract goes deeper into the documentation side of this same problem.

Back to Insights