SAS程序员会经常需要把一个数据集分成几个小的数据集。这是一件很容易就能做到的事情,用where= 选项或者FIRSTOBS=/OBS= 的组合既可以。但是拆分数据集产生了多个文件这意味着占用了更多的硬盘空间,使用了更多的IO操作。而IO操作和硬盘资源的访问对SAS来说是最昂贵的开销。
但是如果你的领导分配你这个任务,你肯定也不会在上面花多少心思。现在我们假设你需要按照数据集的某列的值来拆分这个数据集。你肯定会这么写,例子如下:
- DATA out_Asia;
- set sashelp.cars(where=(origin='Asia'));
- run;
- DATA out_Europe;
- set sashelp.cars(where=(origin='Europe'));
- run;
- DATA out_USA;
- set sashelp.cars(where=(origin='USA'));
- run;
我(原文作者)得承认,现在这不是最有效率或者最优雅的方法。不过这是SAS新手们最容易想到的方法。
上面这段代码是比较简单的,尤其是只有3个不同的值。但是如果用于区分的值有很多这样就需要考虑很多的情况,非常容易出错。这个时候我通常就会使用PROC SQL和SELECT INTO 把值赋给一个宏变量。举个简单的例子:
- proc sql;
- select distinct ORIGIN into :valList separated by ',' from SASHELP.CARS;
- quit;
这段代码创建了一个宏变量VALLIST,[size=13.9200000762939px]并且将所有ORIGIN用逗号区分赋值给它。[size=13.9200000762939px]
但是我们可以用SAS函数改善输出,增加其他的代码把值织入到代码逻辑中去。举个例子,我们可以用CAT函数来连接查询来的值和SAS关键字。得出的结果是纯SAS代码可以直接在宏代码中被引用或者执行。我将共享我最终的代码,并且将代码分成几部分便于读者理解。
- /* define which libname.member table, and by which column */
- %let TABLE=sashelp.cars;
- %let COLUMN=origin;
- proc sql noprint;
- /* build a mini program for each value */
- /* create a table with valid chars from data value */
- select distinct
- cat("DATA out_",compress(&COLUMN.,,'kad'),
- "; set &TABLE.(where=(&COLUMN.='", &COLUMN.,
- "')); run;") into :allsteps separated by ';'
- from &TABLE.;
- quit;
- /* macro that includes the program we just generated */
- %macro runSteps;
- &allsteps.;
- %mend;
- /* and...run the macro when ready */
- %runSteps;
下面是对PROC SQL部分的一些说明:
- SELECT DISTINCT 保证了变量的每个值都只有一条记录。
- CAT函数把一系列字符串进行了连接(注意到CAT函数有一些变异函数比如CATX,CATS,CATT等,这些函数会将字符串的空白部分去掉)。在这个例子中我想保留所有在数据中出现的空白部分,因为我们要做相等判断。
- 程序使用每个值作为输出数据集名称的后缀("OUT_datavalue")。SAS数据集名称只能包含字母和数字,所以这里我使用compress函数删掉所有的空格。kad选项表示值保留英文字母和数字。
- 所有的程序语句最后赋值给了allsteps这个宏变量,我只需要在SAS程序中引用这段代码就可以运行它了。我选择了另外一种方式,将代码包在了宏里面。这样便于控制这段可执行代码的作用域和位置。
“By each value of a variable”仅仅只是拆分数据集的一种条件。我遇到过其他许多拆分数据集的规则:
- 通过观测的数目,比如将一个300万条观测的数据集拆分成3个100万的
- 通过排名或者百分数
- 通过日期拆分