1. 程式人生 > >90萬條資料玩轉RFM使用者分析模型

90萬條資料玩轉RFM使用者分析模型

RFM,是一種經典的使用者分類、價值分析模型:

R,Rencency,即每個客戶有多少天沒回購了,可以理解為最近一次購買到現在隔了多少天。

F,Frequency,是每個客戶購買了多少次。

M,Monetary,代表每個客戶平均購買金額,也可以是累計購買金額。

這三個維度,是RFM模型的精髓所在,幫助我們把混雜一體的客戶資料分成標準的8類,然後根據每一類使用者人數佔比、金額貢獻等不同的特徵,進行人、貨、場三重匹配的精細化運營。

用Python建立RFM模型,整體建模思路分為五步,分別是資料概覽、資料清洗、維度打分、分值計算和客戶分層。

一:資料概覽

開發環境:jupyter Notebook, python 3.6

import pandas as pd
import numpy as np
import os

os.chdir('F:\\50mat')

df = pd.read_excel('PYTHON-RFM實戰資料.xlsx')
df.head()

 

# 列印結果

品牌名稱	買家暱稱	付款日期	訂單狀態	實付金額	郵費	省份	城市	購買數量
0	一隻阿木木	棒西瓜皮的店	2019-04-18 11:05:26	交易成功	210	0	北京	北京市	1
1	一隻阿木木	8fiona_c8	2019-04-18 11:08:03	交易成功	53	0	上海	上海市	4
2	一隻阿木木	3t_1479778131547_04	2019-04-18 11:13:01	交易成功	169	0	上海	上海市	1
3	一隻阿木木	0kexintiantian20	2019-04-18 11:13:19	付款以後使用者退款成功,交易自動關閉	107	0	北京	北京市	1
4	一隻阿木木	ysxxgx	2019-04-18 11:18:07	付款以後使用者退款成功,交易自動關閉	254	0	江蘇省	蘇州市	2

在訂單狀態中,交易成功、使用者退款導致交易關閉的,那還包括其他狀態嗎?退款訂單對於我們模型價值不大,需要在後續清洗中剔除。

df['訂單狀態'].unique()

 

# 列印結果
array(['交易成功', '付款以後使用者退款成功,交易自動關閉', '訂單狀態'], dtype=object)

再觀察資料的型別和缺失情況:

df.info()

 

# 列印結果
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 922658 entries, 0 to 922657
Data columns (total 9 columns):
品牌名稱    922658 non-null object
買家暱稱    922658 non-null object
付款日期    922658 non-null object
訂單狀態    922658 non-null object
實付金額    922658 non-null object
郵費      922658 non-null object
省份      922658 non-null object
城市      922626 non-null object
購買數量    922658 non-null object
dtypes: object(9)
memory usage: 31.7+ MB

資料型別方面,付款日期是時間格式,實付金額、郵費和購買數量是數值型,其他均為字串型別。

二:資料清洗

2.1 剔除退款

在觀察階段,我們明確了第一個清洗的目標,就是剔除退款資料:

df = df.loc[df['訂單狀態'] == '交易成功',:]
print('剔除退款後還剩:%d行' % len(df))

 

列印結果
剔除退款後還剩:889372行

2.2 關鍵欄位提取

剔除之後,覺得我們訂單的欄位還是有點多,而RFM模型只需要買家暱稱,付款時間和實付金額這3個關鍵欄位,所以提取之:

df = df[['買家暱稱','付款日期','實付金額']]
df.head()

 

# 列印結果
買家暱稱	付款日期	實付金額
0	棒西瓜皮的店	2019-04-18 11:05:26	210
1	8fiona_c8	2019-04-18 11:08:03	53
2	3t_1479778131547_04	2019-04-18 11:13:01	169
7	2jill27	2019-01-01 10:00:11	121
8	yjessieni	2019-01-01 10:00:14	211

2.3 關鍵欄位構造

R 值構造 。

R值,即每個使用者最後一次購買時間距今多少天。

  • 如果使用者只下單過一次,用現在的日期減去付款日期即可;
  • 若是使用者多次下單,需先篩選出這個使用者最後一次付款的時間,再用今天減去它。

距離今天越近,時間也就越“大”,

 pd.to_datetime('2019-11-11') > pd.to_datetime('2019-1-1') 

 


 # 列印結果
 True

使用者最近一次付款時間,只需要按買家暱稱分組,再選取付款日期的最大值即可:

r = df.groupby('買家暱稱')['付款日期'].max().reset_index()
r.head()

 

#列印結果
	買家暱稱	付款日期
0	.blue_ram	2019-02-04 17:49:34.000
1	.christiny	2019-01-29 14:17:15.000
2	.willn1	2019-01-11 03:46:18.000
3	.託託m	2019-01-11 02:26:33.000
4	0000妮	2019-06-28 16:53:26.458

用今天減去每位使用者最近一次付款時間,就得到R值了,測試樣本資料是1月至6月上半年的資料,所以我們把“2019-7-1”當作“今天”:

r['R'] = (pd.to_datetime('2019-7-1') - r['付款日期']).dt.days
r = r[['買家暱稱','R']]
r.head()

 

#列印結果

買家暱稱	R
0	.blue_ram	146
1	.christiny	152
2	.willn1	170
3	.託託m	170
4	0000妮	2

F 值構造

F值,即每個使用者累計購買頻次。

我們明確“單個使用者一天內多次下單行為看作整體一次”,引入一個精確到天的日期標籤,依照“買家暱稱”和“日期標籤”進行分組,把每個使用者一天內的多次下單行為合併

統計購買次數:

# F 值構造

# 引入日期標籤
df['日期標籤'] = df['付款日期'].astype(str).str[:10]

#把單個使用者一天內訂單合併
dup_f = df.groupby(['買家暱稱','日期標籤'])['付款日期'].count().reset_index()

#對合並後的使用者統計頻次
f = dup_f.groupby('買家暱稱')['付款日期'].count().reset_index()
f.columns = ['買家暱稱','F']
f.head()

 

# 列印結果

買家暱稱	F
0	.blue_ram	1
1	.christiny	1
2	.willn1	1
3	.託託m	1
4	0000妮	1

M 值構造

客戶平均購買金額,需要得到每個使用者總金額,再用總金額除以購買頻次即可。

# M 值構造
sum_m = df.groupby('買家暱稱')['實付金額'].sum().reset_index()
sum_m.columns = ['買家暱稱','總支付金額']
com_m = pd.merge(sum_m,f,left_on = '買家暱稱',right_on = '買家暱稱',how = 'inner')

#計算使用者平均支付金額
com_m['M'] = com_m['總支付金額'] / com_m['F']
com_m.head()

 

# 列印結果

買家暱稱	總支付金額	F	M
0	.blue_ram	1568	1	1568.0
1	.christiny	5856	1	5856.0
2	.willn1	1088	1	1088.0
3	.託託m	1184	1	1184.0
4	0000妮	5248	1	5248.0

R F M 三值合併

# R F M 值構造併合並
rfm = pd.merge(r,com_m,left_on = '買家暱稱',right_on = '買家暱稱',how = 'inner')
rfm = rfm[['買家暱稱','R','F','M']]
rfm.head()

 

# 列印結果
	買家暱稱	R	F	M
0	.blue_ram	146	1	1568.0
1	.christiny	152	1	5856.0
2	.willn1	170	1	1088.0
3	.託託m	170	1	1184.0
4	0000妮	2	1	5248.0

三:維度打分

此部分不涉及程式碼。

維度確認的核心是分值確定,按照設定的標準,我們給每個消費者的R/F/M值打分,分值的大小取決於我們的偏好,即我們越喜歡的行為,打的分數就越高:

  • R值 使用者有多少天沒來下單,所以R越大,使用者流失的可能性越大,分值越小。
  • F值 使用者購買頻次,數值越大,得分越高
  • M值 使用者平均支付金額,數值越大,得分越高

RFM模型中打分一般採取5分制,有兩種比較常見的方式,

  • 一種是按照資料的分位數來打分
  • 一種是依據資料和業務的理解進行分值的劃分

這裡使用的是第二種,即提前制定好不同數值對應的分值,加深對資料的理解。

R值根據行業經驗,設定為30天一個跨度,區間左閉右開:

R 分R 值
1 [120, +%)
2 [90, 120)
3 [60, 90)
4 [30, 60)
5 [0, 30)

F值 F值和購買頻次掛鉤,每多一次購買,分值就多加一分:

F 分F 值
1 1
2 2
3 3
4 4
5 [5, +%)

M值 先對M值做個簡單的區間統計,然後分組,這裡我們按照1600元的一個區間來進行劃分:

M 分M 值
1 [0, 1600)
2 [1600, 3200)
3 [3200, 4800)
4 [4800, 6400)
5 [6400, +%)

我們確定了一個打分框架,每一位使用者的每個指標,都有了與之對應的分值。

四:分值計算

第一次計算

R值:

# 4 R 值計算

rfm['R-SCORE'] = pd.cut(rfm['R'],bins = [0,30,60,90,120,1000000],labels = [5,4,3,2,1],right = False).astype(float)
rfm.head()

 

# 列印結果
買家暱稱	R	F	M	R-SCORE
0	.blue_ram	146	1	1568.0	1.0
1	.christiny	152	1	5856.0	1.0
2	.willn1	170	1	1088.0	1.0
3	.託託m	170	1	1184.0	1.0
4	0000妮	2	1	5248.0	5.0

F M 值計算

# F M 值計算
rfm['F-SCORE'] = pd.cut(rfm['F'],bins = [1,2,3,4,5,1000000],labels = [1,2,3,4,5],right = False).astype(float)
rfm['M-SCORE'] = pd.cut(rfm['M'],bins = [0,1600,3200,4800,6400,10000000],labels = [1,2,3,4,5],right = False).astype(float)
rfm.head()

 

# 列印結果
買家暱稱	R	F	M	F-SCORE	M-SCORE
0	.blue_ram	146	1	1568.0	1.0	1.0
1	.christiny	152	1	5856.0	1.0	4.0
2	.willn1	170	1	1088.0	1.0	1.0
3	.託託m	170	1	1184.0	1.0	1.0
4	0000妮	2	1	5248.0	1.0	4.0

第二次計算

過多的分類和不分類本質是一樣的。所以,我們通過判斷每個客戶的R、F、M值是否大於平均值,來簡化分類結果。

因為每個客戶和平均值對比後的R、F、M,只有0和1(0表示小於平均值,1表示大於平均值)兩種結果,整體組合下來共有8個分組,是比較合理的一個情況。我們來判斷使用者的每個分值是否大於平均值:

# 第二次計算
rfm['R是否大於均值'] = (rfm['R-SCORE'] > rfm['R-SCORE'].mean()) * 1
rfm['F是否大於均值'] = (rfm['F-SCORE'] > rfm['F-SCORE'].mean()) * 1
rfm['M是否大於均值'] = (rfm['M-SCORE'] > rfm['M-SCORE'].mean()) * 1
rfm.head()

 

# 列印結果
買家暱稱	R	F	M	R-SCORE	F-SCORE	M-SCORE	R是否大於均值	F是否大於均值	M是否大於均值
0	.blue_ram	146	1	1568.0	1.0	1.0	1.0	0	0	0
1	.christiny	152	1	5856.0	1.0	1.0	4.0	0	0	1
2	.willn1	170	1	1088.0	1.0	1.0	1.0	0	0	0
3	.託託m	170	1	1184.0	1.0	1.0	1.0	0	0	0
4	0000妮	2	1	5248.0	5.0	1.0	4.0	1	0	1

程式碼為什麼 * 1,這是由於Python中判斷後返回的結果是True和False,對應著數值1和0,只要把這個布林結果乘上1,True就變成了1,False變成了0,處理之後更加易讀。

知識點:

Pandas的cut函式:

  • 第一個引數傳入要切分的資料列。
  • bins引數代表我們按照什麼區間進行分組,上面我們已經確定了R值按照30天的間隔進行分組,輸入[0,30,60,90,120,1000000]即可,最後一個數值設定非常大,是為了給分組一個容錯空間,允許出現極端大的值。
  • right表示了右側區間是開還是閉,即包不包括右邊的數值,如果設定成False,就代表[0,30),包含左側的分組資料而不含右側,若設定為True,則是[0,30],首尾都包含。
  • labels和bins切分的陣列前後呼應,什麼意思呢?bins設定了6個數值,共切分了5個分組,labels則分別給每個分組打標籤,0-30是5分,30-60是4分,依此類推。

五:客戶分層

清洗完之後我們確定了打分邏輯,然後分別計算每個使用者的R、F、M分值(SCORE),隨後,用分值和對應的平均值進行對比,得到了是否大於均值的三列結果。那麼客戶分層如何處理呢。

R 大於均值F 大於均值M 大於均值一般分類RFM 分類釋義
1 1 1 重要價值客戶 重要價值客戶 近購、高頻、高消費
1 1 0 重要潛力客戶 消費潛力客戶 近購、高頻、低消費
1 0 1 重要深耕客戶 頻次深耕客戶 近購、低頻、高消費
1 0 0 新客戶 新客戶 近購、低頻、低消費
0 1 1 重要喚回客戶 重要價值流失預警客戶 近未購、高頻、高消費
0 1 0 一般客戶 一般客戶 近未購、高頻、低消費
0 0 1 重要挽回客戶 高消費喚回客戶 近未購、低頻、高消費
0 0 0 流失客戶 流失客戶 近未購、低頻、低消費

潛力是針對消費(平均支付金額),深耕是為了提升消費頻次,以及重要喚回客戶其實和重要價值客戶非常相似,只是最近沒有回購了而已,應該做流失預警等等。

5.1 構建合併指標

先引入一個人群數值的輔助列,把之前判斷的R\F\M是否大於均值的三個值給串聯起來:

# 5 客戶分層,構建合併指標

rfm['人群數值'] = (rfm['R是否大於均值'] * 100) + (rfm['F是否大於均值'] * 10) + (rfm['M是否大於均值'] * 1)
rfm.head()

 

# 列印結果
買家暱稱	R	F	M	R-SCORE	F-SCORE	M-SCORE	R是否大於均值	F是否大於均值	M是否大於均值	人群數值
0	.blue_ram	146	1	1568.0	1.0	1.0	1.0	0	0	0	0
1	.christiny	152	1	5856.0	1.0	1.0	4.0	0	0	1	1
2	.willn1	170	1	1088.0	1.0	1.0	1.0	0	0	0	0
3	.託託m	170	1	1184.0	1.0	1.0	1.0	0	0	0	0
4	0000妮	2	1	5248.0	5.0	1.0	4.0	1	0	1	101

人群數值是數值型別,所以位於前面的0就自動略過,比如1代表著“001”的高消費喚回客戶人群,10對應著“010”的一般客戶。

5.2 基於指標給客戶打標籤

為了得到最終人群標籤,再定義一個判斷函式,通過判斷人群數值的值,來返回對應的分類標籤:

#判斷R/F/M是否大於均值
def transform_label(x):
    if x == 111:
        label = '重要價值客戶'
    elif x == 110:
        label = '消費潛力客戶'
    elif x == 101:
        label = '頻次深耕客戶'
    elif x == 100:
        label = '新客戶'
    elif x == 11:
        label = '重要價值流失預警客戶'
    elif x == 10:
        label = '一般客戶'
    elif x == 1:
        label = '高消費喚回客戶'
    elif x == 0:
        label = '流失客戶'
    return label

 

5.3 標籤應用

rfm['人群型別'] = rfm['人群數值'].apply(transform_label)
rfm.head()

 

# 列印結果
買家暱稱	R	F	M	R-SCORE	F-SCORE	M-SCORE	R是否大於均值	F是否大於均值	M是否大於均值	人群數值	人群型別
0	.blue_ram	146	1	1568.0	1.0	1.0	1.0	0	0	0	0	流失客戶
1	.christiny	152	1	5856.0	1.0	1.0	4.0	0	0	1	1	高消費喚回客戶
2	.willn1	170	1	1088.0	1.0	1.0	1.0	0	0	0	0	流失客戶
3	.託託m	170	1	1184.0	1.0	1.0	1.0	0	0	0	0	流失客戶
4	0000妮	2	1	5248.0	5.0	1.0	4.0	1	0	1	101	頻次深耕客戶

RFM 建模,每一位客戶都有了屬於自己的RFM標籤。

六:RFM 模型結果探索性分析

切模型結果最終都要服務於業務,所以我們基於現有模型結果做一些拓展、探索性分析。

6.1 人數統計

# 6.1 人數分析
count = rfm['人群型別'].value_counts().reset_index()
count.columns = ['客戶型別','人數']
count['人數佔比'] = count['人數'] / count['人數'].sum()
count

 

# 列印結果
客戶型別	人數	人數佔比
0	高消費喚回客戶	7338	0.288670
1	流失客戶	6680	0.262785
2	頻次深耕客戶	5427	0.213493
3	新客戶	4224	0.166168
4	重要價值客戶	756	0.029740
5	消費潛力客戶	450	0.017703
6	重要價值流失預警客戶	360	0.014162
7	一般客戶	185	0.007278

6.2 金額統計

# 6.2 金額分析
rfm['購買總金額'] = rfm['F'] * rfm['M']
mon = rfm.groupby('人群型別')['購買總金額'].sum().reset_index()
mon.columns = ['客戶型別','消費金額']
mon['金額佔比'] = mon['消費金額'] / mon['消費金額'].sum()
mon

 

# 列印結果
客戶型別	消費金額	金額佔比
0	一般客戶	825696.0	0.007349
1	新客戶	8667808.0	0.077143
2	流失客戶	14227744.0	0.126625
3	消費潛力客戶	2050506.0	0.018249
4	重要價值客戶	8615698.0	0.076679
5	重要價值流失預警客戶	3732550.0	0.033219
6	頻次深耕客戶	31420996.0	0.279644
7	高消費喚回客戶	42819846.0	0.381092

6.3 客戶型別

result = pd.merge(count,mon,left_on = '客戶型別',right_on = '客戶型別')
result

 

# 列印結果
客戶型別	人數	人數佔比	消費金額	金額佔比
0	高消費喚回客戶	7338	0.288670	42819846.0	0.381092
1	流失客戶	6680	0.262785	14227744.0	0.126625
2	頻次深耕客戶	5427	0.213493	31420996.0	0.279644
3	新客戶	4224	0.166168	8667808.0	0.077143
4	重要價值客戶	756	0.029740	8615698.0	0.076679
5	消費潛力客戶	450	0.017703	2050506.0	0.018249
6	重要價值流失預警客戶	360	0.014162	3732550.0	0.033219
7	一般客戶	185	0.007278	825696.0	0.007349

七:模型封裝 ENTER

模型封裝,一個回車就能返回結果

import pandas as pd
import numpy as np
import os

os.chdir('F:\\50mat')


#輸入源資料檔名
def get_rfm(name = 'PYTHON-RFM實戰資料.xlsx'):
    # 資料概覽
    df = pd.read_excel(name)
    # 資料清洗
    df = df.loc[df['訂單狀態'] == '交易成功',:]
    print('剔除退款後還剩:%d行' % len(df))
    df = df[['買家暱稱','付款日期','實付金額']]

    # 構造 R 值,Recency 即每個使用者最後一次購買時間距今多少天。
    r = df.groupby('買家暱稱')['付款日期'].max().reset_index()
    r['R'] = (pd.to_datetime('2019-7-1') - r['付款日期']).dt.days
    r = r[['買家暱稱','R']]

    # 構造 F 值,Frequency 即每個使用者累計購買頻次。
    #引入日期標籤輔助列
    df['日期標籤'] = df['付款日期'].astype(str).str[:10]

    #把單個使用者一天內訂單合併
    dup_f = df.groupby(['買家暱稱','日期標籤'])['付款日期'].count().reset_index()

    #對合並後的使用者統計頻次
    f = dup_f.groupby('買家暱稱')['付款日期'].count().reset_index()
    f.columns = ['買家暱稱','F']

    # M 值構造,Monetary 客戶平均購買金額
    sum_m = df.groupby('買家暱稱')['實付金額'].sum().reset_index()
    sum_m.columns = ['買家暱稱','總支付金額']
    com_m = pd.merge(sum_m,f,left_on = '買家暱稱',right_on = '買家暱稱',how = 'inner')

    #計算使用者平均支付金額
    com_m['M'] = com_m['總支付金額'] / com_m['F']

    rfm = pd.merge(r,com_m,left_on = '買家暱稱',right_on = '買家暱稱',how = 'inner')
    rfm = rfm[['買家暱稱','R','F','M']]


    rfm['R-SCORE'] = pd.cut(rfm['R'],bins = [0,30,60,90,120,1000000],labels = [5,4,3,2,1],right = False).astype(float)
    rfm['F-SCORE'] = pd.cut(rfm['F'],bins = [1,2,3,4,5,1000000],labels = [1,2,3,4,5],right = False).astype(float)
    rfm['M-SCORE'] = pd.cut(rfm['M'],bins = [0,1600,3200,4800,6400,10000000],labels = [1,2,3,4,5],right = False).astype(float)
    
    rfm['R是否大於均值'] = (rfm['R-SCORE'] > rfm['R-SCORE'].mean()) * 1
    rfm['F是否大於均值'] = (rfm['F-SCORE'] > rfm['F-SCORE'].mean()) * 1
    rfm['M是否大於均值'] = (rfm['M-SCORE'] > rfm['M-SCORE'].mean()) * 1

    rfm['人群數值'] = (rfm['R是否大於均值'] * 100) + (rfm['F是否大於均值'] * 10) + (rfm['M是否大於均值'] * 1)

    rfm['人群型別'] = rfm['人群數值'].apply(transform_label)

    count = rfm['人群型別'].value_counts().reset_index()
    count.columns = ['客戶型別','人數']
    count['人數佔比'] = count['人數'] / count['人數'].sum()

    rfm['購買總金額'] = rfm['F'] * rfm['M']
    mon = rfm.groupby('人群型別')['購買總金額'].sum().reset_index()
    mon.columns = ['客戶型別','消費金額']
    mon['金額佔比'] = mon['消費金額'] / mon['消費金額'].sum()

    result = pd.merge(count,mon,left_on = '客戶型別',right_on = '客戶型別')

    return result


#判斷R/F/M是否大於均值
def transform_label(x):
    if x == 111:
        label = '重要價值客戶'
    elif x == 110:
        label = '消費潛力客戶'
    elif x == 101:
        label = '頻次深耕客戶'
    elif x == 100:
        label = '新客戶'
    elif x == 11:
        label = '重要價值流失預警客戶'
    elif x == 10:
        label = '一般客戶'
    elif x == 1:
        label = '高消費喚回客戶'
    elif x == 0:
        label = '流失客戶'
    return label

res = get_rfm(name = 'PYTHON-RFM實戰資料.xlsx')
res
    

 

# 列印結果
剔除退款後還剩:889372行

客戶型別	人數	人數佔比	消費金額	金額佔比
0	高消費喚回客戶	7338	0.288670	42819846.0	0.381092
1	流失客戶	6680	0.262785	14227744.0	0.126625
2	頻次深耕客戶	5427	0.213493	31420996.0	0.279644
3	新客戶	4224	0.166168	8667808.0	0.077143
4	重要價值客戶	756	0.029740	8615698.0	0.076679
5	消費潛力客戶	450	0.017703	2050506.0	0.018249
6	重要價值流失預警客戶	360	0.014162	3732550.0	0.033219
7	一般客戶	185	0.007278	825696.0	0.007349




by:一隻阿木木