SAS:从数据步循环中调用宏

SAS: Calling macro from within a Data step loop

为了重构一个程序,我采用了一个我想抽象的复杂过程并将其放在一个宏中。

%macro BlackBox();
  data _null_;
    put "This represents a complex process I want to abstract.";
  run;
%mend;

该过程需要连续多次发生,因此显而易见的解决方案是将其置于循环中。

data _null_;
  do i = 1 to 3;
    %BlackBox();
  end;
run;

但是,这会产生以下错误。

ERROR 117-185: There was 1 unclosed DO block.

这是怎么回事?

我最好的猜测是 SAS 正试图 运行 一个数据步骤中的一个数据步骤。

我发现可以通过将循环包含在宏中然后立即调用宏来避免此错误。

%macro PerformDoLoop();
  %do i = 1 %to 3;
    %BlackBox();
  %end;
%mend;
%PerformDoLoop;

所有这些似乎都是处理基本编程任务的迂回方式。我希望更多地了解数据步骤方法失败的原因将使我深入了解如何更优雅地完成此任务。

请理解,这是用于说明我遇到的错误的简化示例。宏的真实实例可以接受参数或 return 值。

您的假设完全正确; SAS 试图在一个数据步骤中执行一个数据步骤,当然这不会去任何地方(好吧,这是可能的,但只是......复杂)。

宏循环方法是完全合理的,我认为其他编程语言基本上就是你会做的。你用C#写了一个方法draw_box在屏幕上显示一个框,然后你写了一个方法draw_three_boxes通过调用3次draw_box在屏幕上显示三个框。

现在,看起来很愚蠢的原因是你的程序设计不好,因为 draw_three_boxes 方法非常有限:它只能做一件事,画三个盒子,所以你为什么不只是让原来的 draw_box 方法首先做到这一点?

想必你应该想写draw_box然后写draw_boxes(int count, int xpos, int ypos)之类的吧?同样的事情在这里。您不应该像以前那样编写 PerformDoLoop() 宏,因为您正在对执行循环的次数进行硬编码。

相反,弄清楚你为什么 运行 三遍。如果它真的是你刚刚知道的东西而不是一条数据,那么写%PerformDoLoop(count=)然后调用%PerformDoLoop(count=3)。或者在原始宏中包含 %do 循环,带有一个参数 count,默认为一个。

更有可能的是,出于数据驱动的原因,执行了 3 次。你有 3 个状态。您有 3 类 名学生。任何。使用它来生成对 %BlackBox 的调用。这会给你最好的结果,因为那样你就不会在程序中这样做——你的数据发生变化,你会立即得到 2 或 4 或任何调用。

您可以查看我最近在 SESUG 2016 上发表的论文 Writing Code With Your Data,了解有关如何做到这一点的更多信息。

宏语言是预处理器。它生成 SAS 代码,并在 DATA 步代码编译之前执行。使用您的代码:

data _null_;
  do i = 1 to 3;
    %BlackBox();
  end;
run;

宏 %BlackBox() 将执行一次(不是三次,因为它在 DO 循环执行之前执行,概念上在 DO 循环之外)。而数据步骤代码变为:

data _null_;
  do i = 1 to 3;
    data _null_;
    put "This represents a complex process I want to abstract.";
    run;
  end;
run;

正如您所说,在 SAS 中不可能在另一个数据步骤中执行一个数据步骤。第 3 行的 data _null_ 结束第一个数据步骤,将其留在未关闭的 do 块中。

我同意@Joe 的观点。如果要生成多个宏调用,使用宏 %DO 循环是一个很好的方法。他的论文提供了一种使用数据生成宏调用的好方法,方法是构建解析为宏调用列表的宏变量。

另一种有用的学习方法是 CALL EXECUTE。这允许您使用数据步骤来生成宏调用。 CALL EXECUTE 在数据步执行时生成宏调用,并且宏将在数据步之外执行(当您使用 %NRSTR 时,如下所示)。例如:

data _null_;
  do i = 1 to 3;
    call execute ('%nrstr(%BlackBox())');
  end;
run;

将生成三个宏调用:

NOTE: CALL EXECUTE generated line.
1   + %BlackBox()
MPRINT(BLACKBOX):   data _null_;
MPRINT(BLACKBOX):   put "This represents a complex process I want to abstract.";
MPRINT(BLACKBOX):   run;

This represents a complex process I want to abstract.

2   + %BlackBox()
MPRINT(BLACKBOX):   data _null_;
MPRINT(BLACKBOX):   put "This represents a complex process I want to abstract.";
MPRINT(BLACKBOX):   run;

This represents a complex process I want to abstract.

3   + %BlackBox()
MPRINT(BLACKBOX):   data _null_;
MPRINT(BLACKBOX):   put "This represents a complex process I want to abstract.";
MPRINT(BLACKBOX):   run;

This represents a complex process I want to abstract.