【问题标题】:Passing an equals sign to a macro in SAS将等号传递给 SAS 中的宏
【发布时间】:2016-08-04 00:44:20
【问题描述】:

我正在尝试在数据步骤中生成代码,然后将代码传递到一个宏中,然后运行该代码。我知道这有点迂回,但我想不出更好的解决方案,因为我的代码内容是基于数据集中的内容。

在我的数据集“test2”中,我只有一个变量“statement”的观察值,它等于

j1=输入(j,anydtdtm.);下降 j;重命名 j1=j; k1=输入(k,anydtdtm。);下降 k;重命名 k1=k; l1=输入(l,anydtdtm。);下降 l;重命名 l1=l;

我有一个宏,基本上是

%macro dummy(ds,statements);
    data &ds.2;
        set &ds.;    
        &statements.
    run;
%mend;

然后我使用调用执行到以下内容:

data test3;
    set test2;
    call execute('%dummy('||strip(ds)||','||strip(statement)||')');
run;

但我收到以下错误:

ERROR: The keyword parameter J1 was not defined with the macro.

显然 SAS 将我的“=”符号解释为宏变量的内容以外的内容。我尝试使用 %str 并将我的调用执行更改为:

data test3;
    set test2;
    call execute('%dummy('||strip(ds)||','||%str(strip(statement))||')');
run;

但它没有用。有人有什么想法吗?

感谢您的帮助!!

【问题讨论】:

  • 您似乎正在尝试自动转换类型?使用动态重命名语句,数据步骤中的数组可能会更好?如果你单独传递变量名并且有一个系统,那么宏应该会生成其余的代码。
  • 我正在自动转换,但在确定要转换哪些变量之前,我必须遍历整个数据集
  • @JonathanWilson 您正在研究我所说的“已解决的问题”,因为我们很多人都遇到过同样的问题。如果您想要关于如何减少迂回的建议,请发布另一个问题,其中包含有关您实际在做什么的更多详细信息 - 在您到达之前它是什么样子上面的变量。最终被用来填充它的东西。有无数种方法可以解决这个问题;你的不错,但有更好的,我很乐意给我的(我相信其他人也会)。
  • 请保持原样;这是宏引用的一个很好的例子和一些绕过它的方法。
  • @Joe 感谢您的提示!我并没有真正想到要询问您对“如何”问题的所有想法,但这真的很酷!我会发布另一个问题

标签: parameters macros sas call execute


【解决方案1】:

首先,如果您使用命名参数,这不是问题。

%macro dummy(ds=,statements=);
    data &ds.2;
        set &ds.;    
        &statements.
    run;
%mend;


data class;
  set sashelp.class;
run;

data fixes;
  infile datalines truncover;
  length statement $40 all_statements $512 execstr $1024;
  do _n_ = 1 to 2;
      input @1 statement $40.;
      all_statements=catx(';',all_statements,statement);
  end;
  put all_statements;
  execstr = cats('%dummy(ds=class,statements=',all_statements,';)');
  call execute(execstr);
  datalines;
if sex='M' then m_height=height
if sex='F' then f_height=height
;;;;
run;

然后 SAS 看到命名参数等号并知道从它到下一个逗号的所有内容都是该参数的值。

当然,如果你有逗号,你仍然需要做一些事情。这是你接近但不完全的地方。 %str 需要引用宏调用,而不是宏调用的构造 - 换句话说,它需要在引号内。

data fixes;
  infile datalines truncover;
  length statement $40 all_statements $512 execstr $1024;
  do _n_ = 1 to 2;
      input @1 statement $40.;
      all_statements=catx(';',all_statements,statement);
  end;
  put all_statements;
  execstr = cats('%dummy(ds=class,statements=%nrstr(',all_statements,';))');
  call execute(execstr);
  datalines;
if sex in ('M','F') then mfheight=height
if sex='F' then f_height=height
;;;;
run;

我个人喜欢这里的%nrstr,因为它还消除了那些讨厌的和号和百分号,这可能有一些意义。

把它放在那里意味着当 SAS 运行调用 execute 时,它​​会传递 %str%nrstr 并引用正在发送的值。

当你在其他地方拥有它时:

execstr = cats('%dummy(ds=class,statements=',%nrstr(all_statements),';)');

被保护/引用的不是all_statements 中的文本,而是字符all_statements(换句话说,变量名)。这对你并没有多大作用。

【讨论】:

  • 不确定创建命名参数与这个问题有什么关系。您可以使用参数名称调用宏,即使对于定义为位置的参数也是如此。 %macro dummy(statement)... %dummy(statement=...)
  • 没错,但我倾向于始终遵循其中一个。我想这可能是混合什么时候有意义的一个例子。
  • @Joe 我不认为我真的明白到底发生了什么 re:使用命名参数和位置参数之间的区别(这是引用它们的正确方法吗?用 = 符号命名和位置没有?)。无论如何,我接受了你的解决方案,我想我改进了一点。你能看看我的解决方案并评论它是否有意义吗?
  • @JonathanWilson 用等号命名参数,是的 - 意味着当您调用宏时,您必须按名称使用它们(%macro_name(param1=A, param2=B)),但您可以按任何顺序放置它们,而不是位置参数去掉等号,它按位置分配参数 (%macro_name(A,B))。汤姆是对的,即使指定了位置,用户仍然可以使用param1=A 方法。
  • 好的,我明白现在发生了什么。我花了一秒钟,但我将其更改为命名参数,并将“ds=”和“statements=”添加到我的调用执行中,它解决了所有问题
【解决方案2】:

传递等号很简单,只需在调用中使用命名参数即可。

%dummy(ds=x,statement=x=2)

传递分号真的很难。为此,您需要引用宏调用中的值。您可以使用宏引用来做到这一点,但我发现使用普通引号并在宏代码中删除它们更容易。

使用DEQUOTE() 函数让宏删除可能围绕参数值的所有引号。

%macro dummy(ds,statements);
data &ds.2;
  set &ds.;    
  %sysfunc(dequote(&statements))
run;
%mend;

让我们将您的示例语句设置为数据集。

data have;
  ds='x';
  statements=
   'j1=input(j,anydtdtm.); drop j; rename j1=j;'
|| 'k1=input(k,anydtdtm.); drop k; rename k1=k;'
|| 'l1=input(l,anydtdtm.); drop l; rename l1=l;'
  ;
run;

还有一些用于这些语句的示例数据。

data x;
 j='01JAN1960';
 k='10FEB2010';
 l='2014-05-01';
run;

现在您可以使用您的数据集生成调用。使用QUOTE() 函数将语句括在引号中。

options mprint;
data _null_;
  set have ;
  call execute(cats('%nrstr(%dummy)(',ds,',',quote(trim(statements)),')'));
run;

这是日志。

1    + %dummy(x,"j1=input(j,anydtdtm.); drop j; rename j1=j;k1=input(k,anydtdtm.); drop k; rename
k1=k;l1=input(l,anydtdtm.); drop l; rename l1=l;")
MPRINT(DUMMY):   data x2;
MPRINT(DUMMY):   set x;
MPRINT(DUMMY):   j1=input(j,anydtdtm.);
MPRINT(DUMMY):   drop j;
MPRINT(DUMMY):   rename j1=j;
MPRINT(DUMMY):  k1=input(k,anydtdtm.);
MPRINT(DUMMY):   drop k;
MPRINT(DUMMY):   rename k1=k;
MPRINT(DUMMY):  l1=input(l,anydtdtm.);
MPRINT(DUMMY):   drop l;
MPRINT(DUMMY):   rename l1=l;
MPRINT(DUMMY):   run;

【讨论】:

  • 我觉得这确实解决了问题,但它是一个凌乱的方式,你不觉得吗?使用数据步引用解决宏引用问题?
  • 因为常规引号比宏引号更容易生成,即使在宏代码中也是如此,尤其是在数据步骤内部。如果用户传入的语句仅被宏引用为 DEQUOTE() 函数,则定义的宏将正常工作,不会对未引用的值执行任何操作。
  • 我不确定一个比另一个更容易,是吗?除了宏引用避免需要dequote()?
  • 为什么dequote()%unquote() 更难?也无需担心引用值中的 % 或括号。
【解决方案3】:

这是在文件中生成代码然后使用 %include 语句提交可能更容易的情况之一:

filename tempsas temp;
data test3;
  set test2;
  file tempsas;
  put
    'data ' ds +(-1) '2;' /
    '  set ' ds ';' /    
    '  ' statements /
    'run;'
    ; 
run;

然后您可以开始查看文件(在编辑窗口中使用“include tempsas”命令),提交一个数据步骤以查看是否一切顺利,当您确定一切正常时,您输入

%include tempsas;

在您的原始代码中。

【讨论】:

    【解决方案4】:

    在这里阅读 cmets 后(感谢您的帮助!),我想出了一个不同的解决方案(尽管更改我的宏以接受命名参数也可以):

    data test3;
        set test2;
        statement=tranwrd(statement,"=",'%nrstr(=)');
        call execute('%dummy('||strip(ds)||','||strip(statement)||')');
    run;
    

    基本上,我只是将“代码”更改为如下所示:

    j1%nrstr(=)输入(j,anydtdtm.);下降 j;重命名 j1%nrstr(=)j; k1%nrstr(=)输入(k,anydtdtm.);下降 k;重命名 k1%nrstr(=)k; l1%nrstr(=)输入(l,anydtdtm.);下降 l;重命名 l1%nrstr(=)l;

    我注意到如果我将宏更改为

    %macro dummy(ds=,statements=);
    data &ds.2;
      set &ds.;    
      &statements.
    run;
    %mend;
    

    然后这个解决方案不起作用。它说“找到的位置参数比定义的多”。任何想法为什么?

    【讨论】:

    • 错误信息说明出了什么问题。您将宏参数定义为已命名,然后在没有名称的情况下调用它。
    • 公平地说,这是更令人困惑的 SAS 错误消息之一,因为您必须知道理解修复的含义:)
    • 我会注意到你当然可以%nrstr 等号,但是你在那里浪费了两个字符(如果你只引用一个等号,%str 会做同样的事情)和我的 $0.02 我发现 %nrstr 整个字符串更有用,这样如果你有逗号或类似的,它就不会中断。
    • @Joe 但是我的语句中确实有逗号,为什么它仍然有效?
    • @JonathanWilson 我应该更具体,抱歉;未受保护的逗号(括号和引号保护它们,即告诉 SAS 等待匹配的逗号完成参数)。 IE %test(y,z); 失败,但 %test((y,z)); 工作,假设 %test 采用一个参数。
    【解决方案5】:

    所以宁愿将语句传递给宏,因为我假设您也在生成要传递给宏的语句,所以我只会传递变量名,原始的和期望的并使用数组。

    %macro converter(var_in= , var_out=);
        data want;
          set data;
          array have(*) &var_in;
          array want(*) &var_out;
    
          do i=1 to dim(have);
             want(i)=input(have(i), anydtdtm.);
          end;
          drop &var_in;
         run;
     %mend;
    
    
     %converter(var_in= j1 k1 l1, var_out= j k l);
    

    编辑:基于 cmets 的版本 2。为了简化过程,我将变量重命名为 temp1-temp{numvars},然后将它们重新编码为所需的变量。这已成功测试。

    %macro converter(var_in= j k l);
    %let n_vars = %sysfunc(countw(&var_in));
        data want;
          set data (rename = (%do i=1 %to &n_vars;
                                %scan(&var_in, &i)=temp&i
                                %end;));
          array have(*) temp1-temp&n_vars.;
          array want(*) &var_in;
    
          do i=1 to dim(have);
             want(i)=input(have(i), anydtdtm.);
          end;
          drop temp1-temp&n_vars;
         run;
     %mend;
    
     %converter(var_in= j k l);
    

    【讨论】:

    • 重命名部分怎么样?
    • 水滴在那里。我错过了对原始变量的重命名......我会看看实现起来有多么困难,但怀疑它的难度。
    • 为什么?这会是一个宏循环吗?
    • 它在循环什么?
    • 变量列表。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多