|
Subject: RE: Problem with %if
Summary: Short macro lesson on decisions and loops
Respondent: IanWhitlock@westat.com
Sheila Whitelaw <sw75@BOXFROG.COM> asks a question about a %IF and shows her
macro. Since she said she is new to macro and shows good promise I decided
to look at some of the issues her question and example should raise. I
hope that the effort is also useful to some other struggling macro
programmers.
The business of macros is to generate SAS code. Hence %IF is used to decide
which SAS code to generate. IF, on the other hand, decides which generated
code to execute. Thus one can usually tell whether to use %IF or IF just by
asking what do I want to decide.
Now let's look at your macro, %DOIT. Bad choice of name. the name should
show function. What is it suppose to do? Ans: Generate 30 random numbers,
X and distribute based on which third of the unit interval X falls. So
RANDOM3 would be a better name. Your macro has no parameters. What would
be natural parameters? Ans: NOBS=10, OUTROOT=TEST, DEBUG=YES (should prints
be made?)
Now let's look at the code. All three files generated in one step. Good!
One should ask if it wouldn't be better to have one set and use Y to pick
out the subsets. In the long run this is usually a better strategy, but it
doesn't matter much here.
How should the assignment lines look? I.e. should you use a DO loop over
two lines or should you use a %DO loop to generate many assignments? Either
is possible and has its advantages. DO has tight code and short log, but a
little overhead to compile the DO-loop thus costing a micro second. %DO
makes a longer log, it is known as unrolling the loop. It avoids the cost
of compiling the DO but runs into the cost of compiling more assignments.
As the length of the loop grows the advantages lie with DO. Personally I
prefer the DO to keep the code as short as possible, but would not quibble
with a loop of only 10 iterations. The macro variable I should be local,
i.e. only known inside the macro while it is executing. %LOCAL statement
should declare your intentions. (Common beginner mistake.)
Now the big question - how do we make the decision to which data set? Well
we need 3 OUTPUT statements for 3 data sets so we do not want to decide
which OUTPUT statement to generate so it is probably not a %IF. The
decision is based on the value generated so it must be at SAS execution
time, hence it must be an IF (or SELECT might be better style).
More possible parameters, NSETS=3. Let the user decide whether the data
should be split out or not.
So here is my revision.
%macro Random ( outroot = test /* root for output data sets */
, split= yes /* one file or many */
, nobs = 10 /* num obs in total */
, nsets = 3 /* determines split var */
, debug = yes /* are prints wanted? */
) ;
%local i ; /* looping variable */
%let split = %upcase ( &split ) ;
%let debug = %upcase ( &debug ) ;
data %if &split = NO %then test ;
%else
%do ;
%do i = 1 %to &nsets ;
&outroot&i
%end ;
%end ;
;
drop j ;
do j=1 to 10;
x=ranuni(0);
y=mod(int(10*x),&nsets);
%if &split = YES %then output test ;
%else
%do ;
select ( y ) ;
%do i = 1 %to &nsets ;
when (&i) output &outroot&i ;
%end ;
otherwise error ;
end ;
%end ;
run;
%if &debug = YES %then
%do ;
%if &split = YES %then
%do ;
%do i=1 %to &nsets ;
proc print data=&outroot&i;
title2 "&outroot&i";
run;
%end ;
%end ;
%else
%do ;
proc print data=&outroot&i;
title2 "&outroot&i";
run;
%end ;
%end;
%mend random ;
Note that indentation is used to show the structure of the program. Macro
decisions and looping are needed where used. The WHEN statements are in SAS
not macro, however, a macro loop is used to generate them because I have not
assumed a fixed number of them.
The code is somewhat harder, but it introduces a lot of the problems you
should be thinking about. It also illustrates how concentration on the
parameters leads to a more interesting and useful macro.
Oh, I never got around to the original question - why only one record in
each data set and always the same value? Well the letter Y is never a digit
so all the %IF tests in Sheila's code fail. Hence the DATA step generated
has not OUTPUT statement. Thus she generates 10 assignements to each X and
Y. The last pair of assignments is output by default to all three data
sets.
IanWhitlock@westat.com
-----Original Message-----
From: Sheila Whitelaw [mailto:sw75@BOXFROG.COM]
Sent: Thursday, May 23, 2002 11:10 AM
To: SAS-L@LISTSERV.UGA.EDU
Subject: Problem with %if
Hi, I am new to macro. I tried out the following codes which are
supposed to generate 3 datasets based on the value of the remainder.
However only one record is output to each dataset. The code is working
if I use if instead of %if. Can anyone tell me what I did wrong?
Code:
%macro DOIT;
data test1 test2 test3;
%do j=1 %to 10;
x=ranuni(0);
y=mod(int(10*x),3);
%if y=0 %then output test3;
%else %if y=1 %then output test1;
%else %if y=2 %then output test2;
%end;
run;
%do i=1 %to 3;
proc print data=test&i;
title2 "test&i";
run;
%end;
%mend DOIT;
%DOIT;
Output:
test1
OBS X Y
1 0.84581 2
test2
OBS X Y
1 0.84581 2
test3
OBS X Y
1 0.84581 2
Thanks.
|