SAS:用一列求另一列的总和的最佳方法是什么?



我想找出在SAS中执行组的最佳方式,这样我就可以执行一些基准测试。我能想到的最简单的两种方法是Proc SQLProc means。以下是proc sql中的示例

proc sql noprint; /* took 6 mins */ create table summ as select id, sum(val) from randint group by id ; quit;

我认为有一些方法可以让快速运行

  • 使用sasfile命令首先将数据加载到内存中
  • id上创建索引

我还可以使用其他选项吗?我应该打开任何SAS选项以使运行尽可能快?我既不依赖proc-sql,也不依赖proc-means,所以如果有更快的方法,我很想知道它!!!

我的设置代码如下

options macrogen;
options obs=max sortsize=max source2 FULLSTIMER;
options minoperator SASTRACE=',,,d' SASTRACELOC=SASLOG;
options compress = binary NOSTSUFFIX;
options noxwait noxsync;
options LRECL=32767;
proc fcmp outlib=work.myfunc.sample;
function RandBetween(min, max);
return (min + floor((1 + max - min) * rand("uniform")));
endsub;
run;
options cmplib=work.myfunc;
data RandInt;
do i = 1 to 250000000;
id = RandBetween(1, 2500000);
val = rand("uniform");
output;
end;
drop i;
run;

我的SAS比较宏如下

%macro sasbench(dosql = N); %macro _; %mend;
%if &dosql. = Y %then %do;
proc sql noprint; /* took 6 mins */
create table summ as select
id,
sum(val)
from 
randint
group by 
id
;
quit;
%end;
proc means data=randint  sum noprint;
var val ;
class id;
output out = summmeans(drop=_type_ _freq_) sum = /autoname;
run;
%mend;
%sasbench();
/**/
/*sasfile randint load;*/
/*%sasbench();*/
/*sasfile randint close;*/
proc datasets lib=work;
modify randint;
INDEX CREATE id / nomiss;
run;
%sasbench();

sasfile只有在整个数据集能够适应会话内存限制并且数据集将被多次使用的情况下才是一个好处。如果您的基准测试在同一个sasfile上包含多个运行/不同的技术,我想这是有意义的。

如果数据未按id排序,则id上的索引会有所帮助。当数据集按id预排序时,id列元数据将按标志集排序,过程可以使用该标志集进行内部优化,但不能保证。对于索引,请使用option msglevel=i在处理过程中在日志中获取有关索引选择的信息性消息。

最快的方法是直接寻址,但需要足够的ram来处理作为数组索引的最大id值:

  • array ids(250000000) _temporary_
  • ids(id) + value

下一个最快的方法可能是基于数组的手工编码哈希:

  • 在SAS会议记录中搜索Paul Dorfman的论文

下一个最快的散列方法可能是具有suminc键的散列组件对象。

数据步骤经过编辑以与注释保持一致

data demo_data;
do rownum = 1 to 1000;
id = ceil(100*ranuni(123));     * NOTE: 100 different groups, disordered;
value = ceil(1000*ranuni(123)); * NOTE: want to sum value over group, for demonstration individual values integers from 1..1000;
output;
end;
run;
data _null_;
if 0 then set demo_data(keep=id value);                         %* prep pdv ;
length total 8;                                                 %* prep keysum variable ;
call missing (total);                                           %* prevent warnings ;
declare hash ids (ordered:'a', suminc:'value', keysum:'total'); %* ordered ensures keys will be sorted ascending upon output ;
ids.defineKey('id');
*ids.defineData('id');                                           % * not having a defineData is an implicit way of adding only the keys as data, only data + keysum variables are .output;
ids.defineDone();
* read all records and touch each hash key in order to perform tacit total+value summation;
do until (end);
set demo_data end=end;
if ids.find() ne 0 then ids.add();
end;
ids.output(dataset:'sum_value_over_id'); * save the summation of each key combination;
stop;
run;

注意:keysum变量只能有一个。

如果suminc变量设置为始终为1而不是值,那么keysum将是计数而不是总数。

通过hash获得组上的sum和count需要对count和sum变量进行显式定义Data,以及稍微不同的语句,例如:

declare hash ids (ordered:'a');
...
ids.defineData('id', 'count', 'total');
...
if ids.find() ne 0 then do; count=0; total=0; end;
count+1;
total+value;
ids.replace();
...

然而,如果已知值总是自然数,并且已知组大小<10组大小限制您可以使用value + 10-组大小限制的suminc对计数进行数字编码,并通过使用count = (total - int(total)) * 10的组大小限制来处理输出数据来对计数进行数值解码

对于已排序的数据,最快的方法很可能是具有累加的DOW循环。

proc sort data=foo;
by id;
data sum_value_over_id_v2(keep=id total);
do until (last.id);
set foo; 
by id;
total = sum(total, value);
end;
run;

您可能会发现I/O是性能的最大组成部分。

最佳答案因应用程序而异。在您的示例中,至少在我的机器上,PROC SQL的性能明显优于PROC MEANS,但在很多情况下它不会这样做。在这种情况下,它之所以能够做到这一点,是因为它在幕后构建哈希表,这很可能非常快-只需要一次数据传递。

如果你有空间存储整个数据集,你当然可以通过SASFILE将整个数据集放入内存来加快速度。不过,您必须将其存储在内存中才能开头,这很可能;仅仅为了这个目的把它读到记忆中并没有真正的帮助,因为你无论如何都在读。

正如Richard所指出的,有很多方法可以做到这一点。我认为PROC SQL通常是最快的,或者在简单情况下与最快的类似,这既是因为它是多线程的(而不是单线程的数据步骤),也是因为它有一个快速的哈希表后端。

PROC MEANS通常也是有竞争力的,您在示例中显示的情况对它来说几乎是最糟糕的情况,因为它有大量的类变量,所以我认为它可能在磁盘上创建一个临时表。它也是多线程的。将类变量类别从2500000减少到2500,PROC MEANS的获得速度会比PROC SQL快一点(但在误差范围内)。

哈希表或DoW循环中的数据步长累积有时会优于上述两者,有时则不然,这同样取决于数据。在这里,它的表现确实略好。数据步骤累积的代码往往有点复杂,这就是为什么我通常不鼓励它,除非节省了大量的代码(通常情况下,维护更多的代码会更糟)。CCD_ 19和CCD_。但是,在性能至关重要且这些解决方案恰好优越的应用程序中,走这条路可能是值得的,尤其是在数据步骤有帮助的情况下。当然,哈希表方法仅限于在内存中拟合结果,尽管这通常是可管理的。


最终,我鼓励您使用任何最容易维护但仍能提供足够性能的方法;并在可能的情况下尝试与其他代码保持自我一致。如果您的大部分代码都是用SQL编写的,那可能没问题。SASFILE和索引可能不需要,除非您正在做比上面更复杂的事情。在许多情况下,求和实际上是比I/O更多的工作。最终不要过于复杂:程序员的时间和QA的难度应该胜过基本性能,除非你说的是几个小时的差异。如果你是,那么只需在你的实际用例上运行测试,看看什么最有效。

如果假设数据已排序,则这是另一种解决方案

data sum_value_over_id_v2(keep=id total);
set a.randint(keep=id val); 
by id;  
total + val;  
if last.id then do;
output;
total = 0;
end;
drop val;
run;

相关内容

最新更新