有个老本家,著有《白话文学史》(上卷)、《中国哲学史大纲》(上卷),——你知道他叫胡适。然后有朋友这个“Think SAS”系列有没有下文,我自然不敢托大,“半卷先生”不能做,还是老老实实地把这个系列往前推吧。

第一篇“Think SAS”中的“Think”,纯粹做“考虑”解,说,诸君如果为工作计,不妨考虑下SAS。下面说些关于SAS本身的一些思考与认识。俗话说,人类一思考,上帝就拍砖。上一篇是纯劝导型,这一篇就是求拍砖型。

0.总结与回应

在展开讨论之前,希望大伙对“事实问题”而不是“价值问题”能有一个统一的认识,这也是我对上篇各位的评论的一个总结与回应。

0.1 功能:SAS 与R

一些朋友询问SAS或R或其他软件功能的优劣,然后决定自己应该着手学哪个。这是一个伪问题,或至少也是一个不好的问题。一般来说,没有不好的问题,只有不好的回答,——但那的确是一个不好的问题。

一门编程语言(本文讨论的是统计编程语言),只要能实现分支(if … then …)和循环(for/while/do … loops),就能够完成几乎所有的运算。这样说来,单问SAS是不是比R强大,或者R是不是比SAS强大就是一个意义不大的问题。它们都能较好地完成大多数的任务,强不强大的区别,在于背后的用户。一些更好的问题或许是,比如,它们的某些模块相比起来如何,它们的扩展性相比如何、它们的运算效率相比如何等等。个人认为,最好的问题是,在我现在如此这般的情况下,哪一款软件最适合我?这些问题也仁者见仁智者见智,但都能讨论出有意思的东西出来。

我认为讨论软件优劣不是一个好问题,还有一个基于经验的理由,就是一般在这种情形下,你需要的不是一个答案,而是不管基于什么理由而迅速选择一门语言,然后沉浸下去。时间和精力不能浪费在观望上面。R如何,SAS又如何,如果还停留在询问探索阶段,它们都不是你的(套用一句话,不学进去,神马都是浮云)。

以前说过,大多时候,你选择一门语言,不是因为你经过比较,认为A比B好故选A。你在图书馆看到哪本书,你旁边的朋友、同事、老师在使用哪个,或者更直接的,你被要求用哪个。无论是什么,学下去了都能胜任你大多数的日常工作。如果发现不够用,那恭喜你,你就处在一种知识/技能上的饥渴状态,我相信无论是哪一种新的语言,你都能迅速上手。

0.2 工具:SAS与统计

提到SAS,一些学统计出身的朋友很不屑:不就工具吗?统计思想才是王道!

都说到思想了,我想认真回应下。在做统计分析时,SAS编程是工具。但在做决策时,统计本身也是辅助工具。因为处在上下游的关系而相互轻视,这不是一种开放的心态。

oloolo对行业顶尖的统计师有一个非常好的总结,就是“对业务敏感,对统计老到,对编程熟练”。要是业务人员嘲笑统计师不懂业务,统计师嘲笑程序员不懂统计,程序员反过来嘲笑他们其实什么啥都做不了,这生态圈的境界未免就低了些。

0.3 SAS 与FDA以及“路径依赖”

作为一家标榜平台无关的监管机构,FDA没有要求药厂必须用SAS作为统计分析工具。但是,FDA要求所有的临床数据,都必须用SAS公司研发出来的一种开放数据格式提交(SAS transport file,后缀是.xpt)。还有,FDA的统计师的PC上装有SAS软件。这样,你大概就能理解为啥这行业都用SAS了。

或者有人说,SAS被广泛使用,是因为它出道早,抢了个先机,倒不是因为它本身如何如何好。这个诘难在我看来是很软弱的:

–首先,SAS并不是出道最早的,SPSS就比它早。S-plus也跟它是一个时代的产物。

–关于“强了个先机”(路径依赖)。有时候你不能过高相信了用户的忠诚度。想想,你换了几种输入法、几种浏览器、几种搜索引擎?几十年来大浪淘沙,一个产品或服务始终占优,单单一个用户忠诚度是不能完全解释的。

举个例子,比如FDA要求所有的临床数据用xpt格式提交,这个不是一成不变的。以前也提到,SAS的优势根本就不在数据存储上,XML就比xpt好,而且就是将来的方向。但是在药厂,程序员们还是将继续使用SAS来生成各种XML文件(包括define.xml)。SAS或许不是处理XML文件的最好工具,但至少在功能上没有任何问题,它可以是SAS程序员的最好工具(参见本文0.1)。

下面回到正题,说说SAS本身。

1. “巨无霸”SAS

下面这幅图,取自SYSTAT的创始人Leland Wilkinson的文章,The Future of Statistical Computing,大概按照几种标准对市场上存在的统计软件做了个聚类。一些统计软件的特点是将数据全部读入内存,在处理完了才跟磁盘交换数据,比如Stata、R等;Minitab、Statistica和SAS的JMP归为一类是因为它们都专注于质量控制领域;SPSS和SYSTAT在框架和用户界面比较类似(而且它们的归宿也类似,SYSTAT卖给了SPSS,后来SPSS又把它转手卖给印度。后来啊,SPSS本身又被IBM给买了);Google和Microsoft归在一起,因为它们不是专注做统计软件的公司,但又有用户界面非常人性化的统计分析产品(轻客户端,比如Google Analytics)。

SAS呢?SAS跟它们都不一样。上文只好根据安装介质最大这条标准,把SAS单独归为一类(图中SAS的字号最大,是因为这家公司在分析领域收入最高)。

SASetc

“巨无霸”的确是SAS给人的第一个鲜明的印象。所以在大多数场合,拿它跟其他统计软件相比,就有以大欺小之嫌,SAS早已经不仅仅是Statistical Analysis System(具体的可参见本系列第一篇之“SAS是什么?”)。

1.1 SAS的编程元素

SAS的”巨无霸”特点,表现在它的产品线的多样化(功能/行业),也表现在它编程元素的多样化

–-它主要是一门过程式的语言(循环、分支、数组,……),但其中也不乏面对对象的特性:

  —首先,SAS/AF就是一门完全面对对象的应用开发语言。

  —在SAS/Base中,–通过Data Step Component Interface,你可以在数据步里建立纯OOP 的Java对象。

  —在DDS中,有一个类似的DDS Data Step Object

  —最后,在Macro中,一样可以体现出以上提到的各种OO特性

 

–对C/C++程序员来说,它提供了一个SAS/C Complier

–-对Java程序员,它有一个基于Eclipse的开发环境SAS AppDev Studio,可以方便地利用SAS的计算引擎开发各种应用,

–-矩阵运算一块,SAS的矩阵语言IML与Matlab、R等相比也不弱

–-字符处理,除了大量的字符函数,SAS也支持Perl正则表达式

–-支持标准SQL语句

–-支持Hash表

–-为XML数据提供了XML引擎

–……

SAS的这种“杂糅”风格,也受到不少批评。一个感觉就是似乎有些“粗”(大象能不能跳舞?),比如,很明显,S语言就比它精细许多。在统计分析一块,S语言是SAS的主要竞争者,我先绕开讲个故事。

1.2 SAS vs S: AK47 vs M16:一个类比

在枪械史上,有两款步枪最受人瞩目,而且经常拿来做对比,它们就是AK47和M16(以下关于这两款步枪的材料来自网络)。

AK47出自前苏联一名坦克车长卡拉什尼科夫上士之手。1942年他回家养伤,跟一个火车司机在一个小工棚里打磨了一支自动步枪。以后不断改进,1946年,他的AK46送去靶场接受极限测试:连续射击子弹1.5万发,枪管打红了,射击精度却没有什么大的变化。以后这支步枪就以AK47的名字扬名天下,它结构简单,结实耐用,故障极少,造价低廉,威力巨大,能在寒冷、炎热、风雨、沙漠甚至水中都能使用。

M16最早出现在上世纪60年代,设计师是美国知名的枪械大师斯通纳。他在M16的各个部件上广泛采用了铝合金和塑料等轻型材料,制造工艺非常先进,枪身也更精致时尚。另外,M16口径稍小,射击精度有所提高,但它对射击的环境要求很高,枪管不能进水,有时下雨受潮也影响射击精度。再加上性能不稳定,连续打出4500发以上的子弹,就造成某些零件的断裂。

讲到这里,大家都应该能猜到我想说什么。除了价格因素,在比喻的意义上,SAS就是AK47(:这里的SAS指其与S相对应的部分,下同;整个SAS系统是一个武器库而不是一个单兵武器),S语系就是M16,或者保险一点,与M16相比,SAS更像AK47;与AK47相比,S语系更像M16。S语言出身名门(贝尔实验室),设计精巧,获过ACM大奖。SAS语言则出自于一位物理学背景的程序员之手(Tony Barr),当时他所在的机构急需解决方差分析等问题。接下来的故事大伙都很清楚了,AK47成了战场上使用最受欢迎的步枪。

1.3 SAS的优缺点,以及关于其编译器的一些讨论

SAS的设计,很多是基于商业世界的要求。比如,对于数值型变量,SAS只提供一种浮点型格式,这让它在计算性能方面有些吃亏(想想看,就连1+1,SAS都要当成两个浮点数来计算,习惯于首先声明“int x”的C/C++程序员看了大概要不爽),但是,比如,关于日期格式(format和informat),SAS却提供了近百种供选择。纯浮点数让系统开销大一些,但是在设计方面可以简化不少,但是全世界各种不同的日期格式却丝毫不能省略。SAS系统一路“堆积”至此,是考虑,有时甚至是迎合了市场的需要。

在比喻的基础上做进一步的引申会很危险。SAS语言脱胎于PL/1。与其他过程式编程语言相比,往好里说,PL/1在异常处理方面强过Ada,在文本处理方面强过Basic,在输入输出管理方面强过Cobol,在计算方面强过Fortran,在流程控制方面强过Pascal,在内存管理方面强过C,当然,在宏替换方面强过汇编。SAS无意在系统编程或算法实现跟其他语言一较长短,但它在数据访问、数据管理、报表展示、数据分析以及编程灵活和易用性方面的强大优势(详见下),使得它在商业世界一直长盛不衰。

当然,SAS的缺点也非常明显。比如, 直到SAS9.2它才能够在数据步(data step)里自定义函数,你会想,作为一门过程式的语言,以前不能自定义函数的日子怎么过?毫无疑问,这是一个限制。但是,在SAS中,你还以通过其他方式完成类似的功能。首先,SAS有大量的内置函数,在你想自定义函数之前,先麻烦查一下函数手册;然后,你可以定义一个“类似函数的”宏(Macro),这是应用最广泛的方式;或者,你也可以直接在SAS的矩阵语言IML定义函数。

说到IML,这是一个好东西,但是,这么多年了,这家伙居然不能够实现递归!!毫无疑问,这也是一个限制。但是,首先,所有的递归都可以写成一个等价的循环,而且循环的效率还高些;其次,你可以在Macro里实现递归;而且,好消息是,SAS9.2的自定义函数完全支持递归调用。

讨论SAS本身的优点跟缺点,不得不提一下系统底层的东西。比如,Tony Barr在设计SAS系统的时候(上世纪六七十年代),它的编译器用的就是一个递归下降解析器(recursive descent parser),这是一种自上而下的编译方式。深入讨论这个话题超出了本文的范围,简单说一下这种编译方式的优点和缺点。

它的突出优点是:它超级简单,跟AK47一样,意思是说它超级稳健(robust)。它的缺点也很明显,超级简单的一个代价就是,有时候它不够精确。想想看,假设你跑一段代码,然后查日志文件(log),发现报错信息是在第2011行。一个建议是,比如,你最好从2009行开始查看你的代码。

1.4 关于SAS与S语系的一些展望

上面多次提到了S语系。我不熟悉它,但也非常看好它。冒着危险再讲个故事。

战后卡拉什尼科夫访问过美国,跟斯通纳玩了个友谊赛,他用M16,斯通纳用AK47。两位对手中的步枪评价都很高。他们都认为,未来步枪将会向向小口径、轻型化、通用性方向发展(S?!);当然,其前提仍然是性能可靠,适合各种复杂多变和条件恶劣的自然气候(SAS?!)。

再一次,我鼓吹的是SAS与S的融合。现在SAS系统有好几个部分兼容R,你可以在Stat Studio和JMP里跑R代码。作为一个SAS程序员,我觉着这样还不够,最好是在SAS Base里整合一个与PROC SQL、PROC IML平行的PROC R。

2. Programming SAS(用SAS编程!)

SAS中文社区神龙见首不见尾的高手,SAS_Dream,在2004年抛出一篇《SAS语言管窥》,梳理SAS 的各种语系,如BASE、STAT、AF等,多读多有启发。

下面讨论的也是SAS的编程特点。有一个区分,SAS程序员(SAS Programmer)和SAS用户(SAS User)。一个纯SAS程序员更多使用SAS的编程模块(SAS Base、SAS/AF等),一个纯SAS用户更多使用SAS的非编程模块,比如统计分析模块SAS/STAT、SAS的GUI工具SAS Enterprise Guide、SAS Enterprise Miner等。从这个角度来说,跟SAS_Dream的看法不一样,我把SAS/STAT等分析模块从SAS编程语言的范畴中剔除出去了。那些模块需要的更多的是业务知识(比如统计学),没有丝毫的编程乐趣。当然,一个人可能倾向于是SAS程序员或者SAS用户,或者有时候是SAS程序员,有时候是SAS用户。

2.1 Data Steps

data steps(数据步)是SAS最核心的东西,一种第四代过程式编程语言,脱胎于PL/1

一些人认为SAS的语法结构怪异,其实这是很大的误解。作为一门通用性的过程式语言,它在顺序语句、分支、循环方面跟其他过程式语言(或过程式/结构式编程方式)没什么太大区别。

SAS data step跟其他语言最大的区别,在于它的内置循环。举个例子,在d盘里有一个数据文件,data.dat,存有三个数字:

1
2
3

用SAS读取这个文件并计算它们的和是很简单的事情。先看看其他语言是如何操作的,比如C++。C++是一门过程式兼面对对象的语言,下面的例子展示的是C++过程式的编程风格(不熟悉C++语法的朋友,可以只看中间加粗的部分):

#include 
#include 
using namespace std;

int main() 
{ 
     int x; 
     int sum=0; 
     ifstream inFile; 
     inFile.open("d:data.dat");    
     inFile >> x;    

    
     while (!inFile.eof( ))      
     { 
         cout<<x<<endl;  
         sum = sum + x; 
         inFile >> x;               
     }


     inFile.close( ); 
     cout << "Sum = " << sum << endl;      
     return 0; 
}

中间加粗的部分是一个循环,在没有到达数据末尾时(即“3”那个数字,代码中的条件是!inFile.eof(),其中,感叹号是not的意思,eof就是end of file。),持续输出文件里的数字并累加,结果就是

1
2
3
sum=6

以下的SAS代码产生同样的结果:

data null; 
    infile "d:\data.dat" end=eof; 
    input x;

    sum+x;

    put x; 
    if eof then put sum=; 
run;

上面的SAS代码,同样是运行直到文件的末尾,但它并不需要一个显式的循环来读取那文件里的3个数字(内置循环,说的就是这个)。理解SAS data step内置循环的特点,是SAS进阶的关键。详细的,可见Overview of DATA Step Processing

—————-

写不动了,下期预告,大伙新年愉快。

2.2 SAS BASE

SAS BASE不是一门语言,而是一系列编程语言的大杂烩。它是在data step的基础上,加上其他编程元素,如SQL、Macro、ODS和一些proc steps等。只有最顽固(可能也是最骄傲)的SAS程序员仍然坚持2.1的风格,大多数SAS程序员都是使用SAS BASE进行混合编程。当我们是SAS编程语言的时候,一般说的就是这个SAS BASE。它是SAS系统的一个模块(或软件),与SAS/STAT等模块并列。

2.2.1 PROC SQL

2.2.2 过程步(PROCs steps)

2.2.3 宏(Macro)

(to be continued)

发表/查看评论