目录
- 1. 介绍
- 2. 留存问题
- 3. 思路解析
- 4. 逻辑
- 4.1 b表建设
- 4.2 留存计算
- 4.3 近X天的访问天数
- 5.分析
1. 介绍
bitmap方法是数据压缩使用的常用算法,当字段有明确上下界的时候,使用位图模式来减少存储。在业务指标体系中特别适合通用型留存指标的计算。
2. 留存问题
我们常写的留存率指标:次留/3留/7留/15留/30留,实际上都是一个公式:
第N日留存率公式 = (T日访问用户)∩ (T+N日访问用户) / (T日访问用户)
那么一般情况下,我们怎么写呢:
select user_id from 访问表 where ds=T 交集
select user_id from 访问表 where ds=T+N 除以
select user_id from 访问表 where ds=T
设计也很简单,每一个bit位代表每一天的访问,最低位代表今天;
比如 0001 就代表最近4天只有今天来访,0101 就代表最近4天有2天来访问,分别是今天和前天
bitmap访问分布 = sum(今日访问 UNION ALL 左移位(昨日访问表))
特别注意:对bitmap做了上限控制,vst_bit_31d&(pow(2,N)-1) 防止溢出。2147483647=pow(2,31)-1表示最近31天的数据
3. 思路解析
- 通常我们计算次日留存,按照正向思考是当天来访的设备,在后面1天是否来访。也就是(ds = T ) 交集 (ds = T+1)
- 但是在写代码的时候,我们通常都是逆向的,是找过去第1天的设备,到现在是否来访。 也就是(ds = T-1) 交集 (ds = T)
- 比如今天是20231117,底层odps产出的是20231116的分区,但是20231115的次留才能计算
- 我们需要一张B表中的一个字段标识是否访问,由上文所说比如 0001 就代表最近4天只有今天来访,0101 就代表最近4天有2天来访问,分别是今天和前天
- 比如该用户在20231116 来了,且20231115也来了,那么这个b表在20231115日分区的这个字段的十进制为1 二进制为(01)2;在20231116日分区的这个字段的十进制为3 二进制为(11)2,那么如何计算20231115的留存呢?
- 我们将20231116分区这个字段 & (10)2 = pow(2,1) 就能判断该设备是否昨天来访;& (11)2 = pow(2,1)+1就能判断他是否昨天今天都来了
- 计算其他留存只是&的字段不同,如果计算当日访问的设备,在第7天是否来访,K1 = (10000001)2 = pow(2,7)+1 ,K2 = (10000000)2 = pow(2,7)
select user_id from B WHERE ds = T and vst_bit_31d & K1 = K1 -- 当日和7日前来访
交集
select user_id from B WHERE ds = T and vst_bit_31d & K2 = K2 -- 7日前来访
除以
select user_id from B WHERE ds = T and vst_bit_31d & K2 = K2 -- 7日前来访
- 如果计算当日访问的设备,在后7天内是否来访,K1 = (11111111)2 = pow(2,8)-1 ,K2 = (10000000)2 = pow(2,7)
select user_id from B WHERE ds = T and vst_bit_31d & K1 > K2 -- 7日前来访 且后面7日中有一天来访就算
交集
select user_id from B WHERE ds = T and vst_bit_31d & K2 = K2
除以
select user_id from B WHERE ds = T and vst_bit_31d & K2 = K2
4. 逻辑
4.1 b表建设
select product_id -- 业务id
, visitor_id -- 访问者id
, sum(vst_bit_31d) -- 最近31天的访问bitmap
FROM
(
-- 昨日分区bit左移1位
SELECT product_id
,visitor_id
,IF(vst_bit_31d > 0, SHIFTLEFT(vst_bit_31d, 1)&2147483647, 0 ) as vst_bit_31d
FROM ${your_bitmap_table_name} -- Bitmap表
WHERE ds = '${sub1d}'
UNION ALL
-- 今日访问bit=1
SELECT product_id
,visitor_id
,1
FROM ${your_visit_table_name} -- 访问表
WHERE ds = '${bizdate}'
group by product_id, visitor_id
)
GROUP by product_id, visitor_id
4.2 留存计算
需要注意的是product_id 为最细粒度,不可跨粒度计算
@vst_bit_2 :=
SELECT product_id, count(visitor_id) as uv
FROM ${your_bitmap_table_name} -- Bitmap表
WHERE ds = '${bizdate}'
AND vst_bit_31d & 2 = 2 -- 昨日访问UV, 分母
AND ( '${your condition}' )
GROUP BY product_id
;
@vst_bit_3 :=
SELECT product_id, count(visitor_id) as uv
FROM ${your_bitmap_table_name} -- Bitmap表
WHERE ds = '${bizdate}'
AND vst_bit_31d & 3 = 3 -- 昨日留存UV, 分子
AND ( '${your condition}' )
GROUP BY product_id
;
-- 计算次留率
select product_id, min(uv)/max(uv) 次留率
(
select * FROM @vst_bit_3
UNION ALL
select * FROM @vst_bit_2
)
group by product_id;
4.3 近X天的访问天数
-- 最近7天内的访问天数,其中127=pow(2,7)-1=127
SELECT product_id
, visitor_id
, bi_udf:bi_bit_count(vst_bit_31d&127, 1) AS vst_days
FROM ${your_bitmap_table_name} -- Bitmap表
WHERE ds = '${bizdate}'
AND ( '${your condition}' )
GROUP BY product_id, visitor_id, bi_udf:bi_bit_count(vst_bit_31d&127, 1)
;
5.分析
● bitmap的使用确实可以加速数据的运算,每天存储的是之前30天是否来访,
● 如果想看过去60天的,也方便进行扩展
○ 可拿ds-30的分区数据,拿到过去30-60天的访问数据
○ 或者把这个vst_bit_31d变成longlong类型
● 但是使用时需要注意,这个建立B表的时候,维度一定要确定不能组合,比较死板
● 新建表后需要按时间顺序回刷