使用 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;
我有以下数据集:
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;