曾经在Airbnb的博客上看到一篇关于建立信任的文章(外网需翻墙),当时留意到里面描述如何运用数据来支持设计的部分。其中一个点是关于对房东个人主页的加强,因为他们从数据上看到那些成功下单的用户,有一半的人都去过房东个人主页,这其中68%的访问是在下单前去的,而且他们还发现新用户在下单去访问房东个人主页的可能性比老用户高出了20%。他们逐渐发现该页面的重要性,所以进行了相应的改进(比如房东必须上传真实头像),这样能加强房客对房东的了解,从而增进信任。虽然这部分叙述得非常简单,但是可以想象,在这些数据的支持下,产品团队能够找到方向去改进,那就是房东个人主页,至于如何改进,辅以一些定性的研究,就知道该如何提升该页面。
身为用研,当时看完这部分时,就想找机会试下怎样能得到这样的数据。刚好最近在网上和书上看到用关联规则来挖掘数据的文章和这个场景比较类似,所以我就试着提取了一些用户的浏览数据来尝试。
其实关联规则在R语言里面的实现很简单,网上也有很多实例,不过我在处理原始数据方面还是花了不少精力。这次是希望多从业务角度去尝试运用关联规则挖掘后的结果,而不仅局限于能得到一些规则。我会尽量把与关联规则相关的语句使用说明讲清楚(也帮助我自己更好理解原理),但是不会过多停留在R语言的技巧使用上。本文面向的是已经有过R语言编程经历的童鞋,所以基本的东西(比如数据类型等)我就不赘述了。
我这次要分析的数据表头中主要的维度如下:
用户id | 访问页面名称 |
---|
我提取的是发生首次投资的用户当天浏览的所有页面(按时间顺序排列),不过我这次用的关联规则是无序分析,所以其实页面的浏览次数在这里没有什么作用。
关联规则简述
关于关联规则,有很多其他的文章都讲得很好,我就不细谈了,这边列一些写得比较易懂的references。
数据挖掘系列(1)关联规则挖掘基本概念与Aprior算法
小白学数据分析之关联分析理论篇
关联规则的算法有很多种,比如Apriori、FP-Growth算法等,我作为用研虽然并不关心具体这个规则如何实现,为了加深印象我还是把规则生成过程还是恶补了一下。在R语言里面有个包是arules,里面的apriori函数就是实现了Apriori算法,只要按照它的格式把数据整好就可以输出结果了。
有几个概念这里要讲一下,因为待会的分析会用上。
- 支持度 support:x的支持度就是x在所有订单里面出现的频率。
- 置信度confidence:x→y规则的置信度就是含x的所有订单里出现y的频率。
- 提升度lift:support(X\Y)/support(X)support(Y),即x、y一起出现的概率与他们单独出现的概率之比,主要是看俩事件的独立性;如果>1则说明有一些相关性,不过通常大于3的话认为这条规则比较有意义。
R语言中的关联规则挖掘
首先,将数据集准备好,就像本文一开始提到的格式就好。然后安装并加载所需要的包arules。
#加载arules包
library(arules)
这里需要引入事务型数据类型transactions
,这是专门用于挖掘项集和规则的类型。需要用as函数转换成transactions,可以转换的类型有list、matrix和data frame。虽然我们的数据一般都是data frame格式的,但是在这种情况下最好是把需要的转换成list格式。因为transaction里面规定是如果从data frame格式转换过来的话,要求每一行就是一个transaction,所有列的类型只能是factor类或binary类,如果是binary,则标记T的时候表示该transaction含这个item,并以该变量的名称的形式出现(比如下表中的pass);如果是factor类,则直接使用对应的level就行(如下表中的age和grade)。比如下面这个系统自带的例子(transactionID是自动生成的)。
data frame
age | grade | pass |
---|---|---|
6 | A | TRUE |
8 | C | TRUE |
16 | F | FALSE |
6 | A | TRUE |
转变成 transaction 后
items | transactionID |
---|---|
{age=6, grade=A, pass} | 1 |
{age=8, grade=C, pass} | 2 |
{grade=F} | 3 |
{age=16, grade=A, pass} | 4 |
回到我们这个场景,也就是说我们可以不用在表中含用户id,只需要把所有的页面名称变成每一列的变量名,然后如果该用户访问过该页面,则标记为T,否则为F。这需要我们把原始数据进行一个大的转换才能达到这种要求(可以用cast相关函数达到这个效果),如果是用用户所有的原始浏览数据来做的话,不建议用这个方式,因为通常涉及的页面数会很多,而且这样的表格过于庞大而且大部分值可能是0,浪费内存,除非是以稀疏矩阵的格式存储的。
网上一般用的案例都是变成list再转换的,也更适合我自己已有的数据格式。我这边的数据是下面这样的,每行是某用户单次访问的某页面,然后顺序下去相同ID的就说明这是该用户顺序浏览的页面。
用户id | 访问页面名称 |
---|---|
0244397D-467A-40B2-XXXX | 充值 |
0244397D-467A-40B2-XXXX | 充值成功 |
0244397D-467A-40B2-XXXX | 定期详情页 |
... | ... |
在这里要用到split函数(split函数返回的就是一个list)。这个list是以用户id来分组,然后每个分组里面包含了该用户访问的所有页面。由于我提取的是原始数据,1个用户可能重复访问一些页面,所以有些分组里同样的页面多次出现,不过这个没有关系,等转变成transaction格式的时候页面都是唯一的。之后用as函数把该list转换成transaction类型。
#生成事务型数据,后者相当于是transactionID,前者相当于是商品list
data_trans<-as(split(data1$页面名称, data1$用户id), "transactions")
#可以看下生成的结果总览
summary(data_trans)
#可以预览前10行生成的交易数据
inspect(data_trans[1:10])
我的数据最后形成的transaction示例如下(隐去部分信息)。
items | transactionID |
---|---|
{充值,充值成功,定期详情-产品详情页,定期详情页,定期购买页,我的-定期,我的余额,我的账户,投资成功,新手专场,活动页,理财投资页} | 010C7271-D7A0-XXXX |
生成了transaction类型的数据之后,开始用apriori函数来生成关联规则。首先看一下apriori函数里的参数设置,apriori(data
, parameter
= NULL, appearance
= NULL, control
= NULL)。
-
data
就是之前建好的transaction数据 -
parameter
是一个list,常设置的是4个参数:supp(支持度)和conf(置信度)在apriori里面默认是0.1和0.8,我自己设置的是0.1和0.3,默认的稍微严格了些,之前用书上的支持度0.01发现挖掘出来的规则太多了,所以自己调整了一下,通常网页访问这种数据还是设置高一些比较好,这样挖掘出来的规则才比较有意义,调整后试着看下产生的规则数量和使用的元素数量,不要太少或太多即可;target(挖掘目标)在apriori函数下只能用"rules";maxlen(项集内的最多项数)默认是10,我自己设置的是2,因为我关心的是一个页面对另一个页面的贡献值,所以规则(X->Y)里面X只要1个页面就好。 -
appearance
是用来指定lhs和rhs为你比较关注的几个项,比如你只关心某些具体页面的时候可以设置。我这里还是默认生成全部的规则。 -
control
是用来控制算法处理过程的,比如免去处理过程报告、对项集排序什么的。这次我也没有特别设置。
#生成关联规则
data_rules<- apriori(data_trans, parameter = list(supp=0.1, conf=0.3, target="rules", maxlen=2))
#如果想要先预览一下生成的规则,用inspect函数
inspect(data_rules)
#按照提升度由高到低来排序规则
rules.sorted <- sort(data_rules, by="lift")
在R里面生成的规则前几个如下,已经按lift排序。
lhs | rhs | support | confidence | lift |
---|---|---|---|---|
{绑定银行卡} | => {绑卡成功} | 0.1544715 | 0.9047619 | 4.7355623 |
{绑卡成功} | => {绑定银行卡} | 0.1544715 | 0.8085106 | 4.7355623 |
{注册} | => {登录} | 0.1219512 | 0.9090909 | 3.6363636 |
{登录} | => {注册} | 0.1219512 | 0.4878049 | 3.6363636 |
{理财投资页} | => {新手专场} | 0.2540650 | 0.6793478 | 2.4045981 |
{新手专场} | => {理财投资页} | 0.2540650 | 0.8992806 | 2.4045981 |
这里的support值是指这对规则出现的概率,所以即使lhs和rhs互换support值都是一样的。成对的规则里,可以通过confidence值推测页面间的关联,比如有浏览注册的都去登录了,但是登录的只有一部分去注册,说明这批首投的用户里面是有些之前就注册了的(这里只是举例子说明解释数据,这个结论也可以通过用户注册信息来得出)。
如果你不是像我一样设置的maxlen=2,而是想要看多个页面与某页面的关联程度,可以允许lhs有更多项,不过我自己的经验是有多项的话,经常出现在一起的都是流程中连续经过的几个页面,没有更多有价值的信息,所以我还是设置成2了。不过不管设置成多少,你都可能会需要删除其中的冗余规则。
冗余规则:如果一个更具体的规则的置信度比其更一般的规则更低,则为冗余规则;换句话说,更一般的规则能概括关系的话,子集如果并没有比更一般的规则表现更好,就没有必要留下来。下面的表达式中的 X' 就是冗余规则。
arules包中有去除冗余规则的函数,这是我在外网上找到的方式。这里有个小细节,由于挖掘的规则里面都会有lhs为空的情况(因为我没设置minlen),即rhs单独出现(下表),如果相同的rhs时lhs不为空,confidence值还更小的话就会被认定为冗余规则。
lhs | rhs | support | confidence | lift |
---|---|---|---|---|
{} | => {充值成功} | 0.670732 | 0.670732 | 1 |
函数如下:
rm_redundant <- a.rules[!is.redundant(a.rules)]
我之前也有看到下面这个去除冗余规则的方式,国内的各种文章都是用的这种:
#生成一个所有规则的子集矩阵,行和列分别是每条rule,其中的值是TRUE和FALSE,当rules2是rules1的子集时,rules2在rules1的值为TRUE
subset.rules <- is.subset(a.rules,a.rules)
#将矩阵对角线以下的元素置为空,只保留上三角
subset.rules[lower.tri(a.rules, diag=T)] <- NA
#R会将矩阵中的TRUE当做1,统计每列的和(忽略缺失值),如果该列的和大于等于1,也就是表示该列(规则)是别的规则的子集,应该删除。
redundant <- colSums(subset.rules, na.rm=T) >=1
#去除冗余规则
rules.pruned <- a.rules[!redundant]
不过我尝试了这2种方式之后发现结果不一样。
我推荐使用第1种方式,因为毕竟这是现成的函数,操作简单,而且去除规则也是根据是子集且confidence值更小所以去除。第2种方式相当于是自己编1个函数,最主要是因为第一步生成的子集matrix时非常大,效率不太高,而且把lhs和rhs混在一起作为子集这个方式我也不太确定是否合适,最后生成的结果也有点奇怪(不知道是不是我自己的原因),所以我也没有在用这种方式。
分析规则
我之后没有在R里面继续做接下来的页面筛选分析,而是把数据导出到Excel了,因为这样选页面还是快一些。
我自己在分析的时候,主要思路是想看投资成功的用户到底主要浏览了哪些页面,以及非临近的页面之间是否存在特殊关联。下图是选择lhs为空之后的表格,相当于是rhs单独出现的概率(所以lift都为1)。这几个页面是有至少30%的用户(因为支持度设置的0.3)访问的浏览量较高的几个页面。可以看到详情页、购买页和投资成功页大概是0.8的支持度,其实如果数据不存在丢失的话这里应该是100%,因为是用户投资的必经页面。
这里就举一个例子,比如我想看「新手专场」页面是否对于用户首投有促进作用。首先我在lhs为空的列表里,并没有在rhs里面看到「新手专场」,所以rhs里有「新手专场」的时候,lhs都不为空,所以有2个可能的假设:
- 「新手专场」的support小于0.1,所以未满足规则条件而没出现
- 其他页面->新手专场,作为子集的confidence值比lhs为空的更高,说明用户浏览其他页面的同时有浏览「新手专场」的概率比「新手专场」单独出现要高
基于第一个假设,我通过其他方式查看首投用户中有浏览「新手专场」的比例为32.4%,是远高于0.1的,所以第一个假设可以排除。
那么就说明新手专场更可能和其他页面一起出现。那会是「投资成功」吗?
下面的表格,第一行表示的是投资成功的用户中有32.9%的用户浏览了新手专场,第二行表示的是浏览了「新手专场」的用户中,93.5%的用户都投资了。第二个数据看起来好像有很大的促进作用。但是后来我也有对比购买了新手产品和未购买新手产品的用户浏览「新手专场」页面比例,发现差异也不是很大,所以即使有促进作用也比较微小,毕竟数据显示还是有将近70%的首投用户是没有浏览过「新手专场」的,所以要多方验证得出结论。关于这个促进作用的研究,如果有未产生投资的用户数据来进行对比更好,比如如果未投资的用户都基本上没有浏览过「新手专场」的话,有可能说明新手专场还是有效的。如果这个页面效果确实不够好,而且我们其实想主推这个页面的话,那说明现在这个页面对用户的吸引程度不够,需要在页面中更加强调。
lhs | rhs | support | confidence | lift |
---|---|---|---|---|
{投资成功} | => {新手专场} | 0.245935 | 0.329949 | 1.1678779 |
{新手专场} | => {投资成功} | 0.264228 | 0.935252 | 1.087025 |
这次挖掘的关联规则是无序的,所以虽然有些规则支持度高,但是不知道页面的前后浏览顺序,所以也不好保证是一个页面对另一个页面有促进作用,就像刚刚的例子里面,除非这些「新手专场」都是「投资成功」前浏览的才说明有意义,不过这个我就不细究了。R语言里面可以使用带时间顺序的cspade函数来实现有序的关联分析。
但是做有序行为分析的话需要对原始数据的精准性要求很高,如果拿到的原始数据缺失得比较多的话,对最后的结果会有很大影响,也就不适合用了,所以我暂时还没试过cspade。以后有机会再看。不过我认为cspade分析后的结果应该会更适用用户路径分析。
总结
关联规则是从购物篮分析发展而来,而且比较适合稀疏型数据,也就是说能够包含很多的观测(transaction),但其实每个观测到的信息(item)是极少的,就好比一个商城能含的商品很多,每天的用户交易(transaction)也很多,但是一个用户能买的商品(item)是有限的,所以才需要关联规则来挖掘出那些频繁被一起购买的商品,从而为促销所用。
在购物篮分析里,我们对于商品的关系更为关注而不是某些具体的商品,而浏览网页这个场景稍微有些不同,我们通常会比较关注用户的什么浏览行为会促成一些目标行为的产生,因此我们会有特别关心的页面(如购买成功页),然后看该用户之前浏览的网页有哪些或频繁访问,由于核心流程通常需要访问的页面是固定的,所以有些规则的置信度高是很正常的(就像「A页面->A页面的母页面」这条规则是接近100%)。在分析数据的时候,也最好多方验证一下规则可能延伸出来的结论,以免数据误读。
另外的尝试:这次我用的是完全原始的数据,所以只能用list的方式来转换transaction类型,其实如果我们有特别关心的页面,可以在数据准备阶段采用data frame的方式,然后用专门的变量来标记不同组的用户(或者用户属性),不同的页面变为不同的列变量,就可以了解到用户的特别属性和一些页面之间的关联,比如已投未投之间的对比,新老用户之间的对比,不同年龄之间的用户对比等等。
不过不管如何利用这些数据来分析用户的行为,终究最后数据给出的是一个大致的改进方向,也就是可能存在问题的部分,具体解决方案需要多思考多试验,甚至可能还需要额外的研究进行补充。
参考资料:
《R语言与网站分析》 李明 著
《R语言市场研究分析》 Chris Chapman等 著
R语言 | 关联规则
R语言 关联规则1---不考虑items之间的时序关系
stack overflow: Association rule in R - removing redundant rule (arules)
Find Redundant Rules
简书作者:janepi,转载请告知作者并注明出处。