7

I'm searching for a while an equivalent of the for in loop (like in Python or in R) in SAS 9.3 macro language. The DO loop seem's to be the solution but did't work exactly as I want. I founded a way to do it in a data step with a DO loop but it don't work with the macro language. For example, in a data step, this code is working :

DATA _NULL_;
  DO i = 1,3,5,9;
    PUT i;
  END;
RUN;

And then the log prompt as expected :

1
3
5
9

When I try to do the same with an %DO loop in a Macro, I have an error.

%MACRO test();
  %DO i = 1,2,4,9 ;
    %PUT i = &i;
  %END;
%MEND;

%test();

The log promp these messages :

ERROR: Expected %TO not found in %DO statement.
ERROR: A dummy macro will be compiled

I'm quite new in SAS and stackoverflow so I hope my question is no too stupid. It's so simple to do this in Python and R then it must have a simple way to do it in SAS.

Thank's for help - J. Muller

Joe
  • 62,789
  • 6
  • 49
  • 67
jomuller
  • 1,032
  • 2
  • 10
  • 19

2 Answers2

6

The closest I've ever come across to this pattern in SAS macro language is this:

%MACRO test();

%let j=1;
%let vals=1 2 4 9;
%do %while(%scan(&vals,&j) ne );
  %let i=%scan(&vals, &j);

  %put &i;

  %let j=%eval(&j+1);
%end;
%MEND;

%test();

(Warning: untested, as I no longer have a SAS installation I can test this out on.)

Joe
  • 62,789
  • 6
  • 49
  • 67
Simon Nickerson
  • 42,159
  • 20
  • 102
  • 127
  • I tried it and it's working perfectly. It's not as simple as Python or R syntax but it work well with characters list too. Thank's! – jomuller Mar 22 '13 at 15:23
4

You can certainly get around it this way:

options mindelimiter=,;
options minoperator;
%MACRO test();
  %DO i = 1 %to 9 ;
    %if &i in (1,2,4,9) %then %do;
    %PUT i = &i;
  %END;
  %end;
%MEND;

%test();

However, I think you can usually avoid this sort of call by executing your macro multiple times rather than attempting to control the loop inside the macro. For example, imagine a dataset and a macro:

data have;
input x;
datalines;
1
2
4
9
;;;;
run;

%macro test(x);
%put &x;
%mend test;

Now you want to call %test() once for each value in that list. Okay, easy to do.

proc sql;
select cats('%test(',x,')') into :testcall separated by ' ' from have;
quit;

&testcall;

That works just as well as your %do in loop, except it's data driven, meaning if you want to change the calls you just change the dataset (or if your data changes, the call automatically changes!). In general, SAS is more effective when designed as data driven programming rather than as entirely written code.

Joe
  • 62,789
  • 6
  • 49
  • 67
  • I used something similar for numerical lists but – jomuller Mar 22 '13 at 14:50
  • 1
    Yeah, if you want to do character lists you need to scan the list like in Simon's example. I would note that the SAS macro language isn't exactly a fully featured language - don't expect it to be. Most things can be done within base SAS, without doing serious programming in the macro language beyond avoiding repetitive code. Example added to the answer. – Joe Mar 22 '13 at 14:52
  • (end of my sentence, sorry technical problem) ... it's not working for a character list I believe. – jomuller Mar 22 '13 at 14:58
  • 1
    Thank you for you answer! If I understand well, the short answer could be "there is no _for in_ loop in SAS macro langage but there is some tips to do something similar" ? – jomuller Mar 22 '13 at 15:16
  • That would make sense, yes. – Joe Mar 22 '13 at 15:17