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(二):基本操作(判断、运算、基本函数)

本系列连载文章:

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

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还有若干变量名的快捷方式,暂不赘述了...