SAS宏编码

SAS Macro coding

我不明白我的 SAS 代码中发生了什么。代码的行为符合预期,但仅在第一次编译期间,即调用 'SummaryTable' 的 table 显示代码触发 %mylogreg 和 3 次迭代中每一次的正确 'LogLik' 值%模型总结。但是,如果我重新运行代码,那么它的行为会有所不同,3 次迭代的 'LogLik' 值是相同的,它等于最后一次迭代的值。因此,变量 'MyLogLik' 以某种方式保存了代码第一次编译期间第三次迭代的值。

拳头数据生成步骤:

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;

然后是宏:

%macro mylogreg(Num,param);

   title "variables are ¶m";
   proc logistic data=ingots des outest = parameters&Num noprint;
model r/n = ¶m;
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;

然后我的代码:

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

data _NULL_;

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

  /*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;

那么问题是,如果我重新运行代码,那么'SummaryTable'中的'LogLik'的值将都是相同的,并且该值对应于第三个模型。正如我之前所说,我第一次运行代码时,循环按预期工作并且每个模型在 'SummaryTable' 中都有其对应的 'LogLik' 值。我检查过 %ModelSummary 是否为变量 'Counter' 传递了正确的值(这也可以在 SQL 输出中得到确认),它看起来是正确的。所以,我相信我需要的是在主代码完成或类似的事情之后清除 'MyLogLik' 的值。

有人能指出我遗漏了什么吗?我没有看到我的代码有什么问题!

它跳不出什么,完全是错误的。 运行 时是否出现任何错误?它 运行 是第一次而不是第二次的事实告诉我,可能不仅仅是宏变量的作用域。

您可以使用 %local MyLogLik;%ModelsSummary 中声明 MyLogLik 本地。

我认为您 运行 对调用执行的工作方式以及宏变量何时解析存在疑问。我的理由是,如果您通过 %ModelsSummary(..) 单独调用宏,则不会发生错误。 我不知道为什么会这样,希望有人能为您提供更好的答复。 解决方法是使用唯一的宏变量,即在 MyLogLik 宏变量的末尾添加 &count。

%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;

简短的回答是尝试将您的宏调用包装在 %NRSTR() 中。这将是一个很长的解释。但是宏语言很难。而用于调用宏的 call execute 的时机使其变得更加棘手。以下大部分内容是我从 Ian Whitlock 的许多 SAS-L posts/papers/discussions 中学到的。如果你 google "Ian Whitlock Call Execute" 你会发现比我提供的更好的解释,但这里有一个镜头。

这里是一个简单的宏的例子。它使用数据步骤 PUT 语句将名称列表写入日志。 PROC SQL 用于生成名称列表并将其存储在宏变量 &list 中。注意宏变量被声明为宏的局部变量。

%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;

这是两次调用宏的日志(没有调用执行):

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.

这里是使用 call execute 调用宏两次的示例,不起作用。类似于您的代码不起作用的方式。首先是代码,然后是日志,然后是我的解释:

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

%put OUTSIDE MACRO list=&list; 

%*cleanup;
%symdel List;

日志:

30         data _null_;
31           input sex ;
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         

解释:

请注意,日志中没有警告或错误,但代码却没有 "work"。宏中 &list 的值始终为空。有趣的是,即使宏声明 &list 是本地的,&list 最终还是作为全局宏变量创建的。惊喜吗?

call execute 和宏语言的工作是生成代码。当您使用 call execute 调用宏时(如上所述),宏将执行并生成任何 SAS 代码,并将其转储到输入堆栈中。 所有代码都是在执行任何代码之前生成的。

上例中生成了PROCSQL步,生成了DATA LIST步。但是 DATA LIST 步骤是在 PROC SQL 步骤执行之前生成的!通常,会认为 PROC SQL 步骤将被执行,并填充 &list,然后 DATA LIST 步骤将被编译和执行。但是请记住,这个宏是由 CALL EXECUTE 调用的,在一个仍然是 运行 的数据步骤中。 SAS 无法在执行主数据步的同时执行 PROC SQL(忽略较新的 DOSUBL 函数和类似函数)。所以在生成 DATA LIST 代码时,宏变量 &list 还没有被填充。它是空的。如果宏没有 %local 语句,我会收到有关宏变量未解析的警告(就像您所做的那样)。

那么为什么宏变量在宏外解析(返回男性列表)?请注意,宏生成的代码实际上是在宏之外执行的。也就是说,call execute 调用了宏,但生成的代码只是简单地放在输入堆栈中。当它执行时,它是开放代码。所以它生成了一个全局宏变量。请注意,您可以看到 CALL EXECUTE 生成的所有代码,因为它在日志中以 + 开头。

解决方案是将宏触发器包装在 %NRSTR() 中。当您这样做时, call execute 实际上不会调用宏。但它会生成宏调用,并将宏调用放入输入堆栈。那么宏执行时,PROCSQL步会在DATA LIST步之前执行。

代码如下:

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

和日志:

49         data _null_;
50           input sex ;
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() 用于对 CALL EXECUTE 隐藏宏触发器。请注意,call execute 仅生成两个宏调用(在带有 + 前缀的日志中显示)。它实际上并不执行宏。宏在使用 CALL EXECUTE 的数据步之后执行。因此,宏生成的代码会按预期执行(PROC SQL 步骤在编译和执行 DATA LIST 步骤之前执行)。

重点是当你使用CALL EXECUTE调用宏时,如果该宏使用DATA STEP或PROC SQL代码生成宏变量,最好使用%NRSTR()来延迟宏的执行。

HTH