Sas macro: Run macro per element in array - sas

I am trying to call another macro for each element in an array. This is an easier description of the same problem. I would expect that the macro prints each var in another row, but it does not. Why? How can this be fixed?
%macro test(varlist);
%local var i ;
%do i = 1 %to %sysfunc(countw(&varlist)) ;
%let var = %scan(&varlist,&i) ;
%put looped var is &var;
%end ;
%mend test;
%test(var1 var2 var3)

SAS Macro does not contain an array structure. However, in SAS Macro, a textual sequence depicting a list of delimited items can be processed in an array like manner. The items depicted can represent other things that can be further utilized or emitted as source code for further processing.
The code as posted does log one line per item in the space separated list of what you are calling variable names.
SAS Log
1 %macro test(varlist);
2 %local var i ;
3 %do i = 1 %to %sysfunc(countw(&varlist)) ;
4 %let var = %scan(&varlist,&i) ;
5 %put looped var is &var;
6
7 %end ;
8 %mend test;
9
10 %test(var1 var2 var3)
looped var is var1
looped var is var2
looped var is var3
For the case of code, especially macro code, not loggin information when expected there is likely other programming statements that are incorrect and changing the expected state. You might have unbalanced quotes that are causing the macro source code to be considered part of another step. Sometimes, when the executor can't be returned to a proper 'ready' state, a restart of the SAS session is the best course of action.

Related

Looping a set of Macros

I'm looking to create a loop such that I run two macros for each dataset
%Let Classification = Data1 data2 data3 data4;
%let index = 1;
%do %until (%Scan(&Classification,&index," ")=);
%Macro1;
%Macro2;
%end;
%let index = %eval(&Index + 1);
The problem is my macros are not pre-loaded and are stored in a macro library, is it possible to do this if I run the above as a macro?
Any advice is appreciate in making this loop of macros work
EDIT:
In my ideal situation the loop would run like a macro
%Macro;
where inside it would look like
%Macro Macro;
%let index = 1;
%do %until (%scan(&classification,&index," ")=);
<Lines of Code>
%end;
%let index = %eval(&Index + 1);
%end;
%mend;
Another problem is my macros enclosed in the loop use the &classification to differentiate between data1, data2, data3, data4 as we process through the different lines of code.
It is probably easier to just iterate over the index. Use the countw() function to find how many iterations to do.
%macro loop(list);
%local index next ;
%do index=1 %to %sysfunc(countw(&list,%str( )));
%let next=%scan(&list,&index,%str( ));
... code to process &NEXT ...
%end;
%mend ;
Then pass in the list to the macro as the parameter value.
%Let Classification = Data1 data2 data3 data4;
%loop(&classification);
SAS does not allow the %DO statement in open-code. When you submit an open code loop you will get log messages
ERROR: The %DO statement is not valid in open code.
...
ERROR: The %END statement is not valid in open code.
as #Tom mentioned the macro %SCAN test should check for null string. Another common and more robust way is to check before token extraction. %do %until will iterate poorly when the classification passed is empty. A %do %while tests the classification scan prior to interior macro invocations. Another common test for null macro value is checking for 0 length and leveraging 0=false ^0=true automatic evaluation.
When the loop is to call other macros with the token value the best practice is to pass the token value instead of having the called macro presume the token symbol (aka macro variable) already exists (in a containing scope) prior the iterated macros invocation.
Example
%macro mydispatch (classification=);
%local index token;
%let index = 1;
%do %while ( %length (%scan (&classification, &index)));
%let token = %scan(&classification,&index));
%* emit code specifically for token;
* this is for &token;
%* iterated invocations, perform two analysis for each data set listed in classification;
%* second analysis is passed another argument specifying the data set that should be used to store output;
%analysis_1 (data=&token)
%analysis_2 (data=&token, out=WORK.results_&token.)
%let index = %eval(&index+1);
%end;
%mend mydispatch;
%mydispatch (classification=data1 data2 data3 data4)
The macro being in an autocall library (which is what I assume you refer to?) does not have any impact on how the above would work. If it's not in an autocall library you'll have to hook up the catalog up to the autocall library first.
In re: your edits; yes, you will need this to be in a macro (I assumed it was a subset of a macro initially). %do is not currently allowed in open code (this may change, but not today).
Note you have several significant issues in your code:
the incrementor is not in the loop
the scan function is wrong; macro language does not use quotations, so
%do %until (%Scan(&Classification,&index," ")=);
needs to be
%do %until (%Scan(&Classification,&index)=);
(space is the default separator), and if you really needed to clarify space:
%do %until (%Scan(&Classification,&index,%str( ))=);
Your macros do not utilize parameters; they should. %macro1; apparently uses &classification and &index; instead you should pass it the thing you want (the "word" from &classification) as a parameter.

Get values of Macro Variables in a SAS Table

I have a set of input macro variables in SAS. They are dynamic and generated based on the user selection in a sas stored process.
For example:There are 10 input values 1 to 10.
The name of the macro variable is VAR_. If a user selects 2,5,7 then 4 macro variables are created.
&VAR_0=3;
&VAR_=2;
&VAR_1=5;
&VAR_2=7;
The first one with suffix 0 provides the count. The next 3 provides the values.
Note:If a user select only one value then only one macro variable is created. For example If a user selects 9 then &var_=9; will be created. There will not be any count macro variable.
I am trying to create a sas table using these variables.
It should be like this
OBS VAR
-----------
1 2
2 5
3 7
-----------
This is what I tried. Not sure if this is the right way to do approach it.
It doesn't give me a final solution but I can atleast get the name of the macro variables in a table. How can I get their values ?
data tbl1;
do I=1 to &var_0;
VAR=CAT('&VAR_',I-1);
OUTPUT;
END;
RUN;
PROC SQL;
CREATE TABLE TBL2 AS
SELECT I,
CASE WHEN VAR= '&VAR_0' THEN '&VAR_' ELSE VAR END AS VAR
from TBL1;
QUIT;
Thank You for your help.
Jay
SAS helpfully stores them in a table for you already, you just need to parse out the ones you want. The table is called SASHELP.VMACRO or DICTIONARY.MACROS
Here's an example:
%let var=1;
%let var2=3;
%let var4=5;
proc sql;
create table want as
select * from sashelp.vmacro
where name like 'VAR%';
quit;
proc print data=want;
run;
I think the real issue is the inconsistent behavior of the stored process. It only creates the 0 and 1 variable when there are multiple selections. I think that your example is a little off. If the value of VAR_0 is three then their should be a VAR_3 macro variable. Also the value of VAR_ and VAR_1 should be set to the same thing.
To fix this in the past I have done something like this. First let's assign the parameter name a macro variable so that the code is reusable for other programs.
%let name=VAR_;
Then first make sure the minimal macro variables exist.
%global &name &name.0 &name.1 ;
Then make sure that you have a count by setting the 0 variable to 1 when it is empty.
%let &name.0 = %scan(&&&name.0 1,1);
Then make sure that you have a 1 variable. Since it should have the same value as the macro variable without a suffix just re-assign it.
%let &name.1 = &&&name ;
Now your data step is easier.
data want ;
length var $32 value $200 ;
do i=1 to &&&name.0 ;
var=cats(symget('name'),i);
value=symget(var);
output;
end;
run;
I don't understand your numbering scheme and recommend changing it, if you can; the &var_ variable is very confusing.
Anyway, the easiest way to do this is SYMGET. That returns a value from the macro symbol table which you can specify at runtime.
%let VAR_0=3;
%let VAR_=2;
%let VAR_1=5;
%let VAR_2=7;
data want;
do obs = 1 to &var_0.;
var = input(symget(cats('VAR_',ifc(obs=1,'',put(obs-1,2.)))),2.);
output;
end;
run;

sas macro loop to rename variable

Hi I am trying to rename variables using SAS Macro loop.
%Let t1=12Mth;
%Let t2=20;
%Let t3=30;
%Let t4=40;
%Let t5=50;
%Let t6=60;
%macro Re(time);
%Do I = 1 %to &time.;
data MilkNew;
set Milk;
rename MT&&t&I..Sp=MTSp&&t&I.;
run;
%end;
%mend Re;
%Re(6)
This loop is mean to rename MT...Sp to MTSp.... Eg:MT20SP to MTSp20.
When I run my loop, there was no error but the variable names were not changed in MilkNew at all.
Where does the problem come? Thanks!
If the only purpose of the macro is to rename the variables in the data set, then why read the data with a set statement. Your data set is probably really small so you don't even realize the inefficiency of doing that. Instead use the modify statement in proc datasets to accomplish the same thing, but more efficiently. Here's an alternative macro for you.
%macro renamevar(dsname, time);
%local lib ds i;
%let lib = %sysfunc(coalescec(%scan(&dsname, -2, %str(.)), work));
%let ds = %scan(&dsname, -1, %str(.));
proc datasets lib=&lib nolist;
modify &ds;
rename
%do i = 1 %to &time;
mt&&t&i..Sp=MTSp&&t&i.
%end;
;
quit;
%mend;
%renamevar(milk, 6);
Here's the log after the macro call:
NOTE: Renaming variable mt12MthSp to MTSp12Mth.
NOTE: Renaming variable mt20Sp to MTSp20.
NOTE: Renaming variable mt30Sp to MTSp30.
NOTE: Renaming variable mt40Sp to MTSp40.
NOTE: Renaming variable mt50Sp to MTSp50.
NOTE: Renaming variable mt60Sp to MTSp60.
NOTE: MODIFY was successful for WORK.MILK.DATA.
NOTE: PROCEDURE DATASETS used (Total process time):
real time 0.00 seconds
cpu time 0.01 seconds
You should move the loop so that it only generates just the RENAME statement (or even just the old=new name pairs). What is happening now is that you keep overwriting MilkNew so only the last RENAME has any effect.
%macro Re(time);
data MilkNew;
set Milk;
%do I = 1 %to &time.;
rename MT&&t&I..Sp=MTSp&&t&I.;
%end;
run;
%mend Re;
%Re(6)
You should have seen the last variable name in the loop (so the 6th) changed. That's because you repeated the same data step with the same source dataset but a different destination - so each time you 'forgot' the changes made in the earlier step.
So, this would've worked, though I'll get in a minute to why this isn't a good way to do this.
%Let t1=12Mth;
%Let t2=20;
%Let t3=30;
%Let t4=40;
%Let t5=50;
%Let t6=60;
%macro Re(time);
%Do I = 1 %to &time.;
data Milk;
set Milk;
rename MT&&t&I..Sp=MTSp&&t&I.;
run;
%end;
%mend Re;
data milk;
input
MT12mthSP
MT20SP
MT30SP
MT40SP
MT50SP
MT60SP
;
datalines;
12 20 30 40 50 60
;;;;
run;
%Re(6)
Here I had it make all changes to Milk and save them back in that dataset. If you want to preserve Milk then first make Milk_New then have that in both set and data statements.
Second, you should not do a new data step for each change. Macros don't have to have a data step in them; they can be run inside the datastep.
So for example:
%macro Re(time);
%Do I = 1 %to &time.;
rename MT&&t&I..Sp=MTSp&&t&I.;
%end;
%mend Re;
data milk_new;
set milk;
%Re(6);
run;
Even better would be generating this list outside of a macro entirely - look up "generating code SAS" for suggestions on that.
If you didn't see any renames at all, you also may have an issue where a label is present on the column(s). That won't affect your usage of the variable name, but it will make it confusing. Use
label _all_;
Or include a label-clearing statement (label <varname>; where you pop in the same variable name as the original variable name before rename) inside your macro loop to fix that.

sas- rank inside a macro

I'm trying to do a loop over a liststo perform ranks for several variables.
And then I do the loop with:
options mprint;
%macro ranks(listado);
%let count=%sysfunc(countw(&listado));/*counw= count words in a string*/
%do i=1 %to &count;
%put 'count' &count;
%let vari=%qscan(&listado,&i,%str(,));
%put 'vari' &vari;
proc rank data=labo2.J_tabla_modelo groups=10 out=labo2.tmp;
var &vari.;
ranks rk_&vari.;
run;
%end;
%mend;
%ranks(%str(G_MERGE6_t1_monto6,A_CLI_monto_sucursal_1,A_CLI_monto_sucursal_2,
A_CLI_monto_sucursal_3, A_CLI_monto_sucursal_4,A_M_0705_monto));
I get the following error:
ERROR: Number of VAR statement variables was not equal to the number of RANKS statement variables.
Don't know how to solve it. Because if I run the code written by the macro works.
Thanks!
First off, don't do the macro loop this way. It's messy. Generate a list of macro calls.
Second, you're not really wanting to do that either, here. What you need to generate is this:
proc rank data=whatever out=whatever;
var v1 v2 v3 v4;
ranks r_v1 r_v2 r_v3 r_v4;
run;
What you are generating is a bunch of different PROC RANKs, which isn't ideal.
How I'd do it:
data my_vars;
length vari $32;
input vari $;
datalines;
G_MERGE6_t1_monto6
A_CLI_monto_sucursal_1
A_CLI_monto_sucursal_2
A_CLI_monto_sucursal_3
A_CLI_monto_sucursal_4
A_M_0705_monto
;;;;
run;
proc sql;
select cats('r_',vari)
into :ranklist separated by ' '
from my_vars;
select vari
into :varlist separated by ' '
from my_vars;
quit;
proc rank data=whatever out=whatever groups=10;
var &varlist;
rank &ranklist;
run;
If you actually do need the individual PROC RANK calls, then you need to figure out how to deal with the output, and then do a similar approach.
(Also, odds are that first datastep isn't needed- you probably have that data somewhere, like in dictionary.columns).

"For in" loop equivalent in SAS 9.3

I'm searching for a while an equivalent of the for in loop (like in Python or in R) in SAS 9.3 macro language. The DO loop seem's to be the solution but did't work exactly as I want.
I founded a way to do it in a data step with a DO loop but it don't work with the macro language.
For example, in a data step, this code is working :
DATA _NULL_;
DO i = 1,3,5,9;
PUT i;
END;
RUN;
And then the log prompt as expected :
1
3
5
9
When I try to do the same with an %DO loop in a Macro, I have an error.
%MACRO test();
%DO i = 1,2,4,9 ;
%PUT i = &i;
%END;
%MEND;
%test();
The log promp these messages :
ERROR: Expected %TO not found in %DO statement.
ERROR: A dummy macro will be compiled
I'm quite new in SAS and stackoverflow so I hope my question is no too stupid. It's so simple to do this in Python and R then it must have a simple way to do it in SAS.
Thank's for help - J. Muller
The closest I've ever come across to this pattern in SAS macro language is this:
%MACRO test();
%let j=1;
%let vals=1 2 4 9;
%do %while(%scan(&vals,&j) ne );
%let i=%scan(&vals, &j);
%put &i;
%let j=%eval(&j+1);
%end;
%MEND;
%test();
(Warning: untested, as I no longer have a SAS installation I can test this out on.)
You can certainly get around it this way:
options mindelimiter=,;
options minoperator;
%MACRO test();
%DO i = 1 %to 9 ;
%if &i in (1,2,4,9) %then %do;
%PUT i = &i;
%END;
%end;
%MEND;
%test();
However, I think you can usually avoid this sort of call by executing your macro multiple times rather than attempting to control the loop inside the macro. For example, imagine a dataset and a macro:
data have;
input x;
datalines;
1
2
4
9
;;;;
run;
%macro test(x);
%put &x;
%mend test;
Now you want to call %test() once for each value in that list. Okay, easy to do.
proc sql;
select cats('%test(',x,')') into :testcall separated by ' ' from have;
quit;
&testcall;
That works just as well as your %do in loop, except it's data driven, meaning if you want to change the calls you just change the dataset (or if your data changes, the call automatically changes!). In general, SAS is more effective when designed as data driven programming rather than as entirely written code.