
实际做超市购物数据分析时最典型的需求并不是“统计哪个商品卖得最多”而是“找出哪些商品经常一起被购买”。这类问题属于关联规则分析中的购物篮分析核心算法是 Apriori。很多人一开始以为只要把算法跑起来就能自动得到有价值结论等到动手才发现原始交易流水和算法要求的输入格式差距很大支持度、置信度、提升度三个参数调不好结果要么为空要么全是噪音最后还要把规则以页面方式交给非技术同事使用。这篇文章用一个完整的 Flask 项目串联起整条链路从 CSV 交易数据出发完成数据清洗和事务矩阵转换调用 mlxtend 库的 Apriori 算法挖掘频繁项集再通过置信度和提升度筛选规则最终用 Flask 页面展示分析结果。做完之后你既能理解关联规则分析的完整流程也能直接把这个项目改造成自己的数据分析工具。1. 先理解关联规则分析与 Flask 为什么会出现在同一个项目里1.1 关联规则分析要回答的是商品组合问题关联规则分析是一种无监督学习方法用于从大量事务数据中发现项集之间的关联关系。在超市场景中“事务”就是一张购物小票“项”就是小票里的具体商品。算法的目标是从成千上万张小票中找出稳定性足够高的商品组合规则例如全脂牛奶 - 面包这条规则表示购买全脂牛奶的顾客有较大概率同时购买面包。规则左侧称为前件右侧称为后件。真正有价值的信息不是“这个顾客买了什么”而是“买了 A 的人还经常买什么”以及“这种关联是否比随机购买更显著”。Flask 在这个场景中的角色是展示层和交互层。数据挖掘算法本身不依赖 Flask但实际业务中运营人员不可能每次都在命令行里修改参数、执行脚本。比较合理的做法是把数据上传、参数设置、规则展示做成 Web 界面让运营人员上传交易流水、设定阈值直接在页面上查看推荐组合。这也是这篇文章选择 Flask 的原因它轻量、简单适合把数据分析脚本包装成可交互的小系统。1.2 支持度、置信度、提升度三个核心指标关联规则的质量不能靠肉眼判断需要用三个统计指标来量化。支持度Support表示规则出现的普遍程度计算公式为support(X - Y) 包含 X 和 Y 的事务数 / 总事务数支持度衡量的是一个组合在所有订单里出现的频率。比如总共有 10000 张订单其中“全脂牛奶 面包”同时出现 300 次支持度就是 0.03。如果支持度太低说明这个组合非常少见不具备普遍意义。置信度Confidence表示前件成立时后件也成立的比率confidence(X - Y) 包含 X 和 Y 的事务数 / 包含 X 的事务数置信度衡量的是规则的可靠性。比如购买全脂牛奶的 600 个顾客里有 330 人也买了面包置信度就是 0.55。置信度高说明从 X 推到 Y 的可信度强。提升度Lift衡量的是关联强度是否高于随机水平lift(X - Y) confidence(X - Y) / support(Y)如果提升度等于 1说明 X 和 Y 互相独立大于 1 表示正相关买 X 会提升买 Y 的概率小于 1 表示负相关。通常只保留提升度明显大于 1 的规则比如大于 1.0 甚至大于 2.0。这三个指标解决的是不同问题支持度防止找到偶然组合置信度保证规则可靠性提升度剔除虚假关联。实际筛选中三个指标需要同时满足阈值条件这一点在后面参数调试部分会展开说明。1.3 Flask 项目雏形要解决什么问题把上述概念放进项目里这个 Flask 应用需要完成四件事接收交易流水文件、把流水整理成算法需要的矩阵、执行频繁项集挖掘和规则生成、把结果渲染到页面。围绕这四个任务项目可以拆成数据处理模块、规则挖掘模块、Web 路由模块和模板文件。这样拆分之后算法逻辑可以独立测试Web 层只负责调用和展示以后想换成 FP-Growth 或者增加可视化都不用改动全部代码。2. 环境准备先让 Flask、pandas、mlxtend 这套组合能跑起来2.1 安装依赖库推荐的 Python 版本是 3.9 以上。关联规则挖掘的主要依赖如下依赖库用途说明FlaskWeb 框架提供上传、路由、模板渲染服务pandas数据处理读取 CSV、做分组清洗、生成事务矩阵mlxtend关联规则算法提供apriori和association_rules函数openpyxlExcel 写入需要导出 Excel 时使用建议先创建虚拟环境然后安装依赖python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate pip install flask pandas openpyxl mlxtend安装完成后在当前虚拟环境里执行下面的命令确认版本没有冲突python -c import flask, pandas, mlxtend; print(flask.__version__); print(pandas.__version__)如果输出正常说明基础环境已经就绪。这里要注意mlxtend 对 pandas 版本有一定要求建议安装时不要固定过旧的 pandas 版本。若遇到版本冲突优先升级 pandas 和 numpy。2.2 项目目录结构项目命名为supermarket_association目录结构如下supermarket_association/ ├── app.py ├── requirements.txt ├── data/ │ └── market_data.csv ├── analysis/ │ ├── __init__.py │ ├── preprocess.py │ └── association.py └── templates/ ├── index.html └── result.html目录职责划分app.pyFlask 应用入口负责页面路由和文件上传处理。analysis/preprocess.py数据清洗、事务矩阵转换。analysis/association.pyApriori 频繁项集挖掘、关联规则筛选。templates/前端页面模板使用 Jinja2 渲染。data/存放 CSV 格式交易流水也可以由用户上传。之所以把算法逻辑从app.py中拆出来是为了保证单测的独立性。Web 层只负责参数解析和结果返回数据处理与挖掘逻辑不依赖 Flask在 Jupyter Notebook 或命令行中也能单独测试。2.3 准备一份交易流水数据为了让项目可运行需要准备 CSV 格式的原始数据。最简格式只需要两列订单编号和商品名。实际超市数据往往还有数量、价格、时间字段分析时可以按需保留。示例market_data.csvTransactionID,Product 1001,全脂牛奶 1001,面包 1002,全脂牛奶 1002,黄油 1003,啤酒 1003,面包 1003,全脂牛奶 1004,黄油 1005,全脂牛奶 1005,面包 1005,香肠这份数据只有 5 张订单适合做功能验证。真实项目建议准备几千到几万笔订单否则频繁项集很难体现统计意义后面参数调优部分会专门说明数据量的影响。3. 数据预处理把订单流水转换成关联规则所需的事务矩阵3.1 原始数据清洗阶段需要处理的问题关联规则算法接收的对象不是流水明细而是“每笔订单购买了什么商品”的矩阵。因此第一步先清洗数据。analysis/preprocess.py中可以这样实现import pandas as pd def load_transactions(file_path_or_buffer): 读取 CSV 交易流水校验必备列。 df pd.read_csv(file_path_or_buffer, encodingutf-8) required_cols [TransactionID, Product] for col in required_cols: if col not in df.columns: raise ValueError(f缺少必需列: {col}) return df读取时容易踩的坑有两个。第一个是编码问题很多国内超市导出的 CSV 是 GBK 编码直接pd.read_csv会报UnicodeDecodeError此时需要指定encodinggbk。第二个是空行和空值问题可以先用dropna(subset[TransactionID, Product])去掉缺失行再删除商品名为空的记录。3.2 生成 One-Hot 编码的事务矩阵Apriori 算法需要的输入格式是行代表订单列代表商品单元格值为 1 或 0表示该订单是否购买了对应商品。这种结构通常称为 One-Hot 编码矩阵或者叫“篮子矩阵”。在preprocess.py中实现转换函数def build_basket(df, transaction_colTransactionID, product_colProduct): 将订单流水转换为事务矩阵。 每一行是订单编号每一列是商品单元格为 1 表示该订单包含该商品。 # 只保留必要字段 df df[[transaction_col, product_col]].dropna() # 统计每个订单中每个商品的出现次数 grouped ( df.groupby([transaction_col, product_col]) .size() .unstack(fill_value0) ) # 出现次数大于 0 即视为购买转换为 0/1 basket (grouped 0).astype(int) return basket关键点在于groupby(cols).size().unstack(fill_value0)这段写法。groupby按订单编号和商品名称聚合.size()统计每个组合出现的次数当同一订单中同一个商品出现多行时例如买了 3 瓶牛奶记录成 3 行这里会统计成数量 3unstack把商品名变成列订单编号变成行fill_value0解决缺失单元格的问题最后用astype(int)转成 0/1 数值避免后续算法读到缺失值。运行后上面的示例数据会转成类似这样的矩阵全脂牛奶 面包 黄油 啤酒 香肠 TransactionID 1001 1 1 0 0 0 1002 1 0 1 0 0 1003 1 1 0 1 0 1004 0 0 1 0 0 1005 1 1 0 0 13.3 为什么 mlxtend 只认这种输入格式mlxtend 的apriori函数要求输入是 DataFrame而且每个单元格必须是布尔值 True/False 或者数值 0/1。程序内部会检查数据是否满足这个要求。这里要注意两点。第一不要把商品名作为 DataFrame 的列名之后又混入数值型字段算法会把所有非布尔列都当成商品参与计算。第二矩阵里不要保留订单号和价格等无关列否则算法会把“订单号”或“价格”当作商品来处理。也就是说build_basket的返回值应该已经是纯粹的 0/1 矩阵不能保留其他业务字段。学习阶段可以写一个简单断言assert basket.isin([0, 1]).all().all(), 事务矩阵必须只包含 0 和 1 assert (basket.sum(axis1) 0).all(), 存在无任何商品的订单请清理在生产环境里也可以在预处理模块中主动做这两个校验避免脏数据进入算法。4. 核心算法模块用 mlxtend 完成频繁项集挖掘和规则筛选4.1 先通过支持度挖掘候选频繁项集Apriori 算法的第一步是产生频繁项集。频繁项集是指支持度大于等于指定阈值的商品组合。association.py中封装如下from mlxtend.frequent_patterns import apriori from mlxtend.frequent_patterns import association_rules def generate_frequent_itemsets(basket, min_support0.03): 生成频繁项集。 if basket.empty: return None frequent_itemsets apriori( basket, min_supportmin_support, use_colnamesTrue, max_lenNone ) return frequent_itemsetsuse_colnamesTrue会把项集中的商品 ID 改为商品名称例如用(全脂牛奶, 面包)替代(12, 15)这样的列索引结果更可读。min_support越小产生的频繁项集越多计算时间越长如果设置为 0可能生成大量长度为 1 和 2 的组合性能会明显下降。实际项目中第一次可以从 0.01 到 0.05 之间取一个初始值。4.2 生成关联规则并同时筛选置信度和提升度得到频繁项集之后就可以生成规则了。association_rules会根据频繁项集推导出所有前后件组合并计算支持度、置信度、提升度等指标。封装成函数def generate_rules(basket, min_support0.03, min_confidence0.30, min_lift1.00): 挖掘关联规则并返回按提升度排序的数据表。 frequent_itemsets generate_frequent_itemsets(basket, min_support) if frequent_itemsets is None or frequent_itemsets.empty: return None rules association_rules( frequent_itemsets, metricconfidence, min_thresholdmin_confidence ) rules rules[rules[lift] min_lift] rules rules.sort_values(lift, ascendingFalse).reset_index(dropTrue) return rules这里有一个很重要的顺序问题。association_rules中只设置metricconfidence和min_threshold是因为该函数只能设置一个筛选指标不能同时把 confidence 和 lift 都写进去。所以正确的做法是先按置信度筛选出基本可靠的规则再在结果上按提升度进一步过滤。如果先用 lift 筛选再按 confidence 阈值过滤会导致部分规则虽然 lift 很高但置信度很低仍然被误选。输出结果的主要列如下列名含义antecedents规则前件商品集合consequents规则后件商品集合support支持度confidence置信度lift提升度leverage杠杆率表示组合出现概率比独立情况高多少conviction确信度表示前件对后件的影响程度实际展示时通常只保留前五列。4.3 一个可以直接测试的独立模块为了让读者能快速验证“代码是否能跑通”这里给出一个可以直接在命令行执行的测试脚本test_association.pyimport pandas as pd from analysis.preprocess import load_transactions, build_basket from analysis.association import generate_rules if __name__ __main__: df load_transactions(data/market_data.csv) basket build_basket(df) print(事务矩阵维度:, basket.shape) rules generate_rules(basket, min_support0.2, min_confidence0.5, min_lift1.0) if rules is None: print(没有生成规则请降低阈值) else: print(rules[[antecedents, consequents, support, confidence, lift]])对前面那份 5 条订单的测试数据如果阈值设得过高可能一条规则都没有这是正常现象。可以把min_support降到 0.2min_confidence降到 0.5再观察输出。这一步能帮助理解参数与结果数量的关系。5. Flask 页面与接口让运营人员可以直接上传数据看结果5.1 配置 Flask 应用入口app.py是 Web 层入口负责文件上传、参数解析和模板渲染import pandas as pd from flask import Flask, render_template, request from analysis.preprocess import load_transactions, build_basket from analysis.association import generate_rules app Flask(__name__) app.config[MAX_CONTENT_LENGTH] 10 * 1024 * 1024 # 限制上传文件 10MB app.route(/, methods[GET]) def index(): return render_template(index.html) app.route(/analyze, methods[POST]) def analyze(): file request.files.get(file) if file is None or file.filename : return 请选择要上传的 CSV 文件, 400 try: min_support float(request.form.get(min_support, 0.03)) min_confidence float(request.form.get(min_confidence, 0.30)) min_lift float(request.form.get(min_lift, 1.00)) except ValueError: return 阈值参数必须为数值, 400 try: df load_transactions(file) basket build_basket(df) if basket.shape[0] 10: return 订单数量太少分析结果可能无统计意义, 400 rules generate_rules(basket, min_support, min_confidence, min_lift) except Exception as exc: return f分析失败: {exc}, 500 rule_list [] if rules is not None and not rules.empty: for _, row in rules.iterrows(): rule_list.append({ antecedents: 、.join(list(row[antecedents])), consequents: 、.join(list(row[consequents])), support: round(row[support], 4), confidence: round(row[confidence], 4), lift: round(row[lift], 4), }) return render_template( result.html, rulesrule_list, min_supportmin_support, min_confidencemin_confidence, min_liftmin_lift ) if __name__ __main__: app.run(debugTrue, host127.0.0.1, port5000)这里加入了几处防御性代码设置上传文件大小上限避免用户上传超大文件导致内存压力对参数做类型转换避免字符串传入导致崩溃订单数量少于 10 时直接返回提示因为过少的样本无法支撑支持度统计。5.2 编写上传页面与结果页面templates/index.html负责提供文件上传和参数输入!DOCTYPE html html langzh-CN head meta charsetUTF-8 title超市客户购物习惯关联规则分析/title /head body h1上传超市交易流水/h1 form action/analyze methodpost enctypemultipart/form-data p labelCSV 文件/label input typefile namefile accept.csv required /p p label支持度阈值/label input typetext namemin_support value0.03 /p p label置信度阈值/label input typetext namemin_confidence value0.30 /p p label提升度阈值/label input typetext namemin_lift value1.00 /p button typesubmit开始分析/button /form /body /htmltemplates/result.html负责展示规则表!DOCTYPE html html langzh-CN head meta charsetUTF-8 title关联规则结果/title style table { border-collapse: collapse; width: 100%; } th, td { border: 1px solid #ccc; padding: 8px; text-align: center; } th { background-color: #f2f2f2; } /style /head body h1关联规则分析结果/h1 p当前设置支持度 {{ min_support }}置信度 {{ min_confidence }}提升度 {{ min_lift }}/p p共发现 {{ rules|length }} 条规则/p {% if rules %} table tr th前件/th th后件/th th支持度/th th置信度/th th提升度/th /tr {% for rule in rules %} tr td{{ rule.antecedents }}/td td{{ rule.consequents }}/td td{{ rule.support }}/td td{{ rule.confidence }}/td td{{ rule.lift }}/td /tr {% endfor %} /table {% else %} p没有满足条件的规则请降低支持度或置信度阈值。/p {% endif %} pa href/重新分析/a/p /body /html参数回显是一个容易被忽略的细节。结果页把本轮使用的阈值显示出来运营人员可以快速判断“为什么规则数量变多或变少”。除此之外无规则时也要给出明确提示并建议降低阈值而不是让页面显示一张空表。5.3 页面、路由和算法层的职责边界三层职责简单总结如下模板层只负责展示不处理数据。路由层负责参数解析、文件读取、调用算法函数、捕获异常。算法层负责清洗、转换、挖掘和规则筛选不关心文件是从哪里上传的。这样的边界让代码更容易测试。关联规则挖掘逻辑可以在没有 Flask 的情况下单独跑Web 层哪怕更换模板或前端框架也不会影响核心算法。6. 运行验证与参数调试支持度、置信度、提升度怎么调才合理6.1 启动服务并验证完整流程在项目根目录执行python app.py浏览器访问http://127.0.0.1:5000选择data/market_data.csv上传填入适当阈值点击开始分析。正常流程如下服务读取上传文件。load_transactions校验列名并读入 DataFrame。build_basket生成事务矩阵返回值是一个行列结构清晰的 0/1 表。generate_rules挖掘频繁项集并筛选规则。结果页展示规则列表包括前件、后件、支持度、置信度和提升度。如果启动后页面无响应打开终端检查 Flask 是否输出访问日志如果上传后返回 500查看异常信息是否为列名不匹配或编码错误。6.2 三个阈值之间的关系与调节方法阈值调参是这个项目的核心操作。三个参数并不是越大越好也不存在万能固定值需要结合数据规模、业务目标和结果数量来调整。参数默认参考调大后的效果调小后的效果常见应用场景支持度0.01 ~ 0.05规则数量减少只保留高频组合规则数量增加会混入低频组合高频商品组合剔除长尾置信度0.30 ~ 0.60规则更可靠但可能过少规则更多但可靠性降低促销捆绑、货架关联摆放提升度1.0 ~ 1.5只保留强关联规则少规则多但很多是无意义关联挖掘交叉销售机会实际调试顺序建议是先大致确定支持度使频繁项集数量在几十到几百条之间。再设置置信度过滤掉那些前件成立但后件经常不成立的情况。最后用提升度去掉低于 1 的负相关和接近 1 的弱相关规则。这里有一个常见的误区如果一上来就把支持度设成 0.5可能一条规则都得不到。因为同时购买两个商品的订单数往往远小于单独购买某一个商品的订单数。越大的数据集支持度阈值可以设得越小几万条订单时0.01 是常见起点几百条订单时则要从 0.1 左右尝试。6.3 规则结果如何反哺业务决策拿到规则表之后分析并没有结束。把规则翻译成业务语言才是关联规则分析的价值所在。以规则全脂牛奶 - 面包为例支持度 0.03 表示两者同时出现的订单占全部订单的 3%置信度 0.55 表示买全脂牛奶的人里有 55% 也会买面包提升度 1.63 表示这个关联比随机情况高出 63%。业务上可以这样应用将高支持度高置信度的规则用于货架摆放把关联商品放置在相邻位置。将高提升度规则用于套餐设计和促销组合采用“买前件减后件价格”的方式提升客单价。将规则结果整理成 Excel 报表供运营人员做品类管理不必每次都跑算法。需要注意的是关联规则只是发现“一起买”的现象并不能证明因果。牛奶和面包一起买也可能只是早餐场景造成的共现不能说“牛奶导致了买面包”。因此业务解读时要结合常识避免过度解读。7. 常见报错、生产化建议与后续扩展7.1 高频问题排查表在实际运行过程中下列问题比较常见问题现象可能原因检查与解决方式UnicodeDecodeError文件编码不是 UTF-8而是 GBK 等用encodinggbk或先用编辑器转换编码报错“缺少必需列”CSV 列名不一致或使用了中文表头统一列名为TransactionID和Product并在代码中做校验运行后无规则支持度、置信度设得过高调低支持度和置信度先输出频繁项集数量观察结果ValueError: The truth value of a DataFrame is ambiguous对多行 DataFrame 做了if df判断使用df.empty或len(df) 0判断空表上传 10MB 文件时直接失败Flask 的MAX_CONTENT_LENGTH限制调大限制或改为异步任务处理大文件结果中出现frozenset({...})对象mlxtend 的antecedents是 frozenset展示时用list(row[antecedents])转成商品列表排查顺序通常从数据开始。先确认文件能正常读取再确认事务矩阵维度是否符合预期然后才去检查算法阈值。如果矩阵行数等于订单数、列数等于商品种类数说明预处理通过问题往往出在阈值设置上。7.2 从本地项目走向生产环境的改造点这篇文章里的项目是典型的学习型跑通方案进入生产环境还需要做以下改造。第一分析任务异步化。当交易流水到达几百万行时在 Flask 请求里同步执行 Apriori 会卡住几十秒甚至更久。生产环境建议使用 Celery 或 Redis 队列把上传和分析拆成两个异步任务页面先返回“分析中”状态完成后通知用户查看结果。第二数据源切换。不要让运营人员重复上传 CSV而是让程序直接从 MySQL、PostgreSQL 或数据仓库读取订单数据用 SQL 提前完成交易明细聚合减少传输和内存开销。第三缓存频繁项集结果。同一批数据在几组阈值下反复分析频繁项集计算成本很高。可以先把支持度较低时得到的频繁项集缓存下来后续调整置信度和提升度时直接从缓存中筛选规则无需重新运行 Apriori。第四增加权限与审计。生产环境要记录谁上传了数据、使用了什么参数、生成了多少规则。Flask-Login 做登录管理操作日志写入数据库防止误操作和分析结果被随意覆盖。7.3 后续可以扩展的分析方向这个项目完成之后可以沿着几个方向继续深入。算法层面当数据量变大时Apriori 会产生大量候选项集性能明显下降可以换成 FP-Growth 算法它通过构建 FP 树压缩数据不需要反复扫描数据库。分析维度层面可以加入时间维度分析工作日和周末的关联规则差异或者按季度观察商品组合变化也可以引入客户维度区分会员和非会员的购买习惯。产品形态层面可以为每个商品设计“你可能会喜欢”推荐模块把规则映射成推荐结果在下单页、结算页推荐关联商品这就从“分析工具”进化成了“推荐系统”。可视化层面规则集合并不仅仅适合表格展示商品的频繁项集和强关联关系非常适合用网络图展示其中节点是商品连线粗细表示支持度或置信度。可以结合 ECharts 或 D3 把规则图画出来运营人员可以更加直观地发现商品组团。关联规则分析的核心价值不在于把 Apriori 跑通而在于把业务数据转换成可操作的商品组合建议。从这个 Flask 项目开始逐步完善数据质量、参数调优、异步任务和可视化最终会形成一套能够支撑运营决策的迷你数据产品。