尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

关联规则挖掘实战:R语言arules包从原理到调参全解析

关联规则挖掘实战:R语言arules包从原理到调参全解析 关联规则这四个字听起来像是某个数据库课上的冷门概念但只要你在真实业务里做过一次商品推荐、用户行为路径分析或者运营策略优化就会发现它其实非常接地气。我最早接触这个概念是在一次零售数据项目里甲方拿着几百万行订单明细问买了A的人还会买什么当时数据清洗和描述性统计已经做到吐直到用R里的arules包跑出第一批关联规则才真正体会到什么叫数据自己会说话。这篇文章我会把关联规则从原理到实操完整过一遍不绕弯子。你会看到它到底在解决什么问题、R语言里最顺手的实现路径是什么、支持度置信度提升度这三个核心指标怎么调参才靠谱以及我在真实项目中踩过的那些坑。无论你是刚开始学R的数据分析新人还是正在做用户行为分析的从业者这篇都能直接当作一份可落地的操作手册来用。1. 关联规则到底在算什么账1.1 从啤酒与尿布说起它真正想表达的逻辑几乎所有讲关联规则的文章都会提啤酒与尿布这个经典案例超市发现周末晚上买尿布的年轻父亲顺手买啤酒的概率明显偏高于是把这两样货架摆在一起销量双双上涨。这个故事流传太广以至于很多人把它当成单纯的段子。但从数据分析的角度看它真正想表达的是我们能不能在大量交易记录里自动找到那些看起来毫不相关、实际却在同一批订单里反复出现的商品组合。这就是关联规则的核心目标——挖掘频繁项集然后从中提炼出有业务价值的规则。1.2 关联规则要回答的问题关联规则的形式很简单就是一条形如A → B的逻辑表达式意思是当用户买了A的时候往往也会买B。A叫前件B叫后件。它跟我们平时做的相关性分析、回归分析有本质区别。相关性分析看的是两个变量之间的线性关系回归分析看的是因变量受哪些自变量影响而关联规则处理的是事务型数据——也就是一条条包含多个物品的记录。比如一个订单里同时包含全脂牛奶、面包、黄油这就是一条事务。它的核心不是比较多少而是看共现的形态。所以关联规则特别适合回答这几类问题电商场景购物车里的常客组合有哪些运营场景完成路径A的用户接下来最可能完成路径B风控场景哪些行为特征经常在同一批异常事件里同时出现医疗场景哪些诊断指标经常一起异常1.3 关联规则的适用边界不过我得说句掏心窝的话关联规则不是万能的。它做的是找关系的工作而不是验证因果的工作。哪怕你很严谨地把支持度和置信度都调得很高也只能说从数据上看这两个东西确实经常一起出现至于为什么会出现需要通过业务逻辑去解释。另外关联规则对数据形态有要求数据必须能整理成事务格式。如果你手里只有一张宽表每行是一个用户、每列是一个指标那更适合先做聚类、因子分析这类工作而不是直接套关联规则。这也是很多人第一次用arules包时遇到的第一个坎——数据格式对不上。2. R语言分析的前置准备与事务数据转换2.1 为什么选R来做关联规则在分析生态里能做关联规则的工具有不少Python的mlxtend、商业软件SPSS Modeler都行。但R在这个领域有个天然优势arules包把整套流程封装得极其完整从事务数据构建、频繁项集挖掘、规则生成、质量评估到可视化一站式搞定。arules包的核心函数只有几个read.transactions()读数据、apriori()跑Apriori算法、eclat()跑频繁项集挖掘、inspect()查看规则、sort()排序、subset()筛选。看起来简单但每步都有讲究。2.2 把订单数据变成事务对象这是整个分析流程里最容易劝退新手的一步。arules包要求数据必须是transactions类的对象也就是一个稀疏矩阵每一行代表一个订单每一列代表一种商品某订单里包含该商品则记为TRUE否则为FALSE。用代码说更清楚。假设你手上的原始数据是这样一个数据框两列order_id订单ID和item商品名head(df) # order_id item # 1 10001 全脂牛奶 # 2 10001 面包 # 3 10001 黄油 # 4 10002 酸奶 # 5 10002 苹果直接把这个数据框丢进apriori()是不行的必须先转成事务对象。最常见的做法是用split()把商品按订单ID聚合成一个列表再转成transactionslibrary(arules) # 按订单分组得到每个订单的商品列表 trans_list - split(df$item, df$order_id) # 转成事务对象 trans - as(trans_list, transactions) # 或者更直接一点读完就转 trans - read.transactions( file orders.csv, format single, # 每行一个订单-商品对 sep ,, cols c(order_id, item) # 第一列订单号第二列商品名 )read.transactions()写起来更省事format single表示每行只有一条记录format basket则适用于每行一个购物篮不同商品用分隔符隔开的情况。转完之后你可以看一眼数据的整体情况summary(trans)它会输出事务总数、商品种类数、每个订单包含商品数量的分布以及最常出现的商品列表。这一步非常值得花时间看因为后续支持度的设定很大程度上就取决于这批商品的基础出现频次。2.3 最省心的练习数据Groceries如果你手头暂时没有合适的业务数据arules包自带一个叫Groceries的数据集——9835条交易记录、169种商品来自一家真实杂货店的订单是练习关联规则的标准素材。data(Groceries) summary(Groceries) # transactions in sparse format with # 9835 transactions (rows) and # 169 items (columns)这个数据集的好处在于它足够真实、规模适中、稀疏度也符合典型的零售场景跑起Apriori算法来既不会慢到让人等崩溃也不会快到失去体感。后文所有示例我都会用它来演示。3. Apriori实操支持度、置信度、提升度到底怎么调3.1 三个指标的计算逻辑很多人看文档时记不住这三个指标我习惯用一个特别朴素的类比想象你是一个超市经理手里捏着一张鸡蛋→面包的规则你会问三个问题有多少顾客同时买了鸡蛋和面包——这是支持度衡量规则的覆盖面。分子是同时包含前件和后件的订单数分母是总订单数。在买了鸡蛋的顾客当中有多大比例也买了面包——这是置信度衡量规则的可靠程度。它是P(面包|鸡蛋)即条件概率。买了鸡蛋之后买面包的概率跟所有人买面包的概率相比是变高了还是变低了——这是提升度衡量规则的增量价值。提升度是三个指标里最有趣的因为它能帮你区分正向关联和负向关联。公式是$$ lift(A \to B) \frac{P(A \cap B)}{P(A) \times P(B)} $$如果提升度大于1说明A、B正相关买A确实会带动买B如果小于1说明二者负相关买A反而可能抑制买B如果等于1说明这俩基本独立规则没有意义。3.2 在Groceries上跑第一条规则先装上并加载包然后直接跑apriori()install.packages(arules) library(arules) data(Groceries) rules - apriori( Groceries, parameter list( support 0.01, # 支持度阈值约98个订单 confidence 0.5, # 置信度阈值 minlen 2, # 规则至少要包含2个商品 maxlen 5 # 规则最多包含5个商品 ) )跑完你会看到类似这样的输出Apriori Parameter specification: confidence minval smax arem aval originalSupport maxtime support minlen maxlen target 0.5 0.1 1 none FALSE TRUE 5 0.01 2 5 rules Algorithmic control: filter tree heap memopt load sort verbose 0.1 TRUE TRUE FALSE TRUE 2 TRUE Absolute minimum support count: 98 set item appearances ...[0 item(s)] done [0.00s]. set transactions ...[169 item(s), 9835 transaction(s)] done [0.01s]. sorting and recoding items ... [120 item(s)] done [0.00s]. creating transaction tree ... done [0.01s]. checking subsets of size 1 2 3 4 5 done [0.01s]. writing ... [42 rule(s)] done [0.00s].42条规则这个规模刚刚好既有内容可看又不至于眼花。3.3 结果解读的正确姿势用inspect()查看规则内容inspect(rules[1:10])你会看到一张表列分别是lhs前件、rhs后件、support支持度、confidence置信度、coverage前件覆盖率、lift提升度、count同时出现订单数。这里有个特别容易忽略的细节输出的前几条规则看起来可能毫无信息量。比如某商品→另一常见商品这种高频组合。所以看规则的时候别按出现顺序扫最好按提升度排序优先看那些真正的增量关联rules_sorted - sort(rules, by lift) inspect(head(rules_sorted, 10))这么一排规则质量就体现出来了。3.4 阈值设置的业务直觉每次讲参数调优总有人问支持度到底该设多少置信度到底该设多高我的回答永远是先看数据规模再看业务目标最后才是经验值。支持度过低会挖出一堆频次极低、偶然共现的规则噪声大支持度过高会漏掉那些小频次但高价值的商品组合。一个简单的参考思路先看最常出现的商品占总订单的比例以这个比例的10%到20%作为支持度下限。置信度代表前件出现时后件的条件概率一般取0.5以上比较有说服力但也要看场景。比如在风控里哪怕置信度只有0.2只要提升度高这条规则也值得关注。另外强制建议设minlen 2。如果不设算法会把一个独立商品本身也输出成规则那种规则没有任何业务价值。4. 规则太多怎么办质量评估与冗余处理4.1 提升度不是万能的实话说按提升度排序能解决一部分规则重要性问题但提升度也有它的局限。它衡量的是A和B的关联强度却无法告诉你这个关联是因为A而B还是因为B而A。举个例子假设奶粉→奶瓶和奶瓶→奶粉两条规则的实际支持度、置信度、提升度完全对称但业务含义完全不同。前者是买了奶粉之后可以推奶瓶后者是买了奶瓶之后可以推奶粉对应的是两种完全不同的推荐时机。所以分析的时候建议定向筛选如果你只关心买了X之后会买什么就把X放到前件里做约束筛选# 只保留前件里包含全脂牛奶的规则 milk_rules - subset(rules, items %in% whole milk) inspect(head(milk_rules, 10))subset支持更灵活的语法比如# 前件包含酸奶或后件包含黄油 subset(rules, lhs %in% yogurt | rhs %in% butter)4.2 冗余规则看起来不同实则信息重复这是我在实际项目里被坑得最惨的一次。当时跑完几万条规则兴冲冲拉着业务方去看结果结果大家发现大多数前件不同的规则实际指向的是同一个结论。比如全脂牛奶 → 黄油和全脂牛奶面包 → 黄油看起来是两条规则但后者的信息完全被前者覆盖了——既然买全脂牛奶的人本来就有较高概率买黄油多一个面包作为条件并没有带来额外增量。这种规则在挖掘结果里大量出现会让规则集变得异常臃肿严重影响后续人工判断。arules包提供了is.redundant()函数可以直接识别这类规则redundant_rules - is.redundant(rules) rules_pruned - rules[!redundant_rules] # 移除冗余后数量明显减少 rules_pruned这个函数判断冗余的思路很直接如果一条规则的置信度、提升度并不比它更简化的父规则更好就认为它是冗余的。跑完这一步规则集通常能瘦身一大半质量却一点不会掉。4.3 其他值得关注的度量指标arules还输出了几个不那么常提但很有用的指标coverage前件的覆盖率等于P(A)。在排序时我们可以对照支持度来看避免被极高置信度但极低覆盖率的组合误导。count前件和后件同时出现的绝对订单数。样本量很小的时候即使支持度达标也要谨慎因为统计意义上的稳定性存疑。进阶用户还可以用interestMeasure()函数计算额外的度量比如卡方值、杠杆率leverage、确信度conviction。我重点想说的是杠杆率$$ \text{leverage} P(A \cap B) - P(A) \times P(B) $$杠杆率衡量的是观察到的共现概率比假设二者独立时的期望概率多了多少。取值范围是[-0.25, 0.25]值越大说明正向关联越强。和提升度比它更直观而且不容易被极端小概率事件带偏。quality(rules)$leverage - interestMeasure(rules, measure leverage, transactions Groceries) rules_sorted_leverage - sort(rules, by leverage) inspect(head(rules_sorted_leverage, 10))5. 可视化关联规则不是列出来就行5.1 用散点图快速感受规则分布规则本身是几十上百条靠inspect()一条条看几万条的时候看得眼冒金星也看不出整体结构。好在我们还有arulesViz包它为关联规则可视化提供了相当完整的方案。install.packages(arulesViz) library(arulesViz) plot(rules)默认画出来的是散点图x轴是支持度y轴是置信度点的颜色代表提升度。这张图非常适合做整体判断——大多数规则会聚在支持度低、置信度高的区域颜色越深的点越值得看。5.2 网络图与平行坐标图如果想看规则之间的关联关系可以用网络图plot(head(sort(rules, by lift), 20), method graph)图上每个点是一个商品箭头表示规则方向点的大小代表支持度颜色代表提升度。这种图在和业务方沟通时特别好用一眼就能看出整个商品网络里的核心节点和关键路径。另一个启发式很强的图是平行坐标图plot(rules, method paracoord)平行坐标图把每条规则画成一条折线从左到右的节点依次是前件中的商品、后件中的商品。当规则量特别大的时候能清楚看到哪些商品反复出现在规则链路的中间位置。可视化这一节我不打算展开讲更多因为重点还是算法和调参。但有一条建议想给大家可视化不是最终交付物而是用来帮你找到值得深入看的规则的过程工具。最终给业务方的永远应该是经过筛选、验证、有业务解释的几十条核心规则而不是一张密密麻麻的图。6. 实战中踩过的坑与选型建议6.1 坑一没有处理订单ID和商品名的格式问题看起来像是小事但真实项目里经常出问题。比如有些订单ID是数字有些是带字母的结果在转换事务时它们被当成不同类型直接报错或者商品名里有空格、大小写不一致导致同一个商品被拆成了两个项。解决办法很简单在导入R之后立刻用as.character()统一格式商品名统一去除首尾空格。先用unique()检查商品个数再转事务对象。这个检查习惯能帮你省下一个小时的排查时间。6.2 坑二关联规则不等于因果这句我在前面提过一次但值得专门拿出来说。Apriori算法输出的只是共现程度的统计描述它不区分是谁引起了谁也不告诉你背后是否存在混淆因素。我当时在一个电商项目里挖到一条规则高额优惠券 → 高退货率提升度接近2业务方差点拿着这个去砍优惠券预算。结果进一步分析发现这两个行为背后真正的驱动因素是高客单价品类而高客单价品类本身退货率就偏高。如果不剥离品类因素就会得出完全错误的运营结论。所以在给业务方汇报前建议对每一条核心规则做一次反向验证控制掉可能的干扰因素之后这层关系还在不在。6.3 坑三数据量很大时Apriori可能力不从心Apriori算法本质上要反复扫描事务列表并逐层组合频繁项集。当商品种类数上万、订单量过百万时运行时间会膨胀得难以接受。这种情况下我会考虑两个替代方案第一先做项集压缩。把出现频次过低的商品直接从事务里去掉因为它们几乎不可能成为有价值的频繁项集# 找出出现频次前50的商品过滤其余 item_freq - itemFrequency(Groceries) keep_items - names(tail(sort(item_freq), 50)) trans_filtered - Groceries[, keep_items]第二改用Eclat算法。Eclat基于垂直数据格式计算交集不需要反复扫描全部事务在密集数据上往往比Apriori快很多itemsets - eclat( trans_filtered, parameter list(support 0.02, minlen 2) ) inspect(itemsets[1:10])需要注意eclat()默认输出的是频繁项集而不是规则需要进一步用ruleInduction()生成规则rules_from_eclat - ruleInduction( itemsets, transactions trans_filtered, confidence 0.5 )6.4 选型建议什么时候用关联规则最划算跟其他分析方法对比着看会更清楚你想掌握每个商品的基础销量和占比 → 用描述性统计你想知道哪些用户特征会导致高消费 → 用回归或决策树你想做用户分群 → 用聚类你想知道买了X的人还买了什么 → 关联规则这时候它最合适尤其是那些没有明确预测目标只想知道数据里有哪些稳定的共现结构的场景关联规则几乎是不可替代的。写在最后关联规则这个工具优点和缺点都很鲜明。优点是它不依赖标签数据只要有事务记录就能跑缺点是结果里噪声多、冗余多特别考验分析师的筛选能力和业务判断力。我个人这几年的体会是别被技术名词唬住也别把输出规则当作终点。关联规则真正的价值是给业务方提供一个值得进一步验证的候选清单。把规则找出来只是第一步之后的筛选、解释、验证才是真正拉开分析师之间差距的地方。最后分享一个小技巧每次跑完关联规则我都习惯把核心规则导出成CSV加上业务解读和是否已验证这两列再发给业务方。这样既方便他们快速反馈也方便后续追踪每条规则的落地效果。这种工作习惯看起来不起眼但能让分析结果真正推动业务决策而不是停在报告里。
返回列表