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.
Chapter 9: ADSL
Section titled “Chapter 9: ADSL”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:
| USUBJID | MAX(exendt) | TRTEDT (rule of step 030) | reference TRTEDT |
|---|---|---|---|
| 01-704-1233 | 04APR2013 | 14JUL2013 | 14JUL2013 |
| 01-705-1018 | . | 12JUL2013 | 12JUL2013 |
| 01-705-1031 | 18DEC2013 | 11MAY2014 | 11MAY2014 |
| 01-705-1303 | 30DEC2013 | 02JUN2014 | 02JUN2014 |
| 01-705-1377 | 25JAN2014 | 07MAR2014 | 07MAR2014 |
| 01-705-1382 | . | 13MAY2013 | 13MAY2013 |
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: [¯o.] 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.
Chapter 10: BDS
Section titled “Chapter 10: BDS”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.
Chapter 11: OCCDS
Section titled “Chapter 11: OCCDS”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.
Chapter 12: ADTTE
Section titled “Chapter 12: ADTTE”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).