客户精细化管理

一 分析背景

对于销售型公司而言会员/客户精细化管理的必要性:

  • 更好的用户体验:对客户进行精细化分类管理,从而进行精准营销,给客户带来的是个性化、精准化的信息,可以根据不同的客户推送不同的信息,或者只针对可能有兴趣的客户推送信息,对客户来说这是一种体验和关怀;
  • 更高的销售回报:实施精细化营销能根据不同的客户提出不同的营销策略,对于忠诚类非价格敏感型客户推送高价值商品、非折扣类活动和定制化服务;对价格敏感型客户推送折扣类活动、优惠券和低价商品等,来提高客单价和收入水平。对不同的客户区分对待能有效提升销售收入;
  • 更低的成本支出:精准营销能有效的节省运营成本,主要体现为营销成本和促销成本。通过精准选择能转化的客户而非全部客户能有效的节省营销费用;在活动推送、优惠券发放时只针对价格敏感型、优惠券激励型客户能有效降低优惠券成本;

基于客户价值度来细分客户对销售型公司尤为关注,这是区分会员价值的重要手段和参考依据,也是衡量不同营销效果的关键指标之一。常用的价值度模型是RFM模型,该模型简单容易理解,并且业务落地能力非常强。
本案例含有2015年至2018年的用户订单抽样数据以及用户的等级表,目的是对客户进行细分分类,并将细分客户的特征概括出来,以便后续对不同群体的精细化运营,根据不同的群体制定差异化的营销和服务。

二 分析思路

如下图所示,其中在RFM模型中,使用RFM组合和RFM加权得分这两种方式来区分客户价值度,使用随机森林RandomForestClassifier分类器对客户进行分类,从而确定R、F、M各个维度的权重,进而求得RFM加权得分,在可视化可视化中科使用3d柱状图来来了解不同时期下RFM分组人数的变化,使用桑基图可以明显看到不同时期的用户价值度状态的转换,最后对结果进行分析。
在这里插入图片描述

三 数据分析

3.1 数据预览

import numpy as np
import pandas as pd
import time
import pymysql
from sklearn.ensemble import RandomForestClassifier
from pyecharts.charts import Bar3D,Sankey
import pyecharts.options as opts
#读取数据
sheet_names=['2015','2016','2017','2018','会员等级']
sheet_datas=[pd.read_excel('F:\Dataanalysis\python_book_v2\chapter5\sales.xlsx',sheet_name=i) for i in sheet_names] #使用列表推导式保存数据
#数据审查
for each_name,each_data in zip(sheet_names,sheet_datas):
    print('\n data_summary for {:=^50}'.format(each_name))
    print(each_data.head())
    print('{:*^70}'.format(' data_head '))
    print(each_data.describe())
    print('{:*^70}'.format(' data_info '))
    print(each_data.info())

输出太长只展示部分输出,如下:

  data_summary for =======================2015=======================
          会员ID         订单号       提交日期    订单金额
0  15278002468  3000304681 2015-01-01   499.0
1  39236378972  3000305791 2015-01-01  2588.0
2  38722039578  3000641787 2015-01-01   498.0
3  11049640063  3000798913 2015-01-01  1572.0
4  35038752292  3000821546 2015-01-01    10.1
***************************** data_head ******************************
               会员ID           订单号           订单金额
count  3.077400e+04  3.077400e+04   30774.000000
mean   2.918779e+10  4.020414e+09     960.991161
std    1.385333e+10  2.630510e+08    2068.107231
min    2.670000e+02  3.000305e+09       0.500000
25%    1.944122e+10  3.885510e+09      59.000000
50%    3.746545e+10  4.117491e+09     139.000000
75%    3.923593e+10  4.234882e+09     899.000000
max    3.954613e+10  4.282025e+09  111750.000000
***************************** data_info ******************************
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 30774 entries, 0 to 30773
Data columns (total 4 columns):
 #   Column  Non-Null Count  Dtype         
---  ------  --------------  -----         
 0   会员ID    30774 non-null  int64         
 1   订单号     30774 non-null  int64         
 2   提交日期    30774 non-null  datetime64[ns]
 3   订单金额    30774 non-null  float64       
dtypes: datetime64[ns](1), float64(1), int64(2)
memory usage: 961.8 KB
None

由以上可得,订单金额的分布不合理,其最小值0.5,而最大值有111750,方差很大,其订单金额存在极少量的缺失值。
经过了解,其订单金额值很小为使用优惠券支付的订单,对客户分类没有很好的效果,可以删除小于1元的客户,此外对于金额很大的订单为客户一次购买多件商品,为正常值。

3.2数据预处理

#数据处理
for ind,each_data in enumerate(sheet_datas[:-1]):
    each_data.dropna(inplace=True)                                                  #删除缺失值
    sheet_datas[ind]=each_data[each_data['订单金额']>1]                             #筛选出订单金额大于1的数据
    sheet_datas[ind]['max_date']=sheet_datas[ind]['提交日期'].max()                 #提取出每年最后的订单日期,用作计算RFM的时间节点
data_merge=pd.concat(sheet_datas[:-1],axis=0)
data_merge['date_interval']=(data_merge['max_date']-data_merge['提交日期']).dt.days  #将timedelta转化为int类型
data_merge['year']=data_merge['提交日期'].dt.year
print(data_merge.head())
     会员ID         订单号   提交日期    订单金额   max_date  date_interval  year
0  15278002468  3000304681 2015-01-01   499.0    2015-12-31       364      2015
1  39236378972  3000305791 2015-01-01  2588.0    2015-12-31       364      2015
2  38722039578  3000641787 2015-01-01   498.0    2015-12-31       364      2015
3  11049640063  3000798913 2015-01-01  1572.0    2015-12-31       364      2015
4  35038752292  3000821546 2015-01-01    10.1    2015-12-31       364      2015

循环将数据中的缺失值删除,并筛选出订单金额大于1的记录,这里以每年的最后订单日期作为时间节点,后续分别计算每年的RFM值,而不是对4年的数据统一做RFM计算;
其中使用了dt对时间数据进行处理,dt是pandas中series时间序列datetime类属性的访问对象,除了year,还可以使用date,dayofweek,year,hour等,dt.day返回的是日期,da.days返回的是日期间隔的天数,可用于timedelta类型

3.3 计算RFM得分

R:最近一次购买时间,该指标可以作为客户消费粘性的评估因素,如距离上次购买或消费时间过长,那么意味着用户可能处于沉默阶段或将要流失,此时应该采取措施挽回用户;
F:消费频次,是一定周期内消费的次数,该指标可以有效分析用户对于企业的消费粘性;
M:代表客户的平均购买金额,也可指累计购买金额;

计算出R、F、M指后需对其进行打分,RFM模型中打分一般采取5分制(三/二分制也可以,划分越多越详细),有两种比较常见的方式,一种是按照数据的分位数来打分,另一种是依据数据和业务的理解,进行分值的划分。根据划分后的值有两种处理思路
1将R、F、M得分进行拼接组合,对组合后的结果进行解读;
2基于R、F、M得分进行加权相加,得到加权得分,其总评分值可以做价值度排名,也可以作为其他数据分析的输入维度进行分析

#计算rfm值
rfm=data_merge.groupby(['year','会员ID'],as_index=False).agg({
    
    'date_interval':'min','提交日期':'count','订单金额':'sum'})
rfm.columns=['year','会员ID','r','f','m']
print(rfm.head())
print(rfm.iloc[:,2:].describe().T)
   year  会员ID   r   f       m
0  2015   267   197   2    105.0
1  2015   282   251   1    29.7
2  2015   283   340   1   5398.0
3  2015   343   300   1    118.0
4  2015   525    37   3    213.0
      count         mean          std  min   25%    50%     75%       max
r  148591.0   165.524043   101.988472  0.0  79.0  156.0   255.0     365.0
f  148591.0     1.365002     2.626953  1.0   1.0    1.0     1.0     130.0
m  148591.0  1323.741329  3753.906883  1.5  69.0  189.0  1199.0  206251.8

使用agg聚合函数得出r、f、m的值,并对其进行汇总描述,由上可知其f值大部分用户分布趋于1,表现为从min到75%分段值均为1,r和m值较为离散,这里使用r、m的中位数进行作为划分的边界值,由于行业特点,f使用1作为边界值,购买频率大于1即可认为是比较高的价值群体,代码如下:

#RFM打分
r_bins=[-1,165,370]
f_bins=[0,1,140]
m_bins=[1,189,206300]
rfm['r_score']=pd.cut(rfm['r'],r_bins,labels=['2','1'])
rfm['f_score']=pd.cut(rfm['f'],f_bins,labels=['1','2'])
rfm['m_score']=pd.cut(rfm['m'],m_bins,labels=['1','2'])

使用cut分箱(分箱默认大于左临界值,小于等于右临界值)的方法将r、f、m值进行打分,对于f、m将值小于临界值的打分为1,大于临界值的打分为2,对于r其值越小等级越高,故小于临界值打分为2,大于临界值打分为1

3.3.1 RFM组合

#RFM组合得分
rfm.loc[:,['r_score','f_score','m_score']]=rfm.loc[:,['r_score','f_score','m_score']].applymap(str)
rfm['rfm_group']=rfm['r_score']+rfm['f_score']+rfm['m_score']
print(rfm.head())
   year  会员ID  r   f     m   r_score  f_score  m_score  rfm_group
0  2015   267  197  2   105.0       1       2       1       121
1  2015   282  251  1    29.7       1       1       1       111
2  2015   283  340  1  5398.0       1       1       2       112
3  2015   343  300  1   118.0       1       1       1       111
4  2015   525   37  3   213.0       2       2       2       222

将’r_score’,‘f_score’,'m_score’转化为字符串然后将其组合得到rfm_group组合得分

3.3.2 RFM加权得分

要得到RFM的加权得分,需要值得各个因子的权重,r、f、m各个因子的权重可以根据业务部分的来进行定义,或根据会员的等级来确定其权重,会员等级也是衡量其价值度高低的**(此处的会员等级是根据客户的各种消费情况等等来进行定义的,而不是充钱所获得的会员等级)**。
此处我们建立rfm三个维度与会员等级的分类模型,然后根据模型输出的各个维度的权重来确定rfm各个维度的权重,如下:

#RFM的加权得分
rfm_merge=pd.merge(rfm,sheet_datas[-1],on='会员ID')
clf=RandomForestClassifier().fit(rfm_merge[['r','f','m']],rfm_merge['会员等级'])
weights=clf.feature_importances_
print('weights:',weights)
rfm.loc[:,['r_score','f_score','m_score']]=rfm.loc[:,['r_score','f_score','m_score']].applymap(np.int)
rfm['rfm_score']=(rfm['r_score']*weights[0]+rfm['f_score']*weights[1]+rfm['m_score']*weights[2]).round(3)
print(rfm.head())
weights: [0.40498853 0.00583437 0.58917709]
   year  会员ID    r  f       m  r_score  f_score  m_score rfm_group  rfm_score
0  2015   267  197  2   105.0        1        2        1       121      1.006
1  2015   282  251  1    29.7        1        1        1       111      1.000
2  2015   283  340  1  5398.0        1        1        2       112      1.592
3  2015   343  300  1   118.0        1        1        1       111      1.000
4  2015   525   37  3   213.0        2        2        2       222      2.000

使用随机森林以r、f、m三个值作为输入变量,会员等级为分类目标训练模型,通过模型的clf.feature_importances_得到各个维度的权重值weights,然后计算r、f、m的加权得分

3.4 保存结果至数据库

将RFM模型所得的结果保存至数据库,以供后续分析的使用

#筛选写入数据库的文件
write_db_data=rfm[['会员ID','r_score','f_score','m_score','rfm_score','rfm_group']]
insert_time=time.strftime('%F',time.localtime(time.time()))     #获得数据写入数据库的日期
#数据库配置信息
config={
    
    'host':'127.0.0.1',
       'user':'root',
       'password':'xuxuxuxu',
       'port':3306,
       'database':'python_mysql',
       'charset':'utf8'}
con=pymysql.connect(**config)           #数据库连接
cursor=con.cursor()                     #获得游标
table_name='sales_rfm_score'            #表名
#创建表
cursor.execute('''
               CREATE TABLE %s(
               userid VARCHAR(20) not null,
               r_score int(2) not null,
               f_score int(2) not null,
               m_score int(2) not null,
               rfm_score DECIMAL(10,2) not null,
               rfm_group VARCHAR(4) not null,
               insert_data VARCHAR(20)
               )ENGINE=InnoDB DEFAULT CHARSET=utf8'''%table_name)
#循环将数据写入数据库
for data in write_db_data.values:
    insert_sql='''insert into %s values('%s',%s,%s,%s,%s,'%s','%s')'''%(table_name,data[0],data[1],data[2],data[3],data[4],data[5],insert_time)
    cursor.execute(insert_sql)
    con.commit()   #提交命令
cursor.clouse()    #关闭游标
con.close()        #关闭数据库连接

使用了time库来获得写入数据库的当前时间,关于时间有四种类型 time spring时间字符串; datetime.datetime类型; time.struct_time时间元组; timestamp时间戳
上述使用了time.time()获取当前时间戳;
time.localtime()将时间戳类型转换为本时区的time.struct_time类型;
time.strftime(’%F’,time_tuple)将元组类型时间转换为字符串类型,其中‘%F’为‘%Y-%m-%d’的缩写;
本文使用了pymysql库来使用python连接Mysql数据库,将rfm结果数据写入python_mysql库中的’sales_rfm_score’表内,此处也可以将结果以csv文件导出至本地,然后将文件直接导入数据库(速度比使用python循环导入快一点)。
导入文件至数据库mysql代码如下

LOAD DATA INFILE 'F:/Dataanalysis/sales_rfm_score2.csv' INTO TABLE sales_rfm_score FIELDS TERMINATED BY ',' LINES TERMINATED BY '\r\n';

四 数据可视化

4.1 3D柱形图

为了更好的了解不同周期下RFM分组人数的变化,可以通过3D柱形图来展示其结果,代码如下:

#对每年每种类别客户聚类
display_data=rfm.groupby(['rfm_group','year'],as_index=False)['会员ID'].count().rename(columns={
    
    '会员ID':'counts'})
#画3D柱状图
bar3D=Bar3D(init_opts=opts.InitOpts(width='900px',height='600px'))
bar3D.add('rfm分组结果',
         data=[d.tolist() for d in display_data.values],                #相当于三维坐标
         grid3d_opts=opts.Grid3DOpts(width=150,height=100,depth=60),
         xaxis3d_opts=opts.Axis3DOpts(name='RFM分组',type_='category'),  
         yaxis3d_opts=opts.Axis3DOpts(name='年份',type_='category'),
         zaxis3d_opts=opts.Axis3DOpts(name='人数',type_='value'))
bar3D.set_global_opts(
        visualmap_opts=opts.VisualMapOpts(
            max_=display_data['counts'].max(),
             range_color=[
                "#313695",
                "#4575b4",
                "#74add1",
                "#abd9e9",
                "#e0f3f8",
                "#ffffbf",
                "#fee090",
                "#fdae61",
                "#f46d43",
                "#d73027",
                "#a50026",
            ]
            ))
bar3D.render(r'F:\Dataanalysis\python_book_v2\chapter5\bar3d.html')

3d柱状图如下所示:
在这里插入图片描述

使用groupby函数将每种分组每个年份的人数进行筛选出来,使用pyecharts中的bar3D进行画图,可以看出该3D图可旋转、缩放以便查看不同的细节,另外左侧的滑块条可以用来显示特定数量的分布结果。

4.2 桑基图

在不同的时间周期下客户的状态会发生改变,在桑基图中可以看到不同价值度之下用户的状态变化,同时可以结合轴做特定时间周期的分析,这相对于趋势图的数据统计,可以明显看到不同状态的用户轮转变换的比例和大小,(没有发生转换的客户无法在桑基图中体现,如2017年购买的新用户,在2018年未购买则无法体现)其代码如下:

#对不同年份的分组进行标记
rfm_sankey=rfm
rfm_sankey['year']=rfm_sankey['year'].map(str)
rfm_sankey['rfm_group']=rfm_sankey['rfm_group']+'-'+rfm_sankey['year']
sankey_data=rfm_sankey.pivot('会员ID','year','rfm_group')
#桑基图
node=rfm_sankey[['rfm_group']].drop_duplicates()
node.columns=['name']
nodes=node.to_dict(orient='records')
links=[]
for i in range(len(sankey_data.columns)-1):
    sample=sankey_data.iloc[:,[i,i+1]].dropna()
    sample.columns=['source','target']
    sample['value']=1
    link=sample.groupby(['source','target'],as_index=False)['value'].count().to_dict(orient='records')
    links.extend(link)
sankey=Sankey()
sankey.add('sankey',
          nodes,                                                                 #节点
          links,                                                                 #边
          linestyle_opt=opts.LineStyleOpts(opacity=0.2,curve=0.7,color='source'),
          label_opts=opts.LabelOpts(position='right'),                           #标签位置
          node_gap=15,                                                           #每一列任意两个矩形节点之间的间隔
          is_draggable=True,                                                     #控制节点交互的拖拽
          focus_node_adjacency='allEdges')  
sankey.set_global_opts(title_opts=opts.TitleOpts(title='RFM桑基图'))
sankey.render(r'F:\picture\sankey.html')

桑基图如下所示:
在这里插入图片描述

这里以不同年份间客户的价值度的变化来进行绘图,这里同样使用pycharts进行绘图,桑基图的绘制需要确定的是节点数据和边数据,其节点以及边的数据格式如下:

print(nodes[:3])
[{
    
    'name': '121-2015'}, {
    
    'name': '111-2015'}, {
    
    'name': '112-2015'}]
print(links[:3])
[{
    
    'source': '111-2015', 'target': '111-2016', 'value': 208}, 
{
    
    'source': '111-2015', 'target': '112-2016', 'value': 49}, 
{
    
    'source': '111-2015', 'target': '121-2016', 'value': 8}]

将数据转化为指定的格式,其中使用.to_dict(orient=‘records’)来将DataFrame格式数据转化为列表字典的形式

五 数据结论

5.1 基于RFM分组分析

对各类客户的数量进行汇总统计,以确定需要分析的目标群体,汇总统计如下:

counts=rfm.groupby('rfm_group')[['会员ID']].count().rename(columns={
    
    '会员ID':'数量'})
counts.sort_values(by='数量',ascending=False,inplace=True)
counts['累计占比(%)']=(counts['数量'].cumsum().div(counts['数量'].sum())*100).round(2)
print(counts)
            数量   累计占比(%)
rfm_group                
211        37215    25.05
111        34818    48.48
212        32461    70.32
112        30213    90.66
222         6409    94.97
122         4938    98.29
221         1936    99.60
121          601   100.00

由以上可知前五个分组的客户占总客户人数的98.29%,这里重点对这六类用户进行分析,根据用户数量可以分为两类,第一类为群体占比超过10%即211、111、212、112这几类;第二类为222,122此类客户数量虽然不多但是其价值度非常高。
对于第一类客户:
211客户:可发展的低价值群体,该类群体购买新近度高,说明最近一次购买发生在较短时间之前,群体对于公司有比较熟悉的接触渠道和认知状态,购买频率低说明客户忠诚度一般,订单金额不高,但由于其具有庞大的群体基础,对于此类客户可以借助其最近购买商品,向其推荐优惠折扣商品,增加与订单相关的刺激措施;
111客户:这是一类在各个维度上表现都较差的群体,一般在其他第一类群体的管理和策略都落地后再考虑他们,主要策略是先通过各种政策挽回客户,可根据其消费水平和品类等情况,有针对性的设置商品暴露条件,在优惠券及优惠商品的刺激下实现其消费,然后再考虑消费频率以及消费金额的提升;
212客户:有潜力的高价值群体,该类群体消费新近度高、订单金额高,并且人数占总人数有20%以上,是第一重点发展对象,但其购买频率低,因此只要提升其购买频次,用户群体的贡献价值就会倍增。提升购买频次上除了再起最近一次的接触渠道上增加曝光外,与最近一次购买渠道相关的渠道也要考虑增加营销资源,也可以指定不同的活动或事件来触达客户,促进其回访和购买,例如节日活动、每周新品推送、高价值客户专享商品等等;
112客户:可挽回的高价值群体,该类群体购买新近度低,说明距离上次购买时间较长,很可能客户已近处于沉默或流失状态,购买频率低,说明对网站忠诚度一般,但其订单金额贡献高,且有较大的群体基础,因此对这类群体的策略是可增加一定的成本多种方式来触达客户并挽回,然后通过针对流失客户的专享优惠促进其消费。
对于第二类客户:
222客户:忠诚的高价值群体,虽然用户占比不多,但是其各方面表现非常突出,对此类客户主要的策略是保持和维护,可设计VIP服务、专享服务、绿色通道等等,此外,针对这部分人群的高价值附加服务的推荐也是提升其价值的重点策略;
122客户:有潜力的高价值群体,这类客户主要着手点事提升其新近购买度,即促进其实现最近一次的购买,可通过电话、线下访谈、微信、电子邮件等方式建立客户挽回通道,以挽回这部分高价值群体。

5.2 基于3D柱状图的分析

重点人群分布:先通过3D柱形图做简单分析,在所有分组中211类客户是相对变化最大的,从2017年到2018年其人数增长了将近一倍,该人群新近购买度高,可以基于最近购买商品向其推荐其他商品,同时通过组合打包商品等销售策略提升其购买金额。
重点分组分布:除了211类客户外,212、111和121类客户的数量总数比211客户总数还多,因此后期也许加以关注
此外将左侧滑块进行拖动,过虑数量在3710以内的客户,可以发现其他分组人数很少,同时也可以清楚发现222类忠诚高价值客户数量逐年增加,可积极建立相应策略,对该类客户进行保持维护
在这里插入图片描述

5.3 基于桑基图的分析

桑基图主要用来观察各类客户的转换情况(对桑基图的rfm_group数据进行筛选能对特定分组的客户转化进行分析),从图中可以得出存在大比例的111、和211类客户的相互转化;在2017年至2018年有较多的112类客户转化为了222类客户,说明公司对112可挽回高价值群体的挽回策略效果不错,有较多112客户转化为了222客户,对转化的客户进行分析,找到其转化的关键因素,其提升转化效果。
使用桑基图只对’222’,‘122’,‘212’,'112’这四类客户的转换进行分析只需将上述代码如下进行修改:

#对不同年份的分组进行标记
rfm_sankey=rfm[['year','rfm_group','会员ID']]
rfm_sankey=rfm_sankey[rfm_sankey['rfm_group'].isin(['222','122','212','112'])]
rfm_sankey['year']=rfm_sankey['year'].map(str)
rfm_sankey['rfm_group']=rfm_sankey['rfm_group']+'-'+rfm_sankey['year']

在这里插入图片描述
以下为案例全部代码:

import numpy as np
import pandas as pd
import time
import pymysql
from sklearn.ensemble import RandomForestClassifier
from pyecharts.charts import Bar3D,Sankey
import pyecharts.options as opts
#读取数据
sheet_names=['2015','2016','2017','2018','会员等级']
sheet_datas=[pd.read_excel('F:\Dataanalysis\sales.xlsx',sheet_name=i) for i in sheet_names]  #使用列表推导式保存数据
#数据审查
for each_name,each_data in zip(sheet_names,sheet_datas):
    print('\n data_summary for {:=^50}'.format(each_name))
    print(each_data.head())
    print('{:*^70}'.format(' data_head '))
    print(each_data.describe())
    print('{:*^70}'.format(' data_info '))
    print(each_data.info())
#数据处理
for ind,each_data in enumerate(sheet_datas[:-1]):
    each_data.dropna(inplace=True)                                                  #删除缺失值
    sheet_datas[ind]=each_data[each_data['订单金额']>1]                             #筛选出订单金额大于1的数据
    sheet_datas[ind]['max_date']=sheet_datas[ind]['提交日期'].max()                 #提取出每年最后的订单日期,用作计算RFM的时间节点
data_merge=pd.concat(sheet_datas[:-1],axis=0)
data_merge['date_interval']=(data_merge['max_date']-data_merge['提交日期']).dt.days  #将timedelta转化为int类型
data_merge['year']=data_merge['提交日期'].dt.year
print(data_merge.head())
#计算rfm值
rfm=data_merge.groupby(['year','会员ID'],as_index=False).agg({
    
    'date_interval':'min','提交日期':'count','订单金额':'sum'})
rfm.columns=['year','会员ID','r','f','m']
print(rfm.head())
print(rfm.iloc[:,2:].describe().T)
#RFM打分
r_bins=[-1,165,370]
f_bins=[0,1,140]
m_bins=[1,189,206300]
rfm['r_score']=pd.cut(rfm['r'],r_bins,labels=['2','1'])
rfm['f_score']=pd.cut(rfm['f'],f_bins,labels=['1','2'])
rfm['m_score']=pd.cut(rfm['m'],m_bins,labels=['1','2'])
#RFM组合得分
rfm.loc[:,['r_score','f_score','m_score']]=rfm.loc[:,['r_score','f_score','m_score']].applymap(str)
rfm['rfm_group']=rfm['r_score']+rfm['f_score']+rfm['m_score']
#RFM的加权得分
rfm_merge=pd.merge(rfm,sheet_datas[-1],on='会员ID')
clf=RandomForestClassifier().fit(rfm_merge[['r','f','m']],rfm_merge['会员等级'])
weights=clf.feature_importances_
print('weights:',weights)
rfm.loc[:,['r_score','f_score','m_score']]=rfm.loc[:,['r_score','f_score','m_score']].applymap(np.int)
rfm['rfm_score']=(rfm['r_score']*weights[0]+rfm['f_score']*weights[1]+rfm['m_score']*weights[2]).round(3)
#筛选写入数据库的文件
write_db_data=rfm[['会员ID','r_score','f_score','m_score','rfm_score','rfm_group']]
insert_time=time.strftime('%F',time.localtime(time.time()))    #获得数据写入数据库的日期
#数据库配置信息
config={
    
    'host':'127.0.0.1',
       'user':'root',
       'password':'xufalin01',
       'port':3306,
       'database':'python_mysql',
       'charset':'utf8'}
con=pymysql.connect(**config) #数据库连接
cursor=con.cursor()           #获得游标
cursor.execute('show tables') #执行命令
table_name='sales_rfm_score'
#创建表
cursor.execute('''
               CREATE TABLE %s(
               userid VARCHAR(20) not null,
               r_score int(2) not null,
               f_score int(2) not null,
               m_score int(2) not null,
               rfm_score DECIMAL(10,2) not null,
               rfm_group VARCHAR(4) not null,
               insert_data VARCHAR(20)
               )ENGINE=InnoDB DEFAULT CHARSET=utf8'''%table_name)
#循环写入数据库
for data in write_db_data.values:
    insert_sql='''insert into %s values('%s',%s,%s,%s,%s,'%s','%s')'''%(table_name,data[0],data[1],data[2],data[3],data[4],data[5],insert_time)   #insert_time为当前时间
    cursor.execute(insert_sql)
    con.commit()   #提交命令
cursor.clouse()    #关闭游标
con.close()        #关闭数据库连接
#对每年每种类别客户聚类
display_data=rfm.groupby(['rfm_group','year'],as_index=False)['会员ID'].count().rename(columns={
    
    '会员ID':'counts'})
#画3D柱状图
bar3D=Bar3D(init_opts=opts.InitOpts(width='900px',height='600px'))
bar3D.add('rfm分组结果',
         data=[d.tolist() for d in display_data.values],      #相当于三维坐标
         grid3d_opts=opts.Grid3DOpts(width=150,height=100,depth=60),
         xaxis3d_opts=opts.Axis3DOpts(name='RFM分组',type_='category'),
         yaxis3d_opts=opts.Axis3DOpts(name='年份',type_='category'),
         zaxis3d_opts=opts.Axis3DOpts(name='人数',type_='value'))
bar3D.set_global_opts(
        visualmap_opts=opts.VisualMapOpts(
            max_=display_data['counts'].max(),
             range_color=[
                "#313695",
                "#4575b4",
                "#74add1",
                "#abd9e9",
                "#e0f3f8",
                "#ffffbf",
                "#fee090",
                "#fdae61",
                "#f46d43",
                "#d73027",
                "#a50026",
            ]
            ))
bar3D.render(r'F:\Dataanalysis\python_book_v2\chapter5\bar3d.html')
#对不同年份的分组进行标记
rfm_sankey=rfm[['year','rfm_group','会员ID']]
#rfm_sankey=rfm_sankey[rfm_sankey['rfm_group'].isin(['222','122','212','112'])]
rfm_sankey['year']=rfm_sankey['year'].map(str)
rfm_sankey['rfm_group']=rfm_sankey['rfm_group']+'-'+rfm_sankey['year']
sankey_data=rfm_sankey.pivot('会员ID','year','rfm_group')
#桑基图
node=rfm_sankey[['rfm_group']].drop_duplicates()
node.columns=['name']
nodes=node.to_dict(orient='records')
links=[]
for i in range(len(sankey_data.columns)-1):
    sample=sankey_data.iloc[:,[i,i+1]].dropna()
    sample.columns=['source','target']
    sample['value']=1
    link=sample.groupby(['source','target'],as_index=False)['value'].count().to_dict(orient='records')
    links.extend(link)
sankey=Sankey()
sankey.add('sankey',
          nodes,                                                                 #节点
          links,                                                                 #边
          linestyle_opt=opts.LineStyleOpts(opacity=0.2,curve=0.7,color='source'),
          label_opts=opts.LabelOpts(position='right'),                           #标签位置
          node_gap=15,                                                           #每一列任意两个矩形节点之间的间隔
          is_draggable=True,                                                     #控制节点交互的拖拽
          focus_node_adjacency='allEdges')  
sankey.set_global_opts(title_opts=opts.TitleOpts(title='RFM桑基图'))
sankey.render(r'F:\picture\sankey.html')
#RFM分组统计
counts=rfm.groupby('rfm_group')[['会员ID']].count().rename(columns={
    
    '会员ID':'数量'})
counts.sort_values(by='数量',ascending=False,inplace=True)
counts['累计占比(%)']=(counts['数量'].cumsum().div(counts['数量'].sum())*100).round(2)
print(counts)

猜你喜欢

转载自blog.csdn.net/weixin_46338676/article/details/109141254