BookmarkSubscribeRSS Feed
🔒 This topic is solved and locked. Need further help from the community? Please sign in and ask a new question.
Demographer
Pyrite | Level 9

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,

1 ACCEPTED SOLUTION

Accepted Solutions
RichardinOz
Quartz | Level 8

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

9 REPLIES 9
art297
Opal | Level 21

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

Astounding
PROC Star

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.

Tom
Super User Tom
Super User

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;

joehinson
Calcite | Level 5

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

Please see

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

RichardinOz
Quartz | Level 8

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.

Demographer
Pyrite | Level 9

Thanks RichardinOz, it works perfectly.

DanielSantos
Barite | Level 11

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

chang_y_chung_hotmail_com
Obsidian | Level 7

@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
  --*;

DanielSantos
Barite | Level 11

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

sas-innovate-2024.png

Join us for SAS Innovate April 16-19 at the Aria in Las Vegas. Bring the team and save big with our group pricing for a limited time only.

Pre-conference courses and tutorials are filling up fast and are always a sellout. Register today to reserve your seat.

 

Register now!

How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

Click image to register for webinarClick image to register for webinar

Classroom Training Available!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

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