对某在线教育平台用户使用RFM模型按价值分类
点击跳转到总目录
点击跳转基于RFM模型的Kmeans聚类算法实现
说在前面
项目背景
- 某在线教育平台,在对2019年用户进行盘点时发现,用户运营过于粗放,没能做到用户分类运营。老板想在新的一年里对不同的用户进行有针对性的营销,达到降低成本提高收入,提升投资回报率的目的。
- 使用工具:excel+jupyter
- excel:数据处理
- jupyter:数据可视化
RFM分析
- RFM分析是根据客户活跃程度和交易金额贡献,进行客户价值细分的一种方法;
- 可以通过R,F,M三个维度,将客户划分为8种类型。
RFM分析过程
- 计算RFM各项分值
R_S,距离当前日期越近,得分越高,最高5分,最低1分
F_S,交易频率越高,得分越高,最高5分,最低1分
M_S,交易金额越高,得分越高,最高5分,最低1分 - 归总RFM分值
RFM=100R_S+10F_S+1*M_S - 根据RFM分值对客户分类
明确目的
该在线教育平台的课程服务形式有两种:
- 图文音频视频形式的课程,用户自学这类课程的价格是100~1000元
- 提供学习服务,如作业答疑,1对1辅导等的培训类课程,这类课程的售价是1000~10000元。
- 目前的问题是:如何对用户分类,从而实现用户分类运营。可以使用RFM模型分析方法对用户按价值分类,从而实现精细化运营。
分析原因
对R,F,M值进行定义
- R值:某用户最后一次消费距离2020年1月1日的天数
- F值:某用户在2019年这一年的消费次数
- M值:某用户在2019年这一年的消费金额
- 要得到R,F,M这三个指标,需要数据的字段包括:用户ID(用户在该教育平台中唯一的编号),订单号(唯一标识用户购买课程的订单编号),订单金额(购买课程花了多少钱,单位是元),下单时间(购买课程的时间)。如下截图所示,数据来源网络,共有9935行x4列数据
统计R,F,M值
- 为了统计R,F,M值,需要将数据按照用户ID进行分组汇总,即每一行是一个用户的消费行为,如下表所示
给R,F,M值“打分”
- R值打分:当前业务处于快速发展期,用户一般是通过课程优惠类的拉新活动吸引来的,根据统计用户注册后一般会在3天内发生购买课程,每周都会有上新,7天是一个重要的时间节点,对于图文音频视频课程,则一般会在上线后40天更新完毕,培训类课程的授课周期一般是90天
- F值打分:根据当前业务发展需要,购买次数超过三次产生的利润才能覆盖获客成本,全平台的用户平均完成订单数是8单,中位数是12单,根据二八法则,期望20%的用户能贡献80%的利润,80%分位数上的用户订单数是16单
- M值打分:按照每20%分位数为一个档次,分别计算出该批用户20%,40%,60%,80%分位数的总消费金额
- 最终确定的打分规则见下表
- 根据这个打分规则,可以在前面的表格里加上二值打分,F值打分,M值打分,这三列并填上对应的分值,如下表所示
计算价值平均值
- 分别计算出R值打分,F值打分,M值打分这三列的平均值。R值打分平均值约为2.6,F值打分平均值3.54,M值打分平均值2.36
用户分类
- 对用户的情况进行分类,记录R,F,M三个值是高于平均值还是低于平均值。
- 如下表所示,如如果该用户的R值打分大于平均值,就在“R值高低”列记录为“高”,否则记录为“低”,F值,M值也同理。
数据可视化
数据预处理
引入数据分析python库
# ! pip install squarify
# 由于系统 python jupyter路径不一致所有各自安装的包不能通用
# ! pip install squarify
# jupyter 命令行下 运行这个即可
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import squarify
from mpl_toolkits.mplot3d import Axes3D
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
print(np.__version__)
print(pd.__version__)
print(mpl.__version__)
设置绘图中文支持
%matplotlib inline
mpl.rcParams["font.family"] = "SimHei"#解决中文乱码问题
mpl.rcParams["axes.unicode_minus"]=False # 用来正常显示负号
plt.rcParams['font.sans-serif']=['SimHei'] # 用来正常显示中文标签
%config InlineBackend.figure_format = 'svg'
排除警告
import warnings
warnings.filterwarnings("ignore")
读入excel处理好的数据
jiaoyu2 = pd.read_excel("22在线教育2.xlsx",sheet_name = "Sheet6",encoding="utf-8")
jiaoyu2.head(10)
# 备份数据
data_edu3 = jiaoyu2.copy()
# 认识以下整体数据
data_edu3.info()
# R值:用户最后一次消费距离2020/1/1的天数
# F值:用户在2019年消费次数
# M值:用户在2019年消费金额
data_edu3.describe()
R值,F值,M值
- 主要是对比和excel处理数据的区别
#R值
a = []
for i in data_edu3["最近一次消费时间间隔(R)"]:
if 0<=i<3:
a.append(5)
elif 3<=i<7:
a.append(4)
elif 7<=i<40:
a.append(3)
elif 40<=i<90:
a.append(2)
else:
a.append(1)
# print(a,len(a),type(a))
data_edu3.insert(4,"R值打分",a)
# data_edu3.head(20)
#F值
b = []
for i in data_edu3["消费频率(F)"]:
if 0<=i<3:
b.append(1)
elif 3<=i<8:
b.append(2)
elif 8<=i<12:
b.append(3)
elif 12<=i<16:
b.append(4)
else:
b.append(5)
data_edu3.insert(5,"F值打分",b)
# data_edu3.head(20)
#M值
c = []
for i in data_edu3["消费金额(M)"]:
if 0<=i<500:
c.append(1)
elif 500<=i<9500:
c.append(2)
elif 9500<=i<15000:
c.append(3)
elif 15000<=i<25000:
c.append(4)
else:
c.append(5)
data_edu3.insert(6,"M值打分",c)
data_edu3.head(20)
构建RFM模型,对用户进行分类
建立RFM模型
# 由上面可知
b = data_edu3["R值打分"].mean()# R值打分均值 y轴
c = data_edu3["F值打分"].mean()# F值打分均值 x轴
a = data_edu3["M值打分"].mean()# M值打分均值 z轴
print("R值:",b,"F值:",c,"M值:",a)
# delta图像调节参数
import turtle as t
def plot_dsw(data, delta, color, categories_nr):
x, y, z, dx, dy, dz = (0, 0, 0,categories_nr+delta, categories_nr+delta, data.iloc[:, 6].max())#薪资
b = categories_nr/2 + 0.5
fig = plt.figure(figsize=(6.8, 5.3), dpi=180)
ax = Axes3D(fig)
# 绘制(x, 3.54, 3.43)线
xc = np.arange(-1, dx+1, 0.01)
yc = 3.54 * np.ones(len(xc))
zc = 3.43 * np.ones(len(xc))
ax.plot(xc, yc, zc, color='#191970')
# 绘制(2.6, y, 3.43)线
yc1 = np.arange(-1, dy+1, 0.01)
xc1 = 2.6 * np.ones(len(yc1))
zc1 = 3.43 * np.ones(len(yc1))
ax.plot(xc1, yc1, zc1, color='#191970')
# 绘制(2.6, 3.54 ,z)线
zc2 = np.arange(-1, dz+1, 0.01)
yc2 = 3.54 * np.ones(len(zc2))
xc2 = 2.6 * np.ones(len(zc2))
ax.plot(xc2, yc2, zc2, color='#191970')
xx = [x, x, x+dx, x+dx, x]
yy = [y, y+dy, y+dy, y, y]
kwargs = {'color': color}
ax.plot3D(xx, yy, [z]*5, **kwargs)# 绘制底面
ax.plot3D(xx, yy, [z+dz]*5, **kwargs)# 绘制顶面
ax.plot3D([x, x], [y, y], [z, z+dz], **kwargs)# 绘制边线
ax.plot3D([x, x], [y+dy, y+dy], [z, z+dz], **kwargs)# 绘制边线
ax.plot3D([x+dx, x+dx], [y+dy, y+dy], [z, z+dz], **kwargs)# 绘制边线
ax.plot3D([x+dx, x+dx], [y, y], [z, z+dz], **kwargs)# 绘制边线
# 绘制平面
# x 平面
Y_p1 = np.arange(0, 5, 0.01)
X_p1 = 2.6 * np.ones(len(Y_p1))
X, Y = np.meshgrid(X_p1, Y_p1)
Z_p1 = np.linspace(0, data.iloc[:, 6].max(), 500)
Z = np.meshgrid(Z_p1, Z_p1)[0]
ax.plot_surface(X, Y, Z, alpha=0.45, color='gray')
# y平面
X_p2 = np.arange(0, 5, 0.01)
Y_p2 = 3.54 * np.ones(len(X_p2))
X1, Y1 = np.meshgrid(Y_p2, X_p2)
Z_p2 = np.linspace(0, data.iloc[:, 6].max(), 500)
Z1 = np.meshgrid(Z_p2, Z_p2)[0]
ax.plot_surface(Y1, X1, Z1, alpha=0.35, color='gray')
# z平面
X_p2 = np.arange(0, 5, 0.01)
Z_p2 = 3.43 * np.ones(len(X_p2))
X1, Y1 = np.meshgrid(X_p2, X_p2)
# Z_p2 = np.linspace(0, data.iloc[:, 6].max(), 500)#斜平面
Z1 = np.meshgrid(Z_p2, Z_p2)[0]
ax.plot_surface(Y1, X1, Z1, alpha=0.36, color='gray')
ax.set_xlim(0, dx)
ax.set_ylim(0, dy)
ax.set_zlim(0, dz)
# ax.view_init(elev=20,azim=0)
#添加注释
ax.text(3.8, 4.8, 4.5, 'R高F高M高-高价值客户1', (1,2,3), color='r', alpha=1)
ax.text(3.5, 4.9, 1.5, 'R高F高M低-一般价值客户5', (1,2,3), color='r', alpha=1)
ax.text(4, 0, 3.5, 'R高F低M低-一般发展客户8', (1,2,3), color='r', alpha=1)
ax.text(3.5,1.5, 5, 'R高F低M高-重点发展客户4', (1,2,3), color='r', alpha=1)
ax.text(0.8, 4, 5, 'R低F高M高-重点保持客户2', (1,2,3), color='r', alpha=1)
ax.text(0, 4.9, 1.5, 'R低F高M低-一般保持客户6', (1,2,3), color='r', alpha=1)
ax.text(0.5, 1.6, 4.5, 'R低F低M高-重点挽留客户3', (1,2,3), color='r', alpha=1)
ax.text(0.1, 1.6, 2, 'R低F低M低-潜在客户7', (1,2,3), color='r', alpha=1)
ax.scatter(data['R值打分'], data['F值打分'], data['M值打分'], color='#FF00FF')
ax.set_xlabel('最近一次消费时间间隔(R)', color='b', fontsize=13)
ax.set_ylabel('消费频率(F)', color='b', fontsize=13)
ax.set_zlabel('消费金额(M)', color='b', fontsize=13)
ax.set_title('RFM模型分析', fontsize=16)
plot_dsw(data_edu3, 1, '#0000FF', 4)
对用户分类
def filter_categories(data_info, center, zone):
if zone == 1:
data_zone1 = data_info.loc[(data_info.iloc[:, 4] > center[0]) & (data_info.iloc[:, 5]> center[1]) \
& (data_info.iloc[:, 6] > center[2]), '客户 Id']
return data_zone1
elif zone == 2:
data_zone2 = data_info.loc[(data_info.iloc[:, 4] <= center[0]) & (data_info.iloc[:, 5] > center[1]) \
& (data_info.iloc[:, 6] > center[2]), '客户 Id']
return data_zone2
elif zone == 3:
data_zone3 = data_info.loc[(data_info.iloc[:, 4] <= center[0]) & (data_info.iloc[:, 5] > center[1]) \
& (data_info.iloc[:, 6] <= center[2]), '客户 Id']
return data_zone3
elif zone == 4:
data_zone4 = data_info.loc[(data_info.iloc[:, 4] > center[0]) & (data_info.iloc[:, 5] > center[1]) \
& (data_info.iloc[:, 6] <= center[2]), '客户 Id']
return data_zone4
elif zone == 5:
data_zone5 = data_info.loc[(data_info.iloc[:, 4] > center[0]) & (data_info.iloc[:, 5] <= center[1]) \
& (data_info.iloc[:, 6] > center[2]), '客户 Id']
return data_zone5
elif zone == 6:
data_zone6 = data_info.loc[(data_info.iloc[:, 4] <= center[0]) & (data_info.iloc[:, 5] <= center[1]) \
& (data_info.iloc[:, 6] > center[2]), '客户 Id']
return data_zone6
elif zone == 7:
data_zone7 = data_info.loc[(data_info.iloc[:, 4] <= center[0]) & (data_info.iloc[:, 5] <= center[1]) \
& (data_info.iloc[:, 6] <= center[2]), '客户 Id']
return data_zone7
elif zone == 8:
data_zone8 = data_info.loc[(data_info.iloc[:, 4] > center[0]) & (data_info.iloc[:, 5] <= center[1]) \
& (data_info.iloc[:, 6] <= center[2]), '客户 Id']
return data_zone8
x = []
for y in range(1,9):
z = (filter_categories(data_edu3, (2.6,3.54,3.43), y).shape[0])
x.append(z)
print(x,len(x),type(x))
柱形图
plt.figure(figsize = (500,200))#设置画布大小
xx = np.array(["R高F高M高-高价值客户1","R低F高M高-重点保持客户2","R低F低M高-重点挽留客户3","R高F低M高-重点发展客户4","R高F高M低-一般价值客户5","R低F高M低-一般保持客户6","R低F低M低-潜在客户7","R高F低M低-一般发展客户8"])
yy = np.array(x)
a = xx
b = yy
plt.bar(a,b,width=0.5,align="center",label="用户数")
plt.title("不同价值类别的用户数量",loc="center")
for ab,cd in zip(a,b):
plt.text(ab,cd,cd,ha="center",va = "bottom",fontsize=12)
plt.xlabel('用户价值分别')
plt.xticks(rotation=90)
plt.ylabel('用户数')
plt.legend()#显示图例
#将图表保存到本地
# plt.savefig("E:/编程语言-青铜/面试准备/01.简历/03.简历几个模块/0猎聘/6.jpg")
饼图
# 饼图
plt.subplot(1,1,1)
# yy 上面有
xxx = ["R高F高M高-高价值客户1","R低F高M高-重点保持客户2","R低F低M高-重点挽留客户3","R高F低M高-重点发展客户4","R高F高M低-一般价值客户5","R低F高M低-一般保持客户6","R低F低M低-潜在客户7","R高F低M低-一般发展客户8"]
explode = [0.1,0,0,0,0,0,0,0]#让第一块离圆心远一点
labeldistance = 1.1
plt.pie(yy,labels = xxx,autopct = '%.0f%%',shadow = True,explode = explode,radius = 1.0,labeldistance = labeldistance)
#设置标题
plt.title("不同价值类别的用户数量",loc="center")
# plt.savefig("E:/编程语言-青铜/面试准备/01.简历/03.简历几个模块/0猎聘/7.jpg")
树地图
# 绘制树地图
import squarify
# 指定每一块的大小
size = np.array([204, 118, 27, 56, 35, 38, 161, 151])
# 指定每一块的文字标签
fenlei = xx
# 指定每一块的数值标签
rate = np.array(["26%","15%","3%","7%","4%","5%","20%","19"])
# 指定每一块的颜色
colors = ['steelblue','#9999ff','red','indianred','green','yellow','orange']
# 绘图
plot = squarify.plot(sizes = size,label = fenlei,color = colors,value = rate,edgecolor = 'white',linewidth = 3)
# 设置标题大小
plt.title('不同价值类别的用户数量',fontdict = {'fontsize':12})
# 去除坐标轴
plt.axis('off')
# 去除上边框和右边框的刻度
plt.tick_params(top = 'off',right = 'off')
# 保存图表到本地
# plt.savefig("E:/编程语言-青铜/面试准备/01.简历/03.简历几个模块/0猎聘/7.jpg")
得出结论
- 从上面数据可视化的图中可以发现
- 重要价值用户最多,说明有很多种子用户
- 一般挽留和一般发展用户也较多用户呈现出较高价值和较低价值都很多的情况
提出建议
- 通过分类后,每个用户都有其对应的价值标签,例如用户10015是一般价值用户,用户10030是重要价值用户。这就使运营人员了解到平台上哪些用户是最好的用户,哪些用户是无价值用户,哪些用户有可能流失。
- 这就可以针对不同的用户,制定不同的运营策略,具体的策略描述如下表所示
end
- 以上便是我对这份在线教育用户数据进行的一次探索分析,相信其中会有很多不足之处,欢迎有缘读到此篇文章的小朋友们批评指正,如有能启发或帮助到你的地方,我将倍感荣幸。(●’◡’●)