0
votes

I would wish to invoke a macro inside my data step for determining what value should the new variable, that I am creating, receive.

I know that I don't necessarily need a macro for this, that I can just include the code in the data step. The problem is that the problem I am working with is quite convoluted, and there are more than 5 similar if-else statements. However, essentially only thing that changes is what goes inside the macro function.

Hence the need for the macro. Below is the code and the error log. I believe the problem should not be in the macro, I have tested it and it succesfully works. For some reason, though, it does not when invoking it in data step with reference to a variable and a local macro variable.

I read that it is because one should use %sysfunc or %execute call sort of things but I cannot use sysfunc with self-defined macros and execute call also didn't work, or at least I couldn't get it to work, because I want to assign the value of that macro to a temp local variable.

I.E. something like %let temp = execute call(CalculatePayment(args)) but this doesn't work.

%let preliminary_1 = 288;
%let preliminary_2 = 115;
%let preliminary_3 = 58;

data __temp__;
set sashelp.cars;
run;


%macro CalculatePayment(initialPayment, paymentLimit);

    %let temp = %sysfunc(min(&initialPayment,&paymentLimit));
    %let candidate = %eval(&temp. - 27);

    %if( &candidate >= 0) %then %let rValue =  &temp;
    %else %let rValue = 0;
    &rValue
%mend;


data _temp_;
set __temp__;
if weight <= 100 then do; 
    preliminary = 0;
end;

else if (find(Model,"2")) then do;
    %let fstCase = %CalculatePayment(weight,&preliminary_1);
    %let seCase = %CalculatePayment(weight,&preliminary_1);
    preliminary = %eval(&fstCase. + &seCase.);
end;
/*
else if (find(Model,"g") & find(Model,"1")) then do;
    %let fstCase = %CalculatePayment(weight,&preliminary_1);
    %let seCase = %CalculatePayment(0.2 * weight,&preliminary_2);
    %let thirCase = %CalculatePayment(0.8 * weight,&preliminary_3);
    preliminary = %eval(&fstCase. + &seCase. + &thirCase.);
end;
*MORE SIMILAR IF-STATEMENTS BELOW with the values to the macro changing;
*/
else do; preliminary = 0; 
end;

run;

Note here is a small snippet of the error log. I did not put it here fully because you can reproduce the same exact log by just running the code.

ERROR LOG:

54          %let fstCase = %CalculatePayment(weight,&preliminary_1);
ERROR: Argument 1 to function MIN referenced by the %SYSFUNC or %QSYSFUNC macro function is not a number.
ERROR: Invalid arguments detected in %SYSCALL, %SYSFUNC, or %QSYSFUNC argument list.  Execution of %SYSCALL statement or %SYSFUNC 
       or %QSYSFUNC function reference is terminated.
ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The condition was: 
       . - 27 
ERROR: The macro CALCULATEPAYMENT will stop executing.
55          %let seCase = %CalculatePayment(weight,&preliminary_1);
ERROR: Argument 1 to function MIN referenced by the %SYSFUNC or %QSYSFUNC macro function is not a number.
ERROR: Invalid arguments detected in %SYSCALL, %SYSFUNC, or %QSYSFUNC argument list.  Execution of %SYSCALL statement or %SYSFUNC 
       or %QSYSFUNC function reference is terminated.
ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The condition was: 
       . - 27 
ERROR: The macro CALCULATEPAYMENT will stop executing.
56          preliminary = %eval(&fstCase. + &seCase.);
ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The condition was: 
       + 
56          preliminary = %eval(&fstCase. + &seCase.);
                                                     _
                                                     22
ERROR 22-322: Syntax error, expecting one of the following: a name, a quoted string, a numeric constant, a datetime constant, 
              a missing value, INPUT, PUT.  
    
1
PROC FCMP which allows you to create a user defined function, is that an option here instead of macros? - Reeza

1 Answers

2
votes

Two issues with your approach.

First is that the macro processor does its work before the code is sent to SAS itself to be compiled and run. %LET statements in the middle of data step are going to execute before the data step is even compileed. Inserting %LET statements there is just going to confuse the programmer.

Second is calculations involving data should be done with SAS code, not macro code.

You can use macro code to help you generate the SAS code you need to run.

Looks like you are trying to do something like this.

First create a macro that generates statements that will work in a data step.

%macro CalculatePayment(initialPayment, paymentLimit, varname);
    temp = min(&initialPayment,&paymentLimit);
    if (temp - 27) >= 0 then &varname = temp;
    else &varname = 0 ;
    drop temp;
%mend;

Now you can call that macro inside your data step and the generated statements will become part of the data step.

%let preliminary_1 = 288;
%let preliminary_2 = 115;
%let preliminary_3 = 58;
options mprint;
data _temp_;
  set sashelp.cars ;
  if weight <= 100 then preliminary = 0;
  else if (find(Model,"2")) then do;
    %CalculatePayment(weight,&preliminary_1,fstCase)
    %CalculatePayment(weight,&preliminary_1,seCase)
    preliminary = fstCase + seCase;
  end;
  else preliminary = 0; 
run;

The MPRINT option will show you the SAS code your macro has generated for you.

1082  options mprint;
1083  data _temp_;
1084    set sashelp.cars ;
1085    if weight <= 100 then preliminary = 0;
1086    else if (find(Model,"2")) then do;
1087      %CalculatePayment(weight,&preliminary_1,fstCase)
MPRINT(CALCULATEPAYMENT):   temp = min(weight,288);
MPRINT(CALCULATEPAYMENT):   if (temp - 27) >= 0 then fstCase = temp;
MPRINT(CALCULATEPAYMENT):   else fstCase = 0 ;
MPRINT(CALCULATEPAYMENT):   drop temp;
1088      %CalculatePayment(weight,&preliminary_1,seCase)
MPRINT(CALCULATEPAYMENT):   temp = min(weight,288);
MPRINT(CALCULATEPAYMENT):   if (temp - 27) >= 0 then seCase = temp;
MPRINT(CALCULATEPAYMENT):   else seCase = 0 ;
MPRINT(CALCULATEPAYMENT):   drop temp;
1089      preliminary = fstCase + seCase;
1090    end;
1091    else preliminary = 0;
1092  run;