SAS® MACROS - A GENTLE INTRODUCTION FOR THE FEARFUL ...

2 downloads 33 Views 572KB Size Report
Many SAS-programmers find the subject of macros ... tutorial will be in the form of a series of Simple ... Without knOwing anything about the application from.
Beginning Tutorials

SAS® MACROS - A GENTLE INTRODUCTION FOR THE FEARFUL Ralph W. Leighton, ITT Hartford Group

1.0 WHITHER MACROS AND OTHER STUFF

3.0 STEPPING IN CAUTIOUSLY - A NO-BRAINER MACRO EXAMPLE

Many SAS-programmers find the subject of macros somewhat intimidating. I certainly did. Some find it difficult to appreciate what the concept serves in relation to their particular application building needs. For others, the apparent need is clear, but how to gain sufficient mastery and understanding in order to use macros is a stumbling point.

Without knOwing anything about the application from whence it came, we all know that the code below:

TITLE "COMPETITOR INVESTMENT RESULTS"; PROC PRINT DATA=INVSTMDL. INCOME LABEL; BY COMPANY CALYEAR; PAGEBY COMPANY; ID LINE LINENAME VAR EARNPREM LOSSINCR UWEXPENS POLDIVID UWINCOME;

I am, quite frankly, a slow leamer of the more esoteric and complex aspects of programming languages. For me, gaining familiarity with macros was anything but quick. And even at this point, to be quite honest, I probably make far less sophisticated use ofthem than many.

RUN; produces a simple print of a file INCOME in the library INVSTMDL. If this SAS code is on a file or PDS member called IM0023RP, we can invoke the code in another SAS program using the command

But at the same time, I have seen from my own experience just how very useful - and indeed essential -- the concept is to solve problems. And it is from this standpoint of solving problems that I'd like to share with you some of my experience.

%INCLUDE INVSTPGM( IM0023RP ); where INVSTPGM points to the PDS, MACLIB or diectory containing the SAS Code. This %INCLUDE command brings in the code for execution as if we had coded it in the program window.

This will not be a linear overview of macro variables and macro syntax in the usual manner of formal tutorials, although I will try to be careful to explain the syntax as we proceed. Rather, after introducing the concept of the macro in sections 2.0-3.0, this tutorial will be in the form of a series of Simple problems whose solution will depend on one or another basic aspect of the macro language.

However, we can effect the same thing using a different ploy - by setting up a macro PRNTREPT to generate the PROC PRINT program step_ Consider the code below:

%MACRO PRNTREPT; «----« TITLE "COMPETITOR INVESTMENT RESULTS"; PROC PRINT DATA=INVSTMDL.INCOME LABEL; BY COMPANY CALYEAR; PAGEBY CALYEAR; ID LINE LINENAME VAR EARNPREM LOSSINCR UWEXPENS POLDIVID UWINCOME; RUN;

2.0 WHAT IS A MACRO? Informally, a macro is a sort of "Meta-program", in Base SAS Software, which, when executed, creates SAS program code. The key-word syntax particular to a macro looks a little like the DATA-step language, but is replete with percent Signs "%" on the statement key words and ampersands on the macro variables. The macro may also contain and usually does contain as per the examples following - embedded SAS Code.

%MEND PRNTREPT;

«----«

This is a simple macro - about as simple as they come. As wih any macro, it begins with the %MACRO key word followed by the name you are giving to the macro (a one to eight character valid SAS name). In the present instance, we've called our macro PRNTREPT. The macro ends with the %MEND statement, optionally referencing the name

One can also use macro variables outside the context of macros, and sections 3.0 and 4.0 give examples of the kind of situation where you might find this ploy useful.

168

Beginning Tutorials

RUN;

ofthe macro. Suppose I now submit this bit of code - i.e. this macro. What happens? Does it immediately execute a PROC PRINT? Actually not very much appears to happen at all; the statements seem to be simply swallowed up by SAS. But what the submission has caused is the setting up of a compiled macro by the name of PRNTREPT which I can now execute by referencing it by name preceded by a percent sign: %PRNTREPT

Then in a DATA-step producing the forecast (or making use of the forecast results) I can import the parameters from the dataset DATEFlLE: DATA INVSTMDL. FORECAST; RETAIN CURNTQTR NUMQTRS; IF N = 1 THEN SET INVSTMDL. DATEFILE;

...

... rest of program ...

«- no semi-colon

RUN;

Each time that command %PRNTREPT is executed, the SAS code in the macro will be submitted to the SAS supervisor for execution. That is to say, each %PRNTREPT will have the same effect as the submission of the earlier statement:

At this point CURNTQTR and NUMQTRS are available for use in the DATA-step. (Note the use of a RETAIN-statement: it ensures the two variables don't become missing values after the first iteration of the DATA-step.)

%INCLUDE INVSTPGM( IM0023RP );

This approach does get the two values into the DATA-step, but can not address gracefully a desire to have them print as part of the report TITLE's displaying the results of the forecast. That is to say suppose we want the second and third title lines of the PROC PRINT report in section 3.0 to display:

So what's to choose? This we'll answer in section 6.0. But first, let us introduce the concept of the macro variable. 4.0 THE MACRO VARIABLES - SETTING THEM USING %LET AND SIMPLE USAGE

FORECAST DATA AS OF 199503 FOR 15 FUTURE QUARTERS

A "global" macro variable may be thought of informally as a value or character string floating around outside the framework. of a SAS program step - that is to say, it is neither a variable on a SAS dataset, nor a variable in a SAS DATA-step. It exists for the duration of a SAS batch job or a SAS display manager session.

and have these report titles automatically change when we change the production date and the span of the forecast. We need TlTLE statements with variable references. Now, without using macro variables, this is actually doable In SAS as of version 6.07, if NUMQTRS and CURNTQTR happened to be BY-variables on the data file being processed by the report program. But NUMQTRS and CURNTQTR are not on the dataset INCOME. In any cast, the "Datefile" approach won't help us easily here. But the use of macro variables will.

Many systems actually can make rather good use of such a concept. For example, the different processing and reporting programs in a forecasting or planning system might need to know what is the current processing year (month or quarter) and how many future years the forecast is generated for.

As an altemative to the "Datefile" approach,

set up

Pursuing this kind of "global paramete(' problem, how might I set such a piece of information for the system and then how do I make use of it?

and submit the following two statements:

Consider a non-macro approach first. I could set up a single record file INVSTMDLDATEmE with the two values:

These are macro assignment statements - note the macro language key word %LET. They create two macro variables CURNTQTR and NUMQTRS with the values indicated. If I now set up my report TITLE statements as follows:

DATA

%LET CURNTQTR =199503; %LET NUMQTRS =15;

INVSTMDL.DATEFILE; CURNTQTR = 199503; NUMQTRS=15; OUTPUT; STOP;

TITLE2 "FORECAST DATA ASOF &CURNTQTR"; TITLE3 "FOR &NUMQTRS FUTURE QUARTERS";

169

Beginning Tutorials

In this version of the DATA-step, the RETAIN statement assigns the value from the macro variables. On first glance the coding of the RETAIN statement looks like a bit of double-speak. But there are actually two different sets of variables here:

The titles will print as desired. If the values of the variables are changed using the O/OLET statements, the report titles will display the revised values. Note that although CURNTQTR and NUMQTRS are preceded by ampersands "&" where they are used, they do not - repeat not - have a preceding ampersand in the O/OLET assignment statement that defines them. (Why they don't goes beyond the level of the discussion here.) What may not be apparent from this example is that a macro variable is simply the holder of a character string. Even though the values 199305 and 15 are numeric, as macro variable values they are still nothing more than character strings. When the SAS Supervisor encounters the macro variable in SAS code, the macro facility "resolves" it. That is to say, its current string value is immediately substituted prior to the interpretation, compilation, and execution of the statement in which the variable is embedded, in this case TITLE statements.



First there are the macro variables CURNTQTR and NUMQTRS that carry the parameters: they are preceded by ampersands "&".



The two variables CURNTQTR and NUMQTRS which do not have the ampersands are ordinary SAS variables in the DATA-step, just as they were in the "Datefile" solution earlier.

When the SAS supervisor sees the macro variables, it sends them to the macro processor to be resolved. This substitutes their values as character strings, and the code which is then compiled actually looks like: DATA INVSTMDL.FORECAST; RETAIN

You will note the use of double quotes in the TITLE statements making use of the macro variables. This is not optional: if you use single quotes, the value of the macro variable will not - repeat not - be substituted and you will get titles that look like:

CURNTQTR NUMQTRS

199305 15;

••• rest of program RUN;

Clearly, to save confusion (which I haven't by intent), I could have used names for the macro variables different from their DATA-step counterpartse.g. MCQTR and MNUMQTR. However, I think it is important to emphasise that the two types of variables are distinct and different animals, and the recycling of the SAS name, as I have done, is not an error or a conflict.

FORECAST DATA AS OF &CURNTQTR FOR &NUMQTRS FUTURE QUARTERS

not quite what one wants to see..•.. As noted earlier, a (global) macro variable like CURNTQTR, once set, holds its value throughout a batch job or an interactive display manager session (until and unless reset to a new value). Thus the macro variable makes a rather ideal way to store and then reference "global" parameters for an application. One can have the application initialisation routine establish the value, by for example, coding the O/OLET statements in an application startup program or in the AUTOEXEC. What follows below is how the same DATA-step example could make use of the macro variables CURNTQTR and NUMQTRS in lieu of file INVSTMDLDATEFILE:

5.0 TRANSFERRING VALUES FROM AND TO MACRO VARIABLES: "SYMGET" AND "CALL SYMPUT" Placing the macro variable in the code of a DATA-step On lieu of a value) is not the only way to bring in a parameter or value stored in a macro variable. One can also altematively make use of the SYMGET function which makes a direct transfer of the value into a SAS variables:

DATA INVSTMDL. FORECAST; RETAIN

CURNTQTR NUMQTRS

DATA INVSTMDL. FORECAST;

&CURNTQTR &NUMQTRS;

RETAIN

... rest of program ...

IF

CURNTQTR

N = 1 THEN DO; CURNTQTR = SYMGET (CURNTQTR) ; NUMQTRS SYMGET (NUMQTRS) ; END;

=

RUN;

170

NUMQTRS ;

Beginning Tutorials

native to the %LET's ;

... rest of program ...

DATA

RUN; This is a bit like the original "Datefile" solution - we are again accessing the parameters on the first iteration of the DATA-step. SYMGET Transfers the On this case numeric) value of the macro variables to their corresponding SAS variable namesakes. However, there is an important difference to note between the manner in which this solution path works and the direct use of macro variables in the RETAIN-statement. •

In the RETAIN solution, the resolution of the macro variable takes place prior to the compilation of the DATA step. .



When SYMGET is used, however, the transfer of value takes place during execution of the DATA step.

NULL ; SET INvSTMDL.DATEFILE; CALL SYMPUT ( • CURNTQTR' ,CURNTQTR) ; CALL SYMPUT ( 'NUMQTRS " NUMQTRS); STOP;

RUN; The first parameter of the CALL SYMPUT routine is the target macro variable. Note that it is quoted in Single quotes. (Again. the reason for the quoting goes beyond the level of discussion here.) The second parameter is the SAS variable supplying the value. The value being passed is assumed to be a character string. However. in this example, the supplying variables on the DATEFILE dataset are not character but are integer numeric. Thus as part of the paSSing of the values, the integer values are treated as character strings - i.e. a conversion takes place. When a passed value is numeric and non-integer in nature, one should format the value to achieve the intended result. Consider the following problem: I have computed the average investment yield for a bunch of insurance companies on a data base of insurance competitors by taking a weighted average of the yields of the individual companies:

This distinction becomes important if one is in the habit of pre-compiling one's DATA-steps and RUN'ning the load modules; the SYMGET approach will permit changes in the parameters - i.e. different values for CURNTQTR and NUMQTRS - to be accepted by the DATA-step as it runs. The RETAIN solution using macro variables, however, is left with whatever values the variables had at the point the DATA-step was pre-compiled.

PROC SUMMAAY DATA=INVSTMDL. COMPET; VAR INVYIELD; WGT INSVSTMTS; OUTPUT OUT=TEMPFILE SUM=;

In any case, SYMGET is a way to move values from a macro variable to a SAS variable in a DATA-step. Can we go the other way?

RUN; TEMPFILE will contain a single record with a single variable INVYIELD containing the average yield. I now want to print that average yield as part of the title on a report displaying the Individual company results:

Suppose we want to keep the application'S global parameter variable values for CURNTQTR and NUMQTRS on a file like DATEFILE rather than hard-coding them into the initialisation using %LET statements. How do we get the values out of such a file and into macro variables so that we can use the values in the manner demonstrated above, in both DATA-steps and TITLE-statements?

COMPETITOR INVESTMENT RESULTS AVERAGE INDUSTRY YIELD OF _ _

the average yield being displayed in the second title line as a percent.. Similar to what we did with DATEFILE we create a macro variable AVEYIELD:

The answer Is the CALL SYMPUT routine which has a bit of a peculiar syntax. In the code that . follows, we'll assume that the parameter values are on INVSTMDLDATEFILE, as they were in the previous section's initial example. Thus, as in section 3.0, DATEFILE has a single record and two variables (called, as before, CURNTQTR and NUMQTRS) carrying the values. The following DATA-step code will create the two macro variables CURNTQTR and NUMQTRS and serve as an alter-

DATA NULL; SET-TEMPFILE; CALL SYMPUT ( 'AVEYIELD' , LEFT (PUT (INVYIELD, PERCENT5.2))) STOP; RUN;

The PUT function creates a character string with the correct format, and the LEFT function eliminates

171

Beginning Tutorials

PUT

@2 LINE 5.1 @8 ITEM $ @30 (QTRO-QTR24) ( 13.1 ); RETURN;

leading blanks from the representation in AVEYIELD. Now we use the macro variable in the TITLE as in section 3.0:

RUN;

TITLE "COMPETITOR INVESTMENT RESULTS"; TITLE2 "AVE INDUSTRY YIELD &AVEYIELD"; PROC PRINT DATA=INVSTMDL.COMPET LABEL ID COMPANY; VAR INVYIELD; RUN;

The "quoting" of ITEM assures that the recipient will be able to use the "/File-Import-Numbers" command sequence in LOTUS® to import the data into his worksheet The FILENAME reference TOLOTUS is to a P.D.S. or Directory where the output files are to be placed.

and assuming the average yield was 5.61% you should see displayed at the top of the pages

To write out the other seven files requires replicating the code and changing three things:

COMPETITOR INVESTMENT RESULTS AVE INDUSTRY YIELD 5.61%

o The SAS Dataset o The output File Name (same as the Dataset) o The output Format for QTRO-QTR24

6.0 PARAMETER DRIVEN RECYCLABLE CODE - MACROS WITH PARAMETERS

The last changes because UWRESULT and PERCENTS have data that are percentages to three decimal places and therefore need to be printed with five decimal places precision. DURATION contains average paid dates in years and needs to be output with three decimal places precision.

The simple macro introduced in section 2.0 - a canned PROC PRINT - did not offer much of an advantage over simply %INCLUD'ing the code for the PROC PRINT. Where simple macro's become truly useful is when you want to set up a block of code that is parameter driven - i.e. where you can execute it using different driving values of one sort or another.

Rather than clone the DATA-step code seven times to produce the other seven files, let's make a macro out of it and simply execute the macro eight times.

Suppose someone wants to use my transposed insurance company model data for their spreadsheet application. To meet his request I want to create importable "flat" files (Non-SAS files) from eight SAS datasets listed below:

BALSHEET INCMSTMT DURATION INVSTDET

%MACRO MAKEFILE(~,FORMAT=); DATA NULL; SET INVSTMDL.&FILE ; FILE TOLOTUS(&FILE) LRECL=450; ITEM = ,,,. I I TRIM (ITEM) II .".; PUT @2 LINE 5.1 @8 ITEM $ @30 (QTRO-QTR24) ( &FORMAT ); RETURN; RUN;

TURNOVER CASH FLOW PERCENTS UWRESULT

All are in library INVSTMDL and have the same variables on them:

LINE ITEM QTRO-QTR24

%MEND MAKEFILE; The difference between this macro and the example in section 3.0 is that this one has parameters. There are two of them:

Line of Business Code Item Description Amounts by calendar Quarter

o o

The "Items" are such things as loss and expense reserves, various assets, income components, yields, cash-flow contributions. and so on. LINE is numeric and ITEM is a character variable. The follOWing code will convert the BALSHEET dataset:

FILE, the Dataset name; and FORMAT, the output format for QTRO, QTR1, .. , QTR24.

FORMAT and FILE are local macro variables local because they are known only inside the macro MAKEFILE. Note that they appear with ampersands "&" where they are used inside the macro and without ampersands in the %MACRO statement. Note the equal signs "=" following each

DATA NULL; SET-INVSTMDL.BALSHEET; FILE TOLOTUS(BALSHEET) LRECL=450; ITEM = .... II TRIM (ITEM) II .".;

172

Beginning Tutorials

RUN; %MEND MAKEFILE;

variable name in the %MACRO statement. FILE and FORMAT have been set up as Key Word Parameters, which means we will reference their names when we execute the macro.

Note the absence of equal-signs "=" in the %MACRO statement. After submission, this version of MAKEFILE would be executed as per.

As was the case in section 2.0, submitting this code 'compiles" the macro and makes it available for use. And use it we do, for each of the eight datasets:

%MAKEFILE(BALSHEET,13.1);

%MAKEFILE(FILE=BALSHEET,FORMAT=13.1) %MAKEFILE(FILE=INCMSTMT,FORMAT=13.1) %MAKEFILE (FILE=CASHFLOW, FORMAT=13. 1) %MAKEFILE(FILE=PERCENTS,FORMAT=13.5) %MAKEFILE(FILE=TURNOVER,FORMAT=13.1) %MAKEFILE(FILE=DURATION,FORMAT=13.3) %MAKEFILE(FILE=INVSTDET,FORMAT=13.1) %MAKEFILE(FILE=UWRESULT,FORMAT=13.5)

which saves writing, but is a bit more cryptic. Personally, I like the key word parameters better: they are forgiving of order; they allow default values; and they are more self-documenting. 7.0 RUBBER PROGRAMS - MAKING USE OF THE MACRO LANGUAGE

InCidentally, it does not make any difference in which order you enter key-word parameters when you execute the macro MAKEmE:

The slang "rubber program" simply means the exact shape of the program, in terms of the SAS code generated, will depend on the values of parameters entered. To this end, consider again the PROC PRINT used as the example in section 3.0:

%MAKEFILE(FORMAT=13.5,FILE=UWRESULT) will work just as well as

TITLE "COMPETITOR INVESTMENT RESULTS"; PROC PRINT DATA=INVSTMDL.INCOME LABEL; BY COMPANY CALYEAR; PAGEBY CALYEAR; ID LINE LINENAME VAR EARNPREM LOSSINCR UWEXPENS POLDIVID UWINCOME; RUN;

%MAKEFILE(FILE=UWRESULT,FORMAT=13.5) You can give default values to macro key-word parameters. The majority of the eight files above required the numeric 13.1 format, and the macro MAKEFILE could have been set up in this manner:

%MACRO MAKEFILE(FILE=,FORMAT=13.1); DATA NULL; •.--:-etc.,

It tums out that file INVSTMDLINCOME is rather large: there are some 300 insurance companies represented on the file with data from 1976 through the present date. Does one want to receive all that paper? We might, but probably not as a matter of course. Clearly we are headed in the direction of a macro to at least optionally subset the data.

as before ...

RUN; %MEND MAKEFILE; Then submitting

%MAKEFILE(FILE=BALSHEET)

We could set this up in the manner of the macro in section 6.0, by setting up a WHERE statement to ask for a single Company and Calendar Year at a time:

is equivalent to:

%MAKEFILE(FILE=BALSHEET,FORMAT=13.1) Default values are highly useful in situations where a parameter normally assumes a certain value and only assumes other values on an exception basiS.

MACRO PRNTREPT (COMPID=,CALYEAR=); PRoe PRINT DATA=INVSTMDL.INCOME LABEL; WHERE COMPANY=&COMPID AND CALYEAR=&CALYEAR ; BY COMPANY CALYEAR; PAGEBY CALYEAR; ID LINE LINENAME VAR EARNPREM LOSSINCR UWEXPENS POLDIVID UWINCOME; RUN; %MEND PRNTREPT;

As noted above, the above macro used "Key word" parameters. One could altematively have coded them as Positional Parameters, in which case the macro would have been coded as:

%MACRO MAKEFILE(FILE,FORMAT); DATA

NULL; ..--:-etc., as before ...

173

Beginning Tutorials

This however, is not really that helpful. I really want to look at more than one company at a time and most certainly a range of calendar years. Thus a more useful WHERE statement would be of the form:



If I don't enter a value for FYR I want to start with the first available year automatically without hard-coding this year as a default value for FYR.



Similarly, if I don't code a value LYR, I want output up through the last year, again without coding this last available year as a default value forLYR.



Finally, I want an error message printed to the SAS-Log, if FYR is greater than L YR.

WHERE COMPANY IN ( '" list ••• AND CALYEAR GE first-year AND CALYEAR LE last year ; Let's try again: consider the following macro implementation:

This innocent complication leads to seven possible forms for the WHERE statement and one further situation in which there is no sub-setting WHERE statement at all:

%MACRO PRNTREPT(COMPLIST=,~,LYR=); TITLE "COMPETITOR INVESTMENT RESULTS"; PROC PRINT DATA=INVSTMDL.INCOME LABEL; WHERE COMPANY IN ( &COMPLIST ) AND CALYEAR GE &FYR AND CALYEAR LE &LYR BY COMPANY CALYEAR; PAGEBY CALYEAR; ID LINE LINENAME VAR EARNPREM LOSSINCR UWEXPENS POLDIVID UWINCOME; RUN; %MEND PRNTREPT;

1* All Three parameters coded */ WHERE COMPANY IN ( ••. list ••• AND CALYEAR GE first year AND CALYEAR LE last-year ;

1* Company List omitted */

WHERE CALYEAR GE first-year AND CALYEAR LE last-year ; /* First Year Omitted */ WHERE COMPANY IN ( ••• list .•. AND CALYEAR LE last year ; /* Last Year Omitted *1 WHERE COMPANY IN ( '" list •.• AND CALYEAR GE First year ; 1* Only company List coded */ WHERE COMPANY IN ( .•. list 1* Only First Year Coded */ WHERE CALYEAR GE first-year 1* Only Last Year Coded */ WHERE CALYEAR LE last-year ; 1* No sub-setting */ 1* ... no WHERE statement */ ;

Let's submit this version for compilation. Now if I want a report for companies 32,65, 71 and 132 for years 1983 to 1992 , I now submit the statement:

%PRNTREPT (COMPLIST=32 65 71 132, FYR=1983, LYR=1992); . Note the absence of commas in the list of Companies: a comma would signal the end of the string to be assigned to COMPLIST. That is to say, commas separate the parameters. Coding commas in the company list would result in an error and the macro would not execute. In any case, the code results in the following being executed by the SAS supervisor:

All of this can be accommodated through the use of the %Do-group and the %IF•••%THEN•••%ELSE statements in the macro language syntax. We now enter the macro language with a vengeance.

TITLE "COMPETITOR INVESTMENT RESULTS"; PROC PRINT DATA=INVSTMDL.INCOME LABEL; WHERE COMPANY IN ( 32 65 71 132 ) AND CALYEAR GE 1983 AND CALYEAR LE 1992; BY COMPANY CALYEAR; PAGEBY CALYEAR; ID LINE LINENAME VAR EARNPREM LOSSINCR UWEXPENS POLDIVID UWINCOME; RUN;

The macro at the top of the next page will conditionally produce the error message and all eight altematives of the sub-setting WHERE statement. What have we wrought here? Let's walk through the macro code to see how and why it works. Following the %MACRO statement, there is a %PUT statement which writes the values of the three variables in the SAS log. The next statement,

But suppose now I get a little fussy: •

;

I want all companies if I omit the company list.

174

Beginning Tutorials

the macro language - one simply abuts str1ng constants and macro variables. Had we (for other purposes) wished to concatenate FYR to the constant "AD" • we would write:

%MACRO PRNTREPT(COMPLIST=,FYR=,LYR=); %PUT &COMPLIST= &FYR= &LYR= ; %IF &FYR GT &LYR %THEN %PUT "** ERROR ** FYR > LYR"; TITLE "COMPETITOR INVESTMENT RESULTS"; PROC PRINT DATA=INVSTMDL.INCOME LABEL; %LET TESTCHAR = &COMPLIST&FYR&LYR; %IF &TESTCHAR NE %THEN %DO; WHERE %LET ANDCHAR=; %IF &COMPLIST NE %THEN %DO; COMPANY IN ( &COMPLIST %LET ANDCHAR=AND; %END; %IF &FYR NE %THEN %DO; &ANDCHAR CALYEAR GT &FYR %LET ANDCHAR=AND; %END; %IF &LYR NE %THEN %DO; &ANDCHAR CALYEAR LE &LYR %END;

%LET YEARCHAR

= &FYR.AD;

Note the use of the period indicating the end of the macro variable. If FYR contains the value 1983. YEARCHAR would contain the string "1983AD" with no embedded period. In any case, TESTCBAR has to have something in it for a WHERE to be coded. And that is what the next macro statement assures. We set up the WHERE statement in a conditionally executed %DO-group:

%IF &TESTCHAR NE WHERE

%THEN %DO;

This says: If TESTCHAR is not equal to the null string then we will need a WHERE statement.. And so the first step in this conditionally executed %DO group places the key word WHERE to be written out asSAS code.

%END; BY COMPANY CALYEARI PAGEBY CALYEARI ID LINE LINENAME VAR EARNPREM LOSSINCR UWEXPENS POLDIVID UWINCOME; RUN;

We next initialise a variable ANDCHAR which will contain either nothing or the operator AND. ANDCHAR is initialised to nothing (null string) in the statement:

%LET ANDCHAR=;

%MEND PRNTREPT I

Next we test whether or not to subset on the basis of a company list. The code:

**************************************

%IF &COMPLIST NE %THEN %00; COMPANY IN ( &COMPLIST %LET ANDCHAR=AND; %END;

%IF &FYR GT &LYR %THEN %PUT "** ERROR ** FYR > LYR"; is similar in syntax to the IF ... THEN •.. in the DATAstep language. Note the ampersands on the macro variables (parameters) FYR and L YR. If FYR is greater than LYR the message

says that if the company list is not null then we add the following to the WHERE statement

COMPANY IN ( &COMPLIST )

- ERROR - FYR > LYR The %LET following puts the value AND in ANDCHAR. The %ENO ends this %OO-group. Next the code:

will appear in the SAS log. We form macro variable TESTCHAR to see if we have any parameters entered at all. The statement

%IF &FYR NE %THEN %00; &ANDCHAR CALYEAR GT &FYR %LET ANDCHAR=ANDI %END;

%LET TESTCHAR = &COMPLIST&FYR&LYR; creates TESTCBAR, setting it to be the concatenation of the strings in the three macro variables COMPLIST, FYR and LYR. Unlike the DATA-step language. there is no operator for concatenation in

adds the test for CALYEAR being greater than FYR if FYR is coded. But should this code fragment be

AND CALYEAR GT &FYR

175

Beginning Tutorials

or should it be

%MACRO CALYRPRT(COMPLIST=,CFY=,CLY=);

CALYEAR GT &FYR ?

%DO JYEAR = &CFY %TO &CLY; %PRNTREPT(COMPLIST=&COMPLIST, FYR=&JYEAR, LYR=&JYEAR ) %END; %MEND CALYRPRT;

Macro variable ANDCHAR takes care of that dilemma. If there was a sub-setting on the basis of company, we need the AND: in this instance ANDCHAR will have been reset to AND as a value. OthelWise we don't, and this latter case ANDCHAR would retain its initialisation to the m.ill string.

************************************

The %LET statement makes sure that if ANDCHAR did not previously contain AND, then it does now

counter the macro variable JYEAR. Note the absence of an ampersand "&" where It is defined as a counter in the %DO statement. On each pass, the previously defined macro PRNTREPT is executed using the company list with FYR and LYR set both equal to the year in JYEAR. Thus:

The next block of code is a third conditionally executed %DO-group, this time adding a check in the WHERE statement for CALYEAR less than or equal to L YR. Again note the role of macro variable ANDCHAR.

%CALYRPRT(COMPLIST=12 32 45 302, CFY=1987,CLY=1990)

%IF &LYR NE %THEN %DO; &ANDCHAR CALYEAR LE &LYR %END;

spawns the following:

The next thing to do is to terminate the WHERE statement with a semi-colon and end the outer %DO-group that set up the WHERE:

%PRNTREPT(COMPLIST=12 32 45 302, FYR=1987,LAYR=1987) %PRNTREPT(COMPLIST=12 32 45 302, FYR=1988,LAYR=1988) PRNTREPT(COMPLIST=12 32 45 302, FYR=1989,LAYR=1989) %PRNTREPT(COMPLIST=12 32 45 302, FYR=1990,LAYR=1990)

%END; The remainder of the code in the macro is the fIXed part ofthe SAS-code in the PROC PRINT;

The iterative %00 is particularly useful in certain situations involving indexed lists of SAS variables, like the QTRO-QTRl4 on the INCOME file. For some types of operations, overlaying these variables with an array, as per:

To execute the report for companies 23, 47 and 201 for years after 1989, we would submit:

%PRNTREPT(COMPLIST=23 47 201, FYR=1990)

ARRAY

QTRDATA(*) QTRO-QTR24;

permits one to use DO-loop processing to compress processing code - see, for example, the paper "Working with Arrays" published in the 1992 N.E.S.U.G. proceedings as well as in the SUGI1994 and 1995 proceedings. Arrays won't help, however, with the problem of reducing the code in non-executable statements such as LABEL or in options like RENAME in a SET statement:

8.0 ANOTHER USEFUL PIECE OF MACRO LANGUAGE SYNTAX - %00 LOOPS Suppose I wanted reports by year from 1983 to 1993 for companies in the list above, but wanted the reports in clusters by year. We could re-sort the file INCOME by CALYEAR and clone the print macro PRNTREPT to use a different BY sequence. Rather than do this, let's code another macro CALYRPRT that invokes PRINTREPT and prints reports by year:

SET INVSTMDL.BALSHEET (RENAME=(QTRO=BSITMO QTR1=BSITMl

In CALYRPRT, the parameter COMPLIST is as before. Parameter CFY holds the first year to be reported on and CLY the last year.

.•. etc....

QTR24=BSITM24» ; But the following macro (which appeared as an example in the SAS Guide to Macro Processing, version 6.0 first edition) will create the RENAME

The %OO-Ioop in CALYRPRT is similar to the DATA-step language iteratlveDO-loop, and has as a

176

Beginning Tutorials

SAS LOG ECHO -- OPTIONS MPRINT AND SYMBOLGEN 89 FILENAME TOLOTUS "0: \NESUG\NESG1995\DATA"; 90 %MAKEFILE ( FILEID=BALSHEET,FORMAT=13.1 ); /* BALANCE SHEET .••• */ 91 MPRINT(MAKEFILE): DATA NULL ; SYMBOLGEN: Macro variable FILEID resolves to BALSHEET MPRINT (MAKEFILE) : FILE TOLOTUS(BALSHEET) LRECL=450; SYMBOLGEN: Macro variable FILEID resolves to BALSHEET SET INVSTMDL.BALSHEET ; MPRINT(MAKEFILE): ITEM = ." I I I TRIM (ITEM) I I • .. I ; MPRINT(MAKEFILE): SYMBOLGEN: Macro variable FORMAT resolves to 13.1 PUT @2 LINE 5.1 @8 ITEM $CH.A.R40. @58 (QTR_0-QTR_24) MPRINT(MAKEFILE): ( 13.1 ) ; RETURN; MPRINT(MAKEFILE): RUN; MPRINT (MAKEFILE) : NOTE: The file library TOLOTUS is: DlRECTORY=D:\NESUG\NESG1995\DATA NOTE: The file TOLOTUS(BALSHEET) is: DIRECTORY=D:\NESUG\NESG1995\DATA, MEMBERNAME=D:\NESUG\NESG1995\DATA\BALSHEET.DAT,RECFM=V,LRECL=300 NOTE: A total of 2720 records were written to the file library TOLOTUS. formatting is a bit messy if the resulting SAS statement is lengthy. Also, there are no cosmetiCS, like indentation for DO-groups or loops.

sequence:

%M.A.CRO RENAME (OLD=, NEW=, FIRST=., LAST=) ; %DO JNUM = &FIRST %TO &LAST; &OLD&JNUM = &NEW&JNUM %END; %MEND RENAME;

Above is the SAS-Iog output MAKEFILE generated with MPRINT and SYMBOLGEN enabled.

Assuming we have compiled RENAME, the SET statement becomes:

10.0

Thus ends our brief introduction to the SAS Macro facility. Some of the features we have not covered - %GOTO, macro labels, and macro comments I'Ve not found all that useful. Others - such as %W1NDOW and %DISPLAY - I've not had much recourse to my basic uses of macros. I will, however, call your attention to one heavily used feature in more advanced applications: the various functions that support quoting and un-quoting of strings.

SET INVSTMDL.INCMSTMT (REN~( %REN1IME(OLD=QTR,NEW=BSITEM, FIRST=0,LAST=24) I); and this will generate the RENAME list. 9.0 MONITORING THE RESOLUTION OF MACRO VARIABLES AND MACRO CODE Especially when getting into this stuff the first time, It is helpful to see what the heck SAS is executing when It processes the execUtion of a macro like those in the previous two sections. To this end, there are two useful options I usually have enabled when I use SAS: •



END OF THE TRAIL •••

Have fun!! NOTES AND REFERENCES SAS Guide to Macro ProceSSing, version 6. 1st Ed 'Working with Arrays" by Ralph Leighton, NESUG 1992 Proceedings

SYMBOLGEN - this documents on the SAS-Iog the substitution in SAS code of values for the ampersand-ed "&" macro variables.

SAS is a registered trademark of the SAS Institute, Inc., Cary NC. LOTUS is a registered trademark of the lotus Development Corporation, Inc.

MPRINT - this prints out each SAS-statement on a separate line in the SAS-Iog. Statement

177