BookmarkSubscribeRSS Feed
🔒 This topic is solved and locked. Need further help from the community? Please sign in and ask a new question.
SAS_inquisitive
Lapis Lazuli | Level 10

Hello,
%wide outer macro contains three macros %meta, %sum and %freq in this paper(http://www.lexjansen.com/pharmasug/2012/CC/PharmaSUG-2012-CC18.pdf). How to see the execution of these inner macros while the outer macro %wide executes? Wouldn't it be good not to nest these inner macros? Thanks !

%macro wide(indsn=, outdsn=wide,
			cols=,
			rows=);
	%local colvar collist rowvar rowlist i ncol label2 loop;

	/* MACRO: give variable metadata of &var */
%macro meta(var);
	%global type label;
	%local num dsid rc;
	%let dsid=%sysfunc(open(&indsn,i));
	%let num=%sysfunc(varnum(&dsid,&var));
	%let type=%sysfunc(vartype(&dsid,&num));
	%let label=%sysfunc(varlabel(&dsid,&num));
	%let rc=%sysfunc(close(&dsid));
%mend meta;

%let colvar=%scan(&cols,1,=);
%let collist=%scan(&cols,2,=);

/* create new column variable _COL */
%let i=1;

data temp1;
	set &indsn;

	%do %while (%length(%scan(&collist,&i,:))>0);
		if &colvar in (%scan(&collist,&i,:)) then
			do;
				_col=&i;
				output;
			end;

		%let ncol=&i;
		%let i=%eval(&i+1);
	%end;
run;

/* population counts */
proc sql noprint;
	%do i=1 %to &ncol;
		%global n&i;
		select count(_col) into :n&i from temp1(keep=_col where=(_col=&i));
	%end;
quit;

/* MACRO: create summary stats from &var by _COL */
%macro sum(var);

	proc means data=temp1(keep=_col &var) noprint nway;
		class _col;
		var &var;
		output out=temps1 n=n median=median mean=mean std=sd min=min max=max;
	run;

	data temps2;
		length n1 msd m1 mm $100;
		set temps1;

		if mean ne . and sd ne . then
			msd=strip(put(mean, 12.1))||" ("||strip(put(sd,12.2))||")";
		else if mean ne . and sd=. then
			msd=strip(put(mean, 12.1))||" (NA)";

		if min ne . then
			mm=strip(put(min,12.1))||", "||strip(put(max,12.1));

		if n ne . then
			n1=strip(put(n,best12.));

		if median ne . then
			m1=strip(put(median, 12.1));
		keep _col n1 msd m1 mm;
		label N1='n' MSD='Mean (SD)' M1='Median' MM='Min, Max';
	run;

	proc transpose data=temps2 out=temp2(drop=_name_ rename=(_label_=desc));
		id _col;
		var n1 msd m1 mm;
	run;

%mend sum;

/* MACRO: create freq stats from &var by &list and _COL */
%macro freq(var, list);
	%local i;

	proc freq data=temp1(keep=_col &var where=(&var ne '')) noprint;
		table _col*&var/out=tempf1;
	run;

	proc sql noprint;
		create table tempf2 as
			select _col, &var length=100 as desc,
				strip(put(count,best12.)) length=100 as freq
			from tempf1 order by desc;
	quit;

	proc transpose data=tempf2 out=tempf3(drop=_name_);
		var freq;
		by desc;
		id _col;
	run;

	%if %length(&list)>0 %then
		%do;

			data tempshell;
				length desc $100;
				%let i=1;

				%do %while (%length(%scan(&list,&i,:))>0);
					desc=%scan(&list,&i,:);
					desc=tranwrd(tranwrd(tranwrd(desc,'(fs)','/'),'(pd)','#'),'(eq)','=');
					c=&i;
					output;
					%let i=%eval(&i+1);
				%end;
			run;

			proc sql noprint;
				create table tempf4(drop=desc1 c) as
					select a.desc, b.*, a.c from
						tempshell a left join tempf3(rename=(desc=desc1)) b on a.desc=b.desc1
					order by c;
			quit;

		%end;
	%else
		%do;

			data tempf4;
				set tempf3;
			run;

		%end;

	data temp2;
		set tempf4;

		%do i=1 %to &ncol;
			if _&i='' then
				_&i='0';
			else _&i=strip(_&i)||' ('||strip(put(input(_&i,best12.)/&&n&i*100,8.1))||'%)';
		%end;
	run;

%mend freq;

/* empty dataset to build on */
data &outdsn;
	stop;
run;

/* loop through variables in &rows */
%let i=1;

%do %while (%length(%scan(&rows,&i,/))>0);

	/* &loop holds one variable for one section */
	%let loop=%scan(&rows,&i,/);

	/* break &loop down into variable and list */
	%let rowvar=%scan(&loop,1,=#);
	%let rowlist=%scan(%scan(&loop,2,=),1,#);

	%meta(&rowvar);

	/* see if label for section is specified, if not use variable label */
	%let label2=%scan(&loop,2,#);

	%if %length(&label2)=0 %then
		%let label2="&label";

	/* call summary stats of freq stats depending on vartype */
	%if &type=N %then
		%sum(&rowvar);
	%else %freq(&rowvar,&rowlist);

	/* temp section label dataset */
	data templ;
		desc=&label2;
	run;

	/* append sections one by one, indent section and convert delimiter replacements
	 */
	data &outdsn;
		length desc _1-_&ncol $100;
		set &outdsn templ(in=a) temp2(in=b);

		if a or b then
			order=&i;

		if b then
			desc=' '||desc;
		desc=tranwrd(tranwrd(tranwrd(desc,'(fs)','/'),'(pd)','#'),'(eq)','=');
	run;

	%let i=%eval(&i+1);
%end;

/* delete temp datasets */
proc datasets library=work;
	delete temp:;
quit;

%mend wide;

 

1 ACCEPTED SOLUTION

Accepted Solutions
Tom
Super User Tom
Super User

I think it is not understanding how macro variable scoping works that causes people to nest macro definitions inside other macro defintions. Whether you place the code to define the macro inside the definition of another macro or not does not change the actual defintion of the macro.  It does not define the scope of what macro variables are visible when the inner macro runs, that is determined by when the inner macro is actually called.  

 

But by placing the macro defintion for the inner macros within the outer macro it does mean that when you run the outer macro (and the lines of code that define the inner macros are re-compiled) then the inner macro is redefined.

 

Here is a little test you use to access metadata see it in action. Notice how the datetime stamps in DICTIONARY.CATALOGS change for the inner macros as the outer macro is re-run. (Note the MEMNAME you need to look in might change depending on how your SAS sessions is launched)

%macro wide ;
  %put &=sysmacroname ;
%macro inner1;
  %put &=sysmacroname ;
%mend inner1;
%macro inner2;
  %put &=sysmacroname ;
%mend inner2;
  %inner1 ;
  %inner2 ;
%mend wide ;
proc sql ;
  create view mymacros as
    select objname,created format=datetime22.2
    from dictionary.catalogs
    where libname='WORK' and memname='SASMACR'
    and memtype='CATALOG' and objtype='MACRO'
    and objname in ('WIDE','INNER1','INNER2')
  ;
quit;
proc print data=mymacros;
  title1 'Before First Call';
run;
%wide ;
proc print data=mymacros;
  title1 'After First Call';
run;
%wide ;
proc print data=mymacros;
  title1 'After Second Call';
run;
title1 ;

View solution in original post

8 REPLIES 8
PaigeMiller
Diamond | Level 26

@SAS_inquisitive wrote:

Hello,
%wide outer macro contains three macros %meta, %sum and %freq in this paper(http://www.lexjansen.com/pharmasug/2012/CC/PharmaSUG-2012-CC18.pdf). How to see the execution of these inner macros while the outer macro %wide executes? Wouldn't it be good not to nest these inner macros? Thanks !

 


I agree, nesting of macros like this is not a good thing to do.

 

I don't know what you mean by "how to see...", but I'll take a guess, you want to turn on options MPRINT and MPRINTNEST.

--
Paige Miller
ballardw
Super User

I am not sure what you mean by

How to see the execution of these inner macros while the outer macro %wide executes?

If you mean to see all of the code generated then you may want to set Options mprint mprintnest;

 

 

Wouldn't it be good not to nest these inner macros?

 

Most programmers will realize that nesting macro definitions is usually not a good idea. If nothing else most times that is done all you do is recompile the same the macro over and over. This may be a symptom of the programmer not wanting to or understanding how to pass values as parameters.

 

I'm not going to go through that code but

SAS_inquisitive
Lapis Lazuli | Level 10

@ballardw you wrote "If nothing else most times that is done all you do is recompile the same the macro over and over. This may be a symptom of the programmer not wanting to or understanding how to pass values as parameters."

 

Can you shed a lignt on this "This may be a symptom of the programmer not wanting to or understanding how to pass values as parameters"? And how macro is recomplied over and over?  Thanks !

Tom
Super User Tom
Super User

I think it is not understanding how macro variable scoping works that causes people to nest macro definitions inside other macro defintions. Whether you place the code to define the macro inside the definition of another macro or not does not change the actual defintion of the macro.  It does not define the scope of what macro variables are visible when the inner macro runs, that is determined by when the inner macro is actually called.  

 

But by placing the macro defintion for the inner macros within the outer macro it does mean that when you run the outer macro (and the lines of code that define the inner macros are re-compiled) then the inner macro is redefined.

 

Here is a little test you use to access metadata see it in action. Notice how the datetime stamps in DICTIONARY.CATALOGS change for the inner macros as the outer macro is re-run. (Note the MEMNAME you need to look in might change depending on how your SAS sessions is launched)

%macro wide ;
  %put &=sysmacroname ;
%macro inner1;
  %put &=sysmacroname ;
%mend inner1;
%macro inner2;
  %put &=sysmacroname ;
%mend inner2;
  %inner1 ;
  %inner2 ;
%mend wide ;
proc sql ;
  create view mymacros as
    select objname,created format=datetime22.2
    from dictionary.catalogs
    where libname='WORK' and memname='SASMACR'
    and memtype='CATALOG' and objtype='MACRO'
    and objname in ('WIDE','INNER1','INNER2')
  ;
quit;
proc print data=mymacros;
  title1 'Before First Call';
run;
%wide ;
proc print data=mymacros;
  title1 'After First Call';
run;
%wide ;
proc print data=mymacros;
  title1 'After Second Call';
run;
title1 ;
ballardw
Super User

if you have a macro that looks like this:

 

%macro big(parameter);

     %macro one;

     %mend;

     %macro two;

      %mend;

 

      <code including calls to macro on and two>

%mend;

when you call big multiple times:

 

%big(abc);

%big(cdf);

EACH time you call for %big to run then macros one and two are compiled. If the macros one and two are "large" and you call macro big many times you can cause much uneeded work.

 

Parameters are how you tell macros what input values the macro is to process.

 

Patrick
Opal | Level 21

@SAS_inquisitive

You can use the following options to get more log about macro execution:

options mprint mlogic;

 

There is nothing wrong in nesting macro calls (except that it makes the code hard to read) but you should never nest macro definitions.

The paper you've referenced does it the right way.

 

Don't write code like below as this will re-compile the inner macro over and over again.

%macro outer();

  %macro inner();

  %mend;

%mend;

%outer();

Write instead:

%macro inner();

%mend;

%macro outer();
  %inner();
%mend;

%outer();
SAS_inquisitive
Lapis Lazuli | Level 10
@Patrick, I have not written code like this (nesting macro). But I have inherited several long macros which includes a lot of nesting.
Patrick
Opal | Level 21

@SAS_inquisitive

Then - in case you have to touch the code - move the macro definitions out of the outer macro and only leave the macro calls in the macro.

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
  • 8 replies
  • 1327 views
  • 6 likes
  • 5 in conversation