I have a dataset that has one row per patient, and it contains information on what date (formatted as a SAS date) a patient took 11 doses of medication. In the dataset, there is at most one dose of medication per day. A patient can have dates populated for anywhere between 1 to 11 doses, and there are no middle doses that are missing information (eg, if Dose5 is populated, by definition Dose1-Dose4 are populated). I'm interested in getting the maximum number of consecutive days a patient took a dose of medication. Here are 5 sample rows of data.
data have;
input PATIENT_ID Dose1 :ddmmyy10. Dose2 :ddmmyy10. Dose3 :ddmmyy10. Dose4 :ddmmyy10. Dose5 :ddmmyy10. Dose6 :ddmmyy10. Dose7 :ddmmyy10. Dose8 :ddmmyy10. Dose9 :ddmmyy10. Dose10 :ddmmyy10. Dose11;
format Dose1 Dose2 Dose3 Dose4 Dose5 Dose6 Dose7 Dose8 Dose9 Dose10 Dose11 ddmmyy10.;
cards;
1 01/01/2020 01/02/2020 01/03/2020 01/04/2020 01/05/2020 01/06/2020 01/07/20 1/08/2020 01/09/2020 01/10/2020 01/11/2020
2 01/01/2020 01/02/2020 01/03/2020 01/05/2020 01/06/2020
3 01/02/2020 01/04/2020 01/06/2020 01/08/2020 01/10/2020 01/12/2020 01/14/2020 01/16/2020 01/18/2020
4 01/03/2020 01/04/2020 01/05/2020 01/06/2020 01/07/2020 01/08/2020 01/09/2020 1/10/2020 01/12/2020 01/13/2020
5 01/01/2020 01/07/2020 01/08/2020 01/10/2020
;
run;
I would like to get the variable MAX_CONSECUTIVE_DAYS:
data want;
input PATIENT_ID MAX_CONSECUTIVE_DAYS Dose1 :ddmmyy10. Dose2 :ddmmyy10. Dose3 :ddmmyy10. Dose4 :ddmmyy10. Dose5 :ddmmyy10. Dose6 :ddmmyy10. Dose7 :ddmmyy10. Dose8 :ddmmyy10. Dose9 :ddmmyy10. Dose10 :ddmmyy10. Dose11;
format Dose1 Dose2 Dose3 Dose4 Dose5 Dose6 Dose7 Dose8 Dose9 Dose10 Dose11 ddmmyy10.;
cards;
1 11 01/01/2020 01/02/2020 01/03/2020 01/04/2020 01/05/2020 01/06/2020 01/07/20 1/08/2020 01/09/2020 01/10/2020 01/11/2020
2 3 01/01/2020 01/02/2020 01/03/2020 01/05/2020 01/06/2020
3 1 01/02/2020 01/04/2020 01/06/2020 01/08/2020 01/10/2020 01/12/2020 01/14/2020 01/16/2020 01/18/2020
4 8 01/03/2020 01/04/2020 01/05/2020 01/06/2020 01/07/2020 01/08/2020 01/09/2020 1/10/2020 01/12/2020 01/13/2020
5 2 01/01/2020 01/07/2020 01/08/2020 01/10/2020
run;
So far, I've only been able to figure out how to do it by brute force piecemeal.
data bruteforce;
set have;
if Dose2 =. then MAX_CONSECUTIVE_DAYS=1;
else if Dose3=. then
do;
if Dose2-Dose1=1 then MAX_CONSECUTIVE_DAYS=2;
else MAX_CONSECUTIVE_DAYS=1;
end;
else if Dose4=. then
do;
if Dose3-Dose1=2 then MAX_CONSECUTIVE_DAYS=3;
else if (Dose2-Dose1=1) or (Dose3-Dose2=1) then MAX_CONSECUTIVE_DAYS=2;
else MAX_CONSECUTIVE_DAYS=1;
end;
else if Dose5=. then
do;
if Dose4-Dose1=3 then MAX_CONSECUTIVE_DAYS=4;
else if (Dose3-Dose1=2) or (Dose4-Dose2=2) then MAX_CONSECUTIVE_DAYS=3;
else if (Dose2-Dose1=1) or (Dose3-Dose2=1) or (Dose4-Dose3=1) then MAX_CONSECUTIVE_DAYS=2;
else MAX_CONSECUTIVE_DAYS=1;
end;
/*And so on and so forth until accounting for rows where Dose10 is populated*/
run;
However, in my actual work, there are over 200 doses of medication, so doing a series of do loops with if-then-else statements doesn't make sense. If I had to guess, the solution might have something to do with arrays, but I'm not sure where or how to start.
First of all, thank you for providing a clear explanation of your problem and what you have tried so far :-)
Just a note. I changed your input data to have dates with mmddyy10 informat/format. I think what you want is to count consecutive days and not months.
Anyway, try this. Feel free to ask
data have;
infile datalines missover;
input PATIENT_ID (Dose1 - Dose11)(:mmddyy10.);
format Dose: mmddyy10.;
cards;
1 01/01/2020 01/02/2020 01/03/2020 01/04/2020 01/05/2020 01/06/2020 01/07/2020 1/08/2020 01/09/2020 01/10/2020 01/11/2020
2 01/01/2020 01/02/2020 01/03/2020 01/05/2020 01/06/2020
3 01/02/2020 01/04/2020 01/06/2020 01/08/2020 01/10/2020 01/12/2020 01/14/2020 01/16/2020 01/18/2020
4 01/03/2020 01/04/2020 01/05/2020 01/06/2020 01/07/2020 01/08/2020 01/09/2020 1/10/2020 01/12/2020 01/13/2020
5 01/01/2020 01/07/2020 01/08/2020 01/10/2020
;
data want(drop=c i);
set have;
array dose {*} Dose:;
c = 1;
do i = 2 to dim(dose);
if dose[i] - dose[i-1] = 1 then c + 1;
else do;
if c > mc then mc = c;
c = 1;
end;
end;
if mc = . then mc = c;
run;
Result:
PATIENT_ID Dose1...Dose11 mc
1 ... 11
2 ... 3
3 ... 1
4 ... 8
5 ... 2
Correct, a variable based array
will let you iterate over the dates and compute the longest run.
Tip: Use a variable name as indicative of value content as possible. dose<n>
is less meta informative then dose_date<n>
A robust computation will check or account for edge cases such as no dosages.
Sample code:
There is a small (perhaps negligible) CPU cost to extracting a value from an array. Suppose the array was named x
. The computation x[index]-x[index-1]
inside a loop repeats such a cost.
For example: x[5]−[x4]
and x[6]−x[5]
. Storing an extracted value in a variable will reduce the repeated cost.
Data set option keep=
is an explicit list of variables to be output. Alternatively, drop=_:
could be used to exclude worker variables (in this sample) whose names start with _
.
data have;
infile datalines missover; input
ID X1-X11; format _numeric_ 4.; datalines;
1 01 02 03 04 05 06 07 08 09 10 11
2 01 02 03 05 06
3 02 04 06 08 10 12 14 16 18
4 03 04 05 06 07 08 09 10 12 13
5 01 07 08 10
6 01
7
8 01 03 05 07 09 11 13 15 17 19 21
;
data want(keep=id x: rl_max);
set have;
array X X:;
if not missing(x1) then _rl = 1; /* preset if not an edge case */
_p = x1;
rl_max = _rl;
do _index = 2 to dim(X);
_q = X[_index]; /* store extracted value in worker variable */
if missing(_q) then leave; /* iterate as little as needed */
if _q - _p = 1 then /* consecutive */
_rl = sum (_rl, 1);
else do; /* gap, check and reset */
if _rl > rl_max then rl_max = _rl;
_rl = 1;
end;
_p = _q; /* current to previous */
end;
if _rl > rl_max then rl_max = _rl; /* no gaps */
run;
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.