該篇主要是對(duì)上篇分析報(bào)告的一個(gè)過(guò)程解讀。
總共有110段代碼:(以下操作均在IPython中進(jìn)行)
(部分重復(fù)代碼略、圖表的標(biāo)題、x軸、y軸、圖列等圖標(biāo)設(shè)置代碼略)
-------------------------------------
大綱:
引入及設(shè)置部分:1~6(6段)
Part 1 :7~28(22段)
Part 2 :29~45(17段)
Part 3 :46~61(16段)
Part 4 :62~106(45段)
? ? ????用戶生命周期?:62~73(12段)
????????用戶購(gòu)買周期?:74~79(6段)
????????回流率/流失率:80~99(20段)
????????復(fù)購(gòu)率/回購(gòu)率:100~106(7段)
Part 5 :補(bǔ)充4段,Part 1每月/日走勢(shì)圖
Let's go!
引入及設(shè)置部分
調(diào)用庫(kù)和一些設(shè)置
In [1]: import pandas as pd
In [2]: import numpy as np
In [3]: import matplotlib.pyplot as plt
In [4]: from datetime import datetime
In [5]: %matplotlib????????????????????? #使在交互軟件中可視化
Using matplotlib backend: Qt5Agg
In [6]:?plt.style.use('ggplot')????? ??#設(shè)置風(fēng)格
Part 1數(shù)據(jù)總覽
知識(shí)點(diǎn)及說(shuō)明:
①描述性分析、用戶的貢獻(xiàn)情況、消費(fèi)數(shù)量和金額關(guān)系
②groupby()函數(shù):變換維度統(tǒng)計(jì)用戶的情況,每天的銷售情況以及每月的銷售情況。
③info()函數(shù):查看數(shù)據(jù)的基本信息類型、是否有缺失值
④describe()函數(shù):查看數(shù)據(jù)的描述性分析(極值、均值、四分位等)
拿到一份csv文件格式的數(shù)據(jù),先打開看看有什么信息。該數(shù)據(jù)沒(méi)有表頭,字符串是空格分割,一共有四列數(shù)據(jù):用戶ID、購(gòu)買的日期、購(gòu)買的數(shù)量,購(gòu)買的金額。下面讀取數(shù)據(jù),并查看前五行以及統(tǒng)計(jì)描述、文件基本信息。
In [7]:columns=['user_id','order_dt','order_products','order_amount']
In [8]: df = pd.read_csv(r'C:\Users\55414\Desktop\CDNOW_master.txt',
names=columns,sep='\s+')
In [9]: df.head()
Out[9]:
??user_id? order_dt? order_products? order_amount
0???????1? 19970101?????????????? 1???????? 11.77
1???????2? 19970112?????????????? 1???????? 12.00
2???????2? 19970112?????????????? 5???????? 77.00
3???????3? 19970102?????????????? 2???????? 20.76
4???????3? 19970330?????????????? 2???????? 20.76
In [10]: df.describe()
Out[10]:
???????????user_id????? order_dt? order_products? order_amount
count?69659.000000? 6.965900e+04??? 69659.000000? 69659.000000
mean??11470.854592? 1.997228e+07??????? 2.410040???? 35.893648
std????6819.904848? 3.837735e+03??????? 2.333924???? 36.281942
min???????1.000000? 1.997010e+07??????? 1.000000????? 0.000000
25%????5506.000000? 1.997022e+07??????? 1.000000???? 14.490000
50%???11410.000000? 1.997042e+07??????? 2.000000???? 25.980000
75%???17273.000000? 1.997111e+07??????? 3.000000???? 43.700000
max???23570.000000? 1.998063e+07?????? 99.000000?? 1286.010000
In [11]: df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 69659 entries, 0 to 69658
Data columns (total 4 columns):
user_id?????????? 69659 non-null int64
order_dt????????? 69659 non-null int64
order_products??? 69659 non-null int64
order_amount????? 69659 non-null float64
dtypes: float64(1), int64(3)
memory usage: 2.1 MB
觀察以上數(shù)據(jù)發(fā)現(xiàn):
①總用戶數(shù)為23570人,總訂單數(shù)為69659個(gè),沒(méi)有缺失值。
②每個(gè)訂單平均有2.4個(gè)產(chǎn)品,35.89元。
③每個(gè)訂單的產(chǎn)品數(shù)標(biāo)準(zhǔn)差為2.33,說(shuō)明數(shù)據(jù)比較集中,用戶每次購(gòu)買的產(chǎn)品數(shù)量大多數(shù)在1~5個(gè)之間。
④訂單的產(chǎn)品數(shù)量的極差為98個(gè),上四分位為3個(gè),說(shuō)明訂單產(chǎn)品數(shù)量超過(guò)3個(gè)的最多占到總訂單數(shù)的25%。
⑤訂單的金額最小值為0,可能存在優(yōu)惠或減免的促銷活動(dòng);上四分位只有43.7元,說(shuō)明絕大部分的用戶為低消費(fèi)群體。符合二八原則期望。
⑥order_dt表示時(shí)間,但文件記錄它只是年月日組合的一串整型數(shù)字,沒(méi)有時(shí)間含義。
下面以用戶維度進(jìn)行轉(zhuǎn)換
In [12]: df_user =df.groupby('user_id').sum()
In [13]: df_user.describe()
Out[13]:
??????????order_dt? order_products? order_amount
count?2.357000e+04??? 23570.000000? 23570.000000
mean??5.902627e+07??????? 7.122656??? 106.080426
std???9.460684e+07?????? 16.983531??? 240.925195
min???1.997010e+07??????? 1.000000????? 0.000000
25%???1.997021e+07??????? 1.000000???? 19.970000
50%???1.997032e+07??????? 3.000000???? 43.395000
75%???5.992125e+07??????? 7.000000??? 106.475000
max???4.334408e+09???? 1033.000000? 13990.930000
In [14]: df_user.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 23570 entries, 1 to 23570
Data columns (total 3 columns):
order_dt????????? 23570 non-null int64
order_products??? 23570 non-null int64
order_amount????? 23570 non-null float64
dtypes: float64(1), int64(2)
memory usage: 736.6 KB
從描述信息得知:
①總用戶數(shù)量為23570
②每個(gè)用戶平均消費(fèi)產(chǎn)品7個(gè),金額(客單價(jià))106.08元。
③每個(gè)用戶購(gòu)買產(chǎn)品數(shù)標(biāo)準(zhǔn)差為16.9個(gè),說(shuō)明數(shù)據(jù)的離散程度比較大。
④用戶所購(gòu)買產(chǎn)品數(shù)和金額的平均值和上四分位比較接近,而且極差相差懸殊,說(shuō)明存在小部分的高額消費(fèi)用戶。
消費(fèi)總金額可以對(duì)order_amount列進(jìn)行求和
In [15]: df.order_amount.sum()
Out[15]: 2500315.6300004106
其他不再贅述。不過(guò)再轉(zhuǎn)換為時(shí)間維度前,先把order_dt轉(zhuǎn)換為時(shí)間類型,如下:
In [16]: df['order_date'] =pd.to_datetime(df.order_dt,
format='%Y%m%d')? ? ?#新增一列,將日期整型轉(zhuǎn)日期格式
In[17]:df['month']=df.order_date.values.astype('datetime64[M]')?????????????? #新增一列,提取每個(gè)日期的月份
In [18]: df.head()
Out[18]:
??user_id? order_dt? order_products? order_amount order_date????? month
0???????1? 19970101?????????????? 1???????? 11.77 1997-01-01 1997-01-01
1???????2? 19970112?????????????? 1???????? 12.00 1997-01-12 1997-01-01
2???????2? 19970112?????????????? 5???????? 77.00 1997-01-12 1997-01-01
3???????3? 19970102?????????????? 2???????? 20.76 1997-01-02 1997-01-01
4???????3? 19970330?????????????? 2???????? 20.76 1997-03-30 1997-03-01
接下來(lái)看用戶的貢獻(xiàn)情況,分別描述用戶和消費(fèi)金額、訂單數(shù)量的關(guān)系。
In [19]: user_amount=df.groupby('user_id').order_amount.sum().sort_values().reset_index()
In [20]:user_amount['amount_cumsum']=user_amount.order_amount.cumsum()
In [21]: user_amount.head()
Out[21]:
??user_id? order_amount? amount_cumsum
0???10175?????? ????0.0??????????? 0.0
1????4559?????????? 0.0??????????? 0.0
2????1948?????????? 0.0??????????? 0.0
3?????925?????????? 0.0??????????? 0.0
4???10798?????????? 0.0??????????? 0.0
將消費(fèi)金額進(jìn)行求和并排序,再累加。查看前5行,發(fā)現(xiàn)都是0.0,說(shuō)明可能是存在大力的促銷減免活動(dòng)
(這可能是前三個(gè)月用戶數(shù)量暴漲的一個(gè)重要因素)。看看尾5行,如下:
In [22]: user_amount.tail()
Out[22]:
??????user_id? order_amount? amount_cumsum
23565????7931?????? 6497.18???? 2463822.60
23566???19339?????? 6552.70???? 2470375.30
23567????7983?????? 6973.07???? 2477348.37
23568???14048?????? 8976.33???? 2486324.70
23569????7592????? 13990.93???? 2500315.63
目的是查看用戶的貢獻(xiàn)情況,把數(shù)據(jù)轉(zhuǎn)換成百分比更直觀。接下來(lái)新增一行把累計(jì)情況轉(zhuǎn)換成百分比。
In [23]: user_amount['prop']=user_amount.apply(lambdax:x.amount_cumsum/user_amount.amount_cumsum.max(),axis=1)
In [24]: user_amount.tail()
Out[24]:
??????user_id? order_amount? amount_cumsum????? prop
23565????7931?????? 6497.18???? 2463822.60?0.985405
23566???19339?????? 6552.70???? 2470375.30?0.988025
23567????7983?????? 6973.07???? 2477348.37?0.990814
23568???14048?????? 8976.33???? 2486324.70?0.994404
23569????7592????? 13990.93???? 2500315.63?1.000000
由圖看出,前20000個(gè)用戶貢獻(xiàn)了40%的消費(fèi)。后面3000多位用戶貢獻(xiàn)了60%,呈現(xiàn)二八傾向。
將以上代碼中的order_amount改為order_dt,即可查看用戶和訂單數(shù)量的關(guān)系。
了解了用戶的貢獻(xiàn)之后,再看看訂單訂購(gòu)的數(shù)量和用戶消費(fèi)金額之間的關(guān)系。引入seaborn模塊做線性回歸分析。
In [26]: import seaborn as sns
In [27]: sns.regplot(x='order_amount',y='order_products', data=df)
In [28]: sns.regplot(x='order_amount',y='order_products',data = df.groupby('user_id').sum())
分別從訂單和用戶兩個(gè)維度進(jìn)行分析,
訂單維度:訂單消費(fèi)金額和訂單商品量呈規(guī)律性,每個(gè)商品十元左右。訂單的極值較少,超出1000的就幾個(gè)
用戶維度:用戶消費(fèi)金額和訂購(gòu)商品數(shù)量的規(guī)律性更強(qiáng)。說(shuō)明銷售的商品相對(duì)比較單一。
Part2用戶分層——RFM模型
知識(shí)點(diǎn)及說(shuō)明:
①R的參數(shù):根據(jù)用戶最近一次消費(fèi)的時(shí)間與截止數(shù)據(jù)采集當(dāng)天的間隔,計(jì)算出時(shí)間間隔均值,然后用每個(gè)用戶的時(shí)間間隔與該均值比較,確定R的參數(shù);
②F的參數(shù):根據(jù)用戶的總購(gòu)買數(shù)量與所有用戶的平均購(gòu)買數(shù)量作比,確定F的參數(shù);
③M的參數(shù):根據(jù)用戶的總購(gòu)買金額與所有用戶的平均購(gòu)買金額作比,確定M的參數(shù)。
④np.timedelta64(1, 'D')將日期類型(timedelta)轉(zhuǎn)換成數(shù)值類型
⑤這里使用均值做為判斷條件,最近一次消費(fèi)的時(shí)間小于均值時(shí),R返回1,否則返回0;購(gòu)買數(shù)量和金額大于均值時(shí)返回1,否則返回0。
In [29]: rfm = df.pivot_table(index='user_id',
???...: values=['order_products', 'order_amount', 'order_date'],
???...: aggfunc={'order_date': 'max','order_amount':'sum','order_products': 'sum'})
In [30]: rfm['R'] = (rfm.order_date -rfm.order_date.max()) / np.timedelta64(1, 'D')?
In [31]:rfm.rename(columns={'order_products': 'F', 'order_amount': 'M'}, inplace=True) #重命名列
In [32]: rfm.head()
Out[32]:
????????????? M order_date?? F?????R
user_id
1????????11.77 1997-01-01?? 1 -545.0
2????????89.00 1997-01-12?? 6 -534.0
3???????156.46 1998-05-28? 16? -33.0
4???????100.50 1997-12-12?? 7 -200.0
5???????385.61 1998-01-03? 29 -178.0
In [33]:?def rfm_func(x):
???...:????? level = x.apply(lambdax: '1' if x >= 0 else '0')
???...:????? label = level.R +level.F + level.M???????? # 字符串拼接
???...:????? level_class = {
???...:????? '111': '重要價(jià)值客戶',
???...:????? '011': '重要保持客戶',
???...:????? '101': '重要挽留客戶',
???...:????? '001': '重要發(fā)展客戶',
???...:????? '110': '一般價(jià)值客戶',
???...:????? '010': '一般保持客戶',
???...:????? '100': '一般挽留客戶',
???...:????? '000': '一般發(fā)展客戶'}
???...:????? result =level_class[label]
???...:????? return result
???...:
In [34]: rfm['label'] = rfm[['R', 'F','M']].apply(lambda x: x -?x.mean()).apply(rfm_func, axis=1)???? #用均值作比,調(diào)用函數(shù)
In [35]: rfm.head()
Out[35]:
????????????? M order_date?? F?????R?? label
user_id
1????????11.77 1997-01-01?? 1 -545.0? 一般發(fā)展客戶
2????????89.00 1997-01-12?? 6 -534.0? 一般發(fā)展客戶
3???????156.46 1998-05-28? 16? -33.0? 重要價(jià)值客戶
4???????100.50 1997-12-12?? 7 -200.0? 一般挽留客戶
5???????385.61 1998-01-03? 29 -178.0? 重要價(jià)值客戶
對(duì)用戶分層完,進(jìn)行繪制餅圖,查看各層用戶占比。
In [36]: use_c =rfm.groupby('label').count()
In [37]: plt.axis('equal')
In [38]: labels = ['一般價(jià)值客戶','一般保持客戶','一般發(fā)展客戶','一般挽留客戶
???...: ','重要價(jià)值客戶','重要保持客戶','重要發(fā)展客戶','重要挽留客戶']
In [39]:plt.pie(use_c['M'],autopct='%1.1f%%',labels = labels,pctdistance=0.9,
...:labeldistance=1.1,radius=2.2,startangle = 180)
此時(shí),顯示的圖表沒(méi)有顯示出漢字,需要對(duì)maplotlib進(jìn)行設(shè)置,如下:
In [40]:plt.rcParams['font.sans-serif']=['SimHei']????? #設(shè)置字體顯示類型
In [41]: plt.rcParams['font.family']='sans-serif'???????? #字體集
In [42]: plt.rcParams['axes.unicode_minus']= False? #設(shè)置顯示負(fù)號(hào)
接下來(lái)對(duì)RFM模型的各個(gè)指標(biāo)進(jìn)行查詢,并在excel中進(jìn)行繪制。
In [43]: -rfm.groupby('label').mean().R
Out[43]:
label
一般價(jià)值客戶???142.951456
一般保持客戶???471.363636
一般發(fā)展客戶???493.947350
一般挽留客戶???179.123636
重要價(jià)值客戶???113.585200
重要保持客戶???455.353240
重要發(fā)展客戶???475.029046
重要挽留客戶???171.105740
Name: R, dtype: float64
In [44]: rfm.groupby('label').sum().F
Out[44]:
label
一般價(jià)值客戶?????1712
一般保持客戶??????650
一般發(fā)展客戶????29346
一般挽留客戶????13977
重要價(jià)值客戶???107789
重要保持客戶????11121
重要發(fā)展客戶?????1263
重要挽留客戶?????2023
Name: F, dtype: int64
In [45]: rfm.groupby('label').sum().M
Out[45]:
label
一般價(jià)值客戶?????19937.45
一般保持客戶??????7181.28
一般發(fā)展客戶????438291.81
一般挽留客戶????196971.23
重要價(jià)值客戶???1592039.62
重要保持客戶????167080.83
重要發(fā)展客戶?????33028.40
重要挽留客戶?????45785.01
Name: M, dtype: float64
從RFM分層可知,六成左右的客戶屬于一般發(fā)展客戶(該分層中的底層用戶),最高層的重要價(jià)值客戶占比19.3%。這一批重要價(jià)值客戶(種子用戶)貢獻(xiàn)了63.6%的銷售額,
在消費(fèi)領(lǐng)域中,狠抓高質(zhì)量用戶是萬(wàn)古不變的道理。
Part 3 用戶活躍度
知識(shí)點(diǎn)及說(shuō)明:
①分類標(biāo)準(zhǔn):
新用戶new: 第一次消費(fèi)。
活躍用戶active: 在某一個(gè)時(shí)間窗口內(nèi)有過(guò)消費(fèi)的老客。
不活躍用戶unactive: 在時(shí)間窗口內(nèi)沒(méi)有消費(fèi)過(guò)的老客。
回流用戶return:在上一個(gè)窗口中沒(méi)有消費(fèi),而在當(dāng)前時(shí)間窗口內(nèi)有過(guò)消費(fèi)。
潛在用戶unreg: 截止當(dāng)前窗口還沒(méi)有消費(fèi)過(guò)
以上的時(shí)間窗口都是按月統(tǒng)計(jì)。
比如某用戶在
1月第一次消費(fèi),那么他在1月的分層就是新用戶;
2月消費(fèi)過(guò),則是活躍用戶;
3月沒(méi)有消費(fèi),此時(shí)是不活躍用戶;
4月再次消費(fèi),此時(shí)是回流用戶;
5月還是消費(fèi),是活躍用戶
②思路:對(duì)數(shù)據(jù)進(jìn)行透視,然后用applymap函數(shù)將透視表的值轉(zhuǎn)換成布爾值(0/1),最后定義一個(gè)函數(shù)對(duì)其進(jìn)行判斷、分類。
In [46]: pivoted_date =df.pivot_table(index='user_id', columns='month',
???...:????? values='order_dt',aggfunc='count').fillna(0)? #對(duì)df進(jìn)行透視,用于計(jì)算的值及計(jì)算方法
In [47]: columns_month = df.month.sort_values().astype('str').unique()
In [48]: pivoted_date.columns =columns_month
In [49]: pivoted_date_bool =pivoted_date.applymap(lambda x: 1 if x > 0 else 0)
In [50]: def active_status(df):
???...:???? status = []
???...:???? for i in range(18):
???...: ????????#若本月沒(méi)有消費(fèi)
???...:???????? if df[i] == 0:
???...:???????????? if len(status)> 0: #排除第一月
???...:???????????????? ifstatus[i-1] == 'unreg':
???...:????????????????????status.append('unreg')
???...:???????????????? else:
???...:??????????????? ?????status.append('unactive')
???...:???????????? else:
???...:????????????????status.append('unreg')???? #<=0,說(shuō)明是第一個(gè)月,未消費(fèi)過(guò)
???...:???????? #若本月消費(fèi)
???...:???????? else:
???...:???????????? if len(status) ==0:
???...:????????????????status.append('new')
???...:???????????? else:
???...:???????????????? ifstatus[i-1] == 'unactive':
???...:????????????????????status.append('return')
???...:???????????????? elifstatus[i-1] == 'unreg':
???...:????????????????????status.append('new')
???...:???????????????? else:
???...:????????????????????status.append('active')
???...:???? return status
???...:
In [51]: user_level =pivoted_date_bool.apply(lambda x: active_status(x), axis=1)
In [52]: user_level.head()
Out[52]:
???????1997-01-01 1997-02-01 1997-03-01 1997-04-01 1997-05-01 1997-06-01? \
user_id
1????????????? new?? unactive??unactive?? unactive?? unactive??unactive
2????????????? new?? unactive??unactive?? unactive?? unactive??unactive
3????????????? new?? unactive????return???? active?? unactive??unactive
4????????????? new?? unactive??unactive?? unactive?? unactive??unactive
5????????????? new???? active??unactive???? return???? active????active
???????1997-07-01 1997-08-01 1997-09-01 1997-10-01 1997-11-01 1997-12-01? \
user_id
1????????unactive?? unactive?? unactive??unactive?? unactive?? unactive
2????????unactive?? unactive?? unactive??unactive?? unactive?? unactive
3????????unactive?? unactive?? unactive??unactive???? return?? unactive
4????????unactive???? return?? unactive??unactive?? unactive???? return
5??????????active?? unactive???? return??unactive?? unactive???? return
???????1998-01-01 1998-02-01 1998-03-01 1998-04-01 1998-05-01 1998-06-01
user_id
1????????unactive?? unactive?? unactive??unactive?? unactive?? unactive
2????????unactive?? unactive?? unactive??unactive?? unactive?? unactive
3????????unactive?? unactive?? unactive??unactive???? return?? unactive
4????????unactive?? unactive?? unactive??unactive?? unactive?? unactive
5??????????active?? unactive?? unactive??unactive?? unactive?? unactive
潛在用戶unreg截止當(dāng)前窗口還沒(méi)有消費(fèi)過(guò),是未來(lái)作為新客,在未發(fā)生交易之前,統(tǒng)計(jì)時(shí)應(yīng)該去掉。
In [53]: user_level_del_unreg =user_level.replace('unreg', np.nan).apply(lambda x: pd.value_counts(x))
In [54]: user_level_del_unreg.T.fillna(0)
Out[54]:
???????????active???? new? return?unactive
1997-01-01???? 0.0?7846.0???? 0.0?????? 0.0
1997-02-01?1157.0? 8476.0???? 0.0???6689.0
1997-03-01?1681.0? 7248.0?? 595.0??14046.0
1997-04-01?1773.0???? 0.0? 1049.0??20748.0
1997-05-01??852.0???? 0.0? 1362.0??21356.0
1997-06-01??747.0???? 0.0? 1592.0??21231.0
1997-07-01??746.0???? 0.0? 1434.0??21390.0
1997-08-01??604.0???? 0.0? 1168.0??21798.0
1997-09-01??528.0???? 0.0? 1211.0??21831.0
1997-10-01??532.0???? 0.0? 1307.0??21731.0
1997-11-01??624.0???? 0.0? 1404.0??21542.0
1997-12-01??632.0???? 0.0? 1232.0??21706.0
1998-01-01??512.0???? 0.0? 1025.0??22033.0
1998-02-01??472.0???? 0.0? 1079.0??22019.0
1998-03-01??571.0???? 0.0? 1489.0??21510.0
1998-04-01??518.0???? 0.0?? 919.0??22133.0
1998-05-01??459.0???? 0.0? 1029.0??22082.0
1998-06-01??446.0???? 0.0? 1060.0??22064.0
新用戶前部都在前三個(gè)月,前面數(shù)據(jù)展示部分訂單的消費(fèi)金額為零,可能是網(wǎng)站有減免活動(dòng)。將數(shù)據(jù)以柱狀圖形式展示,查看各個(gè)時(shí)間窗口的值的差距。
In [55]: bar_values =user_level_del_unreg.T.div(user_level_del_unreg.T.sum(1),axis=0).fillna(0)#歸1化
In [56]: bar_values.plot.bar(figsize=(16,8))
活躍用戶和回流用戶的占比和不活躍用戶的占比相差十分懸殊。單獨(dú)查看活躍用戶和回流用戶的占比。
In [57]: user_level_del_unreg.apply(lambdax: x / x.sum(), axis=0).fillna(0).T[['active', 'return']].plot()
In [58]: values =user_level_del_unreg.fillna(0).T[['active', 'return']].sum()
In [59]: plt.axis('equal')
In [60]: labels = ['active', 'return']
In [61]:plt.pie(values,autopct='%2.2f%%',labels=labels)
Part 4 用戶畫像
用戶生命周期、用戶購(gòu)買周期、回流率、流失率、復(fù)購(gòu)率、回購(gòu)率、
查看用戶周期為0天的占比,超過(guò)一半!說(shuō)明大部分用戶的粘性和忠誠(chéng)度很低!
In [62]: user_life =df.groupby('user_id').order_date.agg(['min','max'])
In [63]: (user_life['min'] ==user_life['max']).value_counts()
Out[63]:
True????12054
False???11516
dtype: int64
In [64]: rate = (user_life['min'] ==user_life['max']).value_counts()
In [65]: labels = ['生命周期為0天','生命周期大于0天']
In [66]: plt.axis('equal')
In [67]: plt.pie(rate,labels=labels,
...:?????????autopct='%2.1f%%',
...:?????????startangle=90,
...:?????????radius=1.5)
查看整體用戶的生命周期
知識(shí)點(diǎn)及說(shuō)明:
①統(tǒng)計(jì)邏輯:第一次消費(fèi)時(shí)間至最后一次消費(fèi)時(shí)間為整個(gè)用戶生命周期
②思路:用用戶最后一次消費(fèi)的時(shí)間減去第一次消費(fèi)的時(shí)間。
In [68]: life_seri =(user_life['max'] -?user_life['min'])
...:?????????????????????/np.timedelta64(1,'D')
In[69]: life_seri.hist(bins=50)
In[70]: life_seri.mean()
Out[70]:134.8719558761137
圖表呈現(xiàn)出長(zhǎng)尾模型,說(shuō)明大部分用戶的生命周期比較短。接下來(lái)把非零的用戶篩選出來(lái)分析。
In [71]: life_seri = (user_life['max'] -user_life['min']).reset_index()[0]/np.timedelta64(1,'D')
In [72]:life_seri[life_seri>0].hist(bins=50)
Out[72]: <matplotlib.axes._subplots.AxesSubplotat 0x224561fdef0>
In [73]: life_seri[life_seri > 0].mean()
Out[73]: 276.0448072247308
排除極值0天的影響之后,雖然仍舊有不少用戶生命周期靠攏在0天,但數(shù)據(jù)呈現(xiàn)雙峰圖,有相當(dāng)部分用戶的生命周期在400天到500天之間,這已經(jīng)屬于忠誠(chéng)用戶。對(duì)于有過(guò)兩次以上消費(fèi)但生命周期在一個(gè)月內(nèi)的而用戶應(yīng)當(dāng)盡量引導(dǎo),逐步培養(yǎng)用戶的忠誠(chéng)度。
再看看用戶的購(gòu)買周期。
知識(shí)點(diǎn)及說(shuō)明:
①統(tǒng)計(jì)邏輯:求用戶相鄰兩次消費(fèi)的時(shí)間間隔;
②思路:先求出用戶后續(xù)的所有產(chǎn)生消費(fèi)的日期與其第一次消費(fèi)的日期進(jìn)行相減,求出差值,再使用一階差分進(jìn)行相減即可求得用戶購(gòu)買周期;
③merge()函數(shù):將用戶消費(fèi)行為和第一次消費(fèi)時(shí)間對(duì)應(yīng)上,形成一個(gè)新的DataFrame。它和SQL中的join差不多;
④shift()函數(shù):可以移動(dòng)行或列的值,將前后的值相減可以進(jìn)行錯(cuò)位求差值(一階差分)
In [74]: df2=df[['user_id','order_products', 'order_amount', 'order_date']]?? #切片
In [75]: buy_df = pd.merge(left=df2, right= user_life['min'].reset_index(),??? #使用merge聯(lián)結(jié)兩個(gè)DF
???...: how = 'inner', on = 'user_id', suffixes = ('', '_min'))
In [76]: buy_df['date_diff'] =(buy_df.order_date -buy_df['min'])/np.timedelta64(1,'D')
In [77]: buy_gap =buy_df.groupby('user_id').apply(lambda x :x.date_diff.shift(-1)-x.date_diff)
In [78]: buy_gap.hist(bins=100)
In [79]: buy_gap.mean()
Out[79]: 68.97376814424265
用戶得購(gòu)買平均周期為69天,想要召回用戶,在60天左右的消費(fèi)間隔是比較好的。直方圖也是典型得長(zhǎng)尾分布,大部分用戶得消費(fèi)間隔比較短。
接下來(lái)分析用戶的留存率:
知識(shí)點(diǎn)及說(shuō)明:
①統(tǒng)計(jì)邏輯:用戶在第一次消費(fèi)后,進(jìn)行第二次消費(fèi)的比率。(某個(gè)時(shí)間窗口,第二次消費(fèi)數(shù)/總的一次消費(fèi)數(shù)),
②思路:對(duì)用戶購(gòu)買周期的數(shù)據(jù)分區(qū)間進(jìn)行統(tǒng)計(jì);再透視相關(guān)數(shù)據(jù),然后用applymap函數(shù)將透視表的值轉(zhuǎn)換成布爾值(0/1),最后求DF.sum()/DF.count()即為所求。
③cut()將區(qū)間切為左開右閉區(qū)間(即date_diff=0并沒(méi)有被劃分入0~3天)
如果用戶僅消費(fèi)了一次或者在第一天內(nèi)消費(fèi)了多次但是往后沒(méi)有消費(fèi),留存率都是是0。統(tǒng)計(jì)時(shí)剔除。
In [80]: bins = [0, 3, 7, 15, 30, 60, 90,180, 365]
In [81]: buy_df['date_diff_bins'] =pd.cut(buy_df.date_diff, bins=bins)
In [82]: pivoted_buy_df =buy_df.pivot_table(index='user_id', columns='date_d
???...: iff_bins',values='order_amount', aggfunc=sum)
In [83]: pivoted_buy_df_bool =pivoted_buy_df.fillna(0).applymap(lambda x: 1 if x > 0 else 0)
In [84]: retention_rate =(pivoted_buy_df_bool.sum() / pivoted_buy_df_bool.count())
In [85]: retention_rate.plot()
In [86]: retention_rate.plot.bar()
In [87]: pivoted_buy_df.mean()
Out[87]:
date_diff_bins
(0, 3]???????35.905798
(3, 7]??????? 36.385121
(7, 15]?????? 42.669895
(15, 30]????? 45.964649
(30, 60]????? 50.215070
(60, 90]????? 48.975277
(90, 180]???? 67.223297
(180, 365]??? 91.960059
dtype: float64
從用戶在后續(xù)各階段的平均消費(fèi)金額效果看,用戶第一次消費(fèi)后的0~3天內(nèi),更可能消費(fèi)更多。雖然后面時(shí)間段的金額高,但是它的時(shí)間范圍也寬廣。
用戶流失率:
知識(shí)點(diǎn)及說(shuō)明:
①統(tǒng)計(jì)邏輯:用戶最后一次購(gòu)買時(shí)間距離最后統(tǒng)計(jì)時(shí)間窗口的間隔大于一個(gè)統(tǒng)計(jì)窗口(這里設(shè)置為1個(gè)月)即為流失。累計(jì)所有流失用戶數(shù)量除以當(dāng)期用戶的總數(shù)即為流失率
②思路:求每月發(fā)生購(gòu)買的用戶數(shù)/新用戶數(shù)每月累加值,得到每月留存的用戶比例,用1減去該比例即是流失率
In [88]: pivoted_data =df.pivot_table(index='user_id', columns='month', values='order_products',aggfunc=sum)
In [89]: pivoted_data_bool =pivoted_data.applymap(lambda x: 1 if x>0 else np.nan)
In [90]: month_buyers =pivoted_data_bool.count()?? #統(tǒng)計(jì)各列,用戶id為統(tǒng)計(jì)對(duì)象,結(jié)果得到每個(gè)月的消費(fèi)人數(shù)
In [91]: month_buyers.name = 'user_buy'
In [92]: month_new=df.groupby('user_id').month.min().value_counts()
#求首次消費(fèi)的用戶數(shù)(即新用戶數(shù)量)在每個(gè)月的分布情況
In [93]: month_new.name = 'new_users'
In [94]: month_new.index.name = 'month'
In [95]: month_buyers_new =pd.merge(left=month_buyers.reset_index(), right=m
???...: onth_new.reset_index(),how='left', on='month', suffixes=('','_min')).fillna(0)
In [96]: month_buyers_new['churn_rate'] =1-month_buyers_new.user_buy/month_buyers_new.new_users.cumsum()
In [97]: month_buyers_new.churn_rate.index= month_buyers_new.month.sort_values().astype('str')
In [98]: month_buyers_new.churn_rate.plot()
In [99]:month_buyers_new.churn_rate.plot.bar()
前三月,用戶的流失率急劇上升,后續(xù)穩(wěn)定在93%。
求用戶的回購(gòu)率
①統(tǒng)計(jì)邏輯:某一個(gè)時(shí)間窗口內(nèi)消費(fèi)的用戶,在下一個(gè)時(shí)間窗口仍舊消費(fèi)的占比。
②思路:將上期有消費(fèi)且本期都有消費(fèi)的用戶,記為1;上期和本期只消費(fèi)一次的記為0;都沒(méi)有消費(fèi)的記為NaN。然后統(tǒng)計(jì)DF.sum()和DF.count()的人數(shù)進(jìn)行相除。
In [100]: def back_buy(data):
????...:???? status=[]
????...:???? for i in range(17):
????...:???????? if data[i]==1:
????...:???????????? if data[i+1]==1:
????...:????????????????status.append(1)
????...:???????????? if data[i+1]==0:
????...:????????????????status.append(0)
????...:???????? else :
????...:????????????status.append(np.nan)
????...:???? else:
????...:???????? status.append(np.nan)
????...:???? return status
????...:
In [101]: pivoted_date_bool_2 =pivoted_date_bool.apply(back_buy,axis=1)
In [102]: back_buy_rate = (pivoted_date_bool_2.sum()/ pivoted_date_bool_2.count())
In [103]: back_buy_rate.plot()
復(fù)購(gòu)率
①統(tǒng)計(jì)邏輯:在某時(shí)間窗口內(nèi)消費(fèi)兩次及以上的用戶在總消費(fèi)用戶中占比。
②思路:將數(shù)據(jù)轉(zhuǎn)換未,消費(fèi)兩次及以上記為1,消費(fèi)一次記為0,沒(méi)有消費(fèi)記為NaN。然后統(tǒng)計(jì)DF.sum()和DF.count()的人數(shù)進(jìn)行相除。
In [104]: pivoted_date_bool_3 =pivoted_date.applymap(lambda x: 1 if x > 1 else np.NaN if x == 0 else 0)
In [105]: rebuy_rate =(pivoted_date_bool_3.sum() / pivoted_date_bool_3.count())
In [106]: rebuy_rate.plot()
Part 5? 補(bǔ)充
In [1]:df.groupby('month').order_products.sum().plot(figsize=(12,6))
Out[1]:<matplotlib.axes._subplots.AxesSubplot at
0x21d31575b00>
In [2]:df.groupby('month').order_amount.sum().plot(figsize=(12,6))
Out[2]:<matplotlib.axes._subplots.AxesSubplot at 0x21d31cdc780>
In [3]: df.groupby('month').order_products.count().plot(figsize=(12,6))
Out[3]:<matplotlib.axes._subplots.AxesSubplot at 0x21d30ae9198>
In [4]:df.groupby('month').user_id.count().plot(figsize=(12,6))
Out[4]:<matplotlib.axes._subplots.AxesSubplot at 0x21d31575b00>
part 1每月/日銷量、銷售額、訂單量、用戶數(shù)的代碼(直接將month->order_date即可得到每日走勢(shì)圖)
Part 6 總結(jié)
來(lái)一波彩蛋吧!
圖表中顯示漢字及負(fù)號(hào):
#中文亂碼的處理(字體設(shè)置、負(fù)號(hào)問(wèn)題)
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['font.family']='sans-serif'
plt.rcParams['axes.unicode_minus'] =False
如果還出現(xiàn)亂碼,需要修改一些默認(rèn)的設(shè)置,參考:
https://blog.csdn.net/minixuezhen/article/details/81516949
設(shè)置圖表的標(biāo)題、坐標(biāo)軸標(biāo)簽及刻度值、字號(hào)的大小:
plt.xlabel(x,fontsize=20)plt.ylabel(y,fontsize=20)
plt.title(t,fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
對(duì)坐標(biāo)軸進(jìn)行格式化,顯示百分比:
from matplotlib.tickerimport FuncFormatter
def to_percent(temp,position):
return '%2.1f' % (100 * temp) +'%'
plt.gca().xaxis.set_major_formatter(FuncFormatter(to_percent))
plt.gca().yaxis.set_major_formatter(FuncFormatter(to_percent))
圖片存儲(chǔ)及展示:
plt.savefig(path, bbox_inches='tight', dpi=200)#一定要在show()前設(shè)置
plt.show()????#主要是在其他編輯器,如pycharm等對(duì)圖表進(jìn)行可視化
來(lái)自小明傳書,一起嗑數(shù)據(jù)。
公眾號(hào)(小明傳書)后臺(tái)恢復(fù):CD,可獲取原始數(shù)據(jù)。
公眾號(hào)(小明傳書)后臺(tái)恢復(fù):CD分析,獲取本人所整理的代碼及分析過(guò)程。