為了創造更多利潤、實現數據驅動運營,某CD網站擬對18個月以來的近7萬條消費數據進行分析。具體的研究思路如下:
1、數據獲取與探索
1.1數據獲取
數據來源:CDNow網站的用戶購買明細(不包含隱私信息)。
數據格式:轉化成.csv格式。
時間跨度:18個月(199701-199806)。
字段:用戶ID,購買日期,購買數量,購買金額四個字段。
user_id:用戶ID
order_dt:購買日期
order_products:購買產品數
order_amount:購買金額
1.2數據導入
#導入相關庫
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
plt.style.use('ggplot') # 更改設計風格
import seaborn as sns
plt.rcParams['font.sans-serif']=['SimHei'] #用來顯示中文標簽
df0=pd.read_csv('D:CDNOW_master.csv',names=['user_id','order_dt','order_products','order_amount'],sep='\s+')
df0.head()
#讀取csv,此處用的是 names= 而非 columns=
2、數據預處理
2.1數據描述
df0.info()
#使用 .info() 查看數據類型及缺失情況。
2.2數據清洗
缺失值:本次數據,無缺失值,故無需對缺失值處理;
數據類型:購買日期的數據類型是int,為方便后面數據處理,在這先將其轉化成datetime格式;
新增['month']列,便于后續按月分析。
df0['order_dt']=pd.to_datetime(df0['order_dt'],format='%Y%m%d')
df0['month']=df0['order_dt'].astype('datetime64[M]')
df0.head()
重新查看,此時的時間列已轉換為正常格式。
df0.describe()
#使用 .describe() 查看數據分布及極值情況。
由上圖可知,
- order_products列:用戶平均每筆訂單購買2.4個商品,標準差在2.3,故有一定的波動性。中位數在2個商品,75分位數在3個商品,說明絕大部分訂單的購買量都不多。最大值為99,受極值影響。
df0['order_amount'].plot.hist(bins=200)
正偏態分布(也稱右偏態分布):頻數分布的高峰向左偏移,長尾向右延伸;【峰左移,右偏,正偏 偏度大于0】
負偏態分布(也稱左偏態分布):頻數分布的高峰向右偏移,長尾向左延伸。【峰右移,左偏,負偏 偏度小于0】
- order_amount列:用戶的消費金額受極值影響,呈正偏態分布(也稱右偏態分布)【中位數50%(25元) < 平均值mean(35元)】
3、數據分析
接下來我們用之前清洗好的字段進行數據分析。
3.1消費趨勢分析(按每個月分析)
3.1.1每月的消費總金額
grouped_month=df0.groupby('month') # 按月分組
grouped_month['order_amount'].sum()
grouped_month['order_amount'].sum().plot()
本次數據時間跨度為18個月(199701-199806),消費金額總體描述分析如下:由上圖可知,消費金額在前三個月 {1997-01-01:299060.17,1997-02-01:379590.03,1997-03-01:393155.27}增長至峰值,在3-4月出現斷崖式下滑,后續消費金額較為平穩,但有輕微下降趨勢。原因分析如下:
- 前3個月的增長,推測是由于CD網站的促銷活動;
- 而3-4月的異常下跌,推測是受促銷活動結束的影響較大。同時由于暫缺乏詳細數據,所以未對3-4月的異常收入下滑進行實際細剖與分析。收入異常下跌的其他原因整理如下:
比如:
(1)【競爭對手】方面,近期是否出現其他競爭與替代產品;
(2)【渠道】方面,可從用戶數量、渠道收入(環比、下滑)兩個維度來評判渠道質量,可從查看不同渠道的等數據指標。不同渠道如web端直接訪問、搜索引擎渠道、第三方合作渠道等,進一步定位是否是渠道推廣有問題;
(3)【用戶來源】方面,從新老客構成、流失客戶的特征、聚類與分類等角度;
(4)【CD產品體驗】方面,針對客戶的評價、網站評論及主動與用戶溝通收集反饋意見問題,對產品體驗進行分析,如是包裝不流行、價格沒優惠了等等進行優化。
(5)【用戶體驗流程】方面,通過埋點數據查看流失客戶在網站上的行為數據:啟動次數、停留時長、流程不暢(如流失客戶大部分在支付環節未完成支付,那么就需要回訪部分客戶、進行優化改進);
3.1.2每月的消費訂單數
grouped_month['user_id'].count().plot()
前三個月消費訂單數在10000筆左右,后續月份的平均則在2500筆。
3.1.3每月的產品購買量
grouped_month['order_products'].sum().plot()
前三個月產品購買數在20000以上,后續月份的產品購買量在6000~8000左右 。
3.1.4每月的消費人數
grouped_month['user_id'].apply(lambda x:len(x.unique())).plot()
#法二:grouped_month['user_id'].apply(lambda x:len(x.drop_duplicates())).plot()
前三個月每月的消費人數在8000-10000之間,后續月份平均消費人數在2000人不到
3.1.5直接利用數據透視表分析消費金額、消費次數、產品購買量
上述消費趨勢的分析可以通過數據透視表分析(不建議數據透視表進行去重操作)
pivot_df0=df0.pivot_table(index='month',values=['order_amount','user_id','order_products'],aggfunc={
'order_amount':'sum',
'user_id':'count',
'order_products':'sum'
})
pivot_df0.head()
#此處是values= 而非columns=
#數據透視表進行去重操作比較麻煩,不建議
pivot_df0.plot()
本章小結——
趨勢分析:總體來看,消費總金額、消費次數、產品購買量、消費人數的趨勢想似:均先上升、下跌、趨于平穩并下降。
可以看出網站的流失用戶在增加,采用開源(拉新)節流(留存)的運營方式,來增加銷售收入。
3.2用戶個體消費能力分析(按每個用戶分析)
上一部分是按月分析,主要看趨勢;本部分按用戶個體分析,來看消費能力。
3.2.1用戶消費金額、消費數量的描述統計
grouped_user=df0.groupby('user_id')
grouped_user.sum().describe()
- 【order_products數量】用戶平均購買了7張CD,但中位數只有3,說明小部分用戶購買了大量的CD
- 【order_amount金額】用戶平均消費106元,中位數為43,判斷同上,有極值干擾
消費、金融和錢相關的數據,基本上都符合二八法則,小部分的用戶占了消費的大頭
3.2.2用戶消費金額和消費數量的散點圖
grouped_user.sum().plot.scatter(x = "order_amount",y = "order_products")
##畫圖前,用query先篩選:.query("order_amount<4000")【df用query,series不能用】
grouped_user.sum().query("order_amount<4000").plot.scatter(x = "order_amount",y = "order_products")
3.2.3用戶消費金額的分布圖
grouped_user['order_amount'].sum().plot.hist(bins=200)
#bins是分組,分200個組
- 從直方圖可知,用戶消費金額,絕大部分呈現集中趨勢
- 部分異常值干擾了判斷。可以使用【切比雪夫定理】過濾異常值,計算95%(mean ± 5std)的數據的分布情況。則:
- order_products為:7+17X5=92;
- order_amount為106+5X241=1311。
grouped_user.sum().query('order_products<92').order_products.plot.hist(bins=20)
# .query作用在df上
grouped_user.sum().query('order_amount<1311').order_amount.plot.hist(bins=80)
3.2.4用戶累計消費金額占比(百分之多少的用戶占了百分之多少的消費額)
user_cumsum=grouped_user.sum().sort_values('order_amount', ascending=False).apply(lambda x:x.cumsum()/x.sum())
user_cumsum.reset_index().order_amount.plot() #reset_index()去掉索引 ,才能作圖
#grouped_user['order_amount'].sum().sort_values()
##df和series都可以調用.sort_values()方法
##list需要轉變成df才可以作圖
#方法二:(grouped_user['order_amount'].sum().sort_values().cumsum()/2500315.63).reset_index().order_amount.plot()
按用戶消費金額進行降序排列,由圖可知,共計約25000個用戶:
- 20%(約5000)的客戶貢獻了70% 的消費額度,近似符合二八定律。
- 50%的客戶貢獻了90% 的消費額度(即剩余50%的客戶僅貢獻10% 的消費額度)。
啟發,只要維護好這5000個用戶(占比20%)就可以把業績KPI完成70%,如果能把5000個用戶運營的更好就可以占比更高。
3.3用戶消費行為分析
通過以上基本數據描述分析可以清楚該網站整體的消費趨勢和用戶消費能力,現在進一步挖掘用戶消費行為數據,通過RFM模型、生命周期等方法對用戶進行分層,為后續運營管理提供依據。
3.3.1用戶第一次消費(首購)
首購可以進一步依渠道劃分,衡量不同渠道的差異性,從而量化渠道能力,為后期渠道優化提供依據。
grouped_user.min().order_dt.value_counts().plot()
用戶第一次購買分布,集中在前三個月(1997年1-3月);其中,在2月11日至2月25日有一次劇烈波動
3.3.2用戶最后一次消費
grouped_user.max().order_dt.value_counts().plot()
- 用戶最后一次購買分布(1997年1月-1998年6月)比第一次分布(1997年1-3月)廣;大部分最后一次購買,集中在前三個月,說明很多用戶購買了一次后就不再進行購買。
- 隨著時間的增長,用戶最后一次購買數略微增加。原因分析如下:
- 可能因網站體驗確實不好而流失,故有必要進行流失原因分析,并進一步建立預警系統。
- 也可能與CD產品屬性(消費頻次較低 / 購買周期較長)有關,此時相對于此網站的用戶流失,用戶拉新與首購才是最核心的指標。
3.3.3新老客消費比
3.3.3.1多少用戶僅消費了一次?
user_life = grouped_user.order_dt.agg(["min","max"])
##groupby后使用多個聚合函數用agg而非apply
user_life.head()
(user_life['min']==user_life['max']).value_counts()
有一半用戶,就消費了一次,可以通過定期發送郵件、信息等方式進行用戶喚回。
3.3.3.2每月新客戶占比?
grouped_um=df0.groupby(['month','user_id']).order_dt.agg(['min','max'])
#按month分組下的userid分組,求每月每個用戶的最早購買日期和最晚消費日期
grouped_um['new'] = (grouped_um['min'] == grouped_um['max'] )
# ['new']為新增列:若為新用戶,則為 True。
grouped_um.reset_index().groupby('month').new.value_counts()
grouped_um.reset_index().groupby('month').new.count()
#計算出每個月下訂單的客戶總數
grouped_um1=grouped_um.reset_index().groupby('month')
grouped_um2=grouped_um1['new'].apply(lambda x:x.value_counts()/x.count()).reset_index()
grouped_um2[grouped_um2['level_1']].plot(y = 'new', x ="month")
#利用布爾值篩選True 作圖
由圖可知,1997年1-4月新用戶數量由90%跌落至80%以下;之后幾個月的新用戶量保持在80~82%區間。
3.3.3.4.用戶分層 -RFM
RFM是一個經典的用戶分類模型,模型利用通用交易環節中最核心的三個維度——最近消費(Recency)、消費頻率(Frequency)、消費金額(Monetary)細分用戶群體,從而分析不同群體的用戶價值,最終達到精準營銷。
3.3.3.4.1基于規則(均值)劃分的RFM
RFM從3個維度、分2個等級(均值)得到8類用戶分層。
rfm=df0.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['R'] = -(rfm['order_dt']-rfm['order_dt'].max())/np.timedelta64(1,'D')
#/np.timedelta64(1,"D") 換成浮點數
rfm.rename(columns={'order_products':'F','order_amount':'M'},inplace=True)
#inplace 代表 是否覆蓋原始二維表
def rfm_func(x):
level=x.apply(lambda x:'1' if x>=0 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)
#axis = 1是逐行應用 #默認axis=0,即表示apply函數逐列應用。
rfm.head()
通過RFM模型,把用戶分為8個類別,分別給用戶打標簽、將客戶分為重要價值、重要保持、重要挽留、重要發展、一般價值、一般保持、一般保留、一般發展8類客戶。
rfm.groupby('label').count().sort_values('order_dt',ascending=False)
從RFM分層可知,本網站的大部分用戶為一般挽留客戶(可適當放棄這部分低價值客戶、也可進一步提高活躍度)、重要保持客戶(企業優質的客戶群,采用會員制運營)。具體運營策略依據參照如下:
【'111':'重要價值客戶'】該類用戶與企業交易頻繁、交易金額大,但長時間沒有二次消費,存在流失風險。——企業利潤的潛在來源,采用用戶喚回運營。
【'011':'重要保持客戶'】該類用戶與企業交易頻繁、交易金額大,且最近一次消費時間間隔短,實際貢獻價值很高。——企業優質的客戶群,采用會員制運營。
【'101':'重要挽留客戶'】該類用戶交易金額大,但交易并不頻繁、且長時間沒有二次消費,也存在流失風險。——有很高潛在價值的客戶,可針對性研究用戶特征,通過特定營銷手段吸引用戶提高購買頻率。
【'001':'重要發展客戶'】該類用戶最近消費間隔短、購買金額大,但交易不頻繁。——有很高潛在價值的客戶。對該類用戶展開專題研究,重點分析用戶群特征,以便在下次促銷時向合適的用戶傳遞合適的CD產品,轉化為留存客戶。
【'110':'一般價值客戶'】該類用戶購買頻率高,但長時間沒有交易,而且購買金額較低,企業已很難獲取更多利潤。
【'010':'一般保持客戶'】該類用戶最近交易時間間隔短、購買頻率高,屬于活躍用戶,但由于累計購買金額較少,消費能力有限,屬于企業的一般維持用戶。
【'100':'一般挽留客戶'】低價值客戶。
【'000':'一般發展客戶'】 無法立即給企業帶來較大利潤的用戶。
針對上述8類用戶分層情況,可初步制定三大運營策略:
- 提高活躍度:注重提升一般客戶、低價值客戶的活躍度,將其轉化為優質客戶。
- 提高留存率:與重要價值、重要挽留客戶互動,提高這部分用戶的留存率。
- 提高付費率:維系重要保持客戶、重要發展客戶的忠誠度,保持網站的良好收入。
具體手段包括搭建會員體系、會員分類管理與升級、積分兌換、發放折扣券等。
rfm.loc[rfm.label=='重要價值客戶','color'] = 'g' #green
rfm.loc[~(rfm.label=='重要價值客戶'),'color'] = 'r' #red
rfm.plot.scatter('F','R',c=rfm.color)
上圖為R(最近消費)與F(消費次數)的散點圖,可以看出重要價值用戶的R大F大,表示消費次數多且消費時間遠。可采用重點喚回方式進行運營。
3.3.3.4.2基于聚類(Kmeans)劃分的RFM
為了避免劃分用戶群體過多(RFM從3個維度、分2個等級得到8類用戶分層的數據立方),可能導致針對性的營銷成本負擔上升;下面將通過聚類方法,基于RFM模型劃分成4類用戶,更快實現后期用戶管理。
rfm_julei=rfm.loc[:,['R','F','M']]
rfm_julei.head()
#聚類之前先作圖觀察
plt.figure()
rfm_julei.plot(x='R',y='F',c='M',cmap='viridis_r', s=10,kind='scatter')
from sklearn.preprocessing import StandardScaler ##均值為0,方差為1的標準正態分布
st=StandardScaler()
st = st.fit(rfm_julei)
st_rfm_julei = st.transform(rfm_julei)
from sklearn.preprocessing import MinMaxScaler ## 0-1 X=mm_rfm_julei.astype('float32')
mm=MinMaxScaler()
mm = mm.fit(rfm_julei)
mm_rfm_julei = mm.transform(rfm_julei)
#輪廓系數(silhouette coefficient)【樣本i的簇內不相似度a i 和簇間不相似度b i ,定義樣本i的輪廓系數】:
#選擇使輪廓系數較大所對應的k值——輪廓系數范圍在[-1,1]之間。該值越大,越合理。
#si接近1,則說明樣本i聚類合理;
#si接近-1,則說明樣本i更應該分類到另外的簇;
#若si 近似為0,則說明樣本i在兩個簇的邊界上
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_samples, silhouette_score
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np
X=mm_rfm_julei.astype('float32')
for n_clusters in [2,3,4,5,6,7]:
n_clusters = n_clusters
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_size_inches(8, 4)
ax1.set_xlim([-0.1, 1])
ax1.set_ylim([0, X.shape[0] + (n_clusters + 1) * 10])
clusterer = KMeans(n_clusters=n_clusters, random_state=10).fit(X)
cluster_labels = clusterer.labels_
silhouette_avg = silhouette_score(X, cluster_labels)
print("For n_clusters =", n_clusters,"The average silhouette_score is :", silhouette_avg)
sample_silhouette_values = silhouette_samples(X, cluster_labels)
y_lower = 10
for i in range(n_clusters):
ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i]
ith_cluster_silhouette_values.sort()
size_cluster_i = ith_cluster_silhouette_values.shape[0]
y_upper = y_lower + size_cluster_i
color = cm.nipy_spectral(float(i)/n_clusters)
ax1.fill_betweenx(np.arange(y_lower, y_upper)
,ith_cluster_silhouette_values
,facecolor=color
,alpha=0.7)
ax1.text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
y_lower = y_upper + 10
ax1.set_title("The silhouette plot for the various clusters.")
ax1.set_xlabel("The silhouette coefficient values")
ax1.set_ylabel("Cluster label")
ax1.axvline(x=silhouette_avg, color="red", linestyle="--")
ax1.set_yticks([])
ax1.set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])
colors = cm.nipy_spectral(cluster_labels.astype(float) / n_clusters)
ax2.scatter(X[:, 0], X[:, 1],marker='o',s=8,c=colors)
centers = clusterer.cluster_centers_
# Draw white circles at cluster centers
ax2.scatter(centers[:, 0], centers[:, 1], marker='x',c="red", alpha=1, s=200)
ax2.set_title("The visualization of the clustered data.")
ax2.set_xlabel("Feature space for the 1st feature")
ax2.set_ylabel("Feature space for the 2nd feature")
plt.suptitle(("Silhouette analysis for KMeans clustering on sample data "
"with n_clusters = %d" % n_clusters),
fontsize=14, fontweight='bold')
plt.show()
顯然,歸一化預處理后,當n=2時,輪廓系數取最大值0.79,僅從模型聚類效果來講分2類合適;而標準正態化預處理后顯示,分4類的輪廓系數最大,達0.6964(但2-7類的輪廓系數整理差別波動不大)
#利用“手肘法”去判定分多少類比較合適
import pandas as pd
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
% matplotlib inline
SSE_st = [] # 存放 標準正太 結果的誤差平方和
SSE_mm = [] # 存放 歸一化后 結果的誤差平方和
for k in range(1,8):
kmodel = KMeans(n_clusters=k) # 構造聚類器
kmodel.fit(st_rfm_julei.astype('float32')) ## 標準正太:st_rfm_julei.astype('float32')
SSE_st.append(kmodel.inertia_) # .inertia_獲取聚類準則的總和
print('SSE_st',k,SSE_st)
kmodel = KMeans(n_clusters=k) # 構造聚類器
kmodel.fit(mm_rfm_julei.astype('float32')) ## 0-1歸一化:mm_rfm_julei.astype('float32')
SSE_mm.append(kmodel.inertia_) # .inertia_獲取聚類準則的總和
print('SSE_mm',k,SSE_mm)
fig = plt.figure(figsize=(10, 4))
ax1 = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2)
X = range(1,8)
ax1.set_xlabel('k')
ax1.set_ylabel('SSE_st')
ax1.plot(X,SSE_st,'o-')
plt.figure()
X = range(1,8)
ax2.set_xlabel('k')
ax2.set_ylabel('SSE_mm')
ax2.plot(X,SSE_mm,'o-')
plt.show()
#導入機器學習及三維作圖相關庫
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
import pylab
##作圖:RFM模型中的三個變量在空間中的分布特征
ax=plt.subplot(111,projection='3d')
ax.scatter(mm_rfm.iloc[:,0],mm_rfm.iloc[:,1],mm_rfm.iloc[:,2],c=label)
ax.set_xlabel('R')
ax.set_ylabel('F')
ax.set_zlabel('M')
plt.show()
crowd_1=mm_rfm.loc[mm_rfm['label']==0]
crowd_1_num=len(crowd_1.index) #第1類用戶人數
crowd_2=mm_rfm.loc[mm_rfm['label']==1]
crowd_2_num=len(crowd_2.index) #第2類用戶人數
print('第1類用戶特征({}人):\n{}\n第2類用戶特征({}人):\n{}\n'.format(crowd_1_num,crowd_1.mean(),crowd_2_num,crowd_2.mean()))
ax=plt.subplot(321)
plt.xlabel('crowd_1_R')
plt.ylabel('crowd_1_M')
plt.scatter(crowd_1.iloc[:,0],crowd_1.iloc[:,2],s=1)
ax=plt.subplot(322)
plt.xlabel('crowd_1_F')
plt.ylabel('crowd_1_M')
plt.scatter(crowd_1.iloc[:,1],crowd_1.iloc[:,2],s=1)
ax=plt.subplot(323)
plt.xlabel('crowd_2_R')
plt.ylabel('crowd_2_M')
plt.scatter(crowd_2.iloc[:,0],crowd_2.iloc[:,2],s=1)
ax=plt.subplot(324)
plt.xlabel('crowd_2_F')
plt.ylabel('crowd_2_M')
plt.scatter(crowd_2.iloc[:,1],crowd_2.iloc[:,2],s=1)
plt.show()
第1類用戶人數少,占比超30%(7422/23570),其消費時間間隔短,頻率較高,且金額較大,是高價值用戶,重要管理;
第2類用戶人數次多,占比70%,其消費時間間隔較長,頻率較低,且金額較少,是價值次低的用戶,做次要管理;
3.3.3.5.用戶分層:新、活躍、回流、消失
參考漏斗模型,針對每個用戶,按18個月內的每個月對用戶情況進行分類,即新用戶、活躍用戶、回流用戶、流失用戶。
通過下面的數據透視表即可得到每個用戶每個月的購買情況,從而進行轉化分析。
pivoted_counts=df0.pivot_table(index='user_id',
columns = 'month',
values='order_dt',
aggfunc='count').fillna(0)
pivoted_counts.head()
df_purchase=pivoted_counts.applymap(lambda x: 1 if x>0 else 0)#簡化模型,只需判斷是否存在 即 1與0
#此處用.applymap()
df_purchase.head ()
def active_status(data):
status=[]
for i in range(18): #12+6個月
#若本月沒有消費
if data[i]==0:
if len(status)>0:
if data[i-1]==0:
if status[i-1]=='unreg':
status.append('unreg')
else:
status.append('unactive')
else:
status.append('unactive')
else:
status.append('unreg')
#若本月消費
else:
if len(status)==0:
status.append('new')
else:
if data[i-1]==0:
if status[i-1]=='unreg':
status.append('new')
else:
status.append('return')
else:
status.append('active')
return pd.Series(status,index=df_purchase.columns)
#或者index = pivoted_counts.columns
purchase_stats=df_purchase.apply(lambda x:active_status(x),axis=1)
purchase_stats.head()
若本月無消費(即為0)
- 之前有記錄
1.上條記錄為無消費(即為0)
1.1若為未注冊,則為未注冊
1.2若為不活躍,則為不活躍
2.上條記錄為有消費(即為1),則為不活躍 - 之前無記錄,則為未注冊
若本月有消費(即為1)
- 之前無記錄,則為新客戶
- 之前有記錄
3.上條記錄為無消費(即為0)
3.1若為未注冊,則為新客戶
3.2若為不活躍,則為回流客戶
4.上條記錄為有消費(即為1),則為活躍客戶
purchase_stats_ct=purchase_stats.replace('unreg',np.NaN).apply(lambda x :x.value_counts())
purchase_stats_ct
#也可以使用pd.value_counts(x) #注意unreg區別
purchase_stats_ct.fillna(0).T.apply(lambda x : x/x.sum(),axis=1)
由上表可知,每月的用戶消費狀態變化
- 活躍用戶(持續消費用戶),對應的是消費運營的質量
- 回流用戶(之前不消費本月才消費),對應的是喚回運營
- 不活躍用戶,對應的是流失
- 新客戶,對應渠道與市場
purchase_stats_ct.fillna(0).T.apply(lambda x : x/x.sum(),axis=1).plot(figsize = (5,5))
purchase_stats_ct.fillna(0).T.plot.area() #.plot.area()面積圖
active:活躍用戶越來越少,說明運營的質量在降低,可能是用戶體驗不好、也可能是競爭加劇;
new:新用戶前三個月之后就顯著降低,說明市場和渠道部門需要加大拉新;
return:回流客戶多,說明喚回運營(促銷)起效;
unactive:不活躍用戶正在增加,說明存在用戶流失(也可能因為CD購買周期較長的原因);
3.3.3.6.用戶購買周期(按訂單)
#用戶消費周期描述
#用戶消費周期分布
order_diff = grouped_user.apply(lambda x: x.order_dt-x.order_dt.shift())
#體會df.apply(列.func)
order_diff.head(15)
#shift() 為 兩個日期錯行相減
##目的是求 時間差值
order_diff.describe() #對時間差值進行描述統計分析(會自動過濾空值)
CD產品的平均購買周期是2個月(均值為68天)
(order_diff/np.timedelta64(1,'D')).plot.hist(bins=20)
#訂單周期呈指數分布
3.3.3.7.用戶生命周期(按第一次&最后一次消費)
#用戶生命周期描述
#用戶生命周期分布
user_life=grouped_user.order_dt.agg(['min','max'])
user_life.head()
(user_life['max']-user_life['min']).describe()
用戶生命周期的均值為134天,但一半的用戶生命周期僅0天【顯然受只購買一次的用戶影響比較厲害,下面將分排除與否分別作圖】
#不排除:
((user_life['max']-user_life['min'])/np.timedelta64(1,'D')).plot.hist(bins=40)
#((user_life.apply(lambda x :x.max()-x.min(),axis=1))/np.timedelta64(1,'D')).plot.hist(bins=20)
user_life['差值']=(user_life['max']-user_life['min'])/np.timedelta64(1,'D')
user_life.head()
#排除(一半生命周期僅0天的用戶):
user_life.query('差值>0')['差值'].plot.hist(bins=40)
## .query('差值>0')
#排除(一半生命周期僅0天的用戶):
##法二
u_l=(user_life['max']-user_life['min']).reset_index()[0]/ np.timedelta64(1,'D')
u_l[u_l>0].hist(bins=40)
#不用 .reset_index()[0] 也可以作圖
3.4復購率和回購率分析
通過對復購率、回購率進行分析,可找到較高的月份進行細化分析,觀察較高的產品(產品特征),以及較高的用戶(用戶特征),可根據這些細化的數據做產品的爆款營銷、推廣、個性化營銷等。
#復購率:自然月內,購買多次的用戶占比
#回購率:曾經購買過的用戶在某一時期內的的再次購買的占比
pivoted_counts.head()
3.4.1復購率
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))
#sum()對于1的求和,count()過濾掉np.Nan
##使用元組的方式賦值
復購率穩定在20%左右,前三個月因為有大量新用戶涌入,而這批用戶只購買了一次,所以導致復購率降低
3.4.2回購率
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)
##需注意 語句中的是 第一位與第二位相比 輸出結構放第一位,
#循環17次,17/18的判斷結束后,輸出的status僅17個,需補上最后一個
return pd.Series(status,index=df_purchase.columns)
#1代表 本月購買及下個月購買 ,sum()可計算 ,
#0代表 本月購買,下個月未購買 count()可計算總人數,
#回購率: 本月及下月購買/本月購買人群
purchase_b=df_purchase.apply(lambda x:purchase_back(x),axis=1)
purchase_b.head()
##此處的透視表為何用的方法不是.applymap 而是 .apply()
(purchase_b.sum()/purchase_b.count()).plot(figsize=(20,4))##使用元組的方式賦值
從圖中可以看出,用戶的回購率約30%左右(高于復購率),說明客戶忠誠度表現較好。
總結
本研究運用多種分析方法,如趨勢分析、用戶行為分析、生命周期分析、RFM模型分析、聚類分析、漏斗轉化分析等,較全面刻畫了CD網的運營現狀。主要結論有:
(1)總體來看,每月的消費總金額、消費次數、產品購買量、消費人數趨勢相似:均先上升、再下跌、最后趨于平穩并下降。初步推斷3-4月的下跌是由于促銷活動結束,導致異常。
(2)20%(約5000)的客戶貢獻了70% 的消費額度,近似符合二八定律。故只要維護好這5000個用戶(占比20%)就可以把業績KPI完成70%,如果能把5000個用戶運營的更好就可以占比更高。
(3)每月新客占比狀態良好:1997年1-4月新用戶數量由90%跌落至80%以下;之后幾個月的新用戶量保持在80~82%區間。但有一半用戶,就消費了一次,可以通過定期發送郵件、信息等方式進行用戶喚回。
(4)兩種RFM劃分模型(均值、聚類)均顯示:低價值客戶數量占比較大,可做次要管理(進一步提升活躍度);而對高價值客戶,尤其是聚類分析得出的171個客戶,更要突出精準營銷與會員管理手段,進一步提升留存率和付費率。
(5)用戶轉化分析:
active:活躍用戶越來越少,說明運營的質量在降低,可能是用戶體驗不好、也可能是競爭加劇;
new:新用戶前三個月之后就顯著降低,說明市場和渠道部門需要加大拉新;
return:回流客戶多,說明喚回運營(促銷)起效;
unactive:不活躍用戶正在增加,說明存在用戶流失(也可能因為CD購買周期較長的原因);
(6)生命周期分析:用戶生命周期的均值為134天,但一半的用戶生命周期僅0天;而CD產品的平均購買周期是2個月(均值為68天)。
(7)用戶的復購率穩定在20%;回購率約30%左右(高于復購率),說明客戶忠誠度表現較好。
網站可根據以上結論,為網站后續的運營、營銷等方面制定策略,并根據反饋數據進一步復盤分析與優化,最終達到數據驅動的精細化運營目的。
k-means與RFM模型結合進行用戶群體劃分
不錯的參考:Mysql與Pyhton實現復購率和回購率
【運營】任意兩個時間段的復購率?Power BI一招幫你搞定
技巧總結
1、得到新字段%Y-%m的方法:
df0['order_dt'].astype('datetime64[M]')
2、去重:
series.apply(lambda x:len(x.unique()))
3、查詢query:
df.query("order_amount<4000").plot(kind='scatter',x = "order_amount",y = "order_products")
4、作圖:
df.plot()可以直接作圖,而非僅series.plot()
5、排序.sort_values():
df和series都可以調用.sort_values()方法
6、分組.agg():
grouped.agg([min,max])
df.groupby('A').agg({'B': ['min', 'max'], 'C': 'sum'})
7、每月新客戶占比(兩次分組統計+分組后可用value_counts):
grouped_um=df0.groupby(['month','user_id']).order_dt.agg(['min','max'])
grouped_um['new'] = (grouped_um['min'] == grouped_um['max'] )
grouped_um.reset_index().groupby('month').new.value_counts()
或者
grouped_um1=grouped_um.reset_index().groupby('month')
grouped_um2=grouped_um1['new'].apply(lambda x:x.value_counts()/x.count()).reset_index()
grouped_um2[grouped_um2['level_1']].plot(y = 'new', x ="month")
8、