SAS 宏:循环创建变量 [macro/quoting 问题]

SAS macro: loop to create variables [macro/quoting problem]

基本上我正在编写一个宏,它将我的输入 table、输出 table 和变量列表作为参数。我的变量列表显示为单个参数,我使用 space 字符作为分隔符。 我的宏应该将我的列表分成 nbvar 宏变量,这些变量将包含我的 (SAS) 变量的名称。然后,我使用数据步将我的 (SAS) 变量从其原始字符格式输入为数值。

这是我的一段代码:

%macro convert_car_to_num(input,output,listvar);

/* First I split my list into nbvar variables named var&i
%qscan to avoid macro resolution of names, not really necessary here
but still works fine. My delimiter is space character, hence 
%str( ) in the %qscan*/

%let nbvar=%sysfunc(countw(&listvar));
%do i = 1 %to &nbvar;
    %let var&i=%qscan(&listvar,&i,%str( ));
%end;

/*Here is my data step. &&var&i_num is resolved just fine*/
data &output;
set &input;
%do i = 1 %to &nbvar;
    &&var&i.._num = input(&&var&i,BEST16.);
%end;
run;

%mend;

由于 &&var&i.._num 和 &&var&i 已解析,我希望我的代码可以工作,但我的日志显示:

varname_num

180

解析名称 "varname" 下划线。过了一会儿我发现:

错误 180-322:语句无效或使用顺序不正确。

这通常是分号放错位置的标准错误。但我知道我的宏变量已解决,因为 mprint 显示:

MPRINT(CONVERT_CAR_TO_NUM): varname_num = 输入(varname,BEST16.)

注意:由宏变量"VAR26".

生成的行

MPRINT(CONVERT_CAR_TO_NUM): 运行;

其中 varname 是我列表中第 26 个变量的正确名称,这表明分辨率工作得很好。

为了让我更难以理解,我将在同一段代码中指出:

&&var&i.. = input(&&var&i,BEST16.);

确实编译,即使它没有以预期的结果结束(变量仍然是 char)。

同样,同样的代码有:

&&var&i.._num = &&var&i;

也不编译。

我还测试了将我的宏变量的名称更改为 num_&&var&i 或 n&&var&i,或者甚至首先声明一个包含 &&var&i 的宏变量 "name",所有这些都具有相同的效果。没有选择与初始变量相同的名称似乎会导致代码显示 180 错误。

我想问题在于尝试声明一个变量,知道我之前写的一段类似的代码确实有效,数据步是一个比较(也将变量列表中的缺失值转换为零):

data &output;
set &input;
    %do i = 1 %to &nbvar;
        if &&var&i = . then &&var&i = 0;
    %end;
run;

但是对于同一段代码,如果我尝试创建一个新变量(同样使用任何名称,就此而言),写作:

if num_&&var&i = . then &&var&i = 0;

我发现自己的解析名称再次带有下划线,但现在指向以下错误:

错误 22-322:语法错误,应为以下之一:!、!!、&、(、*、**、+、-、/、;、<、 <=, <>, =, >, ><, >=, AND, EQ, GE, GT, IN, LE, LT, MAX, MIN, NE, NG, NL, NOTIN, OR, [ ^=, {, |, ||, ~=.

以下是否满足您的需求?我的猜测是你在单独的 if-then-do 语句中发生了太多事情:

%macro new(input,output,listvar);
data &output; set &input;
%do i=1 %to %sysfunc(countw(&listvar.));
    %let var=%scan(&listvar.,&i.);
        var&i._num = input(&var.,BEST16.);
%end;
run;
%mend;

%new(have,want,&listvar.);

如果您尝试为要循环的每个变量创建一个单独的宏变量,下面也可能会执行您想要的操作(尽管在这种情况下可能不值得这样做,但只是为其他应用程序展示了另一种有用的方法):

** put variables into dataset **;
proc sql noprint;
    create table vars
    as select name,type
    from dictionary.columns
    where upcase(libname)="WORK" and 
        upcase(memname)="HAVE" and
        type = "char";
quit; 

** create total count and separate macro variable for each variable **;
data _null_; set vars end=last;
    by name;
    i+1;
    call symputx('name'||strip(put(i,8.)), name);
        if last then call symputx('count',i);
run;

%put &count.;
%put &name1.;
%put &name2.;
%put &name3.;

** loop over each variable using the total count **;
%macro new(input,output);
data &output; set &input;
%do i=1 %to &count.;
    &&name&i.._num = input(&&name&i,BEST16.);
%end;
run;

%mend;

%new(have,want);

这是 SAS 无法自动取消引用值的问题。我学到的规则是,如果您的 SAS 代码(由 MPRINT 显示)看起来有效,但您遇到错误,请尝试取消引用。

在你的情况下,更改为:

%unquote(&&var&i.._num) = input(&&var&i,BEST16.);

使代码工作。当然,根据您的评论,您可能不需要 %qscan 这会引入有问题的引号字符。如果将其更改为 %scan,则不需要 %unquote(),因为它一开始就不会被引用。

也同意@Foxer 使用单个宏变量将 i_th 变量存储在列表中的方法。还建议制作这些 %local 变量以避免冲突。可能是这样的:

%macro convert_car_to_num(input,output,listvar);
  %local i vari;

  data &output;
    set &input;
    %do i = 1 %to %sysfunc(countw(&listvar,%str( )));
      %let vari=%scan(&listvar,&i,%str( ));
      &vari._num=input(&vari,best16.);
    %end;
  run;

%mend;