Multiple values for a sas macro parameter - macros

I'm new to SAS Macro programming and need to enable the following macro to be able to handle and process multiple values for its macro parameters.Hello,
data have;
input name $ ACCOUNT_ID $ cust_id;
cards;
ARTHUR CC1234 1234
TOM eil1235 1235
MIKEZ tb1236 1236
MATT mb1237 1237
LIZ TB1238 1238
PIZ VB1239 1239
TAN MB1240 1240
PANDA . 1241
;
run;
%MACRO algo (IN_DS=,VAR_LIST=,DATA_TYPE_LIST=,OUT_DS=);
DATA &OUT_DS;
SET &IN_DS;
%If &data_type_LIST = num %then
&var_LIST=sum(&VAR_LIST,2);
%else &var_LIST=cats(&var_LIST,'re');;
run;
%mend;
%algo(IN_DS=HAVE,VAR_LIST=CUST_ID,DATA_TYPE_LIST=num,OUT_DS=out1);`
I now need to enable this macro to be able to pass multiple values for the macro parameters. Something like this :
%algo(IN_DS=HAVE,VAR_LIST='CUST_ID,ACCT_ID',DATA_TYPE_LIST='num,char',OUT_DS=out1);
Can someone help me enable this functionality in the macro code.

The parameter argument should be macro quoted with %STR() in the macro invocation.
Try
%algo
( IN_DS=HAVE
, VAR_LIST= %STR (CUST_ID, ACCT_ID)
, DATA_TYPE_LIST=num
, OUT_DS=out1
);
Macro quoting is different than DATA step quoting used for character literals.

Make sure the macro can handle multiple values. In general it is not a good idea to use comma as the delimiter in your list of values when calling a macro.
Usually space is the best delimiter since then you can use the macro value directly in the generated code. For example if your variables are all of the same type you can just use data step ARRAY.
%MACRO algo (IN_DS=,VAR_LIST=,DATA_TYPE_LIST=,OUT_DS=);
DATA &OUT_DS;
SET &IN_DS;
array list &var_list ;
do _n_=1 to dim(list);
%if &data_type_LIST = num %then %do ;
list(_n_)=sum(list(_n_),2);
%end;
%else %do;
list(_n_)=cats(list(_n_),'re');
%end;
end;
run;
%mend algo;
If your variables are NOT all of the same type then you need to generate a separate statement for each variable. In that case you can use a different delimiter if you want, like a pipe character, that is easier to use as delimiter in calls to macro functions like %scan().
%MACRO algo (IN_DS=,VAR_LIST=,DATA_TYPE_LIST=,OUT_DS=);
%local i var;
DATA &OUT_DS;
SET &IN_DS;
%do i=1 %to %sysfunc(countw(&var_list,|));
%let var=%scan(&var_list,&i,|);
%if %scan(&data_type_LIST,&i,|) = num %then %do ;
&var=sum(&var,2);
%end;
%else %do;
&var=cats(&var,'re');
%end;
%end;
run;
%mend algo;
%algo(IN_DS=HAVE,VAR_LIST=CUST_ID|ACCT_ID,DATA_TYPE_LIST=num|char,OUT_DS=out1);

If you want to pass a list of variables and then use that list in the code you posted above, my suggestion would be to treat the &var_list as a list, and use scan to determine how many variables there are, and then loop through the list and execute the code accordingly.

Related

SAS - Do looping from condition1 to condition2

I am looking to have a programme which cleans up some messy data I have, I am looking to do this for both the assets and liabilities side of the project i'm working on.
My question is there a way to use a do loop to use the cleaning up data to first clean up the assets then liabilities. something like this:
%do %I = Asset %to Liability;
%assetorliability= I ;
proc sort data = &assetorliability;
by price;
run;
data want&assetorliability;
set &assetorliability;
if _N_ < 50000;
run;
the actual script is quite long so a singular macro may not be the ideal solution but this loop would be great.
TIA.
EDIT : the programme includes some macros and the errors received are as follows:
%let list =Asset Liability;
%do i=1 %to %sysfunc(countw(&list,%str( )));
%let next=%scan(&list,&i,%str( ));
%Balance;
%end;
in the macro the data steps are named with a balance&list to allow for each scenario. the errors are:
13221 %let list =Asset Liability;
13222 %do i=1 %to %sysfunc(countw(&list,%str( )));
ERROR: The %DO statement is not valid in open code.
13223
13224 %let next=%scan(&list,&i,%str( ));
WARNING: Apparent symbolic reference I not resolved.
WARNING: Apparent symbolic reference I not resolved.
ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric
operand is required. The condition was: &i
ERROR: Argument 2 to macro function %SCAN is not a number.
ERROR: The %END statement is not valid in open code.
The macro %do statement is not as flexible as the data step do statement. To loop over a list of values you would want to put the list into a macro variable and use an index variable in your %do loop.
Note that macro logic needs to be inside of a macro. You cannot use it in "open" code.
%macro do_over(list);
%local i next;
%do i=1 %to %sysfunc(countw(&list,%str( )));
%let next=%scan(&list,&i,%str( ));
proc sort data = &next ;
by price;
run;
data want&next ;
...
%end;
%mend do_over ;
%do_over(Asset Liability)

How to pass macro variable into macro variable name in SAS ?

%let val = ' run';
%macro rrun;
%put successfully run;
%mend;
%macro x;
%r%cmpres(&val.);
/* %rrun;*/
%mend;%x
I"m trying to pass macro variable into macro variable name in SAS. to run %rrun using %cmpres(&val.) passing value to macro name "rrun" I don't understand why this doesn't work. when compress &val is "run"
How can I solve this and still passing &val ?
Thanks,
Why do you have quotes in your macro variable?
166 %let val = ' run';
167 %put |%cmpres(&val)|;
|' run'|
If you want to dynamically generate the name of macro to call it works much easier if you first generate the name into a macro variable and then reference that macro variable. Otherwise you risk confusing the tokenizer.
%macro x;
%local mname;
%let mname=r%cmpres(&val);
%&mname;
%mend;
%x

SAS Error trying to loop through multiple datasets

I'm trying to run some code which will hopefully concatenate multiple months or years worth of data. I am trying to figure out when a field was populated with a value. I.e. there is field XYZ in my data set and it is populated with value A in November 2016. If I run my code from Jan - Dec I would like a new field populated with the date that SAS encounters a non-blank value in that field.
Here's my code:
options mprint symbolgen source mlogic merror syntaxcheck ;
%macro append_monthly(iStart_date=, iEnd_date=);
%local tmp_date i;
%let tmp_date = %sysfunc(intnx(month,&iStart_date,0,beginning)) ;
%do %while (&tmp_date le &iEnd_date);
%let i = %sysfunc(sum(&tmp_date),yymmn4.);
%put &i.;
%let tmp_date = %sysfunc(intnx(month,&tmp_date,1,beginning)) ;
libname note "my.qualifiers.fords.note&i." disp=shr;
data new ;
set note.file ;
%if ln_note_crbur_date_delinq ne '' %then spc_cmt_date = &i.;
run;
%end;
%mend;
%append_monthly(iStart_date=%sysfunc(mdy(5,1,2016)), iEnd_date=%sysfunc(mdy(10,1,2016)) );
LIBNAME _ALL_ CLEAR;
Here's a sample from log with errors :
SYMBOLGEN: Macro variable TMP_DATE resolves to 20606
SYMBOLGEN: Macro variable IEND_DATE resolves to 20728
MLOGIC(APPEND_MONTHLY): %DO %WHILE(&tmp_date le &iEnd_date) condition is TRUE; loop will iterate again.
MLOGIC(APPEND_MONTHLY): %LET (variable name is I)
SYMBOLGEN: Macro variable TMP_DATE resolves to 20606
MLOGIC(APPEND_MONTHLY): %PUT &i.
SYMBOLGEN: Macro variable I resolves to 1606
1606
MLOGIC(APPEND_MONTHLY): %LET (variable name is TMP_DATE)
SYMBOLGEN: Macro variable TMP_DATE resolves to 20606
MPRINT(APPEND_MONTHLY): spc_cmt_date = 1605 run;
SYMBOLGEN: Macro variable I resolves to 1606
MPRINT(APPEND_MONTHLY): libname note "my.qualifiers.fords.note1606" disp=shr;
ERROR: Unable to clear or re-assign the library NOTE because it is still in use.
ERROR: Error in the LIBNAME statement.
NOTE: The SAS System stopped processing this step because of errors.
WARNING: The data set WORK.NEW may be incomplete. When this step was stopped there were 0 observations and 622 variables.
WARNING: Data set WORK.NEW was not replaced because this step was stopped.
NOTE: The DATA statement used 0.01 CPU seconds and 49483K.
NOTE: The address space has used a maximum of 4292K below the line and 240388K above the line.
I can't figure out why this isn't working. Maybe this could work using Proc append.
Basically, I just want my output with a field that returns a date in the form of YYMM for when field ln_note_crbur_date_delinq was non-blank.
Any help would be greatly appreciated
I'd guess the reason for your error is that the handle is not being cleared on your source file before the next libname statement tries to re-assign.
An easy fix would be to use a different alias (libref) each time, as follows:
libname note&i "my.qualifiers.fords.note&i." disp=shr;
Then adjust your data step like so:
data new ;
set note&i..file ;
The next part appears to be confusion between macro logic and data step. Simply remove the % symbols as follows:
if ln_note_crbur_date_delinq ne '' then spc_cmt_date = &i.;
Finally, add a proc append before the %end as follows:
proc append base=work.final data=new; run;
If work.final does not exist, it will be created in the same format as new.
EDIT:
following discussion in comments, here is a revised approach:
%macro append_monthly(iStart_date=, iEnd_date=);
%local tmp_date i set_statement;
%let tmp_date = %sysfunc(intnx(month,&iStart_date,0,beginning)) ;
%do %while (&tmp_date le &iEnd_date);
%let i = %sysfunc(sum(&tmp_date),yymmn4.);
%let tmp_date = %sysfunc(intnx(month,&tmp_date,1,beginning)) ;
%let set_statement=&set_statement &i..file;
libname note&i "my.qualifiers.fords.note&i." disp=shr;
%end;
data new ;
set &set_statement;
if ln_note_crbur_date_delinq ne '' then spc_cmt_date = &i.;
run;
%mend;
%append_monthly(iStart_date=%sysfunc(mdy(5,1,2016)), iEnd_date=%sysfunc(mdy(10,1,2016)) );
LIBNAME _ALL_ CLEAR;

Bootstrap macro in SAS

I started to learn %macro in SAS and now I'm trying to implement simple bootstrap with histogram as an output.
/*Create K data sets(vectors)*/
%macro datasets(K);
%do i=1 %to &K;
data indata&i;
%do j = 1 %to 50;
x=(rand('normal',2,9));
output;
%end;
run;
%end;
%mend datasets;
%datasets(3);
/*Bootstrap and hist*/
%macro boot (data,res);
%do i=1 %to &res;
%let x = (sample(&data,50));
%let m = (mean(&x));
%end;
proc iml;
read &m into A;
create DataM from A;
append from A;
close Data1;
quit;
proc univariate data=Data1;
histogram m;
run;
%mend boot;
%boot(Indata1,100);
It doesn't work and I can't understand why. Can you point me the mistake?
Use PROC SURVEYSELECT to generate bootstrap samples then do bootstrap analysis by Replication (a variable created by SURVEYSELECT). Your macro idea will be far too slow.
As mentioned use Proc SurveySelect and Proc Means. You can select all 100 samples in one Proc SurveySelect and then apply Proc Means with a BY statement to calculate the means in one step. Macro's don't add anything to the solution here.
I'm posting both solutions - the macro solution does take longer as well.
*Without macro;
proc surveyselect data=indata1 out=rsample method=srs n=50 reps=100;
run;
proc means data=rsample noprint;
by replicate;
var x;
output out=Data1 mean(x)=m;
run;
proc univariate data=Data1;
histogram m;
run;
*Macro solution;
%macro boot(data, res);
%do i=1 %to &res;
%*Currently pulls the same sample every time but you can fix that part;
proc surveyselect data=&data out=x method=srs n=50 reps=1 seed=343434;
run;
proc means data=x noprint;
var x;
output out=m mean(x)=m;
run;
proc append base=DataM data=m;
run;
%end;
%mend;
%boot(Indata1,10);
Perhaps it will help if we outline some of the ways that the posted macro code does NOT work. If nothing else then as examples of things to avoid.
If the first macro , %datasets(), you are using a macro %DO loop where you should use a normal data step DO loop. Also make sure to define your local macro variables as local. This will prevent the macro from modifying the value of an existing macro variable with the same name.
/*Create K data sets(vectors)*/
%macro datasets(K);
%local i ;
%do i=1 %to &K;
data indata&i;
do j = 1 to 50;
x=(rand('normal',2,9));
output;
end;
drop j;
run;
%end;
%mend datasets;
In the second macro you have a %DO loop that does nothing.
%do i=1 %to &res;
%let x = (sample(&data,50));
%let m = (mean(&x));
%end;
You repeat the exact same %LET statements multiple times. The result does not change since the loop variable i is not referenced at all. If you called the macro with data=indata1 then the result of the two statements will be that X=(sample(indata1,50)) and that M=(mean((sample(indata1,50)))). I think that perhaps you intended that the strings sample and mean might take some action, but since they have no macro triggers (& or %) they are just streams of characters to the macro processor.
I am not an expert on IML, but those statements also do not look like they are doing much.

Attribute Change of Macro Variable in SAS

I have a macro variable like &a having value of (1234.45)*. I am trying to replace the ( and ) from the macro and replace them with a negative mark since its a negative number.
%let a=(1234.45)
Some of the options which I have appplied are
%macro test1;
%if %substr(&a,1,1) = '(' %then %do;
%let b=%substr(&a,1,'-')
%end;
%mend;
%test1
This is numeric conversion and best handled in a data step. If for some reason you really need a macro variable, use SYMPUT.
%let a=(1234.45);
data _null_;
x=input("&a.",comma10.);
call symputx("b",x);
run;
%put &=a &=b;