漏斗函数

漏斗分析是常见的转化分析方法,可以分析用户在各个阶段的行为转化率,帮助管理者或运营等角色通过转化率来衡量每个阶段的转化情况,从而达到优化产品,提升转化率的目的,被广泛应用于用户行为分析和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。

  • default:在同一个窗口期内,从第一个事件开始匹配,尽量匹配多的事件。例如条件事件为c1,c2,c3,而用户数据为c1,c2,c1,c3,则返回3。

  • strict:windowFunnel()仅对唯一值应用匹配条件,即遇到相同的事件就停止继续匹配。例如条件事件为c1,c2,c3,而用户数据为c1,c2,c1,c3,则返回2。

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)