SAS Enterprise Guide 7中LAG函数的替代方案及日期差计算需求
嘿,针对你遇到的这个问题,我来给你梳理几个可行的解决思路,毕竟不能用LAG函数确实会有点限制,但咱们还有其他办法绕过去~
先明确你的需求和现状
首先,你的原始数据集大概是这样的(我帮你整理了格式,方便查看):
ID, Order Date, Delivery Date, Flag 1, 10/03/12, 15/03/12, 1 1, 17/03/12, 20/03/12, 1
你想要的最终效果是按ID分组,把每组里首次交货日期和后续订单日期的差值算出来,输出成类似这样的宽表:
ID, Order Date, Deliv Date, Order Date_1, Deliv Date_1, DateDIFF(Deliv Date - Order Date_1) 1, 10/03/12, 15/03/12, 17/03/12, 20/03/12, 2
你试过用monotonic()做自连接,但因为每个ID的行数不一样,这个方法没法适配所有情况,而且SAS EG7里没法用LAG函数,那咱们换个思路来搞。
方法一:用PROC SQL提取首次交货日期+合并计算
这个方法比较直观,分三步走:
1. 先给每个ID提取首次交货日期
首先用PROC SQL按ID分组,把每个ID最早的交货日期提出来,单独存成一个表:
/* 假设你的原始表叫original_data,先确保日期列是SAS日期型,如果是字符型要先转换 */ proc sql; create table first_deliv as select ID, min(Delivery_Date) as First_Deliv_Date format=ddmmyy10. from original_data group by ID; quit;
如果你的日期列是字符型(比如原始数据里的10/03/12是字符串),得先转成SAS日期型,代码可以加在这一步或者先预处理:
/* 预处理:字符转日期 */ data original_data_clean; set original_data; Order_Date = input(Order_Date, ddmmyy8.); Delivery_Date = input(Delivery_Date, ddmmyy8.); format Order_Date Delivery_Date ddmmyy10.; run;
2. 把首次交货日期合并到原始数据里
用左连接把刚才的first_deliv表和原始数据合并,这样每一行都能拿到对应ID的首次交货日期:
proc sql; create table data_with_first as select a.*, b.First_Deliv_Date from original_data_clean a left join first_deliv b on a.ID = b.ID; quit;
3. 筛选后续订单并计算日期差
现在你可以筛选出每个ID里除了首次订单之外的行,然后计算首次交货日期和后续订单日期的差。如果要做成你想要的宽表格式,可以用PROC TRANSPOSE把后续的订单转成列:
先给每个ID的订单排序并编号
先给每个ID的订单按日期排序,然后给每行加个序号,方便后续转置:
proc sort data=original_data_clean; by ID Order_Date; run; data ordered_data; set original_data_clean; by ID; if first.ID then order_seq = 1; else order_seq + 1; run;
转置得到宽表
分别转置订单日期和交货日期,生成带序号的列:
/* 转置订单日期 */ proc transpose data=ordered_data out=trans_order prefix=Order_Date_; by ID; id order_seq; var Order_Date; run; /* 转置交货日期 */ proc transpose data=ordered_data out=trans_deliv prefix=Deliv_Date_; by ID; id order_seq; var Delivery_Date; run;
合并并计算日期差
最后把两个转置表合并,计算日期差(SAS里日期是数值型,直接相减就是天数差):
proc sql; create table final_result as select a.ID, a.Order_Date_1 as Order_Date format=ddmmyy10., a.Deliv_Date_1 as Deliv_Date format=ddmmyy10., b.Order_Date_2 as Order_Date_1 format=ddmmyy10., b.Deliv_Date_2 as Deliv_Date_1 format=ddmmyy10., (a.Deliv_Date_1 - b.Order_Date_2) as DateDiff from trans_deliv a left join trans_order b on a.ID = b.ID where a.Order_Date_1 is not null; quit;
这个结果就和你想要的示例格式一致了,如果某个ID有更多后续订单,转置后会生成Order_Date_3、Deliv_Date_3这类列,你可以按需调整。
方法二:用DATA STEP分组处理(不用LAG)
如果你更习惯用DATA STEP,也可以通过分组保留首次交货日期的方式来处理:
proc sort data=original_data_clean; by ID Order_Date; run; data final_result; set original_data_clean; by ID; retain First_Deliv_Date First_Order_Date First_Deliv; /* 用retain保留首次订单的关键信息 */ /* 首次订单时,存储核心信息 */ if first.ID then do; First_Deliv_Date = Delivery_Date; First_Order_Date = Order_Date; First_Deliv = Delivery_Date; end; /* 后续订单时,计算日期差并整理成目标格式 */ else do; DateDiff = First_Deliv_Date - Order_Date; /* 替换当前行的订单信息为首次订单的信息,附加后续订单信息 */ Order_Date = First_Order_Date; Deliv_Date = First_Deliv; Order_Date_1 = original_data_clean.Order_Date; Deliv_Date_1 = Delivery_Date; output; end; format First_Deliv_Date Order_Date Deliv_Date Order_Date_1 Deliv_Date_1 ddmmyy10.; run;
这里用retain语句把首次订单的关键信息保留在内存里,后续行直接引用计算,完全不用LAG函数,完美适配你的限制。
小提示
- 不管用哪种方法,一定要确保日期列是SAS的日期型变量,字符型的日期没法直接计算差值,必须先转换。
- 如果你的原始数据列名有空格(比如
Order Date),建议先重命名成无空格的名字(比如Order_Date),SAS里处理起来会少很多麻烦。
内容的提问来源于stack exchange,提问作者tj123

