Skip to content

OCCDS data sets, ADAE

Adverse events, concomitant medications and medical history are occurrences: things that happen, any number of times, at any time. Their ADaM structure is OCCDS: one record per event, with the subject’s treatment, analysis dates and flags. Every safety table of a study reads ADAE, and almost every one of them counts only the treatment emergent events. That flag is the heart of this chapter.

The program reads WORK.ADSL from Chapter 9, so run 09_1_adsl.sas first, in the same session.

11_1_adae.sas
/* Chapter 11: ADAE by hand (needs ADSL from chapter 9) */
/* Step 010: every AE with the treatment and dates of the subject */
DATA adae_010;
MERGE sdtm.ae(IN=inae KEEP=usubjid aeseq aeterm aedecod aebodsys aesev aeser aestdtc aeendtc)
adsl(KEEP=usubjid trtsdt trtedt trt01a trt01an RENAME=(trt01a=trta trt01an=trtan));
BY usubjid;
IF inae;
RUN;
/* Step 020: start date imputed early (a missing day only), end date as collected */
DATA adae_020;
LENGTH astdtf $1;
SET adae_010;
yy = INPUT(SUBSTR(aestdtc, 1, 4), ?? 4.);
IF LENGTH(aestdtc) >= 7 THEN mm = INPUT(SUBSTR(aestdtc, 6, 2), ?? 2.);
IF LENGTH(aestdtc) = 10 THEN DO;
astdt = INPUT(aestdtc, ?? E8601DA.);
astdtf = "";
END;
ELSE IF NOT MISSING(mm) THEN DO;
astdt = MDY(mm, 1, yy);
astdtf = "D";
END;
/* a start with the year only stays missing: the SAP imputes a missing day, not a missing month */
/* an imputed start before the first dose, in the month of the first dose, moves to the first dose */
IF astdtf = "D" AND astdt < trtsdt AND YEAR(astdt) = YEAR(trtsdt) AND MONTH(astdt) = MONTH(trtsdt) THEN astdt = trtsdt;
aendt = INPUT(aeendtc, ?? E8601DA.);
FORMAT astdt aendt DATE9.;
DROP yy mm;
RUN;
/* Step 030: treatment emergent: starts on or after the first dose */
DATA adae_030;
LENGTH trtemfl $1;
SET adae_020;
IF NOT MISSING(astdt) AND astdt >= trtsdt THEN trtemfl = "Y";
ELSE trtemfl = "N";
RUN;
/* Step 040: first occurrence flags among the treatment emergent events */
PROC SORT DATA=adae_030; BY usubjid trtemfl astdt aeseq; RUN;
DATA adae_040;
SET adae_030;
BY usubjid trtemfl;
LENGTH aoccfl $1;
IF trtemfl = "Y" AND FIRST.trtemfl THEN aoccfl = "Y";
ELSE aoccfl = "";
RUN;
PROC SORT DATA=adae_040; BY usubjid trtemfl aedecod astdt aeseq; RUN;
DATA adae;
SET adae_040;
BY usubjid trtemfl aedecod;
LENGTH aoccpfl $1;
IF trtemfl = "Y" AND FIRST.aedecod THEN aoccpfl = "Y";
ELSE aoccpfl = "";
RUN;
PROC SORT DATA=adae; BY usubjid aeseq; RUN;
PROC FREQ DATA=adae; TABLES astdtf trtemfl / MISSING NOCUM; RUN;
/* Compare with the reference ADAE */
PROC SORT DATA=refadm.adae OUT=ref_ae; BY usubjid aeseq; RUN;
PROC COMPARE BASE=ref_ae COMPARE=adae CRITERION=1E-8 NOSUMMARY;
ID usubjid aeseq;
VAR astdt astdtf aendt trtemfl aoccfl aoccpfl;
RUN;
%PUT NOTE: PROC COMPARE result SYSINFO=&SYSINFO. (0 means equal);

What you should see:

astdtfFrequencyPercent
(blank)117698.74
D151.26
trtemflFrequencyPercent
N655.46
Y112694.54

and NOTE: PROC COMPARE result SYSINFO=0 (0 means equal): for all 1191 adverse events, ASTDT, ASTDTF, AENDT, TRTEMFL, AOCCFL and AOCCPFL are the same as in the reference ADAE.

ASTDT and AENDT are the analysis start and end dates: SAS dates made from the ISO 8601 text dates AESTDTC and AEENDTC. ASTDTF is the imputation flag of ASTDT: it says which part of the date the program imputed.

In SDTM AE of the pilot, 1165 start dates are complete, 15 have a year and a month (2013-03), and 11 have only a year (2003). Step 020 imputes the 15 to the first day of the month and flags them with ASTDTF = "D" (the day was imputed). The start date is imputed early, because a start date feeds the treatment emergent flag. A conservative rule is a rule that never hides a possible drug effect. The early date alone is not conservative: an event in the month of the first dose would move before the dose and lose its treatment emergent flag. The early date is safe only with the next rule, the floor at the first dose.

One more rule: an imputed start in the month of the first dose, but before it, moves to the first dose. An event that started “sometime in March” when the first dose was on 18 March might have started after the dose. Without this rule, the imputation alone would decide that it did not.

The 11 starts with a year only stay missing. This SAP imputes a missing day, not a missing month. The first version of this program imputed them to 1 January. PROC COMPARE found 11 differences: events from 1977 to 2007, long before the trial. Imputing them would have suggested a precision the data does not have.

TRTEMFL = "Y" when the event started on or after the first dose. Events before the first dose are part of the subject’s history, not effects of the drug. Step 030 is one line; the work was in step 020.

A table that counts subjects with an event needs one record per subject to count. AOCCFL marks the first treatment emergent event of each subject; AOCCPFL marks the first per subject and preferred term. Both come from FIRST. on the right sort order (Chapter 4): the order of the BY statement defines what “first” means.

WORK.ADAE with WHERE astdtf = 'D'

WORK.ADAE with WHERE astdtf = “D”: 15 of 1191 events had their start day imputed.

Exercise 11-1. How many subjects have at least one treatment emergent serious event (AESER = "Y")? Count with COUNT(DISTINCT usubjid). Then count the serious records that have AOCCFL = "Y". Why are the two numbers different?

Exercise 11-2. Add AOCCSFL, the first treatment emergent occurrence per subject and system organ class (AEBODSYS). How many records get AOCCSFL = "Y"?

Step 020 of 11_1_adae.sas turns AESTDTC and AEENDTC into SAS dates, with a missing day imputed to the first of the month. That is the job of %m_iso_dt from chapter 9. Run 09_3_m_iso_dt.sas earlier in the same session, so that SAS knows the macro.

11_2_adae_iso.sas
/* Chapter 11: the ADAE dates again, with %m_iso_dt from chapter 9 (needs 09_1, 09_3 and 11_1) */
/* Step 010: the mechanism is the macro: text date to SAS date, a missing day imputed early */
%m_iso_dt(data=adae_010, out=adae_m010, dtc=aestdtc, dt=astdt, impute=FIRST, flag=astdtf)
%m_iso_dt(data=adae_m010, out=adae_m020, dtc=aeendtc, dt=aendt)
/* Step 020: the study rule stays in the program, where a reviewer reads it: */
/* an imputed start before the first dose, in the month of the first dose, */
/* moves to the first dose */
DATA adae_m030;
LENGTH trtemfl $1;
SET adae_m020;
IF astdtf = "D" AND astdt < trtsdt AND YEAR(astdt) = YEAR(trtsdt) AND MONTH(astdt) = MONTH(trtsdt) THEN astdt = trtsdt;
IF NOT MISSING(astdt) AND astdt >= trtsdt THEN trtemfl = "Y";
ELSE trtemfl = "N";
RUN;
PROC SORT DATA=adae_m030; BY usubjid aeseq; RUN;
/* Test: the macro and the hand-written steps of 11_1 must agree */
PROC COMPARE BASE=adae COMPARE=adae_m030 CRITERION=1E-8 NOSUMMARY;
ID usubjid aeseq;
VAR astdt astdtf aendt trtemfl;
RUN;
%PUT NOTE: macro against 11_1: SYSINFO=&SYSINFO. (0 means equal);
/* Use the part of the date you know: before any rule for unknown starts, */
/* compare the known year (and month) of the start with the first dose */
DATA adae_known;
SET adae;
LENGTH precision $5 decided $24;
yy = INPUT(SUBSTR(aestdtc, 1, 4), ?? 4.);
IF LENGTHN(aestdtc) >= 7 THEN mm = INPUT(SUBSTR(aestdtc, 6, 2), ?? 2.);
IF LENGTHN(aestdtc) = 10 THEN precision = "day";
ELSE IF NOT MISSING(mm) THEN precision = "month";
ELSE IF NOT MISSING(yy) THEN precision = "year";
ELSE precision = "none";
/* months counted from year 0, so that one comparison covers year and month */
IF precision = "day" THEN decided = "full date";
ELSE IF MISSING(trtsdt) THEN decided = "no first dose";
ELSE IF precision = "month" THEN DO;
IF yy * 12 + mm < YEAR(trtsdt) * 12 + MONTH(trtsdt) THEN decided = "before the first dose";
ELSE IF yy * 12 + mm > YEAR(trtsdt) * 12 + MONTH(trtsdt) THEN decided = "after the first dose";
ELSE decided = "same month: SAP rule";
END;
ELSE IF precision = "year" THEN DO;
IF yy < YEAR(trtsdt) THEN decided = "before the first dose";
ELSE IF yy > YEAR(trtsdt) THEN decided = "after the first dose";
ELSE decided = "same year: SAP rule";
END;
ELSE decided = "no date: SAP rule";
DROP yy mm;
RUN;
PROC FREQ DATA=adae_known;
WHERE precision NE "day";
TABLES precision * decided * trtemfl / LIST NOCUM NOPERCENT;
TITLE "Partial start dates: what the known part decides";
RUN;
TITLE;
PROC SQL;
TITLE "Year-only starts against the first dose";
SELECT MIN(INPUT(SUBSTR(aestdtc, 1, 4), 4.)) AS first_start, MAX(INPUT(SUBSTR(aestdtc, 1, 4), 4.)) AS last_start,
MIN(YEAR(trtsdt)) AS first_dose_from, MAX(YEAR(trtsdt)) AS first_dose_to
FROM adae_known
WHERE precision = "year";
TITLE;
QUIT;

The program splits the work in two:

  • The mechanism goes in the macro: text to date, a missing day imputed early, ASTDTF = "D". The same code as in chapter 9, already tested.
  • The study rule stays in the program: an imputed start before the first dose, in the month of the first dose, moves to the first dose. A reviewer reads this rule in the program, next to the SAP, and does not have to open a macro to find it.

What you should see:

NOTE: macro against 11_1: SYSINFO=0 (0 means equal)

For all 1191 adverse events, ASTDT, ASTDTF, AENDT and TRTEMFL are the same as in 11_1_adae.sas, and so the same as in the reference.

Eleven start dates have the year only, and ASTDT stays missing (section 11.2). Is such an event treatment emergent? Before you choose a rule for unknown dates, look at the part of the date you do know. A start in 2003 cannot be after a first dose in 2013, whatever the month and the day.

The end of 11_2_adae_iso.sas compares the known part of every partial start date with the same part of the first dose. For a year-only date it compares the year. For a date without a day it compares the year and the month. Only when the known parts are equal (the same year, or the same month) does the date leave the question open.

What you should see:

precisiondecidedtrtemflFrequency
monthafter the first doseY6
monthbefore the first doseN9
yearbefore the first doseN11
first_startlast_startfirst_dose_fromfirst_dose_to
1977200720122014

Every partial date is decided by what is known. The 11 year-only starts are from 1977 to 2007. The first doses of these subjects are from 2012 to 2014. So all 11 events started before treatment, and TRTEMFL = "N" is a fact, not a guess. No partial date falls in the month or the year of the first dose. So in the pilot, the floor rule of section 11.2 never changes a date. It is there for the study where one does.

The SAP still needs a rule for the open case, the start in the same month or year as the first dose (or no start date at all). Many safety reviewers choose the worst case: such an event counts as treatment emergent, because it might have started after the dose. Others choose the opposite. Write the rule before you program, and apply it only after the known part of the date has had its say.

Exercise 11-3. A subject has the first dose on 18 March 2013 and an adverse event that starts 2013-03. Write the SAP sentence for this open case, once for the worst case and once for the opposite choice. What does each rule set TRTEMFL to?

Exercise 11-4. Call %m_iso_dt for AEENDTC with impute=LAST and flag=aendtf. How many end dates get a day imputed?

11.7 Concomitant medications and medical history

Section titled “11.7 Concomitant medications and medical history”

ADCM and ADMH use the same structure and the same tools. They need analysis start and end dates with imputation (%m_iso_dt again) and first occurrence flags. ADCM also needs ONTRTFL, a flag for medications taken during treatment: on a day between TRTSDT and TRTEDT. The coded terms come from dictionaries: MedDRA (the Medical Dictionary for Regulatory Activities) codes adverse events, and WHO Drug codes medications. SDTM holds the coded terms. ADaM keeps them and does not code again.

The exercises are next to the program they change: 11-1 and 11-2 use 11_1_adae.sas, 11-3 and 11-4 use 11_2_adae_iso.sas. The answers are in Answers, Part II.