第一篇“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的字号最大,是因为这家公司在分析领域收入最高)。
“巨无霸”的确是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 <iostream> #include <fstream> 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进阶的关键。
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) (未完)