數據源於CDNOW網站的用戶購買記錄,通過以下字段利用python對CD銷售數據分析
分析需要基於業務,首先需要對數據進行了解
數據字段包括
- user_id 用戶ID
- order_dt :購買日期
- order_products : 購買的產品數量
- order_amount : 購買金額
數據:點我獲取數據 提取碼 : h2s4
分析思路:
0、數據預處理
1、數據按月分析
2、用戶個體消費分析
3、用戶消費分析
4、復購率、回購率分析
0、數據預處理
導入常用的庫
import pandas as pd import numpy as np import matplotlib.pyplot as plt %matplotlib inline import seaborn as sns plt.rcParams['font.sans-serif']=['SimHei'] #用來正常顯示中文標簽
plt.rcParams['axes.unicode_minus']=False #用來正常顯示負號
導入數據,設置相應的字段
columns = ['user_id','order_dt','order_products','order_amount'] data = pd.read_table('D://CDNOW_master.txt',names=columns,sep='\s+') # 查看數據
data.head() # 數據字段
# user_id 用戶ID # order_dt: 購買日期 # order_products: 購買產品數 # order_amount: 購買金額
數據的大概格式
# 大概格式了解
data.info()
數據中沒有缺失值,order_dt列數據類型是int,需要進一步轉換成datetime類型
data['order_dt'] = pd.to_datetime(data.order_dt,format='%Y%m%d') # 新增一列月份 (對月進行分析) values(數組形式) 因為很多是進行月份的一個處理
data['month'] = data.order_dt.values.astype('datetime64[M]')
修改完數據格式后查看order_dt列的格式
# describe是描述統計
data.describe()
- 觀察數據,用戶平均訂單數量為2.4個商品,標准差為2.3,略有波動,中位數書為2個商品,說明絕大部分訂單的購買量不多,最大值為99,有一定的極值干擾
- 用戶消費額均值為35元左右,中位數25元,最大金額達到了1286元,有一定極值干擾
1、按月進數據分析
每月的消費總金額
每月的消費次數
每月的購買產品總數
每月的消費用戶數
# 按月分組
grouped_month = data.groupby('month') fig = plt.figure(figsize = (8,12)) ax1 = fig.add_subplot(4,1,1) ax2 = fig.add_subplot(4,1,2) ax3 = fig.add_subplot(4,1,3) ax4 = fig.add_subplot(4,1,4) # 按月金額數
ax1.plot(grouped_month.order_amount.sum(),c='blue') ax1.set_title('每月消費總金額變化趨勢') # 按月的訂單數
ax2.plot(grouped_month.user_id.count(),c='blue') ax2.set_title('每月訂單數變化趨勢') # 按月產品數
ax3.plot(grouped_month.order_products.sum(),c='blue') ax3.set_title('每月消費產品購買量') # 每月用戶人數 這里需要去重,有些用戶重復購買
ax4.plot(grouped_month.user_id.apply(lambda x:len(x.drop_duplicates())),c='blue') # drop_duplicates去重功能
ax4.set_title('每月用戶數變化趨勢')
按月進行統計,下面通過折線圖分別查看數據變化趨勢
圖一:消費總金額在前三個月達最高峰,或許消費金額較為平穩,有輕微下降趨勢
圖二:前三個月訂單數在10000筆左右,后續月份的訂單數在2500左右
圖三:產品購買數量 呈現早期購買量多 后期下降趨勢
圖四:每月消費人數小小於每月的消費次數(訂單數),但是區別不大,前三個月每月的消費人數在8000-10000之間,后續月份平均2000左右,一樣是前期消費人數多,后期平穩下降趨勢
出現這種狀況,假設問題是出現在用戶身上,早期時間段的用戶有異常值,或者由於各類促銷營銷,由於只有消費數據,無法進一步進行判斷
數據透視查看,按月根據用戶購買金額,訂單數,用戶人數
data.pivot_table(index='month', values = ['order_products','order_amount','user_id'], aggfunc = {'order_products':'sum', 'order_amount':'sum', 'user_id':'count'})
2、用戶個體分析
- 用戶消費金額,消費次數描述統計
- 用戶消費金額和消費次數的散點圖
- 用戶消費金額的分布圖
- 用戶消費次數的分布圖
- 用戶累積消費金額占比(百分之多少的用戶占了百分之多少的消費額)
根據用戶維度進行分析
#01-用戶消費金額和消費次數的描述統計
grouped_user = data.groupby('user_id') grouped_user.sum().describe()
- 用戶平均購買量7張CD,標准差17,波動比較大,但是中位值只有3,說明小部分用戶購買了大量的CD
- 用戶平均消費為106元,中位值為43,也有極值干擾
- 消費的話一般都會符合二八法則
#02 - 用戶消費金額和消費次數散點圖 # 設置排除一些極值的干擾進行觀察 # query 用於數據框選列
grouped_user.sum().query('order_amount < 4000').plot.scatter(x='order_amount',y='order_products')
- 繪制散點圖,由於產品比較單一,購買金額和產品數呈線性關系
03-用戶消費金額的分布圖(二八法則) grouped_user.sum().order_amount.plot.hist(bins=20) # bins=20,就是分成20塊,最高金額是14000,每個項就是700
- 由直方圖可知,用戶消費金額,大部分消費金額能力確實不高,絕大部分呈幾種在很低的消費檔次,高消費用戶在圖中基本看不到,這也符合消費行為的行業規律
- 小部分異常值干擾判斷,可以過濾操作排除異常
#04-用戶消費次數分布圖
grouped_user.sum().query('order_products<92').order_products.hist(bins = 40)
05 -用戶累計消費金額占比 user_cumsum = grouped_user.sum().sort_values('order_amount').apply(lambda x: x.cumsum() / x.sum()) user_cumsum.reset_index().order_amount.plot()
- 通過觀察,50%的用戶僅僅貢獻了15%的消費額度,而排名前五千用戶就貢獻了%60的消費額
print(user_cumsum.head(8)) print(user_cumsum.tail(8))
- 上面是一些累積占比的數據,數據中存在金額為0,可能是一些活動贈送等原因
3.用戶消費行為分析
- 用戶第一次消費(首購)
- 用戶最后一次消費
- 新老客戶消費比
- 多少用戶僅僅消費了一次
- 每月新客占比
- 用戶分層
- RFM
- 新、老、活躍、回流、流失/不活躍
- 用戶購買周期(按訂單)
- 用戶消費周期描述
- 用戶消費周期分布
- 用戶生命周期(按第一次 & 最后一次消費)
- 用戶生命周期描述
- 用戶生命周期分布
#01-第一次購買時間分布 # min() 時間最小,第一次購買
grouped_user.min().order_dt.value_counts().plot() # 2月發生較大下跌 渠道發生變化,或者其他 可以做一些假設
- 用戶第一次購買分布,集中在前三個月,其中2月11日至2月25日有一次劇烈波動
# 02-最后一次購買時間分布
grouped_user.order_dt.max().value_counts().plot() # value_counts計算有哪些不同值,並計算每個值有多少個重復值
- 用戶最后一次購買的分布比第一次購買分布廣
- 大部分最后一次購買在前三個月,說明很多用戶購買一次后就不再進行購買
- 隨着時間遞增,最后一次購買數在遞增,消費呈線性流失上升的情況
# 03-用戶第一次購買與最后一次購買 新老客的消費比
user_life =grouped_user.order_dt.agg(['min','max']) user_life.head()
(user_life['min'] == user_life['max']).value_counts()
True 第一次購買時間和最后一次購買時間一樣,有一半用戶只消費了一次
rate = (user_life['min'] == user_life['max']).value_counts() # 定義餅圖的標簽,標簽是列表,每個標簽多大會自動去算
labels = ['只消費一次用戶','多次消費用戶'] plt.axis('equal') # 設置x,y軸刻度一致,這樣才會是圓的
plt.pie(rate,explode=(0,0.15),labels=labels,autopct='%2.1f%%',startangle=90,colors=['r','orange'],radius=1.5) # autopct,圓里面的文本格式,%2.1f%%代表整數有兩位,小數有一位 #startangle,起始角度,0,表示從0開始逆時針轉,為第一塊。一般選擇從90度開始比較好看
# 04-用戶分層 # RFM 消費額 次數 最近一次消費 進行透視
rfm = data.pivot_table(index = 'user_id', values = ['order_products','order_amount','order_dt'], aggfunc = {'order_dt':'max', 'order_amount':'sum', 'order_products':'sum'}) rfm.head()
# 表示離最近一次消費的時間間隔
-(rfm.order_dt - rfm.order_dt.max()).head() # 數值越大,離現在的時間越長
# /np.timedelta64(1,'D') 除以1天的事件,消除單位 進行重命名
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()
rfm[['R','F','M']].apply(lambda x:x-x.mean()).head()
數據解讀: 負數是小於的 正式大於的
R:離最近一次購買的天數 F:產品數(消費次數) M:消費金額 與均值的對比
def rfm_func(x): level = x.apply(lambda x:'1' if x>= 0 else '0') # 字符串拼接 # 111,R>0,是距離平均消費時間要久,R越大 說明沒有消費時間越久 ,F >0 M>0,消費次數和金額也是較高的,重要價值客戶,依次類推
label = level.R + level.F + level.M d = { '111':'重要價值客戶', '011':'重要保持客戶', '101':'重要挽留客戶', '001':'重要發展客戶', '110':'一般價值客戶', '010':'一般保持客戶', '100':'一般挽留客戶', '000':'一般發展客戶' } result = d[label] return result # x - x.mean() (具體真實情況可以修改,不一定需要用均值) 切比雪夫也可以 > 200 極值人工處理掉
rfm['label'] = rfm[['R','F','M']].apply(lambda x:x-x.mean()).apply(rfm_func,axis=1) rfm.head()
數據解讀: 1 時間比較長,只消費了一次,消費金額低 一般挽留客戶
rfm.groupby('label').sum()
重要保持客戶貢獻了159萬左右 等等
rfm.groupby('label').count()
# 各類類型用戶占比
use_c = rfm.groupby('label').count() plt.axis('equal') labels = ['一般價值客戶','一般保持客戶','一般發展客戶','一般挽留客戶','重要價值客戶','重要保持客戶','重要發展客戶','重要挽留客戶'] plt.pie(use_c['M'], autopct='%3.1f%%', labels = labels, pctdistance=0.9, # 百分比占比距離圓心的位置0.9半徑
labeldistance = 1.2, # 標簽離圓心的位置1.2半徑
radius=3, startangle = 15)
# 對應標簽,使用不同顏色表示 # 綠色為重要價值客戶,紅色為非重要價值用戶
rfm.loc[rfm.label == '重要價值客戶','color'] = 'g' rfm.loc[~(rfm.label == '重要價值客戶'),'color'] = 'r' rfm.plot.scatter('F','R',c=rfm.color)
rfm.head() # rfm 象限法
rfm.groupby('label').sum()
從RFM分層可知,大部分用戶為一般挽留客戶,但是這是由於極值的影響,所以RFM的划分應該以業務為主
- 盡量用小部分的用戶覆蓋大部分的額度
- 不要為了數據好看划分等級
- 極值會拉均值
- 根據數據可以和業務相結合,如何提升一些重要的指標
# 06-用戶生命周期
pivoted_counts = data.pivot_table(index = 'user_id', columns = 'month', values = 'order_dt', aggfunc = 'count').fillna(0) # pivoted_counts.head()
pivoted_counts.head() # 按月份進行對比,1月份哪些是購買的,再去對比二月份哪些是購買的
消費過的為1 ,沒消費過的為0 data_purchase = pivoted_counts.applymap(lambda x: 1 if x > 0 else 0) data_purchase.tail() # 透視會補上 第一次從3月分開始購買 前面補成0 , 需要進行判,第一次消費作為生命周期的起始 # se.columns.values
def active_status(data): status = [] for i in range(18): #若本月沒有消費
if data[i] == 0: if len(status) > 0: if status[i-1] == 'unreg': status.append('unreg') else: status.append('unactive') else: status.append('unreg') #若本月消費
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') # 這里需要對返回的值進行轉換,將列表轉為Series
return pd.Series(status, index = pivoted_counts.columns)
- 若本月沒有消費
- 若之前是未注冊,則依舊未注冊
- 若之前有消費,則為流失/不活躍
- 其他情況,為未注冊
- 若本月有消費
- 若是第一次消費,則為新用戶
- 如果之前有過消費,則上個月為不活躍,則為回流
- 如果上個月為未注冊,則為新用戶
- 除此之外,為活躍
purchase_stats = data_purchase.apply(active_status,axis=1,result_type ='expand') # df.rename(columns={ df.columns[2]: "new name" }, inplace=True) # purchase_stats.rename(columns={purchase_stats.columns:data_purchase.columns})
purchase_stats.head()
purchase_status_ct = purchase_stats.replace('unreg',np.NaN).apply(lambda x : pd.value_counts(x)) purchase_status_ct # 未注冊不希望參與處理 設置我空值
數據解讀: 一月份有7846個用戶 然后2月份分叉為不活躍用戶 和 活躍用戶 有8476個新用戶 等等
新用戶轉換為是否活躍等等 流失用戶在增加
# 各個狀態的占比
purchase_status_ct.fillna(0).T.apply(lambda x:x/x.sum(),axis=1) purchase_status_ct.fillna(0).T.plot.area()
由上表可知,每月的用戶消費轉變
活躍用戶,持續消費的用戶,對應的是消費質量
回流用戶,之前是不消費的本月才消費,采取喚回運營
不活躍用戶,對應的是流失
用戶購買周期 data.order_dt.head(10)
# 把所有的數據進行一個錯位
data.order_dt.shift().head(10)
# 一個用戶可能多個訂單 每個訂單的間隔
order_diff = grouped_user.apply(lambda x:x.order_dt - x.order_dt.shift()) order_diff.head(10)
數據解讀:ID為2 0天當天購買兩次或以上,間隔為0
ID為3的用戶 3-4間隔了87天
NaT表示一個訂單
order_diff.describe()
每個用戶的平均訂單是68天 中位值31天 最大值533天
# 去除單位 間隔天數分布圖
(order_diff / np.timedelta64(1,'D')).hist(bins=20)
(user_life['max'] - user_life['min']).describe() # 整體的生命周期
((user_life['max'] - user_life['min']) / np.timedelta64(1,'D')).hist(bins = 40)
- 用戶的生命周期受只購買一次的用戶影響比較厲害 (可以進行排除 當特殊情況,想提取出購買一次以上的用戶)
- 用戶均消費134天,中位數僅為0
-
u_l = ((user_life['max'] - user_life['min']).reset_index()[0] / np.timedelta64(1,'D')) u_l[u_l > 0].hist(bins=40)
- 左邊比較高,還是有些用戶的生命周期是比較短的,例如10、20天等
- 有不少用戶生命周期也是比較穩定
4.復購率和回購率分析
- 復購率
- 自然月內,購買多次的用戶占比
- 回購率
- 曾今購買過的用戶在某一時期內的再次購買占比
pivoted_counts.head()
# 購買大於1次的 賦值為1 ,然后小於等於1 的 如果是購買次數是0,則賦值為空,否則 就是購買一次,賦值為0 purchase_r = pivoted_counts.applymap(lambda x: 1 if x > 1 else np.NaN if x == 0 else 0) purchase_r.head()
# sum() 0 不計數,count() 0計數
(purchase_r.sum() / purchase_r.count()).plot(figsize = (10,4))
- 復購率穩定在20% 左右,前三個月因為有大量新用戶涌入,而這些用戶只購買了一次,所以導致復購率較低
# 回購率 只需要0 1 代表
data_purchase.head()
def purchase_back(data): status = [] for i in range(17): if data[i] == 1: # 本月進行過消費
if data[i+1] == 1: # 下一月是否進行消費
status.append(1) #消費為1 回購了
if data[i+1] == 0: status.append(0) # 未消費則為0 沒有回購
else: status.append(np.NaN) # 之前沒消費則不計
status.append(np.NaN) # 最后一個月沒有判斷需要補上
return pd.Series(status,data_purchase.columns) purchase_b = data_purchase.apply(purchase_back, axis =1) purchase_b.head(5)
- 0 表示上個月購買了,下個月沒有進行消費,則是沒有回購 ,
- 1代表當月消費過次月依舊消費,表示回購了
- NAN表示當月沒有消費(不進行計算)
# sum() 0 1進行求和,表示次月消費過的
(purchase_b.sum() / purchase_b.count()).plot(figsize = (10, 4))
- 回購率在30%左右波動,比復購率要高,綜合分析得出,新客整體質量低於老客,老客忠誠度(回購率)表現較好,消費頻次稍次,這個是是CDNow網站的用戶消費特征