How to use the && macro variable in SAS - loops

I'm working with SAS and I have to create some macro variables within a DO loop. This is a portion of my code:
%if &dsempty888=0 %then %do;
data _null_;
set freq_&&var&i;
if &&var&i=888888888 then do;
call symput("cont8_&&var&i",percent);
end;
run;
%end;
%if &dsempty888=1 %then %do;
%let cont8_&&var&i=0;
%end;
%if &dsempty999=0 %then %do;
data _null_;
set freq_&&var&i;
if &&var&i=999999999 then do;
call symput("cont9_&&var&i",percent);
end;
run;
%end;
%if &dsempty999=1 %then %do;
%let cont9_&&var&i=0;
%end;
%if &dsempty444=0 %then %do;
data _null_;
set freq_&&var&i;
if &&var&i=444444444 then do;
call symput("cont4_&&var&i",percent);
end;
run;
%end;
%if &dsempty444=1 %then %do;
%let cont4_&&var&i=0;
%end;
This code is inside another DO loop that run from i=1 to &end.
With this my macro variables cont4_&&var&i cont8_&&var&i cont9_&&var&i are costantly overwrited...and they become unuseful outside of their loop. I tried to name them &&cont4_&&var&i for example. But clearly SAS doesn't solve the macro.
In practice inside of the loop the macro are created, but I don't know how to call them when I need outside.
How can I fix?
Thanks in advance.

You've got a lot of issues here, so let's simplify this some. Here's a very simplified example. This, for example, does something:
%let var1 = age;
%let var2 = height;
%let var3 = weight;
proc freq data=sashelp.class noprint;
tables age/out=freq_age;
tables height/out=freq_height;
tables weight/out=freq_weight;
run;
%macro get_freqs(var_count=);
%do i = 1 %to &var_count.;
data _null_;
set freq_&&var&i;
call symput("cont4_&&var&i",percent);
run;
%end;
%mend get_freqs;
%get_Freqs(var_count=3)
But now if we do
%put cont4_&&var&i;
Of course it doesn't work, since &i has no meaning. So instead we replace that with, say, 1:
%put cont4_&&var1;
Now we get something. But we don't get anything useful, do we, just the variable name. But - at least that's something!
Now we don't really need the second & there, right?
%put cont4_&var1;
But we need a & before this to use the macro variable:
%put &cont4_&var1;
But now we get a warning message, CONT4_ not resolved. Okay, let's add a second & to delay the resolution of the macro variable until &var1 is resolved.
%put &cont4_&var1;
Well, that helped, now it says CONT4_AGE not resolved. Why not? We used call symput to define that, right?
The problem is scoping. call symput probably scoped it locally, meaning it was defined in the macro but not outside of the macro. For this we use call symputx and give it a global scope.
%macro get_freqs(var_count=);
%do i = 1 %to &var_count.;
data _null_;
set freq_&&var&i;
call symputx("cont4_&&var&i",percent,'g');
run;
%end;
%mend get_freqs;
This tells SAS that I want &cont4_age to be defined outside of the macro. Otherwise it will only be available locally - inside the macro - and will get cleaned up.
Now, this works:
%put &&cont4_&var1;
But if you wanted to iterate over &i again, it's a bit more complex. That's because you need to delay those ampersands multiple times, and allow several passes.
Here's what works:
%macro iterate_i(var_count=);
%do i = 1 %to &var_count.;
%put &&&&cont4_&&var&i.;
%end;
%mend iterate_i;
%iterate_i(var_count=3);
Why do we need four &s? Well, we need to get to &&cont4_&var1, right? That happens in the first pass. Here are the three passes:
&&&&cont4_&&var&i -> &&cont4_&var1 -> &cont4_age -> 5.26...
Each pass, the following happens:
&& -> & (and save for next pass)
& -> resolve macro variable
So, that's how you'd iterate over those. You can of course explicitly specify at any level the macro variable - so all of these work:
%let i=1;
%put &&&&cont4_&&var&i;
%put &&cont4_&var1;
%put &cont4_age;

Related

Loop and join table

Let say I want to loop for i = 2002 to 2006 and in each loop, I create a key word like:
w2006
w2005
w2004
w2003
w2002
And each keyword will be the input for a macro, let say mymacro(keyword) which create a table named "result&key" (for example: resultw2006), so that I want to execute 5 time : %mymacro(w2006), %mymacro(w2005)... %mymacro(w2002) to have 5 tables and join them after that.
Do you have any idea of how to do this? In fact I tried but it doesn't work, here is my code:
%macro tojoin;
%do i=2002 %to 2006;
key=w&i;
%mymacro(&key);
%data result;
set result result&key;
%run;
output;
%end;
%mend;
You need to provide more information on what SAS code you are tying to use the macro processor to generate to get a real answer.
But assuming that %MYMACRO will generate a dataset named result&key then it looks like you want to run something like this to call it 5 times and append the results into a single dataset.
%macro tojoin;
%local key;
%do i=2002 %to 2006;
%let key=w&i;
%mymacro(&key);
proc append base=result data=result&key;
run;
%end;
%mend;
You don't need % in front of data or run, you need a %let statement, and you don't need the output command. So:
%macro tojoin;
%do i=2002 %to 2006;
%let key=w&i;
%mymacro(&key);
data result;
set result result&key;
run;
%end;
%mend;
Tom's method is cleaner, but your code was basically correct - just a bit of cleanup needed.

Sas loop for start date and end date

I have a code here, where I take a start date and end date from the user and execute a macro for those 2 dates.
%let mon_last=31MAR2019;
%let mon_first=01JAN2019;
%macro call();
data _null_;
array datelist {2} "&mon_first"d "&mon_last"d;
%do i=1 %to 2;
%let yrmon1=%sysfunc(inputn(datelist{i},mmddyy10.), date9.));
%let yrmon=%sysfunc(putn(&yrmon1,date9.),yymmn6.);
%let yr=%sysfunc(year(&yrmon1),4);
%let mon=%sysfunc(month(&yrmon1),z2);
%datapull(&yrmon., &yr., &mon.);
%end;
%mend;
%call();
But I end up getting the following error whichever way I try:
WARNING: Argument 1 to function INPUTN referenced by the %SYSFUNC or %QSYSFUNC macro function is out of range.
NOTE: Mathematical operations could not be performed during %SYSFUNC function execution. The result of the operations have been set
to a missing value.
ERROR: The function PUTN referenced by the %SYSFUNC or %QSYSFUNC macro function has too few arguments.
The macro processor considers everything as a string. You cannot convert the string datelist{i} into a date value.
Looks like you want to have a macro that can take as input a list of strings that are in a format that can be converted into date values and use those to call another macro.
%macro call(date_list);
%local i yrmon yr mon;
%do i=1 %to %sysfunc(countw(&date_list));
%let yrmon=%sysfunc(inputn(%scan(&date_list,&i),date11.),yymmn6.);
%let yr=%substr(&yrmon,1,4);
%let mon=%substr(&yrmon,5);
%datapull(&yrmon., &yr., &mon.);
%end;
%mend;
%call(31MAR2019 01JAN2019);
If instead you wanted to process every month from start to end then you want a different macro with different inputs. In that case you just need two inputs that each can only have one value.
This time let's code it so that the burden of supplying valid date values falls onto the caller of the macro instead of accepting character strings that need to be converted into dates.
%macro call(start,end);
%local i yrmon yr mon;
%do i=0 %to %sysfunc(intck(month,&start,&end));
%let yrmon=%sysfunc(intnx(month,&start,&i),yymmn6.);
%let yr=%substr(&yrmon,1,4);
%let mon=%substr(&yrmon,5);
%datapull(&yrmon., &yr., &mon.);
%end;
%mend;
%call("01JAN2019"d,"31MAR2019"d);

SAS if statement in do loop

Hi I am trying to write a macro function with do loop and if statement. I think I have messed up the if-then do and do loop and I can't figure out the problem.
I have a table of kids information, which contains columns like age, gender, sports, instruments etc,.
My original code, which works, looks like this:
data old;
set new;
if sports in ("football","basketball") and age <=7 then type =1;
else if sports='swimming' then type=2;
if special_kid=. then do;
if piano ^=. and piano_1 ^=. then do; talent_type=1; type_name=piano_1; end;
if violin ^=. and violin_1 ^=. then do; talent_type=1; type_name=violin_1; end;
end;
run;
I have a bunch of instruments that I want to edit the type and name. I want to write a loop to automatically do it, but I am not sure why the codes below doesn't work.
%let instrm = piano violin;
%macro my_func;
data old;
set new;
%if sports in ("football","basketball") and age <=7 %then type =1;
%else %if sports='swimming' %then type=2;
%do %while (special_kid=.);
%do i % to sysfunc(countw(&instrm));
%let word = %scan(&name, &i);
%if &word ^=. and ^word._1 ^=. %then %do;
talent_type=1; type_name=&word._1;
%end;
%end;
%end;
run;
%mend;
It keeps giving me the errors
ERROR: An unexpected semicolon occurred in the %DO statement.
ERROR: A dummy macro will be compiled.
Can anyone please answer my question? Thanks!
The macro variable instrm is really a value containing a space separated list of variable names. You might be better off abstracting away from the specific variable use role and fallback to a more generic parameter name vars. Also, rather than relying on a macro variable defined in a global or encompassing scope, pass the list in during invocation. You are correct that a space separated list can be iterated over in macro with a %do loop with a top limit that is the countw number of 'words' in the list--your syntax is only a little off.
You don't have to macro-ize everything, and the extra macroification of the sports logic went to far. Remember, macro invocations emit (or generates) source code that feeds into the SAS submit system. The macro coding coding process when more abstract or toolboxy is sometimes referred to as codegen.
Your original code may be faulty because you evaluate (in a single row) multiple special kid variables and perform value assignments to the same 2 variables (talent_type and type_name) and thus may overwrite a value previously assigned. Sometimes, such evaluations and assignments are OUTPUT to separate rows.
%macro my_skill_classifier(data=, out=, special_vars=, special_type=);
%local i var;
data &out;
set &data;
if sports in ("football","basketball") and age <=7 then type = 1;
else
if sports='swimming' then type=2;
* what happens to football/baskeball > 7yr ?;
if missing(special_kid) then do;
%do i = 1 %to sysfunc(countw(&special_vars));
%let var = %scan(&special_vars, &i);
* regular data step code with macro resolutions sprinkled in;
if &var ^=. and var._1 ^=. then do;
talent_type = &special_type;
type_name = &var._1;
* maybe you really mean type_name = "&var._1";
end;
%end; %* end loop over special_vars list;
end;
run;
%mend;
%my_skill_classifier(data=new, out=old, special_vars=piano violin, special_type=1)
In long, make sure your data shaping and evaluation processing methodology is rock solid before starting your macro coding. If you ask yourself Should I macro this?, be conservative and answer no. Be friendly to maintainers and future-self by not over complicating things.
There were few tweaks required in your code, I did some changes. Also, when we are using %if we always use macro variables. Otherwise we are good to use just if statements with normal dataset variables.
%let instrm = piano violin;
%macro my_func;
data old;
set new;
if sports in ("football","basketball") and age <=7 then type =1;
else if sports='swimming' then type=2;
if missing(special_kid) then do;
%do i=1 %to %sysfunc(countw(&instrm));
%let word = %scan(&instrm, &i);
%If &word ^=. and &word._1 ^=. %then %do;
talent_type=1; type_name=&word._1;
%end;
%end;
end;
run;
%mend my_func;

How to use loop with a macro in SAS

I have a question about how to use loop in % macro. I've written a sas macro that looks like that:
%macro SortDaysRolling(outdat,var);
proc sort data = &outdat. (keep=ACM_ACCT_NUM DIM_DATE_ID &var.); by ACM_ACCT_NUM DIM_DATE_ID; run;
%mend SortDaysRolling;
I need to apply this function to a number of files, i.e.:
%SortDaysRolling(days_rolling_1_1_1_4,count_times_days_1_4);
%SortDaysRolling(days_rolling_1_1_5_9,count_times_days_5_9);
%SortDaysRolling(days_rolling_1_1_10_14,count_times_days_10_14);
%SortDaysRolling(days_rolling_1_1_15_19,count_times_days_15_19);
%SortDaysRolling(days_rolling_1_1_20_24,count_times_days_20_24);
%SortDaysRolling(days_rolling_1_1_25_29,count_times_days_25_29);
%SortDaysRolling(days_rolling_1_1_30_44,count_times_days_30_44);
%SortDaysRolling(days_rolling_1_1_45_59,count_times_days_45_59);
%SortDaysRolling(days_rolling_1_1_60_89,count_times_days_60_89);
%SortDaysRolling(days_rolling_1_1_90,count_times_days_90);
and then
%SortDaysRolling(days_rolling_1_2_1_4,count_times_days_1_4);
%SortDaysRolling(days_rolling_1_2_5_9,count_times_days_5_9);
%SortDaysRolling(days_rolling_1_2_10_14,count_times_days_10_14);
%SortDaysRolling(days_rolling_1_2_15_19,count_times_days_15_19);
%SortDaysRolling(days_rolling_1_2_20_24,count_times_days_20_24);
%SortDaysRolling(days_rolling_1_2_25_29,count_times_days_25_29);
%SortDaysRolling(days_rolling_1_2_30_44,count_times_days_30_44);
%SortDaysRolling(days_rolling_1_2_45_59,count_times_days_45_59);
%SortDaysRolling(days_rolling_1_2_60_89,count_times_days_60_89);
%SortDaysRolling(days_rolling_1_2_90,count_times_days_90);
So the middle index changes. Since i = 1, ..., 35, I don't want to copy-paste all those lines 35 times. Is there any way to do the loop?
Thank you very much.
You need to determine your sequence abstraction. Then construct the sort macro invocations based on that.
%macro SortDaysRolling(out, var);
%put NOTE: &SYSMACRONAME: &=out &=var;
%mend;
%macro sort_loop (I_FROM=1, I_TO=&I_FROM, J_FROM=1, J_TO=&J_FROM);
%local I J;
%do I = &I_FROM %to &I_TO;
%do J = &J_FROM %to &J_TO;
%SortDaysRolling(days_rolling_&I._&J._1_4,count_times_days_1_4);
%SortDaysRolling(days_rolling_&I._&J._5_9,count_times_days_5_9);
%SortDaysRolling(days_rolling_&I._&J._10_14,count_times_days_10_14);
%SortDaysRolling(days_rolling_&I._&J._15_19,count_times_days_15_19);
%SortDaysRolling(days_rolling_&I._&J._20_24,count_times_days_20_24);
%SortDaysRolling(days_rolling_&I._&J._25_29,count_times_days_25_29);
%SortDaysRolling(days_rolling_&I._&J._30_44,count_times_days_30_44);
%SortDaysRolling(days_rolling_&I._&J._45_59,count_times_days_45_59);
%SortDaysRolling(days_rolling_&I._&J._60_89,count_times_days_60_89);
%SortDaysRolling(days_rolling_&I._&J._90,count_times_days_90);
%end;
%end;
%mend;
%sort_loop (J_TO=35);
The 10 count_times_days variables could also be abstracted, or constructed from generation rules.
Generating the numbers in-macro would shorten the code whilst making it a bit cryptic.
The below macro generates the table name and varname, and checks if the table exists before executing the %sortdaysrolling macro block.
%macro SortDaysRolling(outdat,var);
proc sort data = &outdat. (keep=ACM_ACCT_NUM DIM_DATE_ID &var.); by ACM_ACCT_NUM DIM_DATE_ID; run;
%mend SortDaysRolling;
options mprint mlogic;
%macro sortvarloop;
%do i = 1 %to 2;
%let j = 1;
%do %until (&j = 90);
%if &j = 1 %then %let k = 4;
%else %let k = %sysevalf(&j + 4)
%if &j = 90 %then %let tabsuff = %str( );
%else %let tabsuff = _&k;
%if %sysfunc(exist(days_rolling_1_&i._&j.&tabsuff.)) %then %do;
%sortdaysrolling(days_rolling_1_&i._&j.&tabsuff.,count_times_days_&j.&tabsuff.);
%end;
%let j = %sysevalf(&j + 5);
%end;
%end;
%mend sortvarloop; %sortvarloop;
But if you really want to use a macro block, you should think a little ahead and adopt some naming convention for easy and less-harsh-on-memory execution.

SYMBOLIC REFERENCE - SCAN function within DO LOOP

I am a relative novice to working with macro variables and seem to have gotten stuck.
I have a macro that opens a table and produces a macro variable &TBL_DIM. using PROC SQL select into.
%macro CREATE_DIM_VAR(tbl, var); %macro _; %mend _;
%syslput tbl = &tbl. / remote=gidwsas;
%syslput var = &var. / remote=gidwsas;
rsubmit;
PROC SQL NOPRINT;
*Create SELECT statement for columns we want;
SELECT ALL_DIM INTO: TBL_DIM SEPARATED BY ', '
FROM ALLDIM_LIST
WHERE TBL = "&TBL." AND VAR = "&VAR."
;
QUIT;
endrsubmit;
%mend;
%CREATE_DIM_VAR(A, GENDER);
When I do the following:
rsubmit;
%put &TBL_DIM.;
endrsubmit;
It works fine.
But now, when I try to call it within another macro:
%macro Execute(); %macro _; %mend _;
rsubmit;
%do n = 1 %to 10;
%let THIS_VAR = %scan(&TBL_DIM., &n.));
%put &THIS_VAR.;
%end;
endrsubmit;
%mend;
%Execute();
I get the error that:
WARNING: Apparent symbolic reference TBL_DIM not resolved.
How do I pass TBL_DIM over to the other macro?
EDIT:
When I modify %Execute() to run entirely from the Remote Server it works - but I still don't understand conceptually why...
rsubmit;
%macro Execute(); %macro _; %mend _;
%do n = 1 %to 10;
%let THIS_VAR = %scan(&TBL_DIM., &n.));
%put &THIS_VAR.;
%end;
%mend;
endrsubmit;
rsubmit;
%Execute();
endrsubmit;
I'm adding my last comment as an answer, as the reference below gives the definitive reason for the OP's observed behaviour.
Interaction between Compute Services and Macro Processing
http://support.sas.com/documentation/cdl/en/connref/61908/HTML/default/viewer.htm#a001584568.htm
In your non-working case, the local macro processor is processing the code within your rsubmit/endrsubmit block before sending the processed block to the remote server. However, when you declare a macro within the rsubmit/endrsubmit block, the entire macro is submitted and defined remotely.

Resources