Hi
I am trying to generate X_1 = A_1; X_2 = A_1 A_2; ...X_5 = A_1 A_2 A_3 A_4 A_5; and following code does the job
%macro temp;
%let x_1 = A_1;
%do i= 2 %to 5;
%let x_&i. = &&x_%eval(&i.-1). %str( A_&i.);
%put &&x_&i.;
%end;
%mend;
%temp;
The issue is on every iteration it's trying to resolve &X_ as well and thus throwing a warning!
What am I doing wrong and how can I rectify it?
Thanks a lot in advance.
Good question!
The problem is that you're trying to use && to create an indirect macro reference:
%let x_&i. = &&x_%eval(&i.-1). %str( A_&i.);
This indirect reference works for when you have multiple & in a reference, for example:
%let x_&i = &&x_&j;
But in your case you are generating the second part of your macro variable name with %eval. When the macro processor (word scanner? tokenizer?) starts resolving &&x_%eval(&i.-1) it first sees the && and resolves it to &. Then it sees x_. Then it sees the % and thinks it hit the end of macro variable reference because it doesn't know %eval can generate part of the macro variable name. It knows there is still an & left in the token from the first pass, so it makes a second pass and tries to resolve &x_. It can't resolve. If you add %put _local_; to your macro, you will see you end up with the following macro variables:
TEMP I 6 TEMP X_1 A_1 TEMP X_2 &x_1. A_2 TEMP X_3 &x_2. A_3 TEMP X_4 &x_3. A_4 TEMP X_5 &x_4. A_5
While the %PUT statement resolves everything like you hoped, you haven't actually built the macro variables you want.
@PaigeMiller solved this nicely by refactoring to move the %eval out of the macro reference, avoiding the problem.
One option when you want to build a macro reference from macro statements is to use quoting functions to delay the resolution of the macro variables. It gets a little ugly (but hey, it's macro : ) but you basically quote the & and then %unquote it after the %eval has done its work. Something like:
%macro temp();
%let x_1 = A_1;
%do i= 2 %to 10;
%let x_&i = %unquote(%nrstr(&x_)%eval(&i.-1)) A_&i;
%put &&x_&i;
%end;
%put _local_ ;
%mend;
%temp()
This works for me
%macro temp;
%let x_1 = A_1;
%do i= 2 %to 5;
%let i_minus_1=%eval(&i-1);
%let x_&i = &&x_&i_minus_1 A_&i;
%put &&x_&i;
%end;
%mend;
%temp
ohh, so using %eval in same expression was giving warning?
the warnings were not the only issue in the code.
Assuming the symbol X was misspelled as x_.
NOTE: Line generated by the invoked macro "TEMP".
1 data want; do i= 2 to 5; x_&i. = compress(&&x_%eval(&i.-1). A_&i.);
--
14
1 ! );
WARNING: Apparent symbolic reference I not resolved.
WARNING: Apparent symbolic reference X_ not resolved.
WARNING: Apparent symbolic reference I not resolved.
WARNING: Apparent symbolic reference I not resolved.
ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The
condition was: &i.-1
WARNING: Apparent symbolic reference I not resolved.
WARNING 14-169: Assuming the symbol X was misspelled as x_.
ERROR: The macro TEMP will stop executing.
Thanks for the solution.
Why don't you do it in a data step with call symputx, where the logic is much easier to build?
data _null_;
length string $100;
string = "";
do i = 1 to 5;
string = catx(' ',string,'A_' !! left(put(i,best.)));
call symputx('X_' !! left(put(i,best.)),string);
put string=;
end;
run;
%put x_3=&x_3.;
%put x_5=&x_5.;
Note that the data step is SAS' primary tool for handling data; the macro language is for creating dynamic code, NOT for handling data.
@thepushkarsingh wrote:
5, is not fixed depending on data it's changing and I am using that part inside a macro, but in first place I always thought that using macro facility is more efficient. I just want to use this as passing variable names, do you think data step will be more efficient?
You code will be clearly if you use SAS code. Only resort to using macro logic when normal SAS code cannot do the job.
So if you need to reference 5 variables then just use a variable list.
result=mean(of A1-A5);
So to make that dynamic you could replace just the 5 with a macro variable.
%let n=5;
....
result=mean(of A1-A&n);
But even if did need to generate your list of macro variables if you have a macro that is generating code already. say a data step or a proc step, then using a data step to generate macro variables will probably make it easier to create your code. It is much easier to debug a data step than a macro.
Thanks for the tips.
The macro loop is executed once, and the loop in the data step is executed once, so there's no difference there.
Doing a calculation in a macro may be more efficient when your code looks like this:
data want;
set sashelp.class;
age = age * 12345 ** (-2);
run;
Now, if you change that to this:
data want2;
set sashelp.class;
age = age * %sysevalf(12345 ** (-2));
run;
the macro preprocessor will evaluate the fixed part of the equation once when the data step is compiled, instead of the data step doing the calculation for every observation.
But the bottom line is to always keep data handling and manipulation in Base SAS code, where the logic is easier to code and understand. Optimizations like the one above make sense only when there's a considerable performance gain.
Thanks for nice explanation. I will keep these in my mind.
Good question!
The problem is that you're trying to use && to create an indirect macro reference:
%let x_&i. = &&x_%eval(&i.-1). %str( A_&i.);
This indirect reference works for when you have multiple & in a reference, for example:
%let x_&i = &&x_&j;
But in your case you are generating the second part of your macro variable name with %eval. When the macro processor (word scanner? tokenizer?) starts resolving &&x_%eval(&i.-1) it first sees the && and resolves it to &. Then it sees x_. Then it sees the % and thinks it hit the end of macro variable reference because it doesn't know %eval can generate part of the macro variable name. It knows there is still an & left in the token from the first pass, so it makes a second pass and tries to resolve &x_. It can't resolve. If you add %put _local_; to your macro, you will see you end up with the following macro variables:
TEMP I 6 TEMP X_1 A_1 TEMP X_2 &x_1. A_2 TEMP X_3 &x_2. A_3 TEMP X_4 &x_3. A_4 TEMP X_5 &x_4. A_5
While the %PUT statement resolves everything like you hoped, you haven't actually built the macro variables you want.
@PaigeMiller solved this nicely by refactoring to move the %eval out of the macro reference, avoiding the problem.
One option when you want to build a macro reference from macro statements is to use quoting functions to delay the resolution of the macro variables. It gets a little ugly (but hey, it's macro : ) but you basically quote the & and then %unquote it after the %eval has done its work. Something like:
%macro temp();
%let x_1 = A_1;
%do i= 2 %to 10;
%let x_&i = %unquote(%nrstr(&x_)%eval(&i.-1)) A_&i;
%put &&x_&i;
%end;
%put _local_ ;
%mend;
%temp()
It's a bit of a black box, where we don't understand what is happening 100%. But these results suggest that you should be able to resolve the problem by forcing the macro processor to make an extra pass through the expression:
%let x_&i = &&&&x_%eval(&i.-1). A_&i;
Thank you very much, this makes a lot of things clear.
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 25. Read more here about why you should contribute and what is in it for you!
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.