ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

R语言关联规则实战:从零开始掌握arules包全流程

R语言关联规则实战:从零开始掌握arules包全流程 零基础也能跑通的R语言关联规则实战如果你用R做数据分析总会碰到一类需求翻遍销售订单、购物车、医疗处方、用户行为日志想找出“买A的人还买了什么”、“开了这种药的人常同时开哪种药”、“看完这篇教程的人接着会看哪篇”。这类问题的正规解法就是关联规则Association Rules而R语言里的arules包几乎是最常用的工具。这篇文章不扯高深理论直接从实际案例出发把关联规则从数据准备、参数调优到结果解读、可视化输出整个链路讲透适合刚接触R的初学者也适合想系统化整理关联规则流程的从业者。先说清楚关联规则到底解决什么问题。它不关心因果只关心“同时发生”的规律性。超市里尿布和啤酒放在一起不是尿布导致买啤酒而是数据里这两件事频繁一起发生。R语言做这件事的典型路径是读入事务数据转成transactions格式用apriori()生成规则再按支持度、置信度、提升度筛选最后可视化或导出结果。这条路看起来很顺但实际跑起来每一步都有坑。我这次用的是自己模拟的一份电商订单数据共1万条订单每条订单包含若干商品类别目标是找出强关联组合。所有代码都是tidyversearulesarulesViz的组合。下面按实际操作的顺序写尽量把所有可能踩到的坑都标出来。1. 数据准备事务型数据怎么整理才不踩坑关联规则对输入格式要求很严格它不是普通的data.frame而是transactions对象。很多人第一道坎就卡在这里明明数据长得很规整一运行apriori()就报错“cannot coerce class ‘c(‘tbl_df’, ‘tbl’, ‘data.frame’)’ to ‘transactions’”。原因很简单arules包只认特定格式你需要把数据整理成它认得的形态。最常见的原始数据长这样一列是订单号一列是商品名同一个订单号对应多行商品。比如order_id循环出现item列是商品。这种叫“长格式”数据。另一种叫“宽格式”数据每行是一个订单商品用多列0/1或TRUE/FALSE表示。不管哪种格式核心目标是最终得到一个transactions对象。从长格式转成transactions标准做法是library(arules) library(dplyr) # 模拟一份长格式数据 set.seed(42) orders - tibble( order_id rep(1:10000, sample(1:5, 10000, replace TRUE)), item sample(c(牛奶, 面包, 鸡蛋, 黄油, 奶酪, 苹果, 香蕉, 啤酒, 尿布, 薯片), sum(sample(1:5, 10000, replace TRUE)), replace TRUE) ) orders - orders %% distinct(order_id, item) # 按订单分组把商品合并成一行 orders_list - split(orders$item, orders$order_id) trans - as(orders_list, transactions)关键在于split()后得到的列表每个元素是一个订单里的所有商品向量。再用as(..., transactions)强制转换。这里有个容易忽略的细节同一个订单里若出现重复商品一定要先去重否则会变成“重复购买”的假信号。超市数据里重复购买很常见但大多数场景下关联规则默认关注的是“是否购买”不是“购买几次”。要不要去重要看业务问题。如果研究的是“哪些商品经常被同时加入购物车”就应该去掉重复行如果研究的是“哪些商品多次购买与哪些商品相关”那就要保留数量信息但这时用transactions就不合适了改用tidytable或者加权事务更合理。转成transactions后先别急着跑算法务必做两步检查。一是用inspect(head(trans, 10))看看数据是否被正确解析二是用size(trans)查看每个订单里商品数量的分布。比如summary(trans)会输出事务数量、商品种类数、每条事务的平均商品数等关键信息。实测中很多诡异结果都源于数据没洗干净比如订单里混进了空值、商品名前后有空格、同一个商品在不同记录里叫法不一致这些都会让算法产生一堆垃圾规则。还有一类常见情况原始数据不是订单明细而是已经聚合好的“订单-商品”布尔矩阵。比如每行是一个用户每列是一个商品类目值为0/1。这种情况下as(df, transactions)同样可以转换但要求数据全部是0/1或者logical类型。如果矩阵里有数值型比如购买数量转换时会被当作“商品是否存在”数量大于等于1都变成TRUE小于等于0都变成FALSE这通常不是你要的效果。所以遇到布尔矩阵必须先确认数据语义再决定要不要转换。数据准备工作做到这里就完成了关联规则建模前最关键的一步。很多人觉得关联规则难难在算法参数其实多数问题出在数据格式上。数据格式不对后面的一切都是空中楼阁。2. 算法原理与参数选择支持度、置信度、提升度到底怎么定关联规则算法最核心的是apriori()函数它做的事情本质上就是“先找频繁项集再生成规则”。频繁项集就是出现次数达到阈值的商品组合比如“牛奶”出现5000次“牛奶面包”出现3000次如果最小支持度设为0.2也就是总事务的20%那么“牛奶面包”这个组合如果10000笔订单中出现3000次支持度0.3就满足条件。apriori()里最关键的三个参数是support、confidence、minlen。支持度表示一个项集在所有事务中出现的概率置信度表示在前提出现的条件下结论同时出现的概率提升度表示规则的相关性强度大于1说明正相关小于1说明负相关等于1说明相互独立。三个指标各管一段但实际调参时它们不是孤立的。rules - apriori(trans, parameter list(support 0.01, confidence 0.5, minlen 2))这里support 0.01表示支持度阈值1%即某商品组合至少在1%的订单中出现才被纳入候选。confidence 0.5表示置信度阈值50%即前提出现时结论至少有50%的概率也会出现。minlen 2表示规则至少包含两个项不然会出现“{} {牛奶}”这类没有前提的规则虽然数学上合法但对业务决策毫无意义建议日常分析都加上minlen 2。参数取值没有通解完全取决于数据规模和业务目标。我自己的经验是销量大、品类少的场景支持度阈值可以适当提高比如0.05或0.1否则规则数量爆炸且基本都是常识性组合品类极多、订单分散的稀疏场景比如上千个SKU支持度阈值可能需要降到0.005甚至更低不然一条规则都挖不出来。置信度通常取0.5到0.7之间太低规则不可靠太高规则数量过少又找不到意外发现。提升度不用设阈值它在筛选和解读阶段用。调参的另一个要点是不要只跑一次。我在实际项目里习惯的做法是先设一个较低的支持度和置信度跑一次查看规则数量再逐步收紧阈值观察规则数量的变化曲线。比如先跑support 0.001, confidence 0.3得到大量规则然后逐步提高支持度和置信度观察规则数量下降的趋势最终定一个“规则数量几百条、质量可控”的参数组合。这个方法比拍脑袋定一个值靠谱得多。# 查看规则数量与参数的关系 for (s in c(0.001, 0.005, 0.01, 0.02, 0.05)) { r - apriori(trans, parameter list(support s, confidence 0.5, minlen 2)) cat(support:, s, 规则数量:, length(r), \n) }跑完这一圈你就对数据规律有了直观判断如果support调到0.01时规则数量还有几千条通常说明商品类目之间关联紧密或者数据里存在太多高频商品如果support到0.02时一条规则都没有说明数据太稀疏或者订单间差异化太大。这时再结合业务目标确定最终参数就有据可依了。除了这三个参数apriori()还有一个经常被忽略的maxlen参数用来限制规则的最大长度。默认值是10但超长规则往往难以解释比如“牛奶、面包、鸡蛋、黄油、奶酪、苹果、香蕉、啤酒、尿布、薯片”这种10项规则业务上几乎没有落地价值。实际项目中我一般把maxlen设为3或4也就是最多“三样东西同时出现”或“四样东西组合出现”。规则越短解释成本越低落地可能性越高。还有一点必须强调关联规则挖掘是数据驱动的方法挖掘出的规则并不代表因果关系。可能你会发现“尿布与啤酒”有强关联但这不是因为尿布导致啤酒购买而是两者都跟“年轻爸爸”这个隐含因素相关。所以在解读时要谨慎不要轻易下因果结论。实际业务里更建议把关联规则结果当作用户细分、交叉销售、布局优化、推荐策略的启发式输入而不是唯一依据。3. 规则生成与筛选从原始规则到可用决策跑完apriori()后得到一个rules对象。千万别直接inspect(rules)看全部规则规则数量可能成千上万肉眼根本看不过来。标准做法是先排序、再筛选。排序用的指标是lift提升度它衡量的是“在已知前提的条件下结论出现的概率比无条件出现概率高多少”。提升度越高说明规则中的商品组合越可能带来“超预期”的共现也就越有业务价值。另一种常用排序方式是按置信度降序排列适合看“前提出现时结论大概率出现”的规则。rules - arules::sort(rules, by lift, decreasing TRUE) inspect(head(rules, 20))但光看head还不够实际分析中我倾向于按目标商品做定向筛选。比如业务方只关心“购买牛奶的用户还会买什么”这时就用subset()限定rhs为“牛奶”再按提升度排序milk_rules - subset(rules, rhs %in% 牛奶) inspect(head(milk_rules, 20))subset()函数是处理规则集最灵活的工具。它有lhs左项即前提、rhs右项即结论、items所有项、confidence、support、lift等字段可以组合条件。比如想找“前提包含啤酒或尿布结论包含其他商品且提升度大于2”的规则target_rules - subset(rules, lhs %in% c(啤酒, 尿布) lift 2)注意%in%的用法它表示项集合中出现任意一个就算匹配而不是所有项都要匹配。这个细微差别很容易被忽略在代码里调试半天才发现条件写错。有了定向筛选接下来要会看规则的具体内容。inspect()输出的规则格式是{前提} {结论}后面跟着四个数字support、confidence、coverage、lift。coverage是前提的支持度表示前提在所有事务中出现的比例。它常被忽略但在判断“规则应用范围”时很关键。一条规则提升度再高若coverage只有0.001说明这个前提只覆盖0.1%的订单实际业务价值有限。所以筛选规则时不能只盯着提升度要结合support和coverage一起看。一个我自己经常用的筛选策略是先按lift排序取出前200条再看这些规则里哪些coverage较高把“又强又常见”的组合优先挑出来。这类规则才是能直接落地的比如“买锅的人通常也买锅铲”如果前提覆盖率高就可以在商品详情页做关联推荐。另外用quality()函数可以单独提取规则的指标矩阵方便导出到Excel或做进一步分析quality_df - as.data.frame(quality(rules))但要注意quality()返回的矩阵里不包含规则的项信息所以如果要把“前提、结论、支持度、置信度、提升度”完整导出需要用DATAFRAME()函数rules_df - as(rules, data.frame)这个as()转换会生成包含rules列如“{牛奶,面包} {黄油}”和support、confidence、lift等指标列的数据框直接导出CSV就能给业务同事看。在导出前也可以自己拼一列规则标签方便筛选。进一步实操中我还发现一个坑对不同时间段的数据分别跑关联规则时规则格式可能不一致比如商品名称里多了空格、或者大小写不同导致合并时出现重复。建议前期清洗时统一编码规则比如统一大写、去空格或者建商品别名表。这样后面所有分析都对齐省很多麻烦。4. 结果可视化一眼看出哪些组合值得关注规则数量太多时表格根本看不过来可视化是必备手段。arulesViz包里有几种经典画法按场景选择就行。第一种是散点图横轴是support纵轴是confidence点的大小或颜色表示lift。这种图适合看规则的总体分布快速识别哪些规则在高支持度高置信度区域聚集library(arulesViz) plot(rules, engine ggplot2)实际运行时如果想画交互式图可以用engine htmlwidget会生成一个可缩放、可点击的html页面很适合做汇报展示。但注意规则数量超过几千条时htmlwidget渲染会非常卡所以先筛选到几百条再画。第二种是分组矩阵图特别适合展示规则集的结构关系plot(rules, method grouped, engine ggplot2)这种图用矩阵的形式展示规则左项和右项的对应关系颜色深浅表示提升度或置信度。它最大的好处是能快速看出哪些商品组合是“高频强关联”的簇。比如所有含“啤酒”的规则都聚在一起且颜色偏深说明啤酒相关的组合普遍强关联值得重点关注。第三种是网络图适合作关联探索plot(rules, method graph, engine htmlwidget)网络图里点是商品边是规则边的颜色或粗细表示规则强度。它的视觉效果最强但规则多时图会乱成一团所以务必在规则数量筛选到100到200条以内再画否则只能看出一团乱麻。可视化不只是为了好看更重要的是辅助筛选。我自己经常先画散点图观察支持度与置信度的分布形态。如果大部分规则集中在低支持度区域说明数据稀疏需要调低支持度阈值如果大量规则集中在超高置信度区域可能是数据里存在大量规则性极强的重复模式这时候反而要小心可能某些商品组合是套餐绑定的结果并非自然购买关联。有一类可视化常见问题图中出现“空心点”或者异常点。这通常是因为规则中存在重复项或相同项的规则比如“{牛奶} {牛奶}”这种自关联。minlen 2只能保证规则至少有2个项但lhs和rhs的内容可能重叠比如{牛奶, 面包} {牛奶}虽然数学上有意义但在可视化中会干扰图形表达。若发现结果异常可以用is.redundant()检查冗余规则并在必要时剔除。# 检查冗余规则 redundant - is.redundant(rules) rules_pruned - rules[!redundant]is.redundant()的判定逻辑比较复杂它基于规则推断理论简单理解就是如果一条规则的结论信息已经被另一条更简单或更强的规则包含它就是冗余的。日常分析中剔除冗余规则能大幅减少重复信息提升结果清晰度但也要注意别把有业务价值的规则误删建议先筛选再剔除。5. 常见问题与排查技巧实录关联规则实战中至少一半时间会花在排错上。我把经常碰到的问题列个速查表按出现概率排序。第一个问题转换transactions时报错。场景是as(df, transactions)或as(list, transactions)时数据格式不对。排查思路是从str()看数据结构是不是data.frame是不是listlist的元素是不是向量有没有空值、NULL商品列是不是因子类型有时候data.frame转成list之后会自动变成data.frame的list而不是向量的list这会导致转换失败。第二个问题规则数量与预期的数量差异过大。常见原因主要是阈值设置不对或数据清洗不全。比如数据里有很多高频商品它们会产生大量规则但真正有业务价值的组合比想象中少。此时可以调高支持度或者筛选特定的lhs、rhs。第三个问题inspect()输出乱码。这通常出现在Windows中文环境下R控制台默认编码跟数据编码不一致。解决办法是在读取数据或转换数据时明确指定编码或者在数据清洗阶段把商品名统一转成UTF-8绕开中间环节的编码干扰。第四个问题画图时卡死或内存爆炸。尤其是plot(rules, method graph)时规则上万条内存直接爆掉。解决办法是先筛选、再剔除冗余、最后控制规则数量在几百条内。第五个问题规则结果在业务上“废话连篇”。比如“买牛奶的人多半会买面包”这类常识性规则业务方看了完全没有新鲜感。这种情况不是代码问题而是业务目标不明确。我处理这类问题的办法是一开始就设置排除方向。比如只关注“xx组合”这类特定类型的规则用subset(rules, lhs %in% 某个品类 !(rhs %in% 某个大热品类))来过滤掉高频常识组合。或者在数据预处理阶段把明显捆绑销售、固定套餐的商品先拆开或标注避免算法把它们当成强关联。第六个问题时间窗口混乱。关联规则对时间不敏感如果不加时间标签跨季数据混在一起会产生误导性规则。比如夏季饮料和游泳圈的关联跟冬季火锅底料和羊肉卷的关联完全不同如果用一整年数据挖掘可能得到“啤酒与尿布”这种看似有趣但无法指导当前季节策略的规则。解决方式有两种一是按月份或季度拆分数据分别跑规则再对比二是在数据里增加时间字段用subset()筛选特定时间段再跑算法。把这些问题走完一遍关联规则在你手上就不再是黑盒工具了。你可能还会遇到一些尺度更小但很磨人的问题比如商品的名称不统一、同一个语义用多个词表达“牛奶”、“纯牛奶”、“鲜牛奶”算法会把它当成三个不同商品导致支持度被严重稀释。这类问题靠代码解决不了必须在数据清洗阶段用词表合并。词表的构建可以基于商品类目、品牌、规格做层级归纳也可以直接用字符串模糊匹配工具辅助。6. 关联规则后续还能怎么扩展跑通一套基础流程后再往前走一步就是根据业务需要做扩展。一类扩展是定时重跑。关联规则的结果会随时间漂移如果不定期更新建议每个月或每个季度重跑一次比较不同周期内的规则变化。这里有个实践经验只对比“新增规则”和“消失规则”还远远不够更值得关注的是那些lift显著提升或显著下降的规则。它们往往代表用户行为正在发生结构性变化比如某个新品类起量、某个老品类衰退。把所有规则全量对比反而容易被大量噪声淹没。另一类扩展是跟其他算法结合。关联规则挖掘出的是离散商品组合但如果数据里存在连续型特征比如价格、数量、时间间隔可以考虑先把连续变量分成几个区间再一并作为事务项挖掘。比如把“购买金额高、中、低”和“购买时间在周末/工作日”纳入事务数据这样能得到“周末高金额啤酒尿布”这样更细颗粒度的规则对业务指导性更强。还有一种拓展是与推荐系统对接。用arules跑出的规则要落地到推荐场景时通常会做成一个查询接口输入已知商品集合输出关联商品及置信度排序。实现思路很简单先把规则转换成数据框再用subset匹配最后按置信度排序返回TopN。这种规则召回方法虽然不如协同过滤那么个性化但它有一个独特优势规则本身是可以解释的业务方知道推荐逻辑也方便做审核和风控。在R语言生态里关联规则相关的包还有arulesCBA基于关联规则做分类、arulesNBMiner基于NB频繁项集挖掘、recommenderlab推荐系统框架等。如果你的项目不只是“发现规律”而是要“预测行为”可以考虑往这些方向进阶。最后分享一个我个人的习惯每次跑完关联规则我都会把参数、规则数量、Top规则、业务说明写进一个固定的分析文档模板里。这样三个月后再看依然能快速想起当时为什么选这些阈值、哪些规则在业务上被验证有效、哪些规则最终被否掉。长期积累下来这套分析档案比单次结论更有价值因为你可以从规则的历史演变中看到业务变化的脉络而不只是依赖一次性输出的结果。关联规则本身不是终点它只是从数据中寻找规律的一种手段。真正有价值的是你能把这些规律翻译成业务语言并持续跟踪它是否还成立。用R语言实现这个过程并不复杂把数据、参数、筛选、可视化、复盘这几步跑熟了剩下的事情就会顺很多。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进