SAS Macro coding

2020-03-31 02:50发布

问题:

I dont understand what is going on in my SAS code. The code behaves as expected, but only during the first time it compiles, i.e. the table called 'SummaryTable' shows the correct 'LogLik' value for each of the 3 iterations where the code triggers %mylogreg and %ModelsSummary. However, if I re-run the code then it behaves differently, the 'LogLik' value for the 3 iterations is the same and it is equal to the value for the last iteration. So, in some way the variable 'MyLogLik' saved the value of the third iteration during the first time the code is compiled.

Fist data generation step:

data ingots;
input heat soak r n @@;
datalines;
7 1.0 0 10   7 1.7 0 17   7 2.2 0  7   7 2.8 0 12   7 4.0 0  9
14 1.0 0 31  14 1.7 0 43  14 2.2 2 33  14 2.8 0 31  14 4.0 0 19
27 1.0 1 56  27 1.7 4 44  27 2.2 0 21  27 2.8 1 22  27 4.0 1 16
51 1.0 3 13  51 1.7 0  1  51 2.2 0  1  51 2.8 0  1
;
run;

Then the macros:

%macro mylogreg(Num,param);

   title "variables are &param";
   proc logistic data=ingots des outest = parameters&Num noprint;
model r/n = &param;
run;

%mend;

%macro ModelsSummary(Count,Var,Param);
   proc sql;

   select _LNLIKE_ into:MyLogLik from parameters&Count;

   insert into SummaryTable (ModelNumber,ModelVariables,NumParameters, LogLik) values (&Count,"&Var",&Param, &MyLogLik);

   drop table parameters&Count;


   quit;
%mend;

Then my code:

proc sql;
   create table SummaryTable(
                      ModelNumber num,
                      ModelVariables varchar (500),
                      NumParameters num,
                      LogLik num
                      )
                                                           ;
quit;

data _NULL_;

 array a[2] $ (' heat' ' soak');
 length temp $500;

  /*Number of Variables*/
  n = dim(a);

  /*k tell us the number of variables in aech group of all possible conbination*/
  do k=1 to n;

/*total tells the number of different convinations given that we form groups of k variables*/ 
total = comb(n,k);

do j=1 to total;

    /*allcomb is the SAS function which forms all different combinations*/ 
    call allcomb(j,k,of a[*]);
    /*This counter show the total number of convinations for all ks*/
    Counter + 1;

    do i = 1 to k;
        if i = 1 then temp = '';
        /*this temp string contains the explanatory variables to be passed to the logistic reg*/
        temp=catt(temp,a[i]);
    end;

    /* NumParam shows the number of parameters in the logistic model, including the intercept*/
    NumParam = k + 1;

    /* First we call the logistic regression macro, and the we fill out the table summarizing the important results*/
    call execute('%mylogreg('||Counter||','||temp||')');
    call execute('%ModelsSummary('||Counter||','||temp||','||NumParam||')');


  end;

 end;

run;

Then the problem is that if I re-run the code, then the values for 'LogLik' in 'SummaryTable' will all be the same, and the value corresponds to the third model. As I said it before, the first time i run the code, the loop works as expected and each model has its corresponding 'LogLik' value in 'SummaryTable'. I have checked that %ModelSummary is getting passed the correct values for the variable 'Counter' (this can also be confirmed in the SQL output), and it seems correct. So, I beleived that what I need is to clean the value of 'MyLogLik' after the main code is finished or something like that.

Can someone point me out what I am missing? I dont see what is wrong in my code!

回答1:

Short answer is try wrapping your macro call inside %NRSTR(). This will be a long explanation. But the macro language is hard. And the timing of call execute when used to call macros makes it trickier. Most of below is what I learned from many SAS-L posts/papers/discussions by Ian Whitlock. If you google "Ian Whitlock Call Execute" you will find much better explanations than I can provide, but here is a shot.

Here is an example of a simple macro that works. It uses data step PUT statements to write a list of names to the log. PROC SQL is used to generate the list of names and store it in a macro variable &list. Note that the macro variable is declared to be local to the macro.

%macro ListNames(sex=F);
   %local list;
   proc sql noprint;
     select name into :List separated by " "
     from sashelp.class
     where sex="&sex"
   ;
   quit;

   data list;
     list="&list";
     put "Inside Macro " list=;
   run;

%mend ListNames;

Here is the log from calling the macro twice (without call execute):

20         %listNames(sex=F)
Inside Macro list=Alice Barbara Carol Jane Janet Joyce Judy Louise Mary
NOTE: The data set WORK.LIST has 1 observations and 1 variables.
21         %listNames(sex=M)
Inside Macro list=Alfred Henry James Jeffrey John Philip Robert Ronald Thomas William
NOTE: The data set WORK.LIST has 1 observations and 1 variables.

Here is an example of calling the macro twice using call execute that does not work. Similar to the way your code does not work. First is the code, then the log, then my explanation:

data _null_;
  input sex $1;
  call execute('%ListNames(sex='||sex||')');
  cards;
F
M
;
run;

%put OUTSIDE MACRO list=&list; 

%*cleanup;
%symdel List;

LOG:

30         data _null_;
31           input sex $1;
32           call execute('%ListNames(sex='||sex||')');
33           cards;
NOTE: CALL EXECUTE generated line.
36         ;
1         + proc sql noprint;
1         +                        select name into :List separated by " "      from 
sashelp.class      where sex="F"    ;
1         +
    quit;  
1         +
data list;      list="";      put "Inside Macro " list=;    run;

Inside Macro list= 
NOTE: The data set WORK.LIST has 1 observations and 1 variables.

2         + proc sql noprint;
2         +                        select name into :List separated by " "      from 
sashelp.class      where sex="M"    ;
2         +
    quit;  

2         +
data list;      list="";      put "Inside Macro " list=;    run;

Inside Macro list= 
NOTE: The data set WORK.LIST has 1 observations and 1 variables.

37         run;
38         %put OUTSIDE MACRO list=&list;
OUTSIDE MACRO list=Alfred Henry James Jeffrey John Philip Robert Ronald Thomas William
39         

Explanation:

Notice that there are no warnings or errors in the log, yet the code did not "work". The value of &list inside the macro is always empty. And interestingly, even though the macro declares &list to be local, &list is ultimately created as a global macro variable. Surprising?

The job of call execute, and the macro language, is to generate code. When you use call execute to invoke a macro (as above), the macro executes and generates whatever SAS code, and dumps it on the input stack. All of code is generated before any of the code is executed.

In the example above, the PROC SQL step is generated, and the DATA LIST step is generated. But the DATA LIST step is generated before the PROC SQL step has executed! Normally, would think the PROC SQL step would be executed, and would populate &list, and then the DATA LIST step would be compiled and executed. But remember that this macro was invoked by CALL EXECUTE, inside of a DATA STEP that is still running. SAS cannot execute PROC SQL at the same time as the main DATA STEP is executing (ignoring newer DOSUBL function and similar). So at the time the DATA LIST code is generated, the macro variable &list has not been populated. It is null. If the macro did not have a %local statement, I would get a warning about macro variable not resolving (as you did).

So why does the macro variable resolve outside of the macro (returning list of males)? Note that the code generated by the macro is actually executed outside of the macro. That is, call execute invoked the macro, but the code generated is simply put on the input stack. When it executes, it is in open code. So it generates a global macro variable. Note you can see all the code generated by CALL EXECUTE as it is prefaced with + in the log.

Solution is to wrap the macro trigger in %NRSTR(). When you do this, call execute will not actually invoke the macro. But it will generate the macro call, and put the macro call on the input stack. Then when the macro executes, the PROC SQL step will be executed before the DATA LIST step.

Here's code:

data _null_;
  input sex $1;
  call execute('%nrstr(%%)ListNames(sex='||sex||')');
  cards;
F
M
;
run;

And the log:

49         data _null_;
50           input sex $1;
51           call execute('%nrstr(%%)ListNames(sex='||sex||')');
52           cards;

NOTE: CALL EXECUTE generated line.
55         ;
1         + %ListNames(sex=F)

Inside Macro list=Alice Barbara Carol Jane Janet Joyce Judy Louise Mary
NOTE: The data set WORK.LIST has 1 observations and 1 variables.

2         + %ListNames(sex=M)

Inside Macro list=Alfred Henry James Jeffrey John Philip Robert Ronald Thomas William
NOTE: The data set WORK.LIST has 1 observations and 1 variables.

%NRSTR() is used to hide the macro trigger from CALL EXECUTE. Note that call execute only generates the two macro calls (shown in log with + prefix). It does not actually execute the macros. The macros are executed after the data step that used CALL EXECUTE. So the code generated by the macros executes as one would hope (the PROC SQL step is executed before the DATA LIST step is compiled and executed).

Key point is that when you use CALL EXECUTE to invoke a macro, if that macro uses DATA STEP or PROC SQL code to generate macro variables, it's a good idea to use %NRSTR() to delay the execution of the macro.

HTH



回答2:

It doesn't jump out what, exactly is wrong. Are you getting any ERRORS when you run? The fact that it runs the first time and not the second tells me there might be something more than scoping of a macro variable.

You can declare MyLogLik local in %ModelsSummary with %local MyLogLik;.



回答3:

I think you're running into issue's with how call execute is working and when the macro variables are resolving. My reasoning for that is if you call the macro individually via %ModelsSummary(..) the error does not occur. I don't know why it's occurring and hopefully someone has a better response for you. A workaround is to use unique macro variables, i.e. add &count at the end of MyLogLik macro variable.

%macro ModelsSummary(Count,Var,Param);
   proc sql;

   select _LNLIKE_ into :MyLogLik&count from parameters&Count;

   insert into SummaryTable (ModelNumber,ModelVariables,NumParameters, LogLik) values (&Count,"&Var",&Param, &&MyLogLik&count);

   drop table parameters&Count;


   quit;
%mend;