【问题标题】:cumulative max by group in SAS or PROC SQLSAS 或 PROC SQL 中按组的累积最大值
【发布时间】:2018-02-06 17:44:21
【问题描述】:

我想计算按另一列分组的累积最大值。

假设我有这些数据:

data have;
input grp $ number;
datalines;
a 3
b 4
a 5
b 2
a 1
b 8
;

我想要的输出是:

data want;
input grp $ cummax;
a 3
b 4
a 5
b 4
a 5
b 8
;

我的真实案例将涉及多个分组列 + 过滤器,理想情况下,此累积最大值将同时在多个列上计算。

我主要关心的是计算效率,因为我将在包含 10 到数亿行的表上运行它。欢迎使用 Proc SQL 或原生 SAS。

如有必要,行可能会被打乱。

系统信息

proc product_status;run;
For Base SAS Software ...
   Custom version information: 9.3_M2
   Image version information: 9.03.01M2P080112
For SAS/STAT ...
   Custom version information: 12.1
   Image version information: 9.03.01M0P081512
For SAS/GRAPH ...
   Custom version information: 9.3_M2
For SAS/CONNECT ...
   Custom version information: 9.3_M2
For SAS OLAP Server ...
   Custom version information: 9.3_M1
For SAS Enterprise Miner ...
   Custom version information: 12.1
   Image version information: 9.03.01M0P081512
For SAS Integration Technologies ...
   Custom version information: 9.3_M2
For SAS/ACCESS Interface to Oracle ...
   Custom version information: 9.3_M1
For SAS/ACCESS Interface to PC Files ...
   Custom version information: 9.3_M2
    proc setinit;run;
Product expiration dates:
---Base SAS Software                                                                                    31JUL2018  
---SAS/STAT                                                                                             31JUL2018  
---SAS/GRAPH                                                                                            31JUL2018  
---SAS/CONNECT                                                                                          31JUL2018  
---SAS OLAP Server                                                                                      31JUL2018  
---SAS Enterprise Miner                                                                                 31JUL2018  
---MDDB Server common products                                                                          31JUL2018  
---SAS Integration Technologies                                                                         31JUL2018  
---SAS Enterprise Miner Server                                                                          31JUL2018  
---SAS Enterprise Miner Client                                                                          31JUL2018  
---Unused OLAP Slot                                                                                     31JUL2018  
---SAS Enterprise Guide                                                                                 31JUL2018  
---SAS/ACCESS Interface to Oracle                                                                       31JUL2018  
---SAS/ACCESS Interface to PC Files                                                                     31JUL2018  
---SAS Metadata Bridges for Informatica                                                                 31JUL2018  
---SAS Metadata Bridges for Microsoft SQL Server                                                        31JUL2018  
---SAS Metadata Bridge for Oracle                                                                       31JUL2018  
---SAS Workspace Server for Local Access                                                                31JUL2018  
---SAS Workspace Server for Enterprise Access                                                           31JUL2018  
---SAS Table Server                                                                                     31JUL2018  
---DataFlux Trans DB Driver                                                                             31JUL2018  
---SAS Framework Data Server                                                                            31JUL2018  
---SAS Add-in for Microsoft Excel                                                                       31JUL2018  
---SAS Add-in for Microsoft Outlook                                                                     31JUL2018  
---SAS Add-in for Microsoft PowerPoint                                                                  31JUL2018  
---SAS Add-in for Microsoft Word                                                                        31JUL2018

【问题讨论】:

  • 您有 SAS ETS 许可吗?即使是简单的案例,您也尝试过什么?您应该使用几个分组变量来扩展您的样本数据,以更好地反映您的实际情况。
  • 我有很好的机会,但我不知道如何检查它:)。我提供了简短的数据来说明我的观点,但你说得对,我将用一个更复杂的案例进行编辑。
  • proc setinit;run; 查看已获得许可的内容,proc product_status;run; 查看已安装的内容。
  • 我在上面添加了我的系统信息,我需要更多时间来想出一个复杂的示例表,同时解决简单的示例会有很大的帮助。
  • 以下解决方案有效。除非您可以通过并使用一些 DBMS 函数,否则我不会在这里推荐 SQL。您没有允许您使用 PROC EXPAND 的 SAS/ETS。

标签: sas


【解决方案1】:

使用HASH 对象来存储每个变量和组组合的最大值。这将允许您单次通过您的数据集并编写一些您可以根据组和变量的数量进行扩展的代码。

这不需要在大型数据集上代价高昂的排序。

测试数据

data example;
format grp1-grp5 $1.;
array grp[5];
array val[5];
do rows=1 to 1000000;
    do i=1 to 5;
        r = ceil(ranuni(1)*5);
        grp[i] = substr("ABCDE",r,1);
    end;
    do j=1 to 5;
        val[j] = 10*rannor(1);
    end;
    output;
end;
keep grp: val:;
run;

计算累积最大值的数据步

data want;
set example;
array val[5];
array max[5];
if _n_ = 1 then do;
    declare hash mx();
    rc = mx.defineKey('grp1','grp2','grp3','grp4','grp5');
    rc = mx.definedata('max1','max2','max3','max4','max5');
    rc = mx.definedone();
end;

rc = mx.find();
/*No Max for this combination -- add it*/
if rc then do;
    do i=1 to 5;
        max[i] = val[i];
    end;
end;

/*Update Max Values*/
do i=1 to 5;
    if val[i] > max[i] then
        max[i] = val[i];
end;

/*Update Hash*/
rc = mx.replace();

drop rc i;
n = _n_; /*This is for testing*/
run;

使用测试变量n,我们可以对保持原始顺序的组进行排序,看看它是否有效。 (提示,确实如此)。

proc sort data=want;
by grp: n;
run;

【讨论】:

  • 我将行数增加到 100,000,000(4.5G 输入大小)。我的笔记本电脑计算 1:55.89 中的最大值。
  • 我测试了它并且它有效。代码看起来也很优雅。但可能是我不太了解哈希对象及其方法。让我感到困惑的是,如果您在 mx 哈希中找不到组合键,那么您将 val 分配给 max 变量。但是您并没有真正将它们添加到哈希中,对于下一步更新 max 也是如此。这些值如何添加到 mx 中?如果您能进一步解释一下,这将非常有帮助。谢谢!
  • replace() 方法将添加不存在的值。您可以将 mx.add() 添加到该块,但您不需要。
  • 现在我明白了,它与 find() 一起工作。当组合已经在散列中时,您从散列中获取最大值,这将是前一个最大值,并通过 find() 将它们分配给当前变量。然后将当前 val 变量与等于前一个最大值的当前最大值进行比较。如果它更大,则分配新的最大值并使用 replace() 更新散列中的新值。非常清晰优雅,谢谢!
【解决方案2】:
proc sort data=have;
by grp;
run;

data want;
   set have;
   by grp;
   retain max;
   max=ifn(first.grp,number,max(number,max));
run;

使用没有排序的哈希

data want;
  if _n_=1 then do;
  declare hash h();
  h.definekey('grp');
  h.definedata('value');
  h.definedone();
  end;
  set have;
  if h.find()^=0 then do;
  h.add(key:grp,data:number);
  max=number;
  end;
  else do;
     max=max(number,value);
     h.replace(key:grp,data:number);
  end;
  drop  value number;
run;

【讨论】:

  • 我喜欢带散列的解决方案,它很简单,也保留了原始顺序
【解决方案3】:

类似下面的东西会起作用。如果您想保留原始订单,请添加一个行计数器并使用它:

proc sort data=have; 
by grp; 
run;

data new; 
drop newnum; 
set have;            
by grp;   
retain newnum;                                                  
if first.grp then newnum = number; 
if number > newnum then newnum=number;  
else number=newnum;
run;          

【讨论】:

  • 谢谢它有效,我会看看是否有人在效率方面与竞争对手一起提供并验证:)
  • 可能是如果 number > newnum then newnum=number;可以改为 else if number > newnum then newnum=number;
  • 这个解决方案很简单,效果很好。但是,它需要排序,因此不保留原始数据的顺序
【解决方案4】:

我围绕@DomPazz 的解决方案构建了一个宏函数,可以选择要分组的列、要计算的列以及最后要删除或保留的列。

我认为包含的示例很简单。

我在底部加入了我在cummax 中使用的简短便利宏函数。

*------------------------------------------------------------;
* CUMMAX                                                     ;
* Compute a cumulative max on 1 or several variables grouped ;
* by one or several variables;                               ;
*------------------------------------------------------------;
/* EXAMPLE:
data have;
format grp1-grp2 $1.;
array grp[2];
array val[3];
do rows=1 to 20;
    do i=1 to 2;
        r = ceil(ranuni(1)*2);
        grp[i] = substr("AB",r,1);
    end;
    do j=1 to 3;
        val[j] = 10*rannor(1);
    end;
    output;
end;
keep grp: val:;
run;

%cummax(have,grp=grp1 grp2,val=val1 val2,out= want1)
%cummax(have,grp=grp1,val=val1,drop=grp2 val3,out= want2)
%cummax(have,grp=grp1,val=val1,keep= val2,out= want3)
*/

%macro cummax
(data  /* source table */
,grp=  /* variables to group on */
,val=  /* variables to compute on */
,keep= /* variables to keep additionally to grp and computed columns, don't use with drop */
,drop= /* variables to drop, don't use with keep */
,out=  /* output table */
);

/* default output */
%if not %length(&out) %then %let out = &data;

/* rework keep and drop */
%local n_val max_val;
%let n_val   = %list_length(&val);
%let max_val = %list_fix(&val,suffix=_cmax);
%if %length(&keep) %then %let keep = (keep= &keep &grp &max_val );
%if %length(&drop) %then %let drop = (drop= &drop);

/* data step */
data &out&keep&drop;
set &data;
array val[&n_val] &val;
array max[&n_val] &max_val;

if _n_ = 1 then do;
    declare hash mx();
    rc = mx.defineKey(%list_quote_comma(&grp));
    rc = mx.definedata(%list_quote_comma(&max_val));
    rc = mx.definedone();
end;

rc = mx.find();
/*No Max for this combination -- add it*/
if rc then do;
    do i=1 to &n_val; /* %list_length(&val) */
        max[i] = val[i];
    end;
end;

/*Update Max Values*/
do i=1 to &n_val;
    if val[i] > max[i] then
        max[i] = val[i];
end;

/*Update Hash*/
rc = mx.replace();

drop rc i;
run;

%mend;


*---------------------------------------------------------------;
* LIST_LENGTH                                                   ;
* Length of space separated list                                ;
*---------------------------------------------------------------;
/* EXAMPLES :                                                    
   %put %list_length(item1 item2 item3);                                            
*/
%macro list_length
(data
);
%sysfunc(countw(&data,%str( )))
%mend;

*---------------------------------------------------------------;
* LIST_QUOTE_COMMA                                              ;
* create comma separated list with quoted items, from           ;
* unquoted space separated list.                                ;
*---------------------------------------------------------------;
/* EXAMPLE
%put %list_quote_comma(a b c);
*/
%macro list_quote_comma
(data /* space separated list to quote */
);
%unquote(%str(%')%qsysfunc(tranwrd(&data,%str( ),%str(%',%')))%str(%'))
%mend;

*---------------------------------------------------------------;
* LIST_FIX                                                      ;
* Add prefix and/or suffix to items of space separated list     ;
*---------------------------------------------------------------;
/* EXAMPLES :                                                    
   %put %list_fix(item1 item2 item3,pref_,_suf);                 
   %put %list_fix(item1 item2 item3,pref_);                 
   %put %list_fix(item1 item2 item3,suffix=_suf);                                           
*/

%macro list_fix
(data
,prefix
,suffix
);
%local output;
%do i=1 %to %sysfunc(countw(&data,%str( ))) ;
  %let output= &output &prefix.%scan(&data,&i,%str( ))&suffix;
%end;
&output
%mend;

【讨论】:

    猜你喜欢
    • 2020-05-04
    • 1970-01-01
    • 1970-01-01
    • 2020-07-24
    • 1970-01-01
    • 2018-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多