Question

Edit: I read further on the subject of macro programming and apparently, if the macro is precomplied, then none of its statments may depend on the data contents, hence this way of doing it is no good. Is there an alternative?

Following a previous question, I ran into an issue.

I have a certain macro which, inside of it, creates a data set called merged from a PROC SQL. This dataset looks something like this (simplified):

merged dataset
time  ret1  ret2
   1  0.01     . 
   3    .      . 
   4  0.04  0.04
   5  0.02  0.04

My objective is to delete the records where ret1 and ret2 are missing and to have a variable OBS = 1 in all records where ret1 and ret2 are not missing. i.e.

merged dataset
time  ret1  ret2 OBS     R12
   1  0.01     .   .       .
   4  0.04  0.04   1  0.0016
   5  0.02  0.04   1  0.0008

For this I created this data step.

data merged;
    set merged;
    OBS = 1;
    %if nmiss(of ret1 -- ret2) + cmiss(of ret1 -- ret2) > 1 %then
        delete;
    %else
        %if nmiss(of ret1 -- ret2) + cmiss(of ret1 -- ret2) > 0 %then
            %do;
                ret1 = . ;
                ret2 = . ;
                OBS = . ;
            %end;
    R12 = ret1 * ret2;
run;

If I use it outside a Macro, it works very well, but inside a macro it gives me the following error and I do not know how to go around it.

   ERROR: A character operand was found in the %EVAL function or %IF condition 
   where a numeric operand is required. The condition was: nmiss(of ret1 
   -- ret2) + cmiss(of ret1 -- ret2) > 1 

Apparently the %IF statement in a macro does not evaluate the nmiss or cmiss functions. I have tried this with the missing function but it also did not work.

So my question is: Is it possible to do what I want inside a macro? If so, is my attempt close to the solution or do I need to radically change it? I apologize if this is a simple question or if I am missing a crucial aspect of SAS macro programming (likely to be the case), but I have searched and found no other solution for what I want to do.

Thank you in advance for your help.

Was it helpful?

Solution

Macro programming in SAS exists for the purpose of writing SAS language statements in a more efficient manner than you could write them out by hand. It is not intended to interface with data or even to do most of your work for you in most cases; and being 'in a macro' has nothing to do with whether you're using %IF or IF.

Fundamentally, macros are intended to be ways to take a bunch of complicated things that you do regularly and do them with a few keystrokes rather than writing them out every time. Keyboard macros are an example of this - setting "Ctrl+Shift+A" to load an autoexec and run it, say, more efficiently than ctrl+o, find file, open, hit run. It's not actually doing any of the opening and running - it's just taking a list of commands - open, load file, run file - and executing them one after another.

SAS macros are effectively the same thing. Rather than typing out

data want;
set sashelp.class;
if 11 LE age LE 12 then agerange = '11-12';
if 13 LE age LE 14 then agerange = '13-14';
if 15 LE age LE 16 then agerange = '15-16';
run;

you could type

%macro agerange(low,high);
if &low LE age LE &high then agerange = "&low.-&high.";
%mend agerange

and then

data want;
set sashelp.class;
%agerange(11,12);
%agerange(13,14);
%agerange(15,16);
run;

Then, on top of that, we add a layer of looping to make using the macro easier:

%macro agerange_bytwo(low,high);
%do L = &low %to %eval(&high-1) %by 2;
 %let H = %eval(&L+1);
 if &L LE age LE &H then agerange = "&L.-&H.";
%end;
%mend agerange_bytwo;

data want;
set sashelp.class;
%agerange_bytwo(11,16);
run;

Note we mix %do with if. We're using %do to control our imaginary typer of code (deciding what gets placed in the data step), and we're using if as an actual SAS program statement, something we want actually typed into our program.

Now we add one more step - we add a low and high bound.

%macro agerange_bytwo(low,high);
%do L = &low %to %eval(&high-1) %by 2;
 %let H = %eval(&L+1);

 if 

 %if &L = &LOW %then 0; %else &L;
   LE age LE 
 %if &H = &HIGH %then 999; %else &H;

 then agerange = 

 %if &l = &low %then %do;
      "<= &H."
 %end;
 %else %if &h >= &high %then %do;
      "&L.+"
 %end;
 %else %do;
      "&L.-&H."
 %end;
 ;
%end;

%mend agerange_bytwo;

data want;
set sashelp.class;
%agerange_bytwo(11,16);
run;

Here we are combining %if with if directly - and on top of things, we're actually getting in the middle of the SAS statement. Notice the lack of a semicolon after if ... and then agerange =. That's because we don't want to put a semicolon there! We also left them off of the age ranges on the various %if statements, mostly to prove a point - if I were writing this for real I'd probably have them there - but instead, I move it to after the %if - %end to make it more obvious how it works. On the other hand, if we don't use %DO, we do need some semicolons to end the %then and %else sections. This is one reason I almost always use %DO; it makes the semicolons make more sense to me. I usually do it wrong the first time if I just use %if %else without %do.

The SAS statement is

if 0 LE age LE 12 then agerange='<= 12';

so we start with

if

then we have code deciding whether we should put "&L" or "0" there; that's in the macro language since it's deciding what we should be typing, so, %if.

So:

if 0 LE age LE 

now more macro %if to decide about putting H.

That leaves us at

if 0 le age le 12 then agerange = 

And finally one last set of %if conditions to decide whether to type out "&L - &H" or one of the boundary conditions.

That's basically how you should be deciding between %IF and IF. While it's possible to do some data programming in macros - you'll see a lot of people move data into macro variables and then use it to drive macro loops and things like that - usually that's harder than just doing the work in SAS normally. The macro language was created to be able to do things like the above; other uses tend to be overly complicated since it's not really the intent of the language. Think of it this way - a helpful robot generating code for you so you don't have to, but still generating code and not actually doing the work itself - and you'll avoid a lot of confusion.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top