用户行为分析系列实践 9 – 枚举和标签维度

目标任务

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

Time UserID EventType OS Browser f1 f2 f3 f4 f5
2022/6/1 10:20 1072755 Search Android IE true false false true false
2022/6/1 12:12 1078030 Browse IOS Safari false false true true true
2022/6/1 12:36 1005093 Submit Android Chrome 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 true true false true false
2022/6/1 16:00 1009296 Submit IOS Firefox false true false false true
2022/6/1 16:39 1070713 Browse IOS Sogou true true true false false
2022/6/1 17:40 1090884 Search Windows IE true false true true false

T表字段说明:

字段名 数据类型 字段含义
Time 日期时间 事件发生的时间戳,精确到毫秒
UserID 字符串 用户ID
EventType 字符串 事件类型
OS 字符串 操作系统,取值为Android,IOS,Windows,……,Unknown
Browser 字符串 浏览器,取值为IE,Safari,Edge,Firefox,Chrome,Sogou,……,Unknown
字符串 更多其它取值为枚举值的字段
f1 布尔值 是否异地发生,取值为真和假
f2 布尔值 是否惯用设备,取值为真和假
f3 布尔值 是否惯用浏览器,取值为真和假
f4 布尔值 是否是手机,取值为真和假
f5 布尔值 是否首次操作,取值为真和假
布尔值 更多其它取值为真和假的字段

计算任务:

统计指定时间段内,本地使用安卓/苹果手机,使用Safari/ Edge/ Chrome,且非首次操作的用户,在每种事件类型下的发生次数,以及去重用户数。

实践技能

关于枚举和标签维度的相关知识可参考:

多标签用户画像分析跑得快的关键在哪里?

多维分析后台实践 7:布尔维度和二值维度

1. 将枚举维度值转换成序号

根据OSBrowser字段的取值列表,把事件表T中相应字段值转换成在此列表中的序号

2. 将二值维度值转成为位

将事件表T中的f1,…这些取值为true/false的二值字段,每16个拼成一组,用一个整数字段的位来存储,一个整数字段存储16个原字段。说明:SPL16位整数做了优化,读取速度更快,所以一般用16位整数来存储,如果二值维度超出16个,则用多个整数字段。

经过12动作转换后的T表结构如下:

字段名 数据类型 字段含义
Time 日期时间 事件发生的时间戳,精确到毫秒
UserID 字符串 用户ID
EventType 字符串 事件类型
OS 整数 操作系统,取值为枚举序列中的序号
Browser 整数 浏览器,取值为枚举序列中的序号
b1 整数 整数字段,用位存储二值字段
b2 整数 整数字段,用位存储二值字段

3. 使用转换后的T表进行数据统计

采用SPL提供的序号引用和位式运算实现统计。

示例代码

1、 转储T表的基本方法


A
10 =connect("demo").cursor@x("select * from T order by Time")
11 =file("OS.txt").import@i()
12 =file("Browser.txt").import@i()
13 =A10.new(Time,UserID,EventType,A11.pos(OS):OS,A12.pos(Browser):Browser,……,bits@n(f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15,f16):b1,bits@n(f17,…..,f32):b2,……)
14 =file("T.ctx").create(#Time,UserID,EventType,OS,Browser,……,b1,b2,……)
15 =A14.append(A13)
16 >A14.close()

A10 读事实表,按Time排序

A11 读入OS字段可能取值的列表

undefined

A12 读入Browser字段可能取值的列表

undefined

A13 OS字段值转换成其在A11中的序号,将Browser字段值转换成其在A12中的序号,如果取列表是有序的,可以在pos时加@b选项用二分法定位,速度更快。

依次将取值为true/false的二值字段每16个一组按位存储成一个整数字段,1代表true0代表falsebits@n表示将true/false变成1/0

A14 产生组表文件的结构

A15 A13输出到A14组表文件中

2、 任意多个枚举维度

如果枚举字段数较多,可以让列表值文件名和字段名相同,自动生成相应的表达式,如:


A
6 [OS,Browser]
7 =A6.(file(~/".txt").import@i())
8 =A6.("A7("/#/").pos("/~/"):"/~).concat@c()

A6 枚举字段名序列

A7 读取枚举字段取值列表,返回结果是:

undefined

A8 生成将枚举字段值转换成其在枚举序列中的序号的表达式,其返回结果是:

A7(1).pos(OS):OS,A7(2).pos(Browser):Browser

A8生成的表达式,用宏替换符号${A8}的形式插入到前面的A13表达式,即:

=A10.new(Time,UserID,EventType, ${A8}, bits@n(f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15,f16):b1, bits@n(f17,…..,f32):b2,……)

3、 任意多个二值维度

如果二值字段数很多,且字段命名很有规律,假设都是fn,可以自动生成bits表达式,如:


A
1 >n=50
2 =n\16
3 =A2.((a=(#-1)*16,"bits@n("+16.("f"/(~+a)).concat@c()+"):b"/#))
4 ="bits@n("+to(A2*16+1,n).("f"/~).concat@c()+"):b"/(A2+1)
5 =(A3|A4).concat@c()

A1 二值字段数

A2 计算需要几个整数来存储

A3 自动拼接生成bits@n表达式

A4 把末尾不足16个的字段作为最后一个整数字段生成对应表达式

A5的返回结果是:

bits@n(f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15,f16):b1,bits@n(f17,f18,f19,f20,f21,f22,f23,f24,f25,f26,f27,f28,f29,f30,f31,f32):b2,bits@n(f33,f34,f35,f36,f37,f38,f39,f40,f41,f42,f43,f44,f45,f46,f47,f48):b3,bits@n(f49,f50):b4

A5生成的表达式,用宏替换符号${A5}的形式插入到前面的A13表达式,即:

=A10.new(Time,UserID,EventType, ${A8}, ${A5})

4、 完整的转储代码


A
1 >n=50
2 =n\16
3 =A2.((a=(#-1)*16,"bits@n("+16.("f"/(~+a)).concat@c()+"):b"/#))
4 ="bits@n("+to(A2*16+1,n).("f"/~).concat@c()+"):b"/(A2+1)
5 =(A3|A4).concat@c()
6 [OS,Browser]
7 =A6.(file(~/".txt").import@i())
8 =A6.("A7("/#/").pos("/~/"):"/~).concat@c()
9 =connect("demo").cursor@x("select * from T order by Time")
10 =A9.new(Time,UserID,EventType, ${A8}, ${A5})
11 =file("T.ctx").create(#Time,UserID,EventType,OS,Browser,……,b1,b2,……)
12 =A11.append(A10)
13 >A11.close()

5、 对转换后的事实表统计的基本方法

“本地使用安卓/苹果手机,使用Safari/ Edge/ Chrome,且非首次操作的用户”这个过滤条件对应的是f10f41f50OS取值为Android/IOSBrowser取值为Safari/ Edge/ Chrome


A
12 =bits(0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0)
13 =bits(1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0)
14 =file("OS.txt").import@i()
15 =A14.(["Android","IOS"].contain@b(~))
16 =file("Browser.txt").import@i()
17 =A16.(["Chrome","Edge","Safari"].contain@b(~))
18 >start=date("2022-03-15","yyyy-MM-dd"),end=date("2022-06-16","yyyy-MM-dd")
19 =file("T.ctx").open().cursor(UserID, EventType;Time>=start && Time<=end && A15(OS) && A17(Browser) && and(b1,A13)==A12).groups(EventType; count(1):Num, icount(UserID):iNum)

A12 根据过滤条件,将条件为真的第四位置为1其它位置为0

A13 根据过滤条件,将有条件的第一位、第四位、第五位置为1其它位置为0

A14 读入OS字段的可能取值序列

A15 根据过滤条件,生成一个与A14同长度的序列,其成员值为对应成员是否满足过滤条件。

A16 读入Browser字段的可能取值序列

A17 根据过滤条件,生成一个与A16同长度的序列,其成员值为对应成员是否满足过滤条件。

A19 在游标中对T表进行过滤,然后再分组汇总。这里用到的位运算,and(b1,A13)==A12表示满足第四位为1、第一位和第五位为0的过滤条件。

运行结果:

EventType Num iNum
AddtoCart 945674 85476
Browse 1778901 178791
Login 2033000 193400
Logout 2033000 193400
Search 1547931 127539
Submit 467345 43375

6、 任意多个枚举维度过滤的统计

当需要过滤的枚举维度比较多时,同样的可以自动生成表达式,如:


A
12 ="{OS:[Android,IOS],Browser:[Chrome,Edge,Safari]}"
13 =json(A12)
14 =A13.fname()
15 =A14.(file(~/".txt").import@i().(A13.field(A14.~).contain(~)))
16 =A14.("A15("/#/")("/~/")")
17 =A16.concat("&&")

A12 需要过滤的枚举字段名和过滤值组成的json

A13 A12中的json串转成序表

A14 获得A13中的字段名序列

A15 生成枚举字段的对位布尔值序列,如果A12中传入的枚举值序列是有序的,contain可以加@b选项速度更快

undefined

A16 生成过滤表达式序列

undefined

A17 生成过滤表达式串:A15(1)(OS) && A15(2)(Browser)

A17生成的表达式,用宏替换符号${A17}的形式插入到前面的A19表达式,即:

=file("T.ctx").open().cursor(UserID, EventType;Time>=start && Time<=end && ${A17} && and(b1,A13)==A12).groups(EventType; count(1):Num, icount(UserID):iNum)

7、 任意多个二值维度过滤的统计

如果需要过滤的二值维度比较多,可以自动生成表达式,如:


A
1 [1,5,22]
2 [4,20]
3 50
4 =[0]*A3
5 >A4(A1)=1
6 =[0]*A3
7 >A6(A1|A2)=1
8 =A4.group((#-1)\16)
9 =A6.group((#-1)\16)
10 =A8.len().("and(A9("/~/"),b"/~/")==A8("/~/")")
11 =A10.concat("&&")

A1 过滤条件中要求为真的二值字段序号

A2 过滤条件中要求为假的二值字段序号

A3 二值字段的总个数

A4 产生一个值为0长度为A3的序列

A5 A4中对应需要为真的成员赋值1

A6 产生一个值为0长度为A3的序列

A7 A6中对应需要过滤的成员赋值1

A8 A4拆成16个一组

undefined

A9 A6拆成16个一组

undefined

A10 生成过滤表达式序列

A11 生成过滤表达式串

and(A9(1),b1)==A8(1) && and(A9(2),b2)==A8(2) && and(A9(3),b3)==A8(3) && and(A9(4),b4)==A8(4)

A11生成的表达式,用宏替换符号${A11}的形式插入到前面的A19表达式,即:

=file("T.ctx").open().cursor(UserID, EventType;Time>=start && Time<=end && ${A17} && ${A11}).groups(EventType; count(1):Num, icount(UserID):iNum)

8、 完整的代码


A
1 [1,5,22]
2 [4,20]
3 50
4 =[0]*A3
5 >A4(A1)=1
6 =[0]*A3
7 >A6(A1|A2)=1
8 =A4.group((#-1)\16)
9 =A6.group((#-1)\16)
10 =A8.len().("and(A9("/~/"),b"/~/")==A8("/~/")")
11 =A10.concat("&&")
12 ="{OS:[Android,IOS],Browser:[Chrome,Edge,Safari]}"
13 =json(A12)
14 =A13.fname()
15 =A14.(file(~/".txt").import@i().(A13.field(A14.~).contain(~)))
16 =A14.("A15("/#/")("/~/")")
17 =A16.concat("&&")
18 >start=date("2022-03-15","yyyy-MM-dd"),end=date("2022-06-16","yyyy-MM-dd")
19 =file("T.ctx").open().cursor(UserID, EventType;Time>=start && Time<=end && ${A17} && ${A11}).groups(EventType; count(1):Num, icount(UserID):iNum)