算法说明:FPGrowth算法是用来做购物车分析,说白了就是分析下什么商品和什么商品会被一同购买,还有一同购买的频次是多少,经常被一同购买的商品就可以放到一起做推荐了。
频繁模式挖掘关键词说明:
挖掘数据机:购物篮数据
频繁模式:频繁出现在数据集当中的模式:项集、子结构、子序列
挖掘目标:频繁项集,频繁模式,关联规则
关联规则:牛奶=>鸡蛋,[支持度=2%,置信度=60%]
支持度:%2的购物车买了牛奶也买了鸡蛋,可用百分比也可用具体数字
置信度:60%购买了牛奶的人同时买了鸡蛋
最小支持度:支持度阀值
项集:商品集合
K-项集:K个商品组成的集合
频繁项集:满足最小支持度的项集
下面我们来看下如何通过FPGrowth算法来进行频繁模式挖掘:
1.我们要有一个购物车数据集
I1,I2,I4
I1,I2,I5
I2,I4
I2,I3
I1,I3
I2,I3
I1,I3
I1,I2,I3,I5
I1,I2,I3
2.定一个最小支持度计数,我们暂定为2。
3.下面我们要建立FPTree,FP树是对购物车数据集的一个重构,也是我们FPGrowth算法的基础。我们从韩家炜的书中,摘取出一副FPTree的构造图,然后来分析一下这棵树是如何建立的。首先对购物车的所有商品按支持度计数(在购物车中出线几次)排序,同时放弃支持度计数小于最小支持度计数的项,形成频繁项头链表。以频繁项头链表为基准,扫描每条购物车信息,排序并删除支持度计数过小的项。先建立null结点(FPTree根节点),读入一条购物车信息,按顺序建立树形结构,如果结点已存在直接增加该结点的支持度计数。同时在建立新结点的时候把该结点加入到频繁项头链表的结点链中。
先来定义一下数据结构:
//购物车项信息:itemName为商品名称,supportCount为购物车中的商品数量
case class Item(val itemName: String, var supportCount: Int)
//FP树结点:nodeName为商品名称
class FPTreeNode(val nodeName: String) {
var supportCount: Int = 0 //支持度计数
var parent: Option[FPTreeNode] = None //父结点
var children: mutable.Map[String, FPTreeNode] = mutable.Map() //孩子结点
def increaseSupportCount(n: Int) = supportCount += n //增加支持度计数
def isLeaf = if (children.size == 0) true else false //是否为叶子结点
}
//FP树结构
class FPTree(val name: String, val frequencyItems: List[FrequencyItem]) {
//根节点
val root = new FPTreeNode(name)
//记录当前递归结点
var currentNode = root
def resumeCurrentNode {
currentNode = root
}
/**
* 判断当前FPTree是一棵单叉树
*
* @return
*/
def isSingleTree: Boolean = {
var treeNode = root
while (!treeNode.isLeaf) {
if (treeNode.children.size == 1) {
treeNode = treeNode.children.head._2
} else {
return false
}
}
return true
}
//空树
def isNullTree: Boolean = {
if (root.children.size == 0) return true
else return false
}
}
//频繁项集头列表
class FrequencyItem(val itemName: String, val supportCount: Int) {
val treeNodes: mutable.ArrayBuffer[FPTreeNode] = mutable.ArrayBuffer()
}
首先扫描一遍购物车,构建frequencyItemIndex,用来对频繁项安支持度计数排序Map(频繁项,序号),序号小的靠前
val frequencyItemIndex = mutable.LinkedHashMap[String, Int]()
扫描购物车并建立FPTree
/**
* 扫描购物车 生成FPTree
*
* @param fileName
* @return
*/
def scanCartList(fileName: String): FPTree = {
val itemsList = mutable.ListBuffer[List[Item]]()
//第一次扫描购物车购物车
for (line <- Source.fromFile(fileName).getLines()) {
//分别统计每哥购物车的频繁项支持度和所有购物车频繁项支持度
val cartItemMap = collection.mutable.Map[String, Int]()
for (itemName <- line.split(",")) {
cartItemMap.get(itemName) match {
case Some(_supportCount) => cartItemMap.put(itemName, _supportCount + 1)
case None => cartItemMap += (itemName -> 1)
}
frequencyItemIndex.get(itemName) match {
case Some(_supportCount) => frequencyItemIndex.put(itemName, _supportCount + 1)
case None => frequencyItemIndex += (itemName -> 1)
}
}
itemsList += (for (cartItem <- cartItemMap.toList) yield new Item(cartItem._1, cartItem._2))
}
//移除支持度计数过小的频繁项
frequencyItemIndex.foreach(x => {
if (x._2 < minSupportCount) frequencyItemIndex -= x._1
})
//生成频繁项头链表
val frequencyItemHeadList = frequencyItemIndex
.toList
.sortWith((x, y) => x._2 > y._2)
.map(x => new FrequencyItem(x._1, x._2))
println("开是构建FPTree...")
print("\t打印频繁模式项头:")
frequencyItemHeadList.foreach(x => printf("%s:%d ", x.itemName, x.supportCount))
println("")
val fpTree = new FPTree("root", frequencyItemHeadList.toList)
for (i <- 0 until frequencyItemHeadList.size) {
frequencyItemIndex.put(frequencyItemHeadList(i).itemName, i)
}
for (items <- itemsList) {
val _items = items
.filter(item => frequencyItemIndex.isDefinedAt(item.itemName))
.sortWith((x, y) => frequencyItemIndex(x.itemName) < frequencyItemIndex(y.itemName))
print("\t扫描频繁项集合:")
_items.foreach(x => printf("%s:%d ", x.itemName, x.supportCount))
println("")
insertTree(_items, fpTree)
fpTree.resumeCurrentNode
}
fpTree
}
递归建树方法:
/**
* 插入构建FP树
*
* @param items 购物篮信息(频繁模式项集)
* @param fPTree FP树
*/
def insertTree(items: List[Item], fPTree: FPTree) {
for (item <- items) {
fPTree.currentNode.children.get(item.itemName) match {
case Some(child) => {
child.increaseSupportCount(item.supportCount)
printf("\t\t增加支持度计数:%s up to %d\n", child.nodeName, child.supportCount)
}
case None => {
val child = new FPTreeNode(item.itemName)
child.increaseSupportCount(item.supportCount)
child.parent = Some(fPTree.currentNode)
fPTree.currentNode.children += (child.nodeName -> child)
printf("\t\t在%s下面增加新结点%s:%d\n", fPTree.currentNode.nodeName, child.nodeName, child.supportCount)
fPTree.frequencyItems.find(x => x.itemName == child.nodeName) match {
case Some(frequencyItem) => {
frequencyItem.treeNodes += child
printf("\t\t\t把新结点%s:%d,加入频繁模式头链表\n", child.nodeName, child.supportCount)
}
case None => println("[ERROR]:丢失频繁模式项头信息!!!!!!!!")
}
}
}
fPTree.currentNode = fPTree.currentNode.children(item.itemName)
}
}
看下打印的建树信息
开是构建FPTree...
打印频繁模式项头:I2:7 I1:6 I3:6 I4:2 I5:2
扫描频繁项集合:I2:1 I1:1 I4:1
在root下面增加新结点I2:1
把新结点I2:1,加入频繁模式头链表
在I2下面增加新结点I1:1
把新结点I1:1,加入频繁模式头链表
在I1下面增加新结点I4:1
把新结点I4:1,加入频繁模式头链表
扫描频繁项集合:I2:1 I1:1 I5:1
增加支持度计数:I2 up to 2
增加支持度计数:I1 up to 2
在I1下面增加新结点I5:1
把新结点I5:1,加入频繁模式头链表
扫描频繁项集合:I2:1 I4:1
增加支持度计数:I2 up to 3
在I2下面增加新结点I4:1
把新结点I4:1,加入频繁模式头链表
扫描频繁项集合:I2:1 I3:1
增加支持度计数:I2 up to 4
在I2下面增加新结点I3:1
把新结点I3:1,加入频繁模式头链表
扫描频繁项集合:I1:1 I3:1
在root下面增加新结点I1:1
把新结点I1:1,加入频繁模式头链表
在I1下面增加新结点I3:1
把新结点I3:1,加入频繁模式头链表
扫描频繁项集合:I2:1 I3:1
增加支持度计数:I2 up to 5
增加支持度计数:I3 up to 2
扫描频繁项集合:I1:1 I3:1
增加支持度计数:I1 up to 2
增加支持度计数:I3 up to 2
扫描频繁项集合:I2:1 I1:1 I3:1 I5:1
增加支持度计数:I2 up to 6
增加支持度计数:I1 up to 3
在I1下面增加新结点I3:1
把新结点I3:1,加入频繁模式头链表
在I3下面增加新结点I5:1
把新结点I5:1,加入频繁模式头链表
扫描频繁项集合:I2:1 I1:1 I3:1
增加支持度计数:I2 up to 7
增加支持度计数:I1 up to 4
增加支持度计数:I3 up to 2
建树完毕,开始挖掘,这次是今天我重点要说的,先来看下书上给出的算法:
实在是没办法按这个写出来代码,这只能算是一个指导方向,我来重写一份看看
FP_growth(FP_tree, _CPI)
(1)if FP_tree 只有一条路径P && _CPI不空
(2)for b <- p的所有组合
(3)产生频繁模式bp,support_count=bp中最小的support_count
(4)else for frequencyItem <- FP_tree的频繁项头链表.逆序
(5)if frequencyItem 的结点链只有一个结点 && _CPI不空
(6)for b <- frequencyItem.结点开始上溯到根结点组成的路径P
(7)产生频繁模式bp,support_count=bp中最小的support_count
(8)else frequencyItem 的结点链只有多个结点
(9)for p <- frequencyItem.结点链
(10)根据P路径构造条件模式基集合_CPBs
(11)使用_CPBs建立FP树_FP_tree
(12)if _FP_tree != null
(13)frequencyItem并上_CPI,support_count=frequencyItem.support_count
(14)if _CPI 不为空 生成频繁模式 frequencyItem_CPI:frequencyItem.support_count
(15)FP_growth(_FP_tree, frequencyItem_CPI:frequencyItem.support_count)
/**
* 挖掘频繁模式
*
* @param fPTree FPTree: FP树 CPTree:条件模式树
* @param _CPI 条件模式项
*/
def fp_growth(fPTree: FPTree, _CPI: Option[Item]): Unit = {
if (fPTree.isSingleTree && _CPI != None) {
_CPI match {
case Some(cpItem) => {
val _CPB = fPTree.frequencyItems.map(x => Item(x.itemName, x.supportCount))
if (_CPB.size > 0) {
println()
printf("挖掘%s:%d的频繁模式:", _CPI.get.itemName, _CPI.get.supportCount)
generateFrequencyPattern(_CPB, cpItem)
println()
}
}
case None => {
}
}
} else {
fPTree.frequencyItems.reverse.foreach(frequencyItem => {
if (frequencyItem.treeNodes.size == 1 && _CPI != None) {
val _CPB = mutable.ListBuffer[Item]()
val treeNode = frequencyItem.treeNodes(0)
_CPB += Item(treeNode.nodeName, treeNode.supportCount)
var parentNode = treeNode.parent
while (parentNode != None && parentNode.get.nodeName != "root") {
_CPB += Item(parentNode.get.nodeName, treeNode.supportCount)
parentNode = parentNode.get.parent
}
println()
printf("挖掘%s:%d的频繁模式:", _CPI.get.itemName, _CPI.get.supportCount)
if (_CPB.size > 0) {
generateFrequencyPattern(_CPB.toList, _CPI.get)
println()
}
} else {
var itemName = frequencyItem.itemName
if (_CPI != None) {
itemName = itemName + "," + _CPI.get.itemName
printf("挖掘%s:%d的频繁模式:{%s:%d}", _CPI.get.itemName, _CPI.get.supportCount, itemName, frequencyItem.supportCount)
}
println()
printf("建立%s:%d的条件模式树:\n", itemName, frequencyItem.supportCount)
val _fpTree = buileConditionPatternFree(frequencyItem)
if (!fPTree.isNullTree) fp_growth(_fpTree, Some(Item(itemName, frequencyItem.supportCount)))
}
})
}
/**
* 构造条件模式树
*
* @param frequencyItem
* @return
*/
def buileConditionPatternFree (frequencyItem: FrequencyItem): FPTree = {
val _CPBs = mutable.ListBuffer[List[Item]]()
val frequencyItemMap = mutable.Map[String, Int]()
for (treeNode <- frequencyItem.treeNodes) {
val _CPB = mutable.ListBuffer[Item]()
var parentNode = treeNode.parent
while (parentNode != None && parentNode.get.nodeName != "root") {
val item = Item(parentNode.get.nodeName, treeNode.supportCount)
frequencyItemMap.get(item.itemName) match {
case Some(supportCount) => frequencyItemMap.put(item.itemName, supportCount + item.supportCount)
case None => frequencyItemMap += (item.itemName -> item.supportCount)
}
_CPB += item
parentNode = parentNode.get.parent
}
_CPBs += _CPB.toList
}
frequencyItemMap.foreach(x => {
if (x._2 < minSupportCount) frequencyItemMap -= x._1
})
val frequencyItems = frequencyItemMap
.toList
.map(x => new FrequencyItem(x._1, x._2))
.sortWith((x,y) => frequencyItemIndex(x.itemName) < frequencyItemIndex(y.itemName))
val fpTree = new FPTree("root", frequencyItems)
println("开是构建FPTree...")
if (frequencyItems.size > 0) {
print("\t打印频繁模式项头:")
frequencyItems.foreach(x => printf("%s:%d ", x.itemName, x.supportCount))
println("")
for (_CPB <- _CPBs) {
val _items = _CPB
.filter(x => frequencyItemMap.isDefinedAt(x.itemName))
.sortWith((x, y) => frequencyItemIndex(x.itemName) < frequencyItemIndex(y.itemName))
print("\t扫描频繁项集合:")
_items.foreach(x => printf("%s:%d ", x.itemName, x.supportCount))
println("")
insertTree(_items, fpTree)
fpTree.resumeCurrentNode
}
} else {
print("\t没有可用条件模式基...")
}
fpTree
}
打印挖掘过程
开启频繁模式挖掘...
建立I5:2的条件模式树:
开是构建FPTree...
打印频繁模式项头:I2:2 I1:2
扫描频繁项集合:I2:1 I1:1
在root下面增加新结点I2:1
把新结点I2:1,加入频繁模式头链表
在I2下面增加新结点I1:1
把新结点I1:1,加入频繁模式头链表
扫描频繁项集合:I2:1 I1:1
增加支持度计数:I2 up to 2
增加支持度计数:I1 up to 2
挖掘I5:2的频繁模式:{I2,I5:2} {I2,I1,I5:2} {I1,I5:2}
建立I4:2的条件模式树:
开是构建FPTree...
打印频繁模式项头:I2:2
扫描频繁项集合:I2:1
在root下面增加新结点I2:1
把新结点I2:1,加入频繁模式头链表
扫描频繁项集合:I2:1
增加支持度计数:I2 up to 2
挖掘I4:2的频繁模式:{I2,I4:2}
建立I3:6的条件模式树:
开是构建FPTree...
打印频繁模式项头:I2:4 I1:4
扫描频繁项集合:I2:2
在root下面增加新结点I2:2
把新结点I2:2,加入频繁模式头链表
扫描频繁项集合:I1:2
在root下面增加新结点I1:2
把新结点I1:2,加入频繁模式头链表
扫描频繁项集合:I2:2 I1:2
增加支持度计数:I2 up to 4
在I2下面增加新结点I1:2
把新结点I1:2,加入频繁模式头链表
挖掘I3:6的频繁模式:{I1,I3:4}
建立I1,I3:4的条件模式树:
开是构建FPTree...
打印频繁模式项头:I2:2
扫描频繁项集合:
扫描频繁项集合:I2:2
在root下面增加新结点I2:2
把新结点I2:2,加入频繁模式头链表
挖掘I1,I3:4的频繁模式:{I2,I1,I3:2}
挖掘I3:6的频繁模式:{I2,I3:4}
建立I1:6的条件模式树:
开是构建FPTree...
打印频繁模式项头:I2:4
扫描频繁项集合:I2:4
在root下面增加新结点I2:4
把新结点I2:4,加入频繁模式头链表
扫描频繁项集合:
挖掘I1:6的频繁模式:{I2,I1:4}
建立I2:7的条件模式树:
开是构建FPTree...
没有可用条件模式基...