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
我不明白我的 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