导语:
RMF模型可以用来管理客户价值,通过R、F、M三个维度对用户进行分类,以便做更加精细化运营。具体解释大家可以在各大平台查阅,本文将RFM模型用于在线音乐产品中,以此记录。真诚欢迎拍砖!
一、数据监控
新版本迭代后增加vip歌单功能,用户需购买vip成为会员后方可进行播放行为。因此,主要对vip用户数、付费率进行监控。
二、发现问题
自上线以来,付费率无明显提升。想着看下哪些用户适合定向推送vip内容,哪些适合赠送vip以增加粘性,因此有了这次RFM探索性分析模型。
常用的探索性分析方法包括:RFM分析、聚类分析、因子分析、对应分析等
三、设立解决目标
通过RFM模型,探索用户价值,将用户分重要价值用户、重要发展用户、重要保持用户、重要挽回用户等8个层级进行精细化运营。目的是提高产品付费率。
四、数据探索与分析***
1.数据收集
1) RFM三个维度
虽然RFM通常指发生交易的用户,但由于业务不同,在线音乐产品的交易行为即是播放行为。
R:最近一次发起播放的日期(原:最近一次消费到当前的时间间隔)
F:近半年发起播放的总次数(原:固定时间内购买次数)
M:近半年发起播放的总播放时长(原:固定时间内消费总额)
2) Mysql取数据
- 如何取R:用row_number() over函数取将uid近半年发起播放的期降序排列,并取出top1即为最近一次发起播放的日期
select t.uid,t.cdate as R
from
(select uid,cdate,
row_number() over (partition by uid order by cdate desc) as rank
from 表名
where 日期区间 )t
where rank=1
- 如何取F:以uid为分组,计算近半年内日期出现的次数
select uid,count(distinct cdate) as F
from 表名
where 日期区间
group by uid
- 如何取 M:以uid为分组,计算近半年内日期播放总时长(分钟为单位)
select uid,sum(pt)/60 as pt
fromfrom 表名
where 日期区间
group by uid
3) python了解并清洗数据
- 导入csv文件
import os
import pandas as pd
import numpy as np
import datetime
os.chdir('/Users/xy/Desktop/RFM')
file = pd.read_csv('RFM_data.csv',engine = 'python') #104w行
- 了解文件格式
print(file.shape)#df的形状 104w条记录
print(file.head(10))#获取前10行
print(file.tail(10))#获取后10行
print(file.dtypes)#获取各列类型
print(file.describe())#了解各字段描述性统计
104w条记录,F最大值183,最小值1,还算正常;M最大值730w分钟,均值267分钟,需要清洗;R日期正常。
uid设备id也存在null和不符合规范的情况,也需要清洗。
- 清洗数据
file2 = file[file['uid'].str.len()>=15]#一般来说,uid长度不能少于15个字符,90w行
file3 = file2[file2['playsum']<=263520]#总播放时长清洗,90w
file4 = file3[file3['frequency']>=1]#播放次数清洗,90w
file4.reset_index(drop = True,inplace=True)#清洗后重新设置行索引
df['recently']=pd.to_datetime(df['recently'],format='%Y%m%d')#将int转换成日期类型
df = file4
df['playsum'] =(df['playsum']).astype(np.int) #对播放时长取整数
df['playsum'].replace(0,np.NaN,inplace=True)
df.dropna(axis=0,how='any',inplace=True)#将播放时长为0行删除
df.reset_index(drop = True,inplace=True)#重新索引,还剩75w行
df['recently_diff'] = (pd.to_datetime(df['recently'])-pd.to_datetime('2020-03-01')).map(lambda x:x.days)#计算最近播放日期与3月1日之间的距离
- 分析数据
df2 = pd.DataFrame({'recently_counts':df['recently_diff'].value_counts(),
'recently_sum':df['recently_diff'].value_counts().sum()})
df2['recently_%'] = (df2['recently_counts']/df2['recently_sum']*100).round(1)
df2.reset_index(inplace = True)
plt.bar(df2['index'],df2['recently_%'])
plt.xlabel('最近一次播放日期距今天天数')
plt.ylabel('占总体百分比(单位:%)')
最近一次播放日期距今天天数占比情况.png
图上处于波浪趋势,天数越久远波动越小。
df3 = pd.DataFrame({'frequency_counts':df['frequency'].value_counts(),
'frequency_sum':df['frequency'].value_counts().sum()})
df3['frequency_%'] = (df3['frequency_counts']/df3['frequency_sum']*100).round(1)
df3.reset_index(inplace = True)
plt.bar(df3['index'][:60],df3['frequency_%'][:60])#后60个忽略不计
plt.xlabel('近半年播放次数')
plt.ylabel('占总体百分比(单位:%)')
近半年播放次数占比情况.png
柱形图呈长尾分布,说明更多用户近半年仅播放1次就流失了,大于60以外的用户属于高频播放用户,稍后在分箱时尤其注意。播放日期也是同样
#直方图看R分布,能用等频分箱
ax_R = plt.subplot(223)
ax_R.hist(df['recently_diff'])
df['recently_cut'] = pd.qcut(df['recently_diff'],10,duplicates='drop',labels = [1,2,3,4,5,6,7,8,9,10])
print(df['recently_cut'].value_counts()) #验证
#直方图看F分布,长尾分布,播放次数较少的频次较大,采用自定义分箱方法
ax_F = plt.subplot(222)
plt.title('近半年播放次数频次分布')
y2 = df['frequency'].value_counts()
x2 = y2.index
ax_F.hist(df['frequency'])
#直方图看M分布,长尾分布,播放时长较少的频次较大,采用自定义分箱方法
ax_M = plt.subplot(222)
plt.title('近半年播放时长频次分布')
y2 = df['playsum'].value_counts()
x2 = y1.index
ax_M.hist(df['playsum'])
#开始分箱,分为5个档位
#R近半年最近一次播放日期距当今天数5-1分,反向打分
df['recently_cut'] = pd.qcut(df['recently_diff'],5,duplicates='drop',labels = [1,2,3,4,5])
df['recently_cut'] = 6-df['recently_cut']
#F近半年播放次数1-5分,正向打分
binsF = [0,1,12,40,114,200]
df['frequency_cut'] = pd.cut(df['frequency'],bins = binsF,labels = [1,2,3,4,5])
#M近半年播放总时长1-5分,正向打分
binsM = [0,12,99,600,2000,26000]
df['playsum_cut'] = pd.cut(df['playsum'],bins = binsM,labels = [1,2,3,4,5])
df_new = df[['uid','recently_cut','frequency_cut','playsum_cut']]
五、分析结果
8种用户分层.png这里有个默认的说法:
最近有过播放行为的客户,再次播放几率更高;
播放次数高的客户比播放次数低的客户更有可能再次播放;
播放时长较长的客户更有可能再次播放;
将RFM三个字段以3位分界线,大于等于3值认为是高等级变更为1,小于3认为低等级变更为0
def f(x):
if x>=3:
return 1
else:
return 0
def ff(x):
if x=='111':
return '重要价值客户'
elif x=='011':
return '重要唤回客户'
elif x=='101':
return '重要深耕客户'
elif x=='001':
return '重要挽回客户'
elif x=='110':
return '潜力客户'
elif x=='100':
return '新客户'
elif x=='010':
return '一般维持客户'
else:
return '流失客户'
df_new['recently_score'] = df_new['recently_cut'].apply(lambda x:f(x))
df_new['frequency_score'] = df_new['frequency_cut'].apply(lambda x:f(x))
df_new['playsum_score'] = df_new['playsum_cut'].apply(lambda x:f(x))
df_new['scores'] = df_new['recently_score'].map(str)+df_new['frequency_score'].map(str)+df_new['playsum_score'].map(str)
df_new['customer_type'] = df_new['scores'].apply(lambda x:ff(x))
print(df_new.tail(100))
部分结果.png
可视化
user_count = df_new.groupby('customer_types')['uid'].count()#各层级用户个数
play_mean = df_new.groupby('customer_types')['playsum'].mean()#各层级播放总时长
freq_mean = df_new.groupby('customer_types')['frequency'].mean()#各层级播放总次数
df_new2 = pd.DataFrame({'各层级用户个数':user_count,
'各层级播放总时长':play_mean.astype(np.int),
'各层级播放总次数':freq_mean.astype(np.int)})
df_new2['各层级用户个数占比'] = (df_new2['各层级用户个数']/df_new2['各层级用户个数'].sum()).apply(lambda x:format(x,'.1%'))
df_new2['各层级播放总时长占比'] = (df_new2['各层级播放总时长']/df_new2['各层级播放总时长'].sum()).apply(lambda x: format(x, '.1%'))
df_new2['各层级播放总次数占比'] = (df_new2['各层级播放总次数']/df_new2['各层级播放总次数'].sum()).apply(lambda x: format(x, '.1%'))
print(df_new2 )
各层级用户个数及占比情况
#设置字体、图形样式
sns.set_style("whitegrid")
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['font.family']='sans-serif'
matplotlib.rcParams['axes.unicode_minus'] = False
matplotlib.fontsize='15'
#取做图数据
x=range(len(df_new2.index))
y1=df_new2['各层级用户个数']
y2=((df_new2['各层级用户个数']/df_new2['各层级用户个数'].sum())*100).round(1)
#设置图形大小
plt.rcParams['figure.figsize'] = (20.0,10.0)
fig = plt.figure()
#画柱子
ax1 = fig.add_subplot(111)
ax1.bar(x, y1,alpha=.7,color='cadetblue')
ax1.set_ylabel('各层级用户个数',fontsize='15')
ax1.set_title("各层级用户个数及占比情况",fontsize='20')
plt.yticks(fontsize=15)
plt.xticks(x,df_new2.index)
plt.xticks(fontsize=15)
# 画折线图
ax2 = ax1.twinx() # 这个很重要噢
ax2.plot(x, y2, 'blueviolet',marker='*',ms=10)
ax2.set_ylabel('各层级用户个数占比(%)',fontsize='15')
ax2.set_xlabel('各层级用户个数占比(%)')
# 纵轴标签
plt.yticks(fontsize=15)
plt.xticks(x,df_new2.index)
plt.xticks(fontsize=15)
plt.grid(False)
#添加数据标签
for x, y ,z in zip(x,y2,y1):
plt.text(x, y+0.8, str(y), ha='center', va='bottom', fontsize=14,rotation=0)
plt.text(x, z-z, str(z), ha='center', va='bottom', fontsize=14,rotation=0)
各层级用户个数及占比情况.png
各层级播放总时长占比情况
#取做图数据
x=range(len(df_new2.index))
y1=df_new2['各层级播放总时长']
y2=((df_new2['各层级播放总时长']/df_new2['各层级播放总时长'].sum())*100).round(1)
#设置图形大小
plt.rcParams['figure.figsize'] = (20.0,10.0)
fig = plt.figure()
#画柱子
ax1 = fig.add_subplot(111)
ax1.bar(x, y1,alpha=.7,color='cadetblue')
ax1.set_ylabel('各层级播放总时长',fontsize='15')
ax1.set_title("各层级播放总时长占比情况",fontsize='20')
plt.yticks(fontsize=15)
plt.xticks(x,df_new2.index)
plt.xticks(fontsize=15)
# 画折线图
ax2 = ax1.twinx() # 这个很重要噢
ax2.plot(x, y2, 'blueviolet',marker='*',ms=10)
ax2.set_ylabel('各层级播放总时长占比(%)',fontsize='15')
ax2.set_xlabel('各层级播放总时长占比(%)')
# 纵轴标签
plt.yticks(fontsize=15)
plt.xticks(x,df_new2.index)
plt.xticks(fontsize=15)
plt.grid(False)
#添加数据标签
for x, y ,z in zip(x,y2,y1):
plt.text(x, y+0.8, str(y), ha='center', va='bottom', fontsize=14,rotation=0)
plt.text(x, z-z-1, str(z), ha='center', va='bottom', fontsize=14,rotation=0)
各层级播放总时长占比情况.png
各层级播放总次数占比情况
#取做图数据
x=range(len(df_new2.index))
y1=df_new2['各层级播放总次数']
y2=((df_new2['各层级播放总次数']/df_new2['各层级播放总次数'].sum())*100).round(1)
#设置图形大小
plt.rcParams['figure.figsize'] = (20.0,10.0)
fig = plt.figure()
#画柱子
ax1 = fig.add_subplot(111)
ax1.bar(x, y1,alpha=.7,color='cadetblue')
ax1.set_ylabel('各层级播放总次数',fontsize='15')
ax1.set_title("各层级播放总次数占比情况",fontsize='20')
plt.yticks(fontsize=15)
plt.xticks(x,df_new2.index)
plt.xticks(fontsize=15)
# 画折线图
ax2 = ax1.twinx() # 这个很重要噢
ax2.plot(x, y2, 'blueviolet',marker='*',ms=10)
ax2.set_ylabel('各层级播放总次数占比(%)',fontsize='15')
ax2.set_xlabel('各层级播放总次数占比(%)')
# 纵轴标签
plt.yticks(fontsize=15)
plt.xticks(x,df_new2.index)
plt.xticks(fontsize=15)
plt.grid(False)
#添加数据标签
for x, y ,z in zip(x,y2,y1):
plt.text(x, y+0.8, str(y), ha='center', va='bottom', fontsize=14,rotation=0)
plt.text(x, z-z+1, str(z), ha='center', va='bottom', fontsize=14,rotation=0)
各层级播放总次数占比情况.png
六、制作模型
后续补充
七、结论
- 经过分析可以知道一般发展(新用户)和一般挽留客户(流失用户)占比最高,一般挽留指最近未发起播放,播放次数极少,播放时长极少的用户,属于流失用户,由于占比较大,因此可以对这些用户建立流失模型,分析流失用户与留存用户的区别,采取相应措施避免流失,进一步在预算可观的前提下挽留部分值得挽留的流失用户。文章请戳:用户流失预测模型
-
一般发展(新用户)客户指最近有过播放,但是播放时长和播放次数较少,这类用户可能多数为新客。对于任何产品来说,新用户在初期属于探索阶段,可以让用户尝到一些甜头培养使用产品的习惯,建议赠送VIP活动。
-
一般价值(潜力用户)客户指最近有过播放和一定的播放次数,但是播放时长较少,这类属于有潜力的用户,建议通过运营手段延长听歌时间。
-
重要发展(深耕)用户与重要价值用户个数相似,但是在播放次数和播放总时长相差甚远,要想让重要发展用户成为价值用户,可以通过分析两个层级用户在产品中行为不同,或着重对播放数据进行分析,比如:两类用户播放路径、播放内容的差别,找到重要价值用户的aha时刻对重要发展用户进行培养推广。
-
重要保持用户指最近未播放但近半年播放次数和播放时长占比较高的用户,这种用户在我们的产品中有一定财富积累,最近没有播放可能是竞品影响或没有唤起播放习惯。因此对这类用户进行唤回,建议根据用户播放记录结合VIP文案推送个性化推荐内容的push。
-
重要价值用户是我们的核心用户,可以通过分析这类用户具体行为得到很多增长点,可以分析这类用户的行为路径或进行用户访谈,了解产品的吸引力或优化之处。同时他们购买vip的概率也是极高的,建议对这类用户重点推广VIP付费。
-
重要挽留播放次数较少但是在播放时长上贡献较大,说明用户一旦活跃就会为发生长时间播放,需要为其培养活跃习惯,建议可以对这类用户运营连续播放打卡赠1个月VIP活动等。(与新用户不同的是,重要挽留客户用户获得VIP活动需要付出一定时间)
八、实施方案
事后运营同学针对不同用户有针对性的开展相应的活动:
从数据上看,音乐能量活动效果很好,新客送vip活动需要改进。
后续对付费率的监控,本次活动使付费率(实际付费,除赠送)提升0.8个点
总结:
做RFM模型时,最让我头疼之处在于分箱,因为真实的业务数据中播放时长和播放次数肯定是小占大量,也就是长尾分布,查了很多有关分箱的资料。经过试验发现并不适合等频和等距,于是只能用自定义分箱,这里不确定自定义划分的准确性,如果以后在学到可用的资料,我会及时更新本文。卡方分箱看到一篇好文章,准备试验一下~感谢,听我的碎碎念!!!Peace and love❤️
网友评论