Python數據分析與數據化運營:會員數據化運營5-案例:基於RFM的用戶價值度分析

Python數據分析與數據化運營:會員數據化運營5-案例:基於RFM的用戶價值度分析

來自專欄 Python程序員9 人贊了文章

作者介紹:宋天龍(TonySong),資深大數據技術專家,歷任軟通動力集團大數據研究院數據總監、Webtrekk(德國最大的網站數據分析服務提供商)中國區技術和諮詢負責人、國美在線大數據中心經理。


本文來自《Python數據分析與數據化運營》配套書籍第5章節內容,機械工業出版社華章授權發布,未經允許,禁止轉載!

此書包含 50個數據工作流知識點,14個數據分析和挖掘主題,8個綜合性運營案例。涵蓋了會員、商品、流量、內容4大數據化運營主題,360°把脈運營問題並貼合數據場景落地。

書籍購買鏈接:item.jd.com/12254905.ht

課程學習鏈接:網站數據分析場景和方法——效果預測、結論定義、數據探究和業務執行edu.hellobi.com/course/


往期回顧:Python數據分析與數據化運營:會員數據化運營1-概述與關鍵指標

Python數據分析與數據化運營:會員數據化運營2-應用場景與分析模型

Python數據分析與數據化運營:會員數據化運營3-分析小技巧

Python數據分析與數據化運營:會員數據化運營4-「大實話」


5.7.1 案例背景

用戶價值細分是了解用戶價值度的重要途徑,而銷售型公司中對於訂單交易尤為關注,因此基於訂單交易的價值度模型將更適合運營需求。

對於用戶價值度模型而言,由於用戶的狀態是動態變化的,因此一般需要定期更新,業務方的主要需求是至少每周更新一次。由於要兼顧歷史狀態變化,因此在每次更新時都需要保存歷史數據,不同時間點下的數據將通過日期區分。

每次模型結果的數據,一部分是要給運營直接做分析,一部分是要「回吐」到資料庫中,作為其他數據建模的基本數據維度,因此數據的輸出需要有本地文件和寫資料庫兩種方式。

本節案例的輸入源數據sales.csv和源代碼chapter5_code1.py位於「附件-chapter5」中,默認工作目錄為「附件-chapter5」(如果不是,請cd切換到該目錄下,否則會報「IOError: File sales.csv does not exist」)。程序輸出RFM得分數據寫入本地文件sales_rfm_score.csv和MySQL資料庫的目標數據表(sales_rfm_score)。

5.7.2 案例主要應用技術

本案例沒有使用現有成熟模型包,而是通過Python代碼手動實現RFM模型,主要用到的庫包括time、numpy、pandas和mysql.connector。有關RFM模型更多內容,請查看「5.4.3 會員價值度模型」。

5.7.3 案例數據

案例數據是某企業2016年的部分抽樣數據,數據來源於銷售系統,主要是用戶訂單記錄。以下是數據概況:

  • 特徵變數數:4
  • 數據記錄數:86135
  • 是否有NA值:有
  • 是否有異常值:有

以下是本數據集的4個特徵變數,包括:

  • USERID:用戶ID,每個用戶的ID唯一,由純數字組成。
  • ORDERDATE:訂單日期,格式為YYYY-MM-DD,例如2016-01-01。
  • ORDERID:訂單ID,每個訂單的ID唯一,由純數字組成。
  • AMOUNTINFO:訂單金額,浮點型數據。

5.7.4 案例過程

步驟1 導入用到的庫

import time # 導入時間庫import numpy as np # 導入numpy庫import pandas as pd # 導入pandas庫import mysql.connector # 導入mysql連接庫

該案例用到了四個庫:time、numpy、pandas、mysql.connector。

  • time:用來記錄插入資料庫時的當前時間
  • numpy:用來做基本數據處理等
  • pandas:有關日期轉換、數據格式化處理、主要RFM計算過程等
  • mysql.connector:資料庫連接工具,讀寫MySQL資料庫

步驟2 讀取原始數據

dtypes = {ORDERDATE: object, ORDERID: object, AMOUNTINFO: np.float32} # 設置每列數據類型raw_data = pd.read_csv(sales.csv, dtype=dtypes, index_col=USERID) # 讀取數據文件

dtypes定義的字典用於使用pd.read_csv讀取數據時對數據框數據類型的自定義,而非系統默認類型。本案例中將ORDERDATE(訂單時間)和ORDERID(訂單ID)都設置為object(字元串對象),AMOUNTINFO(訂單金額)設置為浮點型。

使用pd.read_csv讀取數據文件過程中,默認的csv以逗號作為分隔符,因此無需指定分隔符;設置參數dtype為上述自定義的類型字典,同時指定列USERID(用戶ID)為索引列,代替默認的數值索引。

步驟3數據審查和校驗,主要包括數據概覽、缺失值審查等。

# 數據概覽print (Data Overview:)print (raw_data.head(4)) # 列印原始數據前4條print (- * 30)print (Data DESC:)print (raw_data.describe()) # 列印原始數據基本描述性信息print (- * 60)

在數據概覽部分,通過數據框的head方法輸出前4條數據,通過數據框的describe方法輸出其基本描述性統計信息。返回結果如下:

Data Overview: ORDERDATE ORDERID AMOUNTINFOUSERID 142074 2016-01-01 4196439032 9399.056927 2016-01-01 4198324983 8799.087058 2016-01-01 4191287379 6899.0136104 2016-01-01 4198508313 5999.0------------------------------Data DESC: AMOUNTINFOcount 86127.000000mean 744.705261std 1425.211182min 0.50000025% 13.00000050% 59.00000075% 629.000000max 30999.000000------------------------------------------------------------

如何通過上述結果得到有效信息?

在Data Overview部分,主要查看不同數據列的數據格式,尤其是有特定轉換操作之後是否符合源數據文件的格式或得到目標轉換要求;另外,數據的長度、組成規律、類型等是否與真實數據一致。

在Data DESC部分主要分析數值型欄位(本案例中是AMOUNTINFO)的數值分布規律,包括記錄數、極值、標準差、分位數結果等,可用於數據集的適用模型、極值的處理等後續計算的輔助判斷依據。

在本案例中,發現數據的極值相差非常大,並且標準差也很大,說明數據波動非常明顯。另外,最大值和最小值似乎有些奇怪:最大值竟然有超過30000元、最小值卻只有0.5元,這兩種狀態都非常異常。經過與業務方溝通後確認,最大值的訂單金額有效,為某客戶一次性購買多個大家電商品;而訂單金額為0.5元的訂單都屬於促銷優惠券生成的訂單,這些訂單用來為用戶消費時提供優惠券,沒有實際意義,因此這些數據需要去掉。除了這些0.5元的訂單,所有低於1元的訂單均有這個問題。

# 缺失值審查na_cols = raw_data.isnull().any(axis=0) # 查看每一列是否具有缺失值print (NA Cols:)print (na_cols) # 查看具有缺失值的列print (- * 30)na_lines = raw_data.isnull().any(axis=1) # 查看每一行是否具有缺失值print (NA Recors:)print (Total number of NA lines is: {0}.format(na_lines.sum())) # 查看具有缺失值的行總記錄數print (raw_data[na_lines]) # 只查看具有缺失值的行信息print (- * 60)

缺失值對於後續的計算會產生重大影響,因此這裡需要確認數據中是否含有缺失數據。先通過raw_data.isnull().any(axis=0)來判斷所有列是否含有缺失信息,其中的isnull用來查看是否有缺失值,any用來判斷數據記錄中的任何一個位置出現缺失值都會計算在內,參數axis=0以列為基礎做查看。列印結果如下:

NA Cols:ORDERDATE TrueORDERID FalseAMOUNTINFO True

返回結果顯示了ODERDATE和AMOUNTINFO都有缺失。下一步結合每一行看下到底有多少條數據出現缺失。這次使用的方法跟判斷列缺失一致,僅僅是axis設為1用來表示按行查看。在na_lines中,結果由True和False組成,因此可以直接做數值計算,這裡使用sum函數統計一共有多少行為含有NA,含有NA的總記錄數和詳細行記錄列印結果如下:

NA Recors:Total number of NA lines is: 10 ORDERDATE ORDERID AMOUNTINFOUSERID 75849 2016-01-01 4197103430 NaN103714 NaN 4136159682 189.0155209 2016-01-01 4177940815 NaN139877 NaN 4111956196 6.354599 2016-01-01 4119525205 NaN65456 2016-01-02 4195643356 NaN122134 2016-09-21 3826649773 NaN116995 2016-10-24 3981569421 NaN98888 2016-12-06 3814398698 NaN145951 2016-12-29 4139830098 NaN

由返回結果可知,一共有10條數據含有NA值,這些數據在整體樣本集中佔比非常小,因此這裡可以直接刪除。

步驟4 數據預處理準備工作,包括數據異常、格式轉換和處理

# 異常值處理sales_data = raw_data.dropna() # 丟棄帶有缺失值的行記錄sales_data = sales_data[sales_data[AMOUNTINFO] > 1] # 丟棄訂單金額<=1的記錄

對於異常值的處理,直接使用數據框的dropna方法刪除;對於訂單金額<=1的數據,我們也直接丟棄,只選擇訂單金額>1的數據記錄。

# 日期格式轉換sales_data[ORDERDATE] = pd.to_datetime(sales_data[ORDERDATE], format=%Y-%m-%d) # 將字元串轉換為日期格式print (Raw Dtypes:)print (sales_data.dtypes) # 列印輸出數據框所有列的數據類型print (- * 60)

日期轉換的目的是實現基於時間間隔的計算,這樣才能算出R距離指定日期的天數。這一操作沒有在讀取數據轉換(在讀取時的具體轉換方法請參考「4.6.4 代碼實操:Python時間序列分析」部分),這裡使用pd.to_datetime方法將ORDERDATE列轉換為pandas的日期類型(pd.datetime類型),format參數以原始數據字元串的格式來寫,只有格式對應上才能實現解析。解析完成後,使用數據框的dtypes列印輸出Dtypes類型如下:

Raw Dtypes:ORDERDATE datetime64[ns]ORDERID objectAMOUNTINFO float32dtype: object

接下來需要分別計算R、F、M三個原始變數的數值,主要使用的方式是數據框的groupby方法。

# 數據轉換recency_value = sales_data[ORDERDATE].groupby(sales_data.index).max() # 計算原始最近一次訂單時間frequency_value = sales_data[ORDERDATE].groupby(sales_data.index).count() # 計算原始訂單頻率monetary_value = sales_data[AMOUNTINFO].groupby(sales_data.index).sum() # 計算原始訂單總金額

這三行代碼都是以原始數據框的索引為主鍵(以用戶ID作為匯總維度)分別對ORDERDATE求最大值、對ORDERDATE做計數統計、對AMOUNTINFO求和,得分R、F、M三個指標的原始值。

步驟5 計算RFM得分

# 分別計算R、F、M得分deadline_date = pd.datetime(2017, 01, 01) # 指定一個時間節點,用於計算其他時間與該時間的距離r_interval = (deadline_date - recency_value).dt.days # 計算R間隔r_score = pd.cut(r_interval, 5, labels=[5, 4, 3, 2, 1]) # 計算R得分f_score = pd.cut(frequency_value, 5, labels=[1, 2, 3, 4, 5]) # 計算F得分m_score = pd.cut(monetary_value, 5, labels=[1, 2, 3, 4, 5]) # 計算M得分分

首先指定一個時間節點,用於計算其他時間與該時間的距離,這是計算R的基礎。這裡定義了2017-01-01,通過數據框相減得到時間間隔天數對象,並對該對象使用dt.days方法獲得天的數值。

下面對得到的R、F、M三個變數值使用分位數法做區間劃分,這裡使用了pd.cut方法,默認設置為5份,同時通過labels標籤指定區間標誌。主要注意的是對R(最近購買時間)而言,數值越大意味著離指定日期越遠,因此其區間劃分後的值應該越小,所以該標籤列表順序與其他兩個相反。

提示 dt是pandas中Series時間序列datetime類屬性的訪問對象,除了代碼中用到的days(天)以外,還包括:date、dayofweek、dayofyear、days_in_month、freq、hour、microsecond、minute、month、quarter、second、time、tz、week、weekday、weekday_name、weekofyear、year等。這些是提取Series時間數據的常用方法。

在得到RFM各自得分後,將三維維度數據合併為一個數據框,便於在一起做展示以及後續的RFM總得分計算。

# R、F、M數據合併rfm_list = [r_score, f_score, m_score] # 將r、f、m三個維度組成列表rfm_cols = [r_score, f_score, m_score] # 設置r、f、m三個維度列名rfm_pd = pd.DataFrame(np.array(rfm_list).transpose(), dtype=np.int32, columns=rfm_cols, index=frequency_value.index) # 建立r、f、m數據框print (RFM Score Overview:)print (rfm_pd.head(4))print (- * 60)

先建立R、F、M三個維度的值列表和名稱列表,用於生成數據框時指定數據和標籤。然後使用pd.DataFrame建立數據框。使用np.array將R、F、M生成的值列錶轉換為矩陣,此時的矩陣形狀是(3, 59676),不符合我們需要的三列的需求,因此使用transpose方法對矩陣做轉置處理,該方法也可以簡寫為T(注意大小寫),即np.array(rfm_list).transpose()等價於np.array(rfm_list).T;由於R、F、M的值域是[1,5]的整數,因此創建數據框時指定數據類型為整數型,通過dtype=np.int32實現,該方法也可以寫成dtype=int32,二者是等價的;然後設置列名並指定索引列為用戶ID,由於R、F、M三個Series的索引都相同,因此這裡隨便指定為frequency_value.index。最後列印前4條結果如下:

RFM Score Overview: r_score f_score m_scoreUSERID 51220 4 1 151221 2 1 151224 3 1 151225 4 1 1------------------------------------------------------------

完成R、F、M數據框創建後,可以基於該數據框計算RFM總得分,這裡使用兩種方法:基於三個維度加權的RFM總得分以及基於組成的總得分。

# 計算RFM總得分# 方法一:加權得分rfm_pd[rfm_wscore] = rfm_pd[r_score] * 0.6 + rfm_pd[f_score] * 0.3 + rfm_pd[m_score] * 0.1# 方法二:RFM組合rfm_pd_tmp = rfm_pd.copy()rfm_pd_tmp[r_score] = rfm_pd_tmp[r_score].astype(string)rfm_pd_tmp[f_score] = rfm_pd_tmp[f_score].astype(string)rfm_pd_tmp[m_score] = rfm_pd_tmp[m_score].astype(string)rfm_pd[rfm_comb] = rfm_pd_tmp[r_score].str.cat(rfm_pd_tmp[f_score]).str.cat(rfm_pd_tmp[m_score])

在方法一中,直接取出數據框的三列(R、F、M)通過乘以特定權重值得到總得分。由於業務方更關注活躍度,認為訪問的鄰近度最重要,因此R的權重比較高,設置為0.6;其次是訪問頻率F設置為0.3;訂單金額M則設置為0.1。然後基於不同的列直接做加權相加,而無需通過循環讀出各個元素再做計算。

提示 在設定權重值時需要與業務部門溝通,主要看業務關注哪個方面,再設置對應維度的值。而對於權重值的分配一般情況下總權重的和為1,這樣劃分更清晰並便於解釋和管理。

在方法二中,我們要將R、F、M三個值組合,需要用到字元串的組合。由於原始數據框中為了做加權計算而設置為數值型,因此這裡需要轉換為字元串型。為了不影響原始數據,通過copy方法得到一份副本,然後將副本的三列使用astype方法將數值型轉換為字元串型。

提示 在設置數據類型時一般有兩種思路:一種是在創建數據框時通過dtype指定(原始數據框就是這種方法),第二種是在創建好的數據框中使用astype方法將特定欄位轉換為目標類型。

在最後的合併過程中,使用了pandas的字元串處理庫str中的cat方法做字元串合併,該方法可以將右側的數據合併到左側,在連續使用兩個str.cat方法後得到總的R、F、M字元串組合。

相關知識點:使用str.cat方法將字元串合併

關於字元串的合併,之前在不同章節中主要用到了四種方法:

  • 使用+(加號)組合字元串:例如輸入X = a+b能得到X的值是ab
  • 使用%佔位符做字元串組合,主要用在變數輸出上,例如輸入X = I am %s%Tony,能得到X的值是I am Tony
  • 使用.join方法將多個可迭代的對象合併,例如輸入X = .join([I,am,tony]),得到X的值是I am tony
  • 使用.format做佔位符將多個字元串合併,跟%用法類似但更強大,主要用在變數輸出上,例如輸入:X = I am {1} and {0} years old.format(30,Tony),得到X的值是I am Tony and 30 years old

本節使用了pandas自帶的字元串組合方法,該放在在str庫中,它用於字元串對象合併。該方法語法如下:cat(self, others=None, sep=None, na_rep=None),參數:

  • others:要合併的另外一個對象(右側對象),如果為空則將左側對象組合
  • sep:合併的分隔符,默認為空,可自定義,例如,、;等na_rep:如果遇到NA(缺失值)時如果處理,默認為忽略
  • 需要注意的是:該方法用於對Series做組合,而不能是數據框,適用於一維數據或字元串。

示例:

將左側對象組合

輸入:pd.Series([a,b,c]).str.cat(sep=;)

輸出:a;b;c

將左側對象和右側對象組合

輸入:pd.Series([a, b, c]).str.cat([A, B, C], sep=;)

輸出:

0 a;A1 b;B2 c;C

步驟6 列印輸出和保存結果,該步驟中我們將數據列印出來,並分別保存為本地csv數據文件以及將數據存入MySQL資料庫。

經過上述計算過程,已經得到R、F、M結果,這裡我們將其列印出來。

# 列印結果print (Final RFM Scores Overview:)print (rfm_pd.head(4)) # 列印數據前4項結果print (- * 30)print (Final RFM Scores DESC:)print (rfm_pd.describe())

使用head方法指定列印前4條結果以及RFM得分描述性統計結果如下:

Final RFM Scores Overview: r_score f_score m_score rfm_wscore rfm_combUSERID 51220 4 1 1 2.8 41151221 2 1 1 1.6 21151224 3 1 1 2.2 31151225 4 1 1 2.8 411------------------------------Final RFM Scores DESC: r_score f_score m_score rfm_wscorecount 59676.000000 59676.000000 59676.000000 59676.000000mean 3.299970 1.013439 1.000134 2.384027std 1.402166 0.116017 0.018307 0.845380min 1.000000 1.000000 1.000000 1.00000025% 2.000000 1.000000 1.000000 1.60000050% 3.000000 1.000000 1.000000 2.20000075% 5.000000 1.000000 1.000000 3.400000max 5.000000 5.000000 5.000000 5.000000

該過程主要驗證數據輸出是否符合預期,主要是R、F、M的組合和加權計算;使用描述性統計簡單看下數值型得分的區間分布(重點是極值)是否在預期的[1,5]之間。分析發現上述數據沒問題。

注意 如果原始數據集中有NA值而沒有經過處理,那麼在使用分位數方法時會默認忽視NA的分位,即值為NA的數據點仍然是NA。這會導致在做後期加權得分和組合時都會遇到問題。通過描述性統計也能發現該問題是否存在。

接著將RFM得分文件保存到本地,便於業務做進一步分析應用。

  • # 保存RFM得分到本地文件
  • rfm_pd.to_csv(sales_rfm_score.csv) # 保存數據為csv

直接使用數據框對象的to_csv方法將數據保存為csv格式的數據文件。除了保存為csv格式的文件,數據框對象還可以支持保存為excel等文件。另外也支持sql、json、stata、pickle、sparse、hdf、html等對象的輸出。保存到本地csv文件的部分數據截圖如下:

圖5-4本地csv文件結果

由於RFM結果要作為其他模型結果的輸入維度,因此需要將RFM得分寫入數據框。這裡使用的是MySQL官方連接程序實現。

  • # 設置要寫庫的資料庫連接信息
  • table_name = sales_rfm_score # 要寫庫的表名
  • # 資料庫基本信息
  • config = {host: 127.0.0.1, # 默認127.0.0.1
  • user: root, # 用戶名
  • password: 123456, # 密碼
  • port: 3306, # 埠,默認為3306
  • database: python_data, # 資料庫名稱
  • charset: gb2312 # 字元編碼
  • }

首先建立要寫入資料庫的基本信息,通過table_name = sales_rfm_score定義要寫入的表的名稱,該變數會在後續表識別和寫入時用到。config定義了一個用於寫入資料庫的詳細配置信息定義,包括主機host、用戶user、密碼password、埠port、要寫入的數據表所處的資料庫名database、字符集charset。這些信息在「2.2.3 從關係型資料庫MySQL讀取運營數據」中已經詳細介紹過,在此不再贅述。

配置信息定義完成後,下面建立資料庫連接。

con = mysql.connector.connect(**config) # 建立mysql連接cursor = con.cursor() # 獲得游標

使用mysql.connector.connect方法連接資料庫,後續的獲得游標和寫庫都需要該有效連接;使用連接的con.cursor方法獲得游標,後續的查詢等操作都基於該對象實現。

在將數據寫入資料庫之前,需要先判斷資料庫中是否存在要寫入的表。如果不存在需要新建數據表。

  • # 查找資料庫是否存在目標表,如果沒有則新建
  • cursor.execute("show tables") #
  • table_object = cursor.fetchall() # 通過fetchall方法獲得所有數據
  • table_list = [] # 創建庫列表
  • for t in table_object: # 循環讀出所有庫
  • table_list.append(t[0]) # 每個每個庫追加到列表
  • if not table_name in table_list: # 如果目標表沒有創建
  • cursor.execute(
  • CREATE TABLE %s (
  • userid VARCHAR(20),
  • r_score int(2),
  • f_score int(2),
  • m_score int(2),
  • rfm_wscore DECIMAL(10,2),
  • rfm_comb VARCHAR(10),
  • insert_date VARCHAR(20)
  • )ENGINE=InnoDB DEFAULT CHARSET=gb2312
  • % table_name) # 創建新表

使用游標的execute方法執行一個sql語句,由於在config中我們定義了資料庫名,因此這裡直接使用"show tables"來顯示當前資料庫下所有的表列表,而不是先使用use [資料庫],然後再"show tables"。

游標執行後獲得的對象是沒有數據的,使用fetchall方法抓取全部數據並返回,但返回的是一個數據列表對象[(uorder,), (utest,)],列表中的每個元素都是一個元組,而數據表名處於元組的第一個值。因此接著通過一個循環將對象讀取出來,得到數據表列表table_list。

使用if條件語句判斷要寫入的數據表table_name是否在數據表列表中,如果該表不存在則新建。這裡仍然使用游標的execute方法執行一段SQL語句,只是由於創建表的SQL語句較長,使用來表示多個段落的字元串。

相關知識點:Python的字元串標識符

Python的字元串標識符有三種,分別是(單引號)、"(雙引號)、(三個連續單引號)、"""(三個連續雙引號)。這四種表示符號(注意都是英文狀態下)在大多數場景下的作用是相同的,但是都要求成對出現。例如:ABC等價於"ABC",也等價於ABC。

但在某些場景下他們的用途是有區分的,主要體現在兩個方面:

  • 當字元串中出現子集字元串時,需要使用不同類型的字元串做區分,例如I said:"You can go!",這時由於在外層字元串使用了(單引號),在內層就不能再使用相同的表示方法了,所以使用雙引號。
  • 當字元串非常長(通常超過1行)、或者字元串步長但是需要分段顯示時,就需要用到(連續三個單引號)或"""(三個連續雙引號)來表示,上述代碼就是這種應用。除此以外,這種長段落還經常用到字典定義、格式化段落文本輸出、複雜功能的SQL語句(例如SQL嵌套、關聯查詢)等。

經過上述步驟,能保證要寫入的數據表已經存在,並且跟要寫入的數據類型和格式相匹配。接下來開始寫庫操作。

# 將數據寫入資料庫user_id = rfm_pd.index # 索引列rfm_wscore = rfm_pd[rfm_wscore] # RFM加權得分列rfm_comb = rfm_pd[rfm_comb] # RFM組合得分列timestamp = time.strftime(%Y-%m-%d, time.localtime(time.time())) # 寫庫日期print (Begin to insert data into table {0}....format(table_name)) # 輸出開始寫庫的提示信息for i in range(rfm_pd.shape[0]): # 設置循環次數並依次循環 insert_sql = "INSERT INTO `%s` VALUES (%s,%s,%s,%s,%s,%s,%s)" % (table_name, user_id[i], r_score.iloc[i], f_score.iloc[i], m_score.iloc[i], rfm_wscore.iloc[i],rfm_comb.iloc[i], timestamp) # 寫庫SQL依據 cursor.execute(insert_sql) # 執行SQL語句,execute函數裡面要用雙引號 con.commit() # 提交命令

寫庫時用到了7列數據,分別是userid、r_score、f_score、m_score、rfm_wscore、rfm_comb、insert_date。其中:

  • userid可以從數據框的索引中取出,用於標識不同的用戶,使用數據框的index獲得索引列用戶ID數據框;
  • r_score、f_score、m_score三列值在「分別計算R、F、M得分」時已經產生;
  • rfm_wscore、rfm_comb可以分別使用數據框讀取列名以獲得RFM加權得分和組合得分數據;
  • insert_date用來記錄每次寫庫時的日期,該日期可以作為變化狀態的判斷依據,可應用於分析不同時間和周期下用戶的活躍度變化,例如「5.5.3 藉助動態數據流關注會員狀態的輪轉」中的那種應用。而在跟其他模型做數據集成應用時,可根據實際情況取需要的時間對應的狀態,例如取最新狀態可取insert_date中的最大值;

在獲取insert_date寫庫日期時使用了3個時間函數:

  • time.time():獲取當前系統時間下相對於Epoch時間(1970-01-01 00:00:00 UTC),以浮點型數據的形式表示,例如1495770037.572
  • time.localtime():將時間轉換為本地時間,是一個包含了年、月、日、小時、分鐘、秒、一周中第幾天、一年中第幾天等數據的元組。例如將上述時間轉換結果為time.struct_time(tm_year=2017, tm_mon=5, tm_mday=26, tm_hour=11, tm_min=40, tm_sec=37, tm_wday=4, tm_yday=146, tm_isdst=0)
  • time.strftime():將不同形式的時間轉換為特定格式和時間表示方法,該方法在本書中多次用到,例如將上述結果轉換為2017-05-26

經過上述功能轉換後的日期是YYYY-MM-DD的格式,例如2017-05-26。接下來的寫庫操作利用for循環,根據數據框的記錄做讀出索引值,然後使用游標的執行方法運行SQL;由於我們要做批量寫入操作,因此需要使用on.commit方法提交命令。

寫入操作完成後,關閉游標和資料庫連接。

cursor.close() # 關閉游標con.close() # 關閉資料庫連接print (Finish inserting, total records is: %d % (i + 1)) # 列印寫庫結果

得到總的寫入資料庫記錄數如下:

Finish inserting, total records is: 59676

可使用Navicat打開對應的資料庫表,查看結果是否符合預期,以及總記錄數是否一致。

5.7.5 案例數據結論

由於在RFM劃分時,將區間劃分為5份,因此可以將這5份區間分別定義了:高、中、一般、差和非常差5個級別,分別對應到R、F、M中的5/4/3/2/1。

基於RFM得分業務方得到這樣的結論:

  • 公司的會員中99%以上的客戶消費狀態都不容樂觀,主要體現在消費頻率低R、消費總金額低M。——經過分析,這裡主要由於其中有一個用戶(ID為74270)消費金額非常高,導致做5分位時收到最大值的影響,區間向大值域區偏移。
  • 公司中有一些典型客戶的整個貢獻特徵明顯,重點是RFM得分為555的用戶(ID為74270),該用戶不僅影響了訂單金額高,而且其頻率和購買新鮮度和消費頻率都非常高,應該引起會員管理部門的重點關注。
  • 本周表現處於一般水平以上的用戶的比例(R、F、M三個維度得分均在3以上的用戶數)相對上周環比增長了1.3%。這種良好趨勢體現了活躍度的提升。
  • 本周低價值(R、F、M得分為111以上)用戶名單中,新增了1221個新用戶,這些新用戶的列表已經被取出。

5.7.6 案例應用和部署

針對上述得到的分析結論,會員部門採取了以下措施:

  • 將ID為74270的用戶加入大客戶名單,並實現重點關懷和管理
  • 上周的會員工作中有關低價值群體的用戶並未減少,反而新增了一些,需要會員部門重點關注和處理。目前最主要的還是通過會員渠道拉動會員再次訪問網站並訂單,防止客戶的流失和沉默,而訂單的金額是次要因素。

錄入資料庫的RFM得分數據已經應用到其他數據模型中,成為建模輸入的關鍵維度特徵之一。同時,該模型已經作為定時任何每周一早晨上班前運行一次。

5.7.7 案例注意點

本案例中有以下幾個點需要讀者重點關註:

對於R、F、M區間的劃分是一個離散化的過程,具體需要劃分為幾個區間需要跟業務方確認。本案例是劃分為5個區間,實際上一般不會比這個區間更多,3~5是比較好的選擇。

R、F、M的組合加權中關於權重的確認,不同時期的業務關注點可能不同,因此權重值可能是需要調整的;另外,權重值的打分一般使用專家打分法,即讓業務來做權重打分,如果沒有業務打分,那麼大多數情況下三者的權重從大到小依次是:R>F>M。

本案例中有一個ID為74270的用戶,由於其各方面特徵非常顯著,因此整個RFM得分的劃分區間都受其影響。這種極值的影響(包括案例開始時極小值的處理)需要跟業務部門溝通確認才能進行處理,此次的極大值的出現是業務認可的有效數據,因此沒有去除;如果有其他極值可能需要特別處理,否則會影響區間劃分。本例中雖然影響劃分,但恰好發現了該用戶的價值。

雖然訂單資料庫中的數據質量相對較高,但可能由於採集問題、資料庫同步、ETL、查詢、誤操作等問題還是會導致NA值的出現,NA值的處理非常重要。

R、F、M三個維度的處理包括計算、離散化、組合、轉換之前都需要注意其數據類型和格式,尤其是有關時間項的轉換操作提前完成。

5.7.8 案例引申思考

利用RFM模型劃分用戶群體並作價值度分析是統計分析非常基礎且有效的方法。該模型幾乎用不到任何專業的統計分析和挖掘知識,只需要具有基本的數據清洗、處理和轉換技能即可完成,因此幾乎是各個企業都會用到的模型。更重要的是,該模型原理簡單,業務方在理解和應用起來非常容易入手,大大提高了部署落地的可能性。

對於本案例的實施,讀者還可以從以下幾方面做引申思考:

  • 問題一 按照R、F、M每個區間劃分為5份的原則,實際上出來的總得分可能性是5*5*5=125種,為什麼結果中只有14種組合得分?
  • 問題二 如果不同周期下R、F、M的權重會改變,其運營結果是否具有可比性和連續性?
  • 問題三 RFM模型可以作為模型分析方法,也可以作為數據預處理方法,基於不同的維度通過計算組合得分或加權得分的方式獲得新的數據,這是一種數據降維的有效方法。使用組合得分方法和加權得分方法得到的兩種降維方法在後續應用中有哪些不同?

筆者關於引申思考的想法

  • 問題一 在案例注意點中也提到了,由於受到ID為74270的用戶行為影響,其極大值導致整個區間劃分非常大,很多區間內沒有數據。以F值為例,該用戶的F原始值為14(一年中有14個訂單),其他用戶最高的只有6個訂單,因此在做區間劃分時從7-13內沒有一個用戶符合條件,也就不會產生該得分區間的用戶統計量。
  • 問題二 權重的變化影響的僅僅是加權得分項,而不影響組合得分項,因此其他建模應用中沒有任何影響。在分析過程中,這種改變不可避免的會使加權得分產生規則發生變化,需要在業務做分析時加以提醒。
  • 問題三 首先需要明確的是RFM的兩種總得分應該只選擇其中一種加以應用,因為二者具有高度相關性。由於加權RFM得分是一個連續型變數,更適合直接參与到後續回歸建模中;組合RFM得分是一個分類變數,更適合應用到分類建模中,同時需要注意的是該分類需要做標誌轉換,具體過程參見「3.2 將分類數據和順序數據轉換為標誌變數」。

推薦閱讀:

python re.sub 應用
為什麼 Python 中列表的 sort 方法一定要返回 None 而不是排序後的列表?
Python文學家為Python寫的一首詞?(附中英文版)
Scrapy如何動態調整爬取速度?
怎樣用 Matlab 寫出優雅的代碼?

TAG:數據分析 | Python | 數據化運營 |