ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

频繁模式挖掘与关联规则:数据仓库大作业的Python实现与实战

频繁模式挖掘与关联规则:数据仓库大作业的Python实现与实战 简介面向数据仓库与数据挖掘课程期末大作业的完整项目资源采用Python实现频繁模式挖掘。方案基于Apriori算法从多角度、多篮子粒度进行关联规则挖掘在Gutenberg与DBLP数据集上设计了多个应用任务包括作者活跃度分析、团体挖掘和主题关联等场景覆盖较广。代码模块化清晰包含8个Python源文件并附有详细注释搭配PDF报告与Markdown说明文档即使新手也能快速理解核心逻辑简单部署即可运行。压缩包共41个文件约5.84MB另有txt数据文件、结果图片、pyc缓存等目录结构合理便于按需查阅。项目已有817人学习适合作为课程设计、期末大作业的高分参考可帮助读者掌握频繁模式挖掘从理论到落地的完整实现流程。1. 频繁模式挖掘大作业数据仓库课程最需要的是一整条交付链数据仓库和数据挖掘课程里频繁模式挖掘几乎是每届都有的经典选题。可真正让大作业拉开分差的往往不是 Apriori 或 FP-Growth 的算法推导而是源代码和报告之间有没有一条完整交付链。只准备一个能运行的挖掘脚本演示时点两下输出几行频繁项集老师追问数据怎么从仓库取出来、支持度为什么这么设现场就容易冷场。我一般会按题目中的“源代码文档说明报告pdf”三件套反推工作顺序先确定事务数据格式再写模块化代码最后用脚本生成带执行结果的报告。下面按这个顺序把数据仓库事实表到事务集、Apriori 源码结构、规则评估、PDF 报告制作串一遍并给出可直接改用的 Python 代码。适合正在做数据仓库与数据挖掘课程设计的人也适合想在下一次购物篮分析工程中复用这套代码的人。2. 频繁模式挖掘的算法选型与 Python 源码结构2.1 Apriori 和 FP-Growth在什么数据量下选谁更稳妥频繁模式挖掘的经典实现有两个Apriori 和 FP-Growth。Apriori 的核心是一个递推性质一个 k 项集如果不频繁它的所有超集也一定不频繁所以每一轮都只需在上轮频繁 k‑1 项集上生成候选再扫描事务库统计支持度。FP-Growth 的思路则是把事务库压缩成一颗 FP 树在树上递归处理条件模式基省掉层层的全库扫描。两个算法在数据仓库大作业里都能成立区别主要在数据量和你打算在报告里展开的技术深度。教学用数据仓库导出的订单表事务量通常从几千到几十万行。几千行时两个算法的耗时差距是秒级甚至更小Apriori 的实现更短、调参更直观事务量真正到几十万行时FP-Growth 对重复项的压缩效果更明显但纯 Python 手写树的排错成本也更高。我一般先用一个小支持度试探跑一次 Apriori如果耗时超过五秒再考虑切 FP-Growth。两个算法的差异可以直接做进报告文档下面是常用对比表。对比项AprioriFP-Growth对事务库扫描次数每生成一层候选项集都完整扫描建树阶段扫描两次挖掘在内存完成内存开销主体候选项集的组合数量FP 树节点与条件模式基列表纯 Python 实现行数约 100 至 150 行约 200 行以上几千行事务时的耗时直观可接受略快但不明显答辩追问友好度剪枝原因容易讲清楚需要解释树结构和条件模式基从答辩角度说Apriori 每一轮的候选生成、计数、剪枝都能对应到一张过程表FP-Growth 的树结构则要画得足够准确才能讲明白。再考虑作业查重我通常建议源代码以 Apriori 为主线把 FP-Growth 的树构建思路作为扩展章节写进文档说明。等以后真遇到大规模订单数据再按 FP-Growth 重构不迟。2.2 把频繁模式挖掘源代码拆成加载器、挖掘器、规则器拿到一个现成的 Apriori 例程后第一件事不是调参而是把源码拆成 loader、miner、rules 三个文件。原因有两个老师检查源代码时能一眼看出数据的进出口在哪里另一个原因是很多免费 python 源码大全里下载的例程几十行全堆在一个 main.py 里查重时整个文件都会被判定为网络已有实现。拆开之后每个文件都加入针对本次数据仓库结构的自定义逻辑命中率反而下降。推荐的目录结构如下。assignment_python_frequent/ ├── data/ │ ├── transactions.csv # 整理后的事务数据 │ └── warehouse_query.sql # 从数据仓库取数的 SQL ├── loader.py # 读取数据并得到 List[Set[str]] ├── miner.py # Apriori 算法主体 ├── rules.py # 关联规则生成与评估 ├── main.py # 命令行入口 ├── output/ # 挖掘结果和图表 ├── report.md # 报告源文件 └── README.md # 文档说明文件名里越能体现数据仓库上下文越好warehouse_query.sql 的存在就是大作业与实际数据结合的直接证明。loader 只负责把 CSV、数据库结果转成内存中的事务列表miner 只接收事务列表并返回频繁项集字典rules 再把频繁项集推导成关联规则。main.py 只做三件事解析参数、组装流程、写输出文件。后面排错时只要 loader 输出的数据格式不变miner 某个函数改坏了马上能定位到出错文件。2.3 事务数据类型统一为 List[Set[str]]避免重复埋坑频繁模式挖掘代码对输入数据的假设不多但类型必须稳定。我习惯把一切来源统一成List[Set[str]]列表的每一项代表一个订单集合里的字符串代表该订单中的商品。用集合而不是列表是因为 Apriori 里的子集判断issubset()在集合上走哈希运算而且集合天然去重同一订单重复购买的商品不会干扰计数。下面这个函数直接放到 loader.py 里。# loader.py from typing import List, Set def to_transactions(raw_rows: List[List[str]]) - List[Set[str]]: 把原始行数据转换为事务集。 transactions [] for row in raw_rows: items {cell.strip() for cell in row if cell and cell.strip()} if items: transactions.append(items) return transactions函数入参raw_rows来自 csv.reader 或 pandas 的values.tolist()它是行数据的二维矩阵。集合推导式里的if cell过滤空字符串cell.strip()去掉头尾空格这是数据仓库导出数据最常出现的问题同一个商品名在不同批次被写成牛奶和牛奶strip 之后才能算作同一个项。函数返回时既去重也打乱了顺序但顺序不影响挖掘结果因为后续频繁项集判断走的是集合运算不是按位置比较。如果 loader 输出的事务集传给算法后频繁项集数量与预期偏差很大先检查是否漏了这步 strip。另外事务之间必须独立一个订单的商品绝不会跑到另一个订单里分组逻辑应当在 loader 之前完成不要等到 miner 里去强行按订单号拆分。3. Python 实现频繁模式挖掘源代码Apriori 的可运行脚本3.1 候选集生成与剪枝Apriori 循环的主体代码这一部分直接给出 miner.py 的完整核心代码可以原样复制到项目里跑通。两个函数加在一起就是 Apriori 的主体candidate_gen负责由 k‑1 频繁项集生成 k 项候选并剪枝apriori负责迭代计数。# miner.py from itertools import combinations from typing import Dict, List, Set def candidate_gen(prev_itemsets: Set[frozenset], k: int) - Set[frozenset]: 由 k-1 频繁项集生成 k 项候选并执行 Apriori 剪枝。 candidates set() prev list(prev_itemsets) for combo in combinations(prev, 2): union combo[0] | combo[1] if len(union) k: candidates.add(union) # 剪枝候选的任意 k-1 维子集都必须出现在上一轮频繁项集中 pruned set() for cand in candidates: if all(frozenset(sub) in prev_itemsets for sub in combinations(cand, k - 1)): pruned.add(cand) return pruned def apriori(transactions: List[Set[str]], min_support: float) - Dict[int, Set[frozenset]]: 返回 {项集大小: 频繁项集合}支持度统一用 float 表示。 n max(1, len(transactions)) item_count: Dict[str, int] {} for t in transactions: for item in t: item_count[item] item_count.get(item, 0) 1 freq: Dict[int, Set[frozenset]] {1: set()} for item, cnt in item_count.items(): if cnt / n min_support: freq[1].add(frozenset([item])) k 2 while freq[k - 1]: candidates candidate_gen(freq[k - 1], k) count {cand: 0 for cand in candidates} for t in transactions: ts set(t) for cand in candidates: if cand.issubset(ts): count[cand] 1 freq[k] {cand for cand, cnt in count.items() if cnt / n min_support} k 1 return {size: itemsets for size, itemsets in freq.items() if itemsets}关键参数min_support是 0 到 1 之间的小数0.3 表示一个项集至少出现在 30% 的订单中才算频繁。函数第一步统计所有单物品出现次数过滤出频繁 1 项集随后 while 循环从频繁 k‑1 项集生成 k 项候选每个候选都去事务库做子集命中计数只有计数达到cnt / n min_support才进入下一轮。剪枝条件写得更严格一些候选项的每一个 k‑1 子集都必须是上轮频繁项这能提前砍掉大量无效条目。用combinations(prev, 2)做两两连接是常见做法它强调两个 k‑1 项集共享 k‑2 个元素时才可能得到长度为 k 的并集。课程报告里可以把每轮候选数量和剪枝后的数量做成表格这比堆代码更能说明掌握程度。3.2 关联规则生成置信度、提升度怎么算频繁项集本身只是中间产物大作业展示的主体通常是关联规则。rules.py 里生成规则时我习惯把置信度和提升度一起算出来后者能过滤一批“看起来强但实际无意义”的规则。# rules.py from typing import Dict, List, Set def generate_rules( freq: Dict[int, Set[frozenset]], transactions: List[Set[str]], min_confidence: float 0.6, ): 返回规则列表每条规则包含前件、后件、支持度、置信度、提升度。 total len(transactions) # 统计单个商品的支持度用于计算提升度 item_support: Dict[str, int] {} for t in transactions: for item in t: item_support[item] item_support.get(item, 0) 1 rules [] for k, itemsets in freq.items(): if k 2: continue for itemset in itemsets: for conseq in itemset: ante itemset - {conseq} support_ante ( sum(1 for t in transactions if ante.issubset(t)) / total ) support_rules ( sum(1 for t in transactions if itemset.issubset(t)) / total ) confidence support_rules / support_ante if support_ante else 0 lift confidence / (item_support[conseq] / total) if confidence min_confidence: rules.append((ante, conseq, support_rules, confidence, lift)) return rulesmin_confidence是指定规则最低置信度的阈值0.6 表示前件出现时后件至少有 60% 的概率跟着出现。lift的计算是置信度除以后件无条件支持度值大于 1 时才说明前件对后件有正向促进作用等于 1 表示两者独立小于 1 甚至可能是一种抑制关系。这里为了控制代码长度规则只枚举了单后项的情况作业要求完整规则集时把for conseq in itemset换成对非空真子集的组合遍历即可。3.3 main.py 命令行封装与 CSV 输出命令行入口做成 argparse 形式评分老师拿到 README 后不需要打开源码找参数。python main.py --data data/transactions.csv --min-support 0.2 --min-confidence 0.5main.py 只负责组装 loader、miner、rules 三段流程不写任何算法细节。# main.py import argparse import csv from loader import to_transactions, load_csv from miner import apriori from rules import generate_rules def main(): parser argparse.ArgumentParser(description频繁模式挖掘-数据仓库大作业) parser.add_argument(--data, requiredTrue) parser.add_argument(--min-support, typefloat, default0.2) parser.add_argument(--min-confidence, typefloat, default0.5) args parser.parse_args() txs to_transactions(load_csv(args.data)) freq apriori(txs, args.min_support) rules generate_rules(freq, txs, args.min_confidence) with open(output/rules.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([前项, 后项, 支持度, 置信度, 提升度]) for ante, conseq, sup, conf, lift in rules: writer.writerow([,.join(ante), conseq, f{sup:.3f}, f{conf:.3f}, f{lift:.3f}]) if __name__ __main__: main()encodingutf-8-sig是常用做法Excel 直接打开 CSV 时中文不会乱码。load_csv在 loader.py 里封装 csv.reader 即可注意只读表头后按行组织原始数据。输出规则表保留前项、后项、支持度、置信度、提升度五列后两列在报告分析和筛选时最有用。提示Windows 下调试时如果控制台输出中文报错先确认终端代码页支持 UTF-8代码文件本身也要以 UTF-8 保存。3.4 用微型数据集验证频繁项集和规则合理性写完代码后先用四五个订单的小样本验证逻辑不要直接上全量仓库数据。下面这个数据集故意构造了一条“知名反例”买牛奶的人经常买面包但提升度可能接近 1。transactions [ {牛奶, 面包, 黄油}, {牛奶, 尿布, 啤酒}, {面包, 黄油}, {牛奶, 面包, 啤酒}, ] freq apriori(transactions, min_support0.5) for k, items in freq.items(): for itemset in items: print(k, sorted(itemset)) rules generate_rules(freq, transactions, min_confidence0.5) for ante, conseq, sup, conf, lift in rules: print(set(ante), -, conseq, fsupport{sup:.2f}, fconfidence{conf:.2f}, flift{lift:.2f})min_support0.5表示项集至少出现在两个订单中。这个例子会挖出频繁 1 项集“牛奶”“面包”“黄油”频繁 2 项集{牛奶, 面包}和{面包, 黄油}。规则“牛奶 → 面包”的置信度是 2/3提升度却只有 0.89因为面包本身出现率就高而“面包 → 黄油”的提升度大于 1才真正说明两者存在正向关联。把这条解释写进报告比放十张挖掘结果截图更能体现对频繁模式挖掘的理解。4. 数据仓库与数据挖掘的结合星型模型到事务集的预处理4.1 用 SQL 在数据仓库中做订单聚合而不是读明细文件频繁模式挖掘的输入是一个个事务而数据仓库常见输出是订单明细表。把明细按订单 ID 聚合成一行商品列表这一步我一般留在 SQL 里完成而不是先导出几万行明细再到 Python 里 groupby。数据库在聚合时还能顺带做状态过滤和空值处理数据落盘后再校验一遍整个流程更可控。下面这段 SQL 适合直接放到 data/warehouse_query.sql 里。SELECT f.order_id, GROUP_CONCAT(DISTINCT p.product_name ORDER BY p.product_name) AS items FROM sales_fact f JOIN product_dim p ON f.product_key p.product_key JOIN order_dim o ON f.order_key o.order_key WHERE o.order_status completed AND p.product_name IS NOT NULL GROUP BY f.order_id;这里的事实表 sales_fact 通过 product_key 与商品维度关联通过 order_key 与订单维度关联是数据仓库课程标准的星型模型写法。GROUP_CONCAT(DISTINCT ...)的作用是把一个订单下的多条商品记录拼成逗号分隔文本DISTINCT 保证同一商品在事务中只出现一次。o.order_status completed是业务过滤剔除了取消、退款订单这条过滤规则的业务含义要写进文档说明。需要注意 GROUP_CONCAT 是 MySQL 的方言SQL Server 对应 STRING_AGGPostgreSQL 对应 string_agg。如果数据量特别大GROUP_CONCAT 默认最大长度可能截断商品列表这时可以调整数据库会话参数或用两步聚合处理。SQL 执行结果导出为 CSV 后loader 里按逗号拆分即可不必再关注数据库方言差异。4.2 数值字段离散化用 pd.cut 把价格变成可挖掘的业务项事务集中的项必须是类别值而事实表里最常见的数值字段是单价和数量。如果不做离散化两个不同价格的同款商品会被切割成“牛奶:3.99”和“牛奶:4.05”支持度被严重压碎规则失去业务解释。我一般用 pandas 的pd.cut把连续价格分箱再把商品名和价格区间拼成复合项。import pandas as pd df pd.read_csv(sales_detail.csv) df[price_bin] pd.cut( df[unit_price], bins[0, 50, 100, 500, float(inf)], labels[低价, 中低价, 中高价, 高价], ) df[item_with_price] ( df[product_name].astype(str) df[price_bin].astype(str) ) grouped df.groupby(order_id)[item_with_price] transactions [set(items.dropna()) for order_id, items in grouped]bins参数定义价格区间的边界labels给每段命名两者长度必须匹配。如果商品价格集中在 20 到 90 元把边界改成[0, 30, 60, 120, float(inf)]会更贴近业务。复合项牛奶低价让后续挖掘结果天然带业务上下文报告中可以直接说“低价牛奶与中低价面包存在关联”而不是对着一堆数字猜含义。分箱过细时支持度会骤降如果最小支持度保持 0.3 而规则为空优先减少区间数量而不是把 min_support 压到 0.05。4.3 挖掘结果分析提升度排序和图表的报告用法代码跑完之后不要只把规则 CSV 往报告里一贴。先把提升度大于 1 的规则筛出来再按提升度降序取 Top N这样报告的核心结论才会集中在有实际价值的关联上。import pandas as pd rules_df pd.DataFrame( [ (,.join(ante), conseq, sup, conf, lift) for ante, conseq, sup, conf, lift in rules ], columns[antecedent, consequent, support, confidence, lift], ) top_rules rules_df[rules_df[lift] 1].sort_values(lift, ascendingFalse) print(top_rules.head(10))这段代码把 rules 列表转成 DataFrame然后用布尔筛选把提升度不大于 1 的规则全部丢掉。提升度等于 1 表示前件与后件独立这类规则写进报告会被认为是凑数。筛选后的 top_rules 还可以继续按支持度和置信度做二次过滤具体阈值根据挖掘目标调整。可视化层面我常用置信度和提升度的散点图来展示规则分布保存为 PNG 后插入报告 PDF。import matplotlib.pyplot as plt plt.scatter(top_rules[confidence], top_rules[lift], alpha0.6) plt.xlabel(confidence) plt.ylabel(lift) plt.savefig(output/rules_lift.png, dpi200)dpi200保证图片插到 Word 或 PDF 里不至于发虚散点图里右上角的规则是置信度又高、提升度又高的重点规则。图表标题和坐标轴标签要写中文时注意字体设置否则会出现方块字。到这里数据仓库取数、预处理、挖掘、分析四段流程已经完整可以开始组装文档说明和报告 PDF。5. 文档说明与报告 PDF用脚本闭合大作业的最后一环5.1 在 README 里给出可执行的验证命令源代码和报告不一致是频繁模式挖掘大作业最常见的扣分项。我在 README 开头只放两段内容运行环境和一条完整的启动命令。命令必须是在干净环境里也跑得通的。pip install pandas matplotlib python main.py --data data/transactions.csv --min-support 0.2 --min-confidence 0.5运行后检查 output 目录下是否生成了 rules.csv 和 rules_lift.png。如果只有控制台输出、没有文件生成说明 main.py 里输出目录不存在而没有创建需要在代码里加os.makedirs(output, exist_okTrue)。README 里把这两步写清老师不用找代码就能复现结果。5.2 用 Pandoc 和 XeLaTeX 生成中文报告 PDF报告 PDF 不建议用 Word 手工排两者字体公式不一致时会非常难看。我一般用 Markdown 写报告再用 Pandoc 转 PDF公式、图表、代码块都能自动排版。转换命令如下。pandoc report.md -o report.pdf --pdf-enginexelatex \ -V CJKmainfontNoto Sans CJK SC \ -V geometry:margin2.5cm--pdf-enginexelatex强制使用 XeLaTeX 引擎这样才能处理中文 Unicode 字体CJKmainfont指定中文字体名称不同系统差异很大Windows 可以写SimSun或Microsoft YaHeimacOS 可以用PingFang SCLinux 用Noto Sans CJK SC或WenQuanYi Micro Hei。如果命令执行报错找不到字体先查看系统已安装中文字体名再替换这一项。报告中一旦涉及支持度公式和置信度公式可以直接在 Markdown 里写 LaTeX 数学表达式Pandoc 会自动渲染。没有安装完整 TeX 发行版的情况下这条路比较麻烦遇到环境问题时应优先补装 texlive-lang-chinese 组件而不是改用其他 PDF 工具。5.3 交付前做三次人眼核验不管代码多完整PDF 最后的检查要做三次。第一次随便抽 5 个包含规则前件的订单人工核对后件是否同时出现这一步防的是事务拆分引入的脏数据。第二次在文档说明里写清楚 min_support 分别取 0.1、0.2、0.3 时频繁项集数量的变化这比只报告一个参数更有说服力。第三次把 rules.csv 里的前 10 条规则翻译成一句业务话术例如“低价牛奶与中低价面包同单率高”然后放进报告结论段。把上面命令行预演一遍并保留输出截图再生成 PDF这份频繁模式挖掘源代码加文档说明加报告 pdf 就具备了拿到高分的完整条件。本文还有配套的精品资源点击获取
返回列表