Macro execution inside data step - sas

I have the following macro which I will use inside a data step:
%global var3;
%macro add(var1,var2);
%let var3 = %eval(&var1+&var2);
%mend;
I don't understand the results of the following data step:
%let var3 = 2;
data test;
put "The value of var3 is &var3";
x = 3 - &var3;
%add(7,3);
%put &=var3;
run;
From what I understand, macro statements are executed before compilation of a data step. So in this case macro %add is executed first and then %put statement. However, the value of x is 1, not -7. And also put statement prints The value of var3 is 2, instead of 10. I am confused.

Let's look at what happens.
69 %global var3;
70
71 %macro add(var1,var2);
72 %let var3 = %eval(&var1+&var2);
73 %mend;
74
75 %let var3 = 2;
76 options symbolgen mprint;
77 data test;
SYMBOLGEN: Macro variable VAR3 resolves to 2
78 put "The value of var3 is &var3";
79 x = 3 - &var3;
SYMBOLGEN: Macro variable VAR3 resolves to 2
80 %add(7,3);
SYMBOLGEN: Macro variable VAR1 resolves to 7
SYMBOLGEN: Macro variable VAR2 resolves to 3
81 %put &=var3;
SYMBOLGEN: Macro variable VAR3 resolves to 10
VAR3=10
82 run;
The value of var3 is 2
So, SAS goes to run the macro pass, as you say before data step compilation, and turns the code into this...
data test;
put "The value of var3 is 2";
x = 3 - 2;
%let var3 = %eval(7+3);
%put 10;
run;
You're right that SAS does handle the macro language before compilation, but that doesn't mean it happens before SAS begins to prepare the data step for compilation. The macro language pass creates the final data step, but it still happens sequentially - so SAS looks at line 77, sees no macro variables, finalizes it; it looks at line 78, sees &var3, turns it into 2, finalizes that line; looks at line 79, etc.

Related

how can I build a loop for macro in SAS?

I want to do a simulation based on macro in SAS. I can build a function named 'fine()', the code is as follows
DATA CLASS;
INPUT NAME $ SEX $ AGE HEIGHT WEIGHT;
CARDS;
ALFRED M 14 69.0 112.5
ALICE F 13 56.5 84.0
BARBARA F 13 65.3 98.0
CAROL F 14 62.8 102.5
HENRY M 14 63.5 102.5
RUN;
PROC PRINT;
TITLE 'DATA';
RUN;
proc print data=CLASS;run;
PROC FCMP OUTLIB = work.functions.func;
function populationCalc(HEIGHT,WEIGHT,thres);
pop=HEIGHT-WEIGHT-thres;
return (pop);
ENDSUB;
options cmplib=(work.functions);
%macro fine(i);
data ex;
set CLASS;
thres=&i;
pop = populationCalc(HEIGHT,WEIGHT,thres);
if (pop>50) then score=1;
else score=0;
run;
proc iml;
USE ex;
READ all var _ALL_ into ma[colname=varNames];
CLOSE ex;
nn=nrow(ma);
total_score=sum(ma[,'thres']);
avg_score=sum(ma[,'thres'])/nn;
print total_score avg_score;
%mend fine;
%fine(10);
%fine(100);
%fine(150);
I want to build a loop for function 'fine()' ans also use macro, but the result is not as I expect. How can I fix this?
%macro ct(n);
data data_want;
%do i=1 %to &n;
x=%fine(&i);
output x;
%end;
run;
%macro ct;
%ct(10);
%fine does not generate any text that can be used in the context of a right hand side (RHS) of a DATA Step variable assignment statement.
You seem to perhaps want this data set as a result of invoking %ct
i total_score average_score
- ----------- -------------
1 5 1
2 10 2
3 15 3
etc...
Step 1. Save IML result
Add this to the bottom of IML code in %fine, replacing the print
create fine_out var {total_score avg_score};
append;
close fine_out;
quit;
Step 2. Rewrite ct macro
Invoke %fine outside a DATA step context so the DATA and IML steps can run. Append the IML output to a results data set.
%macro ct(n,out=result);
%local i;
%do i=1 %to &n;
%fine(&i)
%if &i = 1 %then %do;
data &out; set fine_out; run;
%end;
%else %do;
proc append base=&out data=fine_out; run;
%end;
%end;
%mend;
options mprint;
%ct(10)
This should be the output WORK.RESULT based on your data

space separated list intersection

Note: code edited after remarks from #user667489 but issues remain.
I built a fairly simple macro to return the intersection of 2 space separated lists as a new space separated list but for some reason the definition of the macro returns errors.
The macro loops through both lists and keep an element if a match is found (very straightforward, no handling of duplicates or optimization).
I cannot make sense of the log, which shows a combination of following error messages:
ERROR: Macro keyword LET appears as text.
ERROR: Macro keyword MACRO appears as text.
%macro list_intersection
(list1= /* space separated list, or unique term */
,list2= /* space separated list, or unique term */
);
%local output;
%local i;
%local j;
%let i = 1;
%let j = 1;
%do %while (%length(%scan(&list1,&i)));
%do %while (%length(%scan(&list2,&j)));
%if (%scan(&list1,&i) = %scan(&list2,&j)) %then
%let output = &output %scan(&list1,&i);
%let j = %eval(&j+1);
%end;
%let i = %eval(&i+1);
%end;
&output
%mend;
Can you help me circling the issue ?
I'm also open to a more efficient/robust/simple way of achieving the same output.
Reproducible log
Using SAS 9.3, I put above code in a separate program for it not to be polluted, save project, close and reopen. Open program, click run button, and here is the complete log:
1 The SAS System 09:50 Monday, January 22, 2018
1 ;*';*";*/;quit;run;
2 OPTIONS PAGENO=MIN;
3 %LET _CLIENTTASKLABEL='Program3';
ERROR: Macro keyword LET appears as text.
4 %LET _CLIENTPROJECTPATH='F:\CI\Projects\Wealth Indicators\106 Explore DIM_PHYSICALPERSON\SAS\Rework_table_auto2.egp';
ERROR: Macro keyword LET appears as text.
5 %LET _CLIENTPROJECTNAME='Rework_table_auto2.egp';
ERROR: Macro keyword LET appears as text.
6 %LET _SASPROGRAMFILE=;
ERROR: Macro keyword LET appears as text.
7
8 ODS _ALL_ CLOSE;
9 OPTIONS DEV=ACTIVEX;
10 GOPTIONS XPIXELS=0 YPIXELS=0;
11 FILENAME EGSR TEMP;
12 ODS tagsets.sasreport13(ID=EGSR) FILE=EGSR STYLE=HtmlBlue
12 ! STYLESHEET=(URL="file:///C:/Program%20Files/SASHome/x86/SASEnterpriseGuide/5.1/Styles/HtmlBlue.css") NOGTITLE NOGFOOTNOTE
12 ! GPATH=&sasworklocation ENCODING=UTF8 options(rolap="on");
13
14 GOPTIONS ACCESSIBLE;
15 %macro list_intersection
ERROR: Macro keyword MACRO appears as text.
16 (list1= /* space separated list, or unique term */
17 ,list2= /* space separated list, or unique term */
18 );
19 %local output;
ERROR: Macro keyword LOCAL appears as text.
20 %local i;
ERROR: Macro keyword LOCAL appears as text.
21 %local j;
ERROR: Macro keyword LOCAL appears as text.
22 %let i = 1;
ERROR: Macro keyword LET appears as text.
23 %let j = 1;
ERROR: Macro keyword LET appears as text.
24 %do %while (%length(%scan(&list1,&i)));
ERROR: Macro keyword DO appears as text.
25 %do %while (%length(%scan(&list2,&j)));
ERROR: Macro keyword DO appears as text.
26 %if (%scan(&list1,&i) = %scan(&list2,&j)) %then
ERROR: Macro keyword IF appears as text.
27 %let output = &output %scan(&list1,&i);
28 %let j = %eval(&j+1);
ERROR: Macro keyword LET appears as text.
29 %end;
ERROR: Macro keyword END appears as text.
30 %let i = %eval(&i+1);
ERROR: Macro keyword LET appears as text.
31 %end;
ERROR: Macro keyword END appears as text.
32 &output
33 %mend;
ERROR: Macro keyword MEND appears as text.
34
35 GOPTIONS NOACCESSIBLE;
36 %LET _CLIENTTASKLABEL=;
ERROR: Macro keyword LET appears as text.
37 %LET _CLIENTPROJECTPATH=;
2 The SAS System 09:50 Monday, January 22, 2018
ERROR: Macro keyword LET appears as text.
38 %LET _CLIENTPROJECTNAME=;
ERROR: Macro keyword LET appears as text.
39 %LET _SASPROGRAMFILE=;
ERROR: Macro keyword LET appears as text.
40
41 ;*';*";*/;quit;run;
42 ODS _ALL_ CLOSE;
43
44
45 QUIT; RUN;
46
Initial code before edit:
%macro list_intersection
(list1= /* space separated list, or unique term */
,list2= /* space separated list, or unique term */
);
%local output =;
%local i = 1;
%local j = 1;
%do %while (%length(%scan(&list1,&i)));
%do %while (%length(%scan(&list2,&j)));
%if (%scan(&list1,&i) = %scan(&list2,&j) %then
%local output = &output %scan(&list1,&i);
%let j = %eval(&j+1);
%end;
%let i = %eval(&i+1);
%end;
&output
%mend;
A few things immediately stand out:
You cannot use %local to set a value for a macro variable. Instead of %local i=1; you must write two separate statements: %local i; %let i = 1;. %local initialises macro variables to an empty string.
You have unbalanced brackets in your %if statement.
Try moving %let j = %eval(&j+1); into the outer %do %while loop.
Also, you probably want to make sure that %scan only uses space as a delimiter - it defaults to space plus . < ( + & ! $ * ) ; ^ - / , % |
Here's a working version:
%macro list_intersection
(list1= /* space separated list, or unique term */
,list2= /* space separated list, or unique term */
);
%local output;
%local i;
%local j;
%let i = 1;
%do %while (%length(%scan(&list1,&i,%str( ))));
%let j = 1;
%do %while (%length(%scan(&list2,&j,%str( ))));
%if %scan(&list1,&i,%str( )) = %scan(&list2,&j,%str( )) %then
%let output = &output %scan(&list1,&i,%str( ));
%let j = %eval(&j+1);
%end;
%let i = %eval(&i+1);
%end;
&output
%mend;
%put %list_intersection(list1=1 2 3,list2=2 3 4);
You can use SAS functions to make that much easier. Use the COUNTW() function to find the upper bound for the %DO loop. Use the FINDW() function to test if word is found in the other list.
%macro list_intersection
(list1 /* space separated list of terms */
,list2 /* space separated list of terms */
);
%local output i next ;
%do i=1 %to %sysfunc(countw(&list1,%str( ))) ;
%let next=%scan(&list1,&i,%str( ));
%if %sysfunc(findw(&list2,&next,,s)) %then %let output=&output &next ;
%end;
&output
%mend;
You could include the i modifier in the findw() call to make it case insensitive. You could also test if the word is already in the output string to eliminate duplicates.
%macro list_intersection_nodups
(list1 /* space separated list of terms */
,list2 /* space separated list of terms */
);
%local output i next ;
%do i=1 %to %sysfunc(countw(&list1,%str( ))) ;
%let next=%scan(&list1,&i,%str( ));
%if %sysfunc(findw(&list2,&next,,si)) and not %sysfunc(findw(&output,&next,,si))
%then %let output=&output &next ;
%end;
&output
%mend;
Example:
274 %put %list_intersection_nodups(A B a C,a c d);
A C

nested sysfunc + cat + leading 0 removed

I'm trying with the below loop to manage a dynamic append between three tables.
However, the nested cat with %sysfuncs removed the zeros from the month and the day
Would be someone so gentle to explain what Is not working fine here?
I'm expecting to dynamically declares the table like, ex.
"FTP.TOTAL_4B_20170603" (yyyymmdd) and not like the code is retriving: "FTP.TOTAL_4B_201763"
data dd; %MACRO H;
%DO I=1 %TO 2;
proc append
base=prod0
data=FTP.TOTAL_4B_%sysfunc(CATS(%sysfunc(year(%sysfunc(intnx(day,%sysfunc(today()),-&i,s)))),
%sysfunc(putn(%sysfunc(month(%sysfunc(intnx(day,%sysfunc(today()),-&i,s)))),z2.)),
%sysfunc(putn(%sysfunc(day(%sysfunc(intnx(day,%sysfunc(today()),-&i,s)))),z2.))));
run;
%end;
proc sort data=prod0;
by pan fecha;
run;
%MEND H;
%H;
run;
Thanks for your help
Bests
D
Not sure why you have proc append and proc sort within a data step, but I think the macro expression below could help:
%MACRO J;
%DO I=1 %TO 2;
%put FTP.TOTAL_4B_%sysfunc(intnx(DAY,%sysfunc(today()),-&I.,S),yymmddn8.);
%END;
%MEND J;
%J;
You can use formats, to get the date YYYYMMDD. Also intnx is not necessary if you are going to iterate on days only. date() returns integer, so you can easily use addition/subtraction.
%MACRO test;
%DO I=1 %TO 2;
%let test_date=%sysfunc(putn(%sysfunc(date()) - &i, yymmddn8.));
%put DS name FTP.TOTAL_4B_&test_date;
%end;
%MEND test;
%test;
Gives the result
1 OPTIONS NONOTES NOSTIMER NOSOURCE NOSYNTAXCHECK;
59
60
61
62
63 %MACRO test;
64 %DO I=1 %TO 2;
65 %let test_date=%sysfunc(putn(%sysfunc(date())-&i, yymmddn8.));
66 %put DS name FTP.TOTAL_4B_&test_date;
67 %end;
68 %MEND test;
69
70 %test;
DS name FTP.TOTAL_4B_20170612
DS name FTP.TOTAL_4B_20170611
71
72 OPTIONS NONOTES NOSTIMER NOSOURCE NOSYNTAXCHECK;
84

SAS store macro reference in macro variable

I want to store a list of macro references in another macro variable and then change the content of one of the referenced variables.
As example:
%LET String=FirstString;
%LET KeepMacroNotString=&String;
%PUT &String = &KeepMacroNotString ?;
%LET String=String changed;
%PUT &String = &KeepMacroNotString?;
In the end I would like that %PUT &KeepMacroNotString resolves to "String changed". However it sticks to the first assignment.
Any ideas?
Thx, Lubenja
Much easier to do with a data step.
data _null_;
call symputx('KeepMacroNotString','&String');
run;
I found the solution:
A combination of the %NRSTR function and the %UNQUOTE function do the trick:
%LET String=FirstString;
%LET KeepMacroNotString=%NRSTR(&String);
%PUT &String = &KeepMacroNotString ?;
%LET String=String changed;
%PUT &String = %UNQUOTE(&KeepMacroNotString)?;
Explanation: First you have to mask the "&" to prevent the macro from being resolved (%NRSTR()).
But when you want to use the marco, then you have to unquote it again (%UNQUOTE()).
I wouldn't use this approach, but if you don't mind warning messages in the log (I do), you could in theory just change the order of your statements:
WARNING: Apparent symbolic reference STRING not resolved.
57
58 %LET KeepMacroNotString=&String;
59
60 %LET String=FirstString;
61 %PUT &String = &KeepMacroNotString ?;
FirstString = FirstString ?
62
63 %LET String=String changed;
64 %PUT &String = &KeepMacroNotString?;
String changed = String changed?
This is basically an ugly way of accomplishing the same sort of indirection that Tom did more gracefully. Key point being that that the macro variable KeepMacroNotString is given a value of &String, not the resolved value.
In a macro setting, this can also be accomplished by assigning default value as a macro variable reference, e.g.:
59 %macro indirect(String=
60 ,KeepMacroNotString=&string
61 );
62 %put _local_;
63 %put &string = &keepmacronotstring;
64 %mend indirect;
65 %indirect(string=Hi)
INDIRECT KEEPMACRONOTSTRING &string
INDIRECT STRING Hi
Hi = Hi

How can I use %SCAN within a macro variable name?

I'm trying to write robust code to assign values to macro variables. I want the names of the macro variables to depend on values coming from the variable 'subgroup'. So subgroup could equal 1, 2, or 45 etc. and thus have macro variable names trta_1, trta_2, trt_45 etc.
Where I am having difficulty is calling the macro variable name. So instead of calling e.g. &trta_1 I want to call &trta_%SCAN(&subgroups, &k), which resolves to trta_1 on the first iteration. I've used a %SCAN function in the macro variable name, which is throwing up a warning 'WARNING: Apparent symbolic reference TRTA_ not resolved.'. However, the macro variables have been created with values assigned.
How can I resolve the warning? Is there a function I could run with the %SCAN function to get this to work?
data data1 ;
input subgroup trta trtb ;
datalines ;
1 30 58
2 120 450
3 670 3
run;
%LET subgroups = 1 2 3 ;
%PUT &subgroups;
%MACRO test;
%DO k=1 %TO 3;
DATA test_&k;
SET data1;
WHERE subgroup = %SCAN(&subgroups, &k);
CALL SYMPUTX("TRTA_%SCAN(&subgroups, &k)", trta, 'G');
CALL SYMPUTX("TRTB_%SCAN(&subgroups, &k)", trtb, 'G');
RUN;
%PUT "&TRTA_%SCAN(&subgroups, &k)" "&TRTB_%SCAN(&subgroups, &k)";
%END;
%MEND test;
%test;
Using the structure you've provided the following will achieve the result you're looking for.
data data1;
input subgroup trta trtb;
datalines;
1 30 58
2 120 450
3 670 3
;
run;
%LET SUBGROUPS = 1 2 3;
%PUT &SUBGROUPS;
%MACRO TEST;
%DO K=1 %TO 3;
%LET X = %SCAN(&SUBGROUPS, &K) ;
data test_&k;
set data1;
where subgroup = &X ;
call symputx(cats("TRTA_",&X), trta, 'g');
call symputx(cats("TRTB_",&X), trtb, 'g');
run;
%PUT "&&TRTA_&X" "&&TRTB_&X";
%END;
%MEND TEST;
%TEST;
However, I'm not sure this approach is particularly robust. If your list of subgroups changes you'd need to change the 'K' loop manually, you can determine the upper bound of the loop by dynamically counting the 'elements' in your subgroup list.
If you want to call the macro variables you've created later in your code, you could a similar method.
data data2;
input subgroup value;
datalines;
1 20
2 25
3 15
45 30
;
run ;
%MACRO TEST2;
%DO K=1 %TO 3;
%LET X = %SCAN(&SUBGROUPS, &K) ;
data data2 ;
set data2 ;
if subgroup = &X then percent = value/&&TRTB_&X ;
format percent percent9.2 ;
run ;
%END;
%MEND TEST2;
%TEST2 ;
Effectively, you're re-writing data2 on each iteration of the loop.
This should cover your requirements. You can load and unload an array of macro variable without a macro. I have included an alternate method of unloading a macro variable array with a macro for comparison.
Load values into macro variables including Subgroup number within macro variable name e.g. TRTA_45.
data data1;
input subgroup trta trtb;
call symput ('TRTA_'||compress (subgroup), trta);
call symput ('TRTB_'||compress (subgroup), trtb);
datalines;
1 30 58
2 120 450
3 670 3
45 999 111
;
run;
No need for macro to load or refer to macro variables.
%put TRTA_45: &TRTA_45.;
%let Subgroup_num = 45;
%put TRTB__&subgroup_num.: &&TRTB_&subgroup_num.;
If you need to loop through the macro variables then you can use Proc SQL to generate a list of subgroups.
proc sql noprint;
select subgroup
, count (*)
into :subgroups separated by ' '
, :No_Subgroups
from data1
;
quit;
%put Subgroups: &subgroups.;
%put No_Subgroups: &No_Subgroups.;
Use a macro to loop through the macro variable array and populate a table.
%macro subgroups;
data subgroup_data_macro;
%do i = 1 %to &no_subgroups.;
%PUT TRTA_%SCAN(&subgroups, &i ): %cmpres(&TRTA_%SCAN(&subgroups, &i ));
%PUT TRTB_%SCAN(&subgroups, &i ): %cmpres(&TRTB_%SCAN(&subgroups, &i ));
subgroup = %SCAN(&subgroups, &i );
TRTA = %cmpres(&TRTA_%SCAN(&subgroups, &i ));
TRTB = %cmpres(&TRTB_%SCAN(&subgroups, &i ));
output;
%end;
run;
%mend subgroups;
%subgroups;
Or use a data step (outside a macro) to loop through the macro variable array and populate a table.
data subgroup_data_sans_macro;
do i = 1 to &no_subgroups.;
subgroup = SCAN("&subgroups", i );
TRTA = input (symget (compress ('TRTA_'||subgroup)),20.);
TRTB = input (symget (compress ('TRTB_'||subgroup)),20.);
output;
end;
run;
Ensure both methods (within and without a macro) produce the same result.
proc compare
base = subgroup_data_sans_macro
compare = subgroup_data_macro
;
run;