項目分享緣由:在學習完Numpy,Pandas,matplotlib後,熟練運用它們的最好方法就是實踐並總結。在下面的分享中,我會將每一步進行分析與代碼展現,html
但願能對你們有所幫助。sql
項目名稱:CD用戶消費行爲分析app
項目概述:本項目主要利用上面提到的三個工具進行數據的處理,來分析用戶消費行爲。數據來源與CDNow網站的用戶購買明細。ide
數據連接:連接:https://pan.baidu.com/s/1Cs36oeH0xgblzULmgX75-g 密碼:oeam函數
分析步驟:工具
第一部分:數據清洗學習
1. 數據類型的轉換
2. 空值處理
3. 異常值處理
第二部分:按月數據分析網站
1. 每個月的消費總金額
2. 每個月的消費次數
3. 每個月的產品購買量
4. 每個月的消費人數
第三部分:用戶個體消費數據分析spa
1. 用戶消費金額和消費次數的描述統計
2. 用戶消費金額和消費次數的散點圖
3. 用戶消費金額的分佈圖
4. 用戶消費次數的分佈圖
5. 用戶累計消費金額的佔比
第四部分:用戶消費行爲分析設計
1. 用戶第一次消費時間
2. 用戶最後一次消費時間
3. 新老客戶消費比
4. 用戶分層
5. 用戶購買週期
6. 用戶生命週期
導包
1 import numpy as np 2 import pandas as pd 3 from pandas import Series,DataFrame 4 import matplotlib.pyplot as plt 5 %matplotlib inline 6 from datetime import datetime
數據載入
1 # 由於原始數據中不包含表頭,在這裏定義好賦值 2 columns = ['user_id','order_dt','order_products','order_amount'] 3 4 # 參數 sep='\s+',用於匹配任意空白符 5 data = pd.read_csv('./CDNOW_master.txt',names=columns,sep='\s+')
''' 字段含義: user_id:用戶ID order_dt:購買日期 order_products:購買產品數 order_amount:購買金額 '''
'''
消費行業或者是電商行業通常是經過訂單數、訂單額、購買日期,用戶ID這四個字段來分析的,基本上這四個字段就能緱進行很豐富的分析。
'''
查看數據概況
display(data.head(),data.info()) ''' 結果以下: <class 'pandas.core.frame.DataFrame'> RangeIndex: 69659 entries, 0 to 69658 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 69659 non-null int64 1 order_dt 69659 non-null int64 2 order_products 69659 non-null int64 3 order_amount 69659 non-null float64 dtypes: float64(1), int64(3) memory usage: 2.1 MB 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 '''
1. 數據完整,沒有空數據 2. order_dt是 int類型,須要將其轉換爲時間類型 3. 用戶可能在同一天內重複購買(如:ID爲2的顧客在1月12日這一天內購買了兩次) 4. 由於後面要按月分析,因此須要添加一列 month
數據清洗
1 # order_dt 數據類型轉換 2 3 df = data.copy() 4 # format='%Y%m%d' 這裏要指明格式,不然可能出錯 5 df['order_dt'] = pd.to_datetime(df['order_dt'],format='%Y%m%d') 6 7 # 增長一列 month 8 df['month'] = df['order_dt'].values.astype('datetime64[M]') 9 10 display(df.head(),df.info(),df.describe()) 11 12 ''' 13 結果以下: 14 <class 'pandas.core.frame.DataFrame'> 15 RangeIndex: 69659 entries, 0 to 69658 16 Data columns (total 5 columns): 17 # Column Non-Null Count Dtype 18 --- ------ -------------- ----- 19 0 user_id 69659 non-null int64 20 1 order_dt 69659 non-null datetime64[ns] 21 2 order_products 69659 non-null int64 22 3 order_amount 69659 non-null float64 23 4 month 69659 non-null datetime64[ns] 24 dtypes: datetime64[ns](2), float64(1), int64(2) 25 memory usage: 2.7 MB 26 user_id order_dt order_products order_amount month 27 0 1 1997-01-01 1 11.77 1997-01-01 28 1 2 1997-01-12 1 12.00 1997-01-01 29 2 2 1997-01-12 5 77.00 1997-01-01 30 3 3 1997-01-02 2 20.76 1997-01-01 31 4 3 1997-03-30 2 20.76 1997-03-01 32 user_id order_products order_amount 33 count 69659.000000 69659.000000 69659.000000 34 mean 11470.854592 2.410040 35.893648 35 std 6819.904848 2.333924 36.281942 36 min 1.000000 1.000000 0.000000 37 25% 5506.000000 1.000000 14.490000 38 50% 11410.000000 2.000000 25.980000 39 75% 17273.000000 3.000000 43.700000 40 max 23570.000000 99.000000 1286.010000 41 '''
describe()是描述統計:
1. 大部分的訂單隻消費了少許的商品(平均2.4),有必定的極值干擾 2. 用戶的消費金額比較穩定,平均消費在35.8元,中位數在25.9元,有必定的極值干擾。 3. 用戶平均每筆訂單購買2.4個商品,標準差在2.3,稍具波動性。中位數2個商品,75分位數3個商品, 說明絕大部分訂單的購買量都很少。最大值在99個,數字比較高。購買金額的狀況差很少,大部分 訂單集中在小額 4. 通常而言,消費類的數據分佈都是長尾形。大部分用戶都是小額,然而小部分用戶貢獻了收入的大頭, 俗稱二八。
1 # 查看一下df的列,方便操做 2 df.columns
# 根據 month分組統計購買金額總和 order_amt_mon = df.groupby('month')['order_amount'].sum() order_amt_mon.head() ''' 結果: month 1997-01-01 299060.17 1997-02-01 379590.03 1997-03-01 393155.27 1997-04-01 142824.49 1997-05-01 107933.30 Name: order_amount, dtype: float64 '''
# 繪圖 order_amt_mon.plot(c='red')
能夠看到,97年1,2,3月銷量很高,每個月平局約3.6萬,後期銷量趨於平穩,每個月在1萬左右波動
df.groupby('month')['user_id'].count().plot(c='red')
df.groupby('month')['order_products'].sum().plot(c='red',figsize=(9,6))
# 去重的緣由:一我的可能在一個月內屢次消費 df.groupby('month')['user_id'].nunique().plot()
df.pivot_table(index='month', values=['user_id','order_amount','order_products'], aggfunc={'user_id':'count','order_amount':'sum','order_products':'sum'})
數據透視表是更簡單的方法,有了透視表,用裏面的數據繪圖也是狠方便的。
上面是經過維度月,來看整體趨勢。下面對個體進行分析,看消費能力如何。 大體分爲如下五個方向:
1. 用戶消費金額和消費次數的描述統計; 2. 用戶消費金額和消費次數的散點圖; 3. 用戶消費金額的分佈圖(二八法則); 4. 用戶消費次數的分佈圖; 5. 用戶累計消費金額的佔比(百分之多少的用戶佔了百分之多少的消費額)
1 group_user = df.groupby('user_id') 2 3 group_user.sum().describe()
1. 從用戶角度看,每位用戶平均購買7件商品,最多的用戶買了1033件。 2. 用戶平均消費額(客單價)100元,標準差是240,結合分位數和最大值看,平均值和75分位接近。 結論:確定存在小部分高額消費用戶,小部分的用戶佔了消費的大頭,符合二八法則。
group_user.sum().query('order_amount < 4000').plot(kind='scatter',x='order_amount',y='order_products')
經過繪製用戶的散點圖,用戶比較健康並且規律性很強。由於這是CD網站的銷售數據,商品比較單一,金額和商品質量的關係 也呈線性,沒幾個離羣點。
group_user.sum()['order_amount'].plot(kind='hist',bins=20)
1. 從直方圖可知,大部分用戶的消費能力確實不高,絕大部分集中在很低的消費檔次。高消費用戶在圖上幾乎看不到。這也確實 符合消費行爲的行業規律。 2. 雖然有極值干擾了咱們的數據,可是大部分用戶仍是集中在比較低的消費檔次。
group_user.sum().query('order_products < 100')['order_products'].plot(kind='hist',bins=40)
1 # cumsum() 滾動累加求和 2 user_cumsum = (group_user.sum().sort_values('order_amount').cumsum())/2500315.63 3 user_cumsum
user_cumsum.reset_index().order_amount.plot()
按用戶消費金額進行升序排序,由圖可知50%的用戶僅貢獻了15%的銷售額。而排名前5000的用戶就貢獻了60%的消費額 也就是說,只要維護好這5000個客戶,就能夠完成業績KPI的60%,若是能把5000個用戶運營的更好就能夠佔比70%-80%甚至更高。
在不少行業中首購是一個很重要的維度,它和渠道信息息息相關,尤爲針對客單價比較高客戶留存率比較低的行業, 第一次客戶從哪裏來能夠拓展出不少運營方式。
多少客戶僅消費了一次 每個月新客佔比
RFM 新、老、活躍、流失
用戶消費週期描述 用戶消費週期分佈
用戶生命週期描述 用戶生命週期分佈
# 求月份的最小值,即用戶消費行爲中的第一次消費時間 group_user = df.groupby('user_id') group_user['month'].min().value_counts() # 經過統計結果發現:全部用戶第一次消費都集中在前3個月 df['user_id'].unique().size---查看用戶總數 ''' 結果: 1997-02-01 8476 1997-01-01 7846 1997-03-01 7248 Name: month, dtype: int64 '''
group_user['month'].max().value_counts() ''' 結果: 1997-02-01 4912 1997-03-01 4478 1997-01-01 4192 1998-06-01 1506 1998-05-01 1042 1998-03-01 993 1998-04-01 769 1997-04-01 677 1997-12-01 620 1997-11-01 609 1998-02-01 550 1998-01-01 514 1997-06-01 499 1997-07-01 493 1997-05-01 480 1997-10-01 455 1997-09-01 397 1997-08-01 384 Name: month, dtype: int64 '''
group_user['order_dt'].max().value_counts().plot()
group_user['order_dt'].agg(['min','max'])
觀察用戶最後一次購買時間發現,用戶最後一次消費比第一次消費分佈廣,大部分最後一次消費集中在前三個月, 說明不少客戶購買一次後就再也不購買。隨着時間的增加,最後一次購買數也在遞增,消費呈現流失上升的狀況,用戶忠誠度在慢慢降低。
user_life = group_user['order_dt'].agg(['min','max']) user_life.head()
(user_life['min'] == user_life['max']).value_counts() '''
結果:
True 12054 False 11516 dtype: int64 '''
1 rfm = df.pivot_table(index='user_id', 2 values=['order_products','order_amount','order_dt'], 3 aggfunc={'order_products':'sum','order_amount':'sum','order_dt':'max'}) 4 5 rfm.head() 6 7 # order_products--求消費產品總數、order_amount---求消費總金額、order_dt--求最近一次消費時間
1 # rfm 距今天數 增長一列 2 3 #-(rfm.order_dt - rfm.order_dt.max())結果爲時間類型,將時間格式轉化爲整數或者浮點數的形式, 4 # 能夠除以單位‘D’,也能夠用astype轉化 5 rfm['R'] = -(rfm['order_dt'] - rfm['order_dt'].max()) / np.timedelta64(1,'D') 6 7 rfm.rename(columns ={'order_products':'F', 'order_amount':'M'},inplace = True ) 8 9 rfm.head()
R表示客戶最近一次交易的時間間隔,M表示客戶在最近一段時間內交易的金額。F表示客戶在最近一段時間內交易的次數。 F值越大,表示客戶交易越頻繁,反之則表示客戶交易不夠活躍。M表示客戶在最近一段時間內交易的金額。 M值越大,表示客戶價值越高,反之則表示客戶價值越低。
1 def rfm_func(x): 2 level = x.apply(lambda x : '1' if x >= 0 else '0') 3 label = level.R + level.F + level.M 4 5 dict = { 6 '111':'重要價值客戶', 7 '011':'重要保持客戶', 8 '101':'重要挽留客戶', 9 '001':'重要發展客戶', 10 '110':'通常價值客戶', 11 '010':'通常保持客戶', 12 '100':'通常挽留客戶', 13 '000':'通常發展客戶' 14 } 15 16 result = dict[label] 17 return result 18 19 # 用戶分層,這裏使用平均數 20 rfm['label'] = rfm[['R','F','M']].apply(lambda x : x - x.mean()).apply(rfm_func,axis=1) 21 22 rfm.head()
rfm.groupby('label').sum()
rfm.groupby('label').count()
1 rfm.loc[rfm.label == '重要價值客戶','color'] = 'g' 2 3 # ~:表示求非 4 rfm.loc[~(rfm.label == '重要價值客戶'),'color'] = 'r' 5 6 rfm.plot('F','R',kind='scatter',c=rfm.color)
1. 從RFM分層可知,大部分用戶爲重要保持客戶,可是這是因爲極值的影響,因此RFM的劃分應該儘可能以業務爲準。 儘可能用小部分的用戶覆蓋大部分的額度,不能爲了數據好看劃分等級。 2. RFM是人工使用象限法把數據劃分爲幾個立方體,立方體對應相應的標籤,咱們能夠把標籤運用到業務層面上。 好比重要保持客戶貢獻金額最多159203.62,咱們如何與業務方配合把數據提升或者維護;而重要發展客戶 和重要挽留客戶他們有一段時間沒消費了,咱們如何把他們拉回來。
1 pivoted_counts = df.pivot_table(index='user_id', 2 columns='month', 3 values='order_dt', 4 aggfunc='count', 5 fill_value=0) 6 pivoted_counts.head()
用戶每月的消費次數,對於生命週期的劃分只須要知道用戶本月是否消費,消費次數在這裏並不重要,須要將模型進行簡化
# 簡化 df_purchase = pivoted_counts.applymap(lambda x: 1 if x>0 else 0) df_purchase.tail()
1 # 用戶生命週期狀態變化 2 3 def active_status(data): 4 5 ur = 'unreg' #未註冊 6 ua = 'unactive' #不活躍 7 n = 'new' #新用戶 8 a = 'active' #活躍 9 r = 'return' #迴流用戶:上個月不活躍,這個月活躍 10 status = [] 11 for i in range(18): 12 #若本月沒有消費 13 if data[i] == 0: 14 if len(status) > 0: 15 if n not in status: 16 status.append(ur) 17 else: 18 status.append(ua) 19 else: 20 status.append(ur) 21 22 #若本月消費 23 else: 24 if len(status) == 0: 25 status.append(n) 26 else: 27 if n not in status: 28 status.append(n) 29 elif status[-1] == ua: 30 status.append(r) 31 else: 32 status.append(a) 33 # 不能直接返回 status,不然會失去表頭 ---重點 34 return pd.Series(status, index = df_purchase.columns) 35 36 pivoted_status = df_purchase.apply(active_status,axis = 1) 37 38 pivoted_status.head()
purchase_status_ct = pivoted_status.replace('unreg',np.NaN).apply(lambda x: pd.value_counts(x)) purchase_status_ct
1 purchase_status_ct.fillna(0,inplace=True) 2 3 # 浮點數轉換爲整數 4 purchase_status_ct.astype(np.int) 5 6 # 繪面積圖 (purchase_status_ct要求一下轉置矩陣) 7 purchase_status_ct.T.plot(kind='area')
purchase_status_ct.T.apply(lambda x: x/x.sum(),axis=1)
由上表能夠看到每個月用戶的消費狀態變化。 1. 活躍用戶,持續消費用戶對應的是---消費運營質量; 2. 迴流用戶(上月不消費本月消費)對應的是---喚回運營狀況; 3. 不活躍的用戶對應的是---用戶流失狀況。
# 將用戶分組後,每一個用戶的訂單購買時間進行錯位相減 shift():下一行減上一行的值 order_diff = group_user.apply(lambda x:x.order_dt - x.order_dt.shift()) order_diff.head(10) ''' 結果: user_id 1 0 NaT 2 1 NaT 2 0 days 3 3 NaT 4 87 days 5 3 days 6 227 days 7 10 days 8 184 days 4 9 NaT Name: order_dt, dtype: timedelta64[ns] '''
1. 能夠看到:user_id 1爲空值,說明用戶只購買過一個訂單 2. user_id 2 的用戶第一筆訂單與第二筆訂單在同一天購買
(order_diff / np.timedelta64(1,'D')).hist(bins = 20)
訂單週期呈指數分佈,用戶的平均購買週期是68天,絕大部分用戶的購買週期都低於100天。
user_life = group_user['order_dt'].agg(['min','max']) user_life.head()
user_life['life_period'] = user_life['max'] - user_life['min'] user_life.head()
user_life['life_period'].describe() ''' 結果: count 23570 mean 134 days 20:55:36.987696 std 180 days 13:46:43.039788 min 0 days 00:00:00 25% 0 days 00:00:00 50% 0 days 00:00:00 75% 294 days 00:00:00 max 544 days 00:00:00 Name: life_period, dtype: object '''
能夠看到,數據偏移較大,中位數是0天,意味着超過50%的用戶生命週期是0天,即只購買了1次。
(user_life['life_period'] / np.timedelta64(1,'D')).plot(kind='hist',bins=40)
能夠看出,用戶生命週期受只購買一次的用戶影響比較大(所以能夠排除生命週期爲0天的用戶再觀察)
# 用戶生命週期大於0天的分佈圖 cond = user_life['life_period'] / np.timedelta64(1,'D') cond[cond>0].hist(bins=40)
1. 有很多用戶生命週期靠攏在0天,部分質量差的用戶雖然消費了兩次,可是仍然沒法持續, 用戶首次消費30天之內應該儘可能引導; 2. 少部分用戶集中在50-300天,屬於普通型的生命週期; 3. 高質量用戶的生命週期,集中在400天之後,這屬於忠誠用戶。
復購率:天然月內,購買屢次的用戶佔比 回購率:曾經購買過的用戶在某一時期內的再次購買佔比
# applymap()針對DataFrame裏的全部數據。使用lambda函數,由於設計了多個結果,因此要用兩個if else 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))
1. 用sum和count相除便可計算出復購率。這兩個函數都會忽略掉NaN,而NaN是沒有消費的用戶,count不管是0或1都會統計, 因此是總的消費用戶數。而sum求和計算了消費兩次及以上的用戶。這裏比較巧妙的用了替代法計算復購率。sql中也能夠用。 2. 圖上能夠看出復購率在早期,由於大量新用戶加入的關係,新客的復購率並不高,譬如1月新客們的復購率只有6%左右。 而在後期,這時的用戶都是大浪淘沙剩下的老客戶,復購率比較穩定,在20%左右。單看新客和老客,復購率有三倍左右的差距。
1 # 消費金額進行透視 2 3 pivoted_amount = df.pivot_table(index='user_id', 4 columns='month', 5 values='order_amount', 6 aggfunc='mean') 7 pivoted_amount.fillna(0,inplace=True) 8 columns_month = df['month'].sort_values().astype('str').unique() 9 pivoted_amount.columns = columns_month 10 11 # pivoted_amount.head() 12 pivoted_amount.head()
pivoted_purchase = pivoted_amount.applymap(lambda x : 1 if x>0 else 0) pivoted_purchase.head()
1 # 0表明當月消費過次月沒有消費過,1表明當月消費過次月依然消費 2 3 def purchase_return(data): 4 status = [] 5 for i in range(17): 6 if data[i] == 1: 7 if data[i+1] ==1: 8 status.append(1) 9 if data[i+1] == 0: 10 status.append(0) 11 else: 12 status.append(np.NaN) 13 status.append(np.NaN) 14 return pd.Series(status, index = pivoted_purchase.columns) 15 16 pivoted_purchase_return = pivoted_purchase.apply(purchase_return,axis = 1) 17 18 pivoted_purchase_return.head()
# 回購率,計算方法和復購率相似,一樣的邏輯 (pivoted_purchase_return.sum()/pivoted_purchase_return.count()).plot(figsize=(10,4))
1. 從上圖看出,用戶的回購率高於復購率,約在30%左右,和老客戶差別不大。 2. 從回購率和復購率綜合分析,新客的總體質量低於老客,老客的忠誠度(回購率)很好,消費頻次稍次