Clickhouse 实现留存分析与数据可视化

1、前言

什么是留存分析?

留存,顾名思义,就是用户在你的产品中留下来、持续使用的意思。
留存为什么重要?留存是 AARRR 模型中重要的环节之一,只有做好了留存,才能保障新用户在注册后不会白白流失。有时候我们光看日活(DAU),会觉得数据不错,但有可能是因为近期有密集的推广拉新活动,注入了大量的新用户,但是留下来的用户不一定在增长,可能在减少,只不过被新用户数掩盖了所以看不出来。这就好像一个不断漏水的篮子,如果不去修补底下的裂缝,而只顾着往里倒水,是很难获得持续的增长的。

关于用户留存模型是各大商业数据分析平台必不可少的功能,企业一般用该模型衡量用户的活跃情况,是能直接反应产品功能价值的直接指标。

今天顺手分享一下我关于留存分析的代码实现:

2、环境准备

2.1、测试表

在clickhouse中创建样例表:

CREATE TABLE ftabcch.behavior
(
    `uid` Int32,
    `event_type` String,
    `tag` String,
    `time` datetime
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(time)
ORDER BY uid
SETTINGS index_granularity = 8192

2.2、插入伪造的样例数据

为了方便展示,我在clickhouse上伪造了一批用户,从2022年4月10号注册之后,以自身日期为瞄点,每过一天之后仍然登录的客户的数据。

2.2.1、插入伪造的2022年04月11号有登录且是2022年4月10号注册的新用户数据

insert into ftabcch.behavior select tupleElement(b, 1) uid, tupleElement(b, 2) event_type,tupleElement(b, 3) tag, tupleElement(b, 4) time from (
with
(select groupArray(b) from (select * from generateRandom('b UInt16') limit 70000)) as uid,
(select groupArray('登录') from numbers(70000)) as event_type,
(select groupArray('2022年4月10号注册的新用户') from numbers(70000)) as tag,
(select groupArray(toDateTime('2022-04-11')) from numbers(70000)) as time
select arrayJoin(arrayZip(uid,event_type,tag,time)) as b)

2.2.1、插入伪造的2022年04月12号有登录且是2022年4月10号注册的新用户数据

insert into ftabcch.behavior select tupleElement(b, 1) uid, tupleElement(b, 2) event_type,tupleElement(b, 3) tag, tupleElement(b, 4) time from (
with
(select groupArray(b) from (select * from generateRandom('b UInt16') limit 60000)) as uid,
(select groupArray('登录') from numbers(60000)) as event_type,
(select groupArray('2022年4月10号注册的新用户') from numbers(60000)) as tag,
(select groupArray(toDateTime('2022-04-12')) from numbers(60000)) as time
select arrayJoin(arrayZip(uid,event_type,tag,time)) as b)

2.2.1、插入伪造的2022年04月13号有登录且是2022年4月10号注册的新用户数据

insert into ftabcch.behavior select tupleElement(b, 1) uid, tupleElement(b, 2) event_type,tupleElement(b, 3) tag, tupleElement(b, 4) time from (
with
(select groupArray(b) from (select * from generateRandom('b UInt16') limit 50000)) as uid,
(select groupArray('登录') from numbers(50000)) as event_type,
(select groupArray('2022年4月10号注册的新用户') from numbers(50000)) as tag,
(select groupArray(toDateTime('2022-04-13')) from numbers(50000)) as time
select arrayJoin(arrayZip(uid,event_type,tag,time)) as b)

2.2.1、插入伪造的2022年04月14号有登录且是2022年4月10号注册的新用户数据

insert into ftabcch.behavior select tupleElement(b, 1) uid, tupleElement(b, 2) event_type,tupleElement(b, 3) tag, tupleElement(b, 4) time from (
with
(select groupArray(b) from (select * from generateRandom('b UInt16') limit 40000)) as uid,
(select groupArray('登录') from numbers(40000)) as event_type,
(select groupArray('2022年4月10号注册的新用户') from numbers(40000)) as tag,
(select groupArray(toDateTime('2022-04-14')) from numbers(40000)) as time
select arrayJoin(arrayZip(uid,event_type,tag,time)) as b)

2.2.1、插入伪造的2022年04月15号有登录且是2022年4月10号注册的新用户数据

insert into ftabcch.behavior select tupleElement(b, 1) uid, tupleElement(b, 2) event_type,tupleElement(b, 3) tag, tupleElement(b, 4) time from (
with
(select groupArray(b) from (select * from generateRandom('b UInt16') limit 30000)) as uid,
(select groupArray('登录') from numbers(30000)) as event_type,
(select groupArray('2022年4月10号注册的新用户') from numbers(30000)) as tag,
(select groupArray(toDateTime('2022-04-15')) from numbers(30000)) as time
select arrayJoin(arrayZip(uid,event_type,tag,time)) as b)

2.2.1、插入伪造的2022年04月16号有登录且是2022年4月10号注册的新用户数据

insert into ftabcch.behavior select tupleElement(b, 1) uid, tupleElement(b, 2) event_type,tupleElement(b, 3) tag, tupleElement(b, 4) time from (
with
(select groupArray(b) from (select * from generateRandom('b UInt16') limit 20000)) as uid,
(select groupArray('登录') from numbers(20000)) as event_type,
(select groupArray('2022年4月10号注册的新用户') from numbers(20000)) as tag,
(select groupArray(toDateTime('2022-04-16')) from numbers(20000)) as time
select arrayJoin(arrayZip(uid,event_type,tag,time)) as b)

2.2.1、插入伪造的2022年04月17号有登录且是2022年4月10号注册的新用户数据

insert into ftabcch.behavior select tupleElement(b, 1) uid, tupleElement(b, 2) event_type,tupleElement(b, 3) tag, tupleElement(b, 4) time from (
with
(select groupArray(b) from (select * from generateRandom('b UInt16') limit 10000)) as uid,
(select groupArray('登录') from numbers(10000)) as event_type,
(select groupArray('2022年4月10号注册的新用户') from numbers(10000)) as tag,
(select groupArray(toDateTime('2022-04-17')) from numbers(10000)) as time
select arrayJoin(arrayZip(uid,event_type,tag,time)) as b)

3、留存分析实现代码

3.1、bitmapAndCardinality函数

with (select toString(groupBitmap(uid)) from behavior where tag='2022年4月10号注册的新用户') as new_num,
    (select ['2022-04-11','2022-04-12','2022-04-13','2022-04-14','2022-04-15','2022-04-16','2022-04-17']) as list_date
        select 
            array(array(list_date[1],new_num,toString(bitmapAndCardinality(list_bit[1],list_bit[1])),
            toString(bitmapAndCardinality(list_bit[1],list_bit[2])),toString(bitmapAndCardinality(list_bit[1],list_bit[3])),
            toString(bitmapAndCardinality(list_bit[1],list_bit[4])),toString(bitmapAndCardinality(list_bit[1],list_bit[5])),
            toString(bitmapAndCardinality(list_bit[1],list_bit[6])),toString(bitmapAndCardinality(list_bit[1],list_bit[7]))),
                array(list_date[2],new_num,toString(bitmapAndCardinality(list_bit[2],list_bit[2])),toString(bitmapAndCardinality(list_bit[2],list_bit[3])),
                toString(bitmapAndCardinality(list_bit[2],list_bit[4])),toString(bitmapAndCardinality(list_bit[2],list_bit[5])),
                toString(bitmapAndCardinality(list_bit[2],list_bit[6])),toString(bitmapAndCardinality(list_bit[2],list_bit[7])),''),
                    array(list_date[3],new_num,toString(bitmapAndCardinality(list_bit[3],list_bit[3])),toString(bitmapAndCardinality(list_bit[3],list_bit[4])),
                    toString(bitmapAndCardinality(list_bit[3],list_bit[5])),toString(bitmapAndCardinality(list_bit[3],list_bit[6])),
                    toString(bitmapAndCardinality(list_bit[3],list_bit[7])),'',''),
                        array(list_date[4],new_num,toString(bitmapAndCardinality(list_bit[4],list_bit[4])),toString(bitmapAndCardinality(list_bit[4],list_bit[5])),
                        toString(bitmapAndCardinality(list_bit[4],list_bit[6])),toString(bitmapAndCardinality(list_bit[4],list_bit[7])),'','',''),
                            array(list_date[5],new_num,toString(bitmapAndCardinality(list_bit[5],list_bit[5])),toString(bitmapAndCardinality(list_bit[5],list_bit[6])),
                            toString(bitmapAndCardinality(list_bit[5],list_bit[7])),'','','',''),
                                array(list_date[6],new_num,toString(bitmapAndCardinality(list_bit[6],list_bit[6])),toString(bitmapAndCardinality(list_bit[6],list_bit[7])),'','','','',''),
                                    array(list_date[7],new_num,toString(bitmapAndCardinality(list_bit[7],list_bit[7])),'','','','','','')) as list_retention
        from
        (select toString(groupArray(date)) as date,groupArray(user_bit) as list_bit from 
           (select toDate(time) as date, groupBitmapState(distinct uid) as user_bit from behavior where tag='2022年4月10号注册的新用户' and event_type='登录' group by date order by date) )

在这里插入图片描述
bitmapAndCardinality的一些详细计算可以在这篇文章参考一下,有些函数附上了图解:
ClickHouse 集成Bitmap(2022-01-16更新)
也可以参考官网:
Bitmap Functions

3.2、retention函数

with (select toString(groupBitmap(uid)) from behavior where tag='2022年4月10号注册的新用户') as new_num,
(select ['2022-04-11','2022-04-12','2022-04-13','2022-04-14','2022-04-15','2022-04-16','2022-04-17']) as list_date
    select array(array(list_date[1],new_num,toString(sum(r20220411[1])),toString(sum(r20220411[2])),toString(sum(r20220411[3])),
                toString(sum(r20220411[4])),toString(sum(r20220411[5])),toString(sum(r20220411[6])),toString(sum(r20220411[7]))),
            array(list_date[2],new_num,toString(sum(r20220412[1])),toString(sum(r20220412[2])),toString(sum(r20220412[3])),toString(sum(r20220412[4])),
                toString(sum(r20220412[5])),toString(sum(r20220412[6])),''),
            array(list_date[3],new_num,toString(sum(r20220413[1])),toString(sum(r20220413[2])),toString(sum(r20220413[3])),toString(sum(r20220413[4])),
                toString(sum(r20220413[5])),'',''),
            array(list_date[4],new_num,toString(sum(r20220414[1])),toString(sum(r20220414[2])),toString(sum(r20220414[3])),toString(sum(r20220414[4])),'','',''),
            array(list_date[5],new_num,toString(sum(r20220415[1])),toString(sum(r20220415[2])),toString(sum(r20220415[3])),'','','',''),
            array(list_date[6],new_num,toString(sum(r20220416[1])),toString(sum(r20220416[2])),'','','','',''),
            (select array(list_date[7],new_num,toString(r20220417),'','','','','','') from (select groupBitmap(uid) r20220417 from behavior where tag='2022年4月10号注册的新用户'
                and event_type='登录' and time== toDate('2022-04-17')))) as list_retention
    from(
        SELECT
            uid,
            retention(time = toDate('2022-04-11'), time = toDate('2022-04-12'), time = toDate('2022-04-13'),time = toDate('2022-04-14'),
                time = toDate('2022-04-15'), time = toDate('2022-04-16'), time = toDate('2022-04-17') ) AS r20220411,
            retention(time = toDate('2022-04-12'), time = toDate('2022-04-13'),time = toDate('2022-04-14'),time = toDate('2022-04-15'), 
                time = toDate('2022-04-16'), time = toDate('2022-04-17') ) AS r20220412,
            retention(time = toDate('2022-04-13'),time = toDate('2022-04-14'),time = toDate('2022-04-15'), time = toDate('2022-04-16'), time = toDate('2022-04-17') ) AS r20220413,
            retention(time = toDate('2022-04-14'),time = toDate('2022-04-15'), time = toDate('2022-04-16'), time = toDate('2022-04-17') ) AS r20220414,
            retention(time = toDate('2022-04-15'), time = toDate('2022-04-16'), time = toDate('2022-04-17') ) AS r20220415,
            retention(time = toDate('2022-04-16'), time = toDate('2022-04-17') ) AS r20220416
        FROM behavior where tag='2022年4月10号注册的新用户' and event_type='登录'
        GROUP BY uid)

在这里插入图片描述
关于这个retention函数,官网有着例子:
retention(cond1, cond2, …, cond32)

其中:cond — 返回 UInt8 结果(1或0)的表达式。即数组为1或0。1 — 条件满足。0 — 条件不满足。

简单来说,这个函数的作用就是计算从瞄点cond1开始,后面的cond2依赖于cond1,cond3会依赖于cond2和cond2,以此类推,condN依赖于从cond1到condN-1的所有条件。以上面所写的代码为例,就是2022年04月16日有登录行为(cond1)的客户杨某,在2022年04月17日仍然有登录行为(cond2),这样,它返回的结果才为1。

4、留存图的pyecharts的实现

数据取自本文所伪造的样例数据所计算的结果。

import pyecharts.options as opts
from pyecharts.charts import Funnel
from pyecharts.globals import CurrentConfig,NotebookType
CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK

table = Table()

headers = ['日期','2022年4月10号注册的新增用户数','当天留存人数','一天后','两天后','三天后','四天后','五天后','六天后']
rows = [['2022-04-11','64614','42982','25877','22920','19655','15811','11266','6006'],
  ['2022-04-12','64614','39480','21041','18010','14588','10376','5503',''],
  ['2022-04-13', '64614', '34881', '15821', '12863', '9121', '4955', '', ''],
  ['2022-04-14', '64614', '29922', '10851', '7875', '4206', '', '', ''],
  ['2022-04-15', '64614', '24064', '6353', '3428', '', '', '', ''],
  ['2022-04-16', '64614', '17223', '2522', '', '', '', '', ''],
  ['2022-04-17', '64614', '9255', '', '', '', '', '', '']]
table.add(headers, rows)
table.set_global_opts(
    title_opts=ComponentTitleOpts(title="留存分析", subtitle="周留存图")
)
table.render_notebook()

在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会点东西的普通人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值