Skip to content

Answers, Part II

Every answer below was checked by running the code on SAS OnDemand with the pilot data, after the chapter programs in book order.

9-1. After step 050, add trtdurd = trtedt - trtsdt + 1;. Compare it with the reference (ID usubjid; VAR trtdur;, with trtdurd renamed to trtdur): SYSINFO=0, all 254 subjects are equal. The reference counts the first day, as you do.

9-2. Six subjects have an open last dose record, and all six differ:

USUBJIDMAX(exendt)TRTEDT (rule of step 030)reference TRTEDT
01-704-123304APR201314JUL201314JUL2013
01-705-1018.12JUL201312JUL2013
01-705-103118DEC201311MAY201411MAY2014
01-705-130330DEC201302JUN201402JUN2014
01-705-137725JAN201407MAR201407MAR2014
01-705-1382.13MAY201313MAY2013

With trtedt = exlast;, PROC COMPARE lists these six subjects for TRTEDT: four get an earlier date, two get no date at all.

9-3.

%MACRO m_yn(data=, out=, var=, flag=);
%LOCAL macro p par;
%LET macro = M_YN;
%DO p = 1 %TO 4;
%LET par = %SCAN(data out var flag, &p.);
%IF %SUPERQ(&par.) = %THEN %DO;
%PUT %STR(ERR)OR: [&macro.] the parameter &par.= is required.;
%RETURN;
%END;
%END;
DATA &out.;
LENGTH &flag. $1;
SET &data.;
IF &var. = "Y" THEN &flag. = "Y";
ELSE &flag. = "N";
RUN;
%MEND m_yn;
DATA yn_0;
MERGE adsl_030(IN=inadsl) supp(KEEP=usubjid safety itt efficacy complt24);
BY usubjid;
IF inadsl;
RUN;
%m_yn(data=yn_0, out=yn_1, var=safety, flag=saffl)
%m_yn(data=yn_1, out=yn_2, var=itt, flag=ittfl)
%m_yn(data=yn_2, out=yn_3, var=efficacy, flag=efffl)
%m_yn(data=yn_3, out=yn_4, var=complt24, flag=comp24fl)
PROC COMPARE BASE=adsl_040 COMPARE=yn_4 NOSUMMARY;
ID usubjid;
VAR saffl ittfl efffl comp24fl;
RUN;

SYSINFO=0: the four flags are the same as in step 040. Is the macro worth it? Four lines replace eight, and a fifth flag costs one line. But the macro hides the rule (Y when the SUPPDM value is Y, else N) behind a name. For a rule this short, both versions are fine; for a longer rule, the macro wins.

9-4. impute=FIRST gives 01DEC2013 and impute=LAST gives 31DEC2013, both with the flag D. The function is INTNX("MONTH", date, 0, "END"): it moves 0 months and returns the last day of that month, so February has 28 or 29 days without any rule of yours.

10-1. Nothing changes: 0 records. In the pilot, no analysis visit has two weights, so the first and the last record of every visit are the same record. The flag still matters: it makes the rule visible, and it decides as soon as a study has repeated measurements. Predicting 0 before the run is the skill this exercise trains.

10-2. The log says:

ERROR: [M_CHG] 2 baseline records in groups with more than one. See WORK._MCH_021.

WORK._MCH_021 holds the two weight records of subject 01-701-1015 with ablfl = "Y", as whole records: SCREENING 1 (26DEC2013, 53.98) and BASELINE (02JAN2014, 54.43). So you see at once which visits collide. The macro stops before it writes the output data set, so no wrong BASE reaches a table. The work data set stays, because the macro returns before its cleanup, so you can look at it.

10-3.

PROC SQL;
SELECT a.avisit, COUNT(*) AS copies
FROM refadm.advs AS a
WHERE a.paramcd IN ("SYSBP" "DIABP" "PULSE")
AND EXISTS (SELECT 1 FROM refadm.advs AS b
WHERE b.usubjid = a.usubjid AND b.paramcd = a.paramcd
AND b.vsseq = a.vsseq AND b.avisit NE a.avisit)
GROUP BY a.avisit;
QUIT;

All 2,043 End of Treatment records have a twin with the same VSSEQ. The twins are at Week 26 (999), Week 12 (225), Week 6 (179), Week 16 (171), Week 4 (163), Week 8 (153), Week 20 (108) and Week 24 (45): the last visit on treatment of each subject, whatever week that was. Together 2,043.

10-4. Use awhi = FLOOR((awtarget + 7 * wk{i + 1} + 1) / 2); in the windows step. All target days are odd (7 * week + 1), so every midpoint is a whole day and every pair of windows has a tie. 30 post-baseline weights move to the earlier window.

10-5. %m_baseline(data=adlb_010, out=adlb_copy, sortby=usubjid paramcd adt lbseq, copy=Y) gives 2068 records: the 1814 collected records plus 254 copies with AVISIT = "Baseline" and ABLFL = "Y", one per subject with a pre-dose ALT. %m_chg(data=adlb_copy, out=adlb_copy_chg, by=usubjid paramcd) then puts the value of the copy on every record: on the collected records, BASE is the same as in ADLB_ALT (0 differences). The two structures carry the same baseline value; only the place of the baseline record differs.

10-6. After %m_chg(data=adlb_020, out=adlb_alt, by=usubjid paramcd):

DATA adlb_alt;
SET adlb_alt;
/* the baseline is the last value on or before the first dose: */
/* so every record on or before the first dose is baseline or earlier */
IF adt <= trtsdt THEN CALL MISSING(chg, pchg);
RUN;

280 of the 1814 records are on or before the first dose, so 1534 records keep a CHG. Against the reference, 410 CHG values differed before the change and 160 after it. SYSINFO stays 4224: the baseline rule still flags another record for 24 subjects and gives another BASE for 22 of them, and the 2 extra records remain. Among the 160 are the 26 unscheduled retests before the first dose: the reference takes SCREENING 1 as baseline, so for it these records come after baseline and get a CHG. The others belong to subjects whose BASE differs from the reference.

11-1. 3 subjects have a treatment emergent serious event, and there are 3 such records: one per subject. Only 1 of them has AOCCFL = "Y". AOCCFL marks the first treatment emergent event of the subject, of any kind; for two of the three subjects another treatment emergent event comes first in the sort order (astdt aeseq). To count subjects with a serious event, use COUNT(DISTINCT usubjid) on the serious records, or build a flag per subject for serious events.

11-2. Sort by usubjid trtemfl aebodsys astdt aeseq and set aoccsfl = "Y" when trtemfl = "Y" AND FIRST.aebodsys. 550 records get the flag.

11-3. For example:

  • Worst case: “An adverse event whose known start (year and month, or year) is the same as that of the first dose is treatment emergent, because a start on or after the first dose cannot be excluded.” TRTEMFL = "Y".
  • The opposite: “An adverse event whose known start is the same as that of the first dose is not treatment emergent, because a start on or after the first dose cannot be shown.” TRTEMFL = "N".

In both sentences, a partial date whose known part is before or after the first dose is decided by that part (section 11.6); the rule covers only the open case. With the floor rule of section 11.2, 2013-03 becomes 18MAR2013, the first dose, so 11_1_adae.sas already follows the worst case for this subject.

11-4. None. AEENDTC in the pilot is complete (718 records) or empty (473 records); no end date has a year and a month only. The flag AENDTF is blank on all 1191 records. Look at the data before you add an imputation: here it would be code that never runs.

12-1. AVAL = adt - startdt + 1 is missing when ADT is missing, and SAS writes a NOTE about missing values. A check:

PROC SQL;
SELECT COUNT(*) AS no_date
FROM refadm.adae
WHERE trtemfl = "Y" AND NOT MISSING(cq01nam) AND MISSING(astdt);
QUIT;

The pilot has 0 such events. Keep the check anyway: it costs one step and protects every later study.

12-2.

DATA ev_dth;
LENGTH evntdesc $40 srcdom srcvar $8;
SET sdtm.dm(KEEP=usubjid dthfl dthdtc WHERE=(dthfl = "Y"));
adt = INPUT(dthdtc, ?? E8601DA.); evntdesc = "Death"; srcdom = "DM"; srcvar = "DTHDTC"; srcseq = .;
FORMAT adt DATE9.;
RUN;
%m_tte(data=subj, out=tte_ser2, paramcd=TTSAE, param=Time to First Serious Adverse Event or Death,
events=ev_ser ev_dth, censors=cn_eos)

Three subjects died (01-701-1211, 01-704-1445 and 01-710-1083), all with a complete DTHDTC. The result: 6 events (3 deaths, 3 serious adverse events, in 6 different subjects) and 248 censored subjects. Give the parameter a new name: it now measures something else.

12-3. 0 records, for TTDE and for the parameter of 12-2: no event comes after RFENDT. Write the check after every %m_tte call, or put it into the macro as version 1.1 with a line in the change log.

12-4. 152 subjects, the same as with the reference. After 11_1_adae.sas:

DATA my_adae;
SET adae;
LENGTH cq01nam $40;
IF (INDEX(aedecod, "APPLICATION") OR INDEX(aedecod, "DERMATITIS") OR INDEX(aedecod, "ERYTHEMA") OR INDEX(aedecod, "BLISTER"))
OR (aebodsys = "SKIN AND SUBCUTANEOUS TISSUE DISORDERS" AND aedecod NOT IN ("COLD SWEAT" "HYPERHIDROSIS" "ALOPECIA"))
THEN cq01nam = "DERMATOLOGIC EVENTS";
ELSE cq01nam = "";
RUN;

PROC COMPARE against refadm.adae (ID usubjid aeseq; VAR cq01nam;) gives SYSINFO=0: 493 of 1191 events are dermatologic. Then replace refadm.adae with my_adae in step 010 of 12_1_adtte.sas: 152 subjects have a first treatment emergent dermatologic event. A PROC COMPARE of that first_derm against the one from the reference gives SYSINFO=32: a variable label differs, no value does (section 9.2 lists the codes).