前言
友情提示,這篇文章很長,光是滑動到底部就需要好幾秒,請合理安排閱讀時間。
最近在學習異常值探測(Outlier Detection)。
家里的書架上有一本叫《數據挖掘與R語言》的書,作者是Luís Torgo。這本書給出了用R做數據挖掘的四個案例,其中一個正好是欺詐交易探測。
這個案例給出的數據預處理過程非常棒,思路很值得學習,所以這部分我跟著案例,把處理過程自己實現了一遍。這篇文章的前半一部分是分享這個數據預處理過程。
在算法部分,作者給出了好幾類不同的異常值探測方法。這篇文章的后半部分討論其中一種探測方法(局部異常因子算法),及其分析效果。
順帶一提,這本書出得早(英文版出版于2011年,中文版出版于2012年),當時一些很elegant的R庫還不成熟,作者在書中使用的代碼比較舊,大家如果想照著案例把分析過程實現一遍,我個人覺得不需要完全按作者的代碼走,太繞。
然后就是,作者并不是系統地介紹各個數據挖掘方法,而是圍繞案例,把能用到的算法都聊一遍,我個人覺得不是很適合新手。(這本書是我在出國前買的,當時對數據挖掘的概念還很模糊,看這本書基本上是一頭霧水。當然,現在依然還是不能全都看懂……)
另外,這本書的翻譯實在是太一言難盡了,機械工業出版社一生黑,對這本書非常感興趣的朋友,我極力推薦你們去看英文原版。
廢話結束,以下是我關于這個分析案例的學習筆記。
數據預處理
0. 分析工具與數據來源
- 分析工具:R
- 主要用到的庫:
tidyverse
,ggplot2
(作者沒有用這些庫,而是用R的基本庫) - 數據來源:
library(DMwR)
data(sales)
1. 數據集
先來看看分析中使用的數據集。
上圖是數據集的前14行。整個數據集一共有401146行。
這個數據集的每一行,表示一份關于某銷售員銷售某款產品的銷售報告,具體特征包括:
- ID:銷售員id,一個銷售員可以銷售多款產品。
- Prod:產品id,一款產品可以被多個銷售員售賣。
- Quant:銷售員上報的產品銷售數量。
- Val:銷售員上報的產品銷售總額。
- Insp:報告標簽。ok - 公司檢查了該銷售報告并認為其真實有效;fraud - 公司認為該報告為欺詐報告;unkn - 該報告未經過公司審查。
Insp是此次欺詐交易探測任務的目標特征,即,我們想要知道,在這些unkn的報告中,哪些是ok的哪些是fraud的。
2. 數據基本情況
明白數據集的基本構成之后,現在我們來進一步認識一下這份數據。
2.1 數據概述
先來summarize一下各個特征。
報告數量最多的銷售員是v431,有10159份銷售報告;報告談及最多的產品是p1125,有3923份報告是關于它的;在產品銷售量上,數據可能是right-skewed的,注意到平均數(mean)比中位數(median)要大很多;同理,在銷售額上,數據也可能是right-tailed的;在報告標簽方面,unkn比另兩者在數量上要大很多。
另外,注意Quant和Val是有不少缺失值的(NA's)。既然有缺失值,那么不妨順帶看一下數據集中的缺失值有什么特點(Missing Pattern):
第2-5行、第2-6列中,1表示該特征無缺失值,0表示該特征有缺失值,由此可以得到4種不同的缺失值分布(最后一列的第2-5行):0 - 所有特征都沒有缺失值,1 - 有一個特征存在缺失值,2 - 有兩個特征存在缺失值。則,第一列表示該缺失值分布下的樣本數,最后一行表示該特征的缺失值總數。
可以看到,在13842個Quant缺失的樣本中,有12954個樣本只在Quant這個特征上有缺失;在1182個Val缺失的樣本中,有294個樣本只在Val這個特征上有缺失;此外,共有888個樣本,Quant和Val同時缺失。
2.2 Insp
接著看一下目標特征Insp的分布情況。
左圖是三類標簽(ok、fraud、unkn)的數量分布,可以看到,unkn比另外兩者要多很多(占96%)。
右圖是去掉了unkn后,ok和fraud的數量分布圖,ok和fraud樣本的數量比約為11:1。
這個情況符合異常值的特性:在一份數據集中,異常值只占很少很少的一部分。(這同時也是異常值探測不那么容易做的原因,占比實在太小。)
2.3 ID & Prod
這個數據集中,有6016個不重復的銷售員id和4548個不重復的產品id。
每個銷售員id下的報告數和每款產品id下的報告數分布如下:
可以看到,數據分布很不均勻,有些銷售員/產品下的報告總數明顯遠高于其他銷售員/產品。
3. 構建新特征
3.1 產品單位價格
有銷售總量Quant和銷售總額Val,我們其實可以用Val/Quant
來計算每份報告的產品單位價格。
Summarize一下這個新特征可以得到:
可以看到,最小產品單位價格為0,最大為26460,平均數(20.3)大于中位數(11.89),數據分布可能是right-skewed的。
下面來具體看一下產品單位價格的分布情況:
為了更清晰地展現數據,我給單位價格小于200和大于200的數據分別做了直方圖。
可以看到,數據非常的skewed。
3.2 產品標準價格
上述產品單位價格是每份報告中Val和Quant的比值,那么一款產品,在不同的報告中可能會有不同的產品單位價格(不同Val、Quant給出不同的比值)。
也就是說,每款產品對應一組產品單位價格數據。
在這種情況下,作者用每組單位價格的中位數來代表其對應產品的標準價格。
去掉極值(大于2500)的產品標準格分布如下:
可以看到產品標準價格同樣是right-tailed的(median = 11.24,mean = 15.00)。
4. 基于Quant、Val、產品單位價格的極值對比
作者在案例中做了三組對比分析:
- 標準價格最高的5款產品 vs. 標準價格最低的5款產品
- 總銷售收益額最高的5個銷售員 vs. 總銷售收益額最低的5個銷售員
- 總銷量最高的5款產品 vs. 總銷售量最低的5款產品
這些對比是很好的BI分析,可以幫助公司管理團隊做出有價值的決策。
上述三組對比通過箱型圖展示如下:
可以看到,在每組對比中,最高的5個數據在數值上比最低的5個數據要高很多,說明公司的銷售情況是極其不均的。
實際上,在銷售員總銷售收益額(sum of Vals per salesperson)方面,前100的銷售員占了所有報告Val的總和的38%,后2000位銷售員只占了2%。而在產品總銷量上,前100的產品占了所有報告Quant的總和的75%,后4000個產品只占9%。
這樣的比例構成說明公司其實可以精簡產品線和銷售員隊伍。
我個人最喜歡這部分,缺少商業經驗的analyst很難在一開始就想到要做這些方面的分析,至少我是被驚艷到了。
5. 缺失值處理
在缺失值的處理上,作者也分析得很細致,思路上值得學習(結論上有待進一步探討)。
回顧一下前文,在所有有缺失值的樣本中,有:
- 888個樣本在Quant和Val上都有缺失
- 12954個樣本只在Quant上有缺失
- 294個樣本只在Val上有缺失
先來看那888個在Quant和Val上都有缺失的樣本。這里主要是看每位銷售員或每款產品的Quant和Val同時缺失的報告的占比。
下圖是Quant和Val同時缺失的報告占比最大的10位銷售員:
這些缺失比例都不是很大,最大的只有14%左右。又考慮到,因為Quant和Val都缺失了,缺失值填補無從下手,所以作者認為可以把這些樣本直接刪除。
下圖是Quant和Val同時缺失的報告占比最大的10款產品:
可以看到,有幾款產品兩個特征同時缺失的報告比例是比較大的,p2689有近40%的報告同時缺失Quant和Val數據。在這種情況下,作者認為用剩余報告去填補缺失信息(如用p2689剩余的60%的銷售信息去填補其40%的缺失數據)是不合理的,所以這些報告也應該直接刪除。(這部分的解釋其實沒有說服我,中文版翻譯得亂七八糟,邏輯上我沒有想透,先留個疑問在這里。)
接下來分析一下那12954份只在Quant上有缺失的樣本。
下圖是僅Quant缺失的報告占比最大的10款產品:
p2442和p2443完全沒有Quant的信息(缺失比例為100%),所以這兩款產品的單位價格和標準價格都無法計算,也即無法進一步通過別的特征來填補這兩款產品的Quant數據;此外,涉及p2442和p2443的一共有54份報告,其中2份標記為fraud,其余52份標記為ok,說明這兩款產品已經被公司做過檢測,暫時不需要進一步進行異常預測。因此,作者決定刪除這54份報告。(其余報告保留。)
再看看僅Quant缺失的報告占比最大的10位銷售員:
有5位銷售員沒有在他們的報告中填寫Quant信息,但這些信息其實可以用Val/產品標準價格
來填補(只要一款產品的Quant或Val不是完全缺失,其標準價格就可以被計算出來,那么某份報告缺失的Quant或Val就可以通過標準價格來計算),所以這些樣本不刪除。
最后來分析那294個只在Val上有缺失的樣本。
來看看僅Val缺失的報告占比最大的10款產品:
比例都不是很大,可以用產品標準價格*Quant
來填補缺失的Val值。
最后看僅Val缺失的報告占比最大的10位銷售員:
同理,可以用產品標準價格*Quant
來填補這些缺失的Val值。
總結,在缺失值處理這部分:
- 刪除888個在Quant和Val上均缺失的樣本和54個涉及p2442、p2443的樣本(完全沒有Quant信息);
- 其余僅Quant有缺失的樣本,通過
Val/產品標準價格
來填補其Quant缺失值; - 其余僅Val有缺失的樣本,通過
產品標準價格*Quant
來填補其Val缺失值。
注意,這里的產品標準價格是剔除被標記為fraud的報告后,重新計算的產品標準價格。
缺失值處理后的數據集共有400204行。
異常值檢測
進行完數據預處理之后,我們就可以開始運用具體算法進行異常值檢測(在這個案例中,也稱為欺詐交易探測),主要目的是找出那些在Quant或Val上有異常數值的報告,并對這些報告的異常程度進行排序(進而依據異常程度對這些報告依次進行人工核查)。
算法方面,作者在書中談到的檢測方法有:
- 無監督方法:基于箱圖規則(i.e. 中位數、IQR)的探測方法,局部異常因子算法(Local Outlier Factor,LOF),基于聚類的離群值排名方法
- 有監督方法:簡單貝葉斯,AdaBoost
- 半監督方法:自我訓練模型
此外,作者沒談到但比較熱門的無監督算法還有Isolation Forest。
這次的異常值探測我只選擇了LOF算法。下面,我簡單談談LOF,重點放在對LOF的理解和對其計算效果的討論上。
1. 數據子集
我下面只用到了整個數據集的1%來做計算,原因是我用全部數據做計算的時候R給拒絕了,說數據量過大導致返回結果超出內存容量(40w+數據其實沒有很大,可能是算法設計不高效導致了這個問題)。
另外,由于LOF是無監督方法,我個人的理解是在訓練模型的時候不需要將數據集分為訓練集和測試集(不一定正確),所以我沒有做數據分割。
以下是用于運算的1%的數據子集的數據概述:
整個計算用到Quant、Val和產品標準價格3個特征,數據集不存在缺失值。
2. 局部異常因子算法(Local Outlier Factor,LOF)
LOF是一種基于距離的異常檢測算法,其思想主要是:
- 通過比較每個點和其鄰域點的密度來判斷該點是否為異常點,點p的密度越低,越可能被認定是異常點;
- 密度通過點之間的距離來計算,點之間距離越遠,密度越低,距離越近,密度越高;
- 因為LOF中,密度通過點的第k鄰域來計算,而不是全局計算,因此得名“局部”異常因子。
算法計算過程的描述如下:
- 計算點p和其他點之間的距離;
- 確定第k距離和第k距離鄰域;
- 計算點p的局部可達密度,即點p的第k鄰域內點到p的平均可達距離的倒數;
- 計算點p的局部離群因子,即點p的鄰域點的平均局部可達密度與點p的局部可達密度之比。
最終,通過各點的局部離群因子可以得到數據的異常值排序,某一點局的部離群因子數值越大(大于1),其越可能為異常值。
具體數學描述可以看這里。
3. 異常值檢測結果
這里,我通過DMwR
包的lofactor()
函數調用LOF算法。且k取10。
3.1 基于原始特征的LOF異常值探測
首先,在不對特征進行任何處理的情況下,調用LOF做計算。
局部離群因子 Top 10 如下:
這些點的局部離群因子都大于5,其中排在第一位的點,其離群因子高達74.16。
去掉局部離群因子最大點之后,各點局部離群因子的直方圖如下:
可以看到,絕大部分點的局部離群因子小于2.5。
下面我們依據計算結果,對樣本進行離散化:局部離群因子大于2.5的點被視為異常值,小于等于2.5的點為正常值。統計結果如下:
可以看到,所有點的局部離群因子的中位數為1.04;在所有被計算的點中,有45個點被視為異常點(局部離群因子大于2.5)。
值得注意的是,統計結果顯示有10個點的計算結果為無窮值,有12個點的計算結果缺失。出現無窮值的原因可能是這些點的鄰域內存在其他點與之重合,導致鄰域點到該點的平均可達距離為0,從而該點局部可達密度無窮大,最終使得其局部離群因子無窮大。至于出現缺失值的原因,我暫時想不明白,可能是所調用的算法在設計上有bug?
下面作散點圖看一下LOF算法下的異常值(局部離群因子大于2.5)分布情況:
綠色表示異常點。
左邊的圖橫軸為Quant,縱軸為Val。這張圖中,所有遠離左下角的點都被標為綠色,即這些極值點都被歸為了異常點,這是我們想要看到的結果。
中間和右邊這兩張圖的橫軸分別為Quant和Val,縱軸都為產品標準價格。可以看到,這兩張圖中,有一些點的Quant或Val值在正常范圍之內,但其產品標準價格卻極高。這些點從直覺上來說并不正常,但其絕大多數并沒有被標為綠色,即LOF算法沒有將其歸為異常點。這不符合預期。
對比上面這三張散點圖可以發現,LOF能夠較準確地識別Quant或Val異常的點,但卻不能很好地辨別出產品標準價格異常的點。而回看我們的數據集,Quant和Val在數值上,比產品標準價格高了幾個量級。
結合以上兩點,我猜測LOF對特征的數值敏感。因此,我決定對Quant、Val、產品標準價格標準化,將它們的數值限定在(-1, 1)區間內,再對這些標準化后的特征進行LOF運算。
3.2 基于標準化特征的LOF異常值探測
標準化后的Quant、Val、產品標準價格如下(前6行):
重新計算的局部離群因子 Top 10:
有6個點的局部離群因子大于10;排名第一的點,其局部離群因子為36.6。
新的計算結果分布如下(排除了局部離群因子大于10的點):
絕大多數點的局部離群因子小于2.5。
接下來我們依舊以2.5為臨界值,對樣本進行離散化:局部離群因子大于2.5的點被視為異常值,小于等于2.5的點為正常值。統計結果如下:
可以看到,所有點的局部離群因子的中位數為1.03;在所有被計算的點中,有46個點被視為異常點(局部離群因子大于2.5)。這一結果與之前結果相似。
這次計算中仍然存在無窮大值和缺失值。
對新結果作散點圖,可以得到如下異常值(局部離群因子大于2.5)分布情況:
綠色表示異常點。
可以看到,相比之前的結果,對標準化后的特征進行LOF計算,總體而言能更高效地識別出各個特征下的異常值。
以上。