Statistical programmer in pharma: SDTM, ADaM and TLFs explained
In this article (9 sections)
- The pipeline you work inside
- The artefact: an ADaM derivation spec and its code
- Double programming and why it exists
- A sample fortnight
- Clean validation, CDISC conformance and study timelines
- Biostatisticians, data managers and medical writers
- Confused with biostatistician
- SAS macros, Define-XML and R in pharma postings
- Listings, then SDTM, then ADaM
Related programme: the Data Science course (6 months; ₹1,30,000 (inclusive of GST) · EMI from ₹5,000/month).
A statistical programmer in pharma turns locked clinical trial data into standardised datasets and the tables, listings and figures (TLFs) that go into a clinical study report and a regulatory submission. The work follows written specifications closely: map raw data to CDISC SDTM, derive analysis datasets in ADaM, then produce outputs that match the statistical analysis plan, usually with a second programmer independently reproducing each key result.
The pipeline you work inside
EDC raw data -> SDTM domains (DM, AE, EX, LB, VS ...) -> ADaM (ADSL, ADAE, ADLB ...) -> TLFs
collected standardised tabulation analysis-ready in the reportSDTM organises what was collected into standard domains. ADaM adds what the analysis needs: derived flags, analysis periods, baseline values, treatment variables. CDISC's standards page is the primary reference for what each standard covers and notes that regulators such as the US FDA and Japan's PMDA require SDTM, ADaM and Define-XML for submissions. Define-XML documents the datasets for reviewers.
The artefact: an ADaM derivation spec and its code
Programmers do not decide derivations; the specification does. A few rows for an adverse events analysis dataset might read:
| Variable | Label | Derivation (from spec) |
|---|---|---|
| TRTSDT | Date of first exposure | From ADSL; first dose date from EX |
| TRTEDT | Date of last exposure | From ADSL; last dose date from EX |
| ASTDT | Analysis start date | AESTDTC converted to a date |
| TRTEMFL | Treatment-emergent flag | "Y" if ASTDT is on or after TRTSDT and no later than TRTEDT + 30 days; blank otherwise |
| AOCCFL | First occurrence flag | "Y" on the first treatment-emergent record per subject |
The 30-day window is study-specific and hypothetical here. The code below applies it to synthetic records, in pandas rather than SAS so it runs anywhere.
import pandas as pd
# Synthetic ADSL (one row per subject) and AE records
adsl = pd.DataFrame({
"USUBJID": ["S-001", "S-002", "S-003"],
"TRTSDT": pd.to_datetime(["2026-01-10", "2026-01-12", "2026-01-15"]),
"TRTEDT": pd.to_datetime(["2026-03-10", "2026-02-20", "2026-03-15"]),
})
ae = pd.DataFrame({
"USUBJID": ["S-001", "S-001", "S-002", "S-002", "S-003"],
"AETERM": ["Headache", "Nausea", "Rash", "Fatigue", "Dizziness"],
"AESTDTC": ["2026-01-05", "2026-02-01", "2026-03-15", "2026-03-30", "2026-01-15"],
})
adae = ae.merge(adsl, on="USUBJID", how="left")
adae["ASTDT"] = pd.to_datetime(adae["AESTDTC"])
window_end = adae["TRTEDT"] + pd.Timedelta(days=30)
emergent = (adae["ASTDT"] >= adae["TRTSDT"]) & (adae["ASTDT"] <= window_end)
adae["TRTEMFL"] = emergent.map({True: "Y", False: ""})
adae = adae.sort_values(["USUBJID", "ASTDT"])
first = adae[adae["TRTEMFL"] == "Y"].groupby("USUBJID").head(1).index
adae["AOCCFL"] = ""
adae.loc[first, "AOCCFL"] = "Y"
print(adae[["USUBJID", "AETERM", "ASTDT", "TRTEMFL", "AOCCFL"]].to_string(index=False))
print("Subjects with a TEAE:", adae.loc[adae["TRTEMFL"] == "Y", "USUBJID"].nunique())Walk the records by hand. S-001's headache started before first dose, so it is not treatment-emergent; the nausea is. S-002's rash on 15 March falls within 30 days of the last dose on 20 February (the window ends 22 March), but the fatigue on 30 March falls outside. S-003's dizziness began on the day of first dose, which the "on or after" condition counts. So three subjects have a treatment-emergent event.
Real derivations add the awkward parts: partial dates like "2026-02" that need imputation rules, events that start before dosing but worsen after, and missing last-dose dates for ongoing subjects. Each is answered in the spec, not improvised by the programmer.
Double programming and why it exists
For key outputs, a second programmer writes independent code from the same specification without seeing the first programmer's work. The two results are compared electronically, and any difference is investigated until both agree or the spec is clarified. It is slow by design. A difference often means the specification was ambiguous, and catching that before submission is the whole point.
A sample fortnight
- Build ADAE for a phase II study from a spec that arrived yesterday.
- Validate a colleague's demographics table by independent programming; raise two discrepancies, one of which turns out to be a spec gap.
- Update outputs after the statistician changes a population definition in the SAP.
- Respond to a reviewer's comment that a listing's sort order does not match the mock shell.
- Run the team's CDISC conformance checks and document explanations for any accepted issues.
Clean validation, CDISC conformance and study timelines
Correctness at first validation, turnaround against the study timeline, conformance of datasets to CDISC rules, quality of documentation and, later, how well you lead a study's programming.
Biostatisticians, data managers and medical writers
A programmer reports to a lead statistical programmer or programming manager. You work most closely with biostatisticians (who own the SAP and table shells), clinical data managers upstream (see the clinical data manager guide), medical writers who use your outputs and, at CROs, the sponsor's programming team.
Confused with biostatistician
The biostatistician decides what analysis to run and interprets it. The programmer implements it exactly as specified. The boundary blurs at senior levels, but a statistical programmer is not usually expected to choose methods.
SAS macros, Define-XML and R in pharma postings
- "Base SAS, SAS macros, PROC REPORT" — a SAS-first team.
- "SDTM/ADaM mapping, Define-XML" — dataset work, core to the role.
- "TLF generation, mock shells" — output programming.
- "R/Shiny, open-source submissions" — a team moving part of its work to R.
- "Lead programmer, study lead" — responsibility for timelines and a small team.
Listings, then SDTM, then ADaM
Month one: SOPs, standards training and validating simple listings. Month two: program SDTM domains under review. Month three: produce ADaM datasets or tables independently. Next come senior programmer, lead programmer, programming manager, or a move toward statistical or clinical data science. Derivations like the TEAE flag above reward exactly the pandas fluency and statistical grounding taught in the Data Science programme; CDISC specifics are then learned on the job.
Frequently asked questions
Is SAS still required for statistical programming jobs?
Many postings still ask for SAS because established submission workflows were built on it. R, and less often Python, appears increasingly in postings. Read each posting; if it asks for SAS certification, that team works in SAS today.
Can a statistics graduate become a statistical programmer?
Yes, it is one of the most common routes. Programming skill and attention to specification detail matter more than advanced theory, and many CROs train new joiners on CDISC standards.
Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.
This article is part of our Data Science programme — 6 months. From data foundations to machine learning, deep learning and deployment.
Explore Data Science