如何转置sas中多次出现的数据

how to transpose data with multiple occurrences in sas

我有一个包含 2 列的数据集 - 帐户和属性,其中有 6 种类型的属性。 我正在尝试使用 PROC TRANSPOSE 将 6 个不同的属性设置为 6 个新列,并在列具有该属性的位置设置 1,在没有该属性的位置设置 0

这个答案展示了两种方法:

  • Proc TRANSPOSE,以及
  • 通过散列使用索引查找的基于数组的转置。

对于所有帐户都缺少相同属性的情况,数据本身将无法展示所有属性——理想情况下,允许的或预期的属性应该在单独的 table 作为数据重塑的一部分。

过程转置

当仅使用 accountattribute 的 table 时,您将需要构建一个视图,添加一个数字变量,该变量可以被转置。在 TRANSPOSE 之后,必须进一步处理结果数据,用 0.

替换缺失值 (.)

示例:

data have;
  call streaminit(123);
  do account = 1 to 10;
    do attribute = 'a','b','c','d','e','f';
      if rand('uniform') < 0.75 then output;
    end;
  end;
run;

data stage / view=stage;
  set have;
  num = 1;
run;

proc transpose data=stage out=want;
  by account;
  id attribute;
  var num;
run;

data want;
  set want;
  array attrs _numeric_;
  do index = 1 to dim(attrs);
    if missing(attrs(index)) then attrs(index) = 0;
  end;
  drop index;
run;

proc sql;
  drop view stage;

来自

高级技巧——数组和哈希映射

在某些情况下,编码器或操作员认为 Proc TRANSPOSE 不可用,可能有很多组和很多属性。将属性值转置为类似命名标志变量的另一种方法是编码:

  • 两次扫描
    • 扫描 1 确定将遇到并用作列名的属性值
      • 在宏变量中存储值列表
    • 扫描2
      • 将属性值排列为变量名
      • 使用散列(或每个@Joe 的自定义信息)将值映射到数组索引
      • 处理每组。将遇到的每个属性值对应的数组变量设置为 1。通过哈希映射查找得到的数组索引。

示例:

* pass #1, determine attribute values present in data, the values will become column names;
proc sql noprint; 
  select distinct attribute into :attrs separated by ' ' from have;

* or make list of attributes from table of attributes (if such a table exists outside of 'have');
*  select distinct attribute into :attrs separated by ' ' from attributes;

%put NOTE: &=attrs;

* pass #2, perform array based tranposformation;

data want2(drop=attribute);
  * prep pdv, promulgate by group variable attributes;
  if 0 then set have(keep=account);

  array attrs &attrs.;
  format &attrs. 4.;

  if _n_=1 then do;
    declare hash attrmap();
    attrmap.defineKey('attribute');
    attrmap.defineData('_n_');
    attrmap.defineDone();
    do _n_ = 1 to dim(attrs);
      attrmap.add(key:vname(attrs(_n_)), data: _n_);
    end;
  end;

  * preset all flags to zero;

  do _n_ = 1 to dim(attrs);
    attrs(_n_) = 0;
  end;

  * DOW loop over by group;

  do until (last.account);
    set have;
    by account;

    attrmap.find();     * lookup array index for attribute as column;
    attrs(_n_) = 1;     * set flag for attribute (as column);
  end;

  * implicit output one row per by group;
run;

另一种不使用 PROC TRANSPOSE 的方法是数据步长数组技术。

在这里,我有一个数据集,希望与您的数据集大致匹配。 ID可能是你的账号,Product是你的属性。

data have;
  call streaminit(2007);
  do id = 1 to 4;
   do prodnum = 1 to 6;
    if rand('Uniform') > 0.5 then do;
      product = byte(96+prodnum);
      output; 
    end;
   end;
  end;
run;

现在,我们在这里转置它。我们用 HAVE 中可能出现的六个变量创建一个数组。然后我们遍历数组以查看该变量是否存在。您可以在 if first.id 块中添加几行,将所有变量设置为 0 而不是最初缺失(我认为缺失更好,但 YMMV)。

data want;
  set have;
  by id;
  array vars[6] a b c d e f;
  retain a b c d e f;
  if first.id then call missing(of vars[*]);
  do _i =  1 to dim(vars);
    if lowcase(vname(vars[_i])) = product then
          vars[_i] = 1;
  end;
  if last.id then output;
run;

当然,如果我们知道数据集是如何构建的,我们可以做得更快。

data want;
  set have;
  by id;
  array vars[6] a b c d e f;
  if first.id then call missing(of vars[*]);
  retain a b c d e f;
  vars[rank(product)-96]=1;
  if last.id then output; 
run;

虽然您的数据实际上并不是那样工作的,但您可以制作一个信息,尽管这样做了。

*First we build an informat relating the product to its number in the array order;
proc format;
  invalue arrayi
  'a'=1
  'b'=2
  'c'=3
  'd'=4
  'e'=5
  'f'=6
  ;
  quit;
  
*Now we can use that!;
data want;
  set have;
  by id;
  array vars[6] a b c d e f;
  if first.id then call missing(of vars[*]);
  retain a b c d e f;
  vars[input(product,arrayi.)]=1;
  if last.id then output; 
run;

这最后一个可能是绝对最快的选项 - 很可能比 PROC TRANSPOSE 快得多,PROC TRANSPOSE 往往是我书中较慢的过程之一,但代价是必须提前知道你的变量是什么将包含在该数组中。