Nested Do Loops in SAS - loops

I'm trying to figure out how to nest these loops so that I can generate some multilevel data. I keep getting an error, though. I'm really new to SAS so I'm struggling to figure out how to fix this. Thanks for any help!
%macro MLM;
data x;
call streaminit(525600);
do rep=1 to 1000;
%do a=1 %to 4;
%if &a=1 %then %do; %let N=100; %end;
%if &a=2 %then %do; %let N=250; %end;
%if &a=3 %then %do; %let N=500; %end;
%if &a=4 %then %do; %let N=1000; %end;
do J=1 to &N;
u0= RAND('NORMAL',0,1);
w1= RAND('BERNOULLI',.5);
do I=1 to (10000/&N);
%do b = 1 %to 5;
%if &b=1 %then %do; %let e=RAND('NORMAL',0,1); %end;
%if &b=2 %then %do; %let e=RAND('UNIFORM'); %end;
%if &b=3 %then %do; %let e=RAND('CHISQUARE', &N-1); %end;
%if &b=4 %then %do; %let e=RAND('LOGNORMAL'); %end;
%if &b=5 %then %do; %let e=RAND('BETA',1,1); %end;
x1= RAND('BERNOULLI',.5);
b0 = 2.5+ 1*w1+u0;
b1 = 0.15;
y=b0+b1*x1+&e;
output;
%end;
end;
end;
%end;
end;
%mend MLM; run; %MLM; run;
I want to run 1000 simulations, with 4 sample sizes (a, N), then generate N samples, during which I want to simulate 5 different error distributions. So, in the end, I should get 1000x4x5 total samples.
Thanks again!

Removing the macro logic and simplifying it slightly gets you this, which seems to work correctly to me. Note I simplified the N to ensure it runs quickly. Note that I'm not verify the logic, assuming that's correct on your side.
data x;
call streaminit(525600);
do rep=1 to 5;
do a=1 to 4;
if a=1 then
N=100;
else if a=2 then
N=250;
else if a=3 then
N=500;
else if a=4 then
N=1000;
do J=1 to N;
u0=RAND('NORMAL', 0, 1);
w1=RAND('BERNOULLI', .5);
do I=1 to (2000/N);
do b=1 to 5;
if b=1 then
e=RAND('NORMAL', 0, 1);
else if b=2 then
e=RAND('UNIFORM');
else if b=3 then
e=RAND('CHISQUARE', N-1);
else if b=4 then
e=RAND('LOGNORMAL');
else if b=5 then
e=RAND('BETA', 1, 1);
x1=RAND('BERNOULLI', .5);
b0=2.5+ 1*w1+u0;
b1=0.15;
y=b0+b1*x1+e;
output;
end;
end;
end;
end;
end;
run;

Related

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);

How to use loop with a macro in SAS

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.

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();

SAS code, loop index and more, how to simplify

I have SAS code below and then can run. The challenge is I have to repeat them so many times and it looks really awkward. I need Trans1-Tran2, TransOut1-TransOut100, and the BBB, AAA, VCS are user defined and no trend to follow.
%transform(Trans1,BBB,TransOut1);
%transform(Trans2,AAA,TransOut2);
%transform(Trans3,VCS,TransOut3);
%transform(Trans4,REM,TransOut4);
I am thinking combine do loop and Scan in my code but I do not know how to use index in the variable name so I cannot test my idea. Sample code that cannot run.
%let second= BBB, AAA, VCS,REM;
%macro trans;
%do i=1 %to 4;
%transform(Trans(i)?,%scan(&second.,&i),TransOut(i);
%end;
%mend;
I post some testing code below:
%macro test(data, var);
Data &data.;
Fname= "John";
Phone= 123;
City="Chicago";
Zip=65456;
keep &var.;
Run;
%mend;
%test(test1, Phone);
%test(test2, Fname);
%test(test3, City);
%test(test4, Zip);
You got it almost right already.
(Best leave out the comma in your list).
%let second= BBB AAA VCS REM;
%macro trans;
%do i=1 %to 4;
%transform(Trans&i,%scan(&second.,&i),TransOut&i);
%end;
%mend;
If you are dealing with a long list, you should consider using call execute:
data init;
input index userDef $;
datalines;
1 AAA
2 BBB
3 VCS
4 REM
;
run;
data _null_;
set init;
call execute(
cats(
'%transform(Trans', index, ',', userDef, ',TransOut', index,')'
)
);
run;
edit: using cats now, as suggested by Joe
In addition to Jetzler's answer, note that depending on the situation, it may be easier to include the loops in your macro:
%let trans = trans1 trans2 trans3 trans4;
%let trips = aaa bbb rem vcs;
%let outs = transout1 transout2 transout3 transout4;
%macro transform;
%do i = 1 %to %sysfunc(countw(&trans.));
%do j = 1 %to %sysfunc(countw(&trips.));
%do k = 1 %to %sysfunc(countw(&outs.));
/*your code here*/
%end;
%end;
%end;
%mend transform;
%transform;

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