用户行为分析系列实践 14 – T+0 实时分析
目标任务
用户事件表T结构和部分数据示例如下:
Time | UserID | EventType | OS | Browser | ProductID | … | f1 | f2 | f3 | f4 | f5 | … |
2022/6/1 10:20 | 1072755 | Search | Android | IE | 100001 | … | true | false | false | true | false | … |
2022/6/1 12:12 | 1078030 | Browse | IOS | Safari | 100002 | … | false | false | true | true | true | … |
2022/6/1 12:36 | 1005093 | Submit | Android | Chrome | 100003 | … | true | true | true | false | false | … |
2022/6/1 13:21 | 1048655 | Login | Windows | Chrome | … | false | false | true | true | true | … | |
2022/6/1 14:46 | 1037824 | Logout | Android | Edge | … | false | false | false | true | true | … | |
2022/6/1 15:19 | 1049626 | AddtoCart | Windows | Edge | 100004 | … | true | true | false | true | false | … |
2022/6/1 16:00 | 1009296 | Submit | IOS | Firefox | 100005 | … | false | true | false | false | true | … |
2022/6/1 16:39 | 1070713 | Browse | IOS | Sogou | 100006 | … | true | true | true | false | false | … |
2022/6/1 17:40 | 1090884 | Search | Windows | IE | 100007 | … | true | false | true | true | false | … |
T表字段说明:
字段名 | 数据类型 | 字段含义 |
Time | 日期时间 | 事件发生的时间戳,精确到毫秒 |
UserID | 字符串 | 用户ID |
EventType | 字符串 | 事件类型,取值为Login,Browse,Search,AddtoCart,Submit,Logout |
OS | 字符串 | 操作系统,取值为Android,IOS,Windows,Unknown |
Browser | 字符串 | 浏览器,取值为IE,Safari,Edge,Firefox,Chrome,Sogou,Unknown |
ProductID | 字符串 | 产品ID,取值为产品维表中的ProductID字段 |
… | 字符串 | 更多其它取值为枚举值的字段 |
f1 | 布尔值 | 是否异地发生,取值为真和假 |
f2 | 布尔值 | 是否惯用设备,取值为真和假 |
f3 | 布尔值 | 是否惯用浏览器,取值为真和假 |
f4 | 布尔值 | 是否是手机,取值为真和假 |
f5 | 布尔值 | 是否首次操作,取值为真和假 |
… | 布尔值 | 更多其它取值为真和假的字段 |
维表Product:
ProductID | ProductName | Unit | Price | ProductType |
100001 | Apple | Pound | 5.5 | Fruits |
100002 | Tissue | Packs | 16 | Home&Personalcare |
100003 | Beef | Pound | 35 | Meat |
100004 | Wine | Bottles | 120 | Beverage |
100005 | Pork | Pound | 25 | Meat |
100006 | Bread | Packs | 10 | Bakery |
100007 | Juice | Bottles | 6 | Beverage |
… | … | … | … | … |
维表Product字段说明:
字段名 | 数据类型 | 字段含义 |
ProductID | 字符串 | 产品ID |
ProductName | 字符串 | 产品名称 |
Unit | 字符串 | 销售单位 |
Price | 数值 | 单价 |
ProductType | 整数 | 产品类别 |
计算任务:
统计指定时间段内,产品类别为Home&Personalcare,本地使用安卓/苹果手机,使用Safari/ Edge/ Chrome,且非首次操作的用户,在最近三个月内依次发生过搜索、加购物车、提交订单这三个动作中前N个的用户数量,从而方便后续统计用户转化率和用户流失率,即漏斗转化分析。
需要注意的事项:时间窗口为最近三个月,要包含最新发生的数据。其它事项和前述漏斗分析一致:
1、 上述三个事件必须按时间顺序依次发生,顺序不对的不算
2、 上述三个事件必须是同一个用户在时间窗口期内发生,超出时间窗口期则不算
3、 以第一个事件发生时间点开始计时,如果后续事件在时间窗口期内按顺序发生,则相应事件记为1次,否则为0次,一旦出现某个事件为0次,后续事件就不用继续扫描。
实践技能
历史数据根据前面文章的介绍,输出到T.ctx中,最新的实时发生的数据,利用append@y()函数,实时追加到内存的补区中,参与实时分析计算,这部分数据不会写入组表文件,不会干扰组表文件的正常存储和定期追加。
虚表和组表、复组表均支持append@y()函数
示例代码
1、 参考前述文章的介绍,将历史数据存入T.ctx组表文件中,按年月分表存储,形成复组表
2、 在复组表的基础上定义虚表,代码和上一篇文章一样
A | |
1 | =to(2021,2022).conj((a=~*100,12.(~+a))) |
2 | =T("Product.btx").keys@i(ProductID) |
3 | =[{file:"T.ctx", zone:A1, user:"UserID", date:"Time", column:[ {name:"Month",exp:"month@y(Time)"}, {name:"EventType",pseudo:"EventTypeName",enum:["Login","Browse","Search","AddtoCart","Submit","Logout"]}, {name:"OS",pseudo:"OSName",enum:["Android","IOS","Windows","Unknown"]}, {name:"Browser",pseudo:"BrowserName",enum:["IE","Safari","Edge","Firefox","Chrome","Sogou","Unknown"]}, {name:"b1",bits:["f1","f2","f3","f4","f5"]}, {name:"ProductID",dim:A2}] }] |
4 | =pseudo(A5) |
3、 把实时新数据读出来,然后按UserID,Time排序,通过append@y()追加到复组表的内存补区中
这里假设每天0点以后会把头一天的数据追加到复组表的文件中,所以实时新数据就是当天早晨0点之后的数据。
A | |
… | /前面定义虚表的代码 |
5 | =connect("demo").cursor@x("select * from T where Time>=? order by UserID,Time",date(now()-1)) |
6 | =A4.append@y(A7) |
A5 连接数据库,读取T表的当天新数据的并产生游标, 数据要按UserID,Time排序
A6 将游标数据读出通过虚表追加到复组表的内存补区中
如果不使用虚表,直接打开复组表用append@y()追加也可以,那就要参考前面的文章,对枚举字段先进行序号化以及对二值字段进行位维转换。
4、 利用虚表进行数据统计,代码和上一篇文章一样,SPL会自动把内存里的实时数据和复组表文件里存储的数据归并按正确的方式输出
A | B | C | D | |
… | /前面定义虚表的代码 | |||
5 | >start =elapse@m(now(),-3),tw=7 | |||
6 | [Search,AddtoCart,Submit] | =A6.(0) | ||
7 | =A4.cursor(UserID, EventType,Time;Time>=start && A6.contain(EventType) && ProductID.ProductType=="Home&Personalcare"&& ["Safari","Edge","Chrome"].pos(BrowserName) && ["Android","IOS"].pos(OSName) && ! f1 && f4 && !f5) | |||
8 | for A7;UserID | =first=A8.select@1(EventType==A6(1)) | ||
9 | if(B8==null) | next | ||
10 | =t=null | =A6.(null) | ||
11 | for A6 | if #B11==1 | >C10(1)=t=t1=first.Time | |
12 | else | >C10(#B11)=t=if(t,A8.select@1(EventType==B11 && Time>t && Time<elapse(t1,tw)).Time,null) | ||
13 | =C10.(if(~,1,0)) | |||
14 | >D6=D6++B13 | |||
15 | return D6 |
5、 如果不采用双维有序的复组表,做法和上述一样,也是通过append@y()把数据追加到组表的内存补区中,统计代码不需要改变。
运行结果:
Member |
393400 |
257539 |
83375 |
英文版