本文经作者授权,独家转载:

作者主页:https://www.jianshu.com/u/8f36a5e63d18

为什么要有路径分析,举个最简单的例子,你的领导想要知道用户在完成下单前的一个小时都做了什么?绝大多数人拿到这个需求的做法就是进行数据抽样观察以及进行一些简单的问卷调参工作,这种方式不但费时费力还不具有代表性,那么这个时候你就需要一套用户行为路径分析的模型作为支撑,才能快速帮组你找到最佳答案

前言

clickhouse是我见过最完美的OLAP数据库,它不仅将性能发挥到了极致,还在数据分析层面做了大量改进和支撑,为用户提供了大量的高级聚合函数和基于数组的高阶lambda函数。

企业中常用的路径分析模型一般有两种:

  • 已经明确了要分析的路径,需要看下这些访问路径上的用户数据:关键路径分析

  • 不确定有哪些路径,但是清楚目标路径是什么,需要知道用户在指定时间范围内都是通过哪些途径触达目标路径的:智能路径分析

关键路径分析

因为我们接下来要通过sequenceCount完成模型的开发,所以需要先来了解一下该函数的使用:

sequenceCount(pattern)(timestamp, cond1, cond2, ...)

该函数通过pattern指定事件链,当用户行为完全满足事件链的定义是会+1;其中time时间类型或时间戳,单位是秒,如果两个事件发生在同一秒时,是无法准确区分事件的发生先后关系的,所以会存在一定的误差。

pattern支持3中匹配模式:

  • (?N):表示时间序列中的第N个事件,从1开始,最长支持32个条件输入;如,(?1)对应的是cond1

  • (?t op secs):插入两个事件之间,表示它们发生时需要满足的时间条件(单位为秒),支持 >=, >,

  • .*:表示任意的非指定事件。

例如,boos要看在会员购买页超过10分钟才下单的用户数据 那么就可以这么写:

SELECT     count(1) AS c1,    sum(cn) AS c2FROM (    SELECT         u_i,        sequenceCount('(?1)(?t>600)(?2)')(toDateTime(time), act = '会员购买页', act = '会员支付成功') AS cn    FROM app.scene_tracker    WHERE day = '2020-09-07'    GROUP BY u_i)WHERE cn >= 1┌──c1─┬──c2─┐│ 102 │ 109 │└─────┴─────┘## c1是满足条件的用户数,c2是满足条件的用户行为总数

根据上面数据可以看出完成支付之前在会员购买页停留超过10分钟的用户有100多个,那么是什么原因导致用户迟迟不肯下单,接下来我们就可以使用智能路径针对这100个用户展开分析,看看他们在此期间都做了什么。

智能路径分析

智能路径分析模型比较复杂,但同时支持的分析需求也会更加复杂,如分析给定期望的路径终点、途经点和最大事件时间间隔,统计出每条路径的用户数,并按照用户数对路径进行倒序排列
虽然clickhouse没有提供现成的分析函数支持到该场景,但是可以通过clickhouse提供的高阶数组函数进行曲线救国,大致SQL如下:

方案一
 SELECT  result_chain,  uniqCombined(user_id) AS user_countFROM (  WITH    toDateTime(maxIf(time, act = '会员支付成功')) AS end_event_maxt,  #以终点事件时间作为路径查找结束时间    arrayCompact(arraySort(  #对事件按照时间维度排序后进行相邻去重      x -> x.1,      arrayFilter(  #根据end_event_maxt筛选出所有满足条件的事件 并按照>结构返回        x -> x.1 <= end_event_maxt,        groupArray((toDateTime(time), (act, page_name)))      )    )) AS sorted_events,    arrayEnumerate(sorted_events) AS event_idxs,  #或取事件链的下标掩码序列,后面在对事件切割时会用到    arrayFilter(  #将目标事件或当前事件与上一个事件间隔10分钟的数据为切割点      (x, y, z) -> z.1 <= end_event_maxt AND (z.2.1 = '会员支付成功' OR y > 600),      event_idxs,      arrayDifference(sorted_events.1),      sorted_events    ) AS gap_idxs,    arrayMap(x -> x + 1, gap_idxs) AS gap_idxs_,  #如果不加1的话上一个事件链的结尾事件会成为下个事件链的开始事件    arrayMap(x -> if(has(gap_idxs_, x), 1, 0), event_idxs) AS gap_masks,  #标记切割点    arraySplit((x, y) -> y, sorted_events, gap_masks) AS split_events  #把用户的访问数据切割成多个事件链  SELECT    user_id,    arrayJoin(split_events) AS event_chain_,    arrayCompact(event_chain_.2) AS event_chain,  #相邻去重    hasAll(event_chain, [('pay_button_click', '会员购买页')]) AS has_midway_hit,    arrayStringConcat(arrayMap(      x -> concat(x.1, '#', x.2),      event_chain    ), ' -> ') AS result_chain  #用户访问路径字符串  FROM (    SELECT time,act,page_name,u_i as user_id    FROM app.scene_tracker    WHERE toDate(time) >= '2020-09-30' AND toDate(time) <= '2020-10-02'    AND user_id IN (10266,10022,10339,10030)  #指定要分析的用户群  )  GROUP BY user_id  HAVING length(event_chain) > 1)WHERE event_chain[length(event_chain)].1 = '会员支付成功'  #事件链最后一个事件必须是目标事件AND has_midway_hit = 1   #必须包含途经点GROUP BY result_chainORDER BY user_count DESC LIMIT 20;

实现思路:

  • 将用户的行为用groupArray函数整理成>的元组,并用arraySort函数按时间升序排序;
  • 利用arrayEnumerate函数获取原始行为链的下标数组;
  • 利用arrayFilter和arrayDifference函数,过滤出原始行为链中的分界点下标。分界点的条件是路径终点或者时间差大于最大间隔;
  • 利用arrayMap和has函数获取下标数组的掩码(由0和1组成的序列),用于最终切分,1表示分界点;
  • 调用arraySplit函数将原始行为链按分界点切分成单次访问的行为链。注意该函数会将分界点作为新链的起始点,所以前面要将分界点的下标加1;
  • 调用arrayJoin和arrayCompact函数将事件链的数组打平成多行单列,并去除相邻重复项。
  • 调用hasAll函数确定是否全部存在指定的途经点。如果要求有任意一个途经点存在即可,就换用hasAny函数。当然,也可以修改WHERE谓词来排除指定的途经点。
  • 将最终结果整理成可读的字符串,按行为链统计用户基数,完成。

方案二不设置途经点,且仅以用户最后一次到达目标事件作为参考

 SELECT  result_chain,  uniqCombined(user_id) AS user_count   FROM (        select          u_i as user_id,          arrayStringConcat(  #获取访问路径字符串           arrayCompact(   #相邻事件去重            arrayMap(              b - > tupleElement(b, 1),              arraySort(   #对用户事件进行排序得到用户日志的先后顺序                y - > tupleElement(y, 2),                arrayFilter(                  (x, y) - > y - x.2 > 3600  #找到目标节点前1小时内的所有事件                     arrayMap(                    (x, y) - > (x, y),                    groupArray(e_t),                    groupArray(time)                  ),                   arrayWithConstant(                    length(groupArray(time)),                    maxIf(time, e_t = '会员支付成功')  #设置目标节点                  )                )              )            )           ),          '->'         ) result_chain        from          bw.scene_tracker        where          toDate(time) >= '2020-09-30' AND toDate(time) <= '2020-10-02' AND user_id IN (10266,10022,10339,10030)        group by          u_i  ) tab   GROUP BY result_chain  ORDER BY user_count DESC LIMIT 20;

简单说一下上面用到的几个高阶函数:

1.arrayJoin

可以理解为行转列操作

SELECT arrayJoin([1, 2, 3, 4]) AS data┌─data─┐│    1 ││    2 ││    3 ││    4 │└──────┘

2.uniqCombined

clickhouse中的高性能去重统计函数,类似count(distinct field),数据量比较小的时候使用数组进行去重,中的数据使用set集合去重,当数据量很大时会使用hyperloglog方式进行j近似去重统计;如果想要精度更改可以使用uniqCombined64支持64位bit

SELECT uniqCombined(data)FROM (    SELECT arrayJoin([1, 2, 3, 1, 4, 2]) AS data)┌─uniqCombined(data)─┐│                  4 │└────────────────────┘

3. arrayCompact对数组中的数据进行相邻去重,用户重复操作的事件只记录一次

SELECT arrayCompact([1, 2, 3, 3, 1, 1, 4, 2]) AS data┌─data──────────┐│ [1,2,3,1,4,2] │└───────────────┘

4. arraySort
对数组中的数据按照指定列进行升序排列;降序排列参考arrayReverseSort

SELECT arraySort(x -> (x.1), [(1, 'a'), (4, 'd'), (2, 'b'), (3, 'c')]) AS data┌─data──────────────────────────────┐│ [(1,'a'),(2,'b'),(3,'c'),(4,'d')] │└───────────────────────────────────┘

5. arrayFilter
只保留数组中满足条件的数据

SELECT arrayFilter(x -> (x > 2), [12, 3, 4, 1, 0]) AS data┌─data─────┐│ [12,3,4] │└──────────┘

6. groupArray
将分组下的数据聚合到一个数组集合中,类似hive中的collect_list函数

SELECT     a.2,    groupArray(a.1)FROM (    SELECT arrayJoin([(1, 'a'), (4, 'a'), (3, 'a'), (2, 'c')]) AS a)GROUP BY a.2┌─tupleElement(a, 2)─┬─groupArray(tupleElement(a, 1))─┐│ c                  │ [2]                            ││ a                  │ [1,4,3]                        │└────────────────────┴────────────────────────────────┘

7. arrayEnumerate
或取数组的下标掩码序列

SELECT arrayEnumerate([1, 2, 3, 3, 1, 1, 4, 2]) AS data┌─data──────────────┐│ [1,2,3,4,5,6,7,8] │└───────────────────┘

8.arrayDifference
参数必须是数值类型;计算数组中相邻数字的差值,第一个值为0

SELECT arrayDifference([3, 1, 1, 4, 2]) AS data┌─data──────────┐│ [0,-2,0,3,-2] │└───────────────┘

9.arrayMap
对数组中的每一列进行处理,并返回长度相同的新数组

SELECT arrayMap(x -> concat(toString(x.1), ':', x.2), [(1, 'a'), (4, 'a'), (3, 'a'), (2, 'c')]) AS data┌─data──────────────────────┐│ ['1:a','4:a','3:a','2:c'] │└───────────────────────────┘

10.arraySplit

按照规则对数组进行分割

SELECT arraySplit((x, y) -> y, ['a', 'b', 'c', 'd', 'e'], [1, 0, 0, 1, 0]) AS data┌─data──────────────────────┐│ [['a','b','c'],['d','e']] │└───────────────────────────┘## 遇到下标为1时进行分割,分割点为下一个 数组的起始点;注意,首项为1还是0不影响结果

11.has
判断数组中是否包含某个数据

SELECT has([1, 2, 3, 4], 2) AS data┌─data─┐│    1 │└──────┘

12.arrayStringConcat
将数组转为字符串,需要注意的是,这里的数组项需要是字符串类型

SELECT arrayStringConcat(['a', 'b', 'c'], '->') AS data┌─data────┐│ a->b->c │└─────────┘

13.arrayWithConstant以某个值进行填充生成数组

SELECT arrayWithConstant(4, 'abc') AS data┌─data──────────────────────┐│ ['abc','abc','abc','abc'] │└───────────────────────────┘

推荐阅读:hive join 数据倾斜 真实案例基于Hadoop的数仓设计-Impala和Hive

根据id获取多维数组路径_clickhouse数据模型之用户路径分析相关推荐

  1. 根据id获取多维数组路径_程序员的进阶课-架构师之路(2)-数组

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/m0_37609579/article/ ...

  2. PHP 如何获取二维数组中某个key的集合(高性能查找)

    分享下PHP 获取二维数组中某个key的集合的方法. 具体是这样的,如下一个二维数组,是从库中读取出来的. 代码: $user = array( 0 => array( 'id' => 1 ...

  3. PHP获取二维数组中某一列的值集合

    PHP还是比较常用的,于是我研究了一下PHP二维数组,下面通过本文给大家介绍PHP获取二维数组中某一列的值集合,对php数组二维数组的值相关知识感兴趣的朋友一起学习吧 PHP还是比较常用的,于是我研究 ...

  4. php中count获取多维数组长度的方法

    转自:http://www.jb51.net/article/57021.htm 本文实例讲述了php中count获取多维数组长度的实现方法.分享给大家供大家参考.具体分析如下: 先来看看下面程序运行 ...

  5. php 动态多维数组长度,怎么在php中利用count获取多维数组的长度

    怎么在php中利用count获取多维数组的长度 发布时间:2021-01-05 16:38:55 来源:亿速云 阅读:80 作者:Leah 今天就跟大家聊聊有关怎么在php中利用count获取多维数组 ...

  6. php数组取字段值,php如何获取二维数组指定的某个字段的值

    php怎么获取二维数组指定的某个字段的值? 需求:有如下数组$arr,数组中有两个模块一个net 一个login,我怎么根据net模块获取最后一个marks, 其中net模块和login模块的数据还是 ...

  7. numpy获取二维数组某一行、某一列

    numpy获取二维数组某一行.某一列 1.需求 有一个二维数组: a = [[1, 2, 3, 4, 5, 6],[7, 8, 9, 10, 11, 12],[13.2, 14.8, 15.9, 16 ...

  8. python读取二维数组的行列数_Python获取二维数组的行列数的2种方法

    Python获取二维数组的行列数的2种方法 这篇文章主要介绍了Python获取二维数组的行列数的2种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考 ...

  9. Java二维数组详解:二维数组的声明和初始化,以及获取二维数组的值

    为了方便组织各种信息,计算机常将信息以表的形式进行组织,然后再以行和列的形式呈现出来.二维数组的结构决定了其能非常方便地表示计算机中的表,以第一个下标表示元素所在的行,第二个下标表示元素所在的列.下面 ...

最新文章

  1. 挨踢项目求生法则-战略篇
  2. Block的副本放置策略
  3. 陈老师Linux内核概述导学
  4. lte 中crs_LTE网络CRS功率配置及其影响研究
  5. IBM服务器指示灯报警说明
  6. php 双向加密解密,一个php双向加密解密法
  7. 站立会议05(第二次冲刺)
  8. 数字图像的大小、所需比特数(二维)
  9. Android屏幕禁止休眠的方法
  10. iOS开发之MapKit
  11. 第二单元答案计算机基础,《计算机基础》资源包-第二单元课后题答案.docx
  12. 析构函数定义为虚函数原因
  13. linux 文件读写 加速,MMAP文件加速读写小技俩
  14. 图神经网络——node2vec
  15. wine linux安装目录,[Linux]WINE安装教程
  16. 用scratch实现网上“超人训练”游戏
  17. 监测 Windows 应用行为
  18. Kubernetes:(十四)安全机制(一定要做好安全措施哦)
  19. MIKE 21 教程 2.5 水动力模块教学:涡粘系数(Eddy viscosity),河床阻力与曼宁系数(Bed Resistance)
  20. ChatGPT的注册和使用教程

热门文章

  1. php常用算法的时间复杂度,php的几个经典排序算法及时间复杂度和耗时​
  2. 沃尔什哈达玛变换Matlab,哈达玛变换矩阵-数字图像处理.ppt
  3. 中南大学c语言试题期末考试,2011年中南大学C语言期末试题卷A
  4. c语言 文件加密头文件,[C语言]文件加密
  5. python3学习之反射的四种基本方法
  6. ●洛谷P3687 [ZJOI2017]仙人掌
  7. 转:Dubbo与Zookeeper、SpringMVC整合和使用(负载均衡、容错)
  8. npm以及gulp相关操作
  9. php获取网页内容方法总结
  10. sicily 1156 ——虽然Wrong error(原因尚未查明),但温习了一下基础知识