【问题标题】:SAS macro: loop to create variables [macro/quoting problem]SAS 宏:循环创建变量 [宏/引用问题]
【发布时间】:2017-01-09 19:08:19
【问题描述】:

基本上,我正在编写一个宏,它将我的输入表、输出表和变量列表作为参数。我的变量列表显示为单个参数,为此我使用空格字符作为分隔符。 我的宏应该将我的列表分成 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 = input(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 的宏变量“名称”,所有效果都相同。不选择与初始变量相同的名称似乎会导致代码显示 180 错误。

我想问题在于尝试声明一个变量,因为我知道我之前编写的一段类似的代码确实可以工作,而 datastep 是一个比较(也将变量列表中的缺失值转换为零):

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, [, ^=, {, |, ||, ~=.

【问题讨论】:

    标签: sas sas-macro


    【解决方案1】:

    这是 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;
    

    【讨论】:

    • 这正是我正在寻找的解释。我想在使用 %qscan 时格外小心,以确保潜在的其他用户不会“混淆”奇怪的名称,但我没有完全意识到让 %qscan 实际引用以避免任何意外的解决方案意味着什么。
    • 唯一临时变量的想法很好,但我实际上需要循环之外的所有变量。至于制作一个独特的循环: %do i = 1 %to %sysfunc(countw(&listvar,%str( )));我确实认为这要好得多,但由于我可能会与其他人共享这些宏,因此我更喜欢它们分解步骤以免混淆人们并使其更容易重复用于其他目的。
    • 我仍然有一个询问:如果我确实需要 %qscan 来避免错误的分辨率,我还能使用 : %unquote(&&var&i.._num) 我的猜测是否定的,因为取消引用会导致潜在的不需要的分辨率。我错了吗 ?如果没有,我将如何处理? (说实话,这是一个混乱的情况,但我只是想完全了解宏的工作原理以及可用于防止意外结果的工具)。不过,非常感谢您的第一个正确答案
    • 我想我已经找到了我上次审讯的答案,它似乎可以使用:%sysfunc(cat(&&var&i,_num))。问题真的是一个宏 sas 引用的宏变量不能被 SAS 编译器“自动”与另一段字符串连接(可能在这里使用了错误的术语,我仍然有点不清楚它是 SASmacro 还是 SAS 编译器,但我认为是后者)。但是使用函数 cat 可以解决问题,因为编译器会理解它的意图。 SAS 宏作为“元层”永远不会让我感到惊讶(和疲惫)。
    • 我认为你最后的评论是错误的。具有引用(屏蔽)值的宏变量可以以通常的方式连接到另一个值,因为即使使用隐藏字符,它仍然是文本。问题是宏语言应该在 sas 编译器获取它们之前的最后一步自动取消引用它们,有时这不会发生。使用 %sysfunc(catt()) 将强制取消引用,但 %unquote() 更清晰。有关更多详细信息,请参阅 Ian Whitlock 的优秀论文,例如www2.sas.com/proceedings/sugi28/011-28.pdf
    【解决方案2】:

    以下是否满足您的需求?我的猜测是您在单独的 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);
    

    【讨论】:

    • 您的答案是正确的,因为错误的根源是 %qscan 函数实现的引用,并且 %scan 可以很好地满足我的即时需求。您的简短版本是我打算为自己编写的,但出于各种原因(主要是我可能会共享代码,并且我确实需要为每个 var 提供单独的宏 var),我更愿意将其保留更长时间,但可能对其他用户更清晰。我将看一下您的 sql 版本,但在我看来,我无法选择要输入的变量,这是我需要的核心(某些 var 在我的初始数据集中是正确的字符)。跨度>
    • 我可以找到解决方法,例如首先创建一个仅包含我想要输入的 var 的数据集,但我发现它违背了即用型解决方案的目的。不过,感谢所有的想法和评论
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-23
    相关资源
    最近更新 更多