尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

基于RFM的用户画像可视化系统:用户分层与运营决策的工程实践

发布时间:2026/9/26 19:15:46

资讯中心
01
ARTICLE

基于RFM的用户画像可视化系统:用户分层与运营决策的工程实践

基于RFM的用户画像可视化系统:用户分层与运营决策的工程实践
简介基于RFM模型的用户画像可视化系统完整代码面向正在学习Python数据分析、机器学习与Django Web开发的读者旨在帮助理解以消费行为数据刻画用户价值并搭建可交互的可视化后台。资源围绕R最近消费时间、F消费频率、M消费金额三个核心维度展示从Pandas清洗数据、计算RFM指标、K-means聚类分群到利用Django框架实现页面展示的完整链路并包含电信、短信、App等多源用户行为数据表。压缩包共43个文件涵盖7个Python源码、csv/tsv数据表、5个HTML页面模板、Jupyter Notebook分析脚本以及环境安装文档与启动说明整体大小14.72MB。已有1565人学习下载。通过实际代码读者可直观掌握RFM用户分层与可视化的落地方法理解数据归一化、聚类建模和Web渲染的关键步骤并可作为课程设计或推荐系统项目的参考范例。配套的目录结构清晰Python源码、数据、模板和文档分区存放便于按模块阅读和二次开发能够快速复现一个可运行的用户画像系统。1. 基于RFM的用户画像可视化系统这不是画几张图是把用户分层变成可运营的决策依据RFM这个词在用户增长和数据分析岗的JD里几乎快写烂了但真正把RFM落地成一套可视化系统的人并不多。多数团队做到最后就是pandas里算三个字段然后丢给BI工具画三个柱状图老板看完说一句“哦”就结束了。这不算用户画像系统这叫数据报表。基于RFM的用户画像可视化系统核心价值是把Recency最近一次消费时间间隔、Frequency消费频次、Monetary消费金额三个维度的计算结果转成业务方能直接理解、直接做运营动作的标签体系和数据看板。它不是算完即止而是要让运营打开一个页面就能回答三个问题哪些用户该召回、哪些用户该发券、哪些用户已经处在流失边缘。这篇文章会按真实项目里从取数、清洗、计算到可视化落地的完整路径来写每一步都给出可抄作业的代码、参数和踩坑记录。适合手里有订单表但没有完整画像系统、正打算从零搭建的读者。2. RFM口径定义与评分标准先把三个维度定义到不会吵架的程度2.1 为什么多数团队的RFM计算从一开始就是错的很多项目翻车不在技术在于口径没定清楚就开写代码。Recency到底算最近一次消费距今多少天还是按月份算第几个自然月没消费Frequency是算累计订单数还是算活跃月份数Monetary是算实付金额还是算包含退款前的原始金额这些差异会导致同一个用户在不同人手里算出完全不同的分层结果。我一般建议先在项目文档里把口径锁定成一张表而不是上来就写SQL。实际操作中Recency统一用「当前日期减去该用户最近一次有效订单的付款时间向下取整到天」Frequency统一用「统计周期内该用户的有效订单数」Monetary统一用「统计周期内该用户的有效订单实付金额之和」。这里的重点在「有效订单」的定义通常要排除已退款、已取消、测试单这三种状态。如果业务方对有效订单有不同意见比如部分退款算不算有效要在这张表里先说明白否则后面代码怎么改都会有人不满意。2.2 RFM评分的分位数切分五档评分与节点选择RFM算出来是三个连续值但画像系统里展现给运营看的应该是评分而不是原始值。常见的做法是把每个维度按分位数切成1到5分5分最好1分最差。这里的关键节点是分位数怎么取。一种做法是等宽切分比如金额最大值的五分之一作为一个档。这在数据分布均匀时没问题但电商和本地生活订单数据基本都呈长尾分布头部用户贡献绝大部分金额等宽切分会让大量用户落在1分档评分完全失去区分度。更推荐等频切分也就是按20%、40%、60%、80%分位点切分。这样每个评分档的用户数量基本一致后续分层才不会出现某个人群占了全体用户80%的极端情况。如果订单表的数据量足够大也可以直接用pd.qcut而不手工传分位点但要注意处理重复值。同一个分位点上出现大量相同值时pd.qcut会直接报错需要先做去重或用rank方法规避。这个坑在电商大促场景尤其常见满减活动会让大量用户实付金额恰好等于同一个门槛值。2.3 用代码固化口径从订单明细表到RFM特征表的可复现脚本下面给一段我常用的口径固化代码。这段代码的输入是一张订单明细表字段包含用户ID、订单状态、付款时间、实付金额输出是每个用户一行、包含r_score、f_score、m_score三列评分数据的RFM特征表。import pandas as pd import numpy as np # 读取订单明细表 df pd.read_csv(orders.csv, parse_dates[pay_time]) # 第一步只保留有效订单这是全项目最关键的一步 valid_status [paid, completed, partial_refund] df df[df[order_status].isin(valid_status)].copy() # 第二步在统计周期上取数避免跨年数据污染 # cutoff_date 是项目设定的统计截止日期 cutoff_date pd.Timestamp(2025-06-30) start_date cutoff_date - pd.DateOffset(months6) df df[(df[pay_time] start_date) (df[pay_time] cutoff_date)].copy() # 第三步三个RFM基础特征 rfm df.groupby(user_id).agg( recency(pay_time, lambda x: (cutoff_date - x.max()).days), frequency(order_id, count), monetary(pay_amount, sum) ).reset_index() # 第四步五档评分用rank(methodfirst)避开分位点重合问题 rfm[r_score] pd.qcut(rfm[recency], 5, labels[5, 4, 3, 2, 1]).astype(int) rfm[f_score] pd.qcut(rfm[frequency].rank(methodfirst), 5, labels[1, 2, 3, 4, 5]).astype(int) rfm[m_score] pd.qcut(rfm[monetary].rank(methodfirst), 5, labels[1, 2, 3, 4, 5]).astype(int) rfm.to_csv(rfm_features.csv, indexFalse)这段代码有几个参数要重点说明。cutoff_date是统计截止日直接影响recency的值上线后如果发现所有用户的recency整体偏小多半是截止日设成了今天而订单数据还包含未来时间这属于脏数据问题。第四步里r_score的分箱逻辑是反过来的因为recency越大表示用户越久没来所以1分的箱对应最新用户5分的箱对应最久未产生消费的用户。这一点新手极容易搞反建议在代码边上注释清楚。rank(methodfirst)的作用是给相同分位值赋不同序号配合qcut可以避免大量重复值导致的分箱崩溃。如果业务上要求相同金额的用户必须得到相同评分则需要改用随机分组或按其他业务字段二次排序但这属于产品取舍代码层面rank(methodfirst)只是保证可运行。3. 从订单表到RFM花名册全量用户的特征计算与画像标签生成3.1 用户完整生命周期的特征记录生成RFM特征表算出来后下一步不是直接画图而是要让这张表具备「可查询、可筛选、可分层」的画像属性。这一步要解决两个问题一是把评分组合映射成人话二是把用户的邮箱、手机号、注册时间、会员等级等基础属性关联进来形成完整的用户花名册。先说评分组合映射。三位评分r_score、f_score、m_score可以组成3位数字串共5^3125种组合。业务上看不了125种所以要归纳成传统RFM模型里的8类人群。比如r_score5且f_score5且m_score5的组合叫「重要价值用户」r_score1且f_score4且m_score5的组合叫「重要唤回用户」。这里要注意评分组合与8类人群的对应规则没有绝对公式不同行业有不同定义只要在项目文档里固定下来就行。# 读取上一步生成的RFM特征表 rfm pd.read_csv(rfm_features.csv) # 定义8类人群的评分区间映射 # 区间写的是闭区间业务方可直接看懂 def map_user_segment(row): r, f, m row[r_score], row[f_score], row[m_score] if f 4 and m 4 and r 4: return 重要价值用户 if f 4 and m 4 and 2 r 3: return 重要保持用户 if f 4 and m 4 and r 1: return 重要唤回用户 if f 4 and m 3 and r 4: return 一般价值用户 if f 4 and m 3 and 2 r 3: return 一般保持用户 if f 4 and m 3 and r 1: return 一般唤回用户 if f 3 and m 4: return 潜力用户 if f 3 and m 3 and r 4: return 新用户 return 流失用户 rfm[segment] rfm.apply(map_user_segment, axis1) # 关联用户基础属性表补齐画像信息 user_info pd.read_csv(user_info.csv) rfm rfm.merge(user_info, onuser_id, howleft) # 输出到数据库或CSV rfm.to_csv(rfm_user_profile.csv, indexFalse)这段代码里的map_user_segment函数是纯规则映射业务方如果觉得某类人群的阈值不合理可以直接改区间。需要注意的是f4这个阈值它意味着一个用户只要下单频率在前40%就算高频而不是非得一个月买很多次。这套规则适合低频高客单场景比如家居、教育、二手车。如果是外卖或网约车这种高频低客单场景「消费频次高」的阈值要重新定义否则8类人群里一半以上都会挤进「重要价值用户」分群就没有意义了。3.2 画像标签体系设计除了RFM还要有哪些标签一个完整的用户画像可视化系统RFM模型提供的是用户价值分层但运营点开页面后还想知道这个用户是新是旧、在哪个城市、最近一次消费买了什么品类。这些信息不来自RFM计算而来自基础标签。我自己的习惯是把标签按性质分三组。第一组是静态属性标签包括性别、年龄段、城市等级、注册渠道直接在user_info表里取第二组是消费行为标签包括近30天订单数、累计消费金额、客单价、常用支付方式从orders表里汇总第三组是RFM价值标签就是我们刚算到的r/f/m评分和用户分群。可视化系统展示的每张卡片、每个筛选器背后都要能落到这三组标签的某个字段上而不是只挂一个用户分群。3.3 特征表的验证在可视化之前先过三关可视化之前先验证特征表否则图上出现数据异常时根本分不清是计算逻辑错还是图表配置错。我每次都会先跑三个检查。第一关是完整性检查用户数是否与原订单表的去重用户数一致有订单但没关联上user_info的用户是否有兜底处理。第二关是分布合理性检查打印每个用户分群的人数占比。正常情况下「重要价值用户」占比应在5%到15%之间。如果超过20%要么是评分阈值太松要么是数据集中在高频高额区要回头调整分位数切分方式。第三关是抽样验证随机抽5个用户手动把他们的订单明细拉出来复核一遍recency和monetary是否和计算一致。这三关跑完没异常才能进可视化环节。4. 可视化系统怎么落地交互式看板的核心配置与参数调优4.1 选型为什么推荐用Plotly而不是Excel或ECharts到可视化阶段常见的选择是Excel数据透视表、BI工具或Plotly。Excel做不了动态下钻BI工具适合固定报表但改交互逻辑成本高Plotly是Python生态里做交互式可视化最顺手的方案。关键优势在于Plotly的图表本身就是Altair和Bokeh之外少数能直接在Python里生成HTML交互图表、随后嵌到Web服务里的方案不依赖前端团队的排期。同时整个RFM计算管线已经写在Python里了用Plotly可以直接复用同一个DataFrame不会出现「分析师在Python算完数据再手工导到BI工具」的分裂流程。前端展示以下三种典型图就可以了人群占比饼图、RFM三维评分分布散点图、用户明细表。4.2 核心代码块RFM三维散点图与分群着色import plotly.express as px # 从特征表读取数据 rfm_profile pd.read_csv(rfm_user_profile.csv) # 关键参数size控制点大小color按人群着色hover_data展示明细 fig px.scatter_3d( rfm_profile, xrecency, yfrequency, zmonetary, colorsegment, sizemonetary, size_max10, hover_data[user_id, r_score, f_score, m_score, order_count_30d], title用户RFM三维分布, labels{recency: 最近消费间隔(天), frequency: 消费次数, monetary: 累计金额(元)} ) fig.update_layout( legend_title_text用户人群, scenedict( xaxisdict(title最近消费间隔(天)), yaxisdict(title消费次数), zaxisdict(title累计金额(元)) ), height600 ) fig.write_html(rfm_3d.html)这里最值得调的是figure的height参数默认值800在某些内网浏览器里会出现滚动条页面整体观感很差。size_max也建议控制在8到12之间数值过大会让头部用户的点连成一片立体图完全看不出密度分布。实际运行时如果发现散点图太稠密可以在读入后先做一次采样。比如用户数超过3万时用rfm_profile.sample(n3000, random_state42)抽样展示否则浏览器渲染几万个点会明显卡顿。但要注意抽样只用于散点图不要影响后面画占比图和数据表的行数。4.3 图表之外的交互筛选数据表与图表的联动可视化系统不只是几张静态图。运营用这个系统时的典型操作是先看饼图发现「重要唤回用户」占18%觉得这部分人有召回价值然后点一下饼图里对应的人群页面下方的用户明细表就只显示这一群人并且可以直接导出成CSV去发短信或做定向活动。这种联动在Plotly里用subplot的click事件实现。最常见的落地方式是做一个简单的Dash应用把图和数据表放在同一个页面里。如果不想引入Dash也可以退一步把图表做成多个HTML文件回传给运营由运营自己开多个页面交叉筛选但体验差不少。参数上需要关注的是数据表的排序逻辑。默认按user_id排序运营根本没法用。强烈建议明细表默认按monetary降序排列让运营第一眼看到的就是钱。这也是「用户画像可视化系统」和「数据报表」的一个分水岭——报表罗列数据系统组织决策顺序。5. 自建RFM可视化系统的避坑手册5个典型翻车现场与解法5.1 Frequency被退款单污染高频人群虚高现象分群结果里「重要价值用户」占比接近30%明显偏离业务感知。原因订单表里的order_status没过滤退款和取消状态。一个反复下单又反复取消的用户在frequency上被计算为多次消费但实际一分钱贡献都没有。解决在口径固化阶段就严格过滤有效状态。尤其是部分退款订单到底算有效还是算无效必须在代码里写死。我的建议是退款金额小于订单金额20%的算有效大于等于20%的整单剔除。这条规则需要业务方签字确认否则后期有人质疑数据时没有依据。5.2 Recency截止日选择不当用户被误判为流失现象某个月的分群结果中「流失用户」激增但业务方确认并没有大量用户离开。原因统计截止日用了当月1日而订单表里还有截止日当月的后续订单数据没更新到最新状态。所有用户最近一次消费时间相对截止日都变长导致recency整体抬高。解决统一用etl任务跑批时刻作为cutoff_date并在特征表里增加一个running_date字段标注这张表是什么时候算的。可视化系统页面上也要显示这个日期否则运营拿着上周的数据做本周的召回动作人群早就变了。5.3 分位数切分在订单量少的月份整体失效现象新用户和老用户的f_score大量集中在同一档新用户人群几乎消失。原因某个月份有效订单总量本来就不高比如春节月份所有人都只下了1单或2单frequency的分布极度集中。这时按分位数切5档就是硬切结果就是天平的每个盘子上几乎一样重。解决低频月份不要用纯分位数改用「观察窗口内是否达到N单」的绝对标准。常见做法是先把f_score改成0/1二值化Frequency≥2单算1分然后重新映射到5档。该方案需要额外写规则但数据分布复杂时比机械分位数靠谱得多。5.4 百分比堆积图里小人群被视觉吞掉现象饼图或堆积条形图里「重要保持用户」只占3%在图上几乎看不见运营误以为这类人群不存在。解决可视化层面不要只依赖饼图加一个表格展示占比数据。用户在这种场景下第一时间看数字而不是看图。具体到Plotly实现可以这样写seg_summary rfm_profile.groupby(segment).agg( user_count(user_id, count), avg_recency(recency, mean), avg_frequency(frequency, mean), avg_monetary(monetary, mean) ).reset_index() seg_summary[pct] (seg_summary[user_count] / seg_summary[user_count].sum() * 100).round(2) # Plotly表格展示列顺序按业务决策顺序排列 from plotly.graph_objects import Figure, Table fig_tbl Figure(Table( headerdict(values[用户人群, 人数, 占比%, 平均最近消费间隔, 平均消费频次, 平均消费金额]), cellsdict(values[ seg_summary[segment], seg_summary[user_count], seg_summary[pct], seg_summary[avg_recency].round(1), seg_summary[avg_frequency].round(1), seg_summary[avg_monetary].round(0) ]) )) fig_tbl.write_html(segment_summary_table.html)这段代码的逻辑很简单但作用很关键。它把分群结果从「看图讲故事」变成了「看表做决策」尤其当某一类人群占比特别小或特别大时表格仍然能给出精确数字饼图则做不到。5.5 历史回溯对比时RFM分群结果对不上账现象月初算的分群结果和月底复盘时重算的分群结果同一批用户的人群体征变化很大运营质问数据口径不稳定。原因底层订单表里补充录入了历史订单或者退款处理发生了延迟。比如用户在月初下单月底退款这把重算时frequency和monetary都改掉了。解决给每个用户存一份历史快照表用batch_id或calc_date字段标识是哪一天算的结果。可视化系统默认展示最新快照但支持运营选择历史日期回看。这个习惯一旦养成业务方对数据系统的信任度会大幅提升。6. 进阶玩法从静态RFM报表走向网约车场景的运营指标可视化6.1 把RFM模型迁移到网约车订单数据的关键改造网格车订单数据和常规电商订单对RFM的含义有显著差异。网约车的消费频次高、单均金额低、消费间隔短用户可能一天内下单多次。如果原封不动照搬电商里的RFM规则Frequency会普遍高得离谱Recency会有大量用户在0到1之间分群依然分不出层次。实际做网约车订单数据处理时我的建议是把时间窗口压缩为两周将frequency替换为「活跃天数」而不是「订单数」。用户一天打5次车和打1次车在消费频次上的业务含义完全不同但对平台来说都是当天活跃用户。用活跃天数做Frequency评分更贴近「用户对这个平台的依赖程度」这个语义。Monetary维度建议用「近两周内平均每单金额」替代「总金额」这样能把高频低价用户和高频高价用户区分开防止快车用户和专车用户被混为一谈。经过这三处改造后网约车订单数据在RFM模型下的分群结果才有运营对照价值。6.2 验证分群效果的技巧RFM迁移矩阵分群模型建好后每周重算一次RFM并输出用户在这两周之间的人群迁移矩阵。迁移矩阵能回答一个关键问题上周的「重要价值用户」这周还在吗还是掉到了「一般保持用户」在运营视角下迁移矩阵比静态分布图更有意义因为它是可以闭环验证ROI的。比如针对「重要唤回用户」发了折扣券下周这批用户有多少比例晋升为「重要价值用户」这个数字直接决定活动预算要不要继续投入。操作上也很简单用上一版的rfm_user_profile.csv和本周的rfm_user_profile.csv在user_id上做merge然后交叉统计segment的流转关系。6.3 可视化系统上线后我的习惯系统不是交付完就跑。我一般会在上线后的前两周每周固定跑一次分群结果和运营手动标记的名单做比对确认模型分层没有离谱偏差。然后每个月滚一次分位点阈值因为用户结构会随时间变化年初和年尾的消费分布完全不是一回事。以上这套下来系统才算是真正在业务里站住了脚。项目运行时踩过的坑都在前面几章里这篇笔记的价值在于让读者少走我走过的弯路。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。