Logic to leave loop not working SAS - loops

I want to repeat some code, varying one of the parameters and test whether a condition is met. If the condition is met, I want to leave the loop, if not I want to proceed to the next value of the parameter. I am using the below code, which works fine except that it does not leave the loop when I expect it to. Despite the summary showing that the condition should have been met it always seems to resolve to False.
%macro set_downward_caps(year, in_year_tolerance, large, small, start, end, increment);
%do c = &start. %to &end. %by &increment.;
%let nominal_down_large_&year. = %sysevalf(&large. + (&c. / 1000));
%let nominal_down_small_&year. = %sysevalf(&small. + (&c. / 100));
%let real_down_large_&year. = %sysevalf((1 - &&nominal_down_large_&year.) * &&rpi&year.);
%let real_down_small_&year. = %sysevalf((1 - &&nominal_down_small_&year.) * &&rpi&year.);
%rates(&year.);
proc means data = output.s_&scenario. noprint nway;
var transbill&year.;
output out = temporary (drop = _type_ _freq_) sum=cost;
run;
data _null_;
set temporary;
call symput('cost', cost);
run;
data temp;
length scenario $ 30;
scenario = "&scenario.";
large = &&real_down_large_&year.;
small = &&real_down_small_&year.;
cost = &cost.;
run;
data output.summary_of_caps;
set output.summary_of_caps temp;
run;
%if %sysfunc(abs(&cost.)) le &in_year_tolerance. %then leave;
%end;
%mend set_downward_caps;
So the sumary_of_caps table contains values that suggest that the following condition should have resolved to true:
%if %sysfunc(abs(&cost.)) le &in_year_tolerance. %then leave;
I've tried sticking it in sysevalf but to no avail.

I don't think there is a LEAVE equivalent for macro code. Why not just use a %GOTO? Or since you seem to want to totally leave the macro you could use %RETURN.
Also if you are comparing floating point numbers you need to use %SYSEVALF(). The implied %EVAL() call of the %IF statement will only handle integer arithmetic.
%if %sysevalf(%sysfunc(abs(&cost)) le &in_year_tolerance) %then %return;
Why do the calculations in macro logic at all? I am not sure why you have all of those macro variables, unless the %RATES() macro is referencing them? But if it needs them why aren't they parameters to the macro like you are passing in &YEAR?
You have plenty of data steps in your current code where you could do the calculation there and just set a flag variable that you can use to control whether to exit the loop.
%macro set_downward_caps
(year
,in_year_tolerance
,large
,small
,start
,end
,increment
);
%local c leave ;
%do c = &start %to &end %by &increment;
%local nominal_down_large_&year ;
%local nominal_down_small_&year ;
%local real_down_large_&year ;
%local real_down_small_&year ;
%let nominal_down_large_&year. = %sysevalf(&large. + (&c. / 1000));
%let nominal_down_small_&year. = %sysevalf(&small. + (&c. / 100));
%let real_down_large_&year. = %sysevalf((1 - &&nominal_down_large_&year.) * &&rpi&year.);
%let real_down_small_&year. = %sysevalf((1 - &&nominal_down_small_&year.) * &&rpi&year.);
%rates(&year.);
proc means data = output.s_&scenario. noprint nway;
var transbill&year.;
output out = temporary sum=cost;
run;
data temp;
length scenario $ 30;
scenario = "&scenario.";
large = &&real_down_large_&year.;
small = &&real_down_small_&year.;
set temporary (keep=cost);
call symputx('leave',abs(cost) le &in_year_tolerance);
put (_all_) (=);
run;
data output.summary_of_caps;
set output.summary_of_caps temp;
run;
%if (&leave) %then %goto quit;
%end;
%quit:
%mend set_downward_caps;

Related

SAS macro loop to loop through datasets in Set statement

I have 2 SAS macro loops - 1 to create time series dataset and another to append the datasets into a master dataset.
Macro Date_loop1 creates time series datasets. In this example I am creating dummy datasets. In reality, the real datasets are several MBs large. So using proc append, within Date_loop1, to the master dataset, results in slow progress. Therefore, I have created a second macro, Date_loop2, to stacks all the datasets above in the SET statement. The issue is that the second loop is not working as intended. Can you please help?
Note: if you run Date_loop1, you can see how the datasets are created. Date_loop2 should also work in the similar manner.
%MACRO LOOP1;
DATA ROLLRATE_&ST_YYYYMM._&ED_YYYYMM.;
ST_YYYYMM = &ST_YYYYMM;
ED_YYYYMM = &ED_YYYYMM;
RUN;
%MEND;
%MACRO DATE_LOOP1(START,END);
%LET START_YEAR = %SYSFUNC(FLOOR(&START/100));
%LET START_MONTH = %SYSFUNC(MOD(&START,100));
%LET END_YEAR = %SYSFUNC(FLOOR(&END/100));
%LET END_MONTH = %SYSFUNC(MOD(&END,100));
%LET START_DATE = %SYSFUNC(MDY(&START_MONTH,01,&START_YEAR));
%LET END_DATE = %SYSFUNC(MDY(&END_MONTH,01,&END_YEAR));
%LET DIF=%EVAL(%SYSFUNC(INTCK(MONTH,&START_DATE,&END_DATE)));
/*DATA MASTER_ROLLRATE;RUN;DATA ROLLRATE;RUN;*/
%DO I=0 %TO &DIF;
%DO J=&I %TO &DIF;
%LET ST_DT = %SYSFUNC(INTNX(MONTH,&START_DATE,&I,B)); /*B - DATE AT THE BEGINNING OF THE WEEK/MONTH/YEAR */
%LET ST_YEAR = %SYSFUNC(YEAR(&ST_DT));
%LET ST_MONTH = %SYSFUNC(MONTH(&ST_DT));
%LET ST_YYYYMM = %EVAL(&ST_YEAR*100+&ST_MONTH);
%LET ED_DT = %SYSFUNC(INTNX(MONTH,&START_DATE,&J,B));
%LET ED_YEAR = %SYSFUNC(YEAR(&ED_DT));
%LET ED_MONTH = %SYSFUNC(MONTH(&ED_DT));
%LET ED_YYYYMM = %EVAL(&ED_YEAR*100+&ED_MONTH);
%PUT &ST_YYYYMM &ED_YYYYMM;
%LET START_YYYYMM = &ST_YYYYMM;%LET END_YYYYMM = &ED_YYYYMM;%LOOP1;RUN;
%END;
%END;
%MEND DATE_LOOP1;
%DATE_LOOP1(201901,201902);
%MACRO DATE_LOOP2(START,END);
%LET START_YEAR = %SYSFUNC(FLOOR(&START/100));
%LET START_MONTH = %SYSFUNC(MOD(&START,100));
%LET END_YEAR = %SYSFUNC(FLOOR(&END/100));
%LET END_MONTH = %SYSFUNC(MOD(&END,100));
%LET START_DATE = %SYSFUNC(MDY(&START_MONTH,01,&START_YEAR));
%LET END_DATE = %SYSFUNC(MDY(&END_MONTH,01,&END_YEAR));
%LET DIF=%EVAL(%SYSFUNC(INTCK(MONTH,&START_DATE,&END_DATE)));
DATA MASTER_ROLLRATE;
SET
%DO I=0 %TO &DIF;
%DO J=&I %TO &DIF;
%LET ST_DT = %SYSFUNC(INTNX(MONTH,&START_DATE,&I,B)); /*B - DATE AT THE BEGINNING OF THE WEEK/MONTH/YEAR */
%LET ST_YEAR = %SYSFUNC(YEAR(&ST_DT));
%LET ST_MONTH = %SYSFUNC(MONTH(&ST_DT));
%LET ST_YYYYMM = %EVAL(&ST_YEAR*100+&ST_MONTH);
%LET ED_DT = %SYSFUNC(INTNX(MONTH,&START_DATE,&J,B));
%LET ED_YEAR = %SYSFUNC(YEAR(&ED_DT));
%LET ED_MONTH = %SYSFUNC(MONTH(&ED_DT));
%LET ED_YYYYMM = %EVAL(&ED_YEAR*100+&ED_MONTH);
%LET START_YYYYMM = &ST_YYYYMM;%LET END_YYYYMM = &ED_YYYYMM;
ROLLRATE_&START_YYYYMM._&END_YYYYMM;
%END;
%END;
;
RUN;
%MEND DATE_LOOP1;
%DATE_LOOP2(201901,201902);
You can use name lists in the SET statement and avoid macro altogether.
Every dataset whose name starts with rollrate will be stacked:
data want;
set work. rollrate:;
run;
There are two issues I am currently seeing
The %mend for second loop is date_loop1 instead of date_loop2 (Second last line of code)
%MEND DATE_LOOP1; /Should be DATE_LOOP2/
%DATE_LOOP2(201901,201902);
Please remove semicolon after ROLLRATE_&START_YYYYMM._&END_YYYYMM in date_loop2 macro, because there is a ; after end statements
Turn on the MPRINT option to see what code your macro is generating. The second one is generating code like:
MPRINT(DATE_LOOP2): data master_rollrate;
MPRINT(DATE_LOOP2): set rollrate_201901_201901;
NOTE: Line generated by the macro variable "ED_YYYYMM".
1 rollrate_201901_201902
----------------------
180
MPRINT(DATE_LOOP2): rollrate_201901_201902;
NOTE: Line generated by the macro variable "ED_YYYYMM".
1 rollrate_201902_201902
----------------------
180
MPRINT(DATE_LOOP2): rollrate_201902_201902;
MPRINT(DATE_LOOP2): ;
MPRINT(DATE_LOOP2): run;
Remove the extra semi-colon in the middle of the nested %do loops since you are only generating PART of a statement in those loops.
You can simplify your macro code a great deal by using informats and formats to convert your YYYYMM digit strings to and from actual date values.
%macro date_loop2(start,end);
%local start_date end_date dif i j st_yyyymm ed_yyyymm;
%let start_date = %sysfunc(inputn(&start.01,yymmdd8));
%let end_date = %sysfunc(inputn(&end.01,yymmdd8));
%let dif=%sysfunc(intck(month,&start_date,&end_date));
data master_rollrate;
set
%do i=0 %to &dif;
%do j=&i %to &dif;
%let st_yyyymm = %sysfunc(intnx(month,&start_date,&i,b),yymmn6);
%let ed_yyyymm = %sysfunc(intnx(month,&start_date,&j,b),yymmn6);
rollrate_&st_yyyymm._&ed_yyyymm
%end;
%end;
;
run;
%mend date_loop2;

Iterate over a custom set in SAS

First of all, apologize for my poor english but that's because I'm not native. I'm a newbie in SAS programming too, and I need someone to help me with this problem struggling me.
I have one dataset A containing a numeric field YM representing year and month (e.g., 200902) that I'm using to filter the dataset. In particular, I want to get N filtered datasets using N differents values YM.
A_filtered_200901 = A.filter(YM == 200901)
A_filtered_200902 = A.filter(YM == 200902)
A_filtered_200903 = A.filter(YM == 200903)
...
My idea was to generate the sequence of YM used to filter and then give it as an argument to a %macro containing a PROC SQL. In code/pseucode:
data ym_dataset;
date = input(put(20090201, 8.), yymmdd8.);
do i = 1 to 3;
aux1 = intnx('MONTH', date, i);
aux2 = put(aux1, yymmddn8.);
list_of_ym_values = substr(aux2 , 1, 6);
output;
end;
run;
%macro my_macro(list_of_ym_values);
proc sql;
%do i = 1 %to dim(&list_of_ym_values)
select *
from A
where YM = &list_of_ym_values(i)
%end
quit;
%mend my_macro;
%my_macro(ym_dataset[list_of_ym_values])
I know that this is not the correct approach, but I hope that someone could shed me some light about doing it properly.
Thank you!!
you need loop through list of variables and this values can be created in a macro variable. But as #richard suggested in comments is not great idea to split datasets.
/* create macrovariable with all values*/
proc sql;
select list_of_ym_values into :List
separated by "|" from ym_dataset;
%put &list;
/* scan through each variable and create new dataset*/
%macro one;
%do i=1 %to %sysfunc(countw(&list),"|") ;
%let val= %scan(&list,&i,|);
proc sql;
create table want_&val as
select * from ym_dataset
where list_of_ym_values = "&val";
%end;
%mend;
%one;

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.

Efficiently concatenate many sas datasets

I have over 200k small datasets with the same variables (n<1000 and usually n<100) that I want to concatenate into a master dataset. I have tried using a macro that uses a data step to just iterate through all of the new datasets and concatenate with the master with "set master new:", but this is taking a really long time. Also, if I try to run at the same time, the call execute data step says that I am out of memory on a huge server box. For reference, all of the small datasets together are just over 5 Gigs. Any suggestions would be greatly appreciated. Here is what I have so far:
%macro catDat(name, nbr) ;
/*call in new dataset */
data new ;
set libin.&name ;
run ;
/* reorder names */
proc sql noprint;
create table new as
select var1, var2, var3
from new;
quit;
%if &nbr = 1 %then %do ;
data master;
set new;
run;
%end;
%if &nbr > 1 %then %do ;
data master;
set master new ;
run;
%end ;
%mend;
/* concatenate datasets */
data runthis ;
set datasetNames ;
call execute('%catdat('||datasetname||','||_n_||')');
run;
Resolved: see Bob's comments below.
Try using PROC APPEND instead of your "new" dataset; that will be much, much faster:
%macro DOIT;
proc sql noprint;
select count(*) into : num_recs
from datasetNames;
quit;
%do i=1 %to &num_recs;
data _null_;
i = &i;
set datasetNames point=i;
call symput('ds_name',datasetname);
stop;
run; /* UPDATE: added this line */
%if &i = 1 %then %do;
/* Initialize MASTER with variables in the order you wish */
data master(keep=var1 var2 var3);
retain var1 var2 var3;
set libin.&ds_name;
stop;
run;
%end;
proc append base=master data=libin.&ds_name(keep=var1 var2 var3);
run;
%end;
%mend DOIT;
PROC APPEND will add each dataset into your new "master" without rebuilding it each time as you are doing now.
This also avoids using CALL EXECUTE, removing that memory issue you were running into (caused by generating so much code into the execution stack).

Resources