SAS:跨多个列输出第一个非缺失的最有效方法

时间:2017-07-25 19:02:26

标签: sql arrays sas coalesce

我拥有的数据是数百万行,相当稀疏,需要处理3到10个变量。我的最终结果必须是包含每列的第一个非缺失值的单行。获取以下测试数据:

** test data **;
data test;
    length ID $5 AID 8 TYPE $5;
    input ID $ AID TYPE $;
    datalines;
    A   .   .
    .   123 .
    C   .   XYZ
    ;
run;

最终结果应如下所示:

ID  AID TYPE
A   123 XYZ

使用宏列表和循环我可以使用多个合并语句来强制执行此结果,其中变量是不可丢失的obs=1但是当数据非常大时这是无效的(下面我循环这些变量而不是写多个merge语句):

** works but takes too long on big data **;
data one_row;
    merge 
        test(keep=ID where=(ID ne "") obs=1) /* character */
        test(keep=AID where=(AID ne .) obs=1) /* numeric */
        test(keep=TYPE where=(TYPE ne "") obs=1); /* character */
run;

coalesce函数看起来非常有前景,但我相信我需要将它与arrayoutput结合使用来构建此单行结果。该函数也有所不同(coalescecoalescec,具体取决于变量类型),而使用proc sql无关紧要。我使用array收到错误,因为数组列表中的所有变量都不是同一类型。

1 个答案:

答案 0 :(得分:1)

最有效的方法在很大程度上取决于数据的特征。特别是,最后一个变量的第一个非缺失值是否通常是相对的"早期"在数据集中,或者如果您通常需要遍历整个数据集才能到达它。

我假设您的数据集未编入索引(因为这会极大地简化事情)。

一个选项是标准数据步骤。这并不一定快,但它可能不会比大多数其他选项慢得多,因为无论你做什么,你都必须阅读大部分/全部行。这有一个很好的优点,它可以在每一行完成时停止。

data want;
  if 0 then set test; *defines characteristics;
  set test(rename=(id=_id aid=_aid type=_type)) end=eof;  
  id=coalescec(id,_id);
  aid=coalesce(aid,_aid);
  type=coalescec(type,_type);
  if cmiss(of id aid type)=0 then do;
    output;
    stop;
  end;
  else if eof then output;
  drop _:;
run;

你可以从dictionary.columns的宏变量填充所有这些,甚至可能使用临时数组,但我认为这太乱了。

另一个选项是自我更新,但需要进行两次更改。一,你需要某些东西加入(而不是合并,它可以没有变量)。第二,它将为您提供 last 非缺失值,而不是第一个,因此您必须对数据集进行反向排序。

但假设您将x添加到第一个数据集中,包含任何值(不重要,但每行都是常量),就这么简单:

data want;
  update test(obs=0) test;
  by x;
run;

因此,它具有代码简单性的巨大优势,需要花费一些时间(反向排序和添加新变量)。

如果您的数据集非常稀疏,转置可能是一个很好的折衷方案。不需要知道变量名称,因为您可以使用数组处理它们。

data test_t;
  set test;
  array numvars _numeric_;
  array charvars _character_;
  do _i = 1 to dim(numvars);
    if not missing(numvars[_i]) then do;
      varname = vname(numvars[_i]);
      numvalue= numvars[_i];
      output;
    end;
  end;
  do _i = 1 to dim(charvars);
    if not missing(charvars[_i]) then do;
      varname = vname(charvars[_i]);
      charvalue= charvars[_i];
      output;
    end;
  end;
  keep numvalue charvalue varname;
run;


proc sort data=test_t;
  by varname;
run;

data want;
  set test_t;
  by varname;
  if first.varname;
run;

然后你转换它以获得所需的欲望(或者这可能适合你)。它确实失去了格式/等。关于值,所以考虑到这一点,你的角色值长度可能需要设置为适当的长度 - 然后回退(你可以使用if 0 then set来修复它。)

类似的哈希方法的工作方式大致相同;它的优势在于它可以更快地停止,并且不需要求助。

data test_h;
  set test end=eof;

  array numvars _numeric_;
  array charvars _character_;
  length varname $32 numvalue 8 charvalue $1024; *or longest charvalue length;
  if _n_=1 then do;
    declare hash h(ordered:'a');
    h.defineKey('varname');
    h.defineData('varname','numvalue','charvalue');
    h.defineDone();
  end;

  do _i = 1 to dim(numvars);
    if not missing(numvars[_i]) then do;
      varname = vname(numvars[_i]);
      rc = h.find();
      if rc ne 0 then do;
        numvalue= numvars[_i];
        rc=h.add();
      end;    
    end;
  end;
  do _i = 1 to dim(charvars);
    if not missing(charvars[_i]) then do;
      varname = vname(charvars[_i]);
      rc = h.find();
      if rc ne 0 then do;
        charvalue= charvars[_i];
        rc=h.add();
      end;    
    end;
  end;

  if eof or h.num_items = dim(numvars) + dim(charvars) then do;
     rc = h.output(dataset:'want');
  end;
run;

还有很多其他解决方案,只需要根据您的数据效率最高。