FP-growth算法Python实现:FP树构建、递归挖掘与可视化
简介这份资源面向数据挖掘与机器学习初学者及需要落地关联规则分析的开发者围绕FP-growth频繁模式增长算法提供Python实现与FP树可视化工具可用于购物篮分析、频繁项集挖掘与大型数据库中的频繁模式发现。压缩包共11个文件约480KB包含py主程序与whl依赖包、csv交易样例数据、png可视化输出图、md与txt说明文档及docx附赠资料覆盖从代码运行到结果解读的完整链路。已有76人学习下载。读者可借助FPTree.py理解FP树构建与递归挖掘流程结合购物篮分析示例数据复现频繁项集发现过程并通过生成的树结构图直观观察模式组织方式同时对照说明文档排查环境依赖与运行问题适合作为课程实验、算法对比研究或商业场景原型验证的参考素材。1. FP-growth 到底解决了什么问题从 Apriori 的两遍扫描说起购物篮分析里最经典的场景是超市想知道“买了啤酒的人有多大比例会顺手拿尿布”。这件事在数据挖掘里叫关联规则学习核心是先从交易流水里挖出频繁项集再据此生成规则。Apriori 是最早普及的算法但它的痛点很致命——每生成一个候选集就要重新扫一遍数据库交易表一大I/O 直接爆炸。FP-growth频繁模式增长换了个思路只扫两遍数据库把事务压缩进一棵 FP 树之后所有挖掘都在内存里的树上做不再反复读盘。这篇笔记就围绕 FP-growth 的 Python 实现与可视化工具展开把 FP 树结构怎么建、条件模式基怎么递归、树怎么画出来讲透适合已经会写 Python、想真正把频繁项集挖掘跑在大型数据库上的从业者。热词里的关联规则学习、频繁项集挖掘、数据挖掘本质都是这一条链路。2. FP 树构建两遍扫描与节点结构设计2.1 为什么第一遍扫描只数频率FP-growth 的第一遍扫描不做任何挖掘只统计每个单品在所有事务中出现的次数。这一步的目的是拿到每个项的全局支持度计数然后按支持度降序排列得到一个“头指针表”header table。为什么要排序因为 FP 树是一棵前缀树事务里的项按支持度从高到低插入能让高频项尽量靠近根节点树的分支更少、压缩率更高后续递归时条件模式基也更短。如果顺序乱了同一批事务可能长出大量重复路径树会膨胀内存和递归深度都会失控。这一步的产出有两个一个是频繁 1 项集支持度低于 min_support 的项直接丢掉另一个是排序后的频繁项列表。注意低于阈值的项在插入树之前就要过滤掉否则它们会污染树结构让后面挖出来的条件模式基包含大量无意义节点。def build_header_table(transactions, min_support): # 第一遍扫描统计每个项的出现次数 freq {} for trans in transactions: for item in trans: freq[item] freq.get(item, 0) 1 # 过滤掉低于最小支持度的项 freq {k: v for k, v in freq.items() if v min_support} # 按支持度降序排列得到头指针表的顺序 sorted_items sorted(freq.items(), keylambda x: x[1], reverseTrue) header {item: [count, None] for item, count in sorted_items} return header逻辑说明freq字典记录原始计数过滤后只保留频繁项。header的 value 是一个列表第一个元素是计数第二个元素是节点链表的头指针初始为 None。参数min_support是绝对支持度计数不是比例如果你习惯用比例要在调用前乘以事务总数。2.2 第二遍扫描把事务压进树里第二遍扫描才真正建树。对每条事务先按头指针表的顺序重排项然后从根节点开始逐项往下走如果当前节点已有该子节点计数加一没有就新建节点并把它挂到对应项的头指针链表上。头指针链表的作用是后面找某个项的所有出现位置时不用遍历整棵树顺着链表就能拿到所有节点再往上回溯得到条件模式基。节点结构至少要存四个东西项名、计数、父节点引用、子节点字典。父节点引用是必须的因为回溯条件模式基时要一路往根走。子节点用字典而不是列表是为了 O(1) 判断某个项是否已经是子节点。class FPNode: def __init__(self, name, count, parent): self.name name self.count count self.parent parent self.children {} self.node_link None # 指向同项的下一个节点 def insert_tree(trans, header, root): # 事务已按头指针表顺序排好 node root for item in trans: if item in node.children: node.children[item].count 1 else: new_node FPNode(item, 1, node) node.children[item] new_node # 挂到头指针链表 if header[item][1] is None: header[item][1] new_node else: cur header[item][1] while cur.node_link: cur cur.node_link cur.node_link new_node node node.children[item]逻辑说明insert_tree接收一条已排序事务从根往下走。node_link串起所有同名节点方便后续find_prefix_path回溯。参数root是空根节点名字通常设为 None 或 null。这里挂链表用的是尾插实际工程里如果链表很长可以维护一个尾指针数组避免每次 O(n) 遍历。2.3 头指针表与节点链表的配合头指针表不是装饰品它是 FP-growth 递归挖掘的入口。挖掘时从支持度最低的项开始头指针表从后往前对每个项顺着它的 node_link 链表找到所有节点每个节点往上回溯到根就得到一条条件模式基前缀路径。这些路径的计数取该节点的计数因为节点计数代表这条路径被多少事务共享。把所有前缀路径收集起来就构成了这个项的“条件 FP 树”的输入事务集然后递归建树、递归挖掘。这里有个容易翻车的点回溯时不要把当前项自己算进去条件模式基是“前缀”不含后缀项。另外路径计数不是简单累加而是取路径末端节点的计数因为一条路径可能被多个事务共享节点计数已经代表了共享次数。3. 递归挖掘频繁项集条件模式基怎么取3.1 从叶子往上为什么从低频项开始挖FP-growth 的挖掘顺序是从头指针表的尾部支持度最低的频繁项往头部走。原因是低频项的条件模式基更短、更集中递归深度小先挖它能把长频繁项集逐步拆解。如果从高频项开始条件树会很大递归分支爆炸。这个顺序不是随便定的是 FP-growth 能比 Apriori 快的关键之一。对每个项拿到它的所有前缀路径后把这些路径当成新的事务集重新统计频率、过滤、建一棵条件 FP 树。如果条件 FP 树只有单条路径直接枚举这条路径上所有子集与当前项组合就是频繁项集如果有多条分支继续递归。def mine_tree(header, min_support, prefix, freq_items): # 按支持度升序处理即从头指针表尾部开始 sorted_items sorted(header.items(), keylambda x: x[1][0]) for item, (count, node) in sorted_items: new_prefix prefix.copy() new_prefix.add(item) freq_items.append((new_prefix, count)) # 收集条件模式基 cond_paths [] cur node while cur: path [] parent cur.parent while parent and parent.name is not None: path.append(parent.name) parent parent.parent if path: cond_paths.append((path, cur.count)) cur cur.node_link # 用条件模式基建条件 FP 树 cond_header build_header_table( [p for p, c in cond_paths for _ in range(c)], min_support) if cond_header: cond_root FPNode(None, 0, None) for p, c in cond_paths: # 按条件头表顺序重排 ordered [i for i in sorted(cond_header, keylambda x: cond_header[x][0], reverseTrue) if i in p] insert_tree(ordered, cond_header, cond_root) mine_tree(cond_header, min_support, new_prefix, freq_items)逻辑说明mine_tree递归处理每个项。cond_paths收集前缀路径和对应计数。建条件树时事务要按条件头表顺序重排这一步不能省否则树结构会乱。freq_items累积所有频繁项集及其支持度。参数prefix是当前已选中的项集合递归时不断扩展。3.2 单路径优化能省一次递归就省当条件 FP 树只有一条路径时不需要再递归建树。直接枚举这条路径上所有非空子集与当前前缀组合每个组合的支持度取路径上最小的节点计数。这个优化在稀疏数据集上效果明显能砍掉大量无意义的递归调用。判断单路径的方法很简单从根往下走如果每个节点只有一个子节点直到叶子就是单路径。def is_single_path(root): node root while node: if len(node.children) 1: return False if not node.children: return True node next(iter(node.children.values())) return True逻辑说明is_single_path从根往下遇到分支就返回 False走到叶子返回 True。单路径枚举时路径上的项按从头到尾的顺序支持度取路径上各节点计数的最小值因为组合的支持度受最弱环节限制。3.3 支持度与置信度规则生成的两个阈值频繁项集挖出来后生成关联规则还要算置信度。对每个频繁项集枚举它的非空真子集作为前件剩余部分作为后件置信度 项集支持度 / 前件支持度。只有置信度不低于 min_conf 的规则才保留。注意前件支持度必须从频繁项集结果里查不能重新扫数据库否则又退化成 Apriori 了。参数含义常用取值影响min_support最小支持度计数2~5小数据集越低项集越多树越大min_conf最小置信度0.5~0.8越高规则越少越可靠max_len最大项集长度3~5限制递归深度防爆炸提示min_support 用绝对计数比用比例更直观尤其在事务数变化时不用反复换算。如果数据量很大先跑一遍频率分布再定阈值。4. FP 树可视化把黑匣子画出来4.1 用 Graphviz 画树结构FP 树不画出来调试时就是黑匣子。常见做法是用 Graphviz 的 Python 绑定把每个节点画成带项名和计数的框父子关系画成有向边头指针链表用虚线连起来。这样一眼就能看出哪些分支被压缩了、哪些项计数异常。from graphviz import Digraph def visualize_tree(root, header, filenamefp_tree): dot Digraph(commentFP Tree) dot.attr(node, shapebox) def add_nodes(node, parent_idNone): if node.name is None: node_id root dot.node(node_id, root) else: node_id f{node.name}_{id(node)} dot.node(node_id, f{node.name}:{node.count}) if parent_id: dot.edge(parent_id, node_id) for child in node.children.values(): add_nodes(child, node_id) add_nodes(root) # 画头指针链表 for item, (count, node) in header.items(): prev None cur node while cur: cur_id f{cur.name}_{id(cur)} if prev: dot.edge(prev, cur_id, styledashed, colorred) prev cur_id cur cur.node_link dot.render(filename, formatpng, cleanupTrue)逻辑说明add_nodes递归遍历树节点 ID 用项名加id(node)保证唯一。头指针链表用红色虚线连接和树边区分开。参数filename是输出文件名cleanupTrue会删掉中间文件只留 png。如果树很大Graphviz 布局会挤可以只画前几层或者用rankdirLR改成横向。4.2 交互式可视化用 Pyecharts 做可缩放树Graphviz 适合静态图树一大就糊。交互式场景可以用 Pyecharts 的 Tree 图支持缩放和折叠。把 FP 树转成嵌套字典每个节点带 name 和 value计数Pyecharts 会自动布局。这样在浏览器里能逐层展开适合给非技术同事看。from pyecharts.charts import Tree from pyecharts import options as opts def tree_to_dict(node): if node.name is None: name root else: name f{node.name}({node.count}) children [tree_to_dict(c) for c in node.children.values()] return {name: name, children: children} def render_interactive(root, filenamefp_tree.html): data [tree_to_dict(root)] tree Tree() tree.add(, data, collapse_interval2) tree.set_global_opts(title_optsopts.TitleOpts(titleFP Tree)) tree.render(filename)逻辑说明tree_to_dict把节点转成 Pyecharts 需要的嵌套结构collapse_interval2表示默认展开两层。参数filename是输出 HTML 路径。这种方式不依赖本地 Graphviz但节点多了浏览器会卡建议先剪枝再画。4.3 频繁项集的支持度分布图除了树还值得画一张频繁项集的支持度分布图横轴是项集长度纵轴是支持度用散点或箱线图看分布。这样能快速判断 min_support 设得合不合理——如果大部分项集支持度都贴着阈值说明阈值偏高漏掉了长尾模式如果支持度分布很散说明数据本身模式丰富。import matplotlib.pyplot as plt def plot_support_dist(freq_items): lengths [len(items) for items, _ in freq_items] supports [sup for _, sup in freq_items] plt.scatter(lengths, supports, alpha0.5) plt.xlabel(Itemset Length) plt.ylabel(Support) plt.title(Support Distribution of Frequent Itemsets) plt.savefig(support_dist.png)逻辑说明freq_items是(set, support)列表。散点图能看出长项集的支持度是否骤降。参数alpha控制透明度点重叠时能看出密度。如果支持度取了对数记得在纵轴标注。5. 避坑与排查FP-growth 落地时的五个血泪经验5.1 现象树节点数远超事务数内存爆了原因插入事务前没有按头指针表顺序重排导致同一批项在不同事务里顺序不一致树无法共享前缀每个事务几乎长出一条独立路径。解决在insert_tree之前对每条事务执行ordered [i for i in header_order if i in trans]确保全局顺序一致。5.2 现象递归深度过大Python 报 RecursionError原因数据集里存在大量长事务且 min_support 设得太低条件树分支多、递归层数深。解决设置sys.setrecursionlimit(10000)只是临时手段根本办法是提高 min_support 或加max_len限制项集长度。另外可以把递归改成显式栈避免爆栈。5.3 现象挖出来的规则置信度算错原因前件支持度没有从频繁项集结果里查而是重新扫库统计导致计数口径不一致。解决把频繁项集存成字典{frozenset: support}生成规则时直接查字典。注意项集用 frozenset 做 key普通 set 不可哈希。5.4 现象头指针链表断了漏挖项集原因挂链表时只挂了第一个节点后续节点没接上或者多线程建树时链表被并发修改。解决单线程建树挂链表时用尾插并维护尾指针。如果必须并发每个项独立加锁或者先分片建树再合并。5.5 现象可视化图节点重叠根本看不清原因树太深或太宽Graphviz 默认布局挤在一起。解决只画支持度 top-N 的子树或者用dot.attr(ranksep1.5, nodesep0.5)加大间距。交互式图用collapse_interval控制默认展开层数让用户自己点开。6. 进阶技巧把 FP-growth 用在真实购物篮数据上真实交易数据往往是稀疏的而且存在大量一次性商品。直接跑 FP-growth 会被这些低频项拖慢。我一般会先做一步预处理统计每个商品的出现次数把低于某个绝对阈值的商品直接过滤掉再跑 FP-growth。这一步和算法内部的第一遍扫描不冲突但能大幅减小输入规模。另一个技巧是分块挖掘把交易按时间或门店分片每片单独挖再合并频繁项集。合并时注意支持度要按全局事务数重新折算不能直接相加。验证挖掘结果是否靠谱我习惯用两个手段一是抽几条规则人工核对看前件后件在业务上是否说得通二是把 min_support 调高一档再跑看高频规则是否稳定。如果两次结果差异很大说明阈值卡在了模式分布的陡坡上需要重新选点。参数上购物篮数据通常 min_support 取总事务数的 0.5%~2%min_conf 取 0.5~0.7max_len 限制在 4 以内避免组合爆炸。最后说个我踩过的坑有次为了追求“全量挖掘”把 min_support 设成 1结果树大到内存扛不住跑了一夜没出结果。后来改成先按商品频次过滤再设 min_support3十分钟就跑完了规则质量反而更高。频繁项集挖掘不是越多越好阈值选对了长尾模式自己会浮出来。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

百度网盘同步卡顿?3步定位IO瓶颈的最佳实践

百度网盘同步卡顿?3步定位IO瓶颈的最佳实践

百度网盘同步卡顿?3步定位IO瓶颈的最佳实践 刚把同事发来的 sync_daemon.py 复制到项目里, python main.py 一敲,终端直接报 OSError: [Errno 110] Connection timed out…

2026/9/23 20:16:31 阅读更多 →
QLV文件是什么?加密原理与转MP4方法解析

QLV文件是什么?加密原理与转MP4方法解析

1. 从一个让人抓狂的场景说起:qlv文件到底是什么很多人第一次遇到qlv文件,都是在整理旧电脑或者拷贝视频资料的时候。你从某个视频平台下载了一部电影或者一套课程,准备换个设备播放,结果双击之后系统弹出一个"无法打开此文件…

2026/9/23 20:16:31 阅读更多 →
facebook账号注册踩坑实录:新手避坑全指南

facebook账号注册踩坑实录:新手避坑全指南

facebook账号注册踩坑实录:新手避坑全指南 盯着屏幕满屏红色的StackTrace,是不是感觉脑子要炸了?刚写完代码,一运行就报一堆看不懂的异常,连报错的第一行都看不懂在说什么。这种“报错一堆看不懂”的绝境,几乎是每个刚接触后端开发或…

2026/9/23 20:16:31 阅读更多 →

最新新闻

不折腾网络搞定 Buzz 音频转录的模型下载卡顿:三条路线一次跑通

不折腾网络搞定 Buzz 音频转录的模型下载卡顿:三条路线一次跑通

不折腾网络搞定 Buzz 音频转录的模型下载卡顿:三条路线一次跑通 【免费下载链接】buzz Buzz transcribes and translates audio offline on your personal computer. Powered by OpenAIs Whisper. 项目地址: https://gitcode.com/GitHub_Trending/buz/buzz B…

2026/9/23 22:39:53 阅读更多 →
手把手搭建开源股票数据系统OpenStock:从数据采集到可视化看板

手把手搭建开源股票数据系统OpenStock:从数据采集到可视化看板

先说清楚一个事儿,OpenStock 不是某只股票的名字,也不是什么内测中的炒股神器,而是一套开源的股票数据获取、分析、可视化展示系统。我自己维护这个项目已经有大半年了,从最初只是想把自己每天手动看盘、复制粘贴数据的活儿自动化…

2026/9/23 22:39:53 阅读更多 →
自适应滤波器原理与MATLAB实现:LMS、NLMS、RLS对比及工程避坑指南

自适应滤波器原理与MATLAB实现:LMS、NLMS、RLS对比及工程避坑指南

简介:这份资源面向信号处理、雷达与通信方向的学习者与工程人员,聚焦线性约束最小方差(LCMV)自适应滤波器的原理与MATLAB实现,帮助读者理解如何在满足线性约束的前提下最大化输出信噪比,并将其用于雷达目标…

2026/9/23 22:39:53 阅读更多 →
用户记忆与知识库

用户记忆与知识库

上篇文章解决的是单次交互的上下文工程管理,本篇文章处理agent在本轮对话结束后如何记住用户、知识。1. 记忆的表示和管理三层级评估框架(如何评估用户记忆系统的能力?)基础记忆:记住用户结构化的、准确的信息,如手机号是123xxxx多…

2026/9/23 22:39:52 阅读更多 →
KingbaseES v8.6 GIS数据迁移避坑指南:SRID、空间索引与逻辑复制实战

KingbaseES v8.6 GIS数据迁移避坑指南:SRID、空间索引与逻辑复制实战

简介:本资源是一份面向GIS系统管理员、数据库工程师及国产化替代项目实施人员的KingbaseES V8.6 GIS数据迁移实战指南,聚焦ArcGIS/GeoScene、SuperMap等主流平台向人大金仓数据库的平滑迁移问题。文档系统梳理了KingbaseES的空间数据支持能力&#xff08…

2026/9/23 22:39:52 阅读更多 →
Atlas 300V 部署 YOLO 完整指南:从环境搭建到推理性能调优

Atlas 300V 部署 YOLO 完整指南:从环境搭建到推理性能调优

Atlas 300V 部署 YOLO:从“这张卡到底是啥”到跑通目标检测的完整记录前一阵子项目组递给我一张 Atlas 300V 24G,任务很简单:把 YOLOv5 在它上面跑起来。我第一反应跟很多人的热搜问题一模一样——这卡到底算不算运算加速卡?查资料…

2026/9/23 22:38:52 阅读更多 →

日新闻

3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A…

2026/9/23 0:00:23 阅读更多 →
2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我

2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我

2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我 刚把开发环境的显示器从1080P换到2K,跑老项目直接报错,版本升级后 API…

2026/9/23 0:01:25 阅读更多 →
3步搞定美眉图实战项目,告别官方文档抓不住重点

3步搞定美眉图实战项目,告别官方文档抓不住重点

3步搞定美眉图实战项目,告别官方文档抓不住重点 官方文档翻了三遍还是云里雾里?别急,美眉图在实战项目中常被用来做数据可视化,但它的原理比你想的简单。今天咱们直接上手,用一个完整的小项目把美眉图跑通,不再死磕那些冗长的理论说明。…

2026/9/23 0:01:25 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/23 4:55:02 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/23 4:49:06 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/23 9:53:41 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/23 9:53:40 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/23 9:53:40 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/23 9:53:40 阅读更多 →