Spark中关于购物篮的设计

Posted 曹军

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spark中关于购物篮的设计相关的知识,希望对你有一定的参考价值。

一:介绍

1.购物篮的定义

  

 

2.适用场景

  

 

3.相关概念

  

  

 

 4.步骤

  

 

5.编程实现

  

 

6.步骤

  

  

 

二:程序

1.程序

  1 package com.ibeifeng.senior.mba.association
  2 
  3 import org.apache.hadoop.fs.{FileSystem, Path}
  4 import org.apache.spark.rdd.RDD
  5 import org.apache.spark.{SparkConf, SparkContext}
  6 
  7 import scala.collection.mutable
  8 
  9 /**
 10   * 使用SparkCore实现购物篮分析
 11   * Created by ibf on 01/12.
 12   */
 13 object FindAssociationRulesSparkCore {
 14   /**
 15     * 先从缓存中获取数据,如果不存在,直接重新获取
 16     *
 17     * @param items
 18     * @param size
 19     * @param cache
 20     * @return
 21     */
 22   def findItemSetsByCache(items: List[(String, Int)], size: Int, cache: mutable.Map[Int, List[List[(String, Int)]]]): List[List[(String, Int)]] = {
 23     cache.get(size).orElse {
 24       // 获取值
 25       val result = findItemSets(items, size, cache)
 26 
 27       // 更新缓存
 28       cache += size -> result
 29 
 30       // 返回值
 31       Some(result)
 32     }.get
 33   }
 34 
 35 
 36   /**
 37     * 构建项集基于items商品列表,项集中的商品数量是size指定
 38     *
 39     * @param items 商品列表:eg: [A, B, C]
 40     * @param size  最终项集包含商品的数量
 41     * @return
 42     */
 43   def findItemSets(items: List[(String, Int)], size: Int, cache: mutable.Map[Int, List[List[(String, Int)]]]): List[List[(String, Int)]] = {
 44     if (size == 1) {
 45       // items中的每个商品都是一个项集
 46       items.map(item => item :: Nil)
 47     } else {
 48       // 当size不是1的时候
 49       // 1. 获取项集大小为size-1的项集列表
 50       val tmpItemSets = findItemSetsByCache(items, size - 1, cache)
 51       // 2. 给tmpItemSets中添加一个新的不重复的项 ==> 数据的转换
 52       val itemSets = tmpItemSets.flatMap(itemSets => {
 53         // 给itemSets项集添加一个新的商品ID,要求不重复
 54         val newItemSets = items
 55           // 将包含的商品过滤掉&要求下标必须大于以及存在
 56           .filter(item => !itemSets.contains(item) && itemSets.forall(_._2 < item._2))
 57           // 将商品添加到项集中,产生一个新的项集
 58           // 为了使用distinct做去重操作,进行一个排序操作
 59           .map(item => (item :: itemSets))
 60 
 61         // 返回值
 62         newItemSets
 63       })
 64 
 65       // 返回项集的值
 66       itemSets
 67     }
 68   }
 69 
 70   def main(args: Array[String]): Unit = {
 71     // 1. 创建SparkContext
 72     val conf = new SparkConf()
 73       .setAppName("find-association-rules")
 74       .setMaster("local[*]")
 75     val sc = SparkContext.getOrCreate(conf)
 76 
 77     // ===========================================
 78     // 测试数据存储的路径
 79     val path = "data/transactions/10"
 80     val savePath = "data/transactions/result"
 81     // 最小支持度
 82     val minSupport = 2
 83     // 最小置信度
 84     val minConfidence = 0.4
 85 
 86     // 创建rdd读取原始的交易数据,
 87     // 假设交易数据是按行存储的,每行是一条交易,每条交易数据包含的商品ID使用","分割
 88     val rdd = sc.textFile(path, 20)
 89 
 90     // 1. 计算频繁项集
 91     // 1.1 获取每条交易存在的项集
 92     val itemSetsRDD: RDD[String] = rdd.flatMap(transaction => {
 93       // 1) 获取当前交易所包含的商品ID
 94       val items = transaction
 95         .split(",") // 分割
 96         .filter(!_.isEmpty) // 过滤
 97         .sorted //排序
 98         .toList // 转换为list
 99         .zipWithIndex // 将数据和下标合并,下标从0开始
100 
101       // 2) 构建辅助对象
102       val itemSize = items.size
103       val cache = mutable.Map[Int, List[List[(String, Int)]]]()
104 
105       // 3) 根据获取的商品ID的信息产生项集
106       // allItemSets集合中最后数据量是:2^itemSize - 1
107       val allItemSets: List[List[String]] = (1 to itemSize).map(size => {
108         // 产生项集中项的数量是size的项集
109         findItemSets(items, size, cache)
110       }).foldLeft(List[List[String]]())((v1, v2) => {
111         v2.map(_.map(_._1)) ::: v1
112       })
113 
114       // 4) 返回结果
115       allItemSets.map(_.mkString(","))
116     })
117 
118     // 1.2 获取频繁项集
119     val supportedItemSetsRDD = itemSetsRDD
120       // 数据转换
121       .map(items => (items, 1))
122       // 聚合求支持度
123       .reduceByKey(_ + _)
124       // 过滤产生频繁项集
125       .filter(_._2 >= minSupport)
126 
127     // 2. 计算关联规则
128     // 2.1 对每个频繁项集获取子项集
129     val subSupportedItemSetsRDD = supportedItemSetsRDD.flatMap(tuple => {
130       val itemSets = tuple._1.split(",").toList.zipWithIndex // 频繁项集
131       val frequency = tuple._2 // 该频繁项集的支持度
132 
133       // 2) 构建辅助对象
134       val itemSize = itemSets.size
135       val cache = mutable.Map[Int, List[List[(String, Int)]]]()
136 
137       // 3) 获取子项集
138       val allSubItemSets: List[List[String]] = (1 to itemSize).map(size => {
139         // 产生项集中项的数量是size的项集
140         findItemSets(itemSets, size, cache)
141       }).foldLeft(List[List[String]]())((v1, v2) => {
142         v2.map(_.map(_._1)) ::: v1
143       })
144 
145       // 4) 转换数据并输出
146       val items = itemSets.map(_._1)
147       allSubItemSets.map(subItemSets => {
148         // (A,B,frequency) ==> 表示A出现的时候B也出现的次数是frequency次
149         // 当subItemSets就是itemSets的时候,返回的二元组的第二个元素的(元组)第一个元素是空的列表
150         (subItemSets.mkString(","), ((items.toBuffer -- subItemSets).toList.mkString(","), frequency))
151       })
152     })
153 
154     // 2.2 计算置信度
155     val assocRulesRDD = subSupportedItemSetsRDD
156       .groupByKey() // 数据聚合
157       .flatMap(tuple => {
158       // 计算执行度: (A, B, k) => A存在的时候B也存储的几率是k
159       // A就是tuple的第一个元素
160       // 获取左件
161       val lhs = tuple._1.split(",").mkString("<", ",", ">")
162 
163       // 获取左件在所有的交易中出现的总的次数 tuple._2中第一个元素为空的数据就是总的次数
164       val frequency = tuple._2
165         // 只要第一个元素为空的值,表示from本身
166         .filter(_._1.isEmpty)
167         // 需要的是第二个元素
168         .map(_._2).toList match {
169         case head :: Nil => head
170         case _ => {
171           throw new IllegalArgumentException("异常")
172         }
173       }
174 
175       // 计算右件出现次数占左件次数的百分比, 并返回最终结果
176       tuple._2
177         // 要求第一个数据非空
178         .filter(!_._1.isEmpty)
179         // 数据转换,获取置信度
180         .map {
181         case (rhs, support) => {
182           // 计算置信度
183           (lhs, rhs.split(",").mkString("<", ",", ">"), 1.0 * support / frequency)
184         }
185       }
186     })
187 
188     // 2.3 过滤置信度太低的数据
189     val resultRDD = assocRulesRDD.filter(_._3 >= minConfidence)
190 
191     // 3. RDD数据保存
192     //resultRDD.collect()
193     FileSystem.get(sc.hadoopConfiguration).delete(new Path(savePath), true)
194     //resultRDD.repartition(1).saveAsTextFile(savePath)
195 
196     // ===========================================
197     sc.stop()
198   }
199 }

 

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

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

  

 

 

三:优化程序

  1.优化的是相集的个数

  2.使用广播变量

  1 package com.ibeifeng.senior.mba.association
  2 
  3 import org.apache.hadoop.fs.{FileSystem, Path}
  4 import org.apache.spark.broadcast.Broadcast
  5 import org.apache.spark.rdd.RDD
  6 import org.apache.spark.{SparkConf, SparkContext}
  7 
  8 import scala.collection.mutable
  9 
 10 /**
 11   * 使用SparkCore实现购物篮分析
 12   * Created by ibf on 01/12.
 13   */
 14 object FindAssociationRulesSparkCore {
 15   /**
 16     * 先从缓存中获取数据,如果不存在,直接重新获取
 17     *
 18     * @param items
 19     * @param size
 20     * @param cache
 21     * @return
 22     */
 23   def findItemSetsByCache(items: List[(String, Int)], size: Int, cache: mutable.Map[Int, List[List[(String, Int)]]]): List[List[(String, Int)]] = {
 24     cache.get(size).orElse {
 25       // 获取值
 26       val result = findItemSets(items, size, cache)
 27 
 28       // 更新缓存
 29       cache += size -> result
 30 
 31       // 返回值
 32       Some(result)
 33     }.get
 34   }
 35 
 36 
 37   /**
 38     * 构建项集基于items商品列表,项集中的商品数量是size指定
 39     *
 40     * @param items 商品列表:eg: [A, B, C]
 41     * @param size  最终项集包含商品的数量
 42     * @return
 43     */
 44   def findItemSets(items: List[(String, Int)], size: Int, cache: mutable.Map[Int, List[List[(String, Int)]]]): List[List[(String, Int)]] = {
 45     if (size == 1) {
 46       // items中的每个商品都是一个项集
 47       items.map(item => item :: Nil)
 48     } else {
 49       // 当size不是1的时候
 50       // 1. 获取项集大小为size-1的项集列表
 51       val tmpItemSets = findItemSetsByCache(items, size - 1, cache)
 52       // 2. 给tmpItemSets中添加一个新的不重复的项 ==> 数据的转换
 53       val itemSets = tmpItemSets.flatMap(itemSets => {
 54         // 给itemSets项集添加一个新的商品ID,要求不重复
 55         val newItemSets = items
 56           // 将包含的商品过滤掉&要求下标必须大于以及存在
 57           .filter(item => !itemSets.contains(item) && itemSets.forall(_._2 < item._2))
 58           // 将商品添加到项集中,产生一个新的项集
 59           // 为了使用distinct做去重操作,进行一个排序操作
 60           .map(item => (item :: itemSets))
 61 
 62         // 返回值
 63         newItemSets
 64       })
 65 
 66       // 返回项集的值
 67       itemSets
 68     }
 69   }
 70 
 71   def main(args: Array[String]): Unit = {
 72     val n = 10000
 73     // 1. 创建SparkContext
 74     val conf = new SparkConf()
 75       .setAppName(s"find-association-rules-${n}")
 76       .setMaster("local[*]")
 77     //      .set("spark.eventLog.enabled", "true")
 78     //      .set("spark.eventLog.dir","hdfs://hadoop-senior01:8020/spark-history")
 79     //      .set("spark.executor.memory","3g")
 80     val sc = SparkContext.getOrCreate(conf)
 81 
 82     // ===========================================
 83     // 测试数据存储的路径
 84     val path = s"data/transactions/${n}"
 85     val savePath = s"result2/${n}"
 86     // 最小支持度
 87     val minSupport = 2
 88     // 最小置信度
 89     val minConfidence = 0.1
 90 
 91     // 创建rdd读取原始的交易数据,
 92     // 假设交易数据是按行存储的,每行是一条交易,每条交易数据包含的商品ID使用","分割
 93     val rdd = sc.textFile(path, 20)
 94 
 95     // 过滤无效数据:对于在整个交易集合中出现比较少的商品过滤掉,先进行需要过滤的商品的RDD数据
 96     val minGoodCount = 3 // 要求商品在整个交易集中至少出现3次
 97     val needFilterGoodsRDD = rdd
 98       .flatMap(transaction => transaction
 99         .split(",")
100         .filter(!_.isEmpty)
101         .map(good => (good, 1))
102       )
103       .reduceByKey(_ + _)
104       .filter(_._2 < minGoodCount)
105       .map(_._1)
106     // 使用广播变量将数据广播输出
107     val needFilterGoods: Broadcast[List[String]] = sc.broadcast(needFilterGoodsRDD.collect().toList)
108 
109     // 1. 计算频繁项集
110     // 1.1 获取每条交易存在的项集
111     val itemSetsRDD: RDD[String] = rdd.flatMap(transaction => {
112       // 1) 获取当前交易所包含的商品ID
113       val goods: Array[String] = transaction
114         .split(",") // 分割
115         .filter(!_.isEmpty) // 过滤
116 
117 
118       // 将需要过滤的数据过滤掉
119       val items = (goods.toBuffer -- needFilterGoods.value)
120         .sorted //排序
121         .toList // 转换为list
122         .zipWithIndex // 将数据和下标合并,下标从0开始
123 
124       // 2) 构建辅助对象
125       // 最大的项集只允许存在5个项的,5怎么来?根据业务规则&根据运行之后的情况
126       val itemSize = Math.min(items.size, 5)
127       val cache = mutable.Map[Int, List[List[(String, Int)]]]()
128 
129       // 3) 根据获取的商品ID的信息产生项集
130       // allItemSets集合中最后数据量是:2^itemSize - 1
131       val allItemSets: List[List[String]] = (1 to itemSize).map(size => {
132         // 产生项集中项的数量是size的项集
133         findItemSets(items, size, cache)
134       }).foldLeft(List[List[String]]())((v1, v2) => {
135         v2.map(_.map(_._1)) ::: v1
136       })
137 
138       // 4) 返回结果
139       allItemSets.map(_.mkString(","))
140     })
141 
142     // 1.2 获取频繁项集
143     val supportedItemSetsRDD = itemSetsRDD
144       // 数据转换
145       .map(items => (items, 1))
146       // 聚合求支持度
147       .reduceByKey(_ + _)
148       // 过滤产生频繁项集
149       .filter(_._2 >= minSupport)
150 
151     // 2. 计算关联规则
152     // 2.1 对每个频繁项集获取子项集
153     val subSupportedItemSetsRDD = supportedItemSetsRDD.flatMap(tuple => {
154       val itemSets = tuple._1.split(",").toList.zipWithIndex // 频繁项集
155       val frequency = tuple._2 // 该频繁项集的支持度
156 
157       // 2) 构建辅助对象
158       val itemSize = itemSets.size
159       val cache = mutable.Map[Int, List[List[(String, Int)]]]()
160 
161       // 3) 获取子项集
162       val allSubItemSets: List[List[String]] = (1 to itemSize).map(size => {
163         // 产生项集中项的数量是size的项集
164         findItemSets(itemSets, size, cache)
165       }).foldLeft(List[List[String]]())((v1, v2) => {
166         v2.map(_.map(_._1)) ::: v1
167       })
168 
169       // 4) 转换数据并输出
170       val items = itemSets.map(_._1)
171       allSubItemSets.map(subItemSets => {
172         // (A,B,frequency) ==> 表示A出现的时候B也出现的次数是frequency次
173         // 当subItemSets就是itemSets的时候,返回的二元组的第二个元素的(元组)第一个元素是空的列表
174         (subItemSets.mkString(","), ((items.toBuffer -- subItemSets).toList.mkString(","), frequency))
175       })
176     })
177 
178     // 2.2 计算置信度
179     val assocRulesRDD = subSupportedItemSetsRDD
180       .groupByKey() // 数据聚合
181       .flatMap(tuple => {
182       // 计算执行度: (A, B, k) => A存在的时候B也存储的几率是k
183       // A就是tuple的第一个元素
184       // 获取左件
185       val lhs = tuple._1.split(",").mkString("<", ",", ">")
186 
187       // 获取左件在所有的交易中出现的总的次数 tuple._2中第一个元素为空的数据就是总的次数
188       val frequency = tuple._2
189         // 只要第一个元素为空的值,表示from本身
190         .filter(_._1.isEmpty)
191         // 需要的是第二个元素
192         .map(_._2).toList match {
193         case head :: Nil => head
194         case _ => {
195           throw new IllegalArgumentException("异常")
196         }
197       }
198 
199       // 计算右件出现次数占左件次数的百分比, 并返回最终结果
200       tuple._2
201         // 要求第一个数据非空
202         .filter(!_._1.isEmpty)
203         // 数据转换,获取置信度
204         .map {
205         case (rhs, support) => {
206           // 计算置信度
207           (lhs, rhs.split(",").mkString("<", ",", ">"), 1.0 * support / frequency)
208         }
209       }
210     })
211 
212     // 2.3 过滤置信度太低的数据
213     val resultRDD = assocRulesRDD.filter(_._3 >= minConfidence)
214 
215     // 3. RDD数据保存
216     FileSystem.get(sc.hadoopConfiguration).delete(new Path(savePath), true)
217     resultRDD.repartition(1).saveAsTextFile(savePath)
218 
219     // ===========================================
220     sc.stop()
221   }
222 }

 

以上是关于Spark中关于购物篮的设计的主要内容,如果未能解决你的问题,请参考以下文章

Spark 中关于Parquet的应用与性能初步测试

使用 BottomAppBar 材料设计组件的片段过渡

spark中关于partition的简单理解

Unity中关于AnimationEvent.Time的问题

SPARK中关于HighlyCompressedMapStatus的说明(会造成运行时的数据不精确)

SPARK中关于HighlyCompressedMapStatus的说明(会造成运行时的数据不精确)