[機器學習筆記] Python數據分析:用戶消費行爲(持續更新)

Python數據分析:用戶消費行爲(持續更新)

紅酒品鑑和用戶消費行爲分析是我學習Python數據分析入門的兩個案例,記錄一下。

網絡上關於這兩個案例的介紹非常多,但是我在學習過程中,發現有很多文章的邏輯不是很清晰,代碼也調試不同。

所以,還是想把自己的調試代碼寫出來。

參考博文:

http://www.360doc.com/content/17/0717/17/16619343_672115832.shtml

https://blog.csdn.net/weixin_44266342/article/details/94187331?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

https://blog.csdn.net/weixin_44875199/article/details/91452282?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

 


準備

1. 數據分析的目標

2. 數據集

3. 在進行數據分析之前,進行數據清洗。即:處理缺失值,數據類型轉化,按照需要將數據整理好。


開始

本數據集共有 6 萬條左右數據,數據爲 CDNow 網站 1997年1月至1998年6月的用戶行爲數據,共計 4 列字段,分別是:

  • user_id: 用戶ID
  • order_dt: 購買日期
  • order_products: 購買產品數
  • order_amount: 購買金額
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
columns = ['user_id','order_dt','order_products','order_amount']
# 用戶id 購買日期 購買產品數 購買金額
df=pd.read_csv('G:\\Data Scientist Learning\\CDNOW_master.txt',names=columns,sep='\s+') #載入數據
print(df.head())

 

接下來:

df.info() #檢查數據是否存在空值

數據讀出無誤時要檢查數據中是否存在空值,並檢查數據的數據類型。發現數據中並不存在空值,很乾淨的數據。那接下來既然我們需要每月這個數據,就要給order_dt這一列的數據進行適當的轉換一下,轉化成通常的時間格式:Y(年)m(月)D(日)。

下面那句代碼的意思是:在df裏新增加一列列名是month,取出這一列的日期order_dt然後掉取這一列的值把值轉換成以月爲單位的,例如6月1號到30號統統屬於6月。
 

 

 這時候可以對數據進行簡單初步分析,用df.describe()。

對以上數據進行分析:

  • 均值:產品購買數量均值在2.4;
  • 中位數:中位數是2;
  • 四分位數:其3/4位數是3
  • 最小值和最大值分別爲:1和3
  • 人均消費CD金額爲:35.89元;中位數爲:25.98元

說明:用戶購買量大部分都不多,少部分購買量大的,最高購買量是99,其中存在一定的極致干擾。用戶的訂單金額比較穩定,人均購買CD金額在35,中位數在25元,存在極致干擾,很多銷售行業都是類似這種分佈,小額比較多,大額的較少,收入來源很大一部分是來自大額。也即是二八定律。


按月分析數據趨勢 

在這裏用到了一個groupby,一個在數據分析中非常好用的函數,這一節是要按月分析用戶行爲,用groupby對用戶按照月分分組。

分組完成之後得到一個新的dataframe叫group_month,然後直接取出組裏的order_amount並求和可得到每個月份的銷售總額,並且畫出折線圖

 

 

 

 


從上面三個圖可以看出數據沒有什麼問題,用戶購買總額跟用戶購買次數以及用戶購買量走勢是大致相同的,但是從四月份開始銷量嚴重下滑,具體是原因是什麼,我們可以再來看一下每個月的消費人數:

 

每月的消費人數小於每月的消費次數,但是區別不大。前三個月每月的消費人數在8000—10000之間,後續月份,平均消費人數在2000不到。一樣是前期消費人數多,後期平穩下降的趨勢。


用戶個體消費數據分析:

之前的都是看趨勢,現在看個體的消費水平如何。主要分析的對象是:

  • 用戶消費金額和消費次數的統計以及散點圖來觀察用戶的平均消費水平
  • 用戶消費金額的分佈圖(二八原則)
  • 用戶消費次數的分佈圖
  • 用戶累計消費金額的佔比(百分之多的用戶佔了百分之多少的消費額)
     
group_user_ID = df.groupby('user_id')
print(group_user_ID.sum().describe())

 

 

以user_id爲索引進行分組,在用戶的角度來看,每位用戶平均購買七張CD,最少的用戶購買了一張,最多購買1033張,中位數是三張,反映出有些數據的波動還是挺大,用戶購買的金額平均是106中位數是43,購買最大金額是13990,四分位數19,這些數據加上之前的按月分析,大致勾勒出CD銷售大致趨勢,在一段時間銷量上升,突然在某時期不景氣開始猛地下跌,但是大部分還都是處於平穩,銷售額也低。
 

group_userID = df.groupby('user_id')
group_userID.sum().query("order_amount<3000").plot.scatter(x = 'order_amount',y = 'order_products')
# group_userID.sum().order_amount. plot.hist(bins = 20)
# group_userID.sum().query("order_products<100").order_products.plot.hist(bins = 40)
#柱狀圖
plt.show()

上一段代碼的意思是以user_id爲索引進行分組,但是分組之後可能會發現打印出來的是對象,因爲需要對分組完的數據進行進一步操作,例如:求和求均值等等。然後再這裏用到的是對數據進行求和,然後調用quary方法規定x軸座標order_amunt的值小於3000,調用plot裏的scatter散點圖,畫出散點圖。

用戶購買金額和購買數量的散點圖

 從散點圖中看出數據集中分佈在購買金額小購買量少上, 數據基本成線性分佈,購買CD金額大數量就多,金額少數量也少。

group_userID.sum().order_amount. plot.hist(bins = 20)

用戶消費金額分佈 

從消費金額中可以看出消費金額偏向很低基本在0-1000元之間,可看出其主要還是面向低消費人羣。 

group_userID.sum().query("order_products<100").order_products.plot.hist(bins = 40)

 用戶消費次數分佈

從消費次數柱狀圖中可以看出,絕大部分用戶消費次數並不多,甚至很少,消費次數基本在0-20次之間。
 

cum1 = group_userID.sum().sort_values("order_amount").apply(lambda x:x.cumsum()/x.sum())
cum1.reset_index().order_amount.plot()
plt.show()

上面這段代碼的意思是求出用戶的累計消費金額佔比,cumsum方法是滾動求和,對求完佔比之後的dataframe進行重置 索引,重置索引之後的索引是按照升序排列好的,所以畫出的圖橫座標就是索引,縱座標就是消費額所佔比例,可以反映出百分之多少的用戶佔了消費額的百分之多少。 

從消費額佔比中看得出百分之五十的用戶才佔了百分之二十不到的消費額,排名前五百的用戶佔有了快百分之五十的消費額,消費還是主要集中在一些大客戶上。


用戶消費行爲分析

  • 用戶第一次消費(首購)時間
  • 用戶最後一次消費時間
  • 用戶分層

RFM (RFM模型是衡量客戶價值和客戶創利能力的重要工具和手段)

新、老、活躍、迴流、流失

  • 用戶購買週期(按訂單)

用戶消費週期描述

用戶消費週期分佈

  • 用戶生命週期(按第一次&最後一次消費)

用戶生命週期描述

用戶生命週期分佈

用戶首購時間
 

# 用戶首次購買時間
grouped_user.min().month.value_counts()
grouped_user.min().order_dt.value_counts().plot() # 首購
plt.show()

 

用戶最後一次購買時間

grouped_user.month.max().value_counts()
grouped_user.max().order_dt.value_counts().plot() # 最後一次消費
plt.show()

 

首購都在一月到三月份,最後一次購買也基本集中在一月到三月份,長期活躍的客戶不是很多,大部分用戶是購買一次之後不在購買,隨着時間的增長,最後一次購買的用戶量也在不斷增加。

第一次消費時間等於最後一次消費時間的數量佔到了一半,說明很多顧客僅消費一次不再消費。


用戶數據分層

將用戶分成:

  • 111':'重要價值客戶',
  • '011':'重要保持客戶',
  • '101':'重要挽留客戶',
  • '001':'重要發展客戶',
  • '110':'一般價值客戶',
  • '010':'一般保持客戶',
  • '100':'一般挽留客戶',
  • '000':'一般發展客戶'

至於前面數字的意義等下會解釋。

構建RFM模型(Recency Frequency Monetary

分別執行下面兩段代碼:

rfm = df.pivot_table(index = 'user_id',
                    values = ['order_products', 'order_amount', 'order_dt'],
                    aggfunc = {'order_dt':'max',
                               'order_amount':'sum',
                               'order_products':'sum'
                              })
rfm.head()

到這裏就開始使用一個新的函數,及python的透視函數,point_table此函數功能跟excel的透視表一樣,但是比透視表更加靈活,df.point_table(index = [],columns = [],values = [],aggfunc = [])這幾個參數等會要用到,先來解釋一下這幾個參數的意思:

  • index指的是分組的時候選擇哪個字段作爲索引;
  • columns指的是指定的列名是什麼;
  • values可以決定保留哪些屬性字段;
  • aggfunc則是決定對每個字段執行的函數
  • 不寫默認執行sum
rfm['R'] = -(rfm.order_dt - rfm.order_dt.max()) / np.timedelta64(1, 'D')
rfm.rename(columns = {'order_products': 'F', 'order_amount':'M'},
                 inplace=True)
rfm.head()

 

def rfm_func(x):
    level = x.apply(lambda x:'1' if x>=1 else '0')
    label = level.R + level.F + level.M
    d = {
        '111':'重要價值客戶', 
        '011':'重要保持客戶',
        '101':'重要挽留客戶',
        '001':'重要發展客戶',
        '110':'一般價值客戶',
        '010':'一般保持客戶',
        '100':'一般挽留客戶',
        '000':'一般發展客戶'
        }
    result = d[label]
    return result

rfm['label'] = rfm[['R', 'F', 'M']].apply(lambda x:x-x.mean()).apply(rfm_func,axis=1)
rfm.head()

rfm.groupby('label').sum()

for label,gropued in rfm.groupby('label'):
    x= gropued['F']
    y = gropued['R']
    
    plt.scatter(x,y,label = label) # 利用循環繪製函數
plt.legend(loc='best') # 圖例位置
plt.xlabel('Frequency')
plt.ylabel('Recency')
plt.show()

從 RFM 分層可知,大部分用戶爲重要保持客戶,但這是因爲極值存在,所以 FRM 的劃分應按照業務爲準劃分

  • 儘量用小部分的用戶覆蓋大部分的額度
  • 不要爲了數據好看而劃分等級

按新、活躍、迴流、流失分層用戶

# 通過每月是否消費來劃分用戶
pivoted_counts = df.pivot_table(index = 'user_id',
                                columns = 'month',
                                values = 'order_dt',
                                aggfunc = 'count').fillna(0)
pivoted_counts.columns = df.month.sort_values().astype('str').unique()
pivoted_counts.head()

df_purchase = pivoted_counts.applymap(lambda x: 1 if x> 0 else 0)
df_purchase.tail() 

若本月沒有消費

  • 若之前未註冊,則依舊未註冊
  • 若之前有消費,則爲流失/爲活躍
  • 其他情況,未註冊

若本月消費

  • 若是第一次消費,則爲新用戶
  • 如果之前有過消費,上個月爲不活躍,則爲迴流
  • 如果上個月未註冊,則爲新用戶
  • 除此之外,爲活躍
purchase_states = df_purchase.apply(active_status,axis = 1)
purchase_states.tail()

 

purchase_states_ct = purchase_states.replace('unreg',np.NaN).apply(lambda x:pd.value_counts(x))
purchase_states_ct

unreg 狀態排除掉,是未來才成爲新用戶,作爲不同分呈用戶每月統計量。

# 轉置後方便觀察
purchase_states_ct.fillna(0).T

# 繪製面積圖
purchase_states_ct.fillna(0).T.plot.area(figsize = (12, 6))
plt.show()

由面積圖,藍色和灰色區域佔大面積,可以不看,因爲這只是某段時間消費過的用戶的後續行爲。其次紅色代表的活躍用戶非常穩定,是屬於核心用戶,以及紫色的迴流用戶,這兩個分層相加,就是消費用戶人數佔比(後期沒用新客)

迴流用戶佔比

plt.figure(figsize=(20, 4))
rate = purchase_states_ct.fillna(0).T.apply(lambda x: x/x.sum())
plt.plot(rate['return'],label='return')
plt.plot(rate['active'],label='active')
plt.legend()
plt.show()

  • 迴流用戶比:某個時間段內迴流用戶在總用戶中的佔比

       由圖可知,用戶每月迴流用戶比佔 5% ~ 8% 之間,有下降趨勢,說明客戶有流失傾向。

  • 迴流用戶率:上月有多少不活躍用戶在本月消費

       由於這份數據的不活躍用戶量基本不變,所以這裏的迴流率,也近似等於迴流比

  • 活躍用戶比:某個時間段內活躍用戶在總用戶中的佔比。

       活躍用戶的佔比在 3% ~ 5%間,下降趨勢更顯著,活躍用戶可以看作連續消費用戶,忠誠度高於迴流用農戶。

結合活躍用戶和迴流用戶看,在後期的消費用戶中,60%是迴流用戶,40%是活躍用戶,整體用戶質量相對不錯。也進一步說明前面用戶消費行爲分析中的二八定律,反應了在消費領域中,狠抓高質量用戶是不變的道理。

用戶購買週期

# 訂單時間間隔
order_diff = grouped_user.apply(lambda x:x.order_dt - x.order_dt.shift())
order_diff.head(10)

order_diff.describe()

# 訂單週期分佈圖
(order_diff / np.timedelta64(1, 'D')).hist(bins = 20)
plt.show()

  • 訂單週期呈指數分佈
  • 用戶的平均購買週期是 68 天
  • 絕大部分用戶的購買週期低於 100 天
  • 用戶生命週期圖是典型的長尾圖,大部分用戶的消費間隔確實比較短。不妨將時間召回點設爲消費後立即贈送優惠券,消費後10天詢問用戶CD怎麼樣,消費後30天提醒優惠券到期,消費後60天短信推送。

用戶生命週期

# 最後一次購買的時間減去首購時間
user_life = grouped_user.order_dt.agg(['min', 'max'])
user_life.head()

# 只消費過一次的用戶佔比
(user_life['min'] == user_life['max']).value_counts().plot.pie()
plt.show()

(user_life['max'] - user_life['min']).describe()

通過描述可知,用戶平均生命週期 134 天,比預想高,但是平均數不靠譜,中位數 0 天,大部分用戶第一次消費也是最後一次,這批屬於低質量用戶,而最大的是 544 天,幾乎是數據集的總天數,這用戶屬於核心用戶。
因爲數據中的用戶都是前三個月第一次消費,所以這裏的生命週期代表的是1月~3月用戶的生命週期。因爲用戶會持續消費,這段時間過後還會繼續消費,用戶的平均生命週期會增長。
 

plt.figure(figsize=(20, 4))
plt.subplot(121)
((user_life['max'] - user_life['min']) / np.timedelta64(1, 'D')).hist(bins = 15)
plt.title('二次消費以上用戶的生命週期直方圖')
plt.xlabel('天數')
plt.ylabel('人數')

# 過濾生命週期爲0 的
plt.subplot(122)
u_l = ((user_life['max'] - user_life['min']).reset_index()[0] / np.timedelta64(1, 'D'))
u_l[u_l > 0].hist(bins = 40)
plt.title('二次消費以上用戶的生命週期直方圖')
plt.xlabel('天數')
plt.ylabel('人數')
plt.show()

通過兩圖對比看出,過濾掉週期爲 0 的用戶後,圖像呈雙峯結構,雖然還是有不少用戶生命週期趨於 0 天,但是相比第一幅圖,靠譜多了。部分低質用戶,雖然消費兩次,但還是不能持續消費,要想提高用戶轉化率,應該用戶首次消費 30 天內儘量引導,少部分用戶集中在 50 - 300 天,屬於普通用戶,忠誠度一般。集中在 400 天以後的,是高質量用戶了,後期人數還在增加,這批用戶已經屬於核心用戶了,忠誠度極高,儘量維護這批用戶的利益。

# 消費兩次以上用戶平均生命週期
u_l[u_l > 0].mean()

消費兩次以上的用戶平均生命週期是 276 天,遠高於總體,所以如何在用戶首次消費後引導其進行多次消費,可以有效提高用戶生命週期。


復購率和回購率分析

  • 復購率
    • 自然月內,購買多次的用戶佔比
  • 回購率
    • 曾經購買的用戶在某一時期內的再次購買的佔比
# 消費兩次及以上爲 1 ,消費一次爲 0 ,沒有消費爲空
purchase_r = pivoted_counts.applymap(lambda x: 1 if x > 1 else np.NaN if x==0 else 0)
purchase_r.head()

 

# 復購率折線圖
(purchase_r.sum() / purchase_r.count()).plot(figsize = (10, 4)) 
plt.show()

復購率穩定在 20% 左右,前三個月因爲有大量新用戶湧入,而這批用戶只購買了一次,所以導致復購率降低。

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 pd.Series(status,df_purchase.columns)
purchase_b = df_purchase.apply(purchase_back,axis = 1)
purchase_b.head()

1 爲回購用戶, 0 爲上月沒購買當月購買過,NaN 爲連續兩月都沒購買

由回購率圖可以看出,用戶回購率高於復購率,約在 30% 左右,波動性較強。新用戶回購率在 15 % 左右,與老用戶相差不大。
由人數分佈圖發現,回購人數在前三月之後趨於穩定,所以波動產生的原因可能由於營銷淡旺季導致,但之前復購用戶的消費行爲與會回購用戶的行爲大致相同,可能有一部分用戶重合,屬於優質用戶。
結合回購率和復購率分析,可以新客的整體忠誠度低於老客,老客的回購率較好,消費頻率稍低,這是 CDNow 網站的用戶消費特徵。


留存率分析

 

# 每一次消費距第一次消費的時間差值
user_purchase = df[['user_id','order_products','order_amount','order_dt']]
user_purchase_retention = pd.merge(left = user_purchase,
                                   right = user_life['min'].reset_index(),
                                  how = 'inner',
                                  on = 'user_id')
user_purchase_retention['order_dt_diff'] = user_purchase_retention['order_dt']-user_purchase_retention['min']
user_purchase_retention['dt_diff'] = user_purchase_retention.order_dt_diff.apply(lambda x: x/np.timedelta64(1,'D'))

user_purchase_retention.head()

 

 

後續補充。

 

 

 

 




 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章