Wednesday, December 18, 2013

SAS: Conditional %include

I have a SAS program which is generating SAS programs, and I want to conditionally use %include; that is, if the %include file exists, then %include it.  This code demonstrates how.

* SAS conditional %include ;
* See also http://www.sascommunity.org/wiki/Conditionally_Executing_Global_Statements ;

data _null_;
file "c:\temp\a.txt";
put "a = 1;";
run;

data _null_;
file "c:\temp\b.txt";
put "b = 1;";
run;

%let include_file=c:\temp\a.txt;
%let include_file=c:\temp\c.txt;
%let include_file=c:\temp\b.txt;

data Work.test;
%sysfunc(ifc(%sysfunc(fileexist("&include_file")),
    %include "&include_file";, %put File "&include_file" not found.;));
run;

proc print data=Work.test;
run;


Saturday, December 7, 2013

SAS: Looping thru a macro variable

Source code:

%macro doit(FOREACH=);
%local i j ;
%let j = 1 ;
%do %while(%scan(&FOREACH,&j) ne ) ;
   %let i = %scan(&FOREACH,&j) ; 

   %put &FOREACH &j &i;
   %let j = %eval(&j+1) ;
%end ;
%mend doit;

%doit();                 * none ;
%doit(FOREACH=A);        * one only, no parenthesis ;
%doit(FOREACH=(B));      * one only, with parenthesis ;
%doit(FOREACH=(C,D));    * two, comma separated, with parenthesis ;
%doit(FOREACH=(E F G));  * three, space separated, with parenthesis ;



Output from log:


125  %macro doit(FOREACH=);
126  %local i j ;
127  %let j = 1 ;
128  %do %while(%scan(&FOREACH,&j) ne ) ;
129     %let i = %scan(&FOREACH,&j) 

130     %put &FOREACH &j &i;
131     %let j = %eval(&j+1) ;
132  %end ;
133  %mend doit;
134
135  %doit();                 * none ;
136  %doit(FOREACH=A);        * one only, no parenthesis ;
A 1 A
137  %doit(FOREACH=(B));      * one only, with parenthesis ;
(B) 1 B
138  %doit(FOREACH=(C,D));    * two, comma separated, with parenthesis ;
(C,D) 1 C
(C,D) 2 D
139  %doit(FOREACH=(E F G));  * three, space separated, with parenthesis ;
(E F G) 1 E
(E F G) 2 F
(E F G) 3 G



Friday, December 6, 2013

SAS: Floating point hell

I grew up on COBOL and mainframe Assembler.  I loved packed decimal numbers because you always knew what you had.  I HATE floating point numbers.  I first stumbled upon this issue accidentally while learning C.  The same issue exists in Java.  And SAS. 

I don't have time to elaborate right now.  So I just want to capture the problem.  Here is the SAS log.  compare variables (e and f), (c and h), and (b and g).  In particular, note my use of +1.E-10. Warning ... +1.E-11 gives different results.

1    data _null_;
2    a = 400.4444;
3    i = 4;
4    b = int(a * 10 ** i) / (10 ** i);
5    c = (a = b);
6    d = a * 10 ** i;
7    e = int(a * 10 ** i);
8    f = int(a * 10 ** i + 1.E-10);
9    g = int(a * 10 ** i + 1.E-10) / (10 ** i);
10   h = (a = g);
11   put a= i= b= c= d= e= f= g= h=;
12   run;

a=400.4444 i=4 b=400.4443 c=0 d=4004444 e=4004443 f=4004444 g=400.4444 h=1


Tuesday, December 3, 2013

SAS: Temporary arrays

Spent way too much time last night trying to determine why a program was taking too long to run.  The time to run increased exponentially -- rather than linearly -- as more records were processed.  I determined that it was too much reliance on symget and symput.  When I used a temporary array instead, the program ran very quickly.

The following statement will create an array of numeric variables. Key points with temporary arrays:
  • You must specify the size with a numeric constant (or, as I did here, with a macro variable which evaluates to a numeric constant.)
  • Temporary arrays are automatically retained.
  • Temporary arrays are not written to the output data set.

Note how I was able to initialize all entries to zero.

      array used [&_NVARS] _temporary_ (&_NVARS * 0);


Here I check the value of an element in the array:

      if (used[&i] = 0) then do;

Here I set the value of an element in the array:

      used[save_i] = 1;