sas宏收集资料

更新时间:2024-05-11 20:00:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

SAS Macro编码的好习惯之一

能被大家都使用的SAS Macro编写指南

Guidelines on Writing SAS Macros for Public Use

原文地址:http://www2.sas.com/proceedings/sugi29/047-29.pdf

转载请注明出处:http://blog.sina.com.cn/s/blog_5d3b177c0100bjom.html

从优秀的源码中学习应该算是一条非常好的学习SAS的途径,我们经常在网上看到一些公开的且优秀的源码。今天我们就来讲一些SAS Macro编写的规则,有了这些规则,你的SAS代码将更容易被别人读懂,从而让更多的人学习和重用。 1 文档化

文档化一直是很重要的东西,特别是对SAS Macro,因为只有这样才有更多的人知道这些Macro如何才能正地确地执行,并且结果是他们所需要的。下面介绍由Whitney (1996)提出来的SAS Macro的文档模板:

上面这些信息可以根据实际情况选择性地列举出来。

其次,为了使代码更有可读性,我们还可以将功能、变量等名字取得更具清晰明白,从而让人能根据其名字就知道这个函数或变量是干什么用的。例如要创建一个重复的数据集,我们可以用PRINT_DUPS来命名这个数据集,而不是用

CheckData来命名,还有就是可以用SAS的保留字来作用后缀或前缀等,例如,SAS Macro里的数据集参数,我们可以用In_Data或Out_Data等,这样我们就很清楚地知道这是数据集,其它的如Keep、Where、Nobs等。一个非常重要的一点是命名一定要有连续性,也即风格要一致。

最后就是SAS Macro的函数或变量最好用描述性的名字命名,这样在后期的Debug或应用时都能帮助理解,例如一般不用i或j等一般性的变量来命名,取而代之以year,visit等来命名。

另外我们还可以加一些选项来对宏进行Debug,这些选项如MPRINT,MLOGIC和SYMBOLGEN等,以及用%put进行Debug。这一块我们讲专门介绍如何Debug Sas Macro。

2获取错误信息

我们可以被动地通过SAS自己的错误提示来得到错误信息,另一方面,我们可以主动地写一些代码来获得错误信息。当然我们很难预测并获取所有的错误信息,但是有一些错误是很标准化的,这些错误我们可以尽量自己获取。例如我们可以通过%LENGTH来判断宏变量是否被指定: %if %length(&data) = 0 %then %do ;

%put ERROR: Value for macro parameter DATA is missing ; %goto finish ; %end ; %finish:

这样,如果没有宏变量,则其length将为空或0,这样就会生成一条错误信息并结束过程。

另一个例子是判别是否指定的数据集存在: %let error = 0 ;

%if %sysfunc(exist(&data)) = 0 %then %do ; %put ERROR: data set &data does not exist ; %let error = 1 ; %end ;

%if &error = 1 %then %goto finish ;

%finish:

这些例子都比较简单,我们在应用于的时候可能会遇到更复杂的情况。其它的应用包括判别变量是否为字符型,是否库名或格式合法等等。为了让主Macro不显得太复杂,我们可以写一些子宏来完成这些功能。

还有一个比较好的习惯是我们可以预先赋给宏变量一些默认值,而不是完全由用户来赋值。并且我们还可以增加赋值的变化来减少输入值可能的错误,例如: %let debug = %upcase(&debug) ;

通过将debug全部转化为大写,从而用户可以输入任意的大小字母,对结果都无影响。另一个例子:

%let debug = %upcase( %substr(&debug,1,1 ) ) ;

我们只取第一个字母,且将其大写,这样,用户可能多输入几个字母,但对结果亦无影响。

3 减少一些可能引起歧议的句子

由于程序总会以程序员没有考虑到的方式运行,比如建立了一个已存在的数据集,这样就很可能将原来的数据集覆盖了。因为,一个简单的方式是我们建立数据集时,一定要先检查一下是否已存在这个数据集,另外,对于Macro生成的数据集,当我们不再需要它时,一定要将其删除。

另一个经常发生的问题是全局宏变量,在Macro中,其值被修改了: %let x = 5 ; %macro check ; %* %local x ; %let x = 1 ; %mend check ; %check

%put x = &x ;

这里的x为1而不是5。这就是因为全局宏变量x的值在宏check里被改为了1。 因此我们在创建全局宏变量时,一定要取一个比较唯一的名字,如前面加_,而局部宏时,我们最好用%Local显性定义。对于宏变量的范围问题前面已有讨论,这里就不再具体介绍了。

还有一个要注意的地方就是title和footnote用完后一定要注意将其关闭掉: title1 'Original title';

%macro test ; proc sql noprint;

select number, text into: num ,:text from dictionary.titles where number = 1; quit ;

%let num = # %let text = &text ;

title1 'Macro title';

title&num \%mend test;

这里最后的title1为'Original title'而不是'Macro title',因为先前定义的title1的值还被宏保存着。 4 好用

好用这个属性太重要了哈。我们介绍一下宏参数主要有两种方式:positional和keyword

%macro recode (data, out, var, groups );

%macro recode (data =, out =, var =, groups = ); 其引用方式为:

%recode(sales, salesr, revenue, 5)

%recode(data = sales, out = salesr, var = revenue, groups = 5 ) Keyword比positional有很多好处:

首先Keyword更直观,我们可以很清楚地知道sales是一个数据集,revenue为一个变量,从而减少错误

其次我们可以先定义一些默认值,例如

%macro recode(data=, out=, var, groups = 5) ; 我们就可以只对前三个变量进行赋值了

在写宏时,我们一定要让宏变量尽量少,因为宏变量太多就太复杂。如果宏变量确实多,那就看看能不能给一些变量赋予默认值。

写宏时另一个好习惯是对宏变量进行注释: %print_vars (data = , var = , nobs =

, byvar = )

另外,我们还要尽量减少没有%的宏,例如: options implmac ; data test ; a = 1 ; run;

%macro printdat(data) / stmt ; proc print data = &data ; run;

%mend printdat ; printdat test ;

这个宏看起来费劲,并且还没实现什么功能,并且宏里面没有一个%,所以这不是一个很好的编程习惯

5 权衡与思考

首先是当我们写宏时,也要考虑一下大家的实际情况,比如在第九版我们创建一个宏变量,可以简单地用call symputx ('count', count) ;实现,但在第6版时,完整代码是call symput ('count', trim(left(put(count,8.)))) ;,其它情况包括我们可能有graph qc or ets等产品,而有的却只有base,这时你写的代码可能就不会被这些人使用。

另一个宏的重要特征是robust,就是可以用到很多平台上,但这样会浪费我们很多时间,这时,我们最好根据实际情况来定是否需要写这样的宏。

最后是一些思考,供大家参考:

(1)对文档标准,变量命名习惯等要保持连续性

(2)在发布程序之前一定要完整地测试你的代码,而不能仅仅debug。要完整地测试其功能和效率。作者举了个例子,他写了个proc freq的宏,测试时没事,但用在大数据量操作时,就死掉了,结果只能重写。

(3)一定要跟你的客户有个良好的交流,了解客户真正需要什么功能,不然写一个没人用的宏是没有意义的

(4)要将公开的宏放在一个只读的文件夹下,这样就能避免对其不经意的更改 (5)定期重新看看你写的宏,这样可能会想到更好的方式来修改你的宏。

SAS Macro编码的好习惯之二 SAS宏设计问题(1) SAS Macro Design Issues

原文地址:www2.sas.com/proceedings/sugi27/p067-27.pdf

转载请注明出处:http://blog.sina.com.cn/s/blog_5d3b177c0100bmgv.html

我们已经学会了如何编写SAS宏代码,但什么时候我们应该用SAS宏来编码以解决我们的问题呢?这篇文章主要考虑了两个重要的问题:一个好的宏应该具备哪些特征,以及如何才能写出一个有用且清晰的宏。

1 关于宏变量

宏变量的定义有很多种方法,如%let等,前面的文章已有介绍,这里就不多说,我主要讲一下作者的一些比较好的编写宏的理念。下面从一个简单的宏赋值语句说起:

%let data = lib.mydata ;

这个语句就是生成一个宏变量data,其值为lib.mydata。在这里要提到的就是SAS宏的编译原理,这个在前面已有介绍。SAS宏编译器前所有的代码都当作文本来处理,因此上面的宏变量data的值lib.mydata是没有特殊意义的,因此也不用加双引号” ”。但是在SAS过程步编译器里,这些值是有意义的,比如lib.mydata中的lib是库名,mydata是表名。因此,另一个例子中: libname lib \

这里的库lib的值c:\\myproject\\dat是要加双引号的。

这里要提到两个好的习惯,其实前面的文章已有介绍:首先是选择变量名时,一定是通过这个名字就知道这个变量的功能,例如前面的data表示是数据,lib变量表示是库名等。其次,变量的值也要完整的给出来,这样增加程序可读性。

第一个好习惯大家应该都能理解,现在说一下第二个好习惯。这里我们举一个反例,例如日期是由月+空格+日+空格+年(DATE = \)组成,我们要转换成SAS日期(DATE = \),我们可以用sasdate = input ( date , date9. );这个input函数来完成。但在本例中,由于我们的是月日年,而SAS日期是日月年,因此我们先得将原来的DATE的内容换一下次序:

scan(date,2)||scan(date,1)||scan(date,3),因此可以用下面的解决方法: %let x = scan(date, ;

sdt=input(&x 2)||&x 1)||&x 3),date9.);

上面的代码可以实现我们将DATE转换成SAS日期格式的功能,但是代码却非常难读懂,甚至会觉得这代码是不是乱写的。我们很难清晰明了的知道sdt的值。引外,这里的变量x的名字也不好,根本不明白它的真正意义。

由于SAS过程步编译器对代码有很多限制,而SAS宏编译器将代码视为文本,因此,在编写SAS宏的时候更容易编写出一些不具可读性,但却能正确执行的代码。

至于SAS宏变量或参数的作用,这个大家都能理解。这里还提到一点,就是双引号和单引号在SAS宏编译时的区别:SAS宏编译的时候,如果你用单引号的话,那么word scanner就将引号内的文本作为单个token,因此此表达式将不进入宏相关的变量或工具,而双引号它自已变是token,因此它将作为宏相关的变量或工具。

另一个例子是当我们要对同一目录或同一服务器下的多个文件或目录进行操作时,最好能将这同一目录和其它文件分开引用,而不是单个引用。例如: filename in

\filename pgm

\libname stat

\

这里只有三个,可能会有更多,因为太多所以很容易引起错误,因此,我们可以定义两个变量,ROOT 和 DSNUM,这样,代码就成了: %let root = \%let dsnum = 1 ; filename in

\filename pgm \libname stat \

这个例子要注意几个要点,一是\这一名的两个点”.”,这个以前的文章也讲过,当库为引用,后面接文件名时,一定要用两个点。然后一点是为什么\或\后面不加点呢,因为“\\”已经起到点的作用了,并且让我们清楚地知道sasdat还是一个目录。下面是一个反例,来说明为什么用“\\”而不用点: %let root = \\\\rk2\\\\vol2302\\stat\\ ; filename pgm \

这里我们就不清楚progs到底是目录还是文件名。

另外,我们对于变量,有时称为变量,有时称为参数,那这两者有什么区别呢。其实参数呢,就是宏里面基本不变的一些变量,参数对宏来说有着重要的作用。而变量,则会经常变化其值,以满足宏的功能需求。

最后宏变量要提到一个函数”%put”,它的用途是将宏变量的值输出到log中,这对宏编程时debug非常有用。下面介绍一下几个常见的应用 %put data=&data ; 输出自定义宏变量data的值

%put _all_ ; 输出所有宏变量及其值 %put _user_ ; 输出用户自定义变量及其值 2 宏

宏就是参数化的SAS代码。下面是一个简单的例子 libname lib \%let data = lib.mydata ;

title3 \proc print data = &data ; run ;

我们可以将其定义为完整的宏:

%macro tprint (data=&syslast, tl=3) ; title&tl \proc print data = &data ; run ; title&tl ; %mend tprint ;

这个宏名字叫tprint,有两个参数data和tl。这里我们来说明一些编写宏的好习惯。

首先是宏名字要能明白地说明该宏的作用(如tprint为打印)

其次考虑好选哪些变量作为宏参数(不能太多,也不能太少),以及它们的名字(data和tl:清晰明白)

最后是要指定一些好的默认值(tl=3,因为3基本能满足大多数人需求,1太小,10太大)

另外,对于title,footnote一定要记得删掉。对于libname,我们一般在代码最开始就定义好,而不是在宏内定义。

下面讲一个重要的概念,就是SAS宏编译时的时间顺序 1. Macro compile time 宏编译时间 2. Macro execution time 宏执行时间

3. SAS compile time SAS过程步编译时间 4. SAS execution time SAS过程步执行时间

理解了宏编译的时间顺序,对于理解SAS宏代码及其功能有着重要的作用。

这里作者用了一个print宏的例子来讲解如何编一个好的程序,比较简单,我就不多作介绍了,里面提到一点是它说的jiggle test,就是每次改变一点点参数或代码,来看结果的变化,从而对宏进行调试和debug。

3 关于宏的选择语句:

那就是%if condition %then consequent ;语句。我们先来讲讲%if与if的区别吧,好像这两个都可以在SAS宏里用到,但是什么时候用呢,这就要用到上面说的SAS宏编译时的时间顺序了。%if是在宏编译时间和宏执行时间时用到,而if则是在SAS过程步编译时间和SAS过程步执行时间里用到。例如: %macro mktrans (type=A) ;

%if not %index(Monday Thrsday, &sysday) %then %do ;

%put Program can only be run ;

%put on Mondays and Thursdays ; %goto mexit ; %end ; ...

data trans ; set &sysday ;

%if %length(&type) > 0 %then %do ;

if upcase(type) = \ %end ; ... ..run ; %mexit:

%mend mktrans ;

首先所有的代码进入宏编译器编译,然后执行,这里我们假如倒数第六行中的\ data trans ; set &sysday ;

if upcase(type) =”Y” ... ..run ;

也即只剩下SAS过程步编译可以编译的内容了,这时再进行SAS过程步编译和SAS过程步执行,这里才会再执行if语句。其它的语句如%do与do等与此类似。

对于 if upcase(type) = \

其解释也是一样,一个是宏编译器里处理的内容,处理完后再交给SAS过程步编译器处理。具体的参见SAS编译原理

因此,作者给出的例子 if weekday(today())=2 then set Monday ; else

if weekday(today())=5 then set Thursday ; else do ;

put \ \ abort ; end ;

至少其在宏编译的时候是不会执行的。如果要让其在宏编译的时候执行,就要将代码改为:

%macro mktrans (day=1) ; ...

data trans ;

%if &day = 2 %then %do ;

set Monday ; %end ; %else

%if &day = 5 %then %do ;

set Thursday ; %end ; %else %do ;

put \ \ abort ; %end ; ... ..run ;

%mend mktrans ;

呵呵,我也经常这样改,因为改起来方便,但其实这个代码可读性也很差。因此作者对代码进行了进一步改进,其核心思想是:将SAS数据步编译的代码尽量放在一起,从而增加程序的可读性 %macro mktrans (day=1) ; %local data ;

%if &day = 2 %then %let data = Monday ;

%else

%if &day = 5 %then %let data = Thursday ; %else

%put Program can only be run ; %put on Mondays and Thursdays ; %goto mexit ; %end ; ...

data trans ; set data ; ... ..run ; %mexit:

%mend mktrans ;

作者再用宏函数%index对程序作了进一步改进: %macro mktrans () ;

%if not %index(Monday Thrsday, &sysday) %then %do ;

%put Program can only be run ;

%put on Mondays and Thursdays ; %goto mexit ; %end ; ...

data trans ; set &sysday ; ... ..run ; %mexit:

%mend mktrans ;

%index函数的作用是第二个参数里是否有第一个参数的值,如果没有则返回0。 另外作者还提到一个问题,就是将宏里的变量尽量定义为local。这个思想也很重要,前面的文章也讲过了,就不再重复。 4 宏循环 语法:

%do variable = firstvalue %to lastvalue %by increment ; ... %end ; 例如:

%macro mklist(root=ds,from=1,to=1) ;

%local i ;

%do i = &from %to &to ; &root&i %end ; %mend mklist ;

这里&root&i后面没有加分号,是因为这样我们就能产生一系列的变量如ds1,da2?

sas macro宏变量作用域(全局变量与局部变量)

转载请注明出处:

http://blog.sina.com.cn/s/blog_5d3b177c0100b3of.html SAS宏变量生成的方法:

%let %global %input %into(sql) %local %macro Symput symputn %windows

全局变量包括:

1 除syspbuff外的所有自动生成的变量 2 所有宏外生成的变量 3 %global定义的变量

4 Call symput生成的变量(这个最后一段详细解释)

局部变量包括: 1 所有的宏参数 2 %local 定义的变量

3 在宏内由%do %let等生成的变量

1 全局变量在宏内赋值,赋值能成功,且该变量依然为全局变量: 例:

%let new=inventry; %macro name1; %let new=report;

%mend name1;

New的值为report,且依然为全避变量。

2 在宏内用%local可强行生成局部变量: 程序:

%let new=inventry; %macro name1; %local new; &new =report; %put &new; %mend name1; %put &new; 结果:

Report(local) Inventory (global)

3 Call symput在最新的symbol table里创建变量,因此,如果: Call symput在proc sql之后创建的变量 Syspbuff已创建 当前宏包括%goto语句 则会生成局部变量。 例:

%macro env1(param1); data _null_;

x = ’a token’;

call symput(’myvar1’,x); run; %mend env1; %env1(10)

Myvar1为局部变量,因为宏包括完整数据步,且宏包括一个参数,故最新生成的为一个局部变量symbol table。

%macro env2(param2); data _null_; x = ’a token’;

call symput(’myvar2’,x); %mend env2; %env2(20) run;

Myvar1为全局变量。

%macro env3; data _null_; x = ’a token’;

call symput(’myvar3’,x); run;

%put ** Inside the macro: **; %put _user_;

%mend env3; %env3

Myvar1为全局变量。

%macro env4 /parmbuff; data _null_; x = ’a token’;

call symput(’myvar4’,x); run;

%put ** Inside the macro: **; %put _user_; %put &syspbuff; %mend env4; %env4

Myvar1为局部变量,由于生成了syspbuff为局部变量

sas macro宏变量引用

转载请注明出处:http://blog.sina.com.cn/s/blog_5d3b177c0100b3oe.html

SAS宏变量都放在sashelp.vmacro中,打开后可以看到这些变量的范围(scope)和值。自动生成的宏变量和自定义的宏变量都可以用&来引用。 下面用几个例子来说明如何引用宏变量: 1 直接引用: 程序:

%let dsn=Newdata; title1 \结果:

TITLE1 \2 与非宏变量字符结合

2.1 如果非宏变量字符在前,宏变量在后,则直接引用: 程序:

%let name=sales; data new&name; set save.&name; run; 结果:

DATA NEWSALES; SET SAVE.SALES; RUN;

2.2 如果非宏变量字符在后,宏变量在前,则引用后一定要加“.” :程序:

data &name1 &name2;(未加“.”,应为data &name.1 &name.2;) set in&name.temp;(加“.”) run; 结果:

DATA &NAME1 &NAME2;(引用错误) SET INSALESTEMP;(引用正确) RUN;

3 当进行层级引用,例如库名.表名时,库名为宏变量时,一定要在后面加两个“.” 程序:

set in&name..temp;(如果只有一个“.”,则结果参照2.2) set &name..&temp; 结果:

SET INSALES.TEMP; SET SALES.TEMP;

4 多级引用(非直接引用),例如&city&n的结果是city6,其结果也是一个宏变量,这里要得到这个宏变量的值,需要在前面再加一个&,因此在原表示式前也要加一个&。 程序: %put &city&n; %put &&city&n;

如果前面再多加N个&,根据SAS的编译原理,其效果与只加一个&一样。 即&&&city&n 与&&city&n一致。

sas macro宏编译原理

转载请注明出处:http://blog.sina.com.cn/s/blog_5d3b177c0100b3le.html

我们提交的SAS程序都先到input stack的地方,然后SAS就将这些程序转换成单个的token,这些token分为四类: Literal:主要是注释用的字符串

Number:数字,时间,日期,十六进制数字 Name:变量名

Special:符号,例如& % + - 等 SAS有四类处理这些token的编译器:

Data step compiler:处理数据步(或过程步?) SCL compiler:处理Sas Component Language Macro compiler:处理宏

Command compiler:处理命令(具体不清楚,可能类似X命令吧,直接调用Windows Dos命令)

图里面还有一个word scanner没介绍到,就是把input stack里的程序,逐行读取每一行的token,然后根据规则把这些token放到相应的编译器compiler里。

介绍完了图里的内容,然后开始看看SAS如何编译没有宏的数据步程序: Word scanner读取Input Stack里的第一行程序,这里可以读到4个Name(data sales drop lastyr)和4个Special((=);)。Word scanner读到的第一个非空token是Data,SAS就知道这个一个数据步的起点,然后将这些token放到Data step compiler里。Word Scanner继续读取token,直到读到Run,SAS就

知道这是这个数据步的终点。然后在data set compiler里对刚才读到的程序进行编译。

这里提一下,进入编译器的程序,SAS都会自动转换为大写,这也是为什么SAS对变量名不进行大小写区分的原因。

接下来介绍SAS宏编译:

首先介绍一下Symbol table,这是放宏变量的一个表,在sashelp.vmacro这张表里。当有新的宏变量时,就会记录在这张表中。

当word scanner扫到%或&开头,且后面为非空字符时,word scanner就将扫到的input stack里的程序放到Macro compiler里。

如图所示,第一行 %let file=in1;这里,SAS宏编译器就将把file放入symbol table里,其宏变量为file,值为in1。只要宏编译器在处理宏,那么data step compiler将不会进行编译。当宏编译完成后,word scanner继续读取下一行的token。当读取以&开头且后面为非空字符(token)时,宏编译器就检查这个token是否在symbol table中,然后用symbol table里的值代替这个token。最后读到这个数据步的终点,进行编译。

宏错误是如何产生的,如何避免并更正

Macro Bugs - How to Create, Avoid and Destroy Them

原文地址:http://www2.sas.com/proceedings/sugi30/252-30.pdf

转载请注明出处:http://blog.sina.com.cn/s/blog_5d3b177c0100c0xx.html

好的宏的开发比一般的简单的SAS代码开发要困难得多,以下原因使得更加难以对宏进行测试:

宏所生成的不同版本的SAS代码可能会出现错误

程序的错误可能是编写的代码引起的,也可能是宏生成的代码引起的

SAS将宏代码作为文本进行处理,因为我们更难以通过SAS系统对宏进行测试 对于不同的程序,你想要生成的SAS代码也不尽相同

SAS Macro Debug宏测试的一般原理是:首先要了解SAS宏的运行机理,并且如果出现错误,一定要想到是你的代码有问题。更具体地说:首先要看到哪里报了错,然后定位到这个错误,然后理解并修正这个错误。

除了SAS代码的一般错误外,SAS Macro宏变量在传递参数的过程中,也可能会产生错误。另外,对于SAS Macro宏编程来说,程序员要处理两种语言,SAS语言和宏语言,这两种语言的编译和执行的时间不一样,如果理解得不透彻也容易产生错误。

1 时间问题

SAS宏代码执行时有四个时间:

宏编译时间:%MACRO和%MEND之间的代码被读取 宏执行时间:宏编译后生成SAS代码

SAS编译时间:生成的SAS代码进行编译 SAS执行时间:编译后的SAS代码的执行

2 下面来讲一些时间问题的经典案例: 2.1 步骤边界问题:

%macro printplus ( data = ) ; title1 \ proc print data = &data ; format _all_ ; %nextstep()

%mend printplus ;

%macro nextstep ( data =sashelp.class ) ; title1 \ proc means data = &data ; run ;

%mend nextstep ;

%printplus( data = sashelp.class );

这里的运行结果是过程步print的title成了\Analysis Variables\而不是应该的\important print\,为什么呢,原因就是因为上面提到的执行时间的问题。当执行宏printplus时,首先是然后编译title1 \然后编译print过程步,这时,由于没有过程

步的边界run,因此,编译器继续编译宏%nextstep(),这时,SAS编译器将会中止,转换为宏编译器,编译宏nextstep并生成SAS代码,再继续由SAS编译器编译生成的SAS代码,这时,编译到title1 \时,title1的值就改变了,最后SAS代码执行时,就出现了非预期的结果。

解决的方法就是在format _all_ ;后面加run;这样就解决了边界的问题。

2.2 在数据步的宏指令时间问题 我们看一下下面的代码: data w ;

do obs = 1 to 10 ; if obs <= 5 then do ;

%let x = 1 ; end ; else do ;

%let x = 2; end ; y = &x ; output ; end ; run ;

我们是不是觉得这里的结果是前五个Y的值为5,后面的Y的值为0;但结果是Y的值都为0,为什么呢?因为我们在执行%let宏指令的时候,就已经进入了宏编译器,并且直到宏编译完,生成的代码就是先赋值5给宏变量x,然后赋值0给宏变量x,然后原理的SAS代码中的do循环其实已经为空,最后将宏变量x的值0赋给变量y,最后y的值就全为0了。 还要注意一点的就是,上面的程序虽然有错误,但是系统并没有提示有任何错误或警告,因此,在写SAS宏代码时一定要非常认真地测试程序

为了更好地说明SAS宏编译时间和SAS代码编译时间的问题,我们再看一下下面这个例子: data w ;

do obs = 1 to 10 ; if obs <= 5 then %let x = 5 ; else

%let x = 0 ; y = &x ; output ; end ; run ;

这里在SAS日志中会出现ERROR 160-185: 没有匹配的 IF-THEN 子句。

为什么呢?原因其实是一样的,执行到if then语句时,SAS编译中止,转而执行宏编译%let x = 5 ;而这里是赋值语句,最后不生成SAS代码,使得then后面没有SAS语句,因此就出现日志的错误。

所以,我们在%let后面分别加上一个分号,就能解决这个问题: data w ;

do obs = 1 to 10 ; if obs <= 5 then %let x = 5 ;; else

%let x = 0 ;; y = &x ; output ; end ; run ;

现在执行就不会再报刚才的错误了。

2.3 CALL SYMPUT的时间问题

CALL SYMPUT是一个非常重要的函数,特别是数据步时传递宏函数的值。我们看下面这个例子: data w ;

do obs = 1 to 10 ; if obs <= 5 then

call symput ( \ else

call symput ( \ y = &x ; output ; end ; run ;

这里日志报的错误是WARNING: 没有解析符号引用 X。也就是说宏变量x没有初始化,为什么呢?我们可以在do循环前再加一条语句%let x = Not initialized ;这时运行程序,发现可以运行,但得到的y的值为1,这又是为什么?

值为1的原因是因为编译器将Not initialized的值赋给了y,这时的y=1即y为true。而x的值没有初始化的原因是因为编译时,symput并未将值赋给x,因此y也没有值。

要让该程序运行,需要用到symget函数,它可以将宏变量的值赋给目标参数,如下程序所示: data w ;

do obs = 1 to 10 ; if obs <= 5 then

call symput ( \ else

call symput ( \

y = input ( symget ( \ output ; end ; run ;

因此,我们在写SAS宏时,一定要分清楚这四个时间,不要将SAS执行时才能得到的值赋

给SAS编译时就需要的变量,不要将SAS编译时的循环来控制宏编译时的语句。

2.4 单引号问题 下面这个例子:

%let root = c:\\project\\data ; filename in '&root\\stuff.dat' ;

这里,filename将不会被执行,因为SAS宏编译器并不能识别这里的&root,一个简单的解决方法就是将单引号改为双引号:filename in \;当我们只能用单引号时,我们就得应用宏引用宏数了:filename in %unquote(%str(%')&root\\stuff.dat%str(%')) ;

CALL EXECUTE时间问题 跟上一个问题差不多

2.5 If还是%if

在写SAS宏时到底该用If还是%if,这个问题经常让很多人困惑。这里说一下两者的区别:%if只能用于宏,它决定生成哪些SAS代码,而if必须用于数据步,在SAS代码执行时它决定哪些代码将被执行。 看下面这个例子

%macro bug ( dummy = ); data _null_ ; x = 1 ;

%if x = 1 %then %do ; put x= ; %end ; run ;

%mend bug ; %bug()

这里的%if应该不是我们需要的,因为我们并不需要决定是否生成put x= ;这句代码,而是要决定是否运行put x= ;这句代码。

3 执行宏的环境

这里最好看一看以前关于宏编译的文章。这里主要强调一下:尽量不要用全局宏变量,主要原因是全局宏变量的值很容易被无意修改,例如某个宏的局部宏变量与全局宏变量相同时,就会修改,这里再用这个全局宏变量时就会产生意想不到的效果。

SAS宏设计问题参见以前的文章。下面介绍本文的重点:

4 宏测试工具 %PUT statement MPRINT option MFILE option MLOGIC option

4.1 %PUT语句

这个太有用了,当要查看某个宏变量的值时,就用这个语句。例如%put var=>>>&var<<< ;就可以在日志中查看宏变量var的值。

这个语句在循环以及条件语句中特别有用,因为这里宏变量的值可能并不是预期的结果,这时如果将宏变量的值都输出到日志中,我们就能检查到程序是否按预期在进行。

%put语句常用到的系统变量有:_USER_, _LOCAL_, _GLOBAL_, _ALL_, _AUTOMATIC_。这个前面也有介绍。

4.2 MPRINT选项

这个选项是我经常用的,它可以将SAS宏生成的SAS代码输出到日志中,这样就可以看到SAS到底在执行哪些语句了。特别是当结果出现偏差时,我们得看宏是否按我们的预期产生代码。例如下面的程序出现的错误是宏编程时经常出现的。 option mprint;

%macro macbug ( proc = freq , debug = 1 ) ; data w ;

retain a b c 0 ; if a = 1 then ;

call symputx ( \ run ;

%put debugging: nvars=&nvars ; data test ; retain y . ; do i = 1 to 10 ; x = ranuni(0) ; z = x + y ;

%if &proc = freq %then x = round ( x ) ; output test ; end ; run ;

proc freq data = test ; table x ; run ;

%mend macbug ;

%macbug ( proc = freq , debug = 1 ) ;

运行后我们可以看一下日志: MPRINT(MACBUG): data w ;

MPRINT(MACBUG): retain a b c 0 ; MPRINT(MACBUG): if a = 1 then ;

MPRINT(MACBUG): call symputx ( \MPRINT(MACBUG): run ;

NOTE: 数据集 WORK.W 有 1 个观测和 3 个变量。 NOTE: “DATA 语句”所用时间(总处理时间): 实际时间 0.01 秒 CPU 时间 0.01 秒 debugging: nvars=3

MPRINT(MACBUG): data test ; MPRINT(MACBUG): retain y . ; MPRINT(MACBUG): do i = 1 to 10 ; MPRINT(MACBUG): x = ranuni(0) ; MPRINT(MACBUG): z = x + y ;

NOTE: 由调用宏“MACBUG”生成行。 7 output test ; end ; run ; ------ 79

MPRINT(MACBUG): x = round ( x ) output test ; MPRINT(MACBUG): end ; MPRINT(MACBUG): run ;

ERROR 79-322: 期望?;?。

NOTE: SAS 系统由于错误而停止了该步的处理。

WARNING: 数据集 WORK.TEST 可能不完整。该步停止时,共有 0 个观测和 4 个变量。 WARNING: 数据集 WORK.TEST 由于该步已停止,而没有被替换。 NOTE: “DATA 语句”所用时间(总处理时间): 实际时间 0.00 秒 CPU 时间 0.00 秒

MPRINT(MACBUG): proc freq data = test ; MPRINT(MACBUG): table x ; MPRINT(MACBUG): run ;

NOTE: 数据集 WORK.TEST 中没有观测。

NOTE: “PROCEDURE FREQ”所用时间(总处理时间): 实际时间 0.00 秒 CPU 时间 0.00 秒

首先我们看到很多MPRINT(MACBUG):开头的语句,这些就是mprint选项所生成的SAS宏编译后的SAS代码。我们日志里唯一报错的地方: MPRINT(MACBUG): x = round ( x ) output test ; MPRINT(MACBUG): end ; MPRINT(MACBUG): run ; ERROR 79-322: 期望?;?。 从日志里我们就很容易看出,x = round ( x ) output test ;, x = round ( x ) 这里差了一个分号,

那为什么我们在原宏代码里不容易发现这个错误呢,我们可以看一下这里的原代码: %if &proc = freq %then x = round ( x ) ; output test ;

这里的编译过程如下:首先在SAS宏编译器里编译 %if &proc = freq %then x = round ( x ) ;

注意这里是有分号的,这时SAS宏编译后得到的SAS代码是x = round ( x ),这时就没有分号了,然后后面直接接着output test ;这一句,因此就出现了刚才的错误。正确的代码应该是加两个分号:

%if &proc = freq %then x = round ( x ) ; ; output test ;

因此用mprint选项能帮助我们识别SAS宏中一些不容易发现的错误。

4.3 MFILE选项

将刚才那个分号加上后,运行代码,就可以在日志中看到以下note: debugging: nvars=3

MPRINT(MACBUG): data test ; MPRINT(MACBUG): retain y . ; MPRINT(MACBUG): do i = 1 to 10 ; MPRINT(MACBUG): x = ranuni(0) ; MPRINT(MACBUG): z = x + y ; MPRINT(MACBUG): x = round ( x ); MPRINT(MACBUG): output test ; MPRINT(MACBUG): end ; MPRINT(MACBUG): run ;

NOTE: 缺失值的生成是对缺失值执行操作的结果。 指定每个位置的方式: (次数)(行:列)。 103:93

NOTE: 数据集 WORK.TEST 有 10 个观测和 4 个变量。 NOTE: “DATA 语句”所用时间(总处理时间): 实际时间 0.48 秒 CPU 时间 0.01 秒

这里的行:列103:93是我们在日志里无法知道的,也就没法给我们更多的帮助了。这时就需要mfile选项,它可以让你将生成的SAS代码存入一个文件中,运用这个选项时,你需要FILEREF, MPRINT:

filename mprint \data _null_ ; file mprint ; run ;13 options mprint mfile ; %macbug()

options nomfile ;

%include mprint / source2 ;

这时我们就看到了提示的这一行的代码:z = x + y ;这里因为y为缺失值,故有些提示。

这里还介绍了两个宏% debugexec和% debugsetup,可以用来控制输出哪一段SAS宏的代码: %macro debugsetup (run=1, file=\ %if &run %then %do ;

filename mprint &file ; %if &freshstart = 1 %then %do ;

data _null_ ; file mprint ; run ; %end ;

options mprint mfile ; %end ;

%mend debugsetup ;

%macro debugexec(run=1, file=\ %if &run %then %do ;

options nomfile ;

%include mprint / source2 ; filename mprint clear ; %end ;

%mend debugexec ;

4.4 MLOGIC选项

MLOGIC选项主要是看宏逻辑的,特别是有循环时的逻辑。看下面的代码: %macro words1 ( n = ) ; %do n = 1 %to &n ; abc&n %end ;

%mend words1 ;

%macro words2 ( n = ) ; %do i = 1 %to &n ; abc&i %end ;

%mend words2 ;

%macro repeatline ( n = , mac = 1) ; %do i = 1 %to &n ; %if &mac = 1 %then

%put %words1( n = 3 ) ; %else

%put %words2( n = 3 ) ; %end ;

%mend repeatline ;

options nomlogic ;

%put %words1( n = 3 ) ; %put %words2( n = 3 ) ;

%repeatline ( n = 3 , mac = 1 ) ; %repeatline ( n = 3 , mac = 2 ) ; 这里我们看日志的输出结果: 116 options nomlogic ; 117 118

119 %put %words1( n = 3 ) ; abc1 abc2 abc3 120

121 %put %words2( n = 3 ) ; abc1 abc2 abc3 122 123

124 %repeatline ( n = 3 , mac = 1 ) ; abc1 abc2 abc3 abc1 abc2 abc3 abc1 abc2 abc3 125

126 %repeatline ( n = 3 , mac = 2 ) ; abc1 abc2 abc3

我们看119和121行中,运行的结果都是一行,但为什么124 上的宏运行结果是三行,而126上的运行结果是一行。

这时我们在原代码中的最后一行即%repeatline ( n = 3 , mac = 2 ) ;之前加入以下语句:options mlogic ;进行宏逻辑测试,在SAS日志中出现以下结果: 157 options mlogic ;

158 %repeatline ( n = 3 , mac = 2 ) ;

MLOGIC(REPEATLINE): 准备开始执行。 MLOGIC(REPEATLINE): 参数 N 的值为 3 MLOGIC(REPEATLINE): 参数 MAC 的值为 2 MLOGIC(REPEATLINE): %DO 循环正准备开始;索引变量为 I;起始值为 1;截止值为 3;增量值为 1。

MLOGIC(REPEATLINE): %IF 条件 &mac = 1 为 FALSE MLOGIC(REPEATLINE): %PUT %words2( n = 3 ) MLOGIC(WORDS2): 准备开始执行。 MLOGIC(WORDS2): 参数 N 的值为 3

MLOGIC(WORDS2): %DO 循环正准备开始;索引变量为 I;起始值为 1;截止值为 3;增量值为 1。

MLOGIC(WORDS2): %DO 循环的索引变量 I 当前为 2;循环将会再迭代。 MLOGIC(WORDS2): %DO 循环的索引变量 I 当前为 3;循环将会再迭代。

MLOGIC(WORDS2): %DO 循环的索引变量 I 当前为 4;循环将not会再迭代。 MLOGIC(WORDS2): 准备结束执行。 abc1 abc2 abc3

MLOGIC(REPEATLINE): %DO 循环的索引变量 I 当前为 5;循环将not会再迭代。 MLOGIC(REPEATLINE): 准备结束执行。

注意这里:

MLOGIC(WORDS2): %DO 循环的索引变量 I 当前为 4;循环将not会再迭代。 MLOGIC(WORDS2): 准备结束执行。 abc1 abc2 abc3

MLOGIC(REPEATLINE): %DO 循环的索引变量 I 当前为 5;循环将not会再迭代。 为什么最后一行会出现“%DO 循环的索引变量 I 当前为 5”;一般说来循环三次的话,会出现“%DO 循环的索引变量 I 当前为 4”,即第一行的内容。这就是因为宏变量i的值不但在宏%WORDS2中被累加,并且在宏% REPEATLINE中也被累加。当然,我们将宏变量全用%local定义就不会出现这个问题了。我们还可以用不同的宏变量来进行循环以规避此类错误。

SAS Macro: Symbols of Frustration? %Let us help! A Guide to Debugging Macros

SAS Macro DEBUG:SAS宏测试手册

原文地址:http://www2.sas.com/proceedings/sugi29/128-29.pdf

转载请注明出处:http://blog.sina.com.cn/s/blog_5d3b177c0100c4hd.html

这篇文章主要讲了如何测试宏,以及介绍了一点编写宏代码时常犯的错误。

1如何测试宏(HOUSTON, WE HAVE A PROBLEM?!)

测试宏第一个重大的困难是不容易发现宏的错误在哪里,宏代码在运行时,在日志里可能会出现错误或警告等提示,但是也可能什么也不提醒,只是得到了非预期的结果。例如下面几个例子:

%macro wrttxt(text=Something we really want to write to the log!); %put text; %mend wrttxt; %wrttxt ;

在日志中的输出结果是text,而不是我们想得到的Something we really want to write to the log!原因是代码中没有在text前加’&’

再看下面这个例子:

%macro wrttxt(text=Something we really want to write to the log!); %put &txt; %mend wrttxt; %wrttxt ;

在日志的输出结果为:

WARNING: 没有解析符号引用 TXT。 &txt

亦不是我们想要得到的结果,原因是将text错误地拼写成了txt

下面这个例子才是得到我们想要的结果:

%macro wrttxt(text=Something we really want to write to the log!); %put &text; %mend wrttxt; %wrttxt

2 系统选项:

最常用的四个系统选项包括:Options symbolgen mlogic mprint mfile 关掉这些系统选项的方法:

Options NoSymbolgen nomlogic nomprint nomfile;

后面三个选项mlogic mprint mfile在上一篇文章已经介绍,这里只介绍symbolgen选项。

symbolgen选项

该选项将在日志中告诉你每个宏变量在每一步中的值是多少,这样我们可以通过追踪宏变量值的变化情况来判断我们程序设计的逻辑、程序的运行等是否正确。例如下面的代码: options symbolgen;

%macro bighouse(Pet=Cat Dog, Type=Fat Fuzzy,

Npets=2, Ntypes=2); %do i=1 %to &npets; %do j=1 %to &ntypes;

%let allpets=%scan(&type,&i) %scan(&pet,&j); %put &allpets; %end; %end;

%mend bighouse;

%bighouse ;

运行后日志的结果为:

SYMBOLGEN: 宏变量 NPETS 解析为 2 SYMBOLGEN: 宏变量 NTYPES 解析为 2 SYMBOLGEN: 宏变量 TYPE 解析为 Fat Fuzzy SYMBOLGEN: 宏变量 I 解析为 1

SYMBOLGEN: 宏变量 PET 解析为 Cat Dog SYMBOLGEN: 宏变量 J 解析为 1

SYMBOLGEN: 宏变量 ALLPETS 解析为 Fat Cat Fat Cat

SYMBOLGEN: 宏变量 TYPE 解析为 Fat Fuzzy SYMBOLGEN: 宏变量 I 解析为 1

SYMBOLGEN: 宏变量 PET 解析为 Cat Dog SYMBOLGEN: 宏变量 J 解析为 2

SYMBOLGEN: 宏变量 ALLPETS 解析为 Fat Dog Fat Dog

SYMBOLGEN: 宏变量 NTYPES 解析为 2 SYMBOLGEN: 宏变量 TYPE 解析为 Fat Fuzzy SYMBOLGEN: 宏变量 I 解析为 2

SYMBOLGEN: 宏变量 PET 解析为 Cat Dog SYMBOLGEN: 宏变量 J 解析为 1

SYMBOLGEN: 宏变量 ALLPETS 解析为 Fuzzy Cat Fuzzy Cat

SYMBOLGEN: 宏变量 TYPE 解析为 Fat Fuzzy SYMBOLGEN: 宏变量 I 解析为 2

SYMBOLGEN: 宏变量 PET 解析为 Cat Dog SYMBOLGEN: 宏变量 J 解析为 2

SYMBOLGEN: 宏变量 ALLPETS 解析为 Fuzzy Dog Fuzzy Dog

3 介绍一些宏编程时经常遇到的问题

前面两个在以前的文章里已有介绍,因此在本文中就不再讲了。 3.1 缺失值与NULL值的比较

在数据步时,并没有NULL值,字符的缺失值用””表示,而数字的缺失值用.来表示。但是在宏语言中,没有表示缺失值的方法。如果在宏语言中,一个宏变量的值为NULL,其值就是NULL。

例如在数据步中,if gender = “ “ then do;,这一句就表示gender如果为空时的情况。而在宏语言中,%if &age = “ “ %then %do;这一句就表示如果&age的值如果为双引号时,而不是空值时的情况。如果要表示空值,则可以用

本文来源:https://www.bwwdw.com/article/bg7g.html

Top