1.3 亲和性分析示例
终于迎来了第一个数据挖掘的例子,我们拿这个亲和性分析的示例来具体看下数据挖掘到底是怎么回事。数据挖掘有个常见的应用场景,即顾客在购买一件商品时,商家可以趁机了解他们还想买什么,以便把多数顾客愿意同时购买的商品放到一起销售以提升销售额。当商家收集到足够多的数据时,就可以对其进行亲和性分析,以确定哪些商品适合放在一起出售。
1.3.1 什么是亲和性分析
亲和性分析根据样本个体(物体)之间的相似度,确定它们关系的亲疏。亲和性分析的应用场景如下。
❑ 向网站用户提供多样化的服务或投放定向广告。
❑ 为了向用户推荐电影或商品,而卖给他们一些与之相关的小玩意。
❑ 根据基因寻找有亲缘关系的人。
亲和性有多种测量方法。例如,统计两件商品一起出售的频率,或者统计顾客购买了商品1后再买商品2的比率。当然还有别的方法,比如后面章节要讲的计算个体之间的相似度。
1.3.2 商品推荐
商品销售从线下搬到线上后,很多之前靠人工完成的工作只有实现自动化,才有望将生意做大。以向上销售为例,向上销售出自英文up-selling,指的是向已经购买商品的顾客推销另一种商品。原来线下由人工来完成的商品推荐工作,现在依靠数据挖掘技术就能完成,而且每年能为商家多进账几亿美元,强力助推电子商务革命的发展!
我们一起看下简单的商品推荐服务,它背后的思路其实很好理解:人们之前经常同时购买的两件商品,以后也很可能会同时购买。该想法确实很简单吧,可这就是很多商品推荐服务的基础,无论线上还是线下。
这种想法很容易转化为算法。顾客购买商品后,在向他们推荐商品前,先查询一下历史交易数据,找到以往他们购买同样商品的交易数据,看看同时购买了什么,再把它们推荐给顾客即可。该算法实际表现也不错,至少比随机推荐商品更有效。然而,它还有很大的提升空间,这正是数据挖掘一展身手的好机会。
为了简化代码,方便讲解,我们只考虑一次购买两种商品的情况。例如,人们去超市既买了面包,又买了牛奶。作为数据挖据入门性质的例子,我们希望得到下面这样的规则:
如果一个人买了商品X,那么他很有可能购买商品Y。
多件商品的规则会更为复杂,比如购买香肠和汉堡包的顾客比起其他顾客更有可能购买番茄酱,本书中不涉及这样的规则。
1.3.3 在NumPy中加载数据集
下载本书配套代码包,保存到你的计算机上,然后找到这个例子的数据集。本例中,建议你新建一个文件夹,把数据集和代码都放进去。在当前目录下,启动IPython Notebook,导航进入新建的文件夹,创建一个新的笔记本文件。
处理该数据集要用到NumPy的二维数组,书中大部分例子都会用到这种数据结构。数组看上去像是一张表,每一行表示样本中一个个体,每一列表示一种特征。
数组的每一项为个体的某项特征值。说起来有些拗口,为方便讲解,使用如下代码把数据集加载进来,稍后输出数组的部分数据看看效果:
import numpy as np dataset_filename = "affinity_dataset.txt" X = np.loadtxt(dataset_filename)
运行IPython Notebook,创建笔记本文件,在第一个格子中输入上述代码。按下Shift+Enter(同时创建新的格子)运行代码。代码运行完毕后,第一个格子左侧的方括号中出现一个表示序号的数字,看到这个数字就表明程序运行结束。第一个格子应该如下所示:
对于笔记本文件,前面的代码运行完后,后面的才能运行;还没有轮到它运行或是在运行中时,方括号中显示一个星号。运行结束后,星号立刻变为序号。
记得把数据集文件和笔记本文件放到同一目录下。否则,请修改上述代码中dataset_filename变量的值。
接下来,我们看看数据集到底是什么样子。在笔记本空格子中输入以下代码,输出数据集的前5行看看:
print(X[:5])
如果你从http://www.packtpub.com网站购买的图书,登录后即可下载已购图书的代码文件。如果你是从别处购买的图书,访问http://www.packtpub.com/support,注册后,我们可以用电子邮件把你需要的文件发给你。
上述代码的运行结果为前5次交易中顾客都买了什么。
输出结果从横向和纵向看都可以。横着看,每次只看一行。第一行(0, 0, 1, 1, 1)表示第一条交易数据所包含的商品。竖着看,每一列代表一种商品。在我们这个例子中,这五种商品分别是面包、牛奶、奶酪、苹果和香蕉。从第一条交易数据中,我们可以看到顾客购买了奶酪、苹果和香蕉,但是没有买面包和牛奶。
每个特征只有两个可能的值,1或0,表示是否购买了某种商品,而不是购买商品的数量。1表示顾客至少买了1个单位的该商品,0表示顾客没有买该种商品。
1.3.4 实现简单的排序规则
正如之前所说,我们要找出“如果顾客购买了商品X,那么他们可能愿意购买商品Y”这样的规则。简单粗暴的做法是,找出数据集中所有同时购买的两件商品。找出规则后,还需要判断其优劣,我们挑好的规则用。
规则的优劣有多种衡量方法,常用的是支持度(support)和置信度(confidence)。
支持度指数据集中规则应验的次数,统计起来很简单。有时候,还需要对支持度进行规范化,即再除以规则有效前提下的总数量。我们这里只是简单统计规则应验的次数。
支持度衡量的是给定规则应验的比例,而置信度衡量的则是规则准确率如何,即符合给定条件(即规则的“如果”语句所表示的前提条件)的所有规则里,跟当前规则结论一致的比例有多大。计算方法为首先统计当前规则的出现次数,再用它来除以条件(“如果”语句)相同的规则数量。
接下来,通过一个例子来说明支持度和置信度的计算方法,我们看一下怎么求“如果顾客购买了苹果,他们也会购买香蕉”这条规则的支持度和置信度。
如下面的代码所示,通过判断交易数据中sample[3]的值,就能知道一个顾客是否买了苹果。这里,sample表示一条交易信息,也就是数据集里的一行数据。
同理,检测sample[4]的值是否为1,就能确定顾客有没有买香蕉。现在可以计算题目给定规则在数据集中的出现次数,从而计算置信度和支持度。
我们需要统计数据集中所有规则的相关数据。首先分别为规则应验和规则无效这两种情况创建字典。字典的键是由条件和结论组成的元组,元组元素为特征在特征列表中的索引值,不要用实际特征名,比如“如果顾客购买了苹果,他们也会买香蕉”就用(3, 4)表示。如果某个个体的条件和结论均与给定规则相符,就表示给定规则对该个体适用,否则如果通过给定条件推出的结论与给定规则的结论不符,则表示给定规则对该个体无效。
为了计算所有规则的置信度和支持度,首先创建几个字典,用来存放计算结果。这里使用defaultdict,好处是如果查找的键不存在,返回一个默认值。需要统计的量有规则应验、规则无效及条件相同的规则数量。
from collections import defaultdict valid_rules = defaultdict(int) invalid_rules = defaultdict(int) num_occurances = defaultdict(int)
计算过程需要用到循环结构,依次对样本的每个个体及个体的每个特征值进行处理。第一个特征为规则的前提条件——顾客购买了某一种商品。
for sample in X: for premise in range(5):
检测个体是否满足条件,如果不满足,继续检测下一个条件。
if sample[premise] == 0: continue
如果条件满足(即值为1),该条件的出现次数加1。在遍历过程中跳过条件和结论相同的情况,比如“如果顾客买了苹果,他们也买苹果”,这样的规则没有多大用处。
num_occurances[premise] += 1 for conclusion in range(n_features): if premise == conclusion: continue
如果规则适用于个体,规则应验这种情况(valid_rules字典中,键为由条件和结论组成的元组)增加一次,反之,违反规则情况(invalid_rules字典中)就增加一次。
if sample[conclusion] == 1: valid_rules[(premise, conclusion)] += 1 else: invalid_rules[(premise, conclusion)] += 1
得到所有必要的统计量后,我们再来计算每条规则的支持度和置信度。如前所述,支持度就是规则应验的次数。
support = valid_rules
置信度的计算方法类似,遍历每条规则进行计算。
confidence = defaultdict(float) for premise, conclusion in valid_rules.keys(): rule = (premise, conclusion) confidence[rule] = valid_rules[rule] / num_occurances[premise]
我们得到了支持度字典和置信度字典,分别包含每条规则的支持度和置信度。我们再来声明一个函数,接收的参数有:分别作为前提条件和结论的特征索引值、支持度字典、置信度字典以及特征列表。输出每条规则及其支持度和置信度,对输出进行格式化,以方便查看。
之前建立的features列表派上用场了,每条规则的条件、结论就是用features列表中特征的索引来表示的。输出时,把索引替换成相应的特征,更容易读懂。
premise_name = features[premise] conclusion_name = features[conclusion] print("Rule: If a person buys {0} they will also buy {1}".format(premise_name, conclusion_name))
接着输出规则的支持度和置信度。
print(" - Support: {0}".format(support[(premise, conclusion)])) print(" - Confidence: {0:.3f}".format(confidence[(premise, conclusion)]))
写完后,自己测试一下代码是否可用——尝试更换条件和结论,看看输出结果如何。
1.3.5 排序找出最佳规则
得到所有规则的支持度和置信度后,为了找出最佳规则,还需要根据支持度和置信度对规则进行排序,我们分别看一下这两个标准。
要找出支持度最高的规则,首先对支持度字典进行排序。字典中的元素(一个键值对)默认为没有前后顺序;字典的items()函数返回包含字典所有元素的列表。我们使用itemgetter()类作为键,这样就可以对嵌套列表进行排序。itemgetter(1)表示以字典各元素的值(这里为支持度)作为排序依据,reverse=True表示降序排列。
from operator import itemgetter sorted_support = sorted(support.items(), key=itemgetter(1), re verse=True)
排序完成后,就可以输出支持度最高的前5条规则。
for index in range(5): print("Rule #{0}".format(index + 1)) premise, conclusion = sorted_support[index][0] print_rule(premise, conclusion, support, confidence, features)
结果如下所示:
同理,我们还可以输出置信度最高的规则。首先根据置信度进行排序。
sorted_confidence = sorted(confidence.items(), key=itemgetter(1), reverse=True)
再次输出看看结果。注意输出方法相同,但是请留意下面第三行代码里sorted_confidence的变化,不要继续使用sorted_support。
for index in range(5): print("Rule #{0}".format(index + 1)) premise, conclusion = sorted_confidence[index][0] print_rule(premise, conclusion, support, confidence, features)
从排序结果来看,“顾客买苹果,也会买奶酪”和“顾客买奶酪,也会买香蕉”,这两条规则的支持度和置信度都很高。超市经理可以根据这些规则来调整商品摆放位置。例如,如果本周苹果促销,就在旁边摆上奶酪。但是香蕉和奶酪同时搞促销就没有多大意义了,因为我们发现购买奶酪的顾客中,接近66%的人即使不搞促销也会买香蕉——即使搞促销,也不会给销量带来多大提升。
从上面这个例子就能看出数据挖掘的洞察力有多强大。人们可以用数据挖掘技术探索数据集中各变量之间的关系,寻找新发现。接下来一节,我们看看数据挖掘的另一个功能:预测。