Categories
游来游去

Shanghai, Yesterday Once More

DSC06635
静与动,inside of the Shanghai Art Museum.

昨天,难得的冬日好阳光,约了三五好友打发时光,却也落得中间几个小时的独行时间,沿着南京东路细细的走了走。越走越熟悉,然后恍然大悟原来同样的路,3年前的这个时候也曾步行而过。那是第一次来上海,那是瞻仰大都市风貌的时候,那是还年轻的抱怨着上海公交车好贵的时候...其实真的,只是过了三年而已。很短,却仿若一个世纪的梦一般长。

DSCN4665
第一次来上海,惊叹于路边的咖啡屋...摄于2009年11月。

DSCN4685
印象颇深的LAN[蘭],至今也不知道是不是餐厅。摄于2009年11月。

南京东路还是一如既往的繁华,繁华到不敢轻易的认知那些看起来有些眼熟的店铺。浮生若梦,不知道在这来来往往的游人脚步中,有多少还会再次相逢。还好有相机为伴,索性仿若第一次来到上海,再次以一个纯粹的游客心情来体会这座迷离的城市。不知道是失落,还是充盈。

DSC06559
南京东路的夕阳, Sunset on the East Nanjing Road, Shanghai.

原来记忆中长长的南京东路并不是那么的长,走走便到了人民广场。念及上海美术馆不久之后就要关门谢客了,赶紧冲进去补拍几张照片——由此也可见我的心是多么的不纯粹,根本不是为了看展览而来的,就是为了看一眼这个古典建筑内部的繁华。

DSC06653
背靠背, Side by side, inside of the Shanghai Art Museum.

DSC06647
冷与暖, cold and warm, inside of the Shanghai Art Museum.

经常会不自觉的驻足在熙熙攘攘人群中的书报摊,这些摊贩们对于潮流文化有着超乎异常的敏锐直觉。果不其然,在一堆八卦杂志之中,《丰乳肥臀》就这么风姿卓绝的挤站着。

DSC06582
九江路上的报摊

走累了就会开始找吃的,比如隐藏在南京西路伊势丹下面的鲷鱼烧...真的吃,好好吃!赶上开业特价买一送一,就毫不客气的红豆奶油抹茶三种口味各入手一个。刚出炉的鲷鱼烧外壳脆脆的里面软软的,口感极佳。

DSC06671
鲷鱼烧

DSC06676
满满的一大袋鲷鱼烧

夜晚的迷离自然需要电影的神奇为伴。艾伦伍迪的新片《To Roma with Love》自然成为了不错的选择。只是在正式上映之前,只有在这种小小的咖啡书屋里面可以一睹为快。不过,真的叫这家咖啡书屋有点委屈它了,人家实则是大名鼎鼎的“私人图书馆”。

DSC06686
2666私人图书馆,静安别墅。

有意思的是上次无意路过也顺手拍了一张,一白一黑,还是蛮有情调来对比看看的。

DSC06688
Day and night, 2666 private library, Jing'an House.

还有一些总是能无意挖掘的神秘地盘,比如这座夜色中的城堡。

DSC06539
人民公园内的餐厅,inside of the Renmin Park at night.

大概是私人的狂欢之所吧,正巧碰到三位婀娜多姿的青年女孩携手走进去。不敢贪恋她们的背影,只有稍稍记录一下这曲径的神秘。

DSC06543
通往狂欢的曲径,Roda towards the restaurant, inside of the Renmin Park, Shanghai.

Yesterday once more, 但也不尽那么的相同一致。在不同的时间,相同的地点,记录下不同或者相同的画面。却也是人生颇有意思的一件事儿。至少,可以在越来越觉年老体衰之时,打发一下时间吧。总觉得陷入回忆是不好的,可是难得回忆如此温馨。

Categories
日常应用

七天搞定SAS(六):宏的编写、程序调错

本系列连载文章:

在SAS各种繁杂的PROC之后,还要来看看MACRO才可以嘛。又不能写函数...

SAS中的MACRO:宏编写

MACRO主要是DO和%LET的各种组合,前者负责循环后者负责变量。

一个例子:

%LET flowertype = Ginger;
* Read the data and subset with a macro variable;
DATA flowersales;
INFILE 'c:\MyRawData\TropicalSales.dat';
INPUT CustomerID $4. @6 SaleDate MMDDYY10. @17 Variety $9. Quantity;
IF Variety = "&flowertype";
RUN;
* Print the report using a macro variable;
PROC PRINT DATA = flowersales;
FORMAT SaleDate WORDDATE18.;
TITLE "Sales of &flowertype";
RUN;

这段代码可以做什么呢?很简单,替换文字。我们指定了一个SAS MACRO中的变量flowertype,在执行MACRO的时候他会被自动翻译成标准的SAS代码。这样执行的结果就是:

2013-12-09 15_57_22-The Little SAS Book(Fourth).PDF - Adobe Reader

看到了吧,标题已经被替换了。

一段MACRO以%macro开始,然后以%mend结束。

* Macro to print 5 largest sales;
%MACRO sample;
PROC SORT DATA = flowersales;
BY DESCENDING Quantity;
RUN;
PROC PRINT DATA = flowersales (OBS = 5);
FORMAT SaleDate WORDDATE18.;
TITLE 'Five Largest Sales';
RUN;
%MEND sample;
* Read the flower sales data;
DATA flowersales;
INFILE 'c:\MyRawData\TropicalSales.dat';
INPUT CustomerID $4. @6 SaleDate MMDDYY10. @17 Variety $9. Quantity;
RUN;
* Invoke the macro;
%sample

这样执行之后的结果就是:

2013-12-09 15_58_09-The Little SAS Book(Fourth).PDF - Adobe Reader

虽然SAS不可以直接写函数,但是MACRO还是有参数可以传入的。

* Macro with parameters;
%MACRO select(customer=,sortvar=);
PROC SORT DATA = flowersales OUT = salesout;
BY &sortvar;
WHERE CustomerID = "&customer";
RUN;
PROC PRINT DATA = salesout;
FORMAT SaleDate WORDDATE18.;
TITLE1 "Orders for Customer Number &customer";
TITLE2 "Sorted by &sortvar";
RUN;
%MEND select;
* Read all the flower sales data;
DATA flowersales;
INFILE 'c:\MyRawData\TropicalSales.dat';
INPUT CustomerID $4. @6 SaleDate MMDDYY10. @17 Variety $9. Quantity;
RUN;
*Invoke the macro;
%select(customer = 356W, sortvar = Quantity)
%select(customer = 240W, sortvar = Variety)

这样传入的参数会自动作为变量被替换掉。结果如下:

2013-12-09 15_59_26-The Little SAS Book(Fourth).PDF - Adobe Reader

当然MACRO中也会有需要判断的时候,这就是IF上场之时啦:

%MACRO dailyreports;
%IF &SYSDAY = Monday %THEN %DO;
PROC PRINT DATA = flowersales;
FORMAT SaleDate WORDDATE18.;
TITLE 'Monday Report: Current Flower Sales';
RUN;
%END;
%ELSE %IF &SYSDAY = Tuesday %THEN %DO;
PROC MEANS DATA = flowersales MEAN MIN MAX;
CLASS Variety;
VAR Quantity;
TITLE 'Tuesday Report: Summary of Flower Sales';
RUN;
%END;
%MEND dailyreports;
DATA flowersales;
INFILE 'c:\MyRawData\TropicalSales.dat';
INPUT CustomerID $4. @6 SaleDate MMDDYY10. @17 Variety $9. Quantity;
RUN;
%dailyreports

比如周二,那么翻译出来的SAS代码就是:

DATA flowersales;
INFILE 'c:\MyRawData\TropicalSales.dat';
INPUT CustomerID $ @6 SaleDate MMDDYY10. @17 Variety $9. Quantity;
RUN;
PROC MEANS DATA = flowersales MEAN MIN MAX;
CLASS Variety;
VAR Quantity;
TITLE 'Tuesday Report: Summary of Flower Sales';
RUN;

最终得到的结果为:
2013-12-09 16_00_55-The Little SAS Book(Fourth).PDF - Adobe Reader

SAS中使用CALL SYMPUT:用数据值赋予变量

如果有的时候需要数据集中的值来给MACRO中的变量赋值,我们就需要使用CALL SYMPUT了。

* Read the raw data;
DATA flowersales;
INFILE 'c:\MySASLib\TropicalSales.dat';
INPUT CustomerID $4. @6 SaleDate MMDDYY10. @17 Variety $9. Quantity;
PROC SORT DATA = flowersales;
BY DESCENDING Quantity;
RUN;
* Find biggest order and pass the customer id to a macro variable;
DATA _NULL_;
SET flowersales;
IF _N_ = 1 THEN CALL SYMPUT("selectedcustomer",CustomerID);
ELSE STOP;
RUN;
PROC PRINT DATA = flowersales;
WHERE CustomerID = "&selectedcustomer";
FORMAT SaleDate WORDDATE18.;
TITLE "Customer &selectedcustomer Had the Single Largest Order";
RUN;

这样的结果就成了:

2013-12-09 16_01_50-The Little SAS Book(Fourth).PDF - Adobe Reader

看出来这里面的逻辑了么?我们先对数据集flowersales进行了排序,然后选择第一名的订单用户,赋值给selectedcustomer这个变量,然后就可以直接在后面用&selectedcustomer调用这个变量值,去查找属于他的观测记录了。

SAS MACRO的DEBUG调试

这里就是一些基本的找错技巧了:

  • 避免最常见的语法错误:先写一般的SAS语句,然后去替换需要用到变量的部分。
  • 引号问题:如果用单引号,那么SAS不会替换里面的变量值;如果用双引号,那么里面&variable的值会被替换掉。所以酌情注意。
  • SAS的报错记录:有MERROR(找不到macro)、SERROR(找不到变量)、MLOGIC(SAS将在日志中输出详细的执行情况)、MPRINT(SAS将在日志中输出翻译出来的SAS代码)、SYMBOLGEN(SAS将在日志中输出变量当时的赋值)。

SAS常见程序错误

最常见的大概就是少了结尾的分号...这里的报错一般是:

ERROR 180-322: Statement is not valid or it is used out of proper order.

或者其他类似的语句无法被SAS理解的。

还有就是输入数据不正确或者有缺失值什么的...这个我觉得在数据源是数据库管理系统的时候,不是什么问题...

还有就是数值型被转换成文本型...报错类似于:

NOTE: Character values have been converted to numeric values at the places
given by:(Line):(Column).

我们利用PUTLOG可以一步步的输出SAS计算的过程:

9 * Keep only students with mean below 70;
10 DATA lowscore;
11 INFILE ’c:MyRawDataClass.dat’;
12 INPUT Name $ Score1 Score2 Score3 Homework;
13 Homework = Homework * 2;
14 AverageScore = MEAN(Score1 + Score2 + Score3 + Homework);
15 PUTLOG Name= Score1= Score2= Score3= Homework= AverageScore=;
16 IF AverageScore < 70;
17 RUN;

这样也有利于查错。

其他的可以直接看报错信息来判断,不赘述了。

Categories
Uncategorized

Protected: To Roma with Love

This post is password protected. To view it please enter your password below:

Password:

Categories
日常应用

七天搞定SAS(五):数据操作与合并

本系列连载文章:

数据集操作永远是逃不掉的问题,最简单的就是两个数据集的合并——当然不是简简单单的行列添加,按照某一主键或者某些主键合并才是最常用的。在SAS中,要熟悉的就是SET这个声明,可以用改变数据集等等。

生成新变量

这里一个比较简单的例子,就是有一个现成的数据集,我们想增加一个变量。

DATA averagetrain;
SET 'c:MySASLibtrains';
PeoplePerCar = People / Cars;
RUN;
PROC PRINT DATA = averagetrain;
TITLE 'Average Number of People per Train Car';
FORMAT Time TIME5.;
RUN;

这样的结果就是增加了一个新的变量PeoplePerCar:

Average Number of People per Train Car
Obs Time Cars People PeoplePerCar
1 10:10 6 21 3.50000
2 12:15 10 56 5.60000
3 15:30 10 25 2.50000
4 11:30 8 34 4.25000
5 13:15 8 12 1.50000
6 10:45 6 13 2.16667
7 20:30 6 32 5.33333
8 23:15 6 12 2.00000

行合并

这里比较类似于R里面的rbind()函数,就是直接在尾部附上后面的数据。当SET指定了两个或多个数据集的时候,可以进行这样的操作。距离如下:

* Create a data set, both, combining northentrance and southentrance;
* Create a variable, AmountPaid, based on value of variable Age;
DATA both;
SET southentrance northentrance;
IF Age = . THEN AmountPaid = .;
ELSE IF Age < 3 THEN AmountPaid = 0;
ELSE IF Age < 65 THEN AmountPaid = 35;
ELSE AmountPaid = 27;
PROC PRINT DATA = both;
TITLE 'Both Entrances';
RUN;

然后结果输出为:

2013-12-09 16_03_41-The Little SAS Book(Fourth).PDF - Adobe Reader

这里很容易看出,对于第一个数据集没有的变量LOT,会自动添加缺失值。

SET还可以进一步结合BY对数据排序:

DATA interleave;
SET northentrance southentrance;
BY PassNumber;
PROC PRINT DATA = interleave;
TITLE 'Both Entrances, By Pass Number';
RUN;

这样返回的结果就是按照PassNumber排序的了:

2013-12-09 16_04_31-The Little SAS Book(Fourth).PDF - Adobe Reader

SAS一对一合并数据集

类似于SQL的join和R的merge,SAS也可以合并数据集。先从最简单的一对一合并说起:

* Merge data sets by CodeNum;
DATA chocolates;
MERGE sales descriptions;
BY CodeNum;
PROC PRINT DATA = chocolates;
TITLE ”Today's Chocolate Sales”;
RUN;

这样就可以得到按照CodeNum来合并这两个数据集了,返回结果为:

2013-12-09 16_05_17-The Little SAS Book(Fourth).PDF - Adobe Reader

当然一对多也是可行的。

原数据为:

2013-12-09 16_06_42-The Little SAS Book(Fourth).PDF - Adobe Reader

然后代码为:

* Perform many-to-one match merge;
DATA prices;
MERGE regular discount;
BY ExerciseType;
NewPrice = ROUND(RegularPrice - (RegularPrice * Adjustment), .01);
PROC PRINT DATA = prices;
TITLE ’Price List for May’;
RUN;

最后得到的结果就是:

2013-12-09 16_07_15-The Little SAS Book(Fourth).PDF - Adobe Reader

有的时候我们还想把一些统计量也合并进来,比如PROC MEANS得到的那些,这样自然也是不怎么麻烦的。

DATA shoes;
INFILE ’c:\MyRawData\Shoesales.dat’;
INPUT Style $ 1-15 ExerciseType $ Sales;
PROC SORT DATA = shoes;
BY ExerciseType;
RUN;
* Summarize sales by ExerciseType and print;
PROC MEANS NOPRINT DATA = shoes;
VAR Sales;
BY ExerciseType;
OUTPUT OUT = summarydata SUM(Sales) = Total;
PROC PRINT DATA = summarydata;
TITLE ’Summary Data Set’;
RUN;
* Merge totals with the original data set;
DATA shoesummary;
MERGE shoes summarydata;
BY ExerciseType;
Percent = Sales / Total * 100;
PROC PRINT DATA = shoesummary;
BY ExerciseType;
ID ExerciseType;
VAR Style Sales Total Percent;
TITLE ’Sales Share by Type of Exercise’;
RUN;

这里用到了OUTPUT输出统计结果到SAS数据集,这样最后结果就是:
2013-12-09 16_08_12-The Little SAS Book(Fourth).PDF - Adobe Reader

2013-12-09 16_08_21-The Little SAS Book(Fourth).PDF - Adobe Reader

 

还有一些特定的情况,可以不用MERGE而是UPDATE,这个就得稍稍小心一点了...

* Update patient data with transactions;
DATA perm.patientmaster;
UPDATE perm.patientmaster transactions;
BY Account;
PROC PRINT DATA = perm.patientmaster;
FORMAT BirthDate LastUpdate MMDDYY10.;
TITLE 'Admissions Data';
RUN;

基本就是把patientmaster这个数据集用transactions里面有的数据覆盖掉相应的记录。

2013-12-09 16_09_06-The Little SAS Book(Fourth).PDF - Adobe Reader

SAS里面拆分数据

在读入数据的时候,SAS还可以自动按照某些条件把其拆分为两个数据集,这里需要调用OUTPUT声明。

DATA morning afternoon;
INFILE 'c:\MyRawData\Zoo.dat';
INPUT Animal $ 1-9 Class $ 11-18 Enclosure $ FeedTime $;
IF FeedTime = 'am' THEN OUTPUT morning;
ELSE IF FeedTime = 'pm' THEN OUTPUT afternoon;
ELSE IF FeedTime = 'both' THEN OUTPUT;
RUN;
PROC PRINT DATA = morning;
TITLE 'Animals with Morning Feedings';
PROC PRINT DATA = afternoon;
TITLE 'Animals with Afternoon Feedings';
RUN;

得到的就是两个数据集(虽然我们读入的只有一个...你也可以理解为生成了两个原数据集的子集):

2013-12-09 16_09_53-The Little SAS Book(Fourth).PDF - Adobe Reader

这里就类似于R里面的split()函数了。

还有一些数据格式比较不稳定,比如一行多条记录:

Jan Varsity 56723 Downtown 69831 Super-6 70025
Feb Varsity 62137 Downtown 43901 Super-6 81534
Mar Varsity 49982 Downtown 55783 Super-6 69800

这个时候就可以利用OUTPUT的操作,来逐行读取并输出:

* Create three observations for each data line read
* using three OUTPUT statements;
DATA theaters;
INFILE 'c:\MyRawData\Movies.dat';
INPUT Month $ Location $ Tickets @;
OUTPUT;
INPUT Location $ Tickets @;
OUTPUT;
INPUT Location $ Tickets;
OUTPUT;
RUN;
PROC PRINT DATA = theaters;
TITLE 'Ticket Sales';
RUN;

最后得到的数据就相当规范了(我在想为啥SAS可以有这么多奇葩的数据输入...真折磨人啊):

2013-12-09 16_10_43-The Little SAS Book(Fourth).PDF - Adobe Reader

SAS里面变量选取等参数

其实DATA里面的参数还是蛮多的,除了以前提到过的KEEP,DROP,还有可以重命名的RENAME等。还有一个比较有用的可能就是IN了:

DATA noorders;
MERGE customer orders (IN = Recent);
BY CustomerNumber;
IF Recent = 0;
PROC PRINT DATA = noorders;
TITLE ’Customers with No Orders in the Third Quarter’;
RUN;

这样可以增加一个新的变量Recent,来记录某条记录是否被合并。
2013-12-09 16_11_22-The Little SAS Book(Fourth).PDF - Adobe Reader

WHERE的用法也可以稍稍赘述一下:

*Input the data and create two subsets;
DATA tallpeaks (WHERE = (Height > 6000))
american (WHERE = (Continent CONTAINS ('America')));
INFILE 'c:\MyRawData\Mountains.dat';
INPUT Name $1-14 Continent $15-28 Height;
RUN;
PROC PRINT DATA = tallpeaks;
TITLE 'Members of the Seven Summits above 6,000 Meters';
PROC PRINT DATA = american;
TITLE 'Members of the Seven Summits in the Americas';
RUN;

这样得到的结果为:

2013-12-09 16_12_18-The Little SAS Book(Fourth).PDF - Adobe Reader

SAS中数据的转置:TRANSPOSE

数据的转置有时候也是逃不掉的。这里就有些类似于R里面的reshape()函数了,但是肯定没有reshape2里面的melt and cast强大...我一度觉得reshape2的用法很麻烦,后来才发现原来这东西真的强大到一定程度了...

DATA baseball;
INFILE 'c:\MyRawData\Transpos.dat';
INPUT Team $ Player Type $ Entry;
PROC SORT DATA = baseball;
BY Team Player;
PROC PRINT DATA = baseball;
TITLE 'Baseball Data After Sorting and Before Transposing';
RUN;
* Transpose data so salary and batavg are variables;
PROC TRANSPOSE DATA = baseball OUT = flipped;
BY Team Player;
ID Type;
VAR Entry;
PROC PRINT DATA = flipped;
TITLE 'Baseball Data After Transposing';
RUN;

结果为:
2013-12-09 16_12_55-The Little SAS Book(Fourth).PDF - Adobe Reader

SAS里面自带的变量

SAS里面有些默认自带的变量,有时候用起来还是蛮方便的,类似于R会自带一个row.names这种变量。

比如_N_就会加上行号(当然有时候也不是,呃,准确的说应该是SAS执行的循环顺序,说了SAS是一行行操作数据的嘛):

DATA walkers;
INFILE 'c:\MyRawData\Walk.dat';
INPUT Entry AgeGroup $ Time @@;
PROC SORT DATA = walkers;
BY Time;
* Create a new variable, Place;
DATA ordered;
SET walkers;
Place = _N_;
PROC PRINT DATA = ordered;
TITLE 'Results of Walk';
PROC SORT DATA = ordered;
BY AgeGroup Time;
* Keep the first observation in each age group;
DATA winners;
SET ordered;
BY AgeGroup;
IF FIRST.AgeGroup = 1;
PROC PRINT DATA = winners;
TITLE 'Winners in Each Age Group';
RUN;

这样得到的结果就是排序后的次序了:

2013-12-09 16_13_36-The Little SAS Book(Fourth).PDF - Adobe Reader

类似的变量还有FIRST.variable和LST.variable,这里由于我们用到了 FIRST.AgeGroup,所以第二次输出的时候只有第一个AGE GROUP的结果。

Categories
日常应用

七天搞定SAS(四):数据输出

本系列连载文章:

弄清楚了基本的PROC之后,开始研究SAS的输出...毕竟有了数据处理的结果之后,还要有一个比较舒服的输出格式才可以嘛。

SAS的结果发送系统:ODS

SAS里面的输出叫的比较好听: Output Delivery System (ODS),结果发送系统。也就是说,要不停的开始研究ODS这个东西了。输出的方向包括:

  • LISTING:标准SAS输出
  • HTML: HTML网页输出
  • RTF: 富文本格式
  • PRINTER:高分辨率打印
  • PS: ps矢量格式
  • PCL: 打印机操纵语言
  • PDF:PDF格式
  • OUTPUT: SAS数据表格
  • MARKUP:XML、excel、csv、latex等格式
  • DOCUMENT:输出文档

基本满足要求了?当然,SAS还有模板可以选,还有追踪和选择...不着急,我们一个个来研究。

模板的话,需要调用PROC TEMPLATE:

PROC TEMPLATE;
LIST STYLES;
RUN;

自带了若干模板:

ANALYSIS D3D MINIMAL SASWEB
BARETTSBLUE DEFAULT PRINTER SANSPRINTER
BRICK JOURNAL RTF STATISTICAL

然后TRACE会在日志文件里面跟踪输出的对象:

DATA giant;
INFILE 'c:\MyRawData\Tomatoes.dat' DSD;
INPUT Name :$15. Color $ Days Weight;
* Trace PROC MEANS;
ODS TRACE ON;
PROC MEANS DATA = giant;
BY Color;
RUN;
ODS TRACE OFF;

这样就有日志中的记录:

Output Added:
-------------
Name: Summary
Label: Summary statistics
Template: base.summary
Path: Means.ByGroup1.Summary
-------------
NOTE: The above message was for the following by-group: Color=red
Output Added:
-------------
Name: Summary
Label: Summary statistics
Template: base.summary
Path: Means.ByGroup2.Summary
-------------
NOTE: The above message was for the following by-group: Color=yellow

最后,还可以选择ODS输出的对象:

PROC MEANS DATA = giant;
BY Color;
TITLE 'Red Tomatoes';
ODS SELECT Means.ByGroup1.Summary;
RUN;

这样就只有第一组的统计数据了。

2013-12-09 16_15_51-The Little SAS Book(Fourth).PDF - Adobe Reader

SAS中建立输出数据表:OUTPUT

很多时候我们希望直接把结果放在另外一个SAS的数据表中,这样就需要OUTPUT声明了。

DATA giant;
INFILE 'c:\MyRawData\Tomatoes.dat' DSD;
INPUT Name :$15. Color $ Days Weight;
PROC TABULATE DATA = giant;
CLASS Color;
VAR Days Weight;
TABLE Color ALL, (Days Weight) * MEAN;
TITLE 'Standard TABULATE Output';
ODS OUTPUT Table = tabout;
RUN;
PROC PRINT DATA = tabout;
TITLE 'OUTPUT SAS Data Set from TABULATE';
RUN;

最终可以得到:
2013-12-09 16_16_42-The Little SAS Book(Fourth).PDF - Adobe Reader

很显然,对于下面那个SAS表格的输出,我们可以像操纵普通表格一样来操纵它,各种方便省事对吧?毕竟有的时候SAS给我们的并不是我们想要的最终结果,还要各种小小加工一番才好。

SAS的输出:HTML

有的时候HTML格式的报告会更加方便传播,或者放在服务器上、自动定期更新什么的,便于大家远程直接查看。代码其实也不麻烦:

* Create the HTML files and remove procedure name;
ODS HTML FILE = 'c:\MyHTMLFiles\Marine.html';
ODS NOPROCTITLE;
DATA marine;
INFILE 'c:\MyRawData\Sealife.dat';
INPUT Name $ Family $ Length @@;
RUN;
PROC MEANS DATA = marine MEAN MIN MAX;
CLASS Family;
TITLE 'Whales and Sharks';
RUN;
PROC PRINT DATA = marine;
RUN;
* Close the HTML files;
ODS HTML CLOSE;

最终可以得到HTML网页截屏如下:
2013-12-09 16_17_15-The Little SAS Book(Fourth).PDF - Adobe Reader

SAS输出富文本:RTF

RTF是一种可以直接被WORD等office软件读取的格式,支持图文表格混排什么的。有的时候直接输出出来也会各种方便(吐槽:相比于R的knitr直接各种文件格式混搭、数据随处可以插入,SAS还是有很长的一段路要走哇)。

* Create an RTF file;
ODS RTF FILE = 'c:\MyRTFFiles\Marine.rtf' BODYTITLE COLUMNS=2;
ODS NOPROCTITLE;
DATA marine;
INFILE 'c:\MyRawData\Sealife.dat';
INPUT Name $ Family $ Length @@;
RUN;
PROC MEANS DATA = marine MEAN MIN MAX;
CLASS Family;
TITLE 'Whales and Sharks';
RUN;
PROC PRINT DATA = marine;
RUN;
* Close the RTF file;
ODS RTF CLOSE;

输出的文档大概长成这样:

2013-12-09 16_17_58-The Little SAS Book(Fourth).PDF - Adobe Reader

勉强可以看看吧。SAS的默认配色真心丑陋,像上世纪win 98时代的...

SAS的输出样式自定义

对于PRINTER输出和REPORT、TABULATE加STYLE选项,就不赘述了,目测不会有用到的需求——一般SAS都不会给我最终拿去给partner或者boss汇报的格式,再者就算SAS里面能做,也实在是太麻烦了,成本太高。我的观点就是,做统计的软件还是做统计吧,那些花里胡哨的修饰还是交给excel这种所见即所得的软件来搞定吧。各有所长才是~

不过SAS有个高亮单元格的功能,可以简单说一下。

ODS HTML FILE='c:MyHTMLmens2.html';
PROC FORMAT;
VALUE rec 0 -< 378.72 ='red'
378.72 -< 382.20 = 'orange'
382.20 - HIGH = 'white';
RUN;
PROC PRINT DATA=results;
ID Place;
VAR Name Country;
VAR Time/STYLE={BACKGROUND=rec.};
TITLE 'Men''s 5000m Speed Skating';
TITLE2 '2002 Olympic Results';
RUN;
ODS HTML CLOSE;

这样最终的结果就会有单元格高亮效果了:
2013-12-09 16_18_37-The Little SAS Book(Fourth).PDF - Adobe Reader 2013-12-09 16_18_53-The Little SAS Book(Fourth).PDF - Adobe Reader

勉强可以一看吧。EXCEL的条件格式(conditional formatting)更顺手强大。

SAS的导出模块:EXPORT

如果说ODS是结果的发送,那么export则是更加原始的数据输出(数据而不一定是分析结果)、供其他软件读取。

SAS图形界面下有一步步向导式的export,但是可惜我悲催的一开始就要接触命令行下面的SAS...跳过。

EXPORT可以输出文本格式,最常用的就是逗号或者tab分割的。

LIBNAME travel ’c:\MySASLib’;
* Create Tab-delimited file;
PROC EXPORT DATA = travel.golf OUTFILE = 'c:\MyRawData\Golf.txt' REPLACE;
RUN;

这里就输出了一个tab分割的文本文件。

当然也可以输出excel文件:

LIBNAME travel 'c:\MySASLib';
* Create Microsoft Excel file';
PROC EXPORT DATA=travel.golf OUTFILE = 'c:\MyExcel\Golf.xls' REPLACE;
RUN;

这个和ODS有点重复了呢。不过R也是啊,有各种各样输出的方式,任君选取。