用户行为分析系列实践 11 – 有序分组

目标任务

用户事件表T结构和部分数据示例如下:

Time

UserID

EventType

2022/6/1 10:20

1072755

Search

2022/6/1 12:12

1078030

Browse

2022/6/1 12:36

1005093

Submit

2022/6/1 13:21

1048655

Login

2022/6/1 14:46

1037824

Logout

2022/6/1 15:19

1049626

AddtoCart

2022/6/1 16:00

1009296

Submit

2022/6/1 16:39

1070713

Browse

2022/6/1 17:40

1090884

Search

T表字段说明:

字段名

数据类型

字段含义

Time

日期时间

事件发生的时间戳,精确到毫秒

UserID

字符串

用户ID

EventType

字符串

事件类型

计算任务:

统计指定时间段内,一个时间窗口期内依次发生过搜索、加购物车、提交订单这三个动作中前N个的用户数量,从而方便后续统计用户转化率和用户流失率,该问题也称为漏斗转化分析。

需要注意的事项:

1、上述三个事件必须按时间顺序依次发生,顺序不对的不算

2、上述三个事件必须是同一个用户在一个时间窗口期内发生,超出时间窗口期则不算

3、以第一个事件发生时间点开始计时,如果后续事件在时间窗口期内按顺序发生,则相应事件记为1次,否则为0次,一旦出现某个事件为0次,后续事件就不用继续扫描。

实践技能

关于漏斗转化分析的相关知识可参考:

SQL 提速:漏斗转化分析

从已经按帐户、时间有序的数据中依次读出每个帐户的数据进入内存后做复杂运算。

本例的算法比较复杂,在外存中很不方便,需要全内存计算。而一个用户的数据量很小,可以全部读进内存。

对于已经按用户和时间有序的数据,在使用游标读取时,可以每次只读入一个用户的全部数据,且这些数据按发生时间排序的,然后在内存中实施漏斗统计。

示例代码

根据前面章节的介绍,把T表的数据按照UserID,Time排序后,转储到T.ctx

然后使用游标循环,每次只取一个用户的数据,进行漏斗转化计算


A

B

C

D

1

>start=date("2022-03-15","yyyy-MM-dd"),end=date("2022-04-05","yyyy-MM-dd"),tw=7

2

[Search,AddtoCart,Submit]

=A2.(0)

3

=file("T.ctx").open().cursor(UserID, EventType,Time;Time>=start && Time<=end && A2.contain(EventType))

4

for A3;UserID

=first=A4.select@1(EventType==A2(1))

5


if(B4==null)

next


6


=t=null

=A2.(null)


7


for A2

if #B7==1

>C6(1)=t=t1=first.Time

8



else

>C6(#B7)=t=if(t,A4.select@1(EventType==B7 && Time>t && Time<elapse(t1,tw)).Time,null)

9


=C6.(if(~,1,0))

10


>D2=D2++B9

11

return D2


A1 定义时间段参数、时间窗口tw,实际使用中通过参数传入

A2 目标事件名称,其顺序是重点

D2 产生一个和A2等长的数组,用于存储每个事件的发生次数,此为最终返回结果

A3 打开组表文件,产生游标,游标中对时间段和目标事件进行过滤

A4 对游标循环,每次取出一个用户的所有数据

B4 读第一个事件第一次发生的记录,赋给first变量

B5 如果第一个事件没有发生,则跳转下一个用户,当前用户为无效用户,不需要继续统计

B6 定义变量t,用于存储后续循环中的当前事件发生的时间

C6 定义和A2等长的数组,用于存储后续循环中每一个事件对应的发生时间

B7 循环A2

C7-D7 如果B7的循环序号是1,表明当前为第一个事件,此时记录tfirst的发生时间,同时把此时间赋给t1

C8-D8 如果不是第一个事件,则判断上一个t是否为空,如果为空则把当前t赋值null;如果不为空,则从满足>t且小于时间窗口t1+tw的数据中寻找最早发生的当前事件记录,把其发生时间赋给t

B9 循环C6,把时间为null的次数记为0,不为null的次数记为1

B10 B9累加到D2

上述计算还可以改成并行计算的方式,进一步提高性能:


A

B

C

D

E

1

>start=date("2022-03-15","yyyy-MM-dd"),end=date("2022-04-05","yyyy-MM-dd"),tw=7

2

[Search,AddtoCart,Submit]


3

=file("T.ctx").open().cursor@m(UserID, EventType,Time;Time>=start && Time<=end && A2.contain(EventType);2)

4

fork A3

=A2.(0)




5


for A4;UserID

=first=B5.select@1(EventType==A2(1))

6



if(first==null)

next


7



=t=null

=A2.(null)


8



for A2

if #C8==1

>D7(1)=t=t1=first.Time

9




else

>D7(#C8)=t=if(t,B5.select@1(EventType==C8 && Time>t && Time<elapse(t1,tw)).Time,null)

10



=D7.(if(~,1,0))

11



>B4=B4++C10


12


return B4



13

return transpose(A4).(~.sum())

A3 cursor加上@m选项,产生多路游标

A4 fork A3产生多个线程,并行计算

B4 产生和A2等长的数组,用于存放当前线程的计算结果

B12 当前线程的计算结果返回,存至A4中,组成一个长度等于线程数的序列

A13 A4中的结果转置后求和,即得到最终结果

运行结果:

Member

393400

257539

83375