[关联规则]寻找最受欢迎月饼搭配-五仁月饼还在吗?

1,449 阅读6分钟

我正在参加中秋创意投稿大赛,详情请看:中秋创意投稿大赛

[关联规则]寻找最受欢迎月饼搭配-五仁月饼还在吗?

中秋节马上到了,月饼是一年一度最受争议的主题,甚至有五仁月饼“滚”出月饼圈这样的热词

五仁月饼.jpeg

现在月饼也流行混搭风格,各种各样的月饼搭配在一起

所以本文的主题就是月饼,目的就是找出到底哪些月饼的搭配最受人们的欢迎

本文使用的数据集是某商家月饼店的出售记录,记录格式为:

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算法

例子:啤酒与尿布:

沃尔玛超市在分析销售记录时,发现了啤酒与尿布经常一起被购买,于是他们调整了货架将两者放在了一起,结果真的提升了啤酒的销量。 原因解释: 爸爸在给宝宝买尿布的时候,会顺便给自己买点啤酒?

img

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

img

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以上

想不到传统的冰皮月饼还如此的受欢迎,还和水果月饼这样的新奇月饼成为了伙伴

月饼的种类越来越多,搭配也越来越多,有些搭配就是“花里胡哨”,希望可以通过上述相似的手段来增加商品的销售额,达到互联网运用到生活的目的

城里人真会玩.jpg

在结果中是没有看到五仁月饼的踪影的,还真的是人见人躲啊,不过老一代人还是对五仁有特殊的感情的,反正我是不喜欢吃

想吐.jpg

扩展

协同过滤:

一般是在海量的用户中发现一小部分和你品味比较相近的,在协同过滤中,这些用户称为邻居,然后根据他们喜欢的东西组织成一个排序的目录来推荐给你。问题的重点就是怎样去寻找和你比较相似的用户,怎么将那些邻居的喜好组织成一个排序的目录给用户。 协同过滤简单来说是利用某兴趣相投、拥有共同经验之群体的喜好来推荐用户感兴趣的信息,个人通过合作的机制给予信息相当程度的回应(如评分)并记录下来以达到过滤的目的进而帮助别人筛选信息,回应不一定局限于特别感兴趣的,特别不感兴趣信息的纪录也相当重要。

今日分享到此结束,内容不是很难,中秋节要到了,大家最喜欢的月饼是什么呢?