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.
Related
Very new to sas, need to perform export procedure for many datasets called data1, data2, data3 ... data10.
Is there a way to operationalize this? I've tried the following without success
LIBNAME input '/home/.../input';
LIBNAME output '/home/.../output';
%macro anynumber(number);
proc export data=input.data&number file="/home/.../output/data&number..dta" dbms=dta replace;
run;
%mend;
do i = 1 to 10;
%anynumber(i)
end;
run;
CALL EXECUTE is the recommended option but a macro loop is also a good option here.
You could loop it within the same macro as well. All options are illustrated below.
Usually when I see this type of coding though, my first thought is that someone isn't familiar with BY group processing.
data demo;
do i=1 to 10;
*make sure this string matches macro call;
str = catt('%anynumber(', i, ');');
*write records to log to check;
put str;
call execute(str);
end;
run;
Another option is a macro loop itself.
%macro loop_export(numLoops=10);
%do i=1 %to &numLoops;
%anywhere(&i);
%end;
%mend;
%loop_export(numLoops=10);
Putting them together:
%macro anynumber(number);
%do i=1 %to &number;
proc export data=input.data&number file="/home/.../output/data&i..dta" dbms=dta
replace;
run;
%end;
%mend;
*will export all 10;
%anyNumber(10);
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;
I am creating a macro variable with the SAS code below. It's storing a list of data names where I need to replace certain values in specific variables.
proc sql noprint;
select distinct data_name
into :data_repl separated by ' '
from TP_attribute_matching
where Country="&Country_Name" and Replace_this ne ' ';
quit;
I would like to skip the following 2 blocks if data_repl is empty. These 2 blocks go through each data set and variables in that data set, and then replaces x with y.
/*Block 1*/
%do i=1 %to %_count_(word=&data_repl);
proc sql noprint;
select var_name,
Replace_this,
Replace_with
into :var_list_repl_&i. separated by ' ',
:repl_this_list_&i. separated by '#',
:repl_with_list_&i. separated by '#'
from TP_attribute_matching
where Replace_this ne ' ' and data_name="%scan(&data_repl,&i.)";
quit;
/* Block 2 */
%do i=1 %to %_count_(word=&data_repl);
data sasdata.%scan(&data_repl,&i);
set sasdata.%scan(&data_repl,&i);
%do j=1 %to %_count_(word=&&var_list_repl_&i.);
%let from=%scan("&&repl_this_list_&i.",&j,'#');
%let to=%scan("&&repl_with_list_&i.",&j,'#');
%scan(&&var_list_repl_&i.,&j)=translate(%scan(&&var_list_repl_&i.,&j),&to,&from);
%end;
run;
%end;
How shoould I do this? I was going through %SKIP and if then leave, but cannot figure this out yet.
%IF and %DO are macro statements that can only be used inside a macro:
%macro DoSomething;
%if "&data_repl" ne "" %then %do;
/*Block 1*/
%do i=1 %to %_count_(word=&data_repl);
proc sql noprint;
select var_name,
Replace_this,
Replace_with
into :var_list_repl_&i. separated by ' ',
:repl_this_list_&i. separated by '#',
:repl_with_list_&i. separated by '#'
from TP_attribute_matching
where Replace_this ne ' ' and data_name="%scan(&data_repl,&i.)";
quit;
/* Block 2 */
%do i=1 %to %_count_(word=&data_repl);
data sasdata.%scan(&data_repl,&i);
set sasdata.%scan(&data_repl,&i);
%do j=1 %to %_count_(word=&&var_list_repl_&i.);
%let from=%scan("&&repl_this_list_&i.",&j,'#');
%let to=%scan("&&repl_with_list_&i.",&j,'#');
%scan(&&var_list_repl_&i.,&j)=translate(%scan(&&var_list_repl_&i.,&j),&to,&from);
%end;
run;
%end;
%end;
%mend;
%DoSomething
EDIT:
Instead of checking the string, you can use count from PROC SQL (&SQLOBS macro var)
%let SQLOBS=0; /* reset SQLOBS */
%let data_repl=; /* initialize data_repl,
would not be defined in case when no rows returned */
proc sql noprint;
select distinct data_name
into :data_repl separated by ' '
from TP_attribute_matching
where Country="&Country_Name" and Replace_this ne ' '
and not missing(data_name);
quit;
%let my_count = &SQLOBS; /* keep the record count from last PROC SQL */
...
%if &my_count gt 0 %then %do;
...
...
%end;
If you already have a main macro, no need to define new (I'm not sure what you're asking now).
First off, this is yet another good example where list processing basics would simplify the code to where you don't need to worry about your actual question. Will elaborate later.
Second off, the way these loops are usually coded is something like
%do ... %while ¯ovar ne ;
which checks for empty and doesn't execute the loop at all if it's empty to start with. ¯ovar there would be the result of the scan. IE:
%let scan_result = %scan(&Data_repl.,1);
%do i = 1 %to %_count_... while &scan_result ne ; *perhaps minus one, not sure what %_count_() does exactly;
... code
%let scan_result=%scan(&data_Repl.,&i+1);
%end;
Going back to list processing, what you're ultimately doing is:
data &dataset.;
set &dataset.;
[for some set of &variables,&tos, &froms]
&variable. = translate(&variable.,&to.,&from.);
[/set of variables]
run;
So what you need is a couple of macros. Assuming you have a dataset with
<dataset> <varname> <to> <from>
You can call this pretty easily. Two ways:
Run it as a set of nested macros/calls. This is a bit messier, but might be a bit easier to understand.
%macro do_dataset(data=);
proc sql noprint;
select cats('%convert_Var(var=',varname,',to=',to,',from=',from,')')
into :convertlist separated by ' '
from dataset_with_conversions
where dataset="&data.";
quit;
data &data;
set &data;
&convertlist.;
run;
%mend do_dataset;
%macro convert_var(var=,to=,from=);
&var. = translate(&var.,"&to.","&from.");
%mend convert_var;
proc sql noprint;
select cats('%do_dataset(data=',dataset,')')
into :dslist separated by ' '
from dataset_with_conversions;
quit;
&dslist;
Second, you can do all of that in one datastep using call execute (rather than having two different steps). IE, do a by dataset statement, then for first.dataset execute data <dataset>; (filling in that) and for last.dataset execute run, and otherwise execute the translates.
More complicated, but one pass solution - depends on your comfort level which you prefer, they should generally work similarly.
if you want to skip something based on the parameter, if data_repl is set as null, you can add a check for the value, it will avoid error causing during the include statement, since at that time this will be null and which may cause error. E.g
if libary path is derived based on variable passed. which will lead to invalid library path during the include statement, We can use the skip statement.
%macro DoSomething(data_repl=);
%if "&data_repl" ne "" %then %do;
// your code goes here.
%end;
%mend;
%DoSomething
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 ∑
Hi I have a program to use one macro to call another one.
I have two month(jun12 and jul12) and each month has two parts(1 & 2), I want to do a loop which I construct a macro called"Loop", Inside it, I constructed a Array, and used Do comment do call a macro "try".
Seems like it doesn't work. Can someone help me with it? Thank you!
LIBNAME EC100006 "G:\sample";
%MACRO try(month=,part=);
...FROM EC100006.monthitsum&month.lag&part AS t1
%MEND try;
%Macro test;
ARRAY Mon(2) jun12 jul12;
%Do i=1 %to 2;
%Do j=1 %to 2
%try(month=Mon(i),part=j)
%End
%End
%Mend test;
%test
You can't have an array of macro variables.
The simplest way to repeatedly call a macro with a list of parameters is to make a dataset with those parameters and call it from the dataset, either with CALL EXECUTE or using PROC SQL to create a macro list of macro calls.
data call;
input month $ part;
datalines;
jun12 1
jul12 2
;;;;
run;
proc sql;
select cats('%try(month=',month,',part=',part,')') into :mcalllist
separated by ' '
from call;
quit;
&mcalllist;
That only works if you have less than 20,000 characters worth of calls or so - if it's more than that you need to try a different option (%include file or call execute).
So right now it's something like this
LIBNAME EC100006 "G:\sample";
%MACRO try(month=,part=);
...FROM EC100006.monthitsum&month.lag&part AS t1
%MEND try;
Data Array
ARRAY Mon{2} jun12 jul12;
RUN;
%Macro test;
%Do i=1 %to 2;
%Do j=1 %to 2
%try(month=Mon(i),part=j)
%End
%End
%Mend test;
%test