FP-growth是频繁项集挖掘的一种优化算法。它先将事务先构造成一棵FP树,这样一来就不用像Apriori一项反复地扫描原来的事务了,大大提高了效率。这篇笔记主要是记录FP-growth的Python3代码实现的,会就算法来讲解FP-growth挖掘频繁项集的步骤,算法的详细内容在《数据挖掘-概念与技术》一书中有非常详细的讲解,这里不再赘述
完整代码在这里
- 数据格式 - 类似下方的二维数组,每一个list代表一个transaction,里面的每个数都是item的编号 - 1 - [[1,2,5], [2,4], [2,3], [1,2,4],[1,3], [2,3], [1,3], [1,2,3,5], [1,2,3]] 
- 读取数据 - 1 
 2
 3
 4
 5
 6
 7
 8- def loadDataSet(): 
 thing_arr = []
 with open('data/Groceries.txt', 'r') as f:
 X = f.read()
 thing_arr = json.loads(X)
 f.close()
 return thing_arr-  
- FP-Growth实现起来相对复杂,需要定义树结点结构 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20- class treeNode: 
 def __init__(self, nameValue, numOccur, parentNode):
 # 值
 self.name = nameValue
 # 计数
 self.count = numOccur
 # 下一个相同值的结点
 self.nodeLink = None
 # 父节点
 self.parent = parentNode
 # 孩子结点
 self.children = {}
 def inc(self, numOccur):
 self.count += numOccur
 def disp(self, ind=1):
 print(" "*ind, self.name, ' ', self.count)
 for child in self.children.values():
 child.disp(ind+1)-  
- 初始化transaction计数 - 1 
 2
 3
 4
 5- def createInitSet(dataSet): 
 retDict = {}
 for trans in dataSet:
 retDict[frozenset(trans)] = 1
 return retDict-  
- 建立FP树 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79- def createTree(dataSet, minSup = 1): 
 '''
 创建根结点以及搜索链表表头
 :param dataSet:
 :param minSup:
 :return:
 '''
 # 搜索链表头
 headerTable = {}
 # 在搜索用的链表头除记录每个item的频数
 for trans in dataSet:
 for item in trans:
 headerTable[item] = headerTable.get(item, 0) + dataSet[trans]
 # 小于最小支持度的item不用考虑
 for k in list(headerTable.keys()):
 if headerTable[k] < minSup:
 del(headerTable[k])
 freqItemSet = set(headerTable.keys())
 # 如果不存在频繁项集则直接返回空
 if len(freqItemSet) == 0:
 return None, None
 # 为每个结点增加一个指向下一个同值结点的指针
 for k in headerTable.keys():
 headerTable[k] = [headerTable[k], None]
 # 树根
 retTree = treeNode('Null Set', 1, None)
 for tranSet, count in dataSet.items():
 localD = {}
 for item in tranSet:
 if item in freqItemSet:
 localD[item] = headerTable[item][0]
 if len(localD) > 0:
 # 每个transaction中的item按出现的次数从高到低排
 orderedItems = [v[0] for v in sorted(localD.items(), key=lambda p: p[1], reverse=True)]
 # 建树
 updateTree(orderedItems, retTree, headerTable, count)
 return retTree, headerTable
 def updateTree(items, inTree, headerTable, count):
 '''
 每个transaction递归更新到树上,并更新搜索链表
 :param items:
 :param inTree:
 :param headerTable:
 :param count: 每个transaction的出现次数
 :return:
 '''
 # 每个transaction的最高出现词数item直接接在root上
 if items[0] in inTree.children:
 # 有该元素项时计数值+1
 inTree.children[items[0]].inc(count)
 else:
 # 没有这个元素项时创建一个新节点
 inTree.children[items[0]] = treeNode(items[0], count, inTree)
 # 更新头指针表或前一个相似元素项节点的指针指向新节点
 if headerTable[items[0]][1] == None:
 headerTable[items[0]][1] = inTree.children[items[0]]
 else:
 updateHeader(headerTable[items[0]][1], inTree.children[items[0]])
 # 递归建树
 if len(items) > 1:
 # 对剩下的元素项迭代调用updateTree函数
 updateTree(items[1:], inTree.children[items[0]], headerTable, count)
 
 def updateHeader(nodeToTest, targetNode):
 '''
 找到链表尾加上一个
 :param nodeToTest:
 :param targetNode:
 :return:
 '''
 while (nodeToTest.nodeLink != None):
 nodeToTest = nodeToTest.nodeLink
 nodeToTest.nodeLink = targetNode-  
- 在每次查询条件FP树时,需要递归寻找其条件前缀路径 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22- def ascendTree(leafNode, prefixPath): 
 '''
 递归寻找父节点
 :param leafNode:
 :param prefixPath:
 :return:
 '''
 if leafNode.parent != None:
 prefixPath.append(leafNode.name)
 ascendTree(leafNode.parent, prefixPath)
 def findPrefixPath(basePat, treeNode):
 condPats = {}
 while treeNode != None:
 prefixPath = []
 # 获得某个叶子节点的前缀路径
 ascendTree(treeNode, prefixPath)
 if len(prefixPath) >= 2:
 # 去掉自己获得前缀路径,且权重为当前结点的权重,用于建立条件前缀树
 condPats[frozenset(prefixPath[1:])] = treeNode.count
 treeNode = treeNode.nodeLink
 return condPats
- 进行频繁项挖掘 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22- def mineTree(inTree, headerTable, minSup, preFix, freqItemList): 
 '''
 递归查找频繁项集
 :param inTree: FP树
 :param headerTable:
 :param minSup:
 :param preFix: 当前前缀
 :param freqItemList: 存储频繁项集
 :return:
 '''
 # 从出现次数少的开始找
 bigL = [v[0] for v in sorted(headerTable.items(), key=lambda p: p[1][0])]
 for basePat in bigL:
 newFreqSet = preFix.copy()
 newFreqSet.add(basePat)
 freqItemList.append(newFreqSet)
 condPattBases = findPrefixPath(basePat, headerTable[basePat][1])
 CondTree, Header = createTree(condPattBases, minSup)
 if Header != None:
 mineTree(CondTree, Header, minSup, newFreqSet, freqItemList)-