How to use loop with a macro in SAS - loops

I have a question about how to use loop in % macro. I've written a sas macro that looks like that:
%macro SortDaysRolling(outdat,var);
proc sort data = &outdat. (keep=ACM_ACCT_NUM DIM_DATE_ID &var.); by ACM_ACCT_NUM DIM_DATE_ID; run;
%mend SortDaysRolling;
I need to apply this function to a number of files, i.e.:
%SortDaysRolling(days_rolling_1_1_1_4,count_times_days_1_4);
%SortDaysRolling(days_rolling_1_1_5_9,count_times_days_5_9);
%SortDaysRolling(days_rolling_1_1_10_14,count_times_days_10_14);
%SortDaysRolling(days_rolling_1_1_15_19,count_times_days_15_19);
%SortDaysRolling(days_rolling_1_1_20_24,count_times_days_20_24);
%SortDaysRolling(days_rolling_1_1_25_29,count_times_days_25_29);
%SortDaysRolling(days_rolling_1_1_30_44,count_times_days_30_44);
%SortDaysRolling(days_rolling_1_1_45_59,count_times_days_45_59);
%SortDaysRolling(days_rolling_1_1_60_89,count_times_days_60_89);
%SortDaysRolling(days_rolling_1_1_90,count_times_days_90);
and then
%SortDaysRolling(days_rolling_1_2_1_4,count_times_days_1_4);
%SortDaysRolling(days_rolling_1_2_5_9,count_times_days_5_9);
%SortDaysRolling(days_rolling_1_2_10_14,count_times_days_10_14);
%SortDaysRolling(days_rolling_1_2_15_19,count_times_days_15_19);
%SortDaysRolling(days_rolling_1_2_20_24,count_times_days_20_24);
%SortDaysRolling(days_rolling_1_2_25_29,count_times_days_25_29);
%SortDaysRolling(days_rolling_1_2_30_44,count_times_days_30_44);
%SortDaysRolling(days_rolling_1_2_45_59,count_times_days_45_59);
%SortDaysRolling(days_rolling_1_2_60_89,count_times_days_60_89);
%SortDaysRolling(days_rolling_1_2_90,count_times_days_90);
So the middle index changes. Since i = 1, ..., 35, I don't want to copy-paste all those lines 35 times. Is there any way to do the loop?
Thank you very much.

You need to determine your sequence abstraction. Then construct the sort macro invocations based on that.
%macro SortDaysRolling(out, var);
%put NOTE: &SYSMACRONAME: &=out &=var;
%mend;
%macro sort_loop (I_FROM=1, I_TO=&I_FROM, J_FROM=1, J_TO=&J_FROM);
%local I J;
%do I = &I_FROM %to &I_TO;
%do J = &J_FROM %to &J_TO;
%SortDaysRolling(days_rolling_&I._&J._1_4,count_times_days_1_4);
%SortDaysRolling(days_rolling_&I._&J._5_9,count_times_days_5_9);
%SortDaysRolling(days_rolling_&I._&J._10_14,count_times_days_10_14);
%SortDaysRolling(days_rolling_&I._&J._15_19,count_times_days_15_19);
%SortDaysRolling(days_rolling_&I._&J._20_24,count_times_days_20_24);
%SortDaysRolling(days_rolling_&I._&J._25_29,count_times_days_25_29);
%SortDaysRolling(days_rolling_&I._&J._30_44,count_times_days_30_44);
%SortDaysRolling(days_rolling_&I._&J._45_59,count_times_days_45_59);
%SortDaysRolling(days_rolling_&I._&J._60_89,count_times_days_60_89);
%SortDaysRolling(days_rolling_&I._&J._90,count_times_days_90);
%end;
%end;
%mend;
%sort_loop (J_TO=35);
The 10 count_times_days variables could also be abstracted, or constructed from generation rules.

Generating the numbers in-macro would shorten the code whilst making it a bit cryptic.
The below macro generates the table name and varname, and checks if the table exists before executing the %sortdaysrolling macro block.
%macro SortDaysRolling(outdat,var);
proc sort data = &outdat. (keep=ACM_ACCT_NUM DIM_DATE_ID &var.); by ACM_ACCT_NUM DIM_DATE_ID; run;
%mend SortDaysRolling;
options mprint mlogic;
%macro sortvarloop;
%do i = 1 %to 2;
%let j = 1;
%do %until (&j = 90);
%if &j = 1 %then %let k = 4;
%else %let k = %sysevalf(&j + 4)
%if &j = 90 %then %let tabsuff = %str( );
%else %let tabsuff = _&k;
%if %sysfunc(exist(days_rolling_1_&i._&j.&tabsuff.)) %then %do;
%sortdaysrolling(days_rolling_1_&i._&j.&tabsuff.,count_times_days_&j.&tabsuff.);
%end;
%let j = %sysevalf(&j + 5);
%end;
%end;
%mend sortvarloop; %sortvarloop;
But if you really want to use a macro block, you should think a little ahead and adopt some naming convention for easy and less-harsh-on-memory execution.

Related

SAS macro loop to loop through datasets in Set statement

I have 2 SAS macro loops - 1 to create time series dataset and another to append the datasets into a master dataset.
Macro Date_loop1 creates time series datasets. In this example I am creating dummy datasets. In reality, the real datasets are several MBs large. So using proc append, within Date_loop1, to the master dataset, results in slow progress. Therefore, I have created a second macro, Date_loop2, to stacks all the datasets above in the SET statement. The issue is that the second loop is not working as intended. Can you please help?
Note: if you run Date_loop1, you can see how the datasets are created. Date_loop2 should also work in the similar manner.
%MACRO LOOP1;
DATA ROLLRATE_&ST_YYYYMM._&ED_YYYYMM.;
ST_YYYYMM = &ST_YYYYMM;
ED_YYYYMM = &ED_YYYYMM;
RUN;
%MEND;
%MACRO DATE_LOOP1(START,END);
%LET START_YEAR = %SYSFUNC(FLOOR(&START/100));
%LET START_MONTH = %SYSFUNC(MOD(&START,100));
%LET END_YEAR = %SYSFUNC(FLOOR(&END/100));
%LET END_MONTH = %SYSFUNC(MOD(&END,100));
%LET START_DATE = %SYSFUNC(MDY(&START_MONTH,01,&START_YEAR));
%LET END_DATE = %SYSFUNC(MDY(&END_MONTH,01,&END_YEAR));
%LET DIF=%EVAL(%SYSFUNC(INTCK(MONTH,&START_DATE,&END_DATE)));
/*DATA MASTER_ROLLRATE;RUN;DATA ROLLRATE;RUN;*/
%DO I=0 %TO &DIF;
%DO J=&I %TO &DIF;
%LET ST_DT = %SYSFUNC(INTNX(MONTH,&START_DATE,&I,B)); /*B - DATE AT THE BEGINNING OF THE WEEK/MONTH/YEAR */
%LET ST_YEAR = %SYSFUNC(YEAR(&ST_DT));
%LET ST_MONTH = %SYSFUNC(MONTH(&ST_DT));
%LET ST_YYYYMM = %EVAL(&ST_YEAR*100+&ST_MONTH);
%LET ED_DT = %SYSFUNC(INTNX(MONTH,&START_DATE,&J,B));
%LET ED_YEAR = %SYSFUNC(YEAR(&ED_DT));
%LET ED_MONTH = %SYSFUNC(MONTH(&ED_DT));
%LET ED_YYYYMM = %EVAL(&ED_YEAR*100+&ED_MONTH);
%PUT &ST_YYYYMM &ED_YYYYMM;
%LET START_YYYYMM = &ST_YYYYMM;%LET END_YYYYMM = &ED_YYYYMM;%LOOP1;RUN;
%END;
%END;
%MEND DATE_LOOP1;
%DATE_LOOP1(201901,201902);
%MACRO DATE_LOOP2(START,END);
%LET START_YEAR = %SYSFUNC(FLOOR(&START/100));
%LET START_MONTH = %SYSFUNC(MOD(&START,100));
%LET END_YEAR = %SYSFUNC(FLOOR(&END/100));
%LET END_MONTH = %SYSFUNC(MOD(&END,100));
%LET START_DATE = %SYSFUNC(MDY(&START_MONTH,01,&START_YEAR));
%LET END_DATE = %SYSFUNC(MDY(&END_MONTH,01,&END_YEAR));
%LET DIF=%EVAL(%SYSFUNC(INTCK(MONTH,&START_DATE,&END_DATE)));
DATA MASTER_ROLLRATE;
SET
%DO I=0 %TO &DIF;
%DO J=&I %TO &DIF;
%LET ST_DT = %SYSFUNC(INTNX(MONTH,&START_DATE,&I,B)); /*B - DATE AT THE BEGINNING OF THE WEEK/MONTH/YEAR */
%LET ST_YEAR = %SYSFUNC(YEAR(&ST_DT));
%LET ST_MONTH = %SYSFUNC(MONTH(&ST_DT));
%LET ST_YYYYMM = %EVAL(&ST_YEAR*100+&ST_MONTH);
%LET ED_DT = %SYSFUNC(INTNX(MONTH,&START_DATE,&J,B));
%LET ED_YEAR = %SYSFUNC(YEAR(&ED_DT));
%LET ED_MONTH = %SYSFUNC(MONTH(&ED_DT));
%LET ED_YYYYMM = %EVAL(&ED_YEAR*100+&ED_MONTH);
%LET START_YYYYMM = &ST_YYYYMM;%LET END_YYYYMM = &ED_YYYYMM;
ROLLRATE_&START_YYYYMM._&END_YYYYMM;
%END;
%END;
;
RUN;
%MEND DATE_LOOP1;
%DATE_LOOP2(201901,201902);
You can use name lists in the SET statement and avoid macro altogether.
Every dataset whose name starts with rollrate will be stacked:
data want;
set work. rollrate:;
run;
There are two issues I am currently seeing
The %mend for second loop is date_loop1 instead of date_loop2 (Second last line of code)
%MEND DATE_LOOP1; /Should be DATE_LOOP2/
%DATE_LOOP2(201901,201902);
Please remove semicolon after ROLLRATE_&START_YYYYMM._&END_YYYYMM in date_loop2 macro, because there is a ; after end statements
Turn on the MPRINT option to see what code your macro is generating. The second one is generating code like:
MPRINT(DATE_LOOP2): data master_rollrate;
MPRINT(DATE_LOOP2): set rollrate_201901_201901;
NOTE: Line generated by the macro variable "ED_YYYYMM".
1 rollrate_201901_201902
----------------------
180
MPRINT(DATE_LOOP2): rollrate_201901_201902;
NOTE: Line generated by the macro variable "ED_YYYYMM".
1 rollrate_201902_201902
----------------------
180
MPRINT(DATE_LOOP2): rollrate_201902_201902;
MPRINT(DATE_LOOP2): ;
MPRINT(DATE_LOOP2): run;
Remove the extra semi-colon in the middle of the nested %do loops since you are only generating PART of a statement in those loops.
You can simplify your macro code a great deal by using informats and formats to convert your YYYYMM digit strings to and from actual date values.
%macro date_loop2(start,end);
%local start_date end_date dif i j st_yyyymm ed_yyyymm;
%let start_date = %sysfunc(inputn(&start.01,yymmdd8));
%let end_date = %sysfunc(inputn(&end.01,yymmdd8));
%let dif=%sysfunc(intck(month,&start_date,&end_date));
data master_rollrate;
set
%do i=0 %to &dif;
%do j=&i %to &dif;
%let st_yyyymm = %sysfunc(intnx(month,&start_date,&i,b),yymmn6);
%let ed_yyyymm = %sysfunc(intnx(month,&start_date,&j,b),yymmn6);
rollrate_&st_yyyymm._&ed_yyyymm
%end;
%end;
;
run;
%mend date_loop2;

Assign filenames through the loop in SAS

I have a list of files that I need to import into SAS. I need to assign filename so that I don't type multiples times the same lines:
filename inf_1_1 'C:\Users\Main_1\final_complete_1.1.csv';
filename inf_1_2 'C:\Users\Main_1\final_complete_1.2.csv';
I tried to write %macro but it does not recognize the file name:
%macro FILENAME (I_FROM=1, I_TO=&I_FROM, J_FROM=1, J_TO=&J_FROM);
%local I J;
%do I = &I_FROM %to &I_TO;
%do J = &J_FROM %to &J_TO;
filename inf_&I._&J. 'C:\Users\Main_&I.\final_complete_&I.&J.csv';
%end;
%end;
%mend;
%FILENAME(J_TO=2); */
I am sure I am doing smth wrong and need to take the filename out of the string variable. But I don't know how to do it.
Any help will be highly appreciated.
See the modifications below - basically reduce the filename string to import the data.
List of issues:
Macro variables resolve only in double quotes, not single quotes
Macro call - don't include the & in the macro definition
FileRefs are limited to 8 characters
FileRefs are not necessarily needed
%macro FILENAME (I_FROM=1, I_TO=I_FROM, J_FROM=1, J_TO=J_FROM);
%local I J;
%do I = &I_FROM %to &I_TO;
%do J = &J_FROM %to &J_TO;
filename i_&I._&J. "C:\Users\Main_&I.\final_complete_&I.&J..csv";
proc import out=data_&i._&j. datafile=i_&i._&j. dbms=csv; run;
%end;
%end;
%mend;
%FILENAME(I_FROM=1, I_TO=10, J_FROM=1, J_TO=2);
As mentioned you don't really need the filename reference.
%macro FILENAME (I_FROM=1, I_TO=I_FROM, J_FROM=1, J_TO=J_FROM);
%local I J;
%do I = &I_FROM %to &I_TO;
%do J = &J_FROM %to &J_TO;
proc import out=data_&i._&j. datafile="C:\Users\Main_&I.\final_complete_&I.&J..csv" dbms=csv; run;
%end;
%end;
%mend;
%FILENAME(I_FROM=1, I_TO=10, J_FROM=1, J_TO=2);
Last but not least, you don't need a macro here:
data _null_;
do i=1 to 2;
do j=1 to 2;
*create filename;
file_name = catt('C:\users_Main\Main_', i, '\final_complete_', i, '_', j, '.csv');
*create output data set name;
outdata = catt('data_', i, '_', j);
*create command to import;
str = catt('proc import out=', outdata, ' datafile=', filename, ' dbms=csv; run;');
call execute(str);
end;
end;
run;
Thank you very much for the hints. I've written the following macro:
%macro Filename_loop (I_FROM=1, I_TO=&I_FROM, J_FROM=1, J_TO=&J_FROM);
%local I J;
%do I = &I_FROM %to &I_TO;
%do J = &J_FROM %to &J_TO;
filename inf_&I._&J. "C:\Users\Main_&I.\final_complete_&I..&J..csv";
%end;
%end;
%mend;
%Filename_loop(J_TO=2);

SAS Arrays within Macro

I try to use a scatterplot template I created within a loop so that I can catch every plot combinations but I see that I cannot use call symputX for arrays. How can I find a turnaround for such cases?
Above you can find one of the scatterplot example for only one combination.
Thank you
data work.mycsv;
set work.mycsv;
array temp[3] x y z;
call symputX('temp',temp);
run;
%macro scatter();
%let i = 1;
%do %while (&i <= 3);
%let j = %sysevalf(&i+1);
%do %while(&j <= 3);
%if &i ne &j %then %do;
proc template;
define statgraph scatterplot;
begingraph;
entrytitle "Title";
layout overlay;
scatterplot x=&&temp[&i.] y=&&temp[&j.] /
group=Survived name="scatter" datalabel=Response;
discretelegend "scatter";
endlayout;
endgraph;
end;
%end;
%let j =%sysevalf(&j + 1);
%end;
%let i = %sysevalf(&i + 1);
%end;
%mend scatter;
%scatter();
proc sgrender data=work.mycsv template=scatterplot;
run;
Usually if you want to store a list of values in macro variables you should just use a delimited string. If the list is of variable names then it is easiest to use space as the delimiter.
%let varlist= X Y Z ;
Then you could easily construct macro logic to find all two way combinations.
%let nitems=%sysfunc(countw(&varlist));
%do i=1 %to %eval(&nitems-1);
%let var1=%scan(&varlist,&i);
%do j=%eval(&i+1) %to &nitems ;
%let var2=%scan(&varlist,&j);
....
%end;
%end;
%macro scatter();
%let varlist = x y z;
%let i = 1;
%do %while (&i <= 3);
%let j = %sysevalf(&i+1);
%do %while(&j <= 3);
%if &i ne &j %then %do;
%let x_used=%qscan(%bquote(&varlist),&i);
%let y_used=%qscan(%bquote(&varlist),&j);
proc template;
define statgraph scatterplot;
begingraph;
entrytitle "&x_used and &y_used by Response";
layout overlay;
scatterplot x=&x_used y=&y_used /
group=Survived name="scatter" datalabel=Response;
discretelegend "scatter";
endlayout;
endgraph;
end;
proc sgrender data=work.mycsv template=scatterplot;
run;
%end;
%let j =%sysevalf(&j + 1);
%end;
%let i = %sysevalf(&i + 1);
%end;
%mend scatter;
%scatter();

Step size in macro's do loop in sas

So I want to run nested loop for my macro function.
Here's my code, it seem like SAS doesn't like by -1. Is there anyway I code this to let the second loop decrease step by -1?
In this case, my yearMix = 1982 and yearMax = 1994.
%Macro theLoop;
%Do I = &yearMin+1 %to &YearMax-1;
%Do J = &YearMax-1 %to &I by -1;
%Meaw;
%END;
%END;
%MEND theLoop;
%theLoop;
I got this error:
ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The condition was: &I by -1
ERROR: The %TO value of the %DO J loop is invalid.
ERROR: The macro THELOOP will stop executing.
You specify your increment in a macro %do loop using %by rather than by. Further details can be found in the user guide here.
In your code SAS is trying to evaluate &I by -1 as a numerical value.
%let yearMin = 1982;
%let yearMax = 1994;
%Macro theLoop;
%Do I = %eval(&yearMin+1) %to %eval(&YearMax-1);
%Do J = %eval(&YearMax-1) %to &I %by -1;
%put &i = &j = ;
%END;
%END;
%MEND theLoop;
%theLoop;

looping over character values in SAS

In Stata you can loop over a list of character values with the foreach command. I've been trying to do the same in SAS to no avail so far. I'm trying to run a series of data and proc statements over all the values of character column. I tried with the following:
%let mylist = a b c; * These are all the values of a column called "code";
data mydata_#mylist; * Each element creates a new table;
set mydata;
where code=&mylist;
run;
What am I doing wrong or missing?
Thanks in advance,
Matías
Try this:
%macro loopit(mylist);
%let n = %sysfunc(countw(&mylist));
%do I=1 %to &n;
%let val = %scan(&mylist,&I);
data mydata_&val;
set mydata;
where code = "&val";
run;
%end;
%mend;
%let list=a b c;
%loopit(&list);
Modified version based on what DomPazz provided:
data mydata;
length code $8;
input code;
cards;
a
a
b
c
n
;
run;
%macro loopit(mylist);
%let else=;
%let n = %sysfunc(countw(&mylist));
data
%do I=1 %to &n;
%let val = %scan(&mylist,&I);
mydata_&val
%end;
other
;
set mydata;
%do j=1 %to &n;
%let val = %scan(&mylist,&j);
%if &j ne 1 %then %do;
%let else=else;
%end;
&else if code = "&val" then output mydata_&val;
%if &j = &n %then %do;
else output other;
%end;
%end;
run;
%mend;
options mprint;
%let list=a b c;
%loopit(&list);
Here I process input data only once (for efficiency if you need) creating all output tables in one data step.
Also I'm creating an "Other" table. To process only records with code from list is desired, you can add WHERE statement under SET statement and omit else output other;
%macro getName;
%let name = %sysfunc(translate(&val, __, -/));
%mend;
%macro loopit(mylist);
%let else=;
%let name=;
%let n = %sysfunc(countw(&mylist, %str( )));
data
%do I=1 %to &n;
%let val = %scan(&mylist,&I, %str( ));
%getName
mydata_&name
%end;
other
;
set mydata;
%do j=1 %to &n;
%let val = %scan(&mylist,&j, %str( ));
%getName
%if &j ne 1 %then %do;
%let else=else;
%end;
&else if code = "&val" then output mydata_&name;
%if &j = &n %then %do;
else output other;
%end;
%end;
run;
%mend;
options mprint;
%let list=a-a b/b c-;
%loopit(&list);
This version uses COUNTW and SCAN functions with modifiers to only use space (%str( )) as a word separator.
Also uses new macro getName to compose a name for datasets according to SAS naming rules (note %let name = ; to simply provide variable inside %loopit for %getName to fill).
%getName translates not allowed characters to underscore (a potential name conflict here if you have same values with different separator).

Resources