使用 SAS 计算在给定日期之前需要随访的受试者数量

Use SAS to count number of subjects who need follow-up visits up until a given date

我有以下数据集:

Data test;
Input id$ visit$ enrdate : mmddyy10. Vsdate : mmddyy10. ;
Format enrdate mmddyy10. Vsdate mmddyy10.; 
Cards;
ABC01 00 1/2/2020 1/2/2020
ABC02 00 5/16/2020 5/16/2020
ABC02 06 5/16/2019 11/12/2019
CDC01 00 8/20/2019 8/20/2019
CDC01 06 8/20/2019 2/16/2020
EFG01 00 5/20/2020 5/20/2020
EFG02 00 12/2/2018 12/2/2018
EFG02 02 12/2/2018 1/31/2019
EFG02 06 12/2/2018 5/31/2019
EFG02 12 12/2/2018 12/2/2019
EFG03 00 3/3/2019 3/3/2019
EFG03 12 3/3/2019 3/2/2020
GFF04 00 6/2/2019 6/2/2019
GFF04 06 6/2/2019     .
;
Run;

我想执行以下操作:

计算当前登记的参与者中有多少人仍需要进行 6 个月的访问(v6),多少人需要 12 个月(v12),多少人需要 18 个月(v18)以及多少人需要他们的 24 个月 (v24)。

例如,我们可以从模拟数据集中看到所有 7 名参与者仍然需要他们的 v18 和 v24,因为他们中的 none 已经进行了这些访问,而参与者 ABC01 和 EFG01 也需要他们的 v6 和 v12,除了 v18 和 v24。除了 v18 和 v24 等之外,参与者 ABC02 和 CDC01 只需要他们的 v12。另一方面,参与者 GFF04 需要从仍然需要他们的 v6 的参与者列表中排除,因为他应该在 12 月的某个时候进行访问2019 但从来没有。不过,我还是想算他需要有他的v12,v18和v24。

一般来说,我们还想排除登记日期是很久以前的参与者,但他们在那之后从未进行过任何其他后续访问。例如,如果参与者的 enrdate=1/20/2018 但此后没有其他访问,那么我们不会将他视为仍需要任何其他访问,因为他很可能退出,因为现在是 2020 年 6 月,并且是唯一我们对他的访问是一次注册访问。

此外,如果参与者输入了下一次访问而不是上一次访问,我们不会将此参与者视为需要上一次访问(也就是说,如果参与者已经有 v12,但不是 v6,那么他会不被视为仍然需要 v6 等等;即参与者 EFG03)

最后,我想要一个截止日期为 2021 年 8 月 19 日。这意味着像 CDC01 这样在 2019 年 8 月 20 日注册的参与者需要在 2021 年 8 月 20 日获得他们的 v24,但那已经过了 8 月 19 日, 2021 年截止,因此该参与者将仅被计为需要 v12 个月和 v18。等等。

这是我到目前为止所做的,但现在我被困住了,不知道如何继续编码以解决上述所有情况。

Data long;
Set test;
Where visit in (“00”, “06”, “12”, “18”, “24”);
If vsdate ne .;
Run;
Proc sort data=long out=longsort;
By id;
Run;

Data wide;
Set longsort;
By id;
Keep id enrdate vsdate00-vsdate24;
Retain vsdate00-vsdate24;
ARRAY avsdate(00:24) vsdate00-vsdate24;
if first.ID then do;
do i = 00 to 24;
avsdate (i) =.;
end;
    end;
avsdate(visit)=vsdate;
if last.ID then output;
run;
data wide_0;
set wide (keep=ID ENRDATE VSDATE00 VSDATE06 VSDATE12 VSDATE18 VSDATE24);
run;
data wide_final;
set wide_0;
attrib 
vsdate00 format=mmddyy10. Informat=anydtdte.
vsdate06 format=mmddyy10. Informat=anydtdte.
Vsdate12 format=mmddyy10. Informat=anydtdte.
Vsdate18 format=mmddyy10. Informat=anydtdte.
Vsdate24 format=mmddyy10. Informat=anydtdte.
;

如果有人可以帮我 suggestions/sample 代码,那将非常有帮助!

谢谢!

您可以在遍历一个组的同时填充一个数组,然后从那里执行您想要的任何算法。该问题似乎在寻找第 24、18、12 和 6 个月的访问,并在(感兴趣的)最高月份访问发生时停止寻找。

示例:

假设 visit 是一个数值变量。 visit 的值可以用作数组索引。这称为 直接寻址

考虑两个数组:

  • months 是一个数组,其作用是保存感兴趣月份的静态列表。
  • visits 是一个直接寻址的数组,用于跟踪 id
  • 发生的访问

每个 id 组都使用 DOW 技术处理,其中 SET 语句位于显式 DO 循环中。

所需的月份频率 table(即计数)保存在 hash 对象中。

数据:

Data have;
Input id$ visit enrdate : mmddyy10. Vsdate : mmddyy10. ;
Format enrdate mmddyy10. Vsdate mmddyy10. visit z2.; 
Cards;
ABC01 00 1/2/2020 1/2/2020
ABC02 00 5/16/2020 5/16/2020
ABC02 06 5/16/2019 11/12/2019
CDC01 00 8/20/2019 8/20/2019
CDC01 06 8/20/2019 2/16/2020
EFG01 00 5/20/2020 5/20/2020
EFG02 00 12/2/2018 12/2/2018
EFG02 02 12/2/2018 1/31/2019
EFG02 06 12/2/2018 5/31/2019
EFG02 12 12/2/2018 12/2/2019
EFG03 00 3/3/2019 3/3/2019
EFG03 12 3/3/2019 3/2/2020
GFF04 00 6/2/2019 6/2/2019
GFF04 06 6/2/2019     .
;

代码:


data _null_;
  array months[1:4] _temporary_  (6,12,18,24);  * visit months of interest;
  array visits[0:24] _temporary_; * array for direct addressed have visit visits for by group.;

  if _n_ = 1 then do;
    declare hash counts (ordered: 'A');
    counts.defineKey('month');
    counts.defineData('month', 'count');
    counts.defineDone();
  end;

  * track visits that occurred;
  do _n_ = 1 by 1 until (last.id);
    set have end=done;
    by id;

    if visit <= hbound(visits) then do;
      visits(visit) = 1;
      flagged_count = sum(flagged_count,1);
    end;
  end;

  * presume enroll date is the same for all rows in the group;
  * at the end of the explicit loop `enrdate` will be available;

  * go from high to low months counting the needed months;

  do _n_ = hbound(months) to lbound(months) by -1;
    month = months(_n_);
  
    if missing(visits(month)) then do;
      * check if needed visit is in date span of interest;

      * compute the expected visit date;
      vsdate = intnx('month', enrdate, month, 'SAMEDAY');

      * if needed visit date is AFTER a cut off date perhaps an earlier one is before;
      if vsdate > '19AUG2021'D then CONTINUE;

      * if needed visit date is from a 'foggy' enrollment date then skip the id;
      if vsdate < '01JAN2020'D and missing(flagged_count) then LEAVE;
      
      * update counts;
      if counts.find() ne 0 then count = 1; else count + 1;
      counts.replace();
    end;
    else
      leave;  /* ignore all non-visits prior to a flagged visit */
  end;

  call missing(of visits[*]);

  if done then do;
    counts.output(dataset: 'want(label="Counts for needed visits")');
  end;

  keep id need;
run;

输出

如果您生成的数据集包含参与者 ID 和访问的每个组合的一行,那么您可以继续对尚未(尚未)参加的每次访问进行分类。这使得手动检查和计算不同类型的访问变得容易。

在下面的代码中,如果计划日期超过一周前,我将访问归类为跳过,如果参与者最近一次访问是在一年多以前,我将其归类为可能退出。您可以修改这些规则。

请注意,结果数据集会随时间变化,因为日期是与 today() 进行比较的。

* Define test data;
data have;
Input id$ visit$ enrdate : mmddyy10. Vsdate : mmddyy10. ;
Format enrdate mmddyy10. Vsdate mmddyy10.; 
Cards;
ABC01 00 1/2/2020 1/2/2020
ABC02 00 5/16/2019 5/16/2019
ABC02 06 5/16/2019 11/12/2019
CDC01 00 8/20/2019 8/20/2019
CDC01 06 8/20/2019 2/16/2020
EFG01 00 5/20/2020 5/20/2020
EFG02 00 12/2/2018 12/2/2018
EFG02 02 12/2/2018 1/31/2019
EFG02 06 12/2/2018 5/31/2019
EFG02 12 12/2/2018 12/2/2019
EFG03 00 3/3/2019 3/3/2019
EFG03 12 3/3/2019 3/2/2020
GFF04 00 6/2/2019 6/2/2019
GFF04 06 6/2/2019     .
;
run;

* Define the planned visits;
data planned_visits;
Input visit$;
Cards;
00
06
12
18
24
;
run;

* List and categorize all visits for all participants;
proc sql;
   create table id_visit_list as 
      select a.id
            ,a.enrdate
            ,b.visit
            ,intnx('month',a.enrdate,input(b.visit,best.),'s') as planned_date format mmddyy10.
            ,c.vsdate
            ,case
               when not missing(vsdate) then "Visit attended"
               when calculated planned_date + 7 < today() then "Visit skipped"
               when calculated planned_date > '19AUG2021'D then "Visit after cut-off"
               when max(vsdate) + 365 < today() then "Likely drop-out"
               else "Scheduled visit"
            end as visit_status 
      from (select distinct id, enrdate from have) as a
      left join (select distinct visit from planned_visits) as b
         on 1
      left join have as c
         on a.id = c.id and b.visit = c.visit
      group by a.id
   ;
quit;

* Count number of scheduled visits;
proc sql;
   create table want as 
      select distinct visit
                     ,sum(visit_status = "Scheduled visit") as count
      from id_visit_list
      group by visit
   ;
quit;