DATA Step, Macro, Functions and more

Repeat a macro

Accepted Solution Solved
Reply
Frequent Contributor
Posts: 113
Accepted Solution

Repeat a macro

Hi,

I've created a macro that I would like to run with 10 differents variables. I was wondering if there is a better and simple way than calling this macro 10 times, i.e.: 

%macr(var1); %macr(var2);...%macr(var10);

%macr(var1-var10) doesn't work and I notice that array/do cannot be used for this purpose neither.

Thanks,


Accepted Solutions
Solution
‎10-19-2012 07:50 PM
Super Contributor
Posts: 644

Re: Repeat a macro

Posted in reply to Demographer

Call execute in a Data _Null_ with a do loop may be what you are looking for

%Let varlist = var1 var2 var3 var4 var5 var6 var7 var8 var9 var10 ;

Data _Null_ ;

     Retain varlist "&varlist" ;

     Do k = 1 to 10 ;

          usevar = scan (varlist, k) ;

          Call execute ('%Let usevar = ' || usevar || ';') ;

          Call execute ('%macr (&usevar) ;') ;

     End ;

Run ;

(Not tested!)

Note use of single quoted

Note use of %Let statements rather than call symput, to ensure that macro values are assigned in sequence.

View solution in original post


All Replies
PROC Star
Posts: 7,480

Re: Repeat a macro

Posted in reply to Demographer

There are a number of alternatives.  Take a look at: http://www.lexjansen.com/pnwsug/2004/c_cc_storing_and_using_a_lis.pdf

Super User
Posts: 5,513

Re: Repeat a macro

Posted in reply to Demographer

One issue to watch for when you reuse macros is to eliminate redundant processing of the data.  For example:

%macro extra_work (varname);

   proc sort data=mydata out=temp;

   by year;

   run;

   proc univariate data=temp;

   by year;

   var &varname;

   run;

%mend;

Now each time you use the macro, it sorts the data.  But sorting the data just once would have been sufficient.  This pitfall takes many forms, but it is something you should watch for.

Good luck.

Super User
Super User
Posts: 7,060

Re: Repeat a macro

Posted in reply to Demographer

Really depends on what the macro is doing, can you modify the macro, etc.

My preference is to pass in variable lists as space delimited lists of variable names, just as you would in a normal SAS statements such as BY, VAR, KEEP, DROP etc.

You might be able to just put that list into the proper place in the code that the macro is generating.

For example:

%macro macr(varlist);

  proc means ;

    var &varlist ;

  run;

%mend macr;

or you might need to loop over the list inside the macro

%macro macr(varlist);

  %local i ;

  %do i=1 %to %sysfunc(countw(&varlist,%str( )));

  proc means ;

    var %scan(&varlist,&i,%str( )) ;

  run;

   %end ;

%mend macr;

Contributor
Posts: 45

Re: Repeat a macro

Posted in reply to Demographer

You may also try the PARMBUFF option with subsequent SYSPBUFF parsing.

Please see

http://www.lexjansen.com/pharmasug/2006/technicaltechniques/tt28.pdf

Solution
‎10-19-2012 07:50 PM
Super Contributor
Posts: 644

Re: Repeat a macro

Posted in reply to Demographer

Call execute in a Data _Null_ with a do loop may be what you are looking for

%Let varlist = var1 var2 var3 var4 var5 var6 var7 var8 var9 var10 ;

Data _Null_ ;

     Retain varlist "&varlist" ;

     Do k = 1 to 10 ;

          usevar = scan (varlist, k) ;

          Call execute ('%Let usevar = ' || usevar || ';') ;

          Call execute ('%macr (&usevar) ;') ;

     End ;

Run ;

(Not tested!)

Note use of single quoted

Note use of %Let statements rather than call symput, to ensure that macro values are assigned in sequence.

Frequent Contributor
Posts: 113

Re: Repeat a macro

Posted in reply to RichardinOz

Thanks RichardinOz, it works perfectly.

Super Contributor
Posts: 474

Re: Repeat a macro

Posted in reply to Demographer

If you don't mind the "twist", here's another option, macro based only, that encapsulates your macro into a second one which will handle the list of variables. Something like this:

%macro macr(VARLIST);

%if %index(%str(&VARLIST),%str(-)) %then %do; * is it a range?;

%let LDX=%sysfunc(anydigit(%str(&VARLIST)));

%let PFX=%substr(%str(&VARLIST),1,&LDX-1); * get prefix;

%let LDX=%substr(%scan(%str(&VARLIST),1,%str(-)),&LDX); * get lower index;

%let HDX=%sysfunc(anydigit(%scan(%str(&VARLIST),2,%str(-))));

%let HDX=%substr(%scan(%str(&VARLIST),2,%str(-)),&HDX); * get higher index;

%macro VARLIST;

%do _I=&LDX %to &HDX-1;&PFX&_I%str( )%end;&PFX&HDX

%mend VARLIST;

%macr(%VARLIST); * resubmit the list, no range this time;

%end;

%else %do;

* single macro var code here;

%macro _macr(VAR);

%put _macr(&VAR);

%mend _macr;

* single macro var code here;

%do _I=1 %to %sysfunc(countc(%str(&VARLIST),%str( )))+1; * iterate through var list;

%_macr(%scan(%str(&VARLIST),&_I,%str( )));

%end;

%end;

%mend macr;

* testing it;

%macr(X1-X10);

%macr(X1 X3 X4 X5 X10);

Allows a range or list of separated variable, not both.

Actually the range is expanded into the corresponding list of separated variable and is resubmited, being at most a one level recursive solution.

Cheers from Portugal.

Daniel Santos @ www.cgd.pt

Regular Contributor
Posts: 241

Re: Repeat a macro

Posted in reply to DanielSantos

@DanielSantos: sas macro does not have the lexical scope. thus no need to nest a macro definition inside the other. This case does not require calling the main function recursively either. Checking for a dash and "un-dashing" if necessary should be enough.

  %*-- "undash" a dashed list of a v# - v# type --*;
  %macro undash(dashed);
    %local prx from prefix1 prefix2 to i;
 
    %let prx = ^\s*([_A-Za-z]+)(\d+)\s*[-]\s*([_A-Za-z]+)(\d+)\s*$;
    %let prefix1 = %sysfunc(prxchange(s|&prx|$1|, 1, &dashed));
    %let from    = %sysfunc(prxchange(s|&prx|$2|, 1, &dashed));
    %let prefix2 = %sysfunc(prxchange(s|&prx|$3|, 1, &dashed));
    %let to      = %sysfunc(prxchange(s|&prx|$4|, 1, &dashed));
 
    %*-- if something is wrong then return nothing --*;
    %if &prefix1 ^= &prefix2 or &to < &from %then %return;
 
    %do i = &from %to &to - 1;
      %*;&prefix1&i
      %*;%str( )
    %end;
    %*;&prefix1&to
  %mend  undash;
 
  %*-- main --*;
  %macro doOver(varlist);
 
    %if %index(&varlist,-) %then %let varlist = %undash(&varlist);
    %if &varlist= %then %return;
 
    %local i n var;
    %let n = %sysfunc(countc(&varlist, %str( ))) + 1;
    %do i = 1 %to &n;
      %let var = %scan(&varlist, &i, %str( ));
      %*;%exec(&var)
    %end;
  %mend  doOver;
 
  %*-- check --*;
  %*-- this is our macro to call with each var --*;
  %macro exec(var);
    %put do something with &var;
  %mend  exec;
  %doOver(a1-a3)
  %doOver(b1 b3 b5)
  %*-- on log
  do something with a1
  do something with a2
  do something with a3
 
  do something with b1
  do something with b3
  do something with b5
  --*;

Super Contributor
Posts: 474

Re: Repeat a macro

Posted in reply to chang_y_chung_hotmail_com

Hi Chang, thanks for the reply but...

Non nesting the macro would imply renaming all invocations of %macr to %doOver (your suggestion), while nesting it will allow you to keep the original name. That's really why I've nested the macro and renamed the parent to the original name.

As for the recursiveness, I believe what I've showed can't be considered recursive, as there is no recursive feedback from the macro to the previous iteration. But I get your point, self-calling the macro just obfuscates the coding, which I really don't like it, also it reminded me what was my mindset at the beginning which I guess I lost on the way. Thank you for pointing that.

So here's the "revisited" code, shorter, polished and truly recursive which allows any form of mixed var list and ranges: (<X1 X2 ... XN>|<Y1-YN>)*

%macro macr(VARLIST);

* expands range recursively; 

%macro expand(VARLIST);

%if %index(%str(&VARLIST),%str(-)) %then %do;

%let PRX=%sysfunc(prxparse(s!(([_A-Z]+\d+\s+)*)([_A-Z]+)(\d+)[-]{1}([_A-Z]+)(\d+)!$3 $4 $6|$1!i));

%let PRX=%sysfunc(prxchange(&PRX,1,&VARLIST));

%macro VARLIST;

%do _I=%scan(&PRX,2) %to %scan(&PRX,3);%scan(&PRX,1)&_I%str( )%end;

%mend VARLIST;

%sysfunc(compbl(%VARLIST%expand(%scan(&PRX,2,%str(|)))))

%end;

%else %str(&VARLIST);

%mend expand;

* single macro var code here;

%macro _macr(VAR);

%put _macr(&VAR);

%mend _macr;

* single macro var code here;

%let VARLIST=%expand(&VARLIST);

%do _I=1 %to %sysfunc(countc(&VARLIST,%str( )))+1; * iterate through var list;

%_macr(%scan(%str(&VARLIST),&_I,%str( )));

%end;

%mend macr;

* so now, everything is possible;

%macr(X1-X10);

%macr(X1-X10 Y1-Y5);

%macr(X1-X10 Y1 Y3 Y6 Y8 Y9);

%macr(X1 X3 X6 X8 X10 Y1-Y5);

%macr(X1 X4 Y1-Y5 X5 X10);

%macr(X1 X3 X4 X7 X10);


Thanks for the reply.

Cheers from Portugal.

Daniel Santos @ www.cgd.pt

🔒 This topic is solved and locked.

Need further help from the community? Please ask a new question.

Discussion stats
  • 9 replies
  • 669 views
  • 3 likes
  • 8 in conversation