我正在参加中秋创意投稿大赛,详情请看:中秋创意投稿大赛
[关联规则]寻找最受欢迎月饼搭配-五仁月饼还在吗?
中秋节马上到了,月饼是一年一度最受争议的主题,甚至有五仁月饼“滚”出月饼圈这样的热词
现在月饼也流行混搭风格,各种各样的月饼搭配在一起
所以本文的主题就是月饼,目的就是找出到底哪些月饼的搭配最受人们的欢迎
本文使用的数据集是某商家月饼店的出售记录,记录格式为:
2018-81-0-16-551;U00107335;YLjcK475224;3 3 4 0;11;21;31
2018-21-29-9-31;U0010609;kHZJU943213;2 1 3;11;01;11
2018-71-6-16-491;U00104128;wIVVz889572;2 4;11;01;01
2018-51-5-15-561;U00109576;ZEspU652465;3 3 3 4;11;11;11
2018-21-26-14-131;U00101209;ELeMt778295;4 2 6 7;11;01;41
各个字段以;号隔开,字段的含义依次为下单时间、用户名称、订单号、商品号、支付方式、城市编号、状态码
我们关注的字段为商品号,该字段包含了本次购买的月饼编号,每个编号针对一种月饼种类,编号之间以 空格隔开
商品号数字的含义如下:
0:水果月饼
1:冰皮月饼
2:咸蛋黄月饼
4:巧克力心月饼
5:五仁月饼
6:巧克力夹心月饼
7:奥利奥夹心月饼
...
运行环境:
jdk=1.8
scala=2.12.12
使用语言:scala
数据集大小:100万条数据
方法概述:本文旨在介绍如何利用数据挖掘算法中的Apriori(关联规则)算法来实现一个月饼搭配的推荐。我们将通过数据预处理、生成频繁项集和关联规则这几个步骤,最终通过关联规则生成月饼搭配的具体数据。
首先,先介绍一下Apriori算法
Apriori算法
例子:啤酒与尿布:
沃尔玛超市在分析销售记录时,发现了啤酒与尿布经常一起被购买,于是他们调整了货架将两者放在了一起,结果真的提升了啤酒的销量。 原因解释: 爸爸在给宝宝买尿布的时候,会顺便给自己买点啤酒?

概述: Apriori算法是一种最有影响力的挖掘布尔关联规则的频繁项集的算法,其命名Apriori源于算法使用了频繁项集性质的先验(Prior)知识。接下来我们将以超市订单的例子理解关联分析相关的重要概念: Support(支持度)、Confidence(置信度)、Lift(提升度)。

Support(支持度):指某事件出现的概率,在本例中即指某个商品组合出现的次数占总次数的比例。例:Support('Bread') = 4/5 = 0.8 Support('Milk') = 4/5 = 0.8 Support('Bread+Milk') = 3/5 = 0.6
Confidence(置信度):本质上是个条件概率,即当购买了商品A的前提下,购买商品B的概率。例:Confidence('Bread'—> 'Milk') = Support('Bread+Milk')/ Support('Bread') = 0.6/0.8 = 0.75
Lift(提升度): 指商品A的出现,对商品B的出现的概率的提升程度。 Lift(A->B) = Confidence(A, B) / Support(B)例:Lift('Bread'—> 'Milk') = 0.75/0.8 = 0.9375
对于Lift(提升度)有三种情况:
- Lift(A->B)>1: 代表A对B的出现概率有提升。
- Lift(A->B)=1: 代表A对B的出现概率没有提升,也没有下降。
- Lift(A->B)<1: 代表A对B的出现概率有下降效果。
原理: 该算法挖掘关联规则的过程,即是查找频繁项集(frequent itemset)的过程:
- 频繁项集:支持度大于等于最小支持度(Min Support)阈值的项集。
- 非频繁集:支持度小于最小支持度的项集。
流程:
- K = 1, 计算K项集的支持度;
- 筛选掉小于最小支持度的项集;
- 如果项集为空,则对应K-1项集的结果为最终结果。否则K = K+1重复2-3步
数据预处理
获取得到商品订单号
val data: Iterator[String] = fromFile("\\output\\data").getLines()
while(data.hasNext){
val line: String = data.next()
val orderList: String = line.split(";")(3)
val orderID: Array[String] = orderList.split(" ")
}
过滤掉月饼种类单号异常的数据
orderID.filter(elem => elem.toInt >=0 || elem.toInt<=5)//编号范围为0-5
将每条订单号使用特定的符号进行处理->,然后保存等待后续的使用
orderID.mkString("->")
val out = new FileWriter("\\output\\lastdata")
out.write(orderID.mkString("->")+"\n")
out.close()
生成关联规则
主程序入口
val data = new AprioriAlgorithm(new File("\\output\\lastdata"))
data.runApriori()
println("===Support Items===")
data.toRetItems.foreach(println)
println("===Association Rules===")
data.associationRules.foreach(println)
核心代码
这部分是预处理输入数据,并且将分隔数据保存到Set中
var transactions: List[Set[String]] = List()
var itemSet: Set[String] = Set()
for (line <- Source.fromFile(inputFile).getLines()) {
val elementSet = line.trim.split("->").toSet
if (elementSet.size > 0) {
transactions = transactions :+ elementSet
itemSet = itemSet ++ elementSet
}
}
计算项集的支持度
var toRetItems: Map[Set[String], java.lang.Double] = Map()
var associationRules: List[(Set[String], Set[String], Double)] = List()
def getSupport(itemComb: Set[String]): Double = {
val count = transactions.filter(transaction => itemComb.subsetOf(transaction)).size
count.toDouble / transactions.size.toDouble
}
K从2开始,挖掘频繁项集,2项集挖掘完成之后,以此挖掘3项集,直到结束
def runApriori(minSupport: Double = 0.45, minConfidence: Double = 0.7) = {
var itemCombs: Set[(Set[String], Double)] = Set()
var currentCSet: Set[Set[String]] = itemSet.map(word => Set(word))
var k: Int = 2
breakable {
while (true) {
val currentItemCombs: Set[(Set[String], Double)] = currentCSet.map(wordSet => (wordSet, getSupport(wordSet)))
.filter(wordSetSupportPair => (wordSetSupportPair._2 > minSupport))
val currentLSet = currentItemCombs.map(wordSetSupportPair => wordSetSupportPair._1).toSet
if (currentLSet.isEmpty) break
currentCSet = currentLSet.map(wordSet => currentLSet.map(wordSet1 => wordSet | wordSet1))
.reduceRight((set1, set2) => set1 | set2)
.filter(wordSet => (wordSet.size == k))
itemCombs = itemCombs | currentItemCombs //合并
k += 1
}
}
for (itemComb <- itemCombs) {
toRetItems += (itemComb._1 -> itemComb._2)
}
calculateAssociationRule(minConfidence)
}
计算出满足条件的关联规则 minConfidence= 0.7
def calculateAssociationRule(minConfidence: Double = 0.7) = {
toRetItems.keys.foreach(item =>
item.subsets.filter(wordSet => (wordSet.size < item.size & wordSet.size > 0))
.foreach(subset => {
associationRules = associationRules :+ (subset, item diff subset,
toRetItems(item).toDouble / toRetItems(subset).toDouble)
}
)
)
associationRules = associationRules.filter(rule => rule._3 > minConfidence)
}
}
程序参数设置
设置参数 minSupport = 0.45, minConfidence = 0.7
过滤掉一项集之后,运行结果如下
===Support Items===
(Set(0, 1),0.8755124487551245)
(Set(0, 4),0.45055494450554945)
(Set(2, 1),0.45495450454954506)
(Set(2, 0),0.45465453454654536)
(Set(4, 1),0.45395460453954606)
===Association Rules===
(Set(4),Set(0),0.9235499077679853)
(Set(2),Set(1),0.9270578647106764)
(Set(2),Set(0),0.9264466177669112)
(Set(4),Set(1),0.9305185488829679)
(Set(0),Set(1),0.9349706353443673)
(Set(1),Set(0),0.932282793867121)
从结果中可以看出,水果月饼和冰皮月饼的搭配是最受欢迎的,支持度达到了0.8755,置信度为0.9349 0.9322,说明很多用户都会买这两种,并且在买了其中的一种的情况下,有90%的可能性会购买另一种,所以商家可以将这两种产品放一块,增大销售额,咸蛋黄月饼和巧克力心月饼的受欢迎程度还是很高的,支持度有0.45以上
想不到传统的冰皮月饼还如此的受欢迎,还和水果月饼这样的新奇月饼成为了伙伴
月饼的种类越来越多,搭配也越来越多,有些搭配就是“花里胡哨”,希望可以通过上述相似的手段来增加商品的销售额,达到互联网运用到生活的目的
在结果中是没有看到五仁月饼的踪影的,还真的是人见人躲啊,不过老一代人还是对五仁有特殊的感情的,反正我是不喜欢吃
扩展
协同过滤:
一般是在海量的用户中发现一小部分和你品味比较相近的,在协同过滤中,这些用户称为邻居,然后根据他们喜欢的东西组织成一个排序的目录来推荐给你。问题的重点就是怎样去寻找和你比较相似的用户,怎么将那些邻居的喜好组织成一个排序的目录给用户。 协同过滤简单来说是利用某兴趣相投、拥有共同经验之群体的喜好来推荐用户感兴趣的信息,个人通过合作的机制给予信息相当程度的回应(如评分)并记录下来以达到过滤的目的进而帮助别人筛选信息,回应不一定局限于特别感兴趣的,特别不感兴趣信息的纪录也相当重要。
今日分享到此结束,内容不是很难,中秋节要到了,大家最喜欢的月饼是什么呢?