Today, I will investigate the macro array in SAS. The SAS macro array is not an official term. There is no documentation for a macro array. Rather, it is a term that has arised among SAS macro programmers. A macro array is a list of macro variables, that share the same prefxix and a numeric, iterating suffix. In this post, I will explore the concept of macro arrays. I will do so by introducing the two macros %Array and %Do_Over. While SAS Institute did not write them, they are quite widespread and well known among macro programmers. The macros are beautifully written and well documented by Ted Clay and David Katz. Download and compile the macros here before you continue.
In the examples to come, I will not go into detail about the underlying macro code. Rather, I will demonstrate how they work by example. If you want a more detailed explanation of the underlying work, read the article Tight Looping With Macro Arrays by Ted Clay.
The %Array Macro
A macro array is a list of macro variables, that share a common prefix and has a numeric, increasing suffix. For example, the macro variables &a1, &a2 and &a3 make up a macro array. The length of the macro variable is stored in an additional macro variable with the suffix n. The %Array Macro is designed to create macro arrays. Consider the example below. Here, I create a macro array named a. Here, the length of the array is determined the the number of values in the Values argument. In this case 3. This means that SAS creates the macro variables &a1, &a2 and &a3 with the values one, two and three. Furthermore, the length of the array is stored in &an.
%array(a, values=one two three) %put &a1; %put &a2; %put &a3; %put &an;
The Values argument accepts numerical lists as well. Consider the example below. This creates the macro variables &num1, &num2, &num3 and &numn with the values 1, 2, 3 and 3 respectively.
%array(num, values=1-3) %put &num1; %put &num2; %put &num3; %put &numn;
More interestingly, the %Array macro allows us to create arrays from a SAS data set. Consider the example below. I use the data= argument to specify the data set from which I want to retrieve values. The data=argument allows me to use data set options. Next, I use the var= argument to point to a specific variable. This code creates the macro array names with ten macro variables with male names plus an additional macro variable with the length of the array. In this case 10.
%array(names, data=sashelp.class(where=(sex="M")), var=name) %put &names1; %put &namesn;
Before you continue, I recommend that you spend some time playing around with the %Array macro. It is crucial that you are comfortable with the macro before you move on.
The %Do_Over Macro
In the section above, we learn how to create SAS macro arrays. Now, let us explore how to use them to create flexible and dynamic SAS code with the %Do_Over macro. The %Do_Over macro is designed to mimic the Do Over syntax of the Implicit Data Step Array. The %Do_Over macro allows us to loop over each element of the array and create dynamic code based on each element. With minimal coding effort. Consider the simple example below. First, I create the same array as in the top of previous sections. Next, I use %Do_Over macro in a %Put Statement and specify the array a in the macro.
%array(a, values=one two three) %put %do_over(a);
The example above is the simplest possible use of %Do_Over. In it, we list only the macro array. The key to build more advanced code with %Do_Over is the Phrase= argument. In the phrase argument, we list the code that we want to generate for each element in the array. We use a question mark ? to specify where to insert the value of the array element. Consider the example below. The first %Do_Over call does exactly the same as the example above because all I specify in the phrase argument is ?. Next, I specify quotation marks around the question mark. Run it and verify that each array element is now quoted in the log. Finally, consider the question mark as an escape character. We can choose another escape character with the escape= argument.
%array(arr, values=one two three) %put %do_over(arr, phrase=?); %put %do_over(arr, phrase="?");
Finally, we can use the between= argument to specify one or more characters to appear between the generated code of the %Do_Over. Consider the examples below. Here, I want to place a comma between each piece of code generated by %Do_Over. In this case, I must use a quoting function to hide the comma from the macro processor. Otherwise, SAS interprets the comma as part of the %Do_Over call and probably fails. Since the comma is quite common to place between the generated code, you can specify the text comma in the argument. Consequently, the two calls below are identical.
%put %do_over(arr, phrase="?", between=%str(,)); %put %do_over(arr, phrase="?", between=comma);
Dynamic Macro Calls with %Do_Over
Next, let us see how to use %Do_Over to create dynamic macro calls. First, I create the simple macro PutIt. Next, I create a macro array with the %Array Macro. Finally, I use the %Do_Over macro with the macro= argument. In the macro= statement, I specify the Putit macro without the parentheses.
%macro PutIt(x); %put &x.; %mend; %array(arr, values=one two three) %do_over(arr, macro=PutIt)
This is equivalent to calling the Putit Macro three times like below.
%PutIt(&a1) %PutIt(&a2) %PutIt(&a3)
The syntax above only works for macros with positional parameters. However, most programmers prefer keyword parameters. For macros with keyword parameters, I have to use the Keywork argument like in the example below.
%macro PutIt2(x=); %put &x.; %mend; %array(arr, values=one two three) %do_over(arr, macro=PutIt2, keyword=x)
As a side note, I do not have to use the macro= argument at all. I can just as well use it directly in the Phrase= argument. However, I must use a proper quoting function like %Nrstr that masks macro triggers as well.
Build Entire Statements and Expressions
The key to utilize the dynamic nature of the two macros to the fullest is to understand the Phrase= argument. The Phrase= argument allows us to build entire statements and expressions in any part of your SAS code. Consider the example below. I intentionally use the Options Mprint global statement to print the generated code in the log. First, I create a macro array with the values one, two and three. Then, I use the %Do_Over macro inside a data step to generate three assignment statements. One for each value in the array. Consequently, I create a data step variable with the value 1 for each value in the macro array.
/* Build entire statements and expressions */ %array(arr, values=one two three) options mprint; data test; %do_over(arr, phrase=?=1;) run; options nomprint;
Let us see another example of creating entire statements with the %Do_Over macro. Below, I use the sashelp.class data set to create a macro array with the names of all males in the data. Then, I use the %Do_Over macro to create a series of If-Then-Else Statements. I use the Between= argument to insert the create else if statements for all but the first one. You can check the log to see the generated code.
%array(names, data=sashelp.class(where=(sex="M")), var=name) options mprint; data test; set sashelp.class; %do_over(names, phrase=if name="?" then ?=1;, between=else) run; options nomprint;
In this post, we explore the concept of macro arrays. Furthermore, we learn about the utility macros %Array and %Do_Over. We see numerous examples of how the two macro create and utilize lists of macro variables (macro arrays) to create dynamic SAS code with minimal effort. The two macros are very popular among SAS macro programmers. And for good reason. Once you get the grasp of them, they become invaluable in your SAS toolkit. I highly recommend playing around with the macros and the examples in this post. It is not hard to see that the macros can be used to create much more advanced code than in the examples here. I hope that you can use the examples in the post as templates to build more advanced code. Read the documentation in the macro definitions thoroughly and spend some time creating dynamic code with the macros. It is well worth the time.
You can download the entire code from this post here. The macro definitions are available in the link at the top.