RFM用户分群分析

目录

会员价制度模型

业务分析

实现

导入模块

读取数据

数据预处理

添加间隔日期列

计算RFM的值

查看数据分布

根据信息自定义区间

计算RFM分箱得分

计算RFM组合

绘图

计算RFM分组会员占比

导出数据


会员价制度模型

价值度模型一般基于交易行为产生,衡量的是有实体转化价值的行为.常用的价值度模型是RFM

  • RFM模型是根据会员,一般用于零售行业(复购率相对较高的行业)

    • 最近一次购买时间R(Recency)

    • 购买频率F(Frequency)

    • 购买金额M(Monetary)计算得出RFM得分

    • 通过这3个维度来评估客户的订单活跃价值,常用来做客户分群或价值区分

    • RFM模型基于一个固定时间点来做模型分析,不同时间计算的的RFM结果可能不一样

|---|---|---|--------|
| R | F | M | 用户类别 |
| 高 | 高 | 高 | 重要价值用户 |
| 高 | 低 | 高 | 重要发展用户 |
| 低 | 高 | 高 | 重要保持用户 |
| 低 | 低 | 高 | 重要挽留用户 |
| 高 | 高 | 低 | 一般价值用户 |
| 高 | 低 | 低 | 一般发展用户 |
| 低 | 高 | 低 | 一般保持用户 |
| 低 | 低 | 低 | 一般挽留用户 |

  • RFM模型的基本实现过程:

    • 设置要做计算时的截止时间节点(例如2017-5-30),用来做基于该时间的数据选取和计算。

    • 在会员数据库中,以今天为时间界限向前推固定周期(例如1年),得到包含每个会员的会员ID、订单时间、订单金额的原始数据集。一个会员可能会产生多条订单记录。

    • 数据预计算。从订单时间中找到各个会员距离截止时间节点最近的订单时间作为最近购买时间;以会员ID为维度统计每个用户的订单数量作为购买频率;将用户多个订单的订单金额求和得到总订单金额。由此得到R、F、M三个原始数据量。

    • R、F、M分区。对于F和M变量来讲,值越大代表购买频率越高、订单金额越高;但对R来讲,值越小代表离截止时间节点越近,因此值越好。对R、F、M分别使用五分位法做数据分区(三分位也可以,分位数越多划分得越详细)。需要注意的是,对于R来讲需要倒过来划分,离截止时间越近的值划分越大。这样就得到每个用户的R、F、M三个变量的分位数值。

    • 将3个值组合或相加得到总的RFM得分。对于RFM总得分的计算有两种方式,一种是直接将3个值拼接到一起,例如RFM得分为312、333、132;另一种是直接将3个值相加求得一个新的汇总值,例如RFM得分为6、9、6。

  • 以某电商公司为例

    • R:例如:正常新用户注册1周内交易,7天是重要的值,日用品采购周期是1个月,30天是重要的值

    • F:例如:1次购买,2次购买,3次购买,4~10次,10次以上

    • M:例如:客单价300,热销单品价格240 等

模拟操作

1)提取用户最近一次交易时间,计算时间间隔

2)从数据中得要用户的购买次数和消费金额

3)根据要求赋值对应的R值,时间间隔越短R值越高

  • =IF(D2>60,1,IF(D2>30,2,IF(D2>14,3,IF(D2>7,4,5)))

4)根据要求赋值对应的F值,购买次数越高F值越高

  • =IF(E2>10,5,IF(E2>3,4,IF(E2>2,3,IF(E2>1,2,1))))

5)根据要求赋值对应的M值,消费金额越高M值越高

  • =IF(F2>1000,5,IF(F2>500,4,IF(F2>300,3,IF(F2>230,2,1))))
  1. 再求中值或中位数 ,进行判断
  • =IF(G2>3,"高","低")
  • =IF(H2>2,"高","低")
  • =IF(I2>3,"高","低")

业务分析

用户价值细分 是了解用户价值度的重要途径常用模型是RFM模型.

  • 将用户做分组,使用RFM模型的3个维度分别做3个区间的离散化,及用户群体最大有3*3*3 = 27个
    • 划分区间过多,不利于用户群体的拆分
    • 划分区间太少,可能会导致每个用户特征区分不显著

实现

导入模块

import time  # 时间库
import numpy as np  # numpy库
import pandas as pd  # pandas库
from pyecharts.charts import Bar3D # 3D柱形图

读取数据

# 1 读取数据
# 1.1 分解式
# sheet_data_2015 = pd.read_excel("../data/h_sales.xlsx", sheet_name="2015")
# sheet_data_2016 = pd.read_excel("../data/h_sales.xlsx", sheet_name="2016")
# sheet_data_2017 = pd.read_excel("../data/h_sales.xlsx", sheet_name="2017")
# sheet_data_2018 = pd.read_excel("../data/h_sales.xlsx", sheet_name="2018")
# sheet_data_member = pd.read_excel("../data/h_sales.xlsx", sheet_name="会员等级")
# 
# sheet_data = [sheet_data_2015, sheet_data_2016, sheet_data_2017, sheet_data_2018, sheet_data_member]

# 1.2 合并式
sheet_names = ["2015", "2016", "2017", "2018", "会员等级"]

sheet_data = [pd.read_excel("../data/h_sales.xlsx", sheet_name=sheet_name) for sheet_name in sheet_names]

sheet_data
  • 该数据是从2015年到2018年共4年的用户订单抽样,来源于销售系列.数据Excel包含4个单独年份的数据,最后一张是会员等级表
  • 数据量:30774 / 41278 / 50839 / 81349
  • 是否有NA值 : 有
  • 是否有异常值 : 有

数据预处理

  • 日期列自动识别为日期格式,如果不是则需要后期转换

  • 订单金额分布式不均匀的,在2016年的数据中,最大值为74900,最小值为0.1(职中可能是使用了优惠券支付的订单,没有意义,因此可以将低于1元的订单在后续处理中去掉)

  • 数据中有缺失值的记录,但数量不多,可以选择丢弃或者填充

    2.1 数据预处理: 对前四个df纵向合并为一个df,然后删除包含缺失值的行数据、保留订单金额大于1的行数据和提取每年数据的最大提交日期值保存到新列中

    汇总所有数据, 使用pandas.concat连接前四个dataframe

    data_merge = pd.concat(sheet_data[:-1], axis=0)

    删除包含缺失值的行数据

    data_merge.dropna(inplace=True)

    保留订单金额大于1的行数据

    print('过滤前: ', data_merge.shape)

    data_merge.query('订单金额 > 1', inplace=True)

    print('过滤前: ', data_merge.shape)

    提取 提交日期 列的年份保存到year新列中

    data_merge['year'] = data_merge['提交日期'].dt.year

    对year新列分组, 提取每组中提交日期列的最大值, 保存到max_year_date新列中, 这样方便后续针对每年的数据分别做RFM计算,而不是针对4年的数据统一做RFM计算

    data_merge['max_year_date'] = data_merge.groupby(['year'])['提交日期'].transform('max')

    data_merge

添加间隔日期列

# 目标: 添加间隔日期列
# 计算日期间隔天数(该订单日期距离当年最后1天的天数),并添加列 date_interval(该列的数据类型为timedelta64[ns])
data_merge['date_interval'] = data_merge['max_year_date'] - data_merge['提交日期']

# 转换日期间隔为数字 
data_merge['date_interval'] = data_merge['date_interval'].dt.days 

data_merge

计算RFM的值

# 目标: 计算 R 求分组后date_interval列中最小值, F 订单频率, M 计算订单总金额
# 1 基于year、会员ID列做分组之后,分别对date_interval、提交日期、订单金额做不同的运算
# as_index=False表示重置索引
rfm_gb = data_merge.groupby(['year', '会员ID'], as_index=False).agg({
    # 1.1 R 求分组后 date_interval 列中最小值:计算当年该会员最后一次订单距离年末12月31日的间隔天数
    "date_interval" : "min",
    # 1.2 F 订单频率,计算当年该会员一共消费多少次,也可以对 订单号 列进行count计算
    "订单号" : "count",
    # 1.3 M 计算订单总金额:计算当年该会员一共消费多少钱
    "订单金额" : "sum"
})


# 2 重命名列名 'year', '会员ID', 'r', 'f', 'm'
rfm_gb.columns = ['year', '会员ID', 'r', 'f', 'm']
rfm_gb

查看数据分布

# 目标: 查看数据分布
# 1 获取 r, f, m 列 的所有数据
# 2 查看统计信息
# 3 转置 T
desc_pd = rfm_gb.iloc[:, 2:].describe().T
desc_pd

根据信息自定义区间

  • R和M的划分:根据统计信息,R和M相较于离散,因此选择25% 和 75% 作为划分区间的2个边界值

  • F的划分:大部分用户分布趋近于1,从min到75%的分段值都是1且mean也才1.365.因此可以和业务部门进行沟通,可以使用 2 和 5 来作为边界

    • 业务部门认为当年购买>=2次可被定义为复购用户(而非累计订单的数量计算复购用户)

    • 业务部门认为普通用户购买5次已经是非常高的次数,超过该次数就属于非常高价值用户群体

  • 后续使用pd.cut 方法,实行的是左开右闭的原则.

    自定义区间边界,划分为3个区间,注意起始边界小于最小值

    r_bins = [-1, 79, 255, 365]
    f_bins = [0, 2, 5, 130] # f数据的分布比较极端,所以这里采用较小的值
    m_bins = [0, 69, 1199, 206252]

计算RFM分箱得分

# 目标: 计算 RFM分箱得分
# 对rfm_gb['r']的值按照r_bins进行划分,划分结果对应为新的值,新的值为labels中的对应值
# range函数:range(start, stop[, step])
# 1 计算 R 得分: r_score
rfm_gb['r_score'] = pd.cut(rfm_gb['r'], bins=r_bins, labels=[j for j in range(len(r_bins)-1, 0, -1)])

# 2 计算 F 得分: f_score
rfm_gb['f_score'] = pd.cut(rfm_gb['f'], bins=f_bins, labels=[i for i in range(1, len(f_bins))])

# 3 计算 M 得分: m_score
rfm_gb['m_score'] = pd.cut(rfm_gb['m'], bins=m_bins, labels=[i for i in range(1, len(m_bins))])

rfm_gb.head()

计算RFM组合

# 目标: 计算RFM组合
# 1 r_score、f_score、m_score 三列转为str类型
rfm_gb['r_score'] = rfm_gb['r_score'].astype(np.str)
rfm_gb['f_score'] = rfm_gb['f_score'].astype(np.str)
rfm_gb['m_score'] = rfm_gb['m_score'].astype(np.str)

# 2 r_score、f_score、m_score 三列拼接字符串,并添加新列 rfm_group
rfm_gb['rfm_group'] = rfm_gb['r_score'] + rfm_gb['f_score'] + rfm_gb['m_score']

rfm_gb.head()

绘图

# 目标: 图形数据汇总
# 1 根据 rfm_group 和 year 分组 再求 每组会员人数
display_data = rfm_gb.groupby(['rfm_group', 'year'])['会员ID'].count().reset_index()

# 2 重命名 'rfm_group','year','number'
display_data.columns = ['rfm_group', 'year', 'number']

# 3 将 rfm_group 转成 np.int32 类型
display_data['rfm_group'] = display_data['rfm_group'].astype(np.int32)

display_data.info()

# 显示图形
from pyecharts.commons.utils import JsCode
import pyecharts.options as opts

# 颜色池
range_color = ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf',
               '#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026']

range_max = int(display_data['number'].max())

c = (
    Bar3D()#设置了一个3D柱形图对象
    .add(
        "",#图例
        [d.tolist() for d in display_data.values],#数据
        xaxis3d_opts=opts.Axis3DOpts(type_="category", name='分组名称'),#x轴数据类型,名称,rfm_group
        yaxis3d_opts=opts.Axis3DOpts(type_="category", name='年份'),#y轴数据类型,名称,year
        zaxis3d_opts=opts.Axis3DOpts(type_="value", name='会员数量'),#z轴数据类型,名称,number
    )
    .set_global_opts( # 全局设置
        visualmap_opts=opts.VisualMapOpts(max_=range_max, range_color=range_color), #设置颜色,及不同取值对应的颜色
        title_opts=opts.TitleOpts(title="RFM分组结果"),#设置标题
    )
)
c.render("RFM模型.html") #在notebook中显示
  • 重点人群分布:212群体

    • 在整个分组中,212群体的用户是相对集中且变化最大的

    • 从2015年到2017年用户群体数量变化不大,但到2018年增长了近一倍

    • 这部分人群将作为重点分析人群

  • 重点分组分布:

    • 除了212人群外,312、213、211及112人群都在各个年份占据很大数量

    • 虽然各自规模不大,但组合起来的总量超过212本身,也要重点做分析。

    • 如果拖动左侧的滑块,仅过滤出用户数量在4085以内的分组结果。观察图形发现,很多分组的人群非常少,甚至没有人

计算RFM分组会员占比

# 目标: 计算RFM分组会员数占比
# 1 按 RFM_Group 分组之后,对number求和,返回新的df
rfm_persent = display_data.groupby(['rfm_group'])['number'].sum().reset_index()

# 2 计算当前RFM_Group分组中会员总数百分比,并添加列 count_per
rfm_persent['count_per'] = rfm_persent['number'] / display_data['number'].sum()

# 3 转换为百分数,保留2位小数
rfm_persent['count_per'] = rfm_persent['count_per'].apply(lambda x : format(x, '.2%'))

# 4 按number列进行排序,由大到小
rfm_persent.sort_values('number', ascending=False, inplace=True)

rfm_persent
  • 第1类人群:212、211、312、112、213;占比超过10%的群体。由于这类人群基数大,必须采取批量操作和运营的方式落地运营策略,一般需要通过系统或产品实现,而不能主要依赖于人工

    • 212:可发展的一般性群体。这类群体购买新近度和订单金额一般,且购买频率低。考虑到其最大的群体基础,以及在新近度和订单金额上都可以,因此可采取常规性的礼品兑换和赠送、购物社区活动、签到、免运费等手段维持并提升其消费状态。

    • 211:可发展的低价值群体。这类群体相对于212群体在订单金额上表现略差,因此在211群体策略的基础上,可以增加与订单相关的刺激措施,例如组合商品优惠券发送、积分购买商品等

    • 312:有潜力的一般性群体。这类群体购买新近度高,说明最近一次购买发生在很短时间之前,群体对于公司尚有比较熟悉的接触渠道和认知状态;购物频率低,说明对网站的忠诚度一般;订单金额处于中等层级,说明其还具有可提升的空间。因此,可以借助其最近购买的商品,为其定制一些与上次购买相关的商品,通过向上销售等策略提升购买频次和订单金额

    • 112:可挽回的一般性群体。这类群体购买新近度较低,说明距离上次购买时间较长,很可能用户已经处于沉默或预流失、流失阶段;购物频率低,说明对网站的忠诚度一般;订单金额处于中等层级,说明其还可能具有可提升的空间。因此,对这部分群体的策略首先是通过多种方式(例如邮件、短信等)触达客户并挽回,然后通过针对流失客户的专享优惠(例如流失用户专享优惠券)措施促进其消费。在此过程中,可通过增加接触频次和刺激力度的方式,增加用户的回访、复购以及订单价值回报

    • 213:可发展的高价值群体。这类人群发展的重点是提升购物频率,因此可指定不同的活动或事件来触达用户,促进其回访和购买,例如不同的节日活动、每周新品推送、高价值客户专享商品等。

  • 第2类人群:占比为1%~10%的群体。这部分人群数量适中,在落地时无论是产品还是人工都可接入

    • 311:有潜力的低价值群体。这部分用户与211群体类似,但在购物新近度上更好,因此对其可采取相同的策略。除此以外,在这类群体的最近接触渠道上可以增加营销或广告资源投入,通过这些渠道再次将客户引入网站完成消费。

    • 111:这是一类在各个维度上都比较差的客户群体。一般情况下,会在其他各个群体策略和管理都落地后才考虑他们。主要策略是先通过多种策略挽回客户,然后为客户推送与其类似的其他群体,或者当前热销的商品或折扣非常大的商品。在刺激消费时,可根据其消费水平、品类等情况,有针对性地设置商品暴露条件,先在优惠券及优惠商品的综合刺激下使其实现消费,再考虑消费频率以及订单金额的提升。

    • 313:有潜力的高价值群体。这类群体的消费新近度高且订单金额高,但购买频率低,因此只要提升其购买频次,用户群体的贡献价值就会倍增。提升购买频率上,除了在其最近一次的接触渠道上增加曝光外,与最近一次渠道相关的其他关联访问渠道也要考虑增加营销资源。另外,213中的策略也要组合应用其中

    • 113:可挽回的高价值群体。这类群体与112群体类似,但订单金额贡献更高,因此除了应用112中的策略外,可增加部分人工的参与来挽回这些高价值客户,例如线下访谈、客户电话沟通等

  • 第3类群体:占比非常少,但却是非常重要的群体

    • 333:绝对忠诚的高价值群体。虽然用户绝对数量只有355,但由于其各方面表现非常突出,因此可以倾斜更多的资源,例如设计VIP服务、专享服务、绿色通道等。另外,针对这部分人群的高价值附加服务的推荐也是提升其价值的重点策略

    • 233、223和133:一般性的高价值群体。这类群体的主要着手点是提升新近购买度,即促进其实现最近一次的购买,可通过DM、电话、客户拜访、线下访谈、微信、电子邮件等方式直接建立用户挽回通道,以挽回这部分高价值用户

    • 322、323和332:有潜力的普通群体。这类群体最近刚完成购买,需要提升的是购买频次及购买金额。因此可通过交叉销售、个性化推荐、向上销售、组合优惠券、打包商品销售等策略,提升其单次购买的订单金额及促进其重复购买

导出数据

1)保存到excel中

rfm_gb.to_excel('sales_rfm_score1.xlsx')  # 保存数据为Excel

2)保存到MySQL中

需要再MySQL中创建库

# 需要安装pymysql,部分版本需要额外安装sqlalchemy
# 导入sqlalchemy的数据库引擎
from sqlalchemy import create_engine

# 创建数据库引擎,传入url规则的字符串
engine = create_engine('mysql+pymysql://root:123456@192.168.88.100:3306/test?charset=utf8')
# mysql+pymysql://root:123456@192.168.88.100:3306/test?charset=utf8
# mysql 表示数据库类型
# pymysql 表示python操作数据库的包
# root:123456 表示数据库的账号和密码,用冒号连接
# 192.168.88.100:3306/test 表示数据库的ip和端口,以及名叫test的数据库
# charset=utf8 规定编码格式

# df.to_sql()方法将df数据快速写入数据库
rfm_gb.to_sql('rfm_gb', engine, index=False, if_exists='append')
# 第一个参数为数据表的名称
# 第二个参数engine为数据库交互引擎
# index=False 表示不添加自增主键
# if_exists='append' 表示如果表存在就添加,表不存在就创建表并写入
相关推荐
大数据追光猿6 小时前
Python应用算法之贪心算法理解和实践
大数据·开发语言·人工智能·python·深度学习·算法·贪心算法
人类群星闪耀时7 小时前
物联网与大数据:揭秘万物互联的新纪元
大数据·物联网·struts
白水先森9 小时前
如何使用ArcGIS Pro高效查找小区最近的地铁站
经验分享·arcgis·信息可视化·数据分析
桃林春风一杯酒13 小时前
HADOOP_HOME and hadoop.home.dir are unset.
大数据·hadoop·分布式
桃木山人14 小时前
BigData File Viewer报错
大数据·java-ee·github·bigdata
B站计算机毕业设计超人14 小时前
计算机毕业设计Python+DeepSeek-R1高考推荐系统 高考分数线预测 大数据毕设(源码+LW文档+PPT+讲解)
大数据·python·机器学习·网络爬虫·课程设计·数据可视化·推荐算法
数造科技14 小时前
紧随“可信数据空间”政策风潮,数造科技正式加入开放数据空间联盟
大数据·人工智能·科技·安全·敏捷开发
逸Y 仙X17 小时前
Git常见命令--助力开发
java·大数据·git·java-ee·github·idea
caihuayuan418 小时前
PHP建立MySQL持久化连接(长连接)及mysql与mysqli扩展的区别
java·大数据·sql·spring
B站计算机毕业设计超人18 小时前
计算机毕业设计Hadoop+Spark+DeepSeek-R1大模型民宿推荐系统 hive民宿可视化 民宿爬虫 大数据毕业设计(源码+LW文档+PPT+讲解)
大数据·hadoop·爬虫·机器学习·课程设计·数据可视化·推荐算法