import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
-
首先當(dāng)然要把該import的都全部Import進(jìn)來(lái)背苦。
plt.rcParams['font.family'] = ['Arial Unicode MS']
-
為了待會(huì)兒畫(huà)圖正常顯示中文歼郭。因?yàn)槲沂荕AC系統(tǒng)遗契,所以代碼跟WIN的可能有些不同。
pd.__version__
-
我pd的版本病曾,是最新安裝的牍蜂,0.24.0
columns=['user_id','order_dt','order_products','order_amount']
df = pd.read_csv('CDNOW_master.txt',names=columns,sep='\s+')
#如果數(shù)據(jù)本身以空格做分割的話,這里sep要用\s+
-
導(dǎo)入數(shù)據(jù)泰涂,并把列頭分別命名為:'user_id','order_dt','order_products','order_amount'
df.order_dt=pd.to_datetime(df.order_dt,format='%Y%m%d')
-
由于原數(shù)據(jù)時(shí)間那一項(xiàng)是‘19970101’這樣的形式的捷兰,因此要用pd.to_datetime()把格式換成yyyy-mm-dd的格式,進(jìn)行后續(xù)的分析時(shí)才更加方便负敏。
df['month']=df.order_dt.values.astype('datetime64[M]')
#后續(xù)要解決的問(wèn)題都需要以月為單位來(lái)計(jì)算贡茅,因此要多加一條column:"month",以月為單位其做。
df.head(10)
-
看下表頭前十行大概長(zhǎng)啥樣顶考。
df.describe()
-
先給這些數(shù)據(jù)做一些簡(jiǎn)單的統(tǒng)計(jì)分析⊙梗可以看出:大部分訂單只消費(fèi)了少量商品(平均2.4)驹沿,用戶的消費(fèi)金額比較穩(wěn)定,平均消費(fèi)35元蹈胡,中位數(shù)25元渊季。有一定的極值干擾。
接下來(lái)就開(kāi)始進(jìn)行進(jìn)一步的分析罚渐。
首先:1.進(jìn)行用戶按月消費(fèi)趨勢(shì)的分析却汉。分別有:
- (1)每月消費(fèi)的總金額
- (2)每月的消費(fèi)次數(shù)
- (3)每月的產(chǎn)品購(gòu)買數(shù)
- (4)每月的消費(fèi)人數(shù)
grouped_month=df.groupby('month')
-
由于是按月消費(fèi)的分析,因此要按月分組荷并。
grouped_month.order_amount.sum().plot()#(1)每月消費(fèi)的總金額
-
利用.sum()函數(shù)把order_amount那一列按月分組的總和求出來(lái)合砂,并且畫(huà)圖,得到下列圖片:
grouped_month.order_products.count().plot()#(2)每月的消費(fèi)次數(shù)
-
利用.count()函數(shù)把總消費(fèi)次數(shù)按月分組的總和求出來(lái)(這里用order_products或者order_amount其實(shí)都一樣)源织。畫(huà)圖,并且得到下列圖片:
grouped_month.order_products.sum().plot()#(3)每月的產(chǎn)品購(gòu)買數(shù)
-
每月產(chǎn)品總購(gòu)買數(shù)的畫(huà)圖:
grouped_month.user_id.count().plot()#(4)每月消費(fèi)人數(shù)
-
每月消費(fèi)人數(shù)的畫(huà)圖:
-
由以上四圖可知凛剥,每月消費(fèi)的總金額、消費(fèi)次數(shù)当悔、產(chǎn)品購(gòu)買數(shù)以及消費(fèi)人數(shù)踢代,都在前三個(gè)月上升達(dá)到高峰,隨后在3到4月份時(shí)巨幅下降嗅骄,4月份之后開(kāi)始穩(wěn)定上下波動(dòng)溺森,甚至有輕微下降趨勢(shì)屏积。
2.用戶個(gè)體消費(fèi)分析
- (1)用戶消費(fèi)金額、產(chǎn)品購(gòu)買數(shù)的描述統(tǒng)計(jì)
- (2)用戶消費(fèi)金額和產(chǎn)品購(gòu)買數(shù)的散點(diǎn)圖
- (3)用戶消費(fèi)金額的分布圖
- (4)用戶產(chǎn)品購(gòu)買數(shù)的分布圖
- (5)用戶累計(jì)消費(fèi)金額占比(百分之多少的用戶占了百分之多少的消費(fèi)額
grouped_user=df.groupby("user_id")
-
用戶個(gè)體消費(fèi)分析姥卢,就要根據(jù)user_id來(lái)分組了独榴。
grouped_user.sum().describe() #(1)用戶消費(fèi)金額棺榔、產(chǎn)品購(gòu)買數(shù)的描述統(tǒng)計(jì)症歇。
-
由上表可知:
-
①用戶平均每個(gè)人購(gòu)買7張CD忘晤,但中位數(shù)是3激捏,說(shuō)明有小部分用戶購(gòu)買了大量CD缩幸。
-
②用戶平均消費(fèi)106元,中位值有43盖喷,判斷同上课梳,有極值干擾暮刃。
-
由于散點(diǎn)圖對(duì)極值比較敏感椭懊,可以做簡(jiǎn)單的過(guò)濾氧猬,因此:
grouped_user.sum().plot.scatter(x='order_amount',y='order_products',c='b')
-
因此可得:
-
由上圖可知盅抚,數(shù)據(jù)主要集中在order_amount小于3000妄均,order_products小于200丛晦。因此超出這兩個(gè)范圍的都視為極值烫沙。
grouped_user.sum().query('order_amount<3000').query('order_products<200').plot.scatter(x='order_amount',y='order_products',c='b')
(2)用戶消費(fèi)金額和產(chǎn)品購(gòu)買數(shù)的散點(diǎn)圖
-
再縮小范圍做一幅圖锌蓄,可以更加清晰地看到瘸爽,絕大部分的數(shù)據(jù)集中在order_amount小于1000剪决,order_products小于100柑潦。:
-
用戶消費(fèi)金額的分布圖
grouped_user.sum().query('order_amount<1000').order_amount.plot.hist(bins=50)
#(3)用戶消費(fèi)金額的分布圖(根據(jù)上面散點(diǎn)圖览露,選擇數(shù)據(jù)較為集中的部分進(jìn)行分析差牛。)
-
用戶消費(fèi)產(chǎn)品數(shù)的分布圖
grouped_user.sum().query('order_products<100').order_products.plot.hist(bins=50)
#(4)用戶消費(fèi)產(chǎn)品數(shù)的分布圖(根據(jù)上面散點(diǎn)圖偏化,選擇數(shù)據(jù)較為集中的部分進(jìn)行分析)
-
用戶累計(jì)消費(fèi)金額占比:利用cumsum() 計(jì)算軸向元素累加和,再除以sum,得到累計(jì)比例袜瞬。
user_cumsum=grouped_user.sum().sort_values('order_amount').apply(lambda x:x.cumsum()/x.sum())
user_cumsum.reset_index().order_amount.plot()
-
按用戶消費(fèi)金額進(jìn)行升序排列邓尤,由圖可知汞扎,排名前50%的用戶只貢獻(xiàn)了15%左右的消費(fèi)額度澈魄。而排名最后3570的用戶就貢獻(xiàn)了60%的消費(fèi)額度痹扇。
3.用戶消費(fèi)行為:
- (1)用戶第一次消費(fèi)(首購(gòu))
- (2)用戶最后一次消費(fèi)
- (3)新老客戶消費(fèi)比:
- ① 多少用戶僅消費(fèi)了一次鲫构?② 每月新客占比结笨。
- (4)用戶分層:
- ① RFM炕吸。② 新算途、活躍扫外、回流廓脆、流失驾讲。
- (5)用戶購(gòu)買周期(按訂單)
- ① 用戶消費(fèi)周期描述吮铭。② 用戶消費(fèi)周期分布谓晌。
- (6)用戶生命周期(按第一次&最后一次消費(fèi))
- ① 用戶生命周期描述纸肉。② 用戶生命周期分布柏肪。
-
(1)用戶第一次消費(fèi)(首購(gòu)):
grouped_user.min().order_dt.value_counts().plot(figsize=(10,4))
#第一次購(gòu)買的時(shí)間烦味,就是時(shí)間最小拐叉,所以用.min()凤瘦。然后用value_counts()匯總起來(lái)蔬芥。
-
由圖可知笔诵,所有用戶的首購(gòu)都集中在97年1月1日至97年3月29日乎婿,2月上旬以前首購(gòu)客戶數(shù)量一直穩(wěn)定上升谢翎。在2月10日左右首購(gòu)客戶的數(shù)量產(chǎn)生劇烈波動(dòng),在2月下旬開(kāi)始穩(wěn)定榨婆,然后一直到3月下旬都呈現(xiàn)穩(wěn)定下跌的狀態(tài)良风。
-
(2)用戶最后一次消費(fèi):
grouped_user.max().order_dt.value_counts().plot()
#最后一次消費(fèi)烟央,日期最大疑俭。
-
用戶最后一次購(gòu)買的分布比第一次分布廣鬼贱,大部分最后一次購(gòu)買集中在前三個(gè)月这难,說(shuō)明有很多用戶購(gòu)買了第一次之后就不再購(gòu)買了姻乓。而且隨著時(shí)間的遞增蹋岩,最后一次購(gòu)買也在遞增剪个,說(shuō)明消費(fèi)呈現(xiàn)流失上升的狀況。
-
(3)新老客戶比:
- ① 有多少客戶只消費(fèi)了一次?
用count對(duì)grouped_user的order_dt進(jìn)行統(tǒng)計(jì)吓蘑,=1的就是只消費(fèi)一次客戶磨镶。
- ① 有多少客戶只消費(fèi)了一次?
(grouped_user.order_dt.count()==1).value_counts()
-
接近一半的客戶是只消費(fèi)了一次的。
- ② 每月新客占比沸移。
user_life=grouped_user.order_dt.agg(['min']).sort_values('min')
user_life['month']=user_life['min'].values.astype('datetime64[M]')
-
新建一個(gè)dataframe雹锣,user_life蕊爵,以min為首購(gòu)日期攒射。在user_life那里添加一列month,也是首購(gòu)日期咧最,不過(guò)是以月為單位矢沿。
user_life.groupby('month').month.describe()
-
由上表可知每個(gè)月份的首購(gòu)客戶數(shù)量:
1月份新客占比:7846/(7846+8476+7248)=33.29%
2月份新客占比:8476/(7846+8476+7248)=35.96%
3月份新客占比:7248/(7846+8476+7248)=30.75%
-
(4)用戶分層:
①RFM模型分析(最近一次消費(fèi)、消費(fèi)頻率栽惶、消費(fèi)金額)媒役。
rfm=df.pivot_table(index='user_id',
values=['order_products','order_amount','order_dt'],
aggfunc={'order_dt':'max',
'order_amount':'sum',
'order_products':'count'})
rfm.head()
利用透視表功能酣衷,把user_id作為索引席爽,根據(jù)order_amount和order_dt的值只锻,算出每個(gè)用戶最大購(gòu)買日期(最后一次消費(fèi)的日期)齐饮、總消費(fèi)金額祖驱,以及總消費(fèi)次數(shù)(這里用 'order_products':'count' 或者 'order_amout':'count'都一樣,因?yàn)樗愕氖谴螖?shù)而不是購(gòu)買的產(chǎn)品數(shù)匕坯。每次消費(fèi)買的產(chǎn)品數(shù)都可能不一樣葛峻。)然后我們可以看下rfm表大概長(zhǎng)啥樣:
rfm["R"]=(rfm.order_dt.max()-rfm.order_dt)/np.timedelta64(1,'D')
-
R逼侦,Recency榛丢,計(jì)算最近一次消費(fèi)距離至今多少天(因?yàn)閿?shù)據(jù)比較久遠(yuǎn)晰赞,所以用order_dt里面最大的一項(xiàng)作為今天)
rfm=rfm.rename(columns={'order_products':'F','order_amount':'M'})
-
F,F(xiàn)requency,消費(fèi)頻率晨仑,這里為總消費(fèi)次數(shù)洪己;M答捕,Monetary,為總金額痢站。
-
然后把按照r阵难、f呜叫、m這三個(gè)維度取平均,每個(gè)維度都把高于平均值的標(biāo)記為1娱颊,低于平均值得標(biāo)記為0.
- 這樣就有以下8種用戶:
111 重要價(jià)值客戶
011 重要保持客戶
101 重要挽留客戶
001 重要發(fā)展客戶
110 一般價(jià)值客戶
010 一般保持客戶
100 一般挽留客戶
000 一般發(fā)展客戶
- 這樣就有以下8種用戶:
def rfm_func(x):
level=x.apply(lambda x:'1' if x>=0 else '0')
label=level.R+level.F+level.M
d={ '111' : '重要價(jià)值客戶',
'011' : '重要保持客戶',
'101' : '重要挽留客戶',
'110' : '一般價(jià)值客戶',
'001' : '重要發(fā)展客戶',
'010' : '一般保持客戶',
'100' : '一般挽留客戶',
'000' : '一般發(fā)展客戶'}
result = d[label]
return result
rfm['label']=rfm[['R','F','M']].apply(lambda x:x-x.mean()).apply(rfm_func,axis=1)
-
設(shè)函數(shù),給rfm添加一列客戶標(biāo)示剧罩。
rfm.head(10)
rfm_grouped=rfm.groupby('label')
rfm_grouped.sum()
-
可以看到不同維度客戶的占比
pd.Series(rfm_grouped['label'].count(),name='series')
-
可以看到不同客戶的總數(shù)。
pd.Series(rfm_grouped['label'].count(),name='series').plot.pie()
-
畫(huà)成餅圖更加直觀。
-
從RFM分層可知赏陵,本數(shù)據(jù)集的大部分用戶為一般挽留客戶蝙搔,其次是重要保持客戶。
② 用戶生命周期:新勤晚、活躍赐写、回流、流失(不活躍)
- 新:首次消費(fèi)在最新時(shí)間端铛。
- 活躍:持續(xù)消費(fèi)禾蚕。
- 回流:曾今有過(guò)消費(fèi)换淆,隔了超過(guò)一個(gè)月后再次消費(fèi)。
- 流失:曾今有過(guò)消費(fèi)易猫,到最新時(shí)間為止還沒(méi)有消費(fèi)過(guò)准颓。
本例直接按一個(gè)月作為間隔。
pivoted_counts = df.pivot_table(index='user_id', # 使用數(shù)據(jù)透視怜跑,將每個(gè)用戶每月消費(fèi)次數(shù)計(jì)算出來(lái)
columns='month', # 如果沒(méi)有數(shù)據(jù)的話(比如1號(hào)客戶1997年2月1日沒(méi)有數(shù)據(jù))样勃,數(shù)據(jù)透視功能也會(huì)把它變成NaN。
values='order_dt',# 因此要價(jià)格fillna(0)把空值變成0性芬。
aggfunc='count').fillna(0)
pivoted_counts.head()
-
透視表大概長(zhǎng)這個(gè)樣子峡眶。
-
由于本次案例是需要計(jì)算每個(gè)月是否有消費(fèi),具體消費(fèi)多少次不重要植锉,因此可以把大于0的都當(dāng)做1:
purchase=pivoted_counts.applymap(lambda x:1 if x>0 else 0)
-
由于很多的首次消費(fèi)是在2月或者3月,所以在寫(xiě)函數(shù)判斷的時(shí)候需要考慮在內(nèi)俊庇,并不是所有的首次消費(fèi)都在一月:
def active_status(data):
status=[]
for i in range(18): #共18個(gè)月
#若本月沒(méi)有消費(fèi)
if data[i]==0:
if len(status)>0:
if status[i-1]=='unreg':
status.append('unreg')
else:
status.append('unactive')
else:
status.append('unreg')
#若本月消費(fèi)
else:
if len(status)==0:
status.append('new')
else:
if status[i-1] =='unactive':
status.append('return')
elif status[i-1]=='unreg':
status.append('new')
else:
status.append('active')
return status
函數(shù)的邏輯:
若本月沒(méi)有消費(fèi):
- 若之前是未注冊(cè)狮暑,則依舊為未注冊(cè)。
- 若之前有消費(fèi)辉饱,則為流失搬男。
- 其他情況,視為未注冊(cè)彭沼。
若本月有消費(fèi):
- 若是第一次消費(fèi)缔逛,則是新用戶。
- 如果之前有過(guò)消費(fèi)姓惑,且上個(gè)月為不活躍译株,則為回流。
- 如果上個(gè)月為未注冊(cè)挺益,則為新用戶歉糜。
- 除此之外,為活躍望众。
indexs=df['month'].sort_values().astype('str').unique()
purchase_sta=purchase.apply(lambda x:pd.Series(active_status(x),index=indexs),axis=1)
purchase_sta.head()
-
可得到一張不同用戶在不同月份的不同狀態(tài)(new=新匪补、active=活躍、return=回流烂翰、unactive=流失),unreg相當(dāng)于未注冊(cè)夯缺,指這個(gè)用戶在這個(gè)月及以前從未購(gòu)買過(guò)產(chǎn)品,主要為了統(tǒng)計(jì)起來(lái)更加方便而加進(jìn)去甘耿。
purchase_sta.replace('unreg',np.NaN).apply(lambda x:pd.value_counts(x)).fillna(0).T
-
把unreg替換成NaN踊兜,再用fillna(0)把空值填為0。然后轉(zhuǎn)置佳恬,把月份作為索引行捏境,狀態(tài)作為列于游,得到如下的表:
purchase_sta.replace('unreg',np.NaN).apply(lambda x:pd.value_counts(x)).fillna(0).T.plot.area(figsize=(10,4))
-
作出非堆積效果圖:
-
由圖可知,到了在前三個(gè)月垫言,新用戶增加的數(shù)量非常大贰剥。從三月一號(hào)開(kāi)始,用戶開(kāi)始快速流失筷频。到后面的幾個(gè)月流失用戶基本占絕大比例蚌成。
(5)用戶購(gòu)買周期(按訂單)
- 用戶消費(fèi)周期描述
- 用戶消費(fèi)周期分布
order_diff=grouped_user.apply(lambda x:x.order_dt-x.order_dt.shift())
-
算出不同用戶每次消費(fèi)相隔多少時(shí)間
order_diff.describe()
-
可以看到每次消費(fèi)相隔時(shí)間大致的分布。
(order_diff/np.timedelta64(1,'D')).hist(bins=20)
-
把分布圖作出來(lái):
-
由上圖以及描述可知:
訂單周期呈指數(shù)分布凛捏。
用戶的平均購(gòu)買周期是68天担忧。
絕大部分用戶的購(gòu)買周期都低于100天。
-
(6)用戶生命周期(按第一次&最后一次消費(fèi))
① 用戶消費(fèi)周期描述
(user_life['max']-user_life['min']).describe()
② 用戶消費(fèi)周期分布
((user_life['max']-user_life['min'])/np.timedelta64(1,'D')).hist(bins=40)
4.復(fù)購(gòu)率和回購(gòu)率分析
- (1) 復(fù)購(gòu)率(自然月內(nèi)坯癣,購(gòu)買多次的用戶占比)
- (2)回購(gòu)率(曾經(jīng)購(gòu)買過(guò)的用戶在某一時(shí)期內(nèi)的再次購(gòu)買的占比)
-
(1)復(fù)購(gòu)率:當(dāng)月多次購(gòu)買(超過(guò)一次)
purchase_r=pivoted_counts.applymap(lambda x: 1 if x>1 else np.NaN if x == 0 else 0)
-
"1"表示消費(fèi)次數(shù)大于1涵妥;"0"表示只消費(fèi)過(guò)一次;"NaN"表示沒(méi)買過(guò)坡锡,在.count中就不會(huì)計(jì)算在名額內(nèi)蓬网。求復(fù)購(gòu)率就計(jì)算消費(fèi)次數(shù)大于1的占比就行了。
(purchase_r.sum()/purchase_r.count()).plot(figsize=(10,4))
-
由上圖可知鹉勒,復(fù)購(gòu)率在前三個(gè)月高速增長(zhǎng)帆锋,最終在大概四月份的時(shí)候穩(wěn)定在20%左右。
-
(2) 回購(gòu)率:上個(gè)月有過(guò)消費(fèi)禽额,這個(gè)月又有消費(fèi)锯厢,就叫回購(gòu)。
def purchase_back(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)
status.append(np.NaN)
return status
purchase_b=purchase.apply(lambda x:pd.Series(purchase_back(x),index=indexs),axis=1)
purchase_b.head()
-
1代表本月有消費(fèi)脯倒,下個(gè)月也有消費(fèi)实辑;0代表本月有消費(fèi),但是下個(gè)月沒(méi)有消費(fèi)藻丢;NaN代表本月沒(méi)有消費(fèi)剪撬。
(purchase_b.sum()/purchase_b.count()).plot(figsize=(10,4))
-
本月與次月都有消費(fèi)的 / 本月有消費(fèi)的,就是回購(gòu)率悠反。
-
可以看出残黑,回購(gòu)率在前三個(gè)月快速增長(zhǎng),知道四月份增長(zhǎng)到30%以后斋否,一直維持在30%左右波動(dòng)梨水。