【问题标题】:Efficient way to select unique records in SAS在 SAS 中选择唯一记录的有效方法
【发布时间】:2014-09-25 09:47:06
【问题描述】:

数据集如下所示:

Code    Type     Rating
0001    NULL      1
0002    NULL      1
0003    NULL      1
0003    PA 1      3
0004    NULL      1
0004    PB 1      2
0005    AC 1      3
0005    NULL      6
0006    AC 1      2

我希望输出数据集看起来像

Code    Type     Rating
0001    NULL      1
0002    NULL      1
0003    PA 1      4        
0004    PB 1      3        
0005    AC 1      9        
0006    AC 1      2

对于每个Code,Type 最多有两个值。我想通过求和Rating 来选择唯一的Code。但问题是,对于Type,如果它只有一个值,则将其值传递给输出数据集。如果 is 有两个值(一个必须是 NULL),则将不等于 NULL 的值传递给输出数据集。

观察总数N>100,000,000。那么有什么棘手的方法可以实现这一目标吗?

【问题讨论】:

  • type 有多少个不同的值?数据是像上面那样排序的,还是没有排序的(至少,按code)?我们将此与什么“不棘手”的方式进行比较(即,您现在拥有什么以及需要多长时间)?
  • 几百个type。数据按code 排序,然后按type 排序。我为每个唯一的code 连接type。然后寻找非空子字符串。
  • 如果它已经排序(即,您不为此进行排序),那么 Keith 提供的数据步解决方案可能是最快的。如果不是,那么如果它只有几百个类型,那么数组解决方案可能是最快的。
  • 已在上一步中排序。但是最好不要先排序,因为执行时间会增加很多。我应该尝试一个不排序的数组解决方案和一个带有排序数据的日期步骤,看看哪个更快?
  • 你应该尝试各种解决方案,看看哪个更快。

标签: sas


【解决方案1】:

如果数据按照您的示例进行排序,那么您可以在单个数据步骤中实现这一点。我假设实际上缺少 NULL 值,但是如果没有,则将 [if missing(type)] 更改为 [if type='NULL']。所有这些都是对每个 Code 的 Rating 值求和,然后输出最后一条记录,保持非 null 类型。如果您的数据没有在 Code 上排序或索引,那么您需要先进行排序,这显然会增加相当多的执行时间。

/* create input file */
data have;
input Code Type $ Rating;
infile datalines dsd;
datalines;
0001,,1
0002,,1
0003,,1
0003,PA 1,3
0004,,1
0004,PB 1,2
0005,AC 1,3
0005,,6
0006,AC 1,2
;
run;

/* create summarised dataset */
data want;
set have;
by code;
retain _type; /* temporary variable */
if first.code then do;
    _type = type;
    _rating_sum = 0; /* reset sum */
end;
_rating_sum + rating; /* sum rating per Code */
if last.code then do;
    if missing(type) then type = _type; /* pick non-null value */
    rating = _rating_sum; /* insert sum */
    output;
end;
run;

【讨论】:

  • 这在很大程度上取决于一个代码最多只有两个观察值的事实,如果有更多观察值,你能告诉它如何扩展吗?
  • 好的,但明确指出最多出现 2 个观察值。如果还有更多,那么这是对逻辑的简单更改。如果 first.code 然后调用 missing(_type, _rating_sum);如果没有缺失(类型),则 _type=type; _rating_sum + 评分;等
  • 这不是攻击,只是关于如何扩展到另一个案例的问题,更接近我的案例。如有冒犯,请见谅。
  • 不是问题安东尼。事后看来,我可能应该发布涵盖任意数量条目的解决方案,而不是 2 个。几乎没有任何额外的代码可以做到这一点,而且它更具可扩展性
【解决方案2】:

鉴于 cmets,存在另一种可能性,即哈希解决方案。这是内存受限的,因此它可能会或可能无法处理实际数据(哈希表不是很大,但 100M 行可能意味着哈希表中有 60 或 70M 行,乘以 40 或 50 个字节会仍然很大)。

如果数据集按代码排序,这几乎肯定不如普通数据步骤方法,所以这应该只用于未排序的数据。

概念:

  • 创建以代码为键的哈希表
  • 如果传入记录是新的,则添加到哈希表中
  • 如果传入记录不是新代码,则取检索到的值并将评级相加。检查是否需要更换类型。
  • 输出到数据集。

代码:

data _null_;
  if _n_=1 then do;
      if 0 then set have;
      declare hash h(ordered:'a');
      h.defineKey('code');
      h.defineData('code','type','rating');
      h.defineDone();
  end;
  set have(rename=(type=type_in rating=rating_in)) end=eof;
  rc_1 = h.find();
  if rc_1 eq 0 then do;
    if type ne type_in and type='NULL' then type=type_in;
    rating=sum(rating,rating_in);
    h.replace();
  end;
  else do;
    type=type_in;
    rating=rating_in;
    h.add();
  end;
  if eof then do;
    h.output(dataset:'want');
  end;
run;

【讨论】:

    【解决方案3】:

    也很容易在一个 SQL 步骤中完成。只需使用 CASE...WHEN...END 删除 NULL 和 MAX 即可获取非空值。

    data have;
    input @1 Code 4.
          @9 Type $4.
          @19 Rating 1.;
    datalines;
    0001    NULL      1
    0002    NULL      1
    0003    NULL      1
    0003    PA 1      3
    0004    NULL      1
    0004    PB 1      2
    0005    AC 1      3
    0005    NULL      6
    0006    AC 1      2
    ;;;;
    run;
    
    proc sql;
    create table want as
        select code, 
            max(case type when 'NULL' then '' else type end) as type,
            sum(Rating) as rating
            from have
            group by code;
    quit;
    

    如果您想要返回 NULL,则需要将选择包装在 select code, case type when ' ' then 'NULL' else type end as type, rating from ( ... ); 中,但我建议将它们留空。

    【讨论】:

    • 您需要使用汇总功能(以便 GROUP BY 工作)。 MAX 与数字一样作用于字符(Z > A,并且空格=缺失最少)。我将 NULL 转换为空格,以保证它与您的其他值相比最少。
    猜你喜欢
    • 2023-01-18
    • 1970-01-01
    • 2015-08-18
    • 1970-01-01
    • 2011-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-26
    相关资源
    最近更新 更多