用户行为分析系列实践 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)