你遇到什么场景?
从EDC收集来的日期经常不完整——有时只有年月、有时只有年、有时甚至缺失。而这些日期直接决定AE的开始结束时间、治疗 emergent 标识、暴露量计算。处理不好就是数据错误。
核心策略
遇到不完整日期,不是随便填一个值——有一套从保守到激进的插补阶梯:
| 不完整程度 | 插补策略 | 示例 |
|---|---|---|
| 完整 YYYY-MM-DD | 直接使用 | 2025-03-15 → 2025-03-15 |
| 仅 YYYY-MM | 开始日期取月初(01日),结束日期取月末 | 2025-03 → 开始:03-01 / 结束:03-31 |
| 仅 YYYY | 开始取0101,结束取1231; 但优先对齐到首次给药日期 | 2025 → 开始:01-01 / 结束:12-31 |
| 缺失 | 开始日期用首次给药日; 结束日期留缺失或用末次接触日截断 | → TRTSDT / LSTALVDT |
原则:插补后的开始日期不能晚于结束日期,结束日期不能超过末次接触日(LSTALVDT)。插补后必须做这个校验。
代码模板
模板一:AE开始日期的三级插补
最常用的模式。按完整度逐级降级,打上插补标识(D=日级/M=月级/Y=年级)。
SAS
/* AE开始日期三级插补 */
data adae2;
set adae1;
format ASTDT date9.; length ASTDTF $1;
/* Level 1: 完整日期 YYYY-MM-DD */
if count(AESTDTC, '-') = 2 then
ASTDT = input(AESTDTC, yymmdd10.);
/* Level 2: 只有年月 YYYY-MM → 开始日期取月初 */
else if count(AESTDTC, '-') = 1 then do;
if AESTDTC = put(TRTSDT, yymmd7.) then ASTDT = TRTSDT;
else ASTDT = input(cats(AESTDTC, '-01'), yymmdd10.);
ASTDTF = 'D'; /* 日级插补 */
end;
/* Level 3: 只有年份 YYYY → 优先对齐首次给药年 */
else if length(AESTDTC) = 4 then do;
if AESTDTC = put(year(TRTSDT), 4.) then ASTDT = TRTSDT;
else ASTDT = input(cats(AESTDTC, '-01-01'), yymmdd10.);
ASTDTF = 'M'; /* 月级插补 */
end;
/* Level 4: 完全缺失 → 用首次给药日 */
else if AESTDTC = '' then do;
ASTDT = TRTSDT;
ASTDTF = 'Y'; /* 年级插补 */
end;
run;
模板二:AE结束日期 → 月末/年末处理
结束日期与开始不同——不完整时应该取月末/年末(保守:AE结束于该时间段的最后一天)。
SAS
/* AE结束日期插补 → 取月末/年末 */
data adae2;
set adae1;
format AENDT date9.; length AENDTF $1;
if count(AEENDTC, '-') = 2 then
AENDT = input(AEENDTC, yymmdd10.);
else if count(AEENDTC, '-') = 1 then do;
/* 只有年月 → 取该月最后一天 */
AENDT = intnx('month',
input(cats(AEENDTC, '-01'), yymmdd10.),
0, 'end');
AENDTF = 'D';
end;
else if length(AEENDTC) = 4 then do;
/* 只有年份 → 取12月31日 */
AENDT = input(cats(AEENDTC, '-12-31'), yymmdd10.);
AENDTF = 'M';
end;
/* 插补后校验:结束日不能超过末次接触日 */
if AENDTF ^= '' and LSTALVDT ^= . then do;
if AENDT > LSTALVDT then AENDT = LSTALVDT;
end;
/* 插补后校验:开始日不能晚于结束日 */
if ASTDTF ^= '' and AENDT ^= . then do;
if ASTDT > AENDT then ASTDT = AENDT;
end;
run;
模板三:AE完整日期时间插补(I期高精度)
健康人I期需要精确到分钟。日期+时间分离处理,参考首次给药日期时间(TR01SDT/TR01STM)。
SAS
/* AE日期+时间完整插补(I期) */
data adae2;
set adae1;
format ASTDT date9. ASTTM time8. ASTDTM datetime20.;
length ASTDTF ASTTMF $1;
/* --- 日期部分 --- */
ASTDC_DATE = strip(scan(AESTDTC, 1, 'T'));
ASTDC_TIME = strip(scan(AESTDTC, 2, 'T'));
if count(ASTDC_DATE, '-') = 2 then
ASTDT = input(ASTDC_DATE, yymmdd10.);
else if count(ASTDC_DATE, '-') = 1 then do;
if ASTDC_DATE = put(TRTSDT, yymmd7.) then ASTDT = TRTSDT;
else ASTDT = input(cats(ASTDC_DATE, '-01'), yymmdd10.);
ASTDTF = 'D';
end;
/* --- 时间部分 --- */
if count(ASTDC_TIME, ':') = 1 then
ASTTM = input(ASTDC_TIME, time8.);
else if length(ASTDC_TIME) = 2 then do;
/* 只有小时HH → 对齐首次给药的小时或填:00 */
if ASTDT = TR01SDT and input(ASTDC_TIME, best.) = hour(TR01STM) then
ASTTM = TR01STM;
else ASTTM = input(cats(ASTDC_TIME, ':00'), time8.);
ASTTMF = 'M';
end;
else if ASTDC_TIME = '' then do;
if ASTDT = TR01SDT then ASTTM = TR01STM;
else ASTTM = input('00:00', time8.);
ASTTMF = 'H';
end;
/* 合并为datetime(秒级) */
ASTDTM = ASTDT * 86400 + ASTTM;
drop ASTDC_:;
run;
关键函数速查
| 函数 | 用途 | 示例 |
|---|---|---|
count(dtc,'-') | 判断日期完整度(2=完整/1=月/0=年) | — |
intnx('month',dt,0,'end') | 取月末最后一天 | intnx('month','01MAR2025'd,0,'end') → 31MAR2025 |
cats(year,'-12-31') | 拼年末日期 | cats('2025','-12-31') → '2025-12-31' |
put(TRTSDT,yymmd7.) | 日期转YYYY-MM格式,用于比较 | put('15MAR2025'd,yymmd7.) → '2025-03' |
scan(dtc,1,'T') | 从ISO格式分离日期部分 | scan('2025-03-15T08:30',1,'T') → '2025-03-15' |
hour(TR01STM) | 提取datetime中的小时数 | hour('08:30:00't) → 8 |
常见坑
⚠️ 插补后不校验顺序
插补完开始/结束日期后必须检查:ASTDT ≤ AENDT,AENDT ≤ LSTALVDT。否则Pinnacle 21会报SDTM inconsistency。
插补完开始/结束日期后必须检查:ASTDT ≤ AENDT,AENDT ≤ LSTALVDT。否则Pinnacle 21会报SDTM inconsistency。
⚠️ count(dtc,'-')的陷阱
count('2025','-') = 0(只有年份),count('2025-03','-') = 1(年月),count('2025-03-15','-') = 2(完整)。但如果ISO格式后面带T:count('2025-03-15T08:30','-')也是2,先用scan分离。
⚠️ intnx('month',...,'end')不会自动截断
INTNX只生成月末日期,不会自动比较LSTALVDT。必须单独加校验逻辑。
INTNX只生成月末日期,不会自动比较LSTALVDT。必须单独加校验逻辑。
⚠️ ASTDTF标记与P21校验
只要做了插补就应打上ASTDTF标记(D/M/Y)。Pinnacle 21会检查这些标记的一致性。
只要做了插补就应打上ASTDTF标记(D/M/Y)。Pinnacle 21会检查这些标记的一致性。