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;
Related
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;
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);
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.
I'm working with SAS and I have to create some macro variables within a DO loop. This is a portion of my code:
%if &dsempty888=0 %then %do;
data _null_;
set freq_&&var&i;
if &&var&i=888888888 then do;
call symput("cont8_&&var&i",percent);
end;
run;
%end;
%if &dsempty888=1 %then %do;
%let cont8_&&var&i=0;
%end;
%if &dsempty999=0 %then %do;
data _null_;
set freq_&&var&i;
if &&var&i=999999999 then do;
call symput("cont9_&&var&i",percent);
end;
run;
%end;
%if &dsempty999=1 %then %do;
%let cont9_&&var&i=0;
%end;
%if &dsempty444=0 %then %do;
data _null_;
set freq_&&var&i;
if &&var&i=444444444 then do;
call symput("cont4_&&var&i",percent);
end;
run;
%end;
%if &dsempty444=1 %then %do;
%let cont4_&&var&i=0;
%end;
This code is inside another DO loop that run from i=1 to &end.
With this my macro variables cont4_&&var&i cont8_&&var&i cont9_&&var&i are costantly overwrited...and they become unuseful outside of their loop. I tried to name them &&cont4_&&var&i for example. But clearly SAS doesn't solve the macro.
In practice inside of the loop the macro are created, but I don't know how to call them when I need outside.
How can I fix?
Thanks in advance.
You've got a lot of issues here, so let's simplify this some. Here's a very simplified example. This, for example, does something:
%let var1 = age;
%let var2 = height;
%let var3 = weight;
proc freq data=sashelp.class noprint;
tables age/out=freq_age;
tables height/out=freq_height;
tables weight/out=freq_weight;
run;
%macro get_freqs(var_count=);
%do i = 1 %to &var_count.;
data _null_;
set freq_&&var&i;
call symput("cont4_&&var&i",percent);
run;
%end;
%mend get_freqs;
%get_Freqs(var_count=3)
But now if we do
%put cont4_&&var&i;
Of course it doesn't work, since &i has no meaning. So instead we replace that with, say, 1:
%put cont4_&&var1;
Now we get something. But we don't get anything useful, do we, just the variable name. But - at least that's something!
Now we don't really need the second & there, right?
%put cont4_&var1;
But we need a & before this to use the macro variable:
%put &cont4_&var1;
But now we get a warning message, CONT4_ not resolved. Okay, let's add a second & to delay the resolution of the macro variable until &var1 is resolved.
%put &cont4_&var1;
Well, that helped, now it says CONT4_AGE not resolved. Why not? We used call symput to define that, right?
The problem is scoping. call symput probably scoped it locally, meaning it was defined in the macro but not outside of the macro. For this we use call symputx and give it a global scope.
%macro get_freqs(var_count=);
%do i = 1 %to &var_count.;
data _null_;
set freq_&&var&i;
call symputx("cont4_&&var&i",percent,'g');
run;
%end;
%mend get_freqs;
This tells SAS that I want &cont4_age to be defined outside of the macro. Otherwise it will only be available locally - inside the macro - and will get cleaned up.
Now, this works:
%put &&cont4_&var1;
But if you wanted to iterate over &i again, it's a bit more complex. That's because you need to delay those ampersands multiple times, and allow several passes.
Here's what works:
%macro iterate_i(var_count=);
%do i = 1 %to &var_count.;
%put &&&&cont4_&&var&i.;
%end;
%mend iterate_i;
%iterate_i(var_count=3);
Why do we need four &s? Well, we need to get to &&cont4_&var1, right? That happens in the first pass. Here are the three passes:
&&&&cont4_&&var&i -> &&cont4_&var1 -> &cont4_age -> 5.26...
Each pass, the following happens:
&& -> & (and save for next pass)
& -> resolve macro variable
So, that's how you'd iterate over those. You can of course explicitly specify at any level the macro variable - so all of these work:
%let i=1;
%put &&&&cont4_&&var&i;
%put &&cont4_&var1;
%put &cont4_age;
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).