
简介面向电影数据分析与关联规则入门场景的Python实践项目围绕“电影数据集关联规则挖掘”主题适合学习数据挖掘、希望掌握Apriori算法并应用于影视决策的初学者。压缩包共9个文件内含2个Python脚本movie_apriori.py实现Apriori关联分析movie_download.py负责数据获取、2个Markdown说明文档含环境配置与算法解读以及5张结果示意图整体大小仅1.01MB轻量易用。资源重点演示了如何利用efficient-apriori工具包挖掘电影数据中导演与演员的关联倾向并详细对比了PyPI上多个Apriori实现的选择思路可帮助读者避开工具选型坑点。通过该资料可复现从数据下载、预处理到关联规则输出的完整流程图片直观展示频繁项集与支持度、置信度结果。目前已有2359人学习下载适合作为入门关联规则、动手实践电影数据分析的配套素材。1. 电影数据集关联规则挖掘为什么观影记录比评分更适合做购物篮分析做电影数据集关联规则挖掘时很多人第一反应是把评分当成宝但我更建议你先从用户实际看过或收藏过的记录下手。关联规则挖掘本来是从购物篮交易里找“买了A的人往往也买B”的规律放到电影场景里真正的“篮子”不是打分行为而是用户在某个时间段里消费过的影片集合。只有把数据组织成“每个用户一条记录、每条记录是一组电影ID”的事务集Apriori或FP-Growth算法才能读得懂。这套流程适合刚接触数据挖掘、手里有一份观影或评分数据、想把“用户还看了什么”落地成推荐策略的从业者。读完你会得到一条从清洗到规则评估、再到接入推荐流程的完整路径以及几个只有跑过才会懂的坑。2. 把原始观影记录变成事务集清洗去重与阈值设定2.1 先想清楚一个问题评分记录还是播放记录关联规则挖掘要求输入是事务transaction每个事务是一组同时或在一个窗口内发生的项目item。电影场景里最自然的映射是“用户在一段时间内看过的电影集合”。公开的电影数据集里最常见的原始表长这样用户ID、影片ID、行为分数、时间戳。如果你直接把所有打分行为当成事务会出现两个问题一是同一个用户对一个影片打分多次会造成重复计数二是用户给一部电影打了低分但他确实看完了这在“看过”语义里仍然是一笔有效行为。我一般会先问自己一个问题这个规则将来用在哪里如果是做“看了A片的人下一部看B片”的推荐就用播放记录或看完记录评分低于3.5的电影也可以保留在事务里因为“看完”本身就是信号。如果是做“喜欢A片的人也会喜欢B片”的推荐那么可以只保留评分4分以上的记录。两种语义下的规则结果差别很大前者偏行为共现后者偏口味相似跑出来的规则解读方式完全不同。选错语义是很多新手第一次翻车的根源。语义定下来后还有一条边界要处理时间窗口。电影不是面包用户不会在同一张收银小票里“同时购买”两部电影。常见做法是把同一个用户在一个自然周或30天内的观影记录合并成一个事务。窗口太短事务集稀疏得没法看窗口太长会把一年前看的和现在看的强行拉到同一个篮子里规则时效性变差。我的初始值一般是30天后续可以按业务节奏调成7天或90天。2.2 最小可用预处理脚本从四列原始数据到关联规则可用的事务集假设你拿到一张行为表字段包括 user_id、movie_id、behavior、timestamp其中 behavior 有 play、collect、score 三种。目标是生成一个 user_id 到电影ID列表的映射。下面这段脚本是跑通流程的最短版本import pandas as pd # 原始行为表user_id, movie_id, behavior, timestamp df pd.read_csv(user_behavior.csv, parse_dates[timestamp]) # 只保留播放行为去重同一用户重复播放同一部电影只算一次 play df[df[behavior] play].copy() play play.drop_duplicates(subset[user_id, movie_id]) # 用30天窗口切分每个用户在每个自然月内看过的一组电影 一个事务 play[month] play[timestamp].dt.to_period(M) transactions ( play.groupby([user_id, month])[movie_id] .apply(list) .reset_index(namemovie_list) ) # 过滤掉只看过1部电影的用户-月记录它们产生不了任何二元规则 transactions transactions[transactions[movie_list].apply(len) 2]逻辑说明先按行为类型筛出播放记录再用 user_id 和 movie_id 两列去重这一步解决重复播放导致的假共现。然后加一个月周期字段按用户和月份分组把当月看过的电影ID收集成一个列表这个列表就是后续算法要吃的“篮子”。最后一步过滤掉长度为1的事务因为长度为1的事务对任何二项规则都没有贡献留着只会增加内存开销。参数说明month 的分组粒度是这段脚本里最值得调的一个值。如果你想做“看完A后一周内看B”的强时效规则可以把 to_period(M) 改成 to_period(W)代价是每个事务里的电影数变少支持度普遍下降。如果你用的是评分数据而不是播放数据不需要时间窗口直接按 user_id 分组即可因为评分行为天然是一次性的。2.3 支持度与置信度的初选经验值先别迷信0.1事务集准备好后下一个要面对的就是阈值。很多人上来就设最小支持度0.1结果在电影这种长尾非常严重的数据集里跑出来的规则永远只有几个头部大片。电影数据集的稀疏程度远高于超市购物篮一个用户一个月看5到10部电影但全库可能有几万部影片单项支持度能超过0.05的影片本身就不多。所以我的习惯是先跑一次单项频率统计看数据分布再定阈值。把 transactions 表里的 movie_list 展开统计每部电影出现在多少个事务里再算一下占事务总数的比例。常见做法是把最小支持度定在“出现次数最多的那批影片的中间值”附近而不是拍脑袋写0.1。下面是经验参考值参数初选值调整方向最小支持度0.010.05规则太少就下调太多噪声就上调最小置信度0.50.6面向推荐场景可降到0.4最小提升度1.2低于1.0的规则直接视为伪关联规则长度23项超过3项解读成本高、落地难这个表不是公式是一个起点。小数据集上我甚至会从支持度0.005开始跑先看规则数量级再逐步上调。置信度0.5的意思是“看了A的人里有50%也看了B”这个值在电影推荐里已经算强关联了因为电影消费的随机性比日用品大得多。提升度这里先记住一个结论小于1的规则说明A和B负相关不要犹豫直接扔掉。2.4 事务长度偏差活跃用户会带偏支持度统计还有一个经常被忽略的数据问题用户活跃度极度不均衡。有的用户一个月看100部电影有的只看4部。支持度按事务占比计算一个活跃用户贡献的100部电影会让很多冷门电影的支持度虚高甚至让一部只在某个“刷片狂”的月度事务里出现的电影进入频繁项集。这会导致后续规则里出现大量“仅发生在几个用户身上”的假规律。我一般会加一道长度截断每个事务最多保留30部电影超出部分随机采样。这样既保留了一个用户的多元口味又不让他一个人的行为主导全局统计。对应代码import numpy as np rng np.random.default_rng(42) transactions[movie_list] transactions[movie_list].apply( lambda x: list(rng.choice(x, sizemin(len(x), 30), replaceFalse)) )逻辑说明random choice 对超过30部的列表做无放回随机抽样短列表则原样保留。这让每个事务的长度上限一致长尾电影的支持度统计不会被极端活跃用户扭曲。参数说明30这个数字可以根据业务调整。如果你的产品人均月观影量本来就不高可以放宽到50如果数据集里存在大量“一个月刷完整个片库”的用户建议收紧到20。注意这条处理必须在生成事务之后、进入算法之前做否则活跃用户的影响会一直留在结果里。到这里事务已经能喂给算法了。下一章进入真正的挖掘环节我会把 Apriori 和 FP-Growth 都跑一遍并说明什么情况下换算法。3. Apriori 与 FP-Growth 选型最小可用代码与参数对比3.1 为什么先跑 Apriori 验证再换 FP-GrowthApriori 的核心是“频繁项集的所有非空子集也必须是频繁的”这条先验性质它通过逐层扫描数据库生成候选集再剪掉支持度不足的候选。优点是逻辑简单、中间结果可解释你在每一步都能知道当前候选集有多大。缺点是当影片总数多、事务平均长度长时候选集组合数会爆炸。FP-Growth 则把事务压缩进一棵 FP 树直接从树上挖频繁项集不生成候选集IO压力小速度快很多。在电影数据集上我的习惯是第一次跑总是先用 Apriori不是因为快是因为它容易排查问题。事务集清洗得干不干净、重复ID有没有合并Apriori 跑出来的频繁项集如果出现明显不该共现的组合你能顺着候选集回溯到原始数据。确认流程没问题、数据质量过关之后再换 FP-Growth 做正式的全量挖掘。有一点要说在前头两个算法对同一份事务集、同一组最小支持度得到的频繁项集集合是一致的。它们只是计算路径不同不存在“FP-Growth 挖出来的规则更准”这种说法。如果你发现结果不一致先检查是不是两个库的参数默认值不同例如是否把支持度解释成了计数而不是比例。3.2 用 mlxtend 跑通 Apriori 的最小代码mlxtend 是目前最省事的关联规则库它要求输入是 pandas DataFrame行是事务列是项目单元格是布尔值或0/1。先拿上一章的 transactions 表生成 one-hot 编码再调用 apriorifrom mlxtend.frequent_patterns import apriori from mlxtend.frequent_patterns import association_rules from mlxtend.preprocessing import TransactionEncoder # transactions: user_id, month, movie_list # 把电影列表展开成 one-hot 编码的事务矩阵 te TransactionEncoder() te_ary te.fit(transactions[movie_list]).transform(transactions[movie_list]) te_df pd.DataFrame(te_ary, columnste.columns_) # 挖掘频繁项集min_support0.02只保留最多3项的组合 freq_items apriori( te_df, min_support0.02, use_colnamesTrue, max_len3, ) # 生成关联规则置信度下限0.5提升度下限1.2 rules association_rules( freq_items, metricconfidence, min_threshold0.5, ) rules rules[rules[lift] 1.2] print(rules.sort_values(lift, ascendingFalse).head(10))逻辑说明TransactionEncoder 把每个事务里的电影ID列表转换成宽表每一列对应一部电影行为 True 表示该用户这个月看过。这一步是关联规则库的通用入口几乎所有开箱即用的库都要这种格式。apriori 返回的是频繁项集表包含 itemsets 列和 support 列。association_rules 再遍历所有频繁项集的前后件拆分计算 confidence 和 lift。参数说明max_len3 很重要。电影数据里四项、五项的组合虽然支持度可能达标但解释和落地都极难限制最大长度能显著减少候选集数量Apriori 跑起来不会失控。metricconfidence 是生成规则的筛选标准但注意最终输出我仍然用 lift1.2 过滤了一遍这一步是为了剔除“前件出现得特别频繁导致置信度虚高”的规则。use_colnamesTrue 让 itemsets 里直接显示电影ID而不是列编号排查数据时省很多功夫。3.3 FP-Growth 的调整点与两者结果差异mlxtend 里的 fpgrowth 接口和 apriori 几乎一样只是函数名不同。换算法只需要把 import 和调用行改掉from mlxtend.frequent_patterns import fpgrowth freq_items_fp fpgrowth( te_df, min_support0.02, use_colnamesTrue, max_len3, ) rules_fp association_rules( freq_items_fp, metricconfidence, min_threshold0.5, ) rules_fp rules_fp[rules_fp[lift] 1.2]逻辑说明fpgrowth 先扫描数据库构建 FP 树把共享前缀的事务压缩存储然后递归挖掘条件模式基。它对内存的使用更平稳不像 Apriori 会临时生成大量候选集。对于几千个用户、几千部电影的事务矩阵两者的运行时间差距可能不到一分钟但换到几万用户、上万部电影时FP-Growth 的优势会非常明显。一个常见误区是看到 FP-Growth 就以为所有参数都能照搬。min_support 的计算口径在两个实现里相同都按事务占比算但如果你的事务集从 Apriori 换到 FP-Growth 前做过采样或去重支持度阈值要重新计算。正式全量挖掘时我会尝试把 min_support 设得比初跑时略低一点因为 FP-Growth 运行成本低可以容忍更多候选项集代价是规则数量变多需要更严格的 lift 过滤。两个算法在验证集上的最终规则集合应该一致。如果不一致我会优先怀疑事务矩阵是否在两次调用之间被改动过比如列顺序变化导致列名错位这属于数据操作问题不是算法问题。3.4 频繁项集到关联规则前后件长度与方向的工程控制association_rules 默认会把频繁项集拆成所有可能的前后件组合含义有些会变得很难解释。比如一个三项频繁项集 {A, B, C}可能被拆成 {A, B} - {C}甚至 {A, C} - {B}。在电影推荐场景里这种前件带两个电影的规则很难直接用用户可能只看了 A 没看 B规则就命中不了。我一般会在生成规则后加一道硬过滤只保留前件和后件各含1部电影的规则rules rules[ (rules[antecedents].apply(len) 1) (rules[consequents].apply(len) 1) ]逻辑说明这一步把候选规则收敛到“一部电影看完了推荐另一部电影”的粒度后续接入推荐系统时不需要再处理复杂条件匹配。参数说明如果要保留两项前件的规则比如“看过A和B的人还看C”可以把 antecedents 的 len 条件放宽到2但 consequents 保持1。方向性也要注意mlxtend 不会为每个频繁项集都生成双向规则如果业务需要“双向推荐”可以在拿到规则表后手动把每条规则的后件当前件、前件当后件再补一遍提升度和支持度不变但置信度要重新按条件概率算。跑通这一步后你已经有几十上百条候选规则。下一章解决的是怎么从这堆规则里挑出真正能用的而不是随便抓一条就去上线。4. 规则评估与解读支持度、置信度、提升度的筛选顺序4.1 三条指标的分工支持度管“量”置信度管“条件概率”提升度管“因果幻觉”支持度是 P(A∩B)度量规则的覆盖范围。置信度是 P(B|A)度量在看过A的前提条件下看B的概率。提升度是 P(A∩B)/(P(A)P(B))度量A的出现是否让B的出现概率偏离独立基线。三条指标里提升度才是判断“这两个电影之间到底有没有真关系”的关键。只看置信度最容易翻车。假设某大片A有60%的用户都看过某经典B也有40%的用户看过如果两者独立看过A的人里本来就该有约40%看B。这时如果置信度算出0.55看起来很强但提升度只有1.375说明A和B的共现只比随机高了一点点。电影数据集里头部影片的单向支持度普遍很高这种“伪强规则”会大量出现。所以我的筛选顺序是先看 lift 1.2再看 support 是否大于某个下限最后才看 confidence 的绝对值。另一个容易被忽略的是规则的“方向性”。association_rules 会同时输出 A→B 和 B→A 两条规则两者的 confidence 不同但 lift 相同。如果 A→B 和 B→A 的 confidence 都很高说明这两部电影捆绑度极强适合做双入口推荐如果只有单向高说明存在观看顺序适合做“接着看”推荐。这一步对后续业务动作影响很大。4.2 把规则翻译成业务动作打包推荐、栏目编排、营销组合得到规则表后我习惯给每条规则打上业务标签而不是直接看数字。对前件 A 和后件 B 的组合如果它们属于同一类型这通常是口味相似型规则适合做“喜欢A的人还会喜欢B”的详情页推荐如果类型不同但 lift 很高往往是场景互补型规则例如看完高密度剧情片后接着看轻松喜剧这种规则适合做周末观影套餐。这里给出一个常用的动作映射表规则特征业务动作举例lift 2 且双向 confidence 都 0.6捆绑打包、联合营销两部系列续集并列推荐位单向 confidence 0.7反向低顺序推荐、“再看一部”前作看完了自然导向续作lift 1.5 但 support 0.01长尾个性化推荐冷门片搭配调性相近的小众片lift 1排除不做任何干预不能出现在同一推荐位这个表的核心是把“统计学显著”转成“业务可执行”。support 低的规则不代表没用它只说明覆盖人群小但 lift 高说明对这个人群来说这个关联很集中适合放到个性化推荐通道里和用户画像匹配而不适合放到全站热门位。4.3 用规则网络图辅助解读过滤后画图规则表超过50条后人眼已经很难发现结构。我一般会把规则转成网络图节点是电影边是规则边的粗细或颜色对应 lift。用 networkx 加 matplotlib 就能完成不需要引入额外图数据库import networkx as nx import matplotlib.pyplot as plt # rules 里已经过滤好 lift1.2 top_rules rules.sort_values(lift, ascendingFalse).head(30) G nx.DiGraph() for _, r in top_rules.iterrows(): a list(r[antecedents])[0] b list(r[consequents])[0] G.add_edge(a, b, weightr[lift]) pos nx.spring_layout(G, k0.8, seed42) nx.draw_networkx_nodes(G, pos, node_size800) nx.draw_networkx_labels(G, pos, font_size8) edges G.edges(dataTrue) nx.draw_networkx_edges( G, pos, width[d[weight] * 2 for _, _, d in edges], alpha0.6, ) plt.axis(off) plt.show()逻辑说明权重用 lift 放大画成边宽lift 越高边越粗一眼就能看出哪些电影之间关系最紧密。spring_layout 的 k 值控制节点间距电影ID较长时把 k 调大避免标签重叠。参数说明head(30) 限制边数避免图变成毛线团。seed42 保证每次画图布局一致方便和同事讨论时对比。如果你发现图里某个节点连着七八条边说明它是这个子图里的核心枢纽电影可以考虑在运营位重点曝光。4.4 规则去重与排序同一部电影出现在多条规则里怎么取舍画完图后还有一个现实问题同一部电影往往出现在十几条规则里前件不同、后件不同到底优先用哪条我的做法是给每条规则算一个综合分再按分数排序。原始置信度排序容易被热门电影霸榜纯 lift 排序又把一些支持度极低的长尾规则顶上来所以要折中rules[rule_score] rules[lift] * rules[confidence] * (rules[support] 0.01) rules rules.sort_values(rule_score, ascendingFalse)逻辑说明用乘积把三条指标压成一个分数support 加上0.01是为了防止支持度接近0的规则靠巨大 lift 拿到过高分。这个分数的绝对值没有业务含义它只用于在同一批规则里排出优先级。参数说明0.01 是一个平滑项。如果你的数据集比较大、支持度普遍在0.05以上可以去掉这0.01如果数据集很稀疏保留它会让规则排序更稳。实际落地时这个分数还可以乘上类型热度、影片新鲜度等业务权重先把结构搭好后面加因子很容易。排序之后接着要做一轮“有没有常识性矛盾”的检查。比如一部电影出现在 A→B 里同时又在 B→A 的反向规则里这两个结果本身不冲突但要确认不是同一部电影的续集因为ID不同被拆成了两个点。这类数据问题在下一章集中说。5. 电影关联规则挖掘常见问题排查从稀疏矩阵到伪关联5.1 现象挖出来的规则全是头部大片运营说“这还用你告诉我”现象规则表里前20条全是热门影片之间的组合例如某大IP系列之间互相推荐业务方看完的反馈是“我也知道但用户早看过了”。原因最小支持度设得偏高而热门影片的单部覆盖了大部分事务导致冷门片根本进不了频繁项集。长尾电影支持度普遍在0.005以下但只要事务数足够大长尾组合也能在细分人群里形成可复现的关联。解决不要只跑一层全量数据。我一般会把用户按观影活跃度分成高频、中频、低频三组分别设置不同的支持度阈值高频组用0.03低频组可能要用0.005。也可以反过来做先剔除那些出现在超过20%事务里的超级头部影片在剩余子集上挖长尾规则再把头部规则和长尾规则合并分两个策略落地。这样粗看像多此一举实际是唯一能避免“大量无意义规则淹没有效规则”的办法。5.2 现象Apriori 跑几分钟后内存暴涨进程被系统杀掉现象数据集只有几万行跑 apriori 时内存持续上涨最后报 MemoryError换 fpgrowth 后虽然慢一些但内存压力还在。原因这是典型的候选集爆炸而且事务矩阵很可能没有做列级清洗。电影ID数量大、事务平均长度较长时即使二项频繁项集不多三项候选的组合数也会指数上升。另一个隐藏原因是把所有电影都留作特征包括只出现过一次的电影它们让矩阵的列数虚高。解决进入算法前先做列级删除把出现在事务中次数少于2次的电影列删掉然后给 max_len 加上硬限制。二项规则能解决的需求不配合挖三项。如果全量数据仍然跑不动按用户采样是最直接的后悔药先跑1/10用户验证方案确认后再上全量。对应代码就是在 apriori 调用里把 max_len 设为2或3配合5.2开头说的列过滤内存能降一个量级。5.3 现象置信度很高但提升度接近1规则被业务方质疑是“巧合”现象某部热门喜剧 A 和某部热门动画 B置信度0.72但提升度只有1.05看起来很强的规则实际没有增量价值。原因热门影片的边际支持度高P(B) 本身就在0.5以上A的出现对B的概率几乎无影响。置信度衡量的是“看过A的人里有多少看了B”它受前件和后件各自热度的影响太大。解决把筛选顺序改成先 lift 后 confidence并且给 lift 设更苛刻的阈值。在电影场景上我个人的经验是 lift 低于1.2的规则全部丢掉低于1.5的要人工复核。如果业务只关心“用户看了A之后点B的概率高”可以直接去算条件概率不需要经过关联规则那是另一种统计口径混在一起解释会出问题。5.4 现象同一部电影因为续集、别名、年份被拆成多个ID规则里“自己和自己”关联现象规则里出现某系列前传到某系列正传的规则lift 高达9业务方怀疑是因为同一个用户把整个系列看完了但数据里它们被登记成了不同的影片ID。原因数据集的影片ID粒度不统一有的是按上映版本分有的是按标题别名分有的是按地区版本分。用户在一个月内连续观看同一系列的不同条目在事务里被记录成两个 item于是形成了“貌似有趣但其实是同一内容”的自关联。解决在挖掘之前建立影片ID映射表。先做标题归一化去空格、去年份、统一大小写再人工核对一批明显同系列的项目对续集片可以按系列名归并成一个逻辑ID也可以反过来只保留系列母题。这一步是数据工程问题没有算法能兜底建议在清洗阶段就完成不要在挖掘后手工删规则。这四条坑基本覆盖了从第一次跑电影数据到现在遇到过的大部分问题。关联规则挖掘这个方向数据质量的影响远大于算法选择的影响很多玄学问题最后查出来都是ID或时间窗口的问题。下一章我们把规则接到推荐流程里让规则产生实际作用。6. 把规则接进推荐流程Top-N 候选生成与维护节奏6.1 命中规则的加权打分把 lift 变成排序分数规则表不能只躺在笔记本里接入推荐的第一步是给用户生成候选集。给每个用户看过的电影作为前件去匹配规则命中一条规则就产生一个后件候选分数用 lift 和 confidence 的加权融合def generate_candidates(user_watched, rules, top_n20): score {} for watched_id in user_watched: sub rules[rules[antecedents].apply(lambda x: watched_id in x)] for _, r in sub.iterrows(): for rec_id in r[consequents]: if rec_id in user_watched: continue score[rec_id] score.get(rec_id, 0) r[lift] * r[confidence] ranked sorted(score.items(), keylambda x: x[1], reverseTrue) return [mid for mid, _ in ranked[:top_n]]逻辑说明用 lift 乘 confidence 作为分数兼顾关联强度和条件概率。同一部电影如果被多条规则命中分数累加相当于多路证据互相佐证。已看过的电影必须过滤否则会把用户正在看的电影推荐回去。对只看过一两部电影的用户命中的规则有限分数更容易被单条高 lift 的规则主导这也是可接受的。参数说明top_n 是输出候选数接入召回阶段时建议给大一点比如50让排序层有裁剪空间。分数累加逻辑针对的是“一部电影被多次推荐”的情况实际工程中会做用户活跃度归一化避免高频用户分数天然偏高这个细节要等接到线上再调。6.2 冷启动用户怎么用规则补位新注册用户通常只有一两部电影的历史关联规则召回基本为空。我的做法是用规则做间接匹配从用户最近一次看的电影 A 出发先找到与 A 同类型的电影集合再对这些集合里的电影跑规则。更简单的方式是给冷启动用户临时放最新一周的高 lift 规则把规则里的后件按热度排序塞进推荐位等用户积累到5部以上再看行为。这里的关键是不要把关联规则当成唯一召回源它在冷启动阶段只是补齐策略。6.3 每周重跑与阈值衰减规则也会过期电影的热度变化很快上周刚上线的影片这周可能已经被讨论淹没。我习惯每周重跑一次规则并且对旧规则做时间衰减超过30天没再命中的规则直接下线支持度阈值可以随数据量增长缓慢下调保持规则数量稳定。关联规则挖掘不是一次性的报表把它当成一个会过期的模型来养才不会让推荐位变得越来越僵。这是我的习惯也建议你从第一次上线起就写好重跑脚本别等运营来问为什么这个推荐还在。希望帮到你。本文还有配套的精品资源点击获取