A tour of clinical SAS
You learn SAS by writing SAS programs. This chapter is a fast tour: six small programs that use the SAS you need most. Do not try to understand every detail now. Each later chapter explains one part in full.
1.1 A first program
Section titled “1.1 A first program”/* Chapter 1: a first program. Read ADSL, keep five variables, count the subjects per arm */DATA adsl_tour; SET refadm.adsl(KEEP=usubjid arm age sex saffl);RUN;
PROC FREQ DATA=adsl_tour; TABLES arm;RUN;Press F4 to run it.
What you should see:
| ARM | Frequency | Percent |
|---|---|---|
| Placebo | 86 | 33.86 |
| Xanomeline High Dose | 84 | 33.07 |
| Xanomeline Low Dose | 84 | 33.07 |
The program has two steps:
- The DATA step makes a data set.
DATA adsl_tour;names the new data set.SETreadsREFADM.ADSLone record at a time.KEEP=keeps only five variables. - The PROC step runs a procedure, a program that SAS gives you.
PROC FREQcounts the values ofarm. - Every statement ends with a semicolon.
RUN;ends a step.
ADSL has one record per subject, so the table counts subjects: 254 in three arms.
Exercise 1-1. Change TABLES arm; to TABLES sex;. How many women and men are in the study?
1.2 Make a new variable
Section titled “1.2 Make a new variable”/* Chapter 1: IF, THEN and ELSE make a new variable from an old one */DATA adsl_age; LENGTH agegrp $11; SET refadm.adsl(KEEP=usubjid arm age); IF age >= 65 THEN agegrp = "65 or older"; ELSE agegrp = "under 65";RUN;
PROC FREQ DATA=adsl_age; TABLES agegrp;RUN;What you should see:
| agegrp | Frequency | Percent |
|---|---|---|
| 65 or older | 221 | 87.01 |
| under 65 | 33 | 12.99 |
IF ... THEN ... ELSEgivesagegrpone of two values for each subject.LENGTH agegrp $11;makesagegrpa text variable of 11 characters. Without it, SAS takes the length from the first value it sees. Chapter 3 shows the trap.
Exercise 1-2. Change the program so that it counts the subjects older than 80. How many are there?
1.3 Sort, then read in groups
Section titled “1.3 Sort, then read in groups”/* Chapter 1: sort, then read in groups. The oldest subject of each arm */PROC SORT DATA=refadm.adsl(KEEP=usubjid arm age) OUT=adsl_sorted; BY arm DESCENDING age usubjid;RUN;
DATA oldest; SET adsl_sorted; BY arm; IF FIRST.arm;RUN;
PROC PRINT DATA=oldest NOOBS;RUN;What you should see: the oldest subject of each arm.
| USUBJID | ARM | AGE |
|---|---|---|
| 01-710-1083 | Placebo | 89 |
| 01-703-1295 | Xanomeline High Dose | 88 |
| 01-710-1002 | Xanomeline Low Dose | 88 |
PROC SORTputs the records in order: by arm, and within an arm from the oldest down.usubjidbreaks a tie, so the result is the same every time.BY arm;in the DATA step reads the records in groups.FIRST.armis 1 on the first record of each arm.IF FIRST.arm;keeps only those records. This “subsetting IF” has noTHEN.
Chapter 4 explains BY, FIRST. and LAST. step by step.
Exercise 1-3. Remove DESCENDING. What do you get now?
1.4 Summary statistics
Section titled “1.4 Summary statistics”/* Chapter 1: summary statistics per arm */PROC MEANS DATA=refadm.adsl N MEAN STD MIN MAX MAXDEC=1; CLASS arm; VAR age;RUN;What you should see:
| ARM | N | Mean | Std Dev | Minimum | Maximum |
|---|---|---|---|---|---|
| Placebo | 86 | 75.2 | 8.6 | 52.0 | 89.0 |
| Xanomeline High Dose | 84 | 74.4 | 7.9 | 56.0 | 88.0 |
| Xanomeline Low Dose | 84 | 75.7 | 8.3 | 51.0 | 88.0 |
CLASS arm makes one row per arm. VAR age names the variable. These are the numbers of the age rows in a demographics table (chapter 13).
1.5 Count subjects, not records
Section titled “1.5 Count subjects, not records”/* Chapter 1: PROC SQL counts subjects, not records */PROC SQL; SELECT trta, COUNT(*) AS records, COUNT(DISTINCT usubjid) AS subjects FROM refadm.adae WHERE trtemfl = "Y" GROUP BY trta;QUIT;What you should see:
| Actual Treatment | records | subjects |
|---|---|---|
| Placebo | 281 | 65 |
| Xanomeline High Dose | 433 | 76 |
| Xanomeline Low Dose | 412 | 77 |
ADAE has one record per adverse event. A subject can have many events. COUNT(*) counts records. COUNT(DISTINCT usubjid) counts each subject once. A safety table reports subjects: 65 subjects on placebo had at least one treatment-emergent adverse event. This is the most important rule of clinical tables. Chapter 6 teaches PROC SQL.
Exercise 1-4. Add AND aesev = "SEVERE" to the WHERE. How many subjects per arm had a severe event?
1.6 Write a value once
Section titled “1.6 Write a value once”/* Chapter 1: a macro variable holds text that you write once and use many times */%LET arm = Placebo;
TITLE "Age of the subjects on &arm.";PROC MEANS DATA=refadm.adsl N MEAN MIN MAX MAXDEC=1; WHERE arm = "&arm."; VAR age;RUN;TITLE;What you should see: a title “Age of the subjects on Placebo”, and N 86, mean 75.2, minimum 52.0, maximum 89.0.
%LET arm = Placebo; makes a macro variable. SAS puts its text where you write &arm., before the step runs. Change the value once, and the title and the WHERE both follow. This is the first idea of the macro language (chapter 8).
Exercise 1-5. Change the value to Xanomeline Low Dose. What are N, the mean and the oldest age?
1.7 What you have seen
Section titled “1.7 What you have seen”| Program | Idea | Explained in |
|---|---|---|
| 01_1 | DATA step, SET, PROC FREQ | Chapter 2 |
| 01_2 | IF/THEN/ELSE, LENGTH | Chapter 3 |
| 01_3 | PROC SORT, BY, FIRST., subsetting IF | Chapter 4 |
| 01_4 | PROC MEANS | Chapter 6 |
| 01_5 | PROC SQL, count subjects | Chapter 6 |
| 01_6 | %LET and &var. | Chapter 8 |
That is most of the SAS in a clinical study. The rest of this chapter shows where the data comes from and what the numbers are for.
1.8 The work of a clinical programmer
Section titled “1.8 The work of a clinical programmer”A clinical programmer turns trial data into the numbers that a regulator reads. Two rules shape every page of this book:
- Every number must be traceable to the data it came from.
- A second programmer must be able to reproduce it.
1.9 The documents that drive your work
Section titled “1.9 The documents that drive your work”You do not decide what to compute. Documents decide it. You implement them exactly.
| Document | Written by | What you take from it |
|---|---|---|
| Protocol | Clinical team | Visits, treatments, populations, the primary endpoint |
| Statistical analysis plan (SAP) | Statistician | Populations, derivation rules, imputation rules, models |
| TLF shells (mock tables) | Statistician | The layout of each output: rows, columns, decimals, footnotes |
| ADaM specification | Lead programmer | Every ADaM variable: name, label, type, length, derivation |
| define.xml | Programmer (generated) | The metadata sent to the regulator with the data |
TLF means tables, listings and figures. When two documents disagree, do not choose. Ask the statistician. Write the answer where the next programmer can find it.
1.10 SDTM and ADaM
Section titled “1.10 SDTM and ADaM”The study data comes in two standards. SDTM holds what was collected. ADaM holds what the analysis needs.
| SDTM | ADaM | |
|---|---|---|
| Purpose | Show what was collected | Make the analysis easy |
| Organised by | Topic: DM, AE, LB, VS, EX, DS | Analysis need: ADSL, ADAE, ADLB, ADTTE |
| Dates | ISO 8601 text, can be partial: 2014-03 | SAS dates (ADT), with imputation flags |
| Derived values | Few | Many: baseline, change, flags, analysis visits |
The core ADaM rule is “one PROC away”. A table program only selects records with flags, counts, summarises and prints. If a table program must derive a value, that value belongs in ADaM.
ADaM has four structures:
- ADSL, subject level: one record per subject. Treatment, populations, demography, key dates. Chapter 9.
- BDS, basic data structure: one record per subject, parameter and time point. Labs, vital signs. Chapter 10.
- OCCDS, occurrence data: one record per event. Adverse events, medications. Chapter 11.
- ADTTE, time to event: a BDS with the time (
AVAL) and a censor flag (CNSR). Chapter 12.
1.11 One number, traced
Section titled “1.11 One number, traced”SDTM DM of the pilot study holds 306 subjects. 52 are screen failures: they signed consent, but they were never randomised. ADSL keeps the 254 randomised subjects: 86 on placebo, 84 on the low dose, 84 on the high dose. Program 01_1 counted them.
When a table says N = 86, you must be able to show the 86 records that made it. That is traceability.
1.12 Why every number is built twice
Section titled “1.12 Why every number is built twice”The usual standard is double programming. Two programmers write the same specification independently. Then PROC COMPARE must find no differences (chapter 15).
This changes how you write code. Your program must:
- be easy to read for the second programmer,
- give the same result each time it runs,
- leave a clean log.
A program that is right but cannot be checked is not finished.
Exercise 1-6. Name the SDTM domain that holds the date of first study treatment. Name the ADSL variable that holds it as a SAS date.
Exercise 1-7. A table program computes “change from baseline” itself. Which ADaM rule does it break?
Exercise 1-8. DM has 306 records and ADSL has 254. Write one sentence for a reviewer that explains the difference.
The answers are in Answers, Part I.