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

时间:2017-11-13 00:06:21

标签: algorithm 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手段,所以如果有更快的方法,那么我很想知道它!

我的设置代码如下

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();

3 个答案:

答案 0 :(得分:2)

如果整个数据集可以适应会话ram限制并且数据集将被多次使用,那么

sasfile只是一个好处。我想如果您的基准测试在同一个sasfile中包含多个运行/不同的技术,那么这将是有意义的。

如果数据未按ID排序,则id的索引会有所帮助。当数据集按id预先排序时,id列元数据将具有设置的sortedby标志,该过程可用于其自身的内部优化,但是不能保证。对于索引,使用option msglevel=i在日志中获取有关处理期间索引选择的信息性消息。

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

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

下一个最快的方法可能是基于手动编码阵列的散列:

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

下一个最快的哈希方式可能是具有键suminc的哈希组件对象。

编辑了DATA Step以与评论保持一致

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而不是value,则keysum将是count而不是total。

通过散列获取group和sum和count将需要一个显式的defineData用于count和sum变量以及稍微不同的语句,例如:

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 -group size limit 的和,对数进行数字编码,并通过使用{{处理输出数据来数字解码计数1}} 组大小限制

对于排序数据,最快的方式很可能是积累的DOW循环。

count = (total - int(total)) * 10

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

答案 1 :(得分:1)

最佳答案因应用程序而异。在您的示例中,PROC SQL至少在我的计算机上明显优于PROC MEANS,但有很多情况下它不会这样做。在这种情况下,它能够在幕后构建哈希表,这很可能,而且非常快 - 只需要通过数据就可以完成所有这些操作。

如果您有足够的空间来存储整个数据集,那么通过使用SASFILE将完整的数据集放入内存中,您当然可以加快速度。尽管如此,你必须在内存中使用开始;只是为了这个目的将其读入内存并不会真正有用,因为无论如何你都在阅读它。

正如理查德所说,有很多方法可以做到这一点。我认为PROC SQL通常是最快或类似于最简单的情况,因为它是多线程的(而不是数据步骤是单线程),因为它有一个快速的哈希表后端。

PROC MEANS通常也会具有竞争力,你在示例中显示的情况几乎是最糟糕的情况,因为它有大量的类变量所以我认为它可能会创建一个临时表磁盘。它也是多线程的。将类变量类别减少到2500而不是2,500,000,并且PROC MEANSPROC SQL快一点(但在误差范围内)。

在散列表或DoW循环中的数据步骤累积有时会优于上述两种情况,有时也不会再次取决于数据。在这里,它的表现略胜一筹。数据步骤累积的代码往往有点复杂,这就是为什么我通常不鼓励它,除非节省很多(通常更多的代码需要维护)。 PROC MEANSPROC SQL需要较少的维护而不需要了解。但是在性能至关重要并且这些解决方案碰巧优越的应用中,走这条路线可能是值得的,特别是如果数据步骤有用的话。当然,哈希表方法仅限于将结果拟合到内存中,尽管通常是可管理的。

最终,我鼓励你使用最容易维护的方法,但仍能提供足够的性能;并且在可能的情况下尝试与其他代码保持一致。如果你的大部分代码都在SQL中,那可能就好了。可能不需要SASFILE和索引,除非您执行的操作比上面提到的更复杂。在很多情况下,求和实际上比I / O更多。不要过度复杂化,最终:程序员的时间和QA的难度应该胜过基本的表现,除非你说几个小时的差异。如果您是,那么只需对您的实际用例进行测试,看看哪种方法效果最好。

答案 2 :(得分:0)

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

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;