- 數據科學工程實踐:用戶行為分析與建模、A/B實驗、SQLFlow
- 謝梁 繆瑩瑩 高梓堯 王子玲等
- 2765字
- 2021-06-24 11:29:40
2.3 生存分析在二手車定價案例中的應用
前文詳細闡述了生存分析的理論,本節將基于2.1節提到的二手車定價案例,帶領大家通過Python代碼實操完成生存分析及結果解讀。在開始實操之前,我們先對二手車定價案例涉及的具體問題及數據進行界定,并對操作流程做簡要介紹。
1. 問題聚焦
我們以帕薩特二手車定價為例,以90天為一個銷售周期,如果二手車超過90天仍未銷售出去,則車輛的收益為0,成本為90天消耗的倉儲及維護總成本。在2.1節推導出的利潤公式中,所有車輛購入成本Cp固定不變,為常數,為了簡化問題,此處不考慮購入成本,我們只需要求得如下公式的最大值:

2. 數據獲取
假設我們從平臺上獲得的數據信息如表2-1所示。
表2-1 二手車定價案例可獲取的數據信息

3. 操作流程
下面圍繞上述信息展開案例實操,操作流程分為以下幾步。
- 了解軟件包及數據要求:介紹軟件包和數據格式,并對樣例數據進行必要的處理。
- 掌握生存分析基礎操作:繪制二手車銷售生存曲線,使用對數秩檢驗判斷不同生存曲線是否存在顯著差異。
- 掌握Cox比例風險模型操作及解讀:通過Cox比例風險模型對變量如何影響二手車銷售生存曲線展開分析及解讀,并對個體維度的生存曲線進行預測。
- Cox比例風險模型應用于最優價格求解:基于前三步的輸出結果,結合利潤公式求解不同定價下的利潤水平,尋找最優價格。
2.3.1 軟件包、數據格式和數據讀入
1. 軟件包
目前,Python中有支持生存分析的軟件包Liflines和scikit-survival可供使用,其中Liflines對分析友好,可以支持生存函數的繪制、對數秩檢驗及Cox模型擬合,本節將通過Python代碼實現基于Liflines包的生存分析。
2. 數據格式
生存分析的數據格式以研究對象為單位,每行包括生存時間(刪失前觀測生存時間),是否發生終點事件的標記及生存概率影響因子。在二手車定價案例中,原始數據以每一個待出售車輛為單位,每一行代表一個車輛的數據信息,信息可以分為以下兩類。
- 車輛出售情況:包括車輛是否發生終點事件(是否售出)和生存時間(在售時長)。
- 車輛公開信息(影響因子):包括車輛顏色、行駛里程、車輛照片、用戶訪問次數和車輛出售價格。
原始數據中各變量的類型及描述如表2-2所示。
表2-2 二手車定價案例數據各變量說明

3. 數據讀入及處理
數據讀入及處理如代碼清單2-1、代碼清單2-2所示。
代碼清單2-1 引入軟件包
##**軟件包引入**重要的軟件包的介紹在代碼備注中 import pandas as pd ##引入基礎分析軟件包pandas import numpy as np ##引入基礎分析軟件包numpy import matplotlib.pyplot as plt ##引入繪圖軟件包 from lifelines import KaplanMeierFitter ##引入生存分析包-KM生存曲線 from lifelines.statistics import logrank_test ##引入生存分析包-logrank檢驗 from lifelines import NelsonAalenFitter ##引入生存分析包-風險曲線 from lifelines import CoxPHFitter ##引入生存分析包-Cox模型
代碼清單2-2 讀入原始數據
##**數據讀入** data_survival = pd.read_csv('input_survival_v2.csv', sep=',', encoding = 'GBK', index_col=False) data_survival = data_survival[(data_survival['Publish_period']<=90)].reset_index(drop=True) data_survival = data_survival.drop(['Departure_Date','End_Date','Car_id'],axis=1) ##刪除不需要的字段 data_survival.head()
原始數據展示如圖2-7所示。

圖2-7 二手車定價案例原始數據格式
其中,Color為字符串,屬于離散變量,需要對該字段進行處理,如代碼清單2-3所示。
代碼清單2-3 對離散變量進行處理
##**對離散變量進行處理** df = pd.get_dummies(data_survival, drop_first=True, columns=['Color']) df.head()
處理后的數據如圖2-8所示。

圖2-8 二手車定價案例處理后的數據格式
2.3.2 繪制二手車銷售生存曲線及差異對比
1. 繪制整體生存曲線
數據處理完畢后,我們繪制KM曲線。首先基于全量數據繪制二手車銷售情況的生存曲線,如代碼清單2-4所示。
代碼清單2-4 繪制二手車銷售情況隨時間變化的整體生存曲線
##**繪制二手車銷售情況隨時間變化的整體生存曲線 kmf = KaplanMeierFitter() T = df["Publish_period"] ##T代表生存時長 E = df["is_sold"] ##E代表關注事件(終點事件) kmf.fit(T, event_observed=E,label='Survival Curve') #kmf.plot(show_censors=True,ci_show=False) ##繪圖,展示刪失數據 kmf.plot() plt.xlim() plt.ylabel("est. probability of survival $\hat{S}(t)$") plt.xlabel("day $t$")
繪制結果如圖2-9所示。

圖2-9 二手車銷售生存曲線
如圖2-9可知,50%生存概率對應天數在20天左右。
2. 繪制不同訪問次數車輛的生存曲線并對比
前面繪制的是整體生存曲線,但在分析中,更需要對不同生存曲線進行繪制及對比,接下來我們繪制兩條生存曲線,實現不同訪問次數的車輛銷售生存曲線對比,如代碼清單2-5所示。
代碼清單2-5 繪制不同訪問次數的二手車銷售情況隨時間變化的生存曲線
##繪制不同訪問次數的二手車銷售情況隨時間變化的生存曲線 avg_inquires=np.mean(df['N_Inquires']) df_less_inquires=df[(df['N_Inquires']<avg_inquires)] df_more_inquires=df[(df['N_Inquires']>avg_inquires)] ax = plt.subplot(111) kmf=KaplanMeierFitter() kmf.fit(df_less_inquires['Publish_period'], event_observed=df_less_inquires['is_sold'], label="less_inquires") ax = kmf.plot(ax=ax) kmf.fit(df_more_inquires['Publish_period'], event_observed=df_more_inquires['is_sold'], label="more_inquires") ax = kmf.plot(ax=ax) plt.title("survival curves of two different type");
繪制結果如圖2-10所示。

圖2-10 不同訪問次數二手車生存曲線
對兩條曲線進行觀察后發現,訪問次數更少的車輛生存曲線更靠左,說明在相同時間下,訪問次數少的車輛出售的概率更高,接下來我們通過對數秩檢驗對曲線差異顯著性進行檢驗,如代碼清單2-6所示。
代碼清單2-6 驗證不同訪問次數二手車銷售生存曲線是否有顯著差異
##**驗證不同訪問次數的二手車銷售生存曲線是否有顯著差異 T1 = df_less_inquires["Publish_period"] E1 = df_less_inquires["is_sold"] T2 = df_more_inquires["Publish_period"] E2 = df_more_inquires["is_sold"] results = logrank_test(T1, T2, event_observed_A=E1, event_observed_B=E2) results.print_summary()
檢驗結果如圖2-11所示。

圖2-11 不同訪問次數二手車生存曲線差異檢驗
圖2-11所示的結果顯示P值小于0.05,可以認為兩條曲線存在顯著差異,那么我們可以直接得出訪問越少,銷售速度越快的結論嗎?答案是否定的,我們在分析過程中需要警惕一點,那就是相關并不代表因果,出現這一現象有可能是因為我們只考慮訪問數量一個因素,而這些訪問少的車輛恰好價格更低或者行駛里程更短。因此,對于沒有控制其他因素下的單一因素對比需要謹慎對待。
2.3.3 二手車銷售生存概率影響因素分析及個體預測
1. 影響因素分析
接下來我們引入Cox比例風險回歸模型,對車輛顏色、行駛里程、車輛照片、用戶訪問次數、車輛出售價格這5個變量進行分析,找出對生存函數有顯著影響變量,如代碼清單2-7所示。
代碼清單2-7 引入Cox模型對特征進行解釋
##**引入Cox模型對特征進行解釋 cph = CoxPHFitter() cph.fit(df, duration_col='Publish_period', event_col='is_sold',show_progress=True,step_size=0.5) cph.print_summary() cph.plot() ##圖形對特征進行解釋
模型結果如圖2-12所示。

圖2-12 模型擬合結果(各特征系數及顯著性)
由模型結果可見,5個變量對結果均有顯著影響(P值均小于0.05),對于系數的解讀如下。
- 連續變量:行駛里程、照片數量、訪問次數及價格的系數為負,代表均為保護因素,對銷售速度有負向影響。控制其他因素后可以看到,訪問次數依然有顯著影響(一般情況下需要先對變量的相關性進行檢驗并剔除高度相關變量,此處在數據采樣階段已進行過相關性檢驗,因此分析過程中不再重復操作)。
- 分類變量:以黑色為基準,棕色的系數為負,表示保護因素,說明棕色車對銷售速度相對黑色車更差,而白色、銀色車的銷售速度優于黑色,其中白色最優。
模型中Concordance是解釋模型整體效果的參數,與一般模型中的AUC含義類似,結果顯示模型的Concordance為0.69,表現尚可。
2. 個體生存概率預測
在模型擬合之后,可以實現對個體生存概率的預測,我們采用Cox模型擬合結果對個體進行預測并隨機抽取兩個樣本(車輛ID為24及11166)展示生存概率預測曲線,如代碼清單2-8所示。
代碼清單2-8 對個體生存函數進行預測
##**對個體生存函數進行預測 X = df.drop(['Publish_period','is_sold'],axis=1) ##篩選特征集合,剔除銷售時間與事件結局數據 surv_hat=cph.predict_survival_function(X) ## 抽取任意兩個樣本,觀察預測結果 surv_hat[24].plot(label='24') surv_hat[11166].plot(label='11166') plt.legend()
兩個樣本的預測生存曲線繪制如圖2-13所示。

圖2-13 兩個樣本的預測生存曲線
以50%生存概率對應生存時間(中位生存時間)為預測生存時間,圖2-13所示二者的預測生存時間分別為62天和41天。
2.3.4 基于Cox風險比例模型的最優價格求解
最優價格分析
在操作之前,我們首先來看一下最優價格的分析流程,如圖2-14所示。

圖2-14 二手車最優價格分析流程
基于分析流程,我們在目前發布的最低價與最高價之間創建等差序列,選定10個價格作為預選價格,并在其中選出利潤最大時對應價格,如代碼清單2-9所示。
代碼清單2-9 基于預測生存函數尋找最優價格策略
##**基于預測生存函數尋找最優價格策略 ## 第1步:創建獲取預測在售天數函數 def predict_day(surv_hat): days = np.zeros(surv_hat.shape[1]) prob = np.zeros(surv_hat.shape[1]) j = surv_hat.shape[1] for i in range(1,surv_hat.shape[1]): prob[i-1] = surv_hat[surv_hat[i-1] >= 0.5][i-1].min() prob[j-1] = surv_hat[surv_hat[j-1] >= 0.5][j-1].min() days[i-1] = surv_hat[surv_hat[i-1] == prob[i-1]].index.values.min() days[j-1] = surv_hat[surv_hat[j-1] == prob[j-1]].index.values.min() ##以預測半衰期作為預測在售天數 return prob,days ## 第2步:創建90天是否賣出函數 def is_sold(data): y = np.zeros(data.shape[0]) for i in range(1,data.shape[0]): if data[i-1]>=0.6: y[i-1]=0 else: y[i-1]=1 return y ## 第3步:創建利潤函數 def profit(data, predict_days,sold_tag): d = list(predict_days) y = list(sold_tag) revenue = np.sum(data['Price']*y) cost = np.sum(1000*d) profit = revenue - cost return profit ## 第4步:計算不同價格下的利潤 min_price = df['Price'].values.min() max_price = df['Price'].values.max() sp_price = np.linspace(min_price, max_price, 10) ##選定十個預選價格 X = df.drop(['Publish_period','is_sold'],axis=1) profit_list = [] price_list = list(sp_price) for p in price_list: X['Price'] = p surv_hat = cph.predict_survival_function(X) prob_result,days_result = predict_day(surv_hat) sold_result = is_sold(prob_result) profit_result = profit(X,days_result,sold_result) profit_list.append(profit_result) profit_res = pd.DataFrame({'price': price_list, 'profit': profit_list})
得出結果如圖2-15所示。

圖2-15 價格與利潤對應關系圖
由此可見,在目前價格水平下,收益處于單調遞增階段,即售價越高利潤越高,目前最優價格處于價格上限21萬。
至此,整個分析實操就結束了,我們通過生存分析實現了最優價格的篩選,在固定分析框架后,我們將代碼整理打包,就能達到自動化定價的目的。需要說明的是,這一分析數據僅為樣例數據,不代表真實情況,因此結果與現實世界相比的合理性不是最重要的,掌握分析方法并能夠有效使用實操才是本節學習的關鍵。
- Voice Application Development for Android
- Learning Spring Boot
- 深入淺出MySQL:數據庫開發、優化與管理維護(第2版)
- 數據架構與商業智能
- Starling Game Development Essentials
- SQL優化最佳實踐:構建高效率Oracle數據庫的方法與技巧
- 深入淺出 Hyperscan:高性能正則表達式算法原理與設計
- Construct 2 Game Development by Example
- Python數據分析與數據化運營
- 數據庫原理與應用
- 視覺大數據智能分析算法實戰
- 貫通SQL Server 2008數據庫系統開發
- 數據分析師養成寶典
- Scratch 2.0 Game Development HOTSHOT
- 工業大數據分析實踐