SAS - 跨行复制多个观察结果

SAS - Replicate multiple observations across rows

我的数据结构如下所示:

DATA have ; 
INPUT famid indid implicate imp_inc; 
CARDS ; 
1 1 1 40000
1 1 2 25000
1 1 3 34000
1 1 4 23555
1 1 5 49850
1 2 1 1000
1 2 2 2000
1 2 3 3000
1 2 4 4000
1 2 5 5000
1 3 1 .
1 3 2 .
1 3 3 .
1 3 4 .
1 3 5 .
2 1 1 40000
2 1 2 45000
2 1 3 50000
2 1 4 34000
2 1 5 23500
2 2 1 .
2 2 2 .
2 2 3 .
2 2 4 .
2 2 5 .
2 3 1 41000
2 3 2 39000
2 3 3 24000
2 3 4 32000
2 3 5 53000
RUN ;

因此,我们有家庭 ID、个人 ID、牵连人数和每个牵连的估算收入。

我需要的是为每个家庭中的其余个体复制每个家庭中第一个个体的结果(所有五个牵连),替换我们之前在这些单元格上的任何值,如下所示:

DATA want ; 
INPUT famid indid implicate imp_inc; 
CARDS ; 
1 1 1 40000
1 1 2 25000
1 1 3 34000
1 1 4 23555
1 1 5 49850
1 2 1 40000
1 2 2 25000
1 2 3 34000
1 2 4 23555
1 2 5 49850
1 3 1 40000
1 3 2 25000
1 3 3 34000
1 3 4 23555
1 3 5 49850
2 1 1 40000
2 1 2 45000
2 1 3 50000
2 1 4 34000
2 1 5 23500
2 2 1 40000
2 2 2 45000
2 2 3 50000
2 2 4 34000
2 2 5 23500
2 3 1 40000
2 3 2 45000
2 3 3 50000
2 3 4 34000
2 3 5 23500
RUN ;

在这个例子中,我试图只复制一个变量,但在我的项目中,我将不得不对几十个变量执行此操作。

到目前为止,我想出了这个解决方案:

%let implist_1=imp_inc;

%macro copyv1(list);
    %let nwords=%sysfunc(countw(&list));
    %do i=1 %to &nwords;
    %let varl=%scan(&list, &i);
        proc means data=have max noprint;
            var &varl;
            by famid implicate;
            where indid=1;
            OUTPUT OUT=copy max=max_&varl;  
        run;
        data want;
            set have;
            drop &varl;
        run;
        data want (drop=_TYPE_ _FREQ_);
            merge want copy;
            by famid implicate;
            rename max_&varl=&varl;
        run;
    %end;
%mend;
%copyv1(&imp_list1);

这适用于一个或两个变量。但是,一旦您对 1.5 GB 大小的数据集中的 400 个变量执行此操作,速度就会非常慢。

我很确定有一种更快的方法可以使用某种形式的 proc sql 或 first.var 等来执行此操作,但我对 SAS 比较陌生,到目前为止我还不能'想出更好的解决办法。

非常感谢您的支持。

此致

这是相当简单的 SQL:

proc sql;
create table want as 
  select a.famid, a.indid, a.implicate, b.* from 
  have a 
  left join (
    select * from have 
    group by famid 
    having indid = min(indid)
  ) b 
  on
        a.famid = b.famid 
    and a.implicate = b.implicate
  order by a.famid, a.indid, a.implicate
  ;
quit;

想法是将 table 连接到其自身的一个子集,该子集仅包含与每个家庭中第一个个体对应的行。

它被设置为选择每个家庭中编号最小的个体,所以即使没有 indid = 1 的行它也会起作用。如果你确定总会有这样的一行,你可以使用稍微简单一点的查询:

proc sql;
create table want as 
  select a.famid, a.indid, a.implicate, b.* from 
  have(sortedby = famid) a 
  left join have(where = (indid = 1)) b 
  on
        a.famid = b.famid 
    and a.implicate = b.implicate
  order by a.famid, a.indid, a.implicate
  ;
quit;

指定 sortedby = famid 向查询优化器提供一个提示,它可以跳过连接所需的初始排序之一,这可能会稍微提高性能。

是的,这可以在数据步骤中使用通过 by 语句提供的 first. 引用来完成。

data want;
  set have (keep=famid indid implicate imp_inc /* other vars */);

  by famid indid implicate; /* by implicate is so step logs an error (at run-time) if data not sorted */

  if first.famid then if indid ne 1 then abort;

  array across imp_inc           /* other vars */;
  array hold [1,5] _temporary_;  /* or [<n>,5] where <n> means the number of variables in the across array */

  if indid = 1 then do;          /* hold data for 1st individuals implicate across data */
    do _n_ = 1 to dim(across);
      hold[_n_,implicate] = across[_n_];  /* store info of each implicate of first individual */
    end;
  end;
  else do;
    do _n_ = 1 to dim(across);
      across[_n_] = hold[_n_,implicate];  /* apply 1st persons info to subsequent persons */
    end;
  end;
run;

由于单次通过数据,DATA 步骤可能会显着加快,但是在 run; 时间计算所有那些讨厌的 [] 数组地址会产生内部处理成本,并且该成本可能会对某些 <n>

产生影响

SQL 语法更简单,理解更清晰,如果 have 数据集未排序或在分组中有一些特殊的排序。