subject visit用来存放受试者计划和实际的访视信息。与TV不同,TV存放方案规定的计划访视,SV还需要计划外访视的信息。
SV Assumptions
- SV提供受试者试验访视的日期/时间,包括进行的(计划/计划外)访视和未发生的计划访视
- 一个受试者一个VISITNUM只能有一条记录
- 筛选失败、撤回知情、死亡或者提前退出试验的受试者,随后应该进行的计划访视不在SV中体现
- 受试者的计划内和计划外访视,无论是否实地访视都应在SV体现
💠SVPRESP=Y 指明计划访视
💠对于计划访视,SVOCCUR指明该访视是否发生
💠SVREASOC,未发生原因
💠对于计划外访视,SVPRESP和SVOCCUR应该为空
这3个变量是在SDTM IG3.4版本新增的。
- VISITDY不应对计划外访视赋值
- 对于那些筛选访视收集的结果是受试者参加试验之前进行的既往检查数据,这样的既往检查日期(知情同意之前的日期)不能用来衍生筛选期的SVSTDTC
是否需要time部分?
通常来说,SVSTDTC、SVENDTC不需要time部分,因为一天只会落在一个访视内,所以没有必要增加衍生SV的复杂程度。如果说某一天有多个访视,那么相应的访视便需要time部分才能保证其他数据集mapping到正确的VISIT、VISITNUM。
例如day 1 pre-dose、day 1 post-dose,类似这样同一天有两个访视的情况,那些相应的day 1 pre-dose、day 1 post-dose这两个VISIT的SVSTDTC、SVENDTC需要包含time部分,对于其余访视的SVSTDTC、SVENDTC做到日期部分就可以。
需要哪些raw data?
首先我们需要知道SV的目的是什么?无非就是呈现出所有计划访视的最早、最晚日期,对于计划外访视需要知道紧跟在哪个计划访视之后,VISIT、VISITNUM在所有包含访视的domain中需要保持一致性。所以SV需要的数据集便是那些进行检查访视的raw data:VS PE RS EG CV LB OE等等,需要排除的有AE CM MH PR等event,以及ENROLL、IC、DS这些milestone相关的数据集。
最主要的原则就是,考虑该raw data进入的domain是否需要VISIT,该日期是否是该VISIT的检查日期。
编程思路
通过proc contents或者proc sql数据词典,找出所有日期变量,再排除掉 那些不属于访视检查日期的日期变量
将所有日期变量set到一起,保留visit, visitnum(folderseq, foldername), subjid, svdat这些变量,对于所有计划访视按日期排序,最小日期为该访视的SVSTDTC,最大日期为该访视的SVENDTC,每个计划访视输出一条记录
对于计划外访视按日期与计划访视merge,取该计划外访视之前距离最近的计划访视名称,并排序给计划外访视命名相应的名称和编号。
注意事项
Partial Date,一般访视日期很少出现部分缺失的情况,如果有的话需要及时和DM确认是否录入正确,如果确实有的话,需要处理好,确保日期排序按照预期;
Raw Data访视编号的renumber,据DM描述已经发布的访视、访视编号上线后不能修改、只能增加。如果一开始建库留的访视编号后续不够用,需要新增访视的话,可能会出现raw data中的访视编号不能如实反映访视发生顺序,这个时候SDTM.SV要对访视进行重新编号。
SV简单程序示例
假设逻辑库raw有如上数据集,进行SV编程。
** 选择raw data所有日期变量,需根据项目剔除非访视日期的日期变量 **;
proc sql;
select catt(libname,'.',memname,'(keep=subjid folderseq foldername')||' '||catt(name,' rename=',name,'=dat)') into:dslist separated by ' '
from dictionary.columns where libname='RAW' and (kindex(upcase(name),'DAT') or kindex(label,'日期'));
quit;
%put &dslist;
data visall;
set &dslist;
run;
** 去重:每个访视相同日期只留一条记录即可 **;
proc sort data=visall nodupkey;
by subjid folderseq foldername dat;
run;
** 处理计划访视 **;
data vissch;
set visall;
where kindex(foldername,'UNS')=0;
by subjid folderseq foldername dat;
retain svstdtc;
if first.foldername then svstdtc = dat;
if last.foldername then svendtc = dat;
if last.foldername;
** visit,visitnum 沿用folderseq,foldername 如foderseq不能如实反映访视顺序,需要重新编号**;
visit = foldername;
visitnum = folderseq;
keep subjid visit visitnum svstdtc svendtc;
run;
** 处理计划外访视 **;
data visuns;
set vissch visall(where=(kindex(foldername,'UNS')) rename=dat=svstdtc in=uns);
by subjid svstdtc;
svendtc = svstdtc;
retain unsvis unsvisn;
if visit>'' then unsvis = visit;
if visitnum>.z then unsvisn = visitnum;
if uns;
keep subjid svstdtc svendtc unsvis unsvisn;
run;
proc sort data=visuns;
by subjid unsvis unsvisn;
run;
data visuns1;
set visuns;
by subjid unsvis unsvisn svstdtc;
if first.unsvisn then num = 1;
else num + 1;
visitnum = unsvisn + num/10;
visit = catt(unsvis)||' UNSCHEDULED '||cats(num);
keep subjid visitnum visit svstdtc svendtc;
run;
** 合并计划、计划外访视 **;
data sv;
retain subjid visit visitnum svstdtc svendtc;
set visuns1 vissch;
by subjid visitnum;
run;