In many programming languages, there is a function named eval() that can be used to evaluate an expression and return the result at run time. For example, in Python, the eval() function parses the expression passed to it and runs a Python expression or code within the program. Even Python eval() supports more optional parameters, such as global/local dictionary for runtime context, but the goal for evaluation remains the same.

When an expression is stored as a character variable of an SAS observation, it suggests the running context is the SAS program execution phase, so the internal data structure like Input Buffer (IB), Program Data Vectors (PDV) and Descriptor Information of the output database are all accessible. Furthermore, all SAS system functions are also accessible in an expression, just like the raw SAS code exposed to SAS compiler and runtime. In the following SAS data set, what the user wants is to get the real value from the expression in variable C. It is different from the commonly used Calculated Column, which uses the same computing rule to expand a new variable for all observations. Here the column C has a different computing rule for each observation; the user's expected result is the column D.

```%let MVAR=2; data a; length a b 8 c \$ 255; a=3; b=4; c='a**2+b**2'; output; /* Arithmetic or Logical */ a=7; b=22; c='b/a * &MVAR'; output; /* SAS Macro Var*/ a=113; b=355; c='cos(b/a)'; output; /* Trigonometric */ a=0; b=1; c='cdf("NORMAL", 0, a, b)'; output; /* Probability */ run; proc print;run;```

Someone might want a solution that parses the expression in variable C, and then try to rebuild the abstract syntax tree and try to evaluate it from bottom to top. I can’t say this solution is totally wrong but it’s very complex and too hard to generate a general solution. If we have an SAS function eval() in DATA Step, then we can easily use the following code to achieve the goal. Unfortunately, SAS did not provide the eval() function.

```  data b; set a; d=eval(c); run;```

SAS provides Macro function %eval, %sysevalf to evaluate arithmetic and logical expressions using integer or floating-point arithmetic. SAS also provides function resolve() (not supported in the DATA Step that runs in CAS) to return the resolved value of the argument after it has been processed by the macro facility. Anyway, resolve() can’t access the values of macro variable assigned by symput() or symputn() at program execution phase. So, for the SAS code as below, it outputs c=150 and d=200. For simple arithmetic and logical expression, we can use the tranwrd() function to replace all variable names with real values, and then use resolve('%sysevalf('|| cats(expression_with_real_values) || ')') to get evaluated result, but it limited to only SAS functions supported by %eval() and %sysevalf() macro functions.

```%let a=100; %let b=50; data _null_; call symput("b", 200); c=resolve("%eval(&a+&b)"); put c=; d=symget("b"); put d=; run;```

Now let’s return to the key question we raised: How can we implement the eval() function in SAS Data Step? The best solution we found so far is to use the dynamic code generation mechanism in SAS, and then submit the code to SAS for parsing and execution. In this way, we retrieve string expression from a variable to a real expression in SAS code successfully. We don’t care what the valid SAS expression is, we totally convert it to the code snippets and submit it for execution. Syntax check, reference to PDV variables, internal functions, and even SAS Macro variables are all supported. Yes, it’s a smart and concise implementation for general purposes.

Due to each observation having a different computing rule, we need to use _N_ to control the observation mapping. So, for the existing dataset A, we can use the following code to achieve the goal. The key secret is to use CALL EXECUTE to generate SAS code dynamically and delay execution after the code is ready. In the output dataset B, we have a numeric column D with the value evaluated with the character expression from column C. You can see the output on the RIGHT part of the Figure 1.

```data _null_; set a end=last; if _n_=1 then call execute("data b; set a;"); call execute( "if _N_=" || compress(_N_) || " then d=" || trim(c) || "; "); if last then call execute("run;"); run; proc print; run;```

## Wrap up as a reusable SAS macro

We also can wrap up the above SAS code in a reusable SAS macro, so this logic can be used anywhere you want. The %evalvar macro has four arguments: ds= and var= are the input dataset and columns with expression to be evaluated. And outds= and outvar= are the output dataset and columns to store result. We also can specify same value for ds= and outds=, so the user just expands the existing dataset with an additional evaluated column.

```%macro EvalVar(ds=, var=, outds=, outvar=); data _null_; set &ds end=last; if _n_=1 then call execute("data &outds; set &ds;"); call execute( "if _n_=" || compress(_n_) || " then &outvar=" || &var || ";"); if last then call execute("run;"); run; %mend;   %EvalVar(ds=a, var=c, outds=b, outvar=d); proc print; run;```

By the way, I also had a temporary SAS code generation solution implemented via the %include SAS macro. The temporary SAS code will be deleted automatically when the SAS session is closed. The sample code is also attached here for your information.

```filename tmpfile temp; data _null_; file tmpfile; set a end=last; if _N_=1 then put "data b; set a;"; put "if _N_ =" _N_ "then d=" c ";"; if last then put "run;"; run; %include tmpfile; proc print; run;```

## Summary

In this article, we talk about how to evaluate SAS expressions in Data Step dynamically. The expression parsing and execution are totally handled by SAS at program execution phase. It avoids handling abstract syntax tree parsing and evaluation ourselves on it. We introduce two dynamic code generation implementations via call execute or %include. We also use _N_ to control observation mapping due to each observation has different computing rules. This implementation can reflect the beauty of simplicity in SAS before SAS provides system function eval() one day.

How to evaluate SAS expression in DATA Step dynamically was published on SAS Users.

Do you want to spend less time on the tedious task of preparing your data? I want to tell you about a magical and revolutionary SAS macro called %TK_codebook. Not only does this macro create an amazing codebook showcasing your data, it also automatically performs quality control checks on each variable. You will easily uncover potential problems lurking in your data including variables that have:

• Incomplete formats
• Out of range values
• No variation in response values
• Variables missing an assigned user-defined format
• Variables that are missing labels

All you need is a SAS data set with labels and formats assigned to each variable and the accompanying format catalogue. Not only will this macro change the way you clean and prepare your data, but it also gives you an effortless way to evaluate the quality of data you obtain from others before you start your analysis. Look how easy it is to create a codebook if you have a SAS data set with labels and formats:

```title height=12pt 'Master Codebook for Study A Preliminary Data'; title2 height=10pt 'Simulated Data for Participants in a Health Study'; title3 height=10pt 'Data simulated to include anomalies illustrating the power of %TK_codebook';   libname library "/Data_Detective/Formats/Blog_1_Codebooks";   %TK_codebook(lib=work, file1=STUDYA_PRELIM, fmtlib=LIBRARY, cb_type=RTF, cb_file=/Data_Detective/Book/Blog/SAS_Programs/My_Codebook.rtf, var_order=INTERNAL, organization = One record per CASEID, include_warn=YES; run;```

## Six steps create your codebook

After creating titles for your codebook, this simple program provides %TK_codebook with the following instructions:

1. Create a codebook for SAS data set STUDYA_PRELIM located in the temporary Work library automatically defined by SAS
2. Find the formats assigned to the STUDYA_PRELIM in a format catalogue located in the folder assigned to the libref LIBRARY
3. Write your codebook in a file named /Data_Detective/Book/Blog/SAS_Programs/My_Codebook.rtf
4. List variables in the codebook by their INTERNAL order (order stored in the data set)
5. Add “One record per CASEID” indicating which variable(s) uniquely identify each observation to codebook header
6. Include reports identifying potential problems in the data

Just these few lines of code will create the unbelievably useful codebook shown below.

The data set used has many problems that can interfere with analysis. %TK_codebook creates reports showing a concise summary of only those problem variables needing close examination. These reports save you an incredible amount of time.

Using assigned formats, %TK_codebook identifies unexpected values occurring in each variable and provides a summary in the first two reports.

Values occurring outside those defined by the assigned format indicate two possible problems:

1. A value was omitted from the format definition (Report 1 – Incomplete formats)
2. The variable has unexpected values needing mitigation before the data is analyzed (Report 2 – Out of Range Value)

The next report lists numeric variables that have no variation in their values.

These variables need examining to uncover problems with preparing the data set.

The next two reports warn you about variables missing an assigned user-defined format. These variables will be excluded from screening for out-of-range values and incomplete format definitions.

The last report informs you about variables that are missing a label or have a label that matches the variable name.

It is easy to use %TK_codebook to resolve problems in your data and create an awesome codebook. Instead of spending your time preparing your data, you will be using your data to change the world!

Download %TK_codebook from my author page, then learn to use it from my new book, The Data Detective’s Toolkit: Cutting-Edge Techniques and SAS Macros to Clean, Prepare, and Manage Data.

Creating codebooks with SAS macros was published on SAS Users.

In my new book, End-to-End Data Science with SAS: A Hands-On Programming Guide, I use the 1.5 IQR rule to adjust multiple variables.  This program utilizes a macro that loops through a list of variables to make the necessary adjustments and creates an output data set.

One of the most popular ways to adjust for outliers is to use the 1.5 IQR rule. This rule is very straightforward and easy to understand. For any continuous variable, you can simply multiply the interquartile range by the number 1.5. You then add that number to the third quartile. Any values above that threshold are suspected as being an outlier. You can also perform the same calculation on the low end. You can subtract the value of IQR x 1.5 from the first quartile to find low-end outliers.

The process of adjusting for outliers can be tedious if you have several continuous variables that are suspected as having outliers. You will need to run PROC UNIVARIATE on each variable to identify its median, 25th percentile, 75th percentile, and interquartile range. You would then need to develop a program that identifies values above and below the 1.5 IQR rule thresholds and overwrite those values with new values at the threshold.

The following program is a bit complicated, but it automates the process of adjusting a list of continuous variables according to the 1.5 IQR rule. This program consists of three distinct parts:

1. Create a BASE data set that excludes the variables contained in the &outliers global macro. Then create an OUTLIER data set that contains only the unique identifier ROW_NUM and the outlier variables.
2. Create an algorithm that loops through each of the outlier variables contained in the global variable &outliers and apply the 1.5 IQR rule to cap each variable’s range according to its unique 1.5 IQR value.
3. Merge the newly restricted outlier variable with the BASE data set.
```/*Step 1: Create BASE and OUTLIER data sets*/   %let outliers = /*list of variables*/;   DATA MYDATA.BASE; SET MYDATA.LOAN_ADJUST (DROP=&amp;outliers.); ROW_NUM = _N_; RUN;   DATA outliers; SET MYDATA.LOAN_ADJUST (KEEP=&amp;outliers. ROW_NUM); ROW_NUM = _N_; RUN;    /*Step 2: Create loop and apply the 1.5 IQR rule*/   %MACRO loopit(mylist); %LET n = %SYSFUNC(countw(&amp;mylist));   %DO I=1 %TO &amp;n; %LET val = %SCAN(&amp;mylist,&amp;I);   PROC UNIVARIATE DATA = outliers ; VAR &amp;val.; OUTPUT OUT=boxStats MEDIAN=median QRANGE=iqr; run;   data _NULL_; SET boxStats; CALL symput ('median',median); CALL symput ('iqr', iqr); run;   %PUT &amp;median; %PUT &amp;iqr;   DATA out_&amp;val.(KEEP=ROW_NUM &amp;val.); SET outliers;   IF &amp;val. ge &amp;median + 1.5 * &amp;iqr THEN &amp;val. = &amp;median + 1.5 * &amp;iqr; RUN;   /*Step 3: Merge restricted value to BASE data set*/   PROC SQL; CREATE TABLE MYDATA.BASE AS SELECT * FROM MYDATA.BASE AS a LEFT JOIN out_&amp;val. as b on a.ROW_NUM = b.ROW_NUM; QUIT;   %END; %MEND;   %LET list = &amp;outliers; %loopit(&amp;list);```

Notes on the outlier adjustment program:

• A macro variable is created that contains all of the continuous variables that are suspected of having outliers.
• Separate data sets were created: one that contains all of the outlier variables and one that excludes the outlier variables.
• A macro program is developed to contain the process of looping through the list of variables.
• A macro variable (n) is created that counts the number of variables contained in the macro variable.
• A DO loop is created that starts at the first variable and runs the following program on each variable contained in the macro variable.
• PROC UNIVARIATE identifies the variable’s median and interquartile range.
• A macro variable is created to contain the values of the median and interquartile range.
• A DATA step is created to adjust any values that exceed the 1.5 IQR rule on the high end and the low end.
• PROC SQL adds the adjusted variables to the BASE data set.

This program might seem like overkill to you. It could be easier to simply adjust outlier variables one at a time. This is often the case; however, when you have a large number of outlier variables, it is often beneficial to create an algorithm to transform them efficiently and consistently

Adjusting outliers with the 1.5 IQR rule was published on SAS Users.

Did you know you could have a single universal function that can replace all the functions in the world? All those sin(x), log(x), … whatever(x) can all be replaced by a single super function f(x). Don’t believe me? Just make those functions names – sin, log, … whatever to be another argument to that all-purpose function f, just like that: f(x, sin), f(x, log), … f(x, whatever). Now, we only must deal with a single function instead of many, and its second argument will define what transformation needs to be done with the first argument in order to arrive at this almighty function’s value.

## How many functions there are in SAS

Last time I counted there were more than 600 SAS functions, and that is excluding call routines and macro functions. But even that huge number grossly under-represents the actual number of functions available in SAS. That is because there are some functions that are built like the universal multi-purpose super function described above. For example, look at the following functions:

finance() function represents several dozen various financial functions;

finfo() function represents multiple functions returning various information items about files (file size, date created, date modified, access permission, etc.);

dinfo() function returns similar information items about directories;

attrn() function returns numeric attributes of a data set (number of observations, number of variables, etc.)

attrc() function returns character attributes of a data set (engine name, encoding name, character set, etc.)

Each of these functions represents not a single function, but a group of functions, and one of their arguments stipulates specific functionality (an information item or an attribute) that is being requested. You can think of this argument as a function modifier.

## %sysfunc SAS macro function

%sysfunc() is a super macro function that brings a wealth of SAS functions into SAS macro language. With very few exceptions, most SAS functions are available in SAS macro language thanks to the %sysfunc().

Moreover, we can build our own user-defined macro functions using SAS-supplied macro functions (such as %eval, %length, %quote, %scan, etc.), as well as hundreds of the SAS non-macro functions wrapped into the %sysfunc() super macro function.

## Building a super macro function to retrieve information about data sets

Armed with such a powerful arsenal, let’s build a multi-purpose macro function that taps into the data tables’ metadata and extracts various information items about those tables.

Let’s make this macro function return any of the following most frequently used values:

• Number of observations
• Number of variables
• Variables list (positional, separated by spaces)
• Variables list (positional, separated by commas)

Obviously, we can create much more of these information items and attributes, but here I am just showing how to do this so that you can create your own list depending on your needs.

In my earlier blog post, How to create and use SAS macro functions, we had already built a macro function for getting the number of observations; let’s expand on that.

Here is the SAS Macro code that handles extraction of all four specified metadata items:

```%macro dsinfo(dset,info); /* dset - data set name */ /* info - modifier (NOBS, NVARS, VARLIST, VARLISTC) */ %local dsid result infocaps i; %let infocaps = %upcase(&info); %let dsid = %sysfunc(open(&dset)); %if &dsid %then %do; %if &infocaps=NOBS %then %let result = %sysfunc(attrn(&dsid,nlobs)); %else %if &infocaps=NVARS %then %let result = %sysfunc(attrn(&dsid,nvars)); %else %if &infocaps=VARLIST %then %do i=1 %to %sysfunc(attrn(&dsid,nvars)); %let result = &result %sysfunc(varname(&dsid,&i)); %end; %else %if &infocaps=VARLISTC %then %do i=1 %to %sysfunc(attrn(&dsid,nvars)); %if &i eq 1 %then %let result = %sysfunc(varname(&dsid,&i)); %else %let result = &result,%sysfunc(varname(&dsid,&i)); %end; %let dsid = %sysfunc(close(&dsid)); %end; %else %put %sysfunc(sysmsg()); &result %mend dsinfo;```

The SAS log will show:

```%put NOBS=***%dsinfo(SASHELP.CARS,NOBS)***;
NOBS=***428***
%put NVARS=***%dsinfo(SASHELP.CARS,NVARS)***;
NVARS=***15***
%put VARLIST=***%dsinfo(SASHELP.CARS,VARLIST)***;
VARLIST=***Make Model Type Origin DriveTrain MSRP Invoice EngineSize Cylinders Horsepower MPG_City MPG_Highway Weight Wheelbase Length***
%put VARLISTC=***%dsinfo(SASHELP.CARS,VARLISTC)***;
VARLISTC=***Make,Model,Type,Origin,DriveTrain,MSRP,Invoice,EngineSize,Cylinders,Horsepower,MPG_City,MPG_Highway,Weight,Wheelbase,Length***```

## Macro function code highlights

We used the following statement to make our macro function case-insensitive regarding the info argument:

%let infocaps = %upcase(&info);

Then depending on the up-cased second argument of our macro function (modifier) we used the attrn(), varnum() and varname() functions within %sysfunc() to retrieve and construct our result macro variable.

We stick that result macro variable value, &result, right before the %mend statement so that the value is returned to the calling environment.

While info=VARLIST (space-separated variable list) is useful in DATA steps, info=VARLISTC (comma-separated variable list) is useful in PROC SQL.

## Usage example

Having this %dsinfo macro function at hands, we can use it in multiple programming scenarios. For example:

```/* ending SAS session if no observations to process */ %if %dsinfo(SASHELP.CARS,NOBS)=0 %then %do; endsas; %end;   /* further processing */ data MYNEWDATA (keep=%dsinfo(SASHELP.CARS,VARLIST)); retain %dsinfo(SASHELP.CARS,VARLIST); set SASHELP.CARS; if _n_=1 then put %dsinfo(SASHELP.CARS,VARLIST); /* ... */ run;```

Here we first check if there is at least one observation in a data set. If not (0 observations) then we stop the SAS session and don’t do any further processing. Otherwise, when there are some observations to process, we continue.

If SAS code needs multiple calls to the same macro function with the same argument, we can shorten the code by first assigning that macro function’s result to a macro variable and then reference that macro variable instead of repeating macro function invocation. Here is an example:

```/* further processing */ %let vlist = %dsinfo(SASHELP.CARS,VARLIST); data MYNEWDATA (keep=&vlist); retain &vlist; set SASHELP.CARS; if _n_=1 then put &vlist; /* ... */ run;```

Do you see the benefits of these multi-purpose SAS macro functions? Can you suggest other scenarios of their usage? Please share your thoughts in the comments section below.

SAS® Macro Language immensely empowers SAS programmers with versatility and efficiency of their code development. It allows SAS users to modularize programming code with “once written – many times used” components, and in many cases automatically generate data-driven SAS code.

## Macro language and macro processor

Generally, SAS software processes your SAS program step by step, first scanning it for macro language objects - macro variables referenced as &somename, and macros referenced as %somename. If found, SAS software activates macro processor which resolves and substitutes those macro references according to the macro language syntax before SAS compiles and executes your programming steps.

## SAS macro language vs. SAS programming language

A SAS program usually consists of two, often interwoven layers – macro layer and non-macro layer, each with its own syntax and its own timing of compilation and execution. In other words, SAS code is a combination of two distinct languages:

• SAS programming language (comprised of DATA steps, PROC steps and global statements such as LIBNAME, OPTIONS, TITLE etc.)
• SAS macro language (comprised of %LET, %IF, %DO, macro functions etc.) which is processed separately from and before SAS compiler executes SAS programming language code.

The difference between them is like a difference between cooking a meal and eating the meal. In this analogy meal=code, cooking=SAS macro language, eating=SAS programming language. Clear understanding of this difference is the key to becoming a successful SAS programmer.

## Two types of SAS macros

There are two distinct types of SAS macros:

1. Macros that generate some SAS programming language code which can span across SAS statements or steps;
2. Macros that generate some string values which can be used as part of SAS programming language code or data values, but they are not complete SAS statements or steps. This type does not generate any SAS executable code, just a value.

## What is a SAS macro function?

SAS macro function is a SAS macro that generates a value. In other words, it is the type 2 macro described above. As any SAS macros, SAS macro functions can have any number (zero or more) of positional or/and named parameters (arguments). SAS users may define their own macro functions, but in doing so you may not utilize any SAS language syntax; only SAS macro language syntax is allowed. You can use existing macro functions in your own macro function definition. Among others, one of the most powerful is %SYSFUNC macro function which brings a wealth of SAS language functions into SAS macro language.

## Sources of SAS macro functions

SAS macro functions may come from the following three sources.

### 1. Pre-built macro functions

Pre-built macro functions that are part of the macro processor. These are such macro functions as %eval, %length, %quote, %scan, %str, %sysfunc, %upcase, etc. Here is a complete list of the pre-built SAS macro functions.

### 2. Auto-call macro functions

Auto-call macros, some of them are type 1 (macros), and some – type 2 (macro functions) such as %cmpres, %left, %lowcase, %trim, %verify, etc. These macro functions supplement the pre-built macro functions. The main difference from the pre-built macro functions is that the auto-call macro functions are program samples of the user-defined macro functions that are written in SAS macro language and made available to you without having to define or include them in your programs. The auto-call macro functions come with your SAS software installation and usually pre-configured for you by setting MAUTOSOURCE and SASAUTOS= macro system options. They may include several macro libraries depending on the SAS products licensed at your site. For example, for my SAS BASE installation the auto-call macro library is in the following folder:

C:\Program Files\SASHome\SASFoundation\9.4\core\sasmacro

Here is a selected list of auto-call macros provided with SAS software.

From the usage standpoint, you will not notice any difference between the pre-built and the auto-call macro functions. For example, macro function %upcase() is pre-built, while macro function %lowcase() is auto-call macro function. They belong to entirely different families, but we use them as if they are complementary siblings.

### 3. User-defined macro functions

Finally, there are user-defined macro functions that do not come with SAS installation. These are the macro functions that you define on your own. Usually, they are kept separately from the auto-call macros mainly in order to distinguish them from SAS-supplied ones.

To enable access to your own SAS macro library in addition to the auto-call macro library (or libraries), you can use the INSERT= system option:

options insert=(sasautos="path_to_your_own_macro_library_folder");

Instead of replacing the SASAUTOS value, this option inserts an additional value into the existing SASAUTOS option as the first value, thus allowing you to tap into your own macro library first, and then also into pre-set SAS auto-call libraries.

## Creating user-defined macro function

Let’s consider the following example. Suppose, we want to create a macro function that takes a data set name as an argument and returns a value equal to the number of observations in that data set.

We know that the following code calculates the number of observations in a data set:

```data _null_; set SASHELP.CARS (obs=0) nobs=n; call symputx('NOBS',n); run;   %put &=NOBS; NOBS=428```

Can we create a SAS macro function by enclosing this code into macro? Something like this:

```%macro nobs(dset=,result=); data _null_; set &dset (obs=0) nobs=n; call symputx("&result",n); run; %mend nobs;```

The answer is “No”. Yes, we created a valid macro; we can invoke this macro to produce the result:

```%nobs(dset=SASHELP.CARS, result=NOBS); %put &=NOBS; NOBS=428```

But this is not a macro function. Remember type 2 macro that does not generate any SAS programming language code, just a value? But this macro does generate SAS code which assigns a value to the macro variable specified as the second argument (result=NOBS).

In order to create a valid macro function, our macro should not have any SAS language code in it – neither a DATA step, nor a PROC step. It may only be comprised of the SAS macro language code. Here it is:

```%macro nobs(dset); %local dsid n rc; %let dsid = %sysfunc(open(&dset)); %if &dsid %then %do; %let n = %sysfunc(attrn(&dsid,nlobs)); %let dsid = %sysfunc(close(&dsid)); %end; %else %put %sysfunc(sysmsg()); &n %mend nobs;```

When macro processor executes this macro, the only object that gets passed to the SAS language compiler is the value shown in the line right before the %mend. This is the calculated value of the number of observations (denoted by &n ). This is the only thing that is visible by the SAS language compiler, the rest is the macro language code visible and being handled by SAS macro processor.

IMPORTANT: When defining SAS macro function always use %local statement to list ALL macro variables that are created in your macro to ensure they will not accidentally overwrite same-named macro variables in the calling environment. You don’t need to declare %local for macro parameters as they are always local automatically.

## SAS macro functions usage examples

When a macro function is defined this way, wherever you place its invocation %nobs(SASHELP.CARS) in your SAS code it will be evaluated and replaced with the corresponding value (in this case it is number 428) by the SAS macro processor. That way you can avoid substandard hard-coding and make your SAS code dynamic and powerful. You can use macro functions in many SAS coding contexts. For example:

• Assignment statements for macro variable: %let NOBS=%nobs(SASHELP.CARS);
• Assignment statement in a DATA step: x = %nobs(SASHELP.CARS);
• As a value of the iterative do loop: do i=1 to %nobs(SASHELP.CARS);
• As part of condition in IF statement: if %nobs(SASHELP.CARS) > 500 then do;

And so on.

Do you find this post useful? Do you use SAS macro functions? Can you suggest other usage examples? Please share with us in the Comments below.

How to create and use SAS macro functions was published on SAS Users.

Suppose you want a list of car manufacturers from the CARS dataset. Easy!  Call the %CHARLIST macro from a %PUT statement, like this: The CHARLIST macro generates a list of unique values of a selected variable from a selected dataset. So does PROC FREQ. But, if you don't need statistics, the CHARLIST [...]

The post Finding your Dream Car with a little macro magic appeared first on SAS Learning Post.

The stored compiled macro facility enables you to compile and save your macro definition in a permanent catalog in a library that you specify. The macro is compiled only once. When you call the macro in the current and subsequent SAS® sessions, SAS executes the compiled code from the macro catalog that you created when you compiled the macro.

The stored compiled facility has two main purposes. The first is that it enables your code to run faster because the macro code does not need to be compiled each time it is executed. The second purpose is to help you protect your code. Sometimes you need to share code that you’ve written with other users, but you do not want them to be able to see the code that is being executed. The stored compiled macro facility enables you to share the program without revealing the code. Compiling the macro with the SECURE option prevents the output of the SYMBOLGEN, MPRINT, and MLOGIC macro debugging options from being written to the log when the macro executes. This means that no code is written to the log when the code executes. After the macro has been compiled, there is no way to decompile it to retrieve the source code that created the catalog entry. This behavior prevents the user from being able to retrieve the code. However, it also prevents you from being able to recover the code.

It is very important to remember that there is no way to get back the code from a stored compiled macro. Because of this behavior, you should ALWAYS save your code when creating a stored compiled macro catalog. In order to update a stored compiled macro, you must recompile the macro. The only way to do this is to submit the macro definition again. Another important fact is that a stored compiled macro catalog can be used only on the same operating system and release of SAS that it was created on. So, in order to use a stored compiled macro on another operating system or release of SAS, that macro must be compiled in the new environment. Again, the only way to compile the macro is to resubmit the macro definition.

### Save the Macro Source Code

To make it easier for you to save your code, the %MACRO statement contains the SOURCE option. When you create a stored compiled macro, the SOURCE option stores the macro definition as part of a catalog entry in the SASMACR catalog in the permanent SAS library listed on the SASMSTORE= system option.

Here is the syntax needed to create a stored compiled macro with the SOURCE option set:

```libname mymacs 'c:\my macro library'; ❶ options mstored sasmstore=mymacs; ❷   %macro test / store source; ❸   libname mylib1 'path-to-my-first-library'; libname mylib2 'path-to-my-second-library';   %mend;```

❶ The LIBNAME statement points to the SAS library that will contain my stored compiled macro catalog.

❷ The MSTORED system option enables the stored compiled facility. The SASMSTORE= option points to the libref that points to the macro library.

❸ The STORE option instructs the macro processor to store the compiled version of TEST in the SASMACR catalog in the library listed in the SASMSTORE= system option. The SOURCE option stores the TEST macro definition in the same SASMACR catalog.

Note that the contents of the SASMACR catalog do not contain an entry for the macro source. The source has been combined with the macro entry that contains the compiled macro. To verify that the source has been saved, add the DES= option to the %MACRO statement. The DES= option enables you specify a description for the macro entry in the SASMACR catalog. So for example, you could add the following description when compiling the macro to indicate that the source code has been saved:

`%macro test / store source des=’Source code saved with entry’;`

You can look at the contents of the macro catalog using the CATALOG procedure:

```proc catalog cat=a.sasmacr; contents; run; quit;```

You see the description indicating that the source code was saved with the macro entry in the output from PROC CATALOG:

### Retrieve the Macro Source Code

When you need to update the macro or re-create the catalog on another machine, you can retrieve the macro source code using the %COPY statement. The %COPY statement enables you to retrieve the macro source code and write the code to a file. Here is the syntax:

`%copy test / source outfile='c:\my macro library\test.sas';`

This %COPY statement writes the source code for the TEST macro to the TEST.SAS file. Using TEST.SAS, you are now able to update the macro or compile the macro on another machine.

Remember, you should always save your source code when creating a stored compiled macro. Without the source code, you will not be able to update the macro or move the macro to a new environment.

Always save your code when creating a stored compiled macro was published on SAS Users.

SAS® offers several ways that you can find the top n% and bottom n% of data values based on a numeric variable. The RANK procedure with the GROUPS= option is one method. Another method is The UNIVARIATE procedure with the PCTLPTS= option. Because there are several ways to perform this task, you can choose the procedure that you are most familiar with. In this blog post, I use the SUMMARY procedure to generate the percentile values and macro logic to dynamically choose the desired percentile statistics. After the percentiles are generated, I subset the data set based on those values. This blog post provides two detailed examples: one calculates percentiles for a single variable and one calculates percentiles within a grouping variable.

### Calculate Percentiles of a Single Variable

Calculating percentiles of a single variable includes the following steps. Within the macro, a PROC SUMMARY step calculates the percentiles. The subsequent DATA step uses CALL SYMPUTX to create macro variables for the percentile values, and the final DATA step uses those macro variables to subset the data. Here is the code, which is explained in detail below:

```/* Create sample data */ data test; do i=1 to 10000; x=ranuni(i)*12345; output; end; drop i; run;   proc sort data=test; by x; run;   %macro generate_percentiles(ptile1,ptile2); /* Output desired percentile values */ proc summary data=test; var x; output out=test1 &amp;ptile1= &amp;ptile2= / autoname; run;   /* Create macro variables for the percentile values */ data _null_; set test1; call symputx("&amp;ptile1", x_&amp;ptile1); call symputx("&amp;ptile2", x_&amp;ptile2); run; %put &amp;&amp;&amp;ptile1; %put &amp;&amp;&amp;ptile2;   data test2; set test; /* Use a WHERE statement to subset the data */ where x le &amp;&amp;&amp;ptile1 or x ge &amp;&amp;&amp;ptile2; run;   proc print; run;   %mend;   options mprint mlogic symbolgen; %generate_percentiles(p1,p99) %generate_percentiles(p25,p75)```

After creating and sorting the sample data, I begin my macro definition with two parameters that enable me to substitute the desired percentiles in my macro invocation:

`%macro generate_percentiles(ptile1,ptile2);`

The PROC SUMMARY step writes the desired percentiles for variable X to the Test1 data set. The AUTONAME option names the percentile statistics in the following format, <varname>_<percentile> (for example, x_p25).

```proc summary data=test; var x; output out=test1 &amp;ptile1= &amp;ptile2= / autoname; run;```

Next, I want to store the values of the percentile statistics in macro variables so that I can use them in later processing. I use CALL SYMPUTX to do this, which gives the macro variables the same name as the statistic. To see the resulting values in the log, I use

 ```data _null_; set test1; call symputx("&ptile1", x_&ptile1); call symputx("&ptile2", x_&ptile2); run; %put &&&ptile1; %put &&&ptile2;```

The SAS log shows the following:

```MLOGIC(GENERATE_PERCENTILES): %PUT &amp;&amp;&amp;ptile1 SYMBOLGEN: &amp;&amp; resolves to &amp;. SYMBOLGEN: Macro variable PTILE1 resolves to p1 SYMBOLGEN: Macro variable P1 resolves to 123.22158288 123.22158288 MLOGIC(GENERATE_PERCENTILES): %PUT &amp;&amp;&amp;ptile2 SYMBOLGEN: &amp;&amp; resolves to &amp;. SYMBOLGEN: Macro variable PTILE2 resolves to p99 SYMBOLGEN: Macro variable P99 resolves to 12232.136483 12232.136483```

I use these macro variables in a WHERE statement within a DATA step to subset the data set based on the percentile values:

```data test2; set test; /* Use a WHERE statement to subset the data */ where x le &amp;&amp;&amp;ptile1 or x ge &amp;&amp;&amp;ptile2; run;```

Finally, the macro invocations below pass in the desired percentile statistics:

```%generate_percentiles(p1,p99) %generate_percentiles(p25,p75)```
 ```/* Create sample data */ data test; do group='a','b'; do i=1 to 10000; x=ranuni(i)*12345; output; end; end; drop i; run;   proc sort data=test; by group x; run;   %macro generate_percentiles(ptile1,ptile2); /* Output desired percentile values by group */ proc summary data=test; by group; var x; output out=test1 &ptile1= &ptile2= / autoname; run;   /* Create macro variables for each value of the BY variable */ /* Create macro variables for the percentile values for each BY group */ /* Create a macro variable that is the count of the unique values of the BY variable */ data _null_; retain count 0; set test1; by group; if first.group then do; count+1; call symputx('val'||left(count),group); call symputx("&ptile1"||'_'||left(count), x_&ptile1); call symputx("&ptile2"||'_'||left(count), x_&ptile2); end; call symput('last',left(count)); run; %put _user_;   /* Loops through each value of the BY variable */ %do i=1 %to &last;   data test&i; set test; where group="&&val&i"; /* Use an IF statement to subset the data */ if x le &&&ptile1._&i or x ge &&&ptile2._&i; run;   proc print; run;   %end; %mend;   options mprint mlogic symbolgen; %generate_percentiles(p1,p99)```

Calculating percentiles has many applications, including ranking data, finding outliers, and subsetting data. Using a procedure in Base SAS® that enables you to request percentile statistics along with the power of the macro language, you can dynamically generate desired values that can be used for further processing and analysis.

Have you ever written a macro and wondered if there was an easy way to pass values to the macro? You can by using macro parameters. Macro parameters enable you to pass values into the macro at macro invocation, and set default values for macro variables within the macro definition. In this blog post, I also discuss how you can pass in a varying number of parameter values.

There are two types of macro parameters: positional and keyword.

### Positional Parameters

You can use positional parameters to assign values based on their position in the macro definition and at invocation. The order that you use to specify the values must match the order in which they are listed in the %MACRO statement. When specifying multiple positional parameters, use a comma to separate the parameters. If you do not pass a value to the macro when it is invoked, a null value is assigned to the macro variable specified in the %MACRO statement.

Here is an example:

```%macro test(var1,var2,var3); %put &=var1; %put &=var2; %put &=var3; %mend test;   /** Each value corresponds to the position of each variable in the definition. **/ /** Here, I am passing numeric values. **/ %test(1,2,3) /** The first position matches with var1 and is given a null value. **/ %test(,2,3) /** I pass no values, so var1-var3 are created with null values. **/ %test() /** The first value contains a comma, so I use %STR to mask the comma. **/ /** Otherwise, I would receive an error similar to this: ERROR: More **/ /** positional parameters found than defined. **/ %test(%str(1,1.1),2,3) /** Each value corresponds to the position of each variable in the definition. **/ /** Here, I am passing character values. **/ %test(a,b,c) /** I gave the first (var1) and second (var2) positions a value of **/ /** b and c, so var3 is left with a null value. **/ %test(b,c)```

Here are the log results:

```173 /** Each value corresponds to the position of each variable in the definition. **/ 174 /** Here, I am passing numeric values. **/ 175 %test(1,2,3) VAR1=1 VAR2=2 VAR3=3 176 /** The first position matches with var1 and is given a null value. **/ 177 %test(,2,3) VAR1= VAR2=2 VAR3=3   178 /** I pass no values, so var1-var3 are created with null values. **/ 179 %test() VAR1= VAR2= VAR3= 180 /** The first value contains a comma, so I use %STR to mask the comma. **/ 181 /** Otherwise, I would receive an error similar to this: ERROR: More **/ 182 /** positional parameters found than defined. **/ 183 %test(%str(1,1.1),2,3) VAR1=1,1.1 VAR2=2 VAR3=3 184 /** Each value corresponds to the position of each variable in the definition. **/ 185 /** Here, I am passing character values. **/ 186 %test(a,b,c) VAR1=a VAR2=b VAR3=c 187 /** I gave the first (var1) and second (var2) positions a value of **/ 188 /** b and c, so var3 is left with a null value. **/ 189 %test(b,c) VAR1=b VAR2=c VAR3=```

### Keyword Parameters

The benefit of using keyword parameters is the ability to give the macro variables a default value within the macro definition. When you assign values using keyword parameters, you must include an equal sign after the macro variable name.

Here is an example:

```%macro test(color=blue,id=123); %put &=color; %put &=id; %mend test;   /** Values passed to the macro overwrite default values from the definition. **/ %test(color=red,id=456) /** Passing in no values allows the default values to take precedence. **/ %test() /** You are not required to pass in a value for each keyword parameter. **/ %test(color=green) /** The order of variables does not matter. **/ %test(id=789,color=yellow)```

Here are the log results:

```270 /** Values passed to the macro overwrite default values from the definition. **/ 271 %test(color=red,id=456) COLOR=red ID=456 272 /** Passing in no values allows the default values to take precedence. **/ 273 %test() COLOR=blue ID=123 274 /** You are not required to pass in a value for each keyword parameter. **/ 275 %test(color=green) COLOR=green ID=123 276 /** The order of variables does not matter. **/ 277 %test(id=789,color=yellow) COLOR=yellow ID=789```

If the macro definition combines positional and keyword parameters, positional parameters must come first. If you do not follow this order, this error is generated:

`ERROR: All positional parameters must precede keyword parameters.`

Here is an example:

```%macro test(val,color=blue,id=123); %put &=color; %put &=id; %put &=val; %mend test;   /** The positional parameter is listed first. **/ %test(1,color=red,id=456)   Here are the log results:   318 /** The positional parameter is listed first. **/ 319 %test(1,color=red,id=456) COLOR=red ID=456 VAL=1```

### PARMBUFF

The PARMBUFF option creates a macro variable called &SYSPBUFF that contains the entire list of parameter values, including the parentheses. This enables you to pass in a varying number of parameter values. In the following example, you can pass any number of parameter values to the macro. This following example illustrates how to parse each word in the parameter list:

```%macro makes/parmbuff; /** The COUNTW function counts the number of words within &SYSPBUFF. **/ %let cnt=%sysfunc(countw(&syspbuff)); /** The %DO loop increments based on the number of words returned to the macro. **/ /** variable &CNT. **/ %do i= 1 %to &cnt; /** The %SCAN function extracts each word from &SYSPBUFF. **/ %let make=%scan(&syspbuff,&i); %put &make; %end; %mend makes;   %makes(toyota,ford,chevy)```

Here are the log results:

```19 %macro makes/parmbuff; 20 /** The COUNTW function counts the number of words within &SYSPBUFF. **/ 21 %let cnt=%sysfunc(countw(&syspbuff)); 22 /** The %DO loop increments based on the number of words returned to the macro **/ 23 /** variable &CNT. **/ 24 %do i= 1 %to &cnt; 25 /** The %SCAN function extracts each word from &SYSPBUFF. **/ 26 %let make=%scan(&syspbuff,&i); 27 %put &make; 28 %end; 29 %mend makes; 30 31 %makes(toyota,ford,chevy) toyota ford chevy```

When you specify the PARMBUFF option and the macro definition includes both positional and keyword parameters, the parameters still receive values when you invoke the macro. In this scenario, the entire invocation list of values is assigned to &SYSPBUFF. Here is an example:

```%macro test(b,a=300)/parmbuff; %put &=syspbuff; %put _local_; %mend;   %test(200,a=100)```

Here are the log results:

```SYSPBUFF=(200,a=100) TEST A 100 TEST B 200```

Notice that &SYSPBUFF includes the entire parameter list (including the parentheses), but each individual parameter still receives its own value.

If you need to know all the parameter values that are passed to the macro, specify the PARMBUFF option in the macro definition to get access to &SYSPBUFF, which contains all the parameter values. For more information about PARMBUFF, see %MACRO Statement in SAS® 9.4 Macro Language: Reference, Fifth Edition.

I hope this blog post has helped you understand how to pass values to a macro. If you have SAS macro questions that you would like me to cover in future blog posts, please comment below.

Using parameters within the macro facility was published on SAS Users.

As technology expands, we have a similarly increasing need to create programs that can be handed off – to clients, to regulatory agencies, to parent companies, or to other projects – and handed off with little or no modification needed by the recipient. Minimizing modification by the recipient often requires […]

The post Using the SAS Macro Language to Create Portable Programs appeared first on SAS Learning Post.