漏斗分析是常见的转化分析方法,可以分析用户在各个阶段的行为转化率,帮助管理者或运营等角色通过转化率来衡量每个阶段的转化情况,从而达到优化产品,提升转化率的目的,被广泛应用于用户行为分析和App数据分析的流量分析、产品目标转化等数据运营与数据分析领域。
背景信息
事件(Event)代表了用户的某个或一系列有意义的行为,比如游戏App的下载、注册、登录等,通过分析用户的各项行为数据还原用户真实的使用过程,从而提升产品转化率,助力业务增长。常见的用户行为分析包括事件分析、漏斗分析、留存分析等。漏斗分析是一种用来分析用户在指定阶段转化情况的分析模型,可以分析用户在各个阶段的行为转化率,然后通过转化率来衡量每一个阶段的表现,从而助力产品针对性优化体验,提升转化率。
Hologres是阿里云自研的一站式实时数仓,支持多种场景的实时数据多维分析。在用户行为分析场景上,Hologres提供多种漏斗函数,快速高效的帮助业务进行用户行为分析,被广泛应用在互联网、电商、游戏等行业客户的用户分析场景中。
使用限制
仅Hologres V0.9及以上版本支持windowFunnel函数。
仅Hologres V2.1及以上版本支持range_funnel函数。
漏斗函数均需要由具备Superuser权限的账号执行以下语句开启Extension。
CREATE extension flow_analysis; --开启Extension
Extension是数据库级别的函数,一个数据库只需开启一次即可。
Extension默认加载到public Schema下,且不支持加载到其他Schema。
前置场景说明
本文中的示例全部基于GitHub公开事件数据集。
数据集介绍
大量开发人员在GitHub上进行开源项目的开发工作,并在项目的开发过程中产生海量事件。GitHub会记录每次事件的类型及详情、开发者、代码仓库等信息,并开放其中的公开事件,包括加星标、提交代码等,具体事件类型请参见Webhook events and payloads。
数据集数据导入
通过Hologres将github_event数据集一键导入至Hologres实例,详情请参见一键导入公共数据集。
GitHub公开事件的建表语句如下:
BEGIN;
CREATE TABLE hologres_dataset_github_event.hologres_github_event (
id bigint,
actor_id bigint,
actor_login text,
repo_id bigint,
repo_name text,
org_id bigint,
org_login text,
type text,
created_at timestamp with time zone NOT NULL,
action text,
iss_or_pr_id bigint,
number bigint,
comment_id bigint,
commit_id text,
member_id bigint,
rev_or_push_or_rel_id bigint,
ref text,
ref_type text,
state text,
author_association text,
language text,
merged boolean,
merged_at timestamp with time zone,
additions bigint,
deletions bigint,
changed_files bigint,
push_size bigint,
push_distinct_size bigint,
hr text,
month text,
year text,
ds text
);
CALL set_table_property('hologres_dataset_github_event.hologres_github_event', 'orientation', 'column');
CALL set_table_property('hologres_dataset_github_event.hologres_github_event', 'bitmap_columns', 'actor_login,repo_name,org_login,type,action,commit_id,ref,ref_type,state,author_association,language,hr,month,year,ds');
CALL set_table_property('hologres_dataset_github_event.hologres_github_event', 'clustering_key', 'created_at:asc');
CALL set_table_property('hologres_dataset_github_event.hologres_github_event', 'dictionary_encoding_columns', 'actor_login:auto,repo_name:auto,org_login:auto,type:auto,action:auto,commit_id:auto,ref:auto,ref_type:auto,state:auto,author_association:auto,language:auto,hr:auto,month:auto,year:auto,ds:auto');
CALL set_table_property('hologres_dataset_github_event.hologres_github_event', 'distribution_key', 'id');
CALL set_table_property('hologres_dataset_github_event.hologres_github_event', 'segment_key', 'created_at');
CALL set_table_property('hologres_dataset_github_event.hologres_github_event', 'time_to_live_in_seconds', '3153600000');
COMMENT ON TABLE hologres_dataset_github_event.hologres_github_event IS NULL;
ALTER TABLE hologres_dataset_github_event.hologres_github_event OWNER TO test1_developer;
END;
漏斗函数(windowFunnel)
漏斗分析是用来分析用户在指定阶段转化情况的一种分析模型,可以分析用户在各个阶段的行为转化率,帮助管理者或运营等角色通过转化率来衡量每个阶段的转化情况,从而达到优化产品,提升转化率的目的。
函数语法
漏斗函数(windowFunnel)可以搜索滑动时间窗口中的事件列表,并计算条件匹配的事件列表的最大长度。
windowFunnel会从第一个事件开始匹配,依次做最长、有序匹配,返回匹配的最大长度。一旦匹配失败,结束整个匹配。
假设在窗口足够大的条件下:
条件事件为c1,c2,c3,而用户数据为c1,c2,c3,c4,最终匹配到c1,c2,c3,函数返回值为3。
条件事件为c1,c2,c3,而用户数据为c4,c3,c2,c1,最终匹配到c1,函数返回值为1。
条件事件为c1,c2,c3,而用户数据为c4,c3,最终没有匹配到事件,函数返回值为0。
windowFunnel函数的语法如下:
windowFunnel(window, mode, timestamp, cond1, cond2, ..., condN)
参数说明:
参数 | 说明 |
window | 统计的窗口时间大小,即从指定的第一个事件开始的时间,根据窗口时间提取后续的相关事件数据。 |
mode | 模式。支持default和strict两种模式 ,默认为default。
|
timestamp | 事件发生的时间范围,支持timestamp、int、bigint类型。 |
cond | 事件类型,代表事件的每个步骤。 |
使用示例
以前置场景中的GitHub公开数据集分析为例,分析一段时间内,用户按照固定转化路径的漏斗情况,使用如下SQL进行分析,SQL中的各个条件如下:
统计间隔:30分钟(即1800秒)
统计时间段:2023-07-28 10:00:00+08至2023-07-31 10:00:00+08
转化路径:共3个事件,依次发生的步骤为:CreateEvent>PushEvent>IssuesEvent
--计算每个用户的漏斗情况
SELECT
actor_id,
windowFunnel (1800, 'default', created_at, type = 'CreateEvent',type = 'PushEvent',type = 'IssuesEvent') AS level
FROM
hologres_dataset_github_event.hologres_github_event
WHERE
created_at >= TIMESTAMP '2023-07-28 10:00:00+08'
AND created_at < TIMESTAMP '2023-07-31 10:00:00+08'
GROUP BY
actor_id
部分显示结果如下,其中:
level=0表示用户在窗口期内没有匹配到第一个事件。
level=1表示用户在窗口期内匹配到第一个事件。
level=2表示用户在窗口期内匹配到第二个事件。
level=3表示用户在窗口期内三个事件都匹配到。
actor_id | level
----------+------
69438111 | 0
62698183 | 0
139085682 | 1
1765075 | 1
132836353 | 2
122081842 | 2
89815869 | 3
为了提高结果的阅读性,您可以通过如下SQL查看每一个步骤分别转化的用户数。
WITH level_detail AS (
SELECT
level,
COUNT(1) AS count_user
FROM (
SELECT
actor_id,
windowFunnel (1800, 'default', created_at, type = 'CreateEvent', type = 'PushEvent',type = 'IssuesEvent') AS level
FROM
hologres_dataset_github_event.hologres_github_event
WHERE
created_at >= TIMESTAMP '2023-07-28 10:00:00+08'
AND created_at < TIMESTAMP '2023-07-31 10:00:00+08'
GROUP BY
actor_id) AS basic_table
GROUP BY
level
ORDER BY
level ASC
)
SELECT CASE level WHEN 0 THEN 'total'
WHEN 1 THEN 'CreateEvent'
WHEN 2 THEN 'PushEvent'
WHEN 3 THEN 'IssuesEvent'
END
,SUM(count_user) over ( ORDER BY level DESC )
FROM
level_detail
GROUP BY
level,
count_user
ORDER BY
level ASC;
结果如下:
case | sum
------------+------
total | 864120
CreateEvent | 275053
PushEvent | 120242
IssuesEvent | 2652
区间漏斗函数
业务在做漏斗分析时,通常会进行分组统计,比如按照天、小时等自定义时间窗口统计。为了更好地满足业务需求,Hologres从V2.1版本开始,在漏斗函数(windowFunnel)的基础上,额外扩展了一个区间漏斗函数range_funnel,range_funnel与windowFunnel函数的区别如下:
windowFunnel函数只能对输入的事件数据做一次聚合计算,结果为完整的时间区间。range_funnel函数不仅支持输出总的聚合计算结果,还能输出自定义时间窗口的分组统计结果,结果为一个数组。
windowFunnel函数不支持提取多个相同事件,而range_funnel函数支持提取多个相同事件。range_funnel的匹配逻辑如下:
如果条件事件为c1,c2,c3,而用户数据为c1,c2,c1,c3,则返回3。
如果有相同事件如c1,c1,c1,用户数据为c1,c2,c1,c3,则返回2。
区间漏斗函数(range_funnel)
仅Hologres V2.1及以上版本的实例可以使用该函数。
函数语法
range_funnel(window, event_size, range_begin, range_end, interval, event_ts, event_bits, use_interval_window, mode)
参数说明
参数
类型
说明
window
interval
统计的窗口时间大小,从第一个事件开始,往后推移一个窗口区间内的事件。单位:秒。
如果window=0,会根据每个区间的起止位置截断,如果正好是每天0点,就变成了自然日,但只能是一个自然日。
当use_interval_window=false时,按照原语义执行,单位:秒。
当use_interval_window=true时,表示窗口为n个区间(包含当前区间)。如果是按天作为漏斗,1、2、3代表多少个自然日。
event_size
int
要分析的事件总数。
range_begin
timestamptz/timestamp/date
分析时段的起始时间,以第一个事件开始计算。
range_end
timestamptz/timestamp/date
分析时段的终止时间,以第一个事件开始计算。
interval
interval
分析时段会以interval为粒度切分成多个连续的区间,分别进行漏斗分析并产出结果。单位:秒。
event_ts
timestamp/timestamptz
事件的发生时间字段。支持timestamp、timestamptz类型。
说明该参数从零点开始计算,因此可能会与真实时间存在一定的时差,通常用于观察天和周的趋势,具体时间可以忽略。
event_bits
bitmap
事件类型字段。以int32类型的bitmap表示,从低位至高位按顺序依次代表一个事件。因此,最多支持32个事件的漏斗分析。
use_interval_window
text
是否使用区间分界计算窗口,默认为false。
说明仅Hologres V2.2.30及以上版本支持该参数。
mode
text
mode='0'
(默认):,相同时间发生的不同事件只会被随机提取一个事件作为转化,其余事件丢弃。mode='1'
:相同时间发生的不同事件都会被作为转化。
说明mode='1'
时,不支持相同事件。仅Hologres V2.2.30及以上版本支持该参数。
返回结果
range_funnel输出一个int64类型的数组,bigint[]。需要注意数组的结果是一个编码值,该值按区间展示,由区间的起始时间(56bit)和提取到的事件数(8bit)两部分拼接组成,所以输出结果之后还需要对数组的内容进行解码,才能得到最终的匹配数据。
同时,区间漏斗函数的结果是经过编码的,结果的可阅读性不强,因此需要使用SQL进行解码。为了更方便地对结果进行解码,Hologres从V2.1.6版本开始,支持使用range_funnel_time和range_funnel_level两个函数对区间漏斗函数进行解码。
区间漏斗解码函数
函数语法
range_funnel_time(range_funnel()) range_funnel_level(range_funnel())
参数说明
函数名
描述
输入参数
输出参数
range_funnel_time
对区间漏斗函数(range_funnel)返回int64结果中的事件时间进行解码。
range_funnel函数输出的int64结果。
事件时间,类型为timestamptz。
range_funnel_level
对区间漏斗函数(range_funnel)返回int64结果中的事件等级进行解码。
range_funnel函数输出的int64结果。
事件等级,类型为bigint。
使用示例
按天展示每天漏斗情况
以前置场景中的GitHub公开数据集为例,分析一段时间内,用户按照固定转化路径的漏斗情况,并且按天分组展示。使用如下SQL进行分析,SQL中的各条件如下:
统计时间间隔:1小时(3600秒)
统计时间段:2023-07-23至2023-07-25号,共计3天
转化路径:共2个事件,依次发生步骤为:CreateEvent>PushEvent
分组时间:1天(86400秒),即按天分组展示每一天的漏斗情况
由于事件类型type字段的类型为text,而range_funnel函数中事件类型event_bits字段的类型必须为32位的bitmap,因此使用bit_construct函数将事件类型转换为bitmap
--这是没有解码的结果
SELECT
actor_id,
range_funnel (3600, 2, '2023-07-23', '2023-07-25', 86400, created_at::timestamp, bits) AS result
FROM (
SELECT
actor_id,
created_at::timestamp,
type,
bit_construct (a := type = 'CreateEvent', b := type = 'PushEvent') AS bits
FROM
hologres_dataset_github_event.hologres_github_event WHERE ds >= '2023-07-23' AND ds <='2023-07-25') tt GROUP BY actor_id ORDER BY actor_id ;
部分查询结果如下:
actor_id | result
----------+------
17 |{432658022400,432680140800,432702259202,9223372036854775554}
47 |{432658022402,432680140800,432702259200,9223372036854775554}
235 |{432658022401,432680140800,432702259200,9223372036854775553}
其中result字段说明如下:
结果为空代表该用户没有匹配到任何时间。
结果中包括总的漏斗结果,以及按天分组展示的漏斗结果,是一个没有解码的数组数据。
为了使结果更具有阅读性,我们使用range_funnel_time和range_funnel_level两个函数对result的结果进行解码,同时按照用户ID展开。SQL命令如下:
SELECT actor_id,
to_timestamp(range_funnel_time(result)) AS res_time, --时间解码
range_funnel_level(result) AS res_level --事件level解码
FROM (
SELECT actor_id, result, COUNT(1) AS cnt FROM (
SELECT actor_id,
UNNEST(range_funnel (3600, 2, '2023-07-23', '2023-07-26', 86400, created_at::timestamp, bits)) AS result FROM (
SELECT actor_id, created_at::timestamp, type, bit_construct (a := type = 'CreateEvent', b := type = 'PushEvent') AS bits from hologres_dataset_github_event.hologres_github_event where ds >= '2023-07-23' AND ds <='2023-07-25'
) a
GROUP BY actor_id
) a
GROUP BY actor_id ,result
) a
ORDER BY actor_id ,res_time limit 10000;
查询后的部分结果如下所示,结果中可以看到每个用户每天匹配的level和次数:
actor_id | res_time | res_level
----------------+-----------------------+-----------
17 |2023-07-23 08:00:00 | 0
17 |2023-07-24 08:00:00 | 0
17 |2023-07-24 08:00:00 | 2
17 |\N | 0
47 |2023-07-23 08:00:00 | 0
47 |2023-07-24 08:00:00 | 0
47 |2023-07-25 08:00:00 | 2
47 |\N | 2
235 |2023-07-23 08:00:00 | 1
235 |2023-07-24 08:00:00 | 0
235 |2023-07-25 08:00:00 | 0
235 |\N | 1
查出每个用户按天分组的漏斗结果之后,我们也可以根据业务需求做进一步的数据探查。示例:查看每天步长汇总以及总的汇总数据,其中下一个level一定包含上一个level。
SELECT res_time, res_level, SUM(cnt) OVER (PARTITION BY res_time ORDER BY res_level DESC) AS res_cnt FROM (
SELECT
to_timestamp(range_funnel_time(result)) AS res_time, --时间解码
range_funnel_level(result) AS res_level, --事件level解码
cnt
FROM (
SELECT result, COUNT(1) AS cnt FROM (
SELECT actor_id,
UNNEST(range_funnel (3600, 2, '2023-07-23', '2023-07-26', 86400, created_at::timestamp, bits)) AS result FROM (
SELECT actor_id, created_at::timestamp, type, bit_construct (a := type = 'CreateEvent', b := type = 'PushEvent') AS bits FROM hologres_dataset_github_event.hologres_github_event WHERE ds >= '2023-07-23' AND ds <='2023-07-25'
) a
GROUP BY actor_id
) a
GROUP BY result
) a
)a
WHERE res_level > 0
GROUP BY res_time, res_level, cnt ORDER BY res_time, res_level;
查询结果如下,其中:
结果中
\N
:代表多天汇总的结果。cnt
字段的内容代表每个level的汇总数据,下一个level一定包含上一个level的数据,例如res_level为2,cnt为1,代表经历过步骤1,步骤2的用户数只有1个。
res_time |res_level | cnt
------------------------+---------------+------
2023-07-23 08:00:00 |1 |114455
2023-07-23 08:00:00 |2 |56155
2023-07-24 08:00:00 |1 |154616
2023-07-24 08:00:00 |2 |71200
2023-07-25 08:00:00 |1 |178662
2023-07-25 08:00:00 |2 |81779
\N |1 |390262
\N |2 |188125
相同时间发生不同事件作为转化
当range_funnel函数的输入参数mode='1'
时,可以将相同时间发生的不同事件都作为一个转化。示例如下:
CREATE extension flow_analysis;
CREATE TABLE funnel_test (
uid int,
event text,
create_time timestamptz
);
INSERT INTO funnel_test VALUES
(11, 'login', '2024-09-26 16:15:28+08'),
(11, 'watch', '2024-09-26 16:15:28+08'),
(11, 'buy', '2024-09-26 16:16:28+08'),
(22, 'login', '2024-09-26 16:15:28+08'),
(22, 'watch', '2024-09-26 16:16:28+08'),
(22, 'buy', '2024-09-26 16:17:28+08');
SELECT res_time, res_level, SUM(cnt) OVER (PARTITION BY res_time ORDER BY res_level DESC) AS res_cnt FROM (
SELECT
to_timestamp(range_funnel_time(result)) AS res_time, --时间解码
range_funnel_level(result) AS res_level, --事件level解码
cnt
FROM (
SELECT result, COUNT(1) AS cnt FROM (
SELECT uid,
UNNEST(range_funnel (3600, 3, '2024-09-26', '2024-09-27', 86400, create_time::timestamp, bits,false,'1')) AS result FROM (
SELECT uid, create_time::timestamp, event, bit_construct (a := event = 'login', b := event = 'watch',c := event = 'buy') AS bits FROM funnel_test
) a
GROUP BY uid
) a
GROUP BY result
) a
)a
GROUP BY res_time, res_level, cnt ORDER BY res_time, res_level;
输出结果如下,可以看到相同时间发生的不同事件都被作为了转化。
res_time | res_level | res_cnt
------------------------+-----------+---------
2024-09-26 08:00:00+08 | 3 | 2
| 3 | 2
(2 rows)
按天分组统计,窗口期是多个自然日
在实际场景中,可能需要价区间分析转化的情况,Hologres的range_funnel函数通过输入参数use_interval_window=true
可以实现区间分界计算漏斗。示例:多个自然日作为漏斗窗口计算转化情况。
--按天分组统计,漏斗窗口支持多个自然日
CREATE TABLE funnel_test_2 (
uid int,
event text,
create_time timestamptz
);
INSERT INTO funnel_test_2 VALUES
(11, 'login', '2024-09-24 16:15:28+08'),
(11, 'watch', '2024-09-25 16:15:28+08'),
(11, 'buy', '2024-09-26 16:16:28+08'),
(22, 'login', '2024-09-24 16:15:28+08'),
(22, 'watch', '2024-09-25 16:16:28+08'),
(22, 'buy', '2024-09-26 16:17:28+08');
多个自然日作为漏斗窗口转化:
--多个自然日作为窗口,3天作为一个窗口
SELECT
to_timestamp(range_funnel_time(result)) AS res_time, --时间解码
range_funnel_level(result) AS res_level, --事件level解码
cnt
FROM (
SELECT result, count(1) AS cnt FROM (
SELECT uid,
unnest(range_funnel (3, 3, '2024-09-24', '2024-09-27', 86400, create_time::timestamp, bits,true,'1')) AS result FROM (
SELECT uid, create_time::timestamp, event, bit_construct (a := event = 'login', b := event = 'watch',c := event = 'buy') AS bits FROM funnel_test_2
) a
GROUP BY uid
) a
GROUP BY result
) a;
输出结果如下,可以看到每天的转化情况:
res_time | res_level | cnt
------------------------+-----------+-----
2024-09-26 08:00:00+08 | 0 | 2
| 3 | 2
2024-09-24 08:00:00+08 | 3 | 2
2024-09-25 08:00:00+08 | 0 | 2
(4 rows)