iterate over a macro list in SAS - sas

I am trying to use a macro variable for the first time,
I have many tables produced every month and I would like for each table take only two variable, so I have created a macro to avoid redundancies in my code but the problem is that the macro variables are not recognized in the data step
%LET OLDTABLES =
oc201710.A20171001_active_households
oc201709.A20170901_active_households ;
* List of tables processed (ex with two tables);
%LET NEWTABLES =
A20171001_HH
A20170901_HH ;
%MACRO VARTEST(x1=&OLDTABLES,x2=&NEWTABLES);
* Loop over the table;
%do i=1 %to %sysfunc(countw(&x1.));
%LET FLAGP = %sysfunc(scan(&x2., &i));
* get the 7 first characters from the table name;
%LET VARFLAG = %sysfunc(substr(&FLAGP,1,7));
%LET OLDTAB = %scan(&x1., &i);
%LET NEWTAB = %scan(&x2., &i);
%PUT &OLDTAB;
%PUT &NEWTAB;
* data step: keeping only the key + create new variable from the name of the table;
rsubmit;
data &NEWTAB (keep=cust_key &VARFLAG_f_act_n &VARFLAG_Households);
set &OLDTAB;
&VARFLAG._f_act_n = f_act_n;
&VARFLAG._Households = 1;
run;
endrsubmit;
%end;
%mend VARTEST;
options mprint mfile;
filename mprint 'external-file';
%VARTEST(x1=&OLDTABLES.,x2=&NEWTABLES.);
Here what I get as an output,:
836
837 * data step: keeping only the key + create new variable from the name of the table
838 options mprint mfile;
WARNING: Apparent symbolic reference OLDTABLES not resolved.
WARNING: Apparent symbolic reference NEWTABLES not resolved.
MPRINT(VARTEST): * Loop over the table;
839 filename mprint 'external-file';
SYMBOLGEN: Macro variable X1 resolves to &OLDTABLES.
WARNING: Apparent symbolic reference OLDTABLES not resolved.
840 %VARTEST(x1=&OLDTABLES.,x2=&NEWTABLES.);
SYMBOLGEN: Macro variable X2 resolves to &NEWTABLES.
WARNING: Apparent symbolic reference NEWTABLES not resolved.
SYMBOLGEN: Macro variable I resolves to 1
MPRINT(VARTEST): * get the 7 first characters from the table name;
SYMBOLGEN: Macro variable FLAGP resolves to NEWTABLES
SYMBOLGEN: Macro variable X1 resolves to &OLDTABLES.
WARNING: Apparent symbolic reference OLDTABLES not resolved.
SYMBOLGEN: Macro variable I resolves to 1
SYMBOLGEN: Macro variable X2 resolves to &NEWTABLES.
WARNING: Apparent symbolic reference NEWTABLES not resolved.
SYMBOLGEN: Macro variable I resolves to 1
SYMBOLGEN: Macro variable OLDTAB resolves to OLDTABLES
OLDTABLES
SYMBOLGEN: Macro variable NEWTAB resolves to NEWTABLES
NEWTABLES
MPRINT(VARTEST): rsubmit

First off, it looks like you didn't run the part of your code defining &oldtables and &newtables.
Second, your %SYSFUNC(COUNTW(... is wrong. COUNTW takes lots of things as delimiters, including . which means you have more than 2 words there. Give it the optional second argument:
%do i=1 %to %sysfunc(countw(&x1.,%str( )));
(That's how you show 'space' in macro language, by the way).
Third, you have some issues with macro variables not being delimited properly. &VARFLAG_f_act_n needs to be &VARFLAG._f_act_n; you do that right later but you don't in the keep statement for either variable. (You could probably just keep &varflag: by the way plus the other variables in that list.)
This works for me. I obviously can't do all of what you can because of not having the datsets, but this works as expected.
Also, I hope you're aware that your RSUBMIT session does not know about the macro variables. They're parsed by the local session before the RSUBMIT happens. But if you have some macro variable that needs to be processed on the server side, you'll have to %SYSLPUT or %SYSRPUT that variable.
%LET OLDTABLES = oc201710.A20171001_active_households oc201709.A20170901_active_households ;
* List of tables processed (ex with two tables);
%LET NEWTABLES = A20171001_HH A20170901_HH ;
options autosignon=yes;
%let sasloc = "C:\Program Files\sas94\sasfoundation\9.4\sas.exe"; *path to sas.exe;
signon process=a
sascmd=&sasloc.;
%MACRO VARTEST(x1=&OLDTABLES,x2=&NEWTABLES);
* Loop over the table;
%do i=1 %to %sysfunc(countw(&x1.,%str( )));
%LET FLAGP = %sysfunc(scan(&x2., &i));
* get the 7 first characters from the table name;
%LET VARFLAG = %sysfunc(substr(&FLAGP,1,7));
%LET OLDTAB = %scan(&x1., &i);
%LET NEWTAB = %scan(&x2., &i);
%PUT &OLDTAB;
%PUT &NEWTAB;
* data step: keeping only the key + create new variable from the name of the table;
rsubmit process=a;
%put &=newtab. &=oldtab. &=Varflag.;
endrsubmit;
%end;
%mend VARTEST;
%VARTEST(x1=&OLDTABLES.,x2=&NEWTABLES.);

Related

load values from datasets into arrays and use them in a datastep

I have 5 separate datasets(actually many more but i want to shorten the code) named dk33,dk34,dk35,dk51,dk63, each dataset contains a numeric field: surv_probs. I would like to load the values into 5 arrays and then use the arrays in a datastep(result), however, I need advice what is the best way to do it.
I am getting error when I use the macro: setarrays: (code below)
WARNING: The quoted string currently being processed has become more than 262 characters long. You might have unbalanced quotation
marks.
WARNING: The quoted string currently being processed has become more than 262 characters long. You might have unbalanced quotation
marks.
ERROR: Illegal reference to the array dk33_arr.
Here is the main code.
%let var1 = dk33;
%let var2 = dk34;
%let var3 = dk35;
%let var4 = dk51;
%let var5 = dk63;
%let varN = 5;
/*put length of each column into macro variables */
%macro getlength;
%do i=1 %to &varN;
proc sql noprint;
select count(surv_probs)
into : &&var&i.._rows
from work.&&var&i;
quit;
%end;
%mend;
/*load values of column:surv_probs into macro variables*/
%macro readin;
%do i=1 %to &varN;
proc sql noprint;
select surv_probs
into: &&var&i.._list separated by ","
from &&var&i;
quit;
%end;
%mend;
data _null_;
call execute('%readin');
call execute('%getlength');
run;
/* create arrays*/
%macro setarrays;
%do i=1 %to 1;
j=1;
array &&var&i.._arr{&&&&&&var&i.._rows};
do while(scan("&&&&&&var&i.._list",j,",") ne "");
&&var&i.._arr = scan("&&&&&&var&i.._list",j,",");
j=j+1;
end;
%end;
%mend;
data result;
%setarrays
put dk33_arr(1);
* some other statements where I use the arrays*
run;
Answer to toms question:
*macro getlength(when executed) creates 5 macro variables named: dk33_rows,dk34_rows,dk35_rows,dk51_rows,dk63_rows
*the macro readin(when executed):creates 5 macro variables dk33_list,dk34_list,dk35_list,dk51_list,dk63_list. Each containing a string which is comma separates the values from the column: eg.: 0.99994,0.1999,0.1111
*the macro setarrays creates 5 arrays,when executed, dk33_arr,dk34_arr,... holding the parsed values from the macro variables created by readin
I find that "macro arrays" like VAR1,VAR2,.... are generally more trouble than they are worth. Either keep your list of dataset names in an actual dataset and generate code from that. Or if the list is short enough put the list into a single macro variable and use %SCAN() to pull out the items as you need them.
But either way it is also better to avoid trying to write macro code that needs more than three &'s. Build up the reference in multiple steps. Build a macro variable that has the name of the macro you want to reference and then pull the value of that into another macro variable. It might take more lines of code, but you can more easily understand what is happening.
%let i=1 ;
%let mvarname=var&i;
%let dataset_name=&&&mvarname;
Before you begin using macro code (or other code generation techniques) make sure you know what code you are trying to generate. If you want to load a variable into a temporary array you can just use a DO loop. There is no need to macro code, or copying values, or even counts, into macro variables. For example instead of getting the count of the observations you could just make your temporary array larger than you expect to ever need.
data test1 ;
if _n_=1 then do;
do i=1 to nobs_dk33;
array dk33 (1000) _temporary_;
set dk33 nobs=nobs_dk33 ;
dk33(i)=surv_probs;
end;
do i=1 to nobs_dk34;
array dk34 (1000) _temporary_;
set dk34 nobs=nobs_dk34 ;
dk34(i)=surv_probs;
end;
end;
* What ever you are planning to do with the DK33 and DK34 arrays ;
run;
Or you could transpose the dataset first.
proc transpose data=dk33 out=dk33_t prefix=dk33_ ;
var surv_probs ;
run;
Then your later step is easier since you can just use a SET statement to read in the one observation that has all of the values.
data test;
if _n_=1 then do;
set dk33_t ;
array dk33 dk33_: ;
end;
....
run;

SAS %while loop condition not evaluating correctly

I'm running a macro loop to recursively strip out stock returns greater than the 99.5 percentile to calculate a volatility model. I'm trying to set the loop to run until there are no observations with a z-score (assuming standard normal for the percentile) greater than 2.58. For some reason though, the loop will not evaluate despite the condition being true. Here's the code that I'm using:
%macro delete_jump;
%let z_max=10.0;
%let i=0;
%let crit=%sysfunc(quantile(%str(NORMAL),0.995,0,1));
%put The critical value is &crit.;
%do %while((&z_max. > &crit.) and (&i. < 30));
%put max=&z_max.;
proc expand data=crsp_data out=crsp_data(drop=time);
convert ret_nojmp=ret_vol / transformout=(movstd &hist_pd. trimleft &hist_pd.);
convert ret_nojmp=ret_ma / transformout=(movave &hist_pd. trimleft &hist_pd.);
by ticker;
run;
data crsp_data(drop=ret_vol ret_ma);
set crsp_data(drop=zscore);
zscore=(ret_nojmp-ret_ma) / ret_vol;
if quantile("NORMAL",0.01,0,1) < zscore < quantile("NORMAL",0.99,0,1) then ret_nojmp=ret;
else ret_nojmp = .;
run;
proc sql;
select max(abs(zscore)) into :z_max
from crsp_data;
quit;
%let i=%eval(&i.+1);
%end;
%mend delete_jump;
I set z=10 to initialize the loop and i is just a counter so I can escape after a certain amount of time. When it gets to that point though, the log file says the following:
MLOGIC(DELETE_JUMP): Beginning execution.
MLOGIC(DELETE_JUMP): %LET (variable name is Z_MAX)
MLOGIC(DELETE_JUMP): %LET (variable name is I)
MLOGIC(DELETE_JUMP): %LET (variable name is CRIT)
MLOGIC(DELETE_JUMP): %PUT The critical value is &crit.
SYMBOLGEN: Macro variable CRIT resolves to 2.57582930354889
The critical value is 2.57582930354889
SYMBOLGEN: Macro variable Z_MAX resolves to 10.0
SYMBOLGEN: Macro variable CRIT resolves to 2.57582930354889
SYMBOLGEN: Macro variable I resolves to 0
MLOGIC(DELETE_JUMP): %DO %WHILE((&z_max. > &crit.) and (&i. < 30)) loop
beginning; condition is FALSE. Loop will not be executed.
MLOGIC(DELETE_JUMP): Ending execution.
I've tried splitting the conditions and hardcoding the value of CRIT, but it never executes the loop. Any thoughts are appreciated.
The problem seems to be that %do while uses %eval internally, which only works properly for integer comparisons, but you are comparing two decimals. Replace as follows and it should work as expected:
%do %while(%sysevalf(&z_max. > &crit.) and (&i. < 30));

Create SAS macro to create a macro variable

I have created a SAS macro, macro A, that takes in a variable name and returns transformed versions of that name i.e. if you run %A(asdf) you get out asdf_log asdf_exp asdf_10. I want to write another macro, macro B, that takes the output from the first macro and appends it together into a new macro variable.
%macro B(varList, outputName);
%let &outputName =
%A(var1);
%A(var2);
;
%mend
Is almost what I want to do, except that it obviously doesn't compile.
I am also not sure if this is possible in SAS.
As a further complication, the input to macro B is a list of variable that I want to run macro A for and append into one long list of variable names.
Why? Because I have a macro that runs on a list of variables and I want to run it on a transformed variable list.
Example:
I have %let varList = x y; and I want as an output x_log x_exp x_10 y_log y_exp y_10. To do this I want two macros one, macro A, that returns the transformed variables names:
%macro A(var);
&var._log
&var._exp
&var._10
%mend
I can't get the second macro (B as written above) to work properly.
So if the inner macro is just returning characters, that is it doesn't actually generate any non macro statements, then why not make the outer one work the same way?
%macro inner(x);
&x._log &x._exp &x._10
%mend;
%macro outer(list);
%local i;
%do i=1 %to %sysfunc(countw(&list));
%inner(%scan(&list,&i))
%end;
%mend outer;
%let want=%outer(X y Z);
This is not too hard. You need to loop over the values in varList, appending results to outputName. You also need to declare outputName as GLOBAL so it will be accessible outside %B
%macro B(varList, outputName);
%global &outputName;
%let &outputName = ;
%local i n var;
%let n = %sysfunc(countw(&varList));
%do i=1 %to &n;
%let var = %scan(&varList,&i);
%let &outputName = &outputName %A(&var);
%end;
%mend;

SAS: Running macros inside do loop does not seem to work

I need to run a series of macros multiple times. To do so, I've built a new macro that has inside of it a do-loop, which is supposed to run "i" number of times, and with each iteration of the do-loop, the series of macros referenced above are supposed to run.
Here is the essence of code (note that the first proc sql essentially takes from a dataset called "DatesRange" and placed a range of dates into the variable "varlist_Dates"; the range of dates within this variable is used for Macro1, Macro2, Macro3).
%macro MultipleTimes;
proc sql noprint;
select distinct Date
into :varlist_Dates separated by ' '
from DatesRange
order by Date;
quit;
%do i = 1 %to 5;
%let date = %scan(&varlist_Dates.,&i.);
%Macro1;
%Macro2;
%Macro3;
%end;
%mend;
I'm finding that it stops at i=1 and never proceeds. I'm completely unclear on why. I've experimented by playing around with removing some macros and keeping others but nothing seems to work. I feel like there may be something more fundamental about my methodology that is off, because there's something I don't know about SAS and about the way a macro inside of a do-loop inside of a macro works.
Any help would be much appreciated. Thanks!
First: check if i is used in any of those macros. I bet it is. It's probably being changed to something higher than 5 (thus exiting after one loop).
In SAS, when you reference a macro variable that already exists, it is defined as having the scope of the most local symbol table that contains it, unless you use %local to specify it as local to the current macro. So if you throw %let i=1 to 5; around in multiple nested macros, it will be using the same &i.
As an example, see the following code:
%let i=A;
%macro outer;
%put &=i;
%do i=1 %to 5;
%put OUTER FIRST: &=i;
%inner;
%put OUTER LAST: &=i;
%end;
%mend outer;
%macro inner;
%do i=1 %to 5;
%put INNER: &=i.;
%end;
%mend;
%put GLOBAL FIRST: &=i;
%outer;
%put GLOBAL LAST:&=i;
Notice how &i is always the same value. That's not what you mean, now is it?
Now, on the other hand, &i does get a different value in each bit if you do it right:
%let i=A;
%macro outer;
%local i;
%put &=i;
%do i=1 %to 5;
%put OUTER FIRST: &=i;
%inner;
%put OUTER LAST: &=i;
%end;
%mend outer;
%macro inner;
%local i;
%do i=1 %to 5;
%put INNER: &=i.;
%end;
%mend;
%put GLOBAL FIRST: &=i;
%outer;
%put GLOBAL LAST:&=i;
And if you %put _all_ in INNER you will see this in action.
Second: Don't write it this way. You're going to so much effort, just write it a better way to begin with.
%Macro RunMyMacros(date=);
%Macro1; *I hope you use &date as a parameter in these and not as a global;
%Macro2;
%Macro3;
%mend RunMyMacros;
proc sql noprint;
select distinct cats('%RunMyMacros(date=',Date,')')
into :calllist_Dates separated by ' '
from DatesRange
order by Date;
quit;
&calllist;
That's far easier to use, troubleshoot, and run, and doesn't require hardcoding the number of valid dates or anything else in it.

Modifying the value of macro variable in SAS

I created two macro variables in my sas code using the %let option.
data sasdata1.dataone;
set sasdata1.dataone ;
%let week=1;
%let sum=0;
do i=1 to 53;
%let sum= _W&week._NRX + &sum.;
week=&week+1;
end;
drop i;
week=&week;
sum=&sum.;
run;
the value of the week variable remains 1 even after the loop has executed. Which is the correct way to change the value of Macro variables?
If your week variables are all next to each other in the dataset, you may want to consider a macro-less approach:
data sasdata1.dataone;
set sasdata1.dataone;
sum = sum(of _W1_NRX--_W53_NRX); *double dash means that the columns are next to each other with _W1_NRX as first and _W53_NRX as last;
run;
If your week variables end with the week number, they do not even need to be next to each other:
data sasdata1.dataone;
set sasdata1.dataone;
sum = sum(of _W1-_W53); *single dash implies that the suffix is numerically increasing;
run;
Clean and easy.
Here's a quick example showing how to do this in the context of a macro. Run this and you can see in the log what's happening.
* 'week' is in scope within the macro only
and iterates because of %do loop (no need
to explicitly increment it ;
%macro test1;
%do week=1 %to 10;
%put &week;
%end;
%mend;
%test1
* sum has global scope ;
%let sum=0;
* 'week' acts as per test1 above, but we
explicitly manipulate 'sum' within each iteration ;
%macro test2;
%do week=1 %to 10;
%let sum=%eval(&sum+&week);
%put in macro: week is &week, sum is now ∑
%end;
%mend;
%put before macro executes, sum is ∑
%test2
%put after macro executes, sum is now ∑