SAS® coding techniques used in Spectre (Clinical)

Introduction

This page will describe some sas techniques that not all programmers will be familiar with. They might be of interest because these techniques I will describe here have helped me, in part, to write Spectre. Most sas programmers are at the level where they can write a single sas program well. They know sas syntax well enough, the many functions they can use and some programmers are fairly competent with sas macro language. However, very few sas programmers can even attempt to write a "reporting system" such as Spectre. To write a "reporting system" needs a good working knowledge of the platform, such as Windows or Unix, because a reporting system will surely need scripts or batch (.bat) files written to link components of the reporting system together, but there is more to be learned on the sas side to make a success of writing a reporting system.

A "sasautos" library

The SAS Institute supply what is known as a "sasautos" library. It has more than one hundred macros in it. Most sas programmers accept this as "a given" in the sense that they know that that is the sasautos library and "that is that!". The know they can add their own macro libraries to the sasautos path but they accept that the only source of sasautos-type macros is the one supplied by the SAS Institute. Actually, that need not be the case. For clinical reporting purposes, the supplied sasautos library is not very good, though it has some very useful macros in it. You would be better off creating your own collection of macros and regarding that as your working base. You would still want to have the SI supplied sasautos library there as the last library declared to the sasautos path, otherwise you would find that what you regarded as the sas macro functions %lowcase(), %trim() and %left() no longer worked because these are actually macros in the sasautos library and not sas macro inbuilt functions.

Over twenty years I have built up a huge collection of sas utility macros. Every time I think of a useful function or procedure to have in a macro or have come across such macros at my place of work, I remember what this macro does and I rewrite it at a later date in a way that fits in with all the other macros I have got. It has got to the stage where I rarely think of any more utility macros that I can usefully add. I say "usefully", as I could think of some more utility macro to write but for all these cases, %sysfunc() calls would get me close enough to what I am trying to do. In 2007 I only added one new macro to my collection. "But what has this got to do with Spectre?", you might be wondering. "How did it help me write Spectre?" I'll tell you. The sas macros in Spectre nearly all came out of this collection of macros and those that didn't were rewritten or amended based on macros in that collection. There was very little new in the Spectre macros. In most cases, as I realized I needed some of these macros, I just copied them across from my old web site. That is how I developed Spectre so quickly. The macros were already there! Many of those macros call other macros in that same collection so it is not a collection of unrelated macros. The other macro calls are listed in the headers. They are meant to be a sasautos library. Developing Spectre would have been impossible to do in the timescales without those macros being there.

You could build up your own collection of macros but my advice is not to bother because I have already done this for you. You can use my macros! I have put these in the public domain so that you can use them too.

Function-style macros

A lot of the simpler macros in my collection are "function-style" macros.  In fact, I always try to write my macros as function-style macros where I think it is appropriate. Function-style macros, if they worked as functions in the strictest sense, would "return" a result. What function-style macros do is nearly that. They sort of "boil down" or "resolve" into the result so that the result is all that is left. In the macro code, this "result" will be a statement that lacks an ending semicolon. If you think of the SI supplied macro %lowcase() then you would use it like this:
 
%let newword=%lowcase(OLDWORD);

What happens in this case is that %lowcase() acts on "OLDWORD" and changes it to "oldword" in lower case, with no ending semicolon, so that "%lowcase(OLDWORD)" resolves to the string "oldword" leaving you with:
 
%let newword=oldword;

....so the value "oldword" gets assigned to the macro variable "newword".

The reason I use function-style macros where I can is because they are easier to call, they keep the code neater and effectively extend the power of the sas macro language by adding extra functions. You can not use function-style macros where your macro (or any submacro it calls either directly or indirectly) contains procedures or data steps. As for data steps, you can sometimes replace data steps with sas macro code that uses %sysfunc() calling SCL for dataset handling and doing so avoids the data step boundary and allows you to use it as a function-style macro. I have many macros of this type.

To illustrate the difference between function-style macros and others, consider the macro named %nobs(), that most sites possess, to give you the number of observations in a dataset. I have one named the same. This is the way most people implement it as an ordinary macro with a data step inside:
 
%let count=;
%nobs(dset,count);

My %nobs() is a function-style macro and I call it like this:
 
%let count=%nobs(dset);

The above might not look like much of an advantage but then I can use my macro in the convenient form:
 
%if %nobs(dset) %then %do;
..
..
%end;

As I said, my %nobs() macro does not contain a data step (and nor does its submacro) and this allows me to use it as a function-style macro. Take a look at my %nobs() macro. It has a line in it with no semicolon at the end. This is deliberate and is how it "returns" its value. You can can link to it here and you will see it calls another macro named %attrn(), again a function-style macro, which again has a line with no semicolon at the end, that uses %sysfunc() calls to SCL statements for handling datasets that you can link to here.

Having function-style macros allows you to use them in data steps, even if they relate to other datasets and other dataset variables and their properties, which gives you an advantage when writing complicated macros. For an example of a complicated reporting macro where I have used a few function-style macros to make coding it easier, look at %unistats() here.

Macro version number messages

It is a very good idea to write to your sas log what macros you called and what version numbers they are. Then if you have to recreate report output at a much later date you can hopefully track down copies of the macros of that same version number and then everything should work the same and give the same result. Version numbers can stay the same with macros until you get a non-backward compatible functional change or anything changes that opens up the possibility of the macro giving a different result. But how you write the message to the log is important. You only want to see this message once for each macro. If a macro is called multiple times then it might clog up you log if it wrote a message each time it was called. It is important to keep your logs as clean as possible so some clever trick is needed to only write the macro and its version number once to the log. The way that is done is to write to the log outside the macro definition. You write it as free macro code before you even declare the macro like this:
 
%put MACRO CALLED: crtitlesds v1.1;

%macro crtitlesds(flatfile,dsout);

Then when your macros is first invoked this message gets written to the log, your macro "compiled" and then when you call the macro again, this message is not part of the macro and so does not get written to the log. To see what I mean, take another look at the two macros you can link to in the previous section and see that their message that puts out the macro name and version is done before the macro is defined. Not many sas programmers are aware of this unusual feature. Even more unusual is that you can use the "%if" statement outside the macro definition in this way if you want to, so long as your macro is a sasautos macro. This is surprising as "%if" statements are not allowed in open code.

"Communicating" with sas programs

I have seen cases where somebody has tried to write a reporting system but because of their lack of knowledge in doing this, they usually opt to run all the programs in one sas call. This is a bad idea as there is the possibility that what is left over in one program can affect a following program. When they realize this then you might see some code added at the end of every program to clear out the work libraries and formats and reset the options to an agreed standard. It starts getting messy. If each program ran in a separate sas session then there would not be this problem but what sas programmers often realize is that they need to communicate some piece of information to all the sas programs and that this information is different for a full run of all programs rather than runs of individual programs as is typical during development. So they think that there is no alternative but to run all the programs in one sas session and set up these values at the start before the first program runs. There will be a block of code at the top of each program that is active for individual runs when the program is being developed but must be commented out for the collective run. Again, this is a bad idea, as there is no guarantee that the programmer will remember to do this. It is also very messy and to make it worse, after a program has been "validated", then the code must be changed slightly by commenting out this block before it can be run in "production". This introduces the chance of an error so by making this sort of edit you might have "invalidated" a "validated" program. This is a bad situation. This mess all comes from the mistaken belief that you can not "communicate" with sas programs independently of changing sas code or setting parameters. You can. One way to do this is to change sas code in the autoexec.sas member so that each program can pick it up and so each program can run independently in its own sas session. Although this is a much better way and avoids sas programs all running together, it is still changing sas code, if only in autoexec.sas. Sometimes this would be inconvenient due to the system setup. Luckily there is another and much better way to do this and this is by using "system environment variables". Most sas programmers have never heard of "system environment variables" because they have little knowledge of the platform they are working on, such as Windows and Unix, and what it can do. It is not the programmer's fault as SAS software claims to be platform independent and so programmers never bother to learn much about the platform it runs on. If you have a script or a batch (.bat) file to run your sas programs then you can set up an environment variable, give it a value and "export" it. This makes it available to all the sas programs run by the script if they know to look for it. In your sas code you can check to see what this environment variable is set to using sysget() in a data step or %sysget() as a sas macro function. You might want to look at the SAS documentation for these. But there is a small problem. Using %sysget() will cause a warning message to be written to your log if the environment variable does not exist, which will most likely be the case when you are developing your program and trying to read a system environment variable that will only be there for the final production run. It is best to avoid this warning message. To get round this I use a macro I wrote called %readpipe(), which reads the results of system commands, so that you can issue an "echo" command for the system environment variable. Doing it this way avoids the warning message. You can link to the %readpipe() macro here.

Spectre has a macro named %closerep() that closes a report. It looks at the system environment variable OUTDIR to determine which directory it should put the final output files in and one called DONELIST so it knows which "donelist" file to write an entry to (if OUTDIR is set) to say what output has been created. Take a look at the code and see how it is done. Note that for Windows (&sysscp EQ WIN) it is done differently than for Unix. Note the %readpipe() call. You can link to the %closerep macro here.

Conclusion

This document has described some of the sas techniques employed as they relate to the task of writing a clinical reporting system.
 
 

Use the "Back" button of your browser to return to the previous page.
 
 

contact the author















SAS and all other SAS Institute Inc. product or service names are registered trademarks or trademarks of SAS Institute Inc. in the USA and other countries. ® indicates USA registration.