复杂的 SAS 循环和排列问题:需要计算所有日期变量之间的天数并找到符合条件的集合

Complicated SAS Loop & Permutation Problem: Need to Calculate # of Days Between All Date Variables and Find Set That Matches Criteria

我有一个包含 6 个日期和 6 种类型的广泛数据集。数据集有 2 到 400 万行,所以我需要最有效的方法来做到这一点。每个类型编号对应于日期编号。我需要比较每个日期和类型(一次 2 个)并找到满足这些要求的 最早 日期:

条件

如果两种类型都= P,则日期之间的差异必须为17或更多。

所有其他组合必须相隔 24 天或更长时间

我必须找到最早的日期匹配。所以在我的示例中,我的“有”数据集包含所有 6 个条目。我想要的数据集有两个最早的日期满足我的标准,即 2 和 4。我可以手动计算,但无法弄清楚如何在 SAS 中进行计算。我很想在某种迭代宏程序中使用它,而不是占用数百行代码。

我纸上谈兵的逻辑

  1. 计算每个admin_date(所有排列)之间的天数
  2. 忽略相隔至少 17 天的任何内容
  3. 在>=17和<24的中,看两个类型是否都是P,如果是,取第2个日期最早的组合。
  4. 如果不是,检查>=24的那些。取最早的第2个日期的组合。

我对日差的计算:

1 & 2: 1天(忽略,小于17)

1 & 3: 7 天(忽略,小于 17)

1 & 4: 18天(忽略,不是两个P)

1 & 5: 20 天(忽略,不是两个 P)

1 & 6: 34 天 (考虑, ge 24)

2 & 3: 6 天(忽略,小于 17)

2 & 4: 17天(考虑,ge 17,都是P)

2 & 5: 19天(考虑,ge 17,都是P)

2 & 6: 33 天 (考虑, ge 24)

等等

在所有符合条件的集合中:1和6、2和4、2和5、2和6。4是最早的,所以我想要。

data have;
input person $
      admin_date1 : ?? mmddyy10.
      admin_date2 : ??mmddyy10. 
      admin_date3 : ?? mmddyy10.
      admin_date4 : ?? mmddyy10.
      admin_date5 : ?? mmddyy10. 
      admin_date6 : ?? mmddyy10.
      type1 $      
      type2 $      
      type3 $     
      type4 $   
      type5 $   
      type6 $;
format admin_date1 mmddyy10.
      admin_date2 mmddyy10. 
      admin_date3 mmddyy10.
      admin_date4 mmddyy10.
      admin_date5 mmddyy10. 
      admin_date6 mmddyy10.;
datalines;
JohnDoe 01/12/2021 01/13/2021 01/19/2021 01/30/2021 02/01/2021 02/15/2021 M P M P P J
;
run; 



data want;
input person $
      admin_date1 : ?? mmddyy10.
      admin_date2 : ??mmddyy10. 
      type1 $      
      type2 $      
;
format admin_date1 mmddyy10.
      admin_date2 mmddyy10. 
;
datalines;
JohnDoe 01/13/2021 01/30/2021 P P
;
run; 

have dataset

want dataset

如果转置为 long,这将非常容易。基本上只是用指向下一行的 point 做第二个嵌套 set 并遍历同一个人的其他记录。它不是 超级 快,有更快的方法,但它很简单,除非你有数百万行。

data have_long( rename=(_admin_date = admin_date _type = type));
  set have;
  array admin_date[6];
  array type[6];
  do _i = 1 to dim(admin_date);
    _admin_date = admin_date[_i];
    _type = type[_i];
    output;
  end;
  keep person _admin_date _type;
  format _admin_date mmddyy10.;
run;


data want;
  set have_long nobs=nobs;
  if type = 'P';
  do _i = _n_ + 1 to nobs until (person ne _person);
    set have_long(rename=(person=_person type=_type admin_date=admin_date2)) point=_i;
    if person eq _person and admin_date2 ge (admin_date + 17) and _type = 'P' then do;
        output;
        leave;
    end;
  end;
  drop _:;
run;

另一个类似的解决方案是使用散列 table,只要 have_long 能够加载到内存中即可。这可能会更快,也可能不会,具体取决于数据的各种细节。

data want;
  if _n_ eq 1 then do;  *declare the hash table;
    declare hash hl(dataset:'have_long', ordered:'a', multidata:'y');   *ordered ascending and multiple records per key is allowed;
    hl.defineKey('person','type');
    hl.defineData('admin_date');
    hl.defineDone();  
  end;
  format admin_date1 mmddyy8.;
  set have_long;
  if type = 'P';
  admin_date1 = admin_date;  *set the original admin_date aside;
  rc = hl.find();   *find the first match;
  do while (rc eq 0 and admin_date1 gt (admin_date - 17));  *loop unless we either run out of matches or find a valid date;
    rc = hl.find_next();
  end;
  if rc eq 0 then output;  *if we exited due to a valid date then output;
run;