客戶精細化管理

weixin_46338676發表於2020-10-19

一 分析背景

對於銷售型公司而言會員/客戶精細化管理的必要性:

  • 更好的使用者體驗:對客戶進行精細化分類管理,從而進行精準營銷,給客戶帶來的是個性化、精準化的資訊,可以根據不同的客戶推送不同的資訊,或者只針對可能有興趣的客戶推送資訊,對客戶來說這是一種體驗和關懷;
  • 更高的銷售回報:實施精細化營銷能根據不同的客戶提出不同的營銷策略,對於忠誠類非價格敏感型客戶推送高價值商品、非折扣類活動和定製化服務;對價格敏感型客戶推送折扣類活動、優惠券和低價商品等,來提高客單價和收入水平。對不同的客戶區分對待能有效提升銷售收入;
  • 更低的成本支出:精準營銷能有效的節省運營成本,主要體現為營銷成本和促銷成本。通過精準選擇能轉化的客戶而非全部客戶能有效的節省營銷費用;在活動推送、優惠券發放時只針對價格敏感型、優惠券激勵型客戶能有效降低優惠券成本;

基於客戶價值度來細分客戶對銷售型公司尤為關注,這是區分會員價值的重要手段和參考依據,也是衡量不同營銷效果的關鍵指標之一。常用的價值度模型是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)

相關文章