SAS (Base SAS)+
*==============================================================================;
* Sample data: 3 subjects, 3 visits each, with numeric AVAL scores
*==============================================================================;
data advs;
infile datalines dlm='|' dsd missover;
input USUBJID : $7. VISITNUM PARAMCD : $6. AVAL;
datalines4;
101-001|1|SYSBP|130
101-001|2|SYSBP|125
101-001|3|SYSBP|120
101-002|1|SYSBP|145
101-002|2|SYSBP|140
101-002|3|SYSBP|138
101-003|1|SYSBP|118
101-003|2|SYSBP|122
101-003|3|SYSBP|119
;;;;
run;
*==============================================================================;
* Approach 1: Sort descending by visit, LAG on the reversed data
*==============================================================================;
* When we sort VISITNUM descending, the "previous" row in that order is
* actually the next visit in chronological order.
*------------------------------------------------------------------------------;
proc sort data=advs out=advs_desc;
by USUBJID descending VISITNUM;
run;
data lead_desc;
set advs_desc;
by USUBJID descending VISITNUM;
next_aval = lag(AVAL);
if first.USUBJID then next_aval = .;
run;
proc sort data=lead_desc;
by USUBJID VISITNUM;
run;
proc print data=lead_desc noobs;
run;
*==============================================================================;
* Approach 2: Merge dataset to itself with an offset visit number
*==============================================================================;
* We create a lookup copy where VISITNUM is decremented by 1, then merge
* on USUBJID and VISITNUM. Each row picks up the AVAL from the next visit.
*------------------------------------------------------------------------------;
proc sort data=advs;
by USUBJID VISITNUM;
run;
data next_visit;
set advs(rename=(AVAL=next_aval));
VISITNUM = VISITNUM - 1;
keep USUBJID VISITNUM next_aval;
run;
proc sort data=next_visit;
by USUBJID VISITNUM;
run;
data lead_merge;
merge advs(in=a) next_visit;
by USUBJID VISITNUM;
if a;
run;
proc print data=lead_merge noobs;
run;- SAS has no built-in LEAD function, so we work around it. Two common approaches are shown here.
- In Approach 1, we sort ADVS by USUBJID and descending VISITNUM. Now visit 3 comes first, then 2, then 1 within each subject.
- We call
lag(AVAL)on this reversed data. Because the rows are backwards, "previous" in processing order is actually the next visit chronologically. - We blank out next_aval on
first.USUBJID(which is the last chronological visit after descending sort) to avoid cross-subject leakage. - We then re-sort by ascending VISITNUM to restore the natural order.
- In Approach 2, we create a lookup dataset where each row's VISITNUM is decremented by 1 and AVAL is renamed to next_aval. Merging this back onto the original by USUBJID and VISITNUM pairs each row with the AVAL from one visit ahead.
- The last visit of each subject has no match in the lookup, so next_aval is missing — exactly what we want.
- Approach 2 is more flexible when visits are not sequential integers — we can join on actual visit dates or visit names instead.