I have a macro code that is supposed to loop though different formats and apply those to the x axis in a line plot produced by sgplot
%let p=date wy my;
%let f=date9. weeku5. monyy5.;
%macro trend_perdiod;
%do I=1 %to %sysfunc(countw(&p.));
%let pp=%scan(&p.,&I.);
%let TF=%scan(&f.,&I.);
proc means data=Insas.&sn. noprint;
class &pp.;
freq w;
output out=insas.&pp._&sn. mean=mean;
run;
*save mean value for time period;
proc sql noprint;
select Mean into: Tmean separated by ' '
from insas.&pp._&sn.
where &pp.=.;
quit;
data insas.&pp._&sn.;
set insas.&pp._&sn.;
where &pp. ne .;
lag_M=lag(mean);
leakage=-(Lag_M-mean)/Lag_M;
format leakage percentn12.2;
run;
*Plot;
Title1 bcolor=gold " &SN. - Average &Pp.";
proc sgplot data=insas.&pp._&sn.;
xaxis TICKVALUEFORMAT=&TF.;
series x=&pp. y=mean / markers;
refline &Tmean. /axis=y label=("Avg=&Tmean.");
yaxis grid;
run;
Title1;
proc print data=insas.&pp._&sn.;
var &pp. mean leakage;
format mean 12.2;
run;
%end;
%mend trend_perdiod;
%trend_perdiod;
The issue that I have is that the &TF. macro variable is not correctly resolved and I get the following error:
ERROR 772-580: Syntax error: expecting a constant or a dynamic.
Any idea how to enclose my list of formats so that they are properly applied into the following statement?
xaxis TICKVALUEFORMAT=&TF.
Any help is appreciated! Thanks a lot!
Here's some of the detail about why that would work:
https://v8doc.sas.com/sashtml/macro/z514scan.htm
By default, %SCAN uses a dot as a delimiter. So %SCAN is not picking up the dot at the end of the words within &F, when computing a value for &TF. It's possible this would also work, but I can't test it right now:
%scan(&f., &i, )
My, what a total ballache. You know, if you did one simple change then that whole block of code would disappear, instead looping over the whole code, process your data once, then call a macro to do the sglpot, (not tested this, just off the top of my head):
proc means data=insas.yourds noprint; by pp; var date wy my; output out=means mean(date)= mean(wy)= mean(my)= / autoname; run; data insas.yourdata; merge insas.yourds means; by pp; run; %macro plot (var=,fmt=); proc sglpot data=insas.yourds; xaxis tickvalueformat=&fmt..; series x=&var. ... refline &var._mean / ... run; %mend plot; %plot (var=date,fmt=date9); %plot (var=wy,fmt=weeku5); %plot (var=my,fmt=monyy5);
Saying that, you could probably modify the underlying proc template code on that, and not need to use the 3 macro calls at all, but I doubt that will show any less code.
With regards to your question on the error, how can we tell unless you post the relevant log section? It is telling you there is something wrong, enable debugging - options mlogic mprint symbolgen; - and then look at the log this will tell you what is wrong. I suspect it maybe the passing of . as well, scan maybe using that as a delimiter. Drop that from the string then use &tf.. - note the two dots, one to finish the macro variable, one to indicate its a format. And avoid mixing upper case and lower case it really makes code hard to read (and use the code window - its the {i} above post area) to retain code formatting.
Use options mprint symbolgen; and check the log - what value has &TF.
If need more help - please post the whole log.
Another reason why working from lists in macro variables is sub-optimal:
%let p=date wy my;
%let f=date9. weeku5. monyy5.;
%macro trend_perdiod;
%do I=1 %to %sysfunc(countw(&p.));
%let pp=%scan(&p.,&I.);
%let TF=%scan(&f.,&I.);
%put pp="&pp.";
%put tf="&tf.";
%end;
%mend;
%trend_perdiod
data control;
input p $ f $;
cards;
date date9.
wy weeku5.
my monyy5.
;
run;
%macro testmac(p,f);
%put p="&p.";
%put f="&f.";
%mend;
data _null_;
set control;
call execute('%nrstr(%testmac(' !! trim(p) !! ',' !! trim(f) !! '));');
run;
Just compare the logs.
You could make it work by using an artificial delimiter:
%let p=date wy my;
%let f=date9.@weeku5.@monyy5.;
%macro trend_perdiod;
%do I=1 %to %sysfunc(countw(&p.));
%let pp=%scan(&p.,&I.);
%let TF=%scan(&f.,&I.,@);
%put pp="&pp.";
%put tf="&tf.";
%end;
%mend;
%trend_perdiod
but I really prefer to work off datasets with call execute().
Hello,
keeping the space as delimiter and using %scan(&f.,&I.,' ') or %scan(&f.,&I.,%str( )) also works.
Here's some of the detail about why that would work:
https://v8doc.sas.com/sashtml/macro/z514scan.htm
By default, %SCAN uses a dot as a delimiter. So %SCAN is not picking up the dot at the end of the words within &F, when computing a value for &TF. It's possible this would also work, but I can't test it right now:
%scan(&f., &i, )
Since the compiler ignores spaces, quotes or %str( ) are needed to tell the compiler that the space is significant.
114 %let x=a. b. c.;
115
116 %macro test_scan;
117 %do i=1 %to %sysfunc(countw(&x.));
118 %put ---;
119 %let a=%scan(&x.,&i.);
120 %put &=a.;
121
122 %let b=%scan(&x.,&i., );
123 %put &=b.;
124
125 %let c=%scan(&x.,&i.,' ');
126 %put &=c.;
127 %end;
128 %mend;
129
130 %test_scan;
---
A=a
B=a. b. c.
C=a.
---
A=b
B=
C=b.
---
A=c
B=
C=c.
Thanks for testing. What a strange result for &B. I can't explain it off the top of my head, but I will try running some tests later.
Also note that using ' ' as the delimiter does use blanks as a delimiter. But it also uses single quotes as a delimiter. For practical purposes that almost never makes a difference.
Maybe having a void third argument means that no delimiter will be used so we'll always
obtain the entire string with 1 as second argument and an empty string for higher values.
The behavior is different with the scan function :
197 data _NULL_;
198 str="a b c";
199 first=scan(str,1,'');
200 put first=;
201 run;
first=a
NOTE: DATA statement used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
Edit : The result above is due to the fact that empty strings in SAS contain one space. I tried the same program, omitting
the third argument. I thought it would result in an error but it produces the same result as its macro counterpart :
202 data _NULL_;
203 str="a b c";
204 first=scan(str,1,);
205 put first=;
206 run;
first=a b c
NOTE: DATA statement used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
From the documentation
http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000214639.htm
Syntax |
SCAN(string, count<,charlist <,modifiers>>) |
The SCAN function allows character arguments to be null. Null arguments are treated as character strings with a length of zero. Numeric arguments cannot be null.
For other people looking for the same answer: It is also working is the . is left out of the list and added when the macro variable is called.
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.