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也是啊,有各种各样输出的方式,任君选取。

Categories
日常应用

七天搞定SAS(三):基本模块调用(格式、计数、概要统计、排序等)

本系列连载文章:

搞定基本的函数之后,开始鼓捣SAS里面的模型。也就是说,要开始写PROC了。说实话,越学SAS,越觉得SAS像Stata...无论是从输出的样式,还是语法。好不习惯没有()的模型调用呀。若是说SAS和Stata的区别,怕只是Stata更侧重于计量模型而SAS则是服务于大多数统计模型吧。

PROC的基本内容:CONTENT

先是一个最基本的PROC:content,可以显示数据集的主要特性。比如,

LIBNAME tropical 'c:\MySASLib';
PROC CONTENTS DATA = tropical.banana;

这里主要是两个声明:TITLE和FOOTNOTE。前者输出时候会产生一个标题,后者会产生尾注。用法也是比较直接的:

TITLE ”Here’s another title”;
TITLE ’Here’’s another title’;
FOOTNOTE3 ’This is the third footnote’;

最后还有一个很像Stata的LABEL声明:

LABEL ReceiveDate = ’Date order was received’
ShipDate = ’Date merchandise was shipped’;

可以变量加注释。其实R里面给变量加注释是一件非常麻烦的事情,只有少数几个包可以搞定,还非常不值的。一般说来,我尽量在变量命名的时候长一点,这样直接可以读懂;再就是重建一个新的表,存储变量名和label。

SAS PROC求子集:WHERE

如果要在PROC里面先求子集的话,可以直接调用WHERE。感觉这里和SQL的思路比较像。用法也算是比较简单(SAS里面的用法都不是很麻烦,除了某些模型):

PROC PRINT DATA = 'c:\MySASLib\style';
WHERE Genre = 'Impressionism';
TITLE 'Major Impressionist Painters';
FOOTNOTE 'F = France N = Netherlands U = US';
RUN;

这样最终得到的结果就是:

Major Impressionist Painters 1
Obs Name Genre Origin
1 Mary Cassatt Impressionism U
3 Edgar Degas Impressionism F
5 Claude Monet Impressionism F
6 Pierre Auguste Renoir Impressionism F
F = France N = Netherlands U = US

SAS PROC 数据进行排序:SORT

排序就更简单了,直接PROC SORT就可以了。

DATA marine;
INFILE 'c:\MyRawData\Lengths.dat';
INPUT Name $ Family $ Length @@;
RUN;
* Sort the data;
PROC SORT DATA = marine OUT = seasort NODUPKEY;
BY Family DESCENDING Length;
PROC PRINT DATA = seasort;
TITLE 'Whales and Sharks';
RUN;

这样数据就按照Family、Length(递减)排序了。

Whales and Sharks 1
Obs Name Family Length
1 humpback 50.0
2 whale shark 40.0
3 basking shark 30.0
4 mako shark 12.0
5 dwarf shark 0.5
6 blue whale 100.0
7 sperm whale 60.0
8 gray whale 50.0
9 killer whale 30.0
10 beluga whale 15.0

SAS PROC 输出数据:PRINT

最简单的数据输出怕就是PRINT了,顾名思义,直接打印数据出来。这里可以进行便啦的选择,还就可以选择统计量:

DATA sales;
INFILE 'c:\MyRawData\Candy.dat';
INPUT Name $ 1-11 Class @15 DateReturned MMDDYY10. CandyType $
Quantity;
Profit = Quantity * 1.25;
PROC SORT DATA = sales;
BY Class;
PROC PRINT DATA = sales;
BY Class;
SUM Profit;
VAR Name DateReturned CandyType Profit;
TITLE 'Candy Sales for Field Trip by Class';
RUN;

得到的结果为:

Candy Sales for Field Trip by Class 1
-------------------------------- Class=14 ---------------------------------
Date Candy
Obs Name Returned Type Profit
1 Nathan 17612 CD 23.75
2 Matthew 17612 CD 17.50
3 Claire 17613 CD 13.75
4 Chris 17616 CD 7.50
5 Stephen 17616 CD 12.50
----- ------
Class 75.00
-------------------------------- Class=21 ---------------------------------
Date Candy
Obs Name Returned Type Profit
6 Adriana 17612 MP 8.75
7 Caitlin 17615 CD 11.25
8 Ian 17615 MP 22.50
9 Anthony 17616 MP 16.25
10 Erika 17616 MP 21.25
----- ------
Class 80.00
======
155.00

SAS PROC里面改变输出格式:FORMAT

基本就是FORMAT一下就可以了,再就是PUT的时候也可以调整。

DATA sales;
INFILE 'c:\MyRawData\Candy.dat';
INPUT Name $ 1-11 Class @15 DateReturned MMDDYY10. CandyType $
Quantity;
Profit = Quantity * 1.25;
PROC PRINT DATA = sales;
VAR Name DateReturned CandyType Profit;
FORMAT DateReturned DATE9. Profit DOLLAR6.2;
TITLE 'Candy Sale Data Using Formats';
RUN;

输出结果为:

Candy Sale Data Using Formats 1
Date Candy
Obs Name Returned Type Profit
1 Adriana 21MAR2008 MP $8.75
2 Nathan 21MAR2008 CD $23.75
3 Matthew 21MAR2008 CD $17.50
4 Claire 22MAR2008 CD $13.75
5 Caitlin 24MAR2008 CD $11.25
6 Ian 24MAR2008 MP $22.50
7 Chris 25MAR2008 CD $7.50
8 Anthony 25MAR2008 MP $16.25
9 Stephen 25MAR2008 CD $12.50
10 Erika 25MAR2008 MP $21.25

常用的格式有:

  • 文本型:$HEXw.和$w.
  • 日期型:DATEw.(输出为ddmmyy或者ddmmyyyy)、DATETIMEw.d(输出为ddmmyy:hh:mm:ss)、DAYw.(输出为dd)、EURDFDDw. 、JULIANw.、MMDDYYw.(输出为mmddyy或mmddyyyy)、TIMEw.d(输出为hh:mm:ss)、WEEKDATEw.(输出为工作日)、WORDDATEw.(输出为单词)。
  • 数字型:BESTw.(自动选择)、COMMAw.d(逗号分隔)、DOLLARw.d(货币)、Ew.(科学计数法)、PDw.d、w.d(标准小数)

输出的样本见下:

当然FORMAT还可以自定义factor型变量的输出格式,比如:

DATA carsurvey;
INFILE 'c:\MyRawData\Cars.dat';
INPUT Age Sex Income Color $;
PROC FORMAT;
VALUE gender 1 = 'Male'
2 = 'Female';
VALUE agegroup 13 -< 20 = 'Teen'
20 -< 65 = 'Adult'
65 - HIGH = 'Senior';
VALUE $col 'W' = 'Moon White'
'B' = 'Sky Blue'
'Y' = 'Sunburst Yellow'
'G' = 'Rain Cloud Gray';
* Print data using user-defined and standard (DOLLAR8.) formats;
PROC PRINT DATA = carsurvey;
FORMAT Sex gender. Age agegroup. Color $col. Income DOLLAR8.;
TITLE 'Survey Results Printed with User-Defined Formats';
RUN;

就可以把数字型的1,2转换为对应的文本male和female等,还可以把变量离散化,得到的输出为:

Survey Results Printed with User-Defined Formats 1
Obs Age Sex Income Color
1 Teen Male $14,000 Sunburst Yellow
2 Adult Male $65,000 Rain Cloud Gray
3 Senior Female $35,000 Sky Blue
4 Adult Male $44,000 Sunburst Yellow
5 Adult Female $83,000 Moon White

最终可以实现的自定义输出还包括简单的文本连接,比如:

* Write a report with FILE and PUT statements;
DATA _NULL_;
INFILE 'c:\MyRawData\Candy.dat';
INPUT Name $ 1-11 Class @15 DateReturned MMDDYY10.
CandyType $ Quantity;
Profit = Quantity * 1.25;
FILE 'c:\MyRawData\Student.txt' PRINT;
TITLE;
PUT @5 'Candy sales report for ' Name 'from classroom ' Class
// @5 'Congratulations! You sold ' Quantity 'boxes of candy'
/ @5 'and earned ' Profit DOLLAR6.2 ' for our field trip.';
PUT _PAGE_;
RUN;

可以给出若干连续的输出(注意DATA _NULL_;将不生成任何SAS的数据表):

Candy sales report for Adriana from classroom 21
Congratulations! You sold 7 boxes of candy and earned $8.75 for our field trip.
------------
Candy sales report for Nathan from classroom 14
Congratulations! You sold 19 boxes of candy and earned $23.75 for our field trip.
------------
Candy sales report for Matthew from classroom 14
Congratulations! You sold 14 boxes of candy and earned $17.50 for our field trip.
------------

SAS里面总结数据:MEANS

SAS当然还有类似于excel的数据透视表和R的data.table的模块,就是MEANS。可以输出的summary statistics包括最大值、最小值、平均值、中位数、余非缺失值个数、缺失值个数、范围、标准差、和等等。此外,还可以使用BY或者CLASS进行分组统计,VAR选择变量等。

比如:

DATA sales;
INFILE 'c:\MyRawData\Flowers.dat';
INPUT CustomerID $ @9 SaleDate MMDDYY10. Petunia SnapDragon
Marigold;
Month = MONTH(SaleDate);
PROC SORT DATA = sales;
BY Month;
* Calculate means by Month for flower sales;
PROC MEANS DATA = sales;
BY Month;
VAR Petunia SnapDragon Marigold;
TITLE 'Summary of Flower Sales by Month';
RUN;

可以实现:

Summary of Flower Sales by Month 1
--------------------------------- Month=5 ---------------------------------
The MEANS Procedure
Variable N Mean Std Dev Minimum Maximum
---------------------------------------------------------------------------
Petunia 3 86.6666667 35.1188458 50.0000000 120.0000000
SnapDragon 3 113.3333333 41.6333200 80.0000000 160.0000000
Marigold 3 81.6666667 25.6580072 60.0000000 110.0000000
--------------------------------- Month=6 ---------------------------------
Variable N Mean Std Dev Minimum Maximum
---------------------------------------------------------------------------
Petunia 4 81.2500000 16.5201897 60.0000000 100.0000000
SnapDragon 4 97.5000000 47.8713554 60.0000000 160.0000000
Marigold 4 83.7500000 19.7378655 60.0000000 100.0000000
---------------------------------------------------------------------------

当然这些统计量也可以直接的写入一个SAS数据表,只需要加上一个OUTPUT就可以了。原数据:

756-01 05/04/2008 120 80 110
834-01 05/12/2008 90 160 60
901-02 05/18/2008 50 100 75
834-01 06/01/2008 80 60 100
756-01 06/11/2008 100 160 75
901-02 06/19/2008 60 60 60
756-01 06/25/2008 85 110 100

SAS代码:

DATA sales;
INFILE 'c:\MyRawData\Flowers.dat';
INPUT CustomerID $ @9 SaleDate MMDDYY10. Petunia SnapDragon Marigold;
PROC SORT DATA = sales;
BY CustomerID;
* Calculate means by CustomerID, output sum and mean to new data set;
PROC MEANS NOPRINT DATA = sales;
BY CustomerID;
VAR Petunia SnapDragon Marigold;
OUTPUT OUT = totals MEAN(Petunia SnapDragon Marigold) =
MeanPetunia MeanSnapDragon MeanMarigold
SUM(Petunia SnapDragon Marigold) = Petunia SnapDragon Marigold;
PROC PRINT DATA = totals;
TITLE 'Sum of Flower Data over Customer ID';
FORMAT MeanPetunia MeanSnapDragon MeanMarigold 3.;
RUN;

最终结果为:

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

SAS PROC统计频率:FREQ

计数的话,就要靠SAS里面的FREQ模块了。比如我们有一个数据集:

esp w cap d cap w kon w ice w kon d esp d kon w ice d esp d
cap w esp d cap d Kon d . d kon w esp d cap w ice w kon w
kon w kon w ice d esp d kon w esp d esp w kon w cap w kon w

然后可以用FREQ来统计一些基本量:

DATA orders;
INFILE 'c:\MyRawData\Coffee.dat';
INPUT Coffee $ Window $ @@;
* Print tables for Window and Window by Coffee;
PROC FREQ DATA = orders;
TABLES Window Window * Coffee;
RUN;

最终会得到一个2×5的表格:

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

SAS PROC汇报表格:TABULATE

基本看到TABULATE就可以想到那个著名的软件Tabular了...不过貌似SAS也自带了一个类似的表格模块。这个东西可以变得非常复杂,不过鉴于我一时半会儿还用不到,所以也没有细细看。抄个例子吧。

原数据:

Silent Lady Maalea sail sch 75.00
America II Maalea sail yac 32.95
Aloha Anai Lahaina sail cat 62.00
Ocean Spirit Maalea power cat 22.00
Anuenue Maalea sail sch 47.50
Hana Lei Maalea power cat 28.99
Leilani Maalea power yac 19.99
Kalakaua Maalea power cat 29.50
Reef Runner Lahaina power yac 29.95
Blue Dolphin Maalea sail cat 42.95

SAS代码:

DATA boats;
INFILE 'c:\MyRawData\Boats.dat';
INPUT Name $ 1-12 Port $ 14-20 Locomotion $ 22-26 Type $ 28-30
Price 32-36;
RUN;
* Tabulations with three dimensions;
PROC TABULATE DATA = boats;
CLASS Port Locomotion Type;
TABLE Port, Locomotion, Type;
TITLE 'Number of Boats by Port, Locomotion, and Type';
RUN;

最终结果:
2013-12-09 16_30_08-The Little SAS Book(Fourth).PDF - Adobe Reader

类似的,还可以增加统计量(类似于MEANS那里):

DATA boats;
INFILE 'c:\MyRawData\Boats.dat';
INPUT Name $ 1-12 Port $ 14-20 Locomotion $ 22-26 Type $ 28-30
Price 32-36;
RUN;
* PROC TABULATE report with options;
PROC TABULATE DATA = boats FORMAT=DOLLAR9.2;
CLASS Locomotion Type;
VAR Price;
TABLE Locomotion ALL, MEAN*Price*(Type ALL)
/BOX='Full Day Excursions' MISSTEXT='none';
TITLE;
RUN;

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

最后还可以混合FORMAT等等,可以变得相当的复杂。貌似这东西是美国劳工部鼓捣出来的格式...

DATA boats;
INFILE 'c:\MyRawData\Boats.dat';
INPUT Name $ 1-12 Port $ 14-20 Locomotion $ 22-26 Type $ 28-30
Price 32-36 Length 38-40;
RUN;
* Using the FORMAT= option in the TABLE statement;
PROC TABULATE DATA = boats;
CLASS Locomotion Type;
VAR Price Length;
TABLE Locomotion ALL,
MEAN * (Price*FORMAT=DOLLAR6.2 Length*FORMAT=6.0) * (Type ALL);
TITLE 'Price and Length by Type of Boat';
RUN;

BOSS级汇报表格呈现了...

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

我只能感慨,不愧是商业软件啊,用户需求考虑的真的是特别的周到...这种费时费力做汇报表格的事情也被搞定了,强悍。

SAS里面的报告:REPORT

还有一个REPORT,看到有TABULATE的时候我已经不奇怪并略略的有些期待一个做报告的模块出现了。这东西基本就是前面几个的超级混合体,反正你想搞到的汇报模式总是能够搞出来的。

又是一堆数据:

17 sci 9 bio 28 fic 50 mys 13 fic 32 fic 67 fic 81 non 38 non
53 non 16 sci 15 bio 61 fic 52 ref 22 mys 76 bio 37 fic 86 fic
49 mys 78 non 45 sci 64 bio 8 fic 11 non 41 fic 46 ref 69 fic
34 fic 26 mys 23 sci 74 ref 15 sci 27 fic 23 mys 63 fic 78 non
40 bio 12 fic 29 fic 54 mys 67 fic 60 fic 38 sci 42 fic 80 fic

然后一堆SAS代码:

DATA books;
INFILE 'c:\MyRawData\LibraryBooks.dat';
INPUT Age BookType $ @@;
RUN;
*Define formats to group the data;
PROC FORMAT;
VALUE agegpa
0-18 = '0 to 18'
19-25 = '19 to 25'
26-49 = '26 to 49'
50-HIGH = ' 50+ ';
VALUE agegpb
0-25 = '0 to 25'
26-HIGH = ' 26+ ';
VALUE $typ
'bio','non','ref' = 'Non-Fiction'
'fic','mys','sci' = 'Fiction';
RUN;
*Create two way table with Age grouped into four categories;
PROC FREQ DATA = books;
TITLE 'Patron Age by Book Type: Four Age Groups';
TABLES BookType * Age / NOPERCENT NOROW NOCOL;
FORMAT Age agegpa. BookType $typ.;
RUN;
*Create two way table with Age grouped into two categories;
PROC FREQ DATA = books;
TITLE 'Patron Age by Book Type: Two Age Groups';
TABLES BookType * Age / NOPERCENT NOROW NOCOL;
FORMAT Age agegpb. BookType $typ.;
RUN;

然后一堆交叉计数的结果就出来了:
2013-12-09 16_33_53-The Little SAS Book(Fourth).PDF - Adobe Reader

当然,简单的计算和分类统计也不在话下:

DATA natparks;
INFILE 'c:\MyRawData\Parks.dat';
INPUT Name $ 1-21 Type $ Region $ Museums Camping;
RUN;
*Statistics in COLUMN statement with two group variables;
PROC REPORT DATA = natparks NOWINDOWS HEADLINE;
COLUMN Region Type N (Museums Camping),MEAN;
DEFINE Region / GROUP;
DEFINE Type / GROUP;
TITLE 'Statistics with Two Group Variables';
RUN;
*Statistics in COLUMN statement with group and across variables;
PROC REPORT DATA = natparks NOWINDOWS HEADLINE;
COLUMN Region N Type,(Museums Camping),MEAN;
DEFINE Region / GROUP;
DEFINE Type / ACROSS;
TITLE 'Statistics with a Group and Across Variable';
RUN;

可以得到一个看起来很fancy的表格:
2013-12-09 16_34_41-The Little SAS Book(Fourth).PDF - Adobe Reader

SAS数据总结综述

我的感觉是,MEANS, TABULATE和REPORT这三个模块各有千秋,基本就是可以替代EXCEL的数据透视表,虽然效率上说不好谁比谁高...随便哪一个用习惯了就好,反正又不是天天出政府报告的,我就懒得深究了。

 

Categories
日常应用

七天搞定SAS(二):基本操作(判断、运算、基本函数)

本系列连载文章:

继续,今天开始注重变量操作。

SAS生成新变量

SAS支持基本的加减乘除,值得一提的是它的**代表指数,而不是^。

* Modify homegarden data set with assignment statements;
DATA homegarden;
INFILE 'c:\MyRawData\Garden.dat';
INPUT Name $ 1-7 Tomato Zucchini Peas Grapes;
Zone = 14;
Type = 'home';
Zucchini = Zucchini * 10;
Total = Tomato + Zucchini + Peas + Grapes;
PerTom = (Tomato / Total) * 100;
RUN;
PROC PRINT DATA = homegarden;
TITLE 'Home Gardening Survey';
RUN;

但是如果有缺失值的话,SAS的加法会生成缺失值而不是自动按0处理。为了避免这一点,应该调用sum()函数而不是直接写+。

SAS的函数调用很简单:

AvgScore = MEAN(Scr1, Scr2, Scr3, Scr4, Scr5);
DayEntered = DAY(Date);
Type = UPCASE(Type);

函数有文本类、数字类、日期类等等。

SAS文本类函数

  • ANYALNUM(arg,start):返回第一次出现任意数字或字母的位置,可选开始位置start。
  • ANYALPHA(arg,start):返回第一次出现任意字母的位置,可选开始位置start。
  • ANYDIGIT(arg,start):返回第一次出现任意数字的位置,可选开始位置start。
  • ANYSPACE(arg,start):返回第一次出现任意空白的位置,可选开始位置start。
  • CAT(arg-1,arg-2,...arg-n):连接字符串,留下头尾空白。
  • CATS(arg-1,arg-2,...arg-n):连接字符串,删除头尾空白。
  • CATX('separator-string', arg-1,arg-2,...arg-n):连接字符串,删除头尾空白并用指定标点连接。
  • COMPRESS(arg, 'char'):移除字符串中的空格和可选字符。
  • INDEX(arg, 'string') :返回指定字符在变量中的位置。
  • LEFT(arg) :字符串左对齐。
  • LENGTH(arg):返回字符串长度,不考虑尾部空格。
  • PROPCASE(arg) :首字母大写。
  • SUBSTR(arg,position,n):从字符串中提取指定开始位置指定长度字符。
  • TRANSLATE(source,to-1, from-1,...to-n,from-n):替换字符。
  • TRANWRD(source,from,to) :替换字符串。
  • TRIM(arg):删除尾部空白。
  • UPCASE(arg):替换成大写。

SAS数值函数

  • INT(arg):返回整数。
  • LOG(arg):自然对数。
  • LOG10(arg) :10为底对数。
  • MAX(arg-1,arg-2,...arg-n) :最大值
  • MEAN(arg-1,arg-2,...arg-n) :均值
  • MIN(arg-1,arg-2,...arg-n) :最小值
  • N(arg-1,arg-2,...arg-n) :非缺失值个数
  • NMISS(arg-1,arg-2,...arg-n) :缺失值个数。
  • ROUND(arg, round-off-unit) :保留几位小数。
  • SUM(arg-1,arg-2,...arg-n):求和。

SAS日期函数

  • DATEJUL(julian-date) :标准julian日期到SAS日期。
  • DAY(date):返回「日」。
  • MDY(month,day,year) :年月日到SAS日期。
  • MONTH(date) :返回「月」。
  • QTR(date):返回季度。
  • TODAY():今日
  • WEEKDAY(date):返回周几(周日为1)。
  • YEAR(date):返回「年」。
  • YRDIF(start-date,end- date,’ACTUAL’):返回相差年份。

SAS中判断语句

如果,则:

IF then: IF Model = 'Mustang' THEN Make = 'Ford';

还可以执行多项命令,需要嵌套do;可以用and和or:

IF Year IF Model = 'Corvette' OR Model = 'Camaro' THEN Make = 'Chevy';
IF Model = 'Miata' THEN DO;
Make = 'Mazda';
Seats = 2;
END;

还可以if else:

IF Cost = . THEN CostGroup = 'missing';
ELSE IF Cost ELSE IF Cost ELSE CostGroup = 'high';

用if可以选择数据子集:

IF Sex = 'f'; IF Sex = 'm' THEN DELETE;

SAS中保留和累加

比如要求累加值(等价于R里面的cumsum),需要:

* Using RETAIN and sum statements to find most runs and total runs;
DATA gamestats;
INFILE 'c:\MyRawData\Games.dat';
INPUT Month 1 Day 3-4 Team $ 6-25 Hits 27-28 Runs 30-31;
RETAIN MaxRuns;
MaxRuns = MAX(MaxRuns, Runs);
RunsToDate + Runs;
RUN;
PROC PRINT DATA = gamestats;
TITLE "Season's Record to Date";
RUN;

看一眼最终数据:

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

累加效果出来了~还有一栏是迄今最大值。这也是我觉得sas和R很不同的一点:sas是指针式操作,一行行往下读;而在R里面我们更多是向量或者矩阵式运算,感觉还是有所区别的...

SAS的数组操作

这个就更有点矩阵的味道了,不过还是偶尔感觉怪怪的...感觉数据整理和操纵方面,SAS还是比不上R灵活...

例子为替换为缺失值:

* Change all 9s to missing values;
DATA songs;
INFILE 'c:\MyRawData\WBRK.dat';
INPUT City $ 1-15 Age domk wj hwow simbh kt aomm libm tr filp ttr;
ARRAY song (10) domk wj hwow simbh kt aomm libm tr filp ttr;
DO i = 1 TO 10;
IF song(i) = 9 THEN song(i) = .;
END;
RUN;
PROC PRINT DATA = songs;
TITLE 'WBRK Song Survey';
RUN;

这样9就全部替换为缺失值了。把后面10列认为是一个数组,可以直接操作。

SAS还有若干变量名的快捷方式,暂不赘述了...