一:介绍

1.购物篮的定义

  

2.适用场景

  

3.相关概念

  

  

4.步骤

  

5.编程实现

  

6.步骤

  

  

二:程序

1.程序

 package com.ibeifeng.senior.mba.association

 import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext} import scala.collection.mutable /**
* 使用SparkCore实现购物篮分析
* Created by ibf on 01/12.
*/
object FindAssociationRulesSparkCore {
/**
* 先从缓存中获取数据,如果不存在,直接重新获取
*
* @param items
* @param size
* @param cache
* @return
*/
def findItemSetsByCache(items: List[(String, Int)], size: Int, cache: mutable.Map[Int, List[List[(String, Int)]]]): List[List[(String, Int)]] = {
cache.get(size).orElse {
// 获取值
val result = findItemSets(items, size, cache) // 更新缓存
cache += size -> result // 返回值
Some(result)
}.get
} /**
* 构建项集基于items商品列表,项集中的商品数量是size指定
*
* @param items 商品列表:eg: [A, B, C]
* @param size 最终项集包含商品的数量
* @return
*/
def findItemSets(items: List[(String, Int)], size: Int, cache: mutable.Map[Int, List[List[(String, Int)]]]): List[List[(String, Int)]] = {
if (size == 1) {
// items中的每个商品都是一个项集
items.map(item => item :: Nil)
} else {
// 当size不是1的时候
// 1. 获取项集大小为size-1的项集列表
val tmpItemSets = findItemSetsByCache(items, size - 1, cache)
// 2. 给tmpItemSets中添加一个新的不重复的项 ==> 数据的转换
val itemSets = tmpItemSets.flatMap(itemSets => {
// 给itemSets项集添加一个新的商品ID,要求不重复
val newItemSets = items
// 将包含的商品过滤掉&要求下标必须大于以及存在
.filter(item => !itemSets.contains(item) && itemSets.forall(_._2 < item._2))
// 将商品添加到项集中,产生一个新的项集
// 为了使用distinct做去重操作,进行一个排序操作
.map(item => (item :: itemSets)) // 返回值
newItemSets
}) // 返回项集的值
itemSets
}
} def main(args: Array[String]): Unit = {
// 1. 创建SparkContext
val conf = new SparkConf()
.setAppName("find-association-rules")
.setMaster("local[*]")
val sc = SparkContext.getOrCreate(conf) // ===========================================
// 测试数据存储的路径
val path = "data/transactions/10"
val savePath = "data/transactions/result"
// 最小支持度
val minSupport = 2
// 最小置信度
val minConfidence = 0.4 // 创建rdd读取原始的交易数据,
// 假设交易数据是按行存储的,每行是一条交易,每条交易数据包含的商品ID使用","分割
val rdd = sc.textFile(path, 20) // 1. 计算频繁项集
// 1.1 获取每条交易存在的项集
val itemSetsRDD: RDD[String] = rdd.flatMap(transaction => {
// 1) 获取当前交易所包含的商品ID
val items = transaction
.split(",") // 分割
.filter(!_.isEmpty) // 过滤
.sorted //排序
.toList // 转换为list
.zipWithIndex // 将数据和下标合并,下标从0开始 // 2) 构建辅助对象
val itemSize = items.size
val cache = mutable.Map[Int, List[List[(String, Int)]]]() // 3) 根据获取的商品ID的信息产生项集
// allItemSets集合中最后数据量是:2^itemSize - 1
val allItemSets: List[List[String]] = (1 to itemSize).map(size => {
// 产生项集中项的数量是size的项集
findItemSets(items, size, cache)
}).foldLeft(List[List[String]]())((v1, v2) => {
v2.map(_.map(_._1)) ::: v1
}) // 4) 返回结果
allItemSets.map(_.mkString(","))
}) // 1.2 获取频繁项集
val supportedItemSetsRDD = itemSetsRDD
// 数据转换
.map(items => (items, 1))
// 聚合求支持度
.reduceByKey(_ + _)
// 过滤产生频繁项集
.filter(_._2 >= minSupport) // 2. 计算关联规则
// 2.1 对每个频繁项集获取子项集
val subSupportedItemSetsRDD = supportedItemSetsRDD.flatMap(tuple => {
val itemSets = tuple._1.split(",").toList.zipWithIndex // 频繁项集
val frequency = tuple._2 // 该频繁项集的支持度 // 2) 构建辅助对象
val itemSize = itemSets.size
val cache = mutable.Map[Int, List[List[(String, Int)]]]() // 3) 获取子项集
val allSubItemSets: List[List[String]] = (1 to itemSize).map(size => {
// 产生项集中项的数量是size的项集
findItemSets(itemSets, size, cache)
}).foldLeft(List[List[String]]())((v1, v2) => {
v2.map(_.map(_._1)) ::: v1
}) // 4) 转换数据并输出
val items = itemSets.map(_._1)
allSubItemSets.map(subItemSets => {
// (A,B,frequency) ==> 表示A出现的时候B也出现的次数是frequency次
// 当subItemSets就是itemSets的时候,返回的二元组的第二个元素的(元组)第一个元素是空的列表
(subItemSets.mkString(","), ((items.toBuffer -- subItemSets).toList.mkString(","), frequency))
})
}) // 2.2 计算置信度
val assocRulesRDD = subSupportedItemSetsRDD
.groupByKey() // 数据聚合
.flatMap(tuple => {
// 计算执行度: (A, B, k) => A存在的时候B也存储的几率是k
// A就是tuple的第一个元素
// 获取左件
val lhs = tuple._1.split(",").mkString("<", ",", ">") // 获取左件在所有的交易中出现的总的次数 tuple._2中第一个元素为空的数据就是总的次数
val frequency = tuple._2
// 只要第一个元素为空的值,表示from本身
.filter(_._1.isEmpty)
// 需要的是第二个元素
.map(_._2).toList match {
case head :: Nil => head
case _ => {
throw new IllegalArgumentException("异常")
}
} // 计算右件出现次数占左件次数的百分比, 并返回最终结果
tuple._2
// 要求第一个数据非空
.filter(!_._1.isEmpty)
// 数据转换,获取置信度
.map {
case (rhs, support) => {
// 计算置信度
(lhs, rhs.split(",").mkString("<", ",", ">"), 1.0 * support / frequency)
}
}
}) // 2.3 过滤置信度太低的数据
val resultRDD = assocRulesRDD.filter(_._3 >= minConfidence) // 3. RDD数据保存
//resultRDD.collect()
FileSystem.get(sc.hadoopConfiguration).delete(new Path(savePath), true)
//resultRDD.repartition(1).saveAsTextFile(savePath) // ===========================================
sc.stop()
}
}

2.注意点(本地的完全运行)

  不需要开启服务,也不需要上传文件,讲文件保存在本地的方式

  

三:优化程序

  1.优化的是相集的个数

  2.使用广播变量

 package com.ibeifeng.senior.mba.association

 import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext} import scala.collection.mutable /**
* 使用SparkCore实现购物篮分析
* Created by ibf on 01/12.
*/
object FindAssociationRulesSparkCore {
/**
* 先从缓存中获取数据,如果不存在,直接重新获取
*
* @param items
* @param size
* @param cache
* @return
*/
def findItemSetsByCache(items: List[(String, Int)], size: Int, cache: mutable.Map[Int, List[List[(String, Int)]]]): List[List[(String, Int)]] = {
cache.get(size).orElse {
// 获取值
val result = findItemSets(items, size, cache) // 更新缓存
cache += size -> result // 返回值
Some(result)
}.get
} /**
* 构建项集基于items商品列表,项集中的商品数量是size指定
*
* @param items 商品列表:eg: [A, B, C]
* @param size 最终项集包含商品的数量
* @return
*/
def findItemSets(items: List[(String, Int)], size: Int, cache: mutable.Map[Int, List[List[(String, Int)]]]): List[List[(String, Int)]] = {
if (size == 1) {
// items中的每个商品都是一个项集
items.map(item => item :: Nil)
} else {
// 当size不是1的时候
// 1. 获取项集大小为size-1的项集列表
val tmpItemSets = findItemSetsByCache(items, size - 1, cache)
// 2. 给tmpItemSets中添加一个新的不重复的项 ==> 数据的转换
val itemSets = tmpItemSets.flatMap(itemSets => {
// 给itemSets项集添加一个新的商品ID,要求不重复
val newItemSets = items
// 将包含的商品过滤掉&要求下标必须大于以及存在
.filter(item => !itemSets.contains(item) && itemSets.forall(_._2 < item._2))
// 将商品添加到项集中,产生一个新的项集
// 为了使用distinct做去重操作,进行一个排序操作
.map(item => (item :: itemSets)) // 返回值
newItemSets
}) // 返回项集的值
itemSets
}
} def main(args: Array[String]): Unit = {
val n = 10000
// 1. 创建SparkContext
val conf = new SparkConf()
.setAppName(s"find-association-rules-${n}")
.setMaster("local[*]")
// .set("spark.eventLog.enabled", "true")
// .set("spark.eventLog.dir","hdfs://hadoop-senior01:8020/spark-history")
// .set("spark.executor.memory","3g")
val sc = SparkContext.getOrCreate(conf) // ===========================================
// 测试数据存储的路径
val path = s"data/transactions/${n}"
val savePath = s"result2/${n}"
// 最小支持度
val minSupport = 2
// 最小置信度
val minConfidence = 0.1 // 创建rdd读取原始的交易数据,
// 假设交易数据是按行存储的,每行是一条交易,每条交易数据包含的商品ID使用","分割
val rdd = sc.textFile(path, 20) // 过滤无效数据:对于在整个交易集合中出现比较少的商品过滤掉,先进行需要过滤的商品的RDD数据
val minGoodCount = 3 // 要求商品在整个交易集中至少出现3次
val needFilterGoodsRDD = rdd
.flatMap(transaction => transaction
.split(",")
.filter(!_.isEmpty)
.map(good => (good, 1))
)
.reduceByKey(_ + _)
.filter(_._2 < minGoodCount)
.map(_._1)
// 使用广播变量将数据广播输出
val needFilterGoods: Broadcast[List[String]] = sc.broadcast(needFilterGoodsRDD.collect().toList) // 1. 计算频繁项集
// 1.1 获取每条交易存在的项集
val itemSetsRDD: RDD[String] = rdd.flatMap(transaction => {
// 1) 获取当前交易所包含的商品ID
val goods: Array[String] = transaction
.split(",") // 分割
.filter(!_.isEmpty) // 过滤 // 将需要过滤的数据过滤掉
val items = (goods.toBuffer -- needFilterGoods.value)
.sorted //排序
.toList // 转换为list
.zipWithIndex // 将数据和下标合并,下标从0开始 // 2) 构建辅助对象
// 最大的项集只允许存在5个项的,5怎么来?根据业务规则&根据运行之后的情况
val itemSize = Math.min(items.size, 5)
val cache = mutable.Map[Int, List[List[(String, Int)]]]() // 3) 根据获取的商品ID的信息产生项集
// allItemSets集合中最后数据量是:2^itemSize - 1
val allItemSets: List[List[String]] = (1 to itemSize).map(size => {
// 产生项集中项的数量是size的项集
findItemSets(items, size, cache)
}).foldLeft(List[List[String]]())((v1, v2) => {
v2.map(_.map(_._1)) ::: v1
}) // 4) 返回结果
allItemSets.map(_.mkString(","))
}) // 1.2 获取频繁项集
val supportedItemSetsRDD = itemSetsRDD
// 数据转换
.map(items => (items, 1))
// 聚合求支持度
.reduceByKey(_ + _)
// 过滤产生频繁项集
.filter(_._2 >= minSupport) // 2. 计算关联规则
// 2.1 对每个频繁项集获取子项集
val subSupportedItemSetsRDD = supportedItemSetsRDD.flatMap(tuple => {
val itemSets = tuple._1.split(",").toList.zipWithIndex // 频繁项集
val frequency = tuple._2 // 该频繁项集的支持度 // 2) 构建辅助对象
val itemSize = itemSets.size
val cache = mutable.Map[Int, List[List[(String, Int)]]]() // 3) 获取子项集
val allSubItemSets: List[List[String]] = (1 to itemSize).map(size => {
// 产生项集中项的数量是size的项集
findItemSets(itemSets, size, cache)
}).foldLeft(List[List[String]]())((v1, v2) => {
v2.map(_.map(_._1)) ::: v1
}) // 4) 转换数据并输出
val items = itemSets.map(_._1)
allSubItemSets.map(subItemSets => {
// (A,B,frequency) ==> 表示A出现的时候B也出现的次数是frequency次
// 当subItemSets就是itemSets的时候,返回的二元组的第二个元素的(元组)第一个元素是空的列表
(subItemSets.mkString(","), ((items.toBuffer -- subItemSets).toList.mkString(","), frequency))
})
}) // 2.2 计算置信度
val assocRulesRDD = subSupportedItemSetsRDD
.groupByKey() // 数据聚合
.flatMap(tuple => {
// 计算执行度: (A, B, k) => A存在的时候B也存储的几率是k
// A就是tuple的第一个元素
// 获取左件
val lhs = tuple._1.split(",").mkString("<", ",", ">") // 获取左件在所有的交易中出现的总的次数 tuple._2中第一个元素为空的数据就是总的次数
val frequency = tuple._2
// 只要第一个元素为空的值,表示from本身
.filter(_._1.isEmpty)
// 需要的是第二个元素
.map(_._2).toList match {
case head :: Nil => head
case _ => {
throw new IllegalArgumentException("异常")
}
} // 计算右件出现次数占左件次数的百分比, 并返回最终结果
tuple._2
// 要求第一个数据非空
.filter(!_._1.isEmpty)
// 数据转换,获取置信度
.map {
case (rhs, support) => {
// 计算置信度
(lhs, rhs.split(",").mkString("<", ",", ">"), 1.0 * support / frequency)
}
}
}) // 2.3 过滤置信度太低的数据
val resultRDD = assocRulesRDD.filter(_._3 >= minConfidence) // 3. RDD数据保存
FileSystem.get(sc.hadoopConfiguration).delete(new Path(savePath), true)
resultRDD.repartition(1).saveAsTextFile(savePath) // ===========================================
sc.stop()
}
}

016 Spark中关于购物篮的设计,以及优化(两个点)的更多相关文章

  1. Spark小课堂Week7 从Spark中一个例子看面向对象设计

    Spark小课堂Week7 从Spark中一个例子看面向对象设计 今天我们讨论了个问题,来设计一个Spark中的常用功能. 功能描述:数据源是一切处理的源头,这次要实现下加载数据源的方法load() ...

  2. R语言和数据分析十大:购物篮分析

    提到数据挖掘,我们的第一个反应是之前的啤酒和尿布的故事听说过,这个故事是一个典型的数据挖掘关联规则.篮分析的传统线性回归之间的主要差别的差别,对于离散数据的相关性分析: 常见的关联规则: 关联规则:牛 ...

  3. 购物篮模型&Apriori算法

    一.频繁项集 若I是一个项集,I的支持度指包含I的购物篮数目,若I的支持度>=S,则称I是频繁项集.其中,S是支持度阈值. 1.应用 "尿布和啤酒" 关联概念:寻找多篇文章中 ...

  4. 数据算法 --hadoop/spark数据处理技巧 --(5.移动平均 6. 数据挖掘之购物篮分析MBA)

    五.移动平均 多个连续周期的时间序列数据平均值(按相同时间间隔得到的观察值,如每小时一次或每天一次)称为移动平均.之所以称之为移动,是因为随着新的时间序列数据的到来,要不断重新计算这个平均值,由于会删 ...

  5. Apriori算法在购物篮分析中的运用

    购物篮分析是一个很经典的数据挖掘案例,运用到了Apriori算法.下面从网上下载的一超市某月份的数据库,利用Apriori算法进行管理分析.例子使用Python+MongoDB 处理过程1 数据建模( ...

  6. 关于Spark中RDD的设计的一些分析

    RDD, Resilient Distributed Dataset,弹性分布式数据集, 是Spark的核心概念. 对于RDD的原理性的知识,可以参阅Resilient Distributed Dat ...

  7. 数据挖掘算法之-关联规则挖掘(Association Rule)(购物篮分析)

    在各种数据挖掘算法中,关联规则挖掘算是比較重要的一种,尤其是受购物篮分析的影响,关联规则被应用到非常多实际业务中,本文对关联规则挖掘做一个小的总结. 首先,和聚类算法一样,关联规则挖掘属于无监督学习方 ...

  8. Spark中的编程模型

    1. Spark中的基本概念 Application:基于Spark的用户程序,包含了一个driver program和集群中多个executor. Driver Program:运行Applicat ...

  9. Spark中的partition和block的关系

    hdfs中的block是分布式存储的最小单元,类似于盛放文件的盒子,一个文件可能要占多个盒子,但一个盒子里的内容只可能来自同一份文件.假设block设置为128M,你的文件是250M,那么这份文件占3 ...

随机推荐

  1. luogu P3198 [HNOI2008]遥远的行星

    bzoj 洛谷 这题意是不是不太清楚 真正题意:求\[f_i=\sum_{j=1}^{\lfloor i*A \rfloor} \frac{M_i*M_j}{i-j}\] 似乎只能\(O(n*\lfl ...

  2. 洛谷 P2257 YY的GCD

    洛谷 P2257 YY的GCD \(solution:\) 这道题完全跟[POI2007]ZAP-Queries (莫比乌斯反演+整除分块) 用的一个套路. 我们可以列出答案就是要我们求: \(ans ...

  3. mod_wsgi 的两种模式

    mod_wsgi 的两种模式 http://ssmax.net/archives/977.html http://www.cnblogs.com/yuxc/p/3555005.html mod_wsg ...

  4. mysql一次查询,返回多个统计结果

    1.sum(if) select sum(if(status=1,1,0)) as s1_count,sum(if(status=2,1,0)) as s2_countfrom order; 2.co ...

  5. 2017-2018-2 20155303『网络对抗技术』Exp5:MSF基础应用

    2017-2018-2 20155303『网络对抗技术』Exp5:MSF基础应用 --------CONTENTS-------- 一.原理与实践说明 1.实践内容 2.预备知识 3.基础问题 二.实 ...

  6. 【Eclipse】Eclipse中打开cmd窗口和terminal窗口

    在IDEA的时候可以直接使用terminal打开类似于cmd窗口的功能,于是想着在eclipse也使用类似的功能. 1.Eclipse打开类似于cmd窗口的功能.(DOS) 1.window——> ...

  7. Latex 公式颜色

    如何给这个公式加上颜色? 解决方法: \usepackage{xcolor} \begin{align}   \textcolor{red}{\int_a^b}\textcolor{blue}{f(x ...

  8. bzoj2152 树分治

    还是太菜了,自己写的wa,但是找不到哪里错了,, 感觉现在学树分治早了点..以后回来再看吧 /* 多少点对之间的路径是3的倍数 */ #include<iostream> #include ...

  9. Fiddler抓包7-post请求(json)

    前言上一篇讲过get请求的参数都在url里,post的请求相对于get请求多了个body部分,本篇就详细讲解下body部分参数的几种形式. 一.body数据类型 常见的post提交数据类型有四种: 1 ...

  10. BOM下的属性和方法---上

    -------------BOM------------------------------------------------ 三个系统对话框   浏览器可以通过alert().confirm()和 ...