众所周知,SAS DATA步的特性是一条条数据处理,相对将数据一次性加载到内存的R语言来说,在同样的机器配置下,可以处理更大量的数据。理论上 来讲,无论多大的数据,DATA步都可以处理,只不过是时间的问题。 优势明显,劣势同样明显,那就是无法最大限度的利用机器性能。针对变量不多,大小适中的数据,如果不是非常复杂的处理过程,那么机器性能的提升对于处理时间的减少几乎没什么大的影响。因为都是一条一条数据处理,从硬盘读取数据的速度相差不大(同是机械的情况),简单的计算:加减乘除,单核CPU跟四核CPU相差不大;因此最终的运行时间也相差不大。 那么,面对超大数据集(上亿条数据),需要复杂的计算,有没有办法提高DATA步的处理效率?当然有,那就是并行处理。 因DATA步本身不支持多线程并行处理,那就只能另辟蹊径,采取多进程的方式解决。整体的流程简单来说,就是下面这张图: 具体来说,就有以下几个步骤:
干货开始 1 拆分数据集 为了提高运行效率,将拆分的过程和处理的过程合并。即在将超大数据集拆分成N个子集的时候,就对这N个子集完成相应的处理,得到的结果即是可以合并的结果。拆分的方法使用两个DATA SET OPTION:FIRSTOBS=,OBS=来完成。 所以,第一步就是完成如何拆分,即是要确定每个拆分子集的FIRSTOBS和OBS。要想确定这两个参数,先得知道要拆分的数据集的观测数和要拆分为多少个子集。通过下面的方式完成: /*定义要拆分的个数*/ %let num_partition=10; /*定义要处理的数据集*/ %let dsn=out_lib.test_data; /*获取要处理的数据集的观测数,并输出到宏变量*/ data _null_; did=open('&dsn'); obs=attrn(did,'nobs'); call symput('dsobs',obs); re=close(did); run; /*计算平均每个子集的数据量,取整*/ %let tp_size=%sysfunc(int(&dsobs/&num_partition)); /*计算整除后的余量*/ %let tp_mod=%sysfunc(mod(&dsobs,&num_partition)); /*确定每个拆分子集的FIRSTOBS和OBS*/ %macro split_infor; %do i=1 %to &num_partition; %let j=%eval(&i-1); %if &i=1 %then %let tp_start&i=1; %else %let tp_start&i=%eval(&&tp_end&j+1); %if %eval(&i le &tp_mod) %then %let tp_end&i=%eval(&&tp_start&i+&tp_size); %else %let tp_end&i=%eval(&&tp_start&i+&tp_size-1); %end; %mend split_infor; 生成的拆分信息保存在宏变量tp_start1, tp_end1, tp_start2, tp_end2, tp_start3, tp_end3, …中。注意此处生成的宏变量是局部的,只在宏内有效。为了方便说明,此处单独拿出来。 测试数据集out_lib.test_data是基于sashelp.class生成而来: data out_lib.test_data; set sashelp.class; do i=1 to 5300000; output; end; run; 2 生成SAS处理代码及批处理文件 为了程序的统一性,将要进行的数据处理操作也单独放在一个宏里面,如下所示: /*要进行的数据处理操作(此处只举例进行简单的求和及赋值运算)*/ %macro process_content; sum=sum(weight+height); test='character test'; %mend process_content; 接下来完成生成针对每个数据子集的处理操作SAS代码及批处理文件。下面这步就一次性完成了数据拆分及处理操作。 /*定义一个存储路径(为了方便,要处理的数据也放在此处;生成的结果数据也放在此处)*/ %let out_path=D:\SAS_DATA; /*定义SAS可执行程序所在的全路径*/ %let sas_path='D:\ProgramFiles\SASHome\SASFoundation\9.4\sas.exe'; %macro generate_code; %do i=1 %to &num_partition; filename temp1 '&out_path\sasjob&i..sas'; filename temp2 '&out_path\runsasjob&i..bat'; data _null_; file temp1; put 'libname out_lib '&out_path';'; put 'data out_lib.sample&i;'; put 'set &dsn(firstobs=&&tp_start&i obs=&&tp_end&i);'; put '%bquote(%process_content)'; put 'run;'; run; data _null_; file temp2; put '%bquote(&sas_path) -sysin %str(%”)&out_path\sasjob&i..sas%str(%”) -log %str(%”)&out_path\sasjoblog&i..log%str(%”)'; run; %end; %mend generate_code; 注:在Windows中,完成批处理SAS代码的文件为bat文件,其语法格式为: “SAS可执行程序全路径” -sysin “要执行的SAS程序全路径” -log “生成的日志存储文件” 3 调用执行批处理文件 利用systask command语句调用bat文件,启动SAS进程运行SAS代码得到结果。 %macro execute_job; %do i=1 %to &num_partition; systask command '&out_path\runsasjob&i..bat' taskname='job&i'; %end; %mend execute_job; 4 合并结果数据 利用waitfor语句等待所有的批处理进程执行完毕,然后通过DATA步合并子结果数据集。 %macro combine_data; waitfor _all_ %do i=1%to &num_partition; job&i %end; ; data data_result; set %doi=1%to &num_partition; out_lib.sample&i %end; ; run; %mend combine_data; 至此,整个过程就已介绍完毕,完整的过程还请大家动手自己尝试完成。需要注意的是,我为了方便介绍说明,将部分过程拆分出来单独放到一个宏里面,因此在宏中生成的局部宏变量在其他宏就无法使用。所以,需要大家将整个流程串起来,放到一个大的Macro中就好了。 作者:辛岩,从事了多年的SAS数据分析挖掘工作,担任过项目经理、技术顾问、培训讲师等职务,拥有丰富的项目实战经验。 |
|