limit %do %while iterations - sas

I want to run a while/until loop in a macro function, and have its maximum number of iterations limited. I found how to do it in 'usual' sas :
data dataset;
do i=1 to 10 until(condition); /*10 iterations max */
/* stuff */
end;
run;
but if I try it in a macro function :
%macro mf;
data dataset;
%do i=1 %to 10 %until(nrow(X)>10); /*10 iterations max */
/* stuff */
%end;
run;
%mend;
%mf;
I get these errors :
ERROR: Improper use of macro reserved word until.
ERROR: A dummy macro will be compiled.
ERROR: Required operator not found in expression: 10 %until(nrow(X)>10)
ERROR: The %TO value of the %DO I loop is invalid.
ERROR: The macro MF will stop executing.
What is the proper way to limit loops iterations in macro-functions ?
Here is a data set in case you want to test ideas :
DATA dataset;
input X Y Z;
cards;
10 0 20
50 20 60
90 60 30
run;

The following is an example of what you can use:
%macro mf;
%let i=0;
%do %until(&onechar=e or &i=10);
%let i=%eval(&i+1);
%let onechar=%substr(abcdefghij,&i,1);
%end;
%put onechar=&onechar;
%put i=&i;
%mend mf;
%mf;
The macro loop stops if it finds "e" or i=10.

Related

macro seems stuck in infinite loop, don't know how to debug

I'm trying to define a macro function that returns unique list items from a space separated list. This macro itself uses other macros that I tested and seem to work fine by themselves (see examples below), it's all very simple code.
However for some reason the code runs indefinitely and I don't know how to debug it properly. I usually debug using %put statements but they don't print here as there's no error and I stop the code manually.
Here is the main macro, followed by my other convenience macros that I use, you can execute the whole batch to load the macros and then check out given examples.
*---------------------------------------------------------------;
* LIST_UNIQUE ;
* Return only unique items from list, ;
* in order of first appearance ;
*---------------------------------------------------------------;
/* EXAMPLE
%put %list_unique(); ** (nothing)
%put %list_unique(a); ** a
%put %list_unique(a a); ** doesn't work (should be a)
%put %list_unique(a b); ** doesn't work (should be a b)
*/
%macro list_unique(data);
%local out curr_item;
%do i=1 %to %list_length(&data);
%let curr_item = %extract(&data,&i);
%if not %list_in(&curr_item,&out) %then %let out = &out &curr_item;
%end;
&out
%mend;
*---------------------------------------------------------------;
* LIST_LENGTH ;
* Length of space separated list ;
*---------------------------------------------------------------;
/* EXAMPLES :
%put %list_length(); ** 0
%put %list_length(item1 item2 item3); ** 3
*/
%macro list_length(data);
%sysfunc(countw(&data,%str( )))
%mend;
*---------------------------------------------------------------;
* LIST_IN ;
* check if item is in list ;
*---------------------------------------------------------------;
/* EXAMPLE
%put %list_in(,a); ** 0
%put %list_in(a,); ** 0
%put %list_in(a,a); ** 1
%put %list_in(a,a a); ** 1
%put %list_in(b,a b c d); ** 1
%put %list_in(e,a b c d); ** 0
*/
%macro list_in
(item /* item to search in list */
,list /* space separated list to quote */
);
/* exception when list has null length */
%if not %length(&list) %then 0%return;
/* general case */
%do i=1 %to %list_length(&list);
%if %extract_pos(&list,&i) = &item %then 1%return;
%end;
0
%mend;
*-------------------------------------------------------------------------------;
* EXTRACT_POS ;
* Extracts subset of values from space separated list ;
*-------------------------------------------------------------------------------;
/* EXAMPLES
%put %extract_pos(,1); ** (nothing)
%put %extract_pos(a b c d,); ** (nothing)
%put %extract_pos(a b c d,1); ** a
%put %extract_pos(a b c d,2 1:3 1); ** b a b c a
*/
%macro extract_pos
(data
,ind
);
%local i j token output new_ind;
%do i=1 %to %sysfunc(countw(&ind,%str( )));
%let token = %scan(&ind,&i,%str( ));
%if %index(&token,:) %then %do; /* if token with ':' */
%do j=%scan(&token,1,:) %to %scan(&token,2,:);
%let output = &output %scan(&data,&j,%str( ));
%end;
%end;
%else %do; /* if simple token */
%let output = &output %scan(&data,&token,%str( ));
%end;
%end;
&output
%mend;
You cannot protect macros you call from modifying your macro variables, but if the macros are designed properly they will NOT. Unless you are INTENDING to modify any existing macro variable you need to define your macro variables as local. One or more of your macros were using the macro variable I without defining it as local. So if there already existed a macro variable named I then the macro modified the existing variable's value.
Also one of your macros was calling %extract() instead of %extract_pos().
I also simplified your %list_in() macro to just be a call to an existing SAS function, like your %list_length() macro.
%macro list_unique
/*---------------------------------------------------------------
Return only unique items from list
---------------------------------------------------------------*/
(data /* Space delimited list of items */
);
%local i curr_item out ;
%do i=1 %to %list_length(&data);
%let curr_item = %extract_pos(&data,&i);
%if not %list_in(&curr_item,&out) %then %let out=&out &curr_item;
%end;
&out
%mend list_unique;
%macro list_length(data);
%sysfunc(countw(&data,%str( )))
%mend list_length;
%macro list_in
/*---------------------------------------------------------------
Check if item is in list
---------------------------------------------------------------*/
(item /* item to find in list */
,list /* space separated list to search */
);
%sysevalf(%sysfunc(indexw(&list,&item,%str( ))),boolean)
%mend list_in;
%macro extract_pos
/*-------------------------------------------------------------------------------
Extracts subset of values from space separated list
-------------------------------------------------------------------------------*/
(data /* Space delimited list of values */
,ind /* Space delimited list of positions or position ranges */
);
%local i j token output;
%do i=1 %to %sysfunc(countw(&ind,%str( )));
%let token = %scan(&ind,&i,%str( ));
%do j=%scan(&token,1,:) %to %scan(&token,-1,:);
/* Token is single position or range in format start:end */
%let output = &output %scan(&data,&j,%str( ));
%end;
%end;
&output
%mend extract_pos;
Test
831 %put %list_unique(); %** (nothing);
832 %put %list_unique(a); %** a ;
a
833 %put %list_unique(a a); %** doesnot work (should be a);
a
834 %put %list_unique(a b); %** doesnot work (should be a b);
a b
Enable Macro Debugging by adding this line to the beginning of your code, which will resolve the macro code and variables:
Options macrogen symbolgen mlogic mprint mfile;
Run your code and check your log for details,
When done Disable Macro Debugging by replacing the options in step 1 with the options below:
Options nomacrogen NoSymbolgen nomlogic nomprint nomfile;
For more details you can check the SAS Debugging documentation
http://support.sas.com/documentation/cdl/en/mcrolref/61885/HTML/default/viewer.htm#a001066200.htm
Variable I is shared down in nameSpace. An easy fix is to use different looping variable in each macro.
Found some documentation on SAS logic of sharing. SAS Blogs

space separated list intersection

Note: code edited after remarks from #user667489 but issues remain.
I built a fairly simple macro to return the intersection of 2 space separated lists as a new space separated list but for some reason the definition of the macro returns errors.
The macro loops through both lists and keep an element if a match is found (very straightforward, no handling of duplicates or optimization).
I cannot make sense of the log, which shows a combination of following error messages:
ERROR: Macro keyword LET appears as text.
ERROR: Macro keyword MACRO appears as text.
%macro list_intersection
(list1= /* space separated list, or unique term */
,list2= /* space separated list, or unique term */
);
%local output;
%local i;
%local j;
%let i = 1;
%let j = 1;
%do %while (%length(%scan(&list1,&i)));
%do %while (%length(%scan(&list2,&j)));
%if (%scan(&list1,&i) = %scan(&list2,&j)) %then
%let output = &output %scan(&list1,&i);
%let j = %eval(&j+1);
%end;
%let i = %eval(&i+1);
%end;
&output
%mend;
Can you help me circling the issue ?
I'm also open to a more efficient/robust/simple way of achieving the same output.
Reproducible log
Using SAS 9.3, I put above code in a separate program for it not to be polluted, save project, close and reopen. Open program, click run button, and here is the complete log:
1 The SAS System 09:50 Monday, January 22, 2018
1 ;*';*";*/;quit;run;
2 OPTIONS PAGENO=MIN;
3 %LET _CLIENTTASKLABEL='Program3';
ERROR: Macro keyword LET appears as text.
4 %LET _CLIENTPROJECTPATH='F:\CI\Projects\Wealth Indicators\106 Explore DIM_PHYSICALPERSON\SAS\Rework_table_auto2.egp';
ERROR: Macro keyword LET appears as text.
5 %LET _CLIENTPROJECTNAME='Rework_table_auto2.egp';
ERROR: Macro keyword LET appears as text.
6 %LET _SASPROGRAMFILE=;
ERROR: Macro keyword LET appears as text.
7
8 ODS _ALL_ CLOSE;
9 OPTIONS DEV=ACTIVEX;
10 GOPTIONS XPIXELS=0 YPIXELS=0;
11 FILENAME EGSR TEMP;
12 ODS tagsets.sasreport13(ID=EGSR) FILE=EGSR STYLE=HtmlBlue
12 ! STYLESHEET=(URL="file:///C:/Program%20Files/SASHome/x86/SASEnterpriseGuide/5.1/Styles/HtmlBlue.css") NOGTITLE NOGFOOTNOTE
12 ! GPATH=&sasworklocation ENCODING=UTF8 options(rolap="on");
13
14 GOPTIONS ACCESSIBLE;
15 %macro list_intersection
ERROR: Macro keyword MACRO appears as text.
16 (list1= /* space separated list, or unique term */
17 ,list2= /* space separated list, or unique term */
18 );
19 %local output;
ERROR: Macro keyword LOCAL appears as text.
20 %local i;
ERROR: Macro keyword LOCAL appears as text.
21 %local j;
ERROR: Macro keyword LOCAL appears as text.
22 %let i = 1;
ERROR: Macro keyword LET appears as text.
23 %let j = 1;
ERROR: Macro keyword LET appears as text.
24 %do %while (%length(%scan(&list1,&i)));
ERROR: Macro keyword DO appears as text.
25 %do %while (%length(%scan(&list2,&j)));
ERROR: Macro keyword DO appears as text.
26 %if (%scan(&list1,&i) = %scan(&list2,&j)) %then
ERROR: Macro keyword IF appears as text.
27 %let output = &output %scan(&list1,&i);
28 %let j = %eval(&j+1);
ERROR: Macro keyword LET appears as text.
29 %end;
ERROR: Macro keyword END appears as text.
30 %let i = %eval(&i+1);
ERROR: Macro keyword LET appears as text.
31 %end;
ERROR: Macro keyword END appears as text.
32 &output
33 %mend;
ERROR: Macro keyword MEND appears as text.
34
35 GOPTIONS NOACCESSIBLE;
36 %LET _CLIENTTASKLABEL=;
ERROR: Macro keyword LET appears as text.
37 %LET _CLIENTPROJECTPATH=;
2 The SAS System 09:50 Monday, January 22, 2018
ERROR: Macro keyword LET appears as text.
38 %LET _CLIENTPROJECTNAME=;
ERROR: Macro keyword LET appears as text.
39 %LET _SASPROGRAMFILE=;
ERROR: Macro keyword LET appears as text.
40
41 ;*';*";*/;quit;run;
42 ODS _ALL_ CLOSE;
43
44
45 QUIT; RUN;
46
Initial code before edit:
%macro list_intersection
(list1= /* space separated list, or unique term */
,list2= /* space separated list, or unique term */
);
%local output =;
%local i = 1;
%local j = 1;
%do %while (%length(%scan(&list1,&i)));
%do %while (%length(%scan(&list2,&j)));
%if (%scan(&list1,&i) = %scan(&list2,&j) %then
%local output = &output %scan(&list1,&i);
%let j = %eval(&j+1);
%end;
%let i = %eval(&i+1);
%end;
&output
%mend;
A few things immediately stand out:
You cannot use %local to set a value for a macro variable. Instead of %local i=1; you must write two separate statements: %local i; %let i = 1;. %local initialises macro variables to an empty string.
You have unbalanced brackets in your %if statement.
Try moving %let j = %eval(&j+1); into the outer %do %while loop.
Also, you probably want to make sure that %scan only uses space as a delimiter - it defaults to space plus . < ( + & ! $ * ) ; ^ - / , % |
Here's a working version:
%macro list_intersection
(list1= /* space separated list, or unique term */
,list2= /* space separated list, or unique term */
);
%local output;
%local i;
%local j;
%let i = 1;
%do %while (%length(%scan(&list1,&i,%str( ))));
%let j = 1;
%do %while (%length(%scan(&list2,&j,%str( ))));
%if %scan(&list1,&i,%str( )) = %scan(&list2,&j,%str( )) %then
%let output = &output %scan(&list1,&i,%str( ));
%let j = %eval(&j+1);
%end;
%let i = %eval(&i+1);
%end;
&output
%mend;
%put %list_intersection(list1=1 2 3,list2=2 3 4);
You can use SAS functions to make that much easier. Use the COUNTW() function to find the upper bound for the %DO loop. Use the FINDW() function to test if word is found in the other list.
%macro list_intersection
(list1 /* space separated list of terms */
,list2 /* space separated list of terms */
);
%local output i next ;
%do i=1 %to %sysfunc(countw(&list1,%str( ))) ;
%let next=%scan(&list1,&i,%str( ));
%if %sysfunc(findw(&list2,&next,,s)) %then %let output=&output &next ;
%end;
&output
%mend;
You could include the i modifier in the findw() call to make it case insensitive. You could also test if the word is already in the output string to eliminate duplicates.
%macro list_intersection_nodups
(list1 /* space separated list of terms */
,list2 /* space separated list of terms */
);
%local output i next ;
%do i=1 %to %sysfunc(countw(&list1,%str( ))) ;
%let next=%scan(&list1,&i,%str( ));
%if %sysfunc(findw(&list2,&next,,si)) and not %sysfunc(findw(&output,&next,,si))
%then %let output=&output &next ;
%end;
&output
%mend;
Example:
274 %put %list_intersection_nodups(A B a C,a c d);
A C

Execution of data step despite of the errors

I am executing the following code to combine a large number of data sets . Here is code:
%macro combine;
data modelfit;
set
%do i = 30 %to 116 %by 3 ;
stat&i
%end;
;
%end;
run;
%mend;
%combine;
However, few of the data sets are not present. Hence, the data set is failing to execute. How can I prevent the data step from stopping to execute and combine the data sets that are there . Can anybody please help me with this
Check if the files exist before adding them in the loop:
%macro combine;
data modelfit;
set
%do i = 30 %to 116 %by 3 ;
%if %sysfunc(exist(stat&i)) %then %do ; stat&i %end ;
%end;
;
run;
%mend;
%combine;

How can I use %SCAN within a macro variable name?

I'm trying to write robust code to assign values to macro variables. I want the names of the macro variables to depend on values coming from the variable 'subgroup'. So subgroup could equal 1, 2, or 45 etc. and thus have macro variable names trta_1, trta_2, trt_45 etc.
Where I am having difficulty is calling the macro variable name. So instead of calling e.g. &trta_1 I want to call &trta_%SCAN(&subgroups, &k), which resolves to trta_1 on the first iteration. I've used a %SCAN function in the macro variable name, which is throwing up a warning 'WARNING: Apparent symbolic reference TRTA_ not resolved.'. However, the macro variables have been created with values assigned.
How can I resolve the warning? Is there a function I could run with the %SCAN function to get this to work?
data data1 ;
input subgroup trta trtb ;
datalines ;
1 30 58
2 120 450
3 670 3
run;
%LET subgroups = 1 2 3 ;
%PUT &subgroups;
%MACRO test;
%DO k=1 %TO 3;
DATA test_&k;
SET data1;
WHERE subgroup = %SCAN(&subgroups, &k);
CALL SYMPUTX("TRTA_%SCAN(&subgroups, &k)", trta, 'G');
CALL SYMPUTX("TRTB_%SCAN(&subgroups, &k)", trtb, 'G');
RUN;
%PUT "&TRTA_%SCAN(&subgroups, &k)" "&TRTB_%SCAN(&subgroups, &k)";
%END;
%MEND test;
%test;
Using the structure you've provided the following will achieve the result you're looking for.
data data1;
input subgroup trta trtb;
datalines;
1 30 58
2 120 450
3 670 3
;
run;
%LET SUBGROUPS = 1 2 3;
%PUT &SUBGROUPS;
%MACRO TEST;
%DO K=1 %TO 3;
%LET X = %SCAN(&SUBGROUPS, &K) ;
data test_&k;
set data1;
where subgroup = &X ;
call symputx(cats("TRTA_",&X), trta, 'g');
call symputx(cats("TRTB_",&X), trtb, 'g');
run;
%PUT "&&TRTA_&X" "&&TRTB_&X";
%END;
%MEND TEST;
%TEST;
However, I'm not sure this approach is particularly robust. If your list of subgroups changes you'd need to change the 'K' loop manually, you can determine the upper bound of the loop by dynamically counting the 'elements' in your subgroup list.
If you want to call the macro variables you've created later in your code, you could a similar method.
data data2;
input subgroup value;
datalines;
1 20
2 25
3 15
45 30
;
run ;
%MACRO TEST2;
%DO K=1 %TO 3;
%LET X = %SCAN(&SUBGROUPS, &K) ;
data data2 ;
set data2 ;
if subgroup = &X then percent = value/&&TRTB_&X ;
format percent percent9.2 ;
run ;
%END;
%MEND TEST2;
%TEST2 ;
Effectively, you're re-writing data2 on each iteration of the loop.
This should cover your requirements. You can load and unload an array of macro variable without a macro. I have included an alternate method of unloading a macro variable array with a macro for comparison.
Load values into macro variables including Subgroup number within macro variable name e.g. TRTA_45.
data data1;
input subgroup trta trtb;
call symput ('TRTA_'||compress (subgroup), trta);
call symput ('TRTB_'||compress (subgroup), trtb);
datalines;
1 30 58
2 120 450
3 670 3
45 999 111
;
run;
No need for macro to load or refer to macro variables.
%put TRTA_45: &TRTA_45.;
%let Subgroup_num = 45;
%put TRTB__&subgroup_num.: &&TRTB_&subgroup_num.;
If you need to loop through the macro variables then you can use Proc SQL to generate a list of subgroups.
proc sql noprint;
select subgroup
, count (*)
into :subgroups separated by ' '
, :No_Subgroups
from data1
;
quit;
%put Subgroups: &subgroups.;
%put No_Subgroups: &No_Subgroups.;
Use a macro to loop through the macro variable array and populate a table.
%macro subgroups;
data subgroup_data_macro;
%do i = 1 %to &no_subgroups.;
%PUT TRTA_%SCAN(&subgroups, &i ): %cmpres(&TRTA_%SCAN(&subgroups, &i ));
%PUT TRTB_%SCAN(&subgroups, &i ): %cmpres(&TRTB_%SCAN(&subgroups, &i ));
subgroup = %SCAN(&subgroups, &i );
TRTA = %cmpres(&TRTA_%SCAN(&subgroups, &i ));
TRTB = %cmpres(&TRTB_%SCAN(&subgroups, &i ));
output;
%end;
run;
%mend subgroups;
%subgroups;
Or use a data step (outside a macro) to loop through the macro variable array and populate a table.
data subgroup_data_sans_macro;
do i = 1 to &no_subgroups.;
subgroup = SCAN("&subgroups", i );
TRTA = input (symget (compress ('TRTA_'||subgroup)),20.);
TRTB = input (symget (compress ('TRTB_'||subgroup)),20.);
output;
end;
run;
Ensure both methods (within and without a macro) produce the same result.
proc compare
base = subgroup_data_sans_macro
compare = subgroup_data_macro
;
run;

SAS macros to get the output for a desired range

I'm new to SAS. Please help with my query.
I have my codes working fine for a single date where I have defined
%let yyyymmdd= 20020509;
data x;
set PQ.CP_&yyyymmdd
and then the conditions to get the desired output.
Now, I want my same codes to work for the period between startrange=-50 days before yyyymmdd and endrange=-10 days before yyyymmdd. How can I define this in the beginning to get the output for this period also as I want to use it further. Please suggest me a way to do this.
Ok - am answering this in the context of your previous question - to address just the current question, ignore the 'inner loop' below:
%macro loop(yyyymmdd=, startrange=, endrange=);
%local date x ds1 ds2 y;
%let date=%sysfunc(mdy(%substr(&yyyymmdd,5,2)
,%substr(&yyyymmdd,7,2)
,%substr(&yyyymmdd,1,4)));
/* this outer loop answers the curent question */
%do x=&startrange %to &endrange;
%let ds1=PQ.CP_%sysfunc(intnx(day,&date,&x),yymmddn8.);
%if %sysfunc(exist( &ds1 )) %then %do;
data x_%sysfunc(intnx(day,&date,&x),yymmddn8.);
set &ds1
/* this inner loop answers your previous question - remove if necessary */
%do y=-55 %to -10;
/* next date range is 55 to 10 days BEFORE the current iterative*/
/* value of (startrange-endrange) */
%let ds2=QA.TP_%sysfunc(intnx(day,&date,%eval(&y+&x)),yymmddn8.);
%if %sysfunc(exist( &ds2 )) %then %do;
&ds2
%end;
%end;
/* inner loop end */
;run;
%end;
%end;
/* outer loop end */
%mend;
%loop(yyyymmdd=20020509, startrange=-50, endrange=-10);