I was just trying to learn SAS more thoroughly, I came across the below code can anyone explain what does this code is performing. (much appreciated if you could do a line by line explanation except data and set. xD)
%macro mymacro(k);
data want;
set temp;
%do i = 1 %to &k;
if _N_ = &i then y = %eval(&i.* 10);
%end;
run;
%mend;
data _null_;
call execute ('%mymacro(6)');run;
Kindly, help.
Thanks in advance. Much Appreciated. 🙂
Hi,
First of of all, this code is just an ugly version of:
data want;
set temp;
if _N_ <= 6 then y = _N_*10;
run;
but lets got through it.
Starting from the end:
data _null_;
call execute ('%mymacro(6)');run;
this part is running call execute() function to run macro "mymacro" defined just before that data step. Such code can be justified in a situation when you want to use data from some dataset as input parameters for some macro e.g., a dataset contains variable "date" and you want to run a macro for each value of "date" from that dataset. In case of the code we are looking at it could be just replaced with simple:
%mymacro(6)
The part:
%macro mymacro(k);
...
%mend;
defines a macro named "mymacro". Macros, in short words, are the way to make (or rather "add to") static 4GL language more dynamics. Or, another "short", macros are "code which writes code".
Inside the macro definition you can see both 4GL code and macro code. 4GL code is for example:
data want;
set temp;
if _N_ = then y = ;
run;
and macro code is:
%do i = 1 %to &k;
&i %eval(&i.* 10);
%end;
The %do-loop is a macro loop which will repeat and generate text which is in its body i.e.,
if _N_ = &i then y = %eval(&i.* 10);
(so mix of 4GL and macro language) into pure 4GL. For example the first iteration will give you:
if _N_ = 1 then y = 10;
and the 5th iteration will give:
if _N_ = 5 then y = 50;
so the "final code generated by the macro", and executed for value 6 will be:
data want;
set temp;
if _N_ = 1 then y = 10;
if _N_ = 2 then y = 20;
if _N_ = 3 then y = 30;
if _N_ = 4 then y = 40;
if _N_ = 5 then y = 50;
if _N_ = 6 then y = 60;
run;
But the best way to check it out would be to generate a test dataset temp:
data temp;
do x=1 to 10;
output;
end;
run;
and run the code.
All the best
Bart
Great explanation from @yabwon !
I add that if you see macro code, and don't understand what it is doing, you can turn on macro debugging options before you run the code.
options mprint;
So now if you run the code
%macro mymacro(k);
data want;
set temp;
%do i = 1 %to &k;
if _N_ = &i then y = %eval(&i.* 10);
%end;
run;
%mend;
%mymacro(6)
then you see this in the log, this is the code generated by the macro
MPRINT(MYMACRO): data want; MPRINT(MYMACRO): set temp; MPRINT(MYMACRO): if _N_ = 1 then y = 10; MPRINT(MYMACRO): if _N_ = 2 then y = 20; MPRINT(MYMACRO): if _N_ = 3 then y = 30; MPRINT(MYMACRO): if _N_ = 4 then y = 40; MPRINT(MYMACRO): if _N_ = 5 then y = 50; MPRINT(MYMACRO): if _N_ = 6 then y = 60; MPRINT(MYMACRO): run;
So this is yet another way to see what macro code is doing.
Also it is worth pointing out that this code is overly complicated. You can easily get the same result without a macro at all:
%let k = 6;
data want;
set temp;
if _N_ <= &k then y = _N_* 10;
run;
:
This code is a textbook example for the abuse of the macro language. It illustrates the inefficiency of repeatedly creating code vs. using repeating code.
Let's make a small dataset:
data have;
do x = 1 to 10000;
output;
end;
run;
69 data have; 70 do x = 1 to 10000; 71 output; 72 end; 73 run; NOTE: The data set WORK.HAVE has 10000 observations and 1 variables. NOTE: Verwendet wurde: DATA statement - (Gesamtverarbeitungszeit): real time 0.00 seconds user cpu time 0.00 seconds system cpu time 0.00 seconds
As you can see, this step runs in almost no time.
Now, let's apply your macro to half of the dataset:
%macro mymacro(k);
data want;
set have;
%do i = 1 %to &k.;
if _n_ = &i. then y = %eval(&i. * 10);
%end;
run;
%mend;
%mymacro(5000)
69 %macro mymacro(k); 70 data want; 71 set have; 72 %do i = 1 %to &k.; 73 if _n_ = &i. then y = %eval(&i. * 10); 74 %end; 75 run; 76 %mend; 77 %mymacro(5000) NOTE: There were 10000 observations read from the data set WORK.HAVE. NOTE: The data set WORK.WANT has 10000 observations and 2 variables. NOTE: Verwendet wurde: DATA statement - (Gesamtverarbeitungszeit): real time 0.79 seconds user cpu time 0.78 seconds system cpu time 0.01 seconds
Now, use the simple code which uses the internal loop of the data step itself and only data step language:
%let k = 5000;
data want2;
set have;
if _n_ le &k. then y = _n_ * 10;
run;
69 %let k = 5000; 70 71 data want2; 72 set have; 73 if _n_ le &k. then y = _n_ * 10; 74 run; NOTE: There were 10000 observations read from the data set WORK.HAVE. NOTE: The data set WORK.WANT2 has 10000 observations and 2 variables. NOTE: Verwendet wurde: DATA statement - (Gesamtverarbeitungszeit): real time 0.00 seconds user cpu time 0.00 seconds system cpu time 0.00 seconds
Wow! While the use of data step language runs in next to no time (like the creation of the dataset itself), the macro loop consumes a whopping 0.8 seconds! Why is this?
The macro loop creates 5000 IF statements and runs them 10000 times, leading to the execution of 50 million logical evaluations during the data step. Such evaluations are the most time-consuming operations for any CPU (except in quantum computers). And you also must consider that compiling 5000 lines of code itself takes much longer than compiling a single line.
The data step solution runs just 10000 IFs, so it stays well within the limits set by simply reading and writing data from/to storage.
To me, this piece of code is an example for what the premature teaching of macro coding to SAS newbies causes. Before they have even an inkling of what the SAS language (procedures and data steps) is really capable of, the seemingly simple tool of the macro language misguides them, leading to code which is hard to understand and is inefficient on top.
Heed Maxim 11. First exploit the capabilities of the Base SAS language and tools, and resort to macro programming only once this is exhausted. The most important skill of a macro programmer is to know when to not use the macro language.
PS just to make sure that the codes result in the same outcome, run this:
proc compare base=want compare=want2;
var x y;
with x y;
run;
Kurt ( @Kurt_Bremser ),
Thank you for your comment, you gave a beautiful explanation to my oversimplified comment about the original code, which was:
First of of all, this code is just an ugly version of:
data want;
set temp;
if _N_ <= 6 then y = _N_*10;
run;
😀
All the best
Bart
@yabwon @Kurt_Bremser It's also a classic example of someone else's code leading a SAS beginner astray...when maybe that someone else should know better.
I could wish that some of my inherited code was only this poor...
Are you ready for the spotlight? We're accepting content ideas for SAS Innovate 2025 to be held May 6-9 in Orlando, FL. The call is open until September 16. Read more here about why you should contribute and what is in it for you!
SAS' Charu Shankar shares her PROC SQL expertise by showing you how to master the WHERE clause using real winter weather data.
Find more tutorials on the SAS Users YouTube channel.