用戶消費行爲的分析報告
想必大家對於CD用戶消費者行爲的分析已經見得多了,這裏就不再一一敘述,這裏主要是作爲我的一個小練習,來提高自己處理業務的能力。
項目需求如下:
(1)用戶消費趨勢分析
- 每月的消費總金額
- 每月的消費次數
- 每月的產品購買量
- 每月的消費人數
(2)用戶個體消費行爲分析
- 用戶消費金額和消費總數的描述統計
- 用戶消費金額和消費總數的散點圖
- 用戶消費金額和消費總數的分佈圖
- 用戶累計消費金額的佔比
(3)用戶消費行爲分析
- 用戶第一次消費時間(用戶首次購買產品的時間)
- 用戶最後一次消費時間
- 新老客消費佔比
- 用戶分層(RFM模型)
獲取數據
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = False
%matplotlib inline
columns = ['user_id','order_dt','order_products','order_amount']
CDNOW = pd.read_table('./CDNOW_master.txt',sep='\s+',names=columns) # \s+多個空格進行分割,+是空格匹配
CDNOW.head()
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 |
數據來源CDNow網站的用戶購買明細,主要包括以下幾個字段:
- user_id 用戶ID
- order_dt 購買日期
- order_products 購買產品數
- order_amount 購買金額
數據處理
- 檢查數據是否有空值
# CDNOW.info()
CDNOW.isnull().any()
user_id False
order_dt False
order_products False
order_amount False
dtype: bool
用CDNOW.info()可以發現order_dt的類型爲int型,需要轉換爲日期類型
CDNOW['order_dt'] = pd.to_datetime(CDNOW['order_dt'],format='%Y%m%d')
添加一列month,方便後面進行分析
CDNOW['month'] = CDNOW['order_dt'].values.astype('datetime64[M]')
CDNOW.head()
user_id | order_dt | order_products | order_amount | month | |
---|---|---|---|---|---|
0 | 14048 | 1997-02-24 | 1 | 11.77 | 1997-03-01 |
查看數據描述
print(CDNOW.mode())
CDNOW.describe()
user_id order_dt order_products order_amount month
0 14048 1997-02-24 1 11.77 1997-03-01
user_id | order_products | order_amount | |
---|---|---|---|
count | 69659.000000 | 69659.000000 | 69659.000000 |
mean | 11470.854592 | 2.410040 | 35.893648 |
std | 6819.904848 | 2.333924 | 36.281942 |
min | 1.000000 | 1.000000 | 0.000000 |
25% | 5506.000000 | 1.000000 | 14.490000 |
50% | 11410.000000 | 2.000000 | 25.980000 |
75% | 17273.000000 | 3.000000 | 43.700000 |
max | 23570.000000 | 99.000000 | 1286.010000 |
- 從用戶購買的商品數來看,平均每位用戶購買2.4個商品,標準差在2.3左右,證明有一定的偏差,而中位數爲2,3/4分位數爲3,說明了大多數的訂單量都不多,購買商品的最大值爲99。(中位數<均值,結合衆數可以發現,用戶購買的商品數分佈是屬於右偏分佈的,均值>中位數>衆數)。
- 從用戶購買的金額來看,每個用戶貢獻的金額爲36,而最大值達到了1286,證明用戶的購買金額也是服從長尾分佈的,符合二八原則,即20%的用戶貢獻了80%的交易額。
數據分析
進行用戶消費趨勢的分析(按月)
該部分內容主要是從用戶每月的消費總金額、每月的消費次數、每月的產品購買量以及每月的消費人數進行分析。
每月的消費總金額
按月分組後,對order_amount進行求和,可以得到每月的消費總金額
group_month = CDNOW.groupby('month') # 按月分組
group_month_amount = group_month['order_amount'].sum()
plt.style.use('ggplot')
group_month_amount.plot()
plt.xlabel('月份')
plt.ylabel('消費金額')
plt.title('每月的消費金額折線圖')
從上圖可以看出,消費金額在1997年的前三個月達到了高峯。其中在3月份,銷售額達到了最大值,爲393155.27元,之後消費金額呈下降趨勢,並不斷趨於平穩的狀態。
每月的消費次數
按月分組後,對order_product進行計數,可以得到每月的商品消費次數
group_month_order = group_month['order_products'].count()
group_month_count.plot(color='yellow')
plt.xlabel('月份')
plt.ylabel('消費次數')
plt.title('每月的消費次數')
從上圖可以看出,消費次數在1997年的前三個月達到了最大值。其中在3月份達到了最大值,消費次數爲 11598次,平均消費次數在10000左右,而後續的月份平均消費次數在2500左右。
每月的商品購買量
按月分組後,對order_product進行求和,可以得到每月的商品購買量。
group_month_product = group_month['order_products'].sum()
group_month_product.plot(color='green')
plt.xlabel('月份')
plt.ylabel('商品購買量')
plt.title('每月的商品購買量')
和前面的分析一樣,商品購買量的最大值出現在了1997年的前三個月中,其中最大值達到了26159,其中不斷下降並趨於平穩狀態。1997年的前三個月出現購買量劇增的情況,可能與當時的外部環境、內部環境等多方面因素都有一定的聯繫,這裏沒能拿到進一步的數據,無法詳細的進行分析。
每月的消費人數
使用nunique()去重
# group_userid_count = df.groupby(['month','user_id']).count().reset_index()
# group_userid_count = group_month['user_id'].nunique()
CDNOW.groupby('month').user_id.nunique().plot(color='blue')
plt.xlabel('月份')
plt.ylabel('消費人數')
plt.title('每月的消費人數')
從上圖來看,每月的消費人數在1997年的前三個月出現了最大值,而後出現平穩的下降趨勢,由於其他的數據,暫時還沒有辦法分析出前三個月出現最大值的原因。
使用數據透視表進行求取
也可以使用數據透視表進行求和、計數等操作
CDNOW.pivot_table(
index='month',
values= ['user_id','order_products','order_amount'],
aggfunc={
'order_products':'sum',
'order_amount':'sum',
'user_id':'count' }
).head()
order_amount | order_products | user_id | |
---|---|---|---|
month | |||
1997-01-01 | 299060.17 | 19416 | 8928 |
1997-02-01 | 379590.03 | 24921 | 11272 |
1997-03-01 | 393155.27 | 26159 | 11598 |
1997-04-01 | 142824.49 | 9729 | 3781 |
1997-05-01 | 107933.30 | 7275 | 2895 |
用戶個體消費行爲分析
這部分內容主要從用戶消費金額和消費總數的描述統計、用戶消費金額和消費總數的散點圖、用戶消費金額和消費總數的分佈圖(二八法則)、用戶累計消費金額的佔比這幾個方面進行分析。
用戶消費金額和消費總數的描述統計
根據用戶的ID進行分組,分別對每個用戶購買的商品數、消費金額進行求和。
group_user = CDNOW.groupby('user_id')
group_user.sum().describe()
order_products | order_amount | |
---|---|---|
count | 23570.000000 | 23570.000000 |
mean | 7.122656 | 106.080426 |
std | 16.983531 | 240.925195 |
min | 1.000000 | 0.000000 |
25% | 1.000000 | 19.970000 |
50% | 3.000000 | 43.395000 |
75% | 7.000000 | 106.475000 |
max | 1033.000000 | 13990.930000 |
每位用戶平均購買了7個產品,但是中位數只有3,結合衆數來看,說明用戶的消費次數時符合右偏分佈的,數據中存在極大值(小部分用戶購買了大量的產品),把平均值往右邊拉,提高了平均值。
每位用戶的平均消費金額爲106元,中位數值爲43,和用戶的商品購買量一樣,符合右偏分佈,說明了存在極值的干擾,拉高了平均消費金額。
用戶消費金額和消費總數的散點圖
# group_user.sum().plot.scatter(x = 'order_amount',y='order_products')
plt.scatter(group_user.sum()['order_amount'],group_user.sum()['order_products'],c='blue')
plt.xlabel('消費金額')
plt.ylabel('消費總數')
plt.title('用戶消費金額和消費總數的散點圖')
從上圖的散點圖中可以明顯的看出,數據中存在少數極值,這些極值可能會影響到分析的結果,故需要把極值過濾掉。
group_user_sum = group_user.sum().query('order_amount<4000')
plt.scatter(group_user_sum['order_amount'],group_user_sum['order_products'],c='red')
plt.xlabel('消費金額')
plt.ylabel('消費總數')
plt.title('用戶消費金額和消費總數的散點圖')
從這張散點圖中可以看出,用戶的消費總數和用戶的消費總金額存在某種線性關係。
用戶消費金額和消費總數的分佈圖(二八法則)
# bins =(group_user.sum()['order_amount'].max()-group_user.sum()['order_amount'].min()) / len(group_user.sum()['order_amount'])
plt.figure(figsize=(16,9))
plt.subplot(2,2,1)
plt.hist(group_user.sum()['order_amount'],bins=20,color='green')
plt.xlabel('order_amount')
plt.ylabel('frequency')
plt.title('用戶消費金額的分佈圖')
plt.subplot(2,2,2)
plt.hist(group_user.sum()['order_products'],bins=20,color='red')
plt.xlabel('order_products')
plt.ylabel('frequency')
plt.title('用戶消費次數的分佈圖')
從上述直方圖可以看出,用戶消費金額,絕大部分是呈現集中趨勢,小部分極值干擾了判斷,故需要過濾掉極值
plt.figure(figsize=(16,9))
plt.subplot(2,2,1)
group_user_sum = group_user.sum().query('order_amount<4000')
plt.hist(group_user_sum['order_amount'],bins=40,color='green')
plt.xlabel('order_amount')
plt.ylabel('frequency')
plt.title('用戶消費金額的分佈圖')
plt.subplot(2,2,2)
group_user_sum = group_user.sum().query('order_products<100')
plt.hist(group_user_sum['order_amount'],bins=40,color='red')
plt.xlabel('order_products')
plt.ylabel('frequency')
plt.title('用戶消費總數的分佈圖')
從上面兩個圖中,可以明顯的看出,用戶消費金額的分佈和用戶消費總數的分佈都是符合右偏分佈的,和前面的描述性統計結果一致。
用戶累計消費金額的佔比
累積消費金額的佔比是使用每次累積得到的消費金額除以總的消費金額。
# user_cumsum = group_user.sum().sort_values('order_amount').apply(lamda x: x.cumsum()/x.sum())
user_cumsum =group_user.sum().sort_values('order_amount').cumsum()/group_user.sum().sort_values('order_amount').sum()
user_cumsum.reset_index().head()
user_id | order_products | order_amount | |
---|---|---|---|
0 | 10175 | 0.000006 | 0.0 |
1 | 4559 | 0.000012 | 0.0 |
2 | 1948 | 0.000018 | 0.0 |
3 | 925 | 0.000024 | 0.0 |
4 | 10798 | 0.000030 | 0.0 |
order_amount出現0的情況可能是因爲商品有不同的優惠方式,所以出現購買金額出現0的情況
plt.plot(range(0,23570),user_cumsum['order_amount'],color='blue')
plt.xlabel('消費人數')
plt.ylabel('用戶累計消費金額佔比')
plt.title('用戶累計消費金額的佔比圖')
按照用戶消費金額進行升序排列,並進行累計求和,可以發現:50%的用戶貢獻了15%的消費額度,而排名前5000的用戶就貢獻了60%的消費額(看頭部用戶)。
用戶消費行爲分析
用戶消費行爲分析可以從用戶第一次消費時間、用戶最後一次消費時間、新老客消費比、用戶分層、用戶購買週期、用戶生命週期進行分析。
用戶第一次消費時間(用戶首次購買產品的時間)
先對用戶進行分組,然後取用戶的最小購買時間。
group_user = CDNOW.groupby('user_id')
group_user.min()['order_dt'].value_counts().plot() # value_counts()根據日期進行計數
plt.xlabel('用戶第一次消費時間')
plt.ylabel('用戶數')
用戶的第一次購買時間,集中在前面三個月。其中,在2月11日-2月25日之間有兩次劇烈的波動。
由於沒有具體的數據,無法分析引起數據發生的變化,但是我們可以做出假設:
- 渠道發生了變化?
- 週期性變化?
- 等等
用戶最後一次消費時間
group_user.max().order_dt.value_counts().plot(color='blue')
plt.xlabel('用戶最後一次消費時間')
plt.ylabel('用戶數')
用戶最後一次購買的分佈比用戶第一次購買的分佈更廣,大部分用戶最後一次後買,集中在前三個月,說明了有很多新用戶購買了一次之後就不再進行購買了。
隨着時間的遞增,最後一次購買也在遞增,消費呈現流失上升的狀況。
新老客消費比
- 多少用戶僅消費了一次?
user_life = group_user['order_dt'].agg(['min','max'])
user_life.head()
min | max | |
---|---|---|
user_id | ||
1 | 1997-01-01 | 1997-01-01 |
2 | 1997-01-12 | 1997-01-12 |
3 | 1997-01-02 | 1998-05-28 |
4 | 1997-01-01 | 1997-12-12 |
5 | 1997-01-01 | 1998-01-03 |
先計算出用戶的首次消費時間和最後一次消費時間,如果首次消費時間=最後一次消費時間,則證明該用戶只消費了一次。比如說用戶ID爲1的用戶,他的首次消費時間爲1997年1月1日,而他的最後一次消費時間也是1997年1月1日,說明這位用戶僅消費了一次。
(user_life['max'] == user_life['min']).value_counts()
True 12054
False 11516
dtype: int64
結果分析:有超過一半的用戶只消費了一次
- 新客佔比?
group_month_user = CDNOW.groupby(['month','user_id'])
user_life_month = group_month_user['order_dt'].agg(['min','max'])
(user_life_month['min'] == user_life_month['max']).value_counts()
True 46727
False 8652
dtype: int64
從上面的分析結果可以看出,新客爲46727人,而老客人數爲8652。
用戶分層
(1)RFM模型
可以通過用戶進行細分,區別出低價值用戶、高價值用戶,針對不同的用戶羣體開展不同的個性化服務,將有限的資源合理地分配給不同價值的客戶,實現效益最大化。
- R:recency最近一次消費時間,理論上R值越小,價值越高;
- F:frequency最近一次消費頻率,消費頻率越高意味着這部分用戶對產品的滿意度越高,用戶粘性比較好,忠誠度也高;
- M:Montary最近一段時間消費的金額,符合二八原則
# 建立數據透視表
rfm = CDNOW.pivot_table(
index='user_id',
values= ['order_products','order_amount','order_dt'],
aggfunc={
'order_products': 'count',
'order_amount': 'sum',
'order_dt': 'max'
})
# 計算R是使用當前時間-最近一次時間,但是由於這個數據比較老,隔的時間比較久遠,故用最大時間來代替
rfm['R'] = (rfm['order_dt'].max() - rfm['order_dt']) / np.timedelta64(1,'D') # np.timedelta64(1,'D')是爲了消除單位
rfm.rename(columns={'order_amount':'M','order_products':'F'},inplace=True)
rfm.head()
M | order_dt | F | R | |
---|---|---|---|---|
user_id | ||||
1 | 11.77 | 1997-01-01 | 1 | 545.0 |
2 | 89.00 | 1997-01-12 | 2 | 534.0 |
3 | 156.46 | 1998-05-28 | 6 | 33.0 |
4 | 100.50 | 1997-12-12 | 4 | 200.0 |
5 | 385.61 | 1998-01-03 | 11 | 178.0 |
rfm1 = rfm[['R','F','M']].apply(lambda x: x-x.mean()) # 根據R、F、M用當前值減去每一列的均值
def rfm_func(x):
level = x.apply(lambda x:'1' if x>= 1 else '0') # 根據計算的RFM的數值,如果大於1,則標記爲1,如果小於1則標記爲0
label = level['R'] + level['F'] +level['M'] # 根據上述的標記,將標記合併則得到每位用戶的標籤,如用戶ID爲1的用戶的標籤爲100
# 根據各個用戶標籤,將用戶進行分羣
d = {
'111':'重要價值客戶',
'011':'重要保持客戶',
'101':'重要發展客戶',
'001':'重要挽留客戶',
'110':'一般價值客戶',
'010':'一般保持客戶',
'100':'一般發展客戶',
'000':'一般挽留客戶'
}
result = d[label] # 將用戶標籤對應到具體的用戶分羣中
return result
rfm['label'] = rfm1.apply(rfm_func,axis=1)
# 針對客戶類別進行分組
rfm.groupby('label').sum()
M | F | R | |
---|---|---|---|
label | |||
一般價值客戶 | 9061.26 | 515 | 51893.0 |
一般保持客戶 | 74247.25 | 4243 | 130603.0 |
一般發展客戶 | 437939.13 | 16860 | 6940318.0 |
一般挽留客戶 | 145929.29 | 6320 | 499073.0 |
重要價值客戶 | 66774.84 | 1345 | 106737.0 |
重要保持客戶 | 1496321.20 | 36839 | 420516.0 |
重要發展客戶 | 130311.05 | 1421 | 354279.0 |
重要挽留客戶 | 139731.61 | 2116 | 151995.0 |
rfm.loc[rfm['label']=='重要價值客戶','color'] = 'g' # 設置顏色
rfm.loc[~(rfm['label']=='重要價值客戶'),'color'] = 'r'
plt.scatter(rfm['R'],rfm['F'],c=rfm.color)
plt.xlabel('R')
plt.ylabel('F')
從RFM分層可知,大部分用戶爲重要保持客戶,但是這是由於極值的影響,所以RFM的劃分標準應該以業務爲準
- 儘量用小部分的用戶覆蓋大部分的額度
- 不用爲了數據好看劃分等級。