3.6 數據化運營要抽樣還是全量數據
說明:本文是《Python數據分析與數據化運營》中的「3.6 數據化運營要抽樣還是全量數據」。
-----------------------------下面是正文內容--------------------------
抽樣是從整體樣本中通過一定的方法選擇一部分樣本,抽樣是數據處理的基本步驟之一,也是科學實驗、質量檢驗、社會調查普遍採用的一種經濟有效的工作和研究方法。
3.6.1 什麼時候需要抽樣
抽樣工作在數據獲取較少或處理大量數據比較困難的時代非常流行,主要有以下幾方面背景:
- 數據計算資源不足。計算機軟硬體的限制是導致抽樣產生的基本原因之一,尤其是在數據密集的生物、科學工程等領域,不抽樣往往無法對海量數據進行計算。
- 數據採集限制。很多時候抽樣從數據採集端便已經開始,例如做社會調查必須採用抽樣方法進行研究,因為根本無法做所有人群做調查。
- 時效性要求。抽樣帶來的以局部反應全局的思路,如果方法正確,可以以極小的數據計算量來實現對整體數據的統計分析,在時效性上會大大增強。
如果存在上述條件限制或有類似強制性要求,那麼抽樣工作仍然必不可少。但是在當前數據化運營的大背景下,數據計算資源充足、數據採集端可以採集更多的數據並且可以通過多種方式滿足時效性的要求。抽樣工作是否就沒有必要了?其實不是的,即使上述限制條件都滿足,還有很多場景依然需要通過抽樣方法來解決具體問題:
- 通過抽樣來實現快速的概念驗證。數據工作中可能會包括創新性或常識性項目,對於這類項目進行快速驗證、迭代和交付結論往往是概念驗證的關鍵,通過抽樣方法帶來的不僅是計算效率的提升,還有前期數據準備、數據預處理、演算法實現等各個方面的開發,以及伺服器、硬體的配套方案的部署等內容的可行性、簡單化和可操作性。
- 通過抽樣來解決樣本不均衡問題。在「3.4解決樣本類別分布不均衡的問題」中,我們提到了通過欠抽樣、過抽樣以及組合/集成的方法解決不均衡的問題,這個過程就用到了抽樣方法。
- 無法實現對全部樣本覆蓋的數據化運營場景。典型場景包括市場研究、客戶線下調研分析、產品品質檢驗、用戶電話滿意度調查等,這些場景下無法實現對所有樣本的採集、分析、處理和建模。
- 定性分析的工作需要。在定性分析工作中,通常不需要定量分析時的完整假設、精確數據和複雜統計分析過程,更多的是採用訪問、觀察和文獻法收集資料並通過主觀理解和定性分析找到問題答案,該過程中主要依靠人自身的能力而非密集的計算機能力來完成研究工作。如果不使用抽樣方法,那麼定性分析將很難完成。
3.6.2 如何進行抽樣
抽樣方法從整體上分為非概率抽樣和概率抽樣兩種。非概率抽樣不是按照等概率的原則進行抽樣,而是根據人類的主觀經驗和狀態進行判斷;概率抽樣則是以數學概率論為基礎,按照隨機的原則進行抽樣。本節以下內容介紹的抽樣方法屬於概率抽樣。
簡單隨機抽樣
該抽樣方法是按等概率原則直接從總中抽取n個樣本,這種隨機樣本方法簡單,易於操作;但是它並不能保證樣本能完美的代表總體,這種抽樣的基本前提是所有樣本個體都是等概率分布,但真實情況卻是很多數樣本都不是或無法判斷是否等概率分布。在簡單隨機抽樣中,得到的結果是不重複的樣本集,還可以使用有放回的簡單隨機抽樣,這樣得到的樣本集中會存在重複數據。該方法適用於個體分布均勻的場景。
等距抽樣
等距抽樣是先將總體的每個個體按順序編號,然後再計算出抽樣間隔,再按照固定抽樣間隔抽取個體。這種操作方法易於理解、簡便易行,但當總體樣本的分布呈現明顯的分布規律時容易產生偏差,例如增減趨勢、周期性規律等。該方法適用於個體分布均勻或呈現明顯的均勻分布規律,無明顯趨勢或周期性規律的數據。
分層抽樣
分層抽樣是先將所有個體樣本按照某種特徵劃分為幾個類別,然後從每個類別中使用隨機抽樣或等距抽樣的方法選擇個體組成樣本。這種操作方法能明顯的降低抽樣誤差,並且便於針對不同類別的數據樣本進行單獨研究,因此是一種較好的實現方法。該方法適用於帶有分類邏輯的屬性、標籤等特徵的數據。
整群抽樣
整群抽樣是先將所有樣本分為幾個小群體集,然後隨機抽樣幾個小群體集來代表總體。這種操作方法跟之前的3種方法的差異點在於該方法抽取的是小群體集,而不是每個數據個體本身。該方法雖然簡單易行,但是樣本的分布受限於小群體集的劃分,抽樣誤差較大。這種方法適用於小群體集的特徵差異比較小,並且對劃分小群體集有更高要求。
3.6.3 抽樣需要注意的幾個問題
數據抽樣要能反映運營背景
數據能正確反映運營背景看起來非常簡單,但實際上需要數據工作者對於運營環節和流程非常熟悉才有可能實現。以下是常見的抽樣不能反映運營背景的情況:
- 數據時效性問題:例如使用過時的數據(例如1年前的數據)來分析現狀的運營狀態。
- 缺少關鍵因素數據:沒有將運營分析涉及到的主要因素所產生的數據放到抽樣數據中,導致無法根據主要因素產生有效結論,模型效果差;例如抽樣中沒有覆蓋大型促銷活動帶來的銷售增長。
- 不具備業務隨機性:有意/無意的多抽取或覆蓋特定數據場景,使得數據明顯趨向於特定分布規律,例如在做社會調查時使用北京市的抽樣數據來代表全國。
- 沒有考慮業務增長性:例如在成長型公司中,公司的發展不都是呈現線性趨勢,很多時候會產生指數趨勢。這時需要根據這種趨勢來使得業務滿足不同增長階段的分析需求,而不只是集中於增長爆發區間。
- 沒有考慮數據來源的多樣性:只選擇某一來源的數據做抽樣,使得數據的分布受限於數據源。例如在做各分公司的銷售分析中,僅僅將北方大區的數據納入其中做抽樣,而忽視了其他大區的數據,其結果必然失之偏頗。
- 業務數據可行性問題:很多時候,由於受到經費、許可權、職責等方面的限制,在數據抽樣方面無法按照數據工作要求來執行,此時要根據運營具體實際情況調整。這點往往被很多數據工作者忽視。
數據抽樣要能滿足數據分析和建模需求
數據抽樣必須兼顧後續的其他數據處理工作,尤其是分析和建模需求。主要包括以下幾方面注意點:
抽樣樣本量的問題
對於大多數數據分析建模而言,數據規模越大,模型擬合結果越準確。但到底如何定義數據量的大小,筆者根據不同類型的數據應用總結為以下幾個維度:
- 以時間為維度分布的,至少包含一個能滿足預測的完整業務周期。例如做月度銷售預測,至少包含12個月的數據;做日銷售預測的,至少包含30天的數據,如果天中包含特定周期,則需要重複多個周期。同時,時間性特徵的要充分考慮季節性、波動性、節假日等特殊規律,這些都要盡量包含在抽樣數據中。
- 做預測(包含分類和回歸)分析建模的,需要考慮特徵數量和特徵值域(非數值型)的分布,通常數據記錄數要同時滿足特徵數量和特徵值域的100倍以上。例如數據集有5個特徵,假如每個特徵有2個值域,那麼數據記錄數需要至少在1000(100×5×2)條以上。
- 做關聯規則分析建模的,根據關聯前後項的數量(每個前項或後項可包含多個要關聯的主體,例如品牌+商品+價格關聯),每個主體需要至少1000條數據。例如只做單品銷售關聯,那麼單品的銷售記錄需要在1000條以上;如果要同時做單品+品牌的關聯,那麼需要至少2000條數據。
- 對於異常檢測類的分析建模的,無論是監督式還是非監督式建模,由於異常數據本來就是小概率分布的,因此異常數據記錄一般越多越好。
以上的數據記錄數不是固定的,筆者在實際工作時,如果沒有特定時間要求,一般都會選擇一個適中的樣本量做分析,此時會綜合考慮特徵數、特徵值域分布數、模型演算法適應性、建模需求等;而如果是面向機器計算的工作項目時,一般都會選擇盡量多的數據參與計算,而有關演算法實時性和效率的問題會讓技術和運維人員配合實現,例如提高伺服器配置、擴大分散式集群規模、優化底層程序代碼、使用實施計算的引擎和機制等。
抽樣樣本在不同類別的分布問題
做分類分析建模問題時,不同類別下的數據樣本需要是均衡分布的,有關數據樣本均衡分布的更多話題,具體參見「3.4 解決樣本類別分布不均衡的問題」。
抽樣樣本能準確代表全部整體特徵
- 非數值型的特徵的值域(例如各值頻數相對比例、值域範圍等)分布需要與總體一致。
- 數值型特徵的數據分布區間和各個統計量(如均值、方差、偏度等)需要與整體數據分布區間一致。
- 缺失值、異常值、重複值等特殊數據的分布要與整體數據分布一致。
異常檢測類數據的處理
- 對於異常檢測類的應用要包含全部異常樣本。對於異常檢測類的分析建模,本來異常數據就非常稀有,因此抽樣時要優先將異常數據包含進去。
- 對於需要去除非業務因素的數據異常,如果有類別特徵需要跟類別特徵分布一致;如果沒有類別特徵,屬於非監督式的學習,則需要與整體分布一致。
3.6.4 代碼實操:Python數據抽樣
本示例中,將使用random包以及自定義代碼實現抽樣處理。數據源文件data2.txt、data3.txt和data4.txt位於「附件-chapter3」中,默認工作目錄為「附件-chapter3」(如果不是,請cd切換到該目錄下,否則會報類似「IOError:File data3.txt does not exist」)。完整代碼如下:
import random # 導入標準庫import numpy as np # 導入第三方庫# 簡單隨機抽樣data = np.loadtxt(data3.txt) # 導入普通數據文件data_sample = random.sample(data, 2000) # 隨機抽取2000個樣本print (data_sample[:2]) # 列印輸出前2條數據print (len(data_sample)) # 列印輸出抽樣樣本量# 等距抽樣data = np.loadtxt(data3.txt) # 導入普通數據文件sample_count = 2000 # 指定抽樣數量record_count = data.shape[0] # 獲取最大樣本量width = record_count / sample_count # 計算抽樣間距data_sample = [] # 初始化空白列表,用來存放抽樣結果數據i = 0 # 自增計數以得到對應索引值while len(data_sample) <= sample_count and i * width <= record_count - 1: # 當樣本量小於等於指定抽樣數量並且矩陣索引在有效範圍內時 data_sample.append(data[i * width]) # 新增樣本 i += 1 # 自增長print (data_sample[:2]) # 列印輸出前2條數據print (len(data_sample)) # 列印輸出樣本數量# 分層抽樣# 導入有標籤的數據文件data2 = np.loadtxt(data2.txt) # 導入帶有分層邏輯的數據each_sample_count = 200 # 定義每個分層的抽樣數量label_data_unique = np.unique(data2[:, -1]) # 定義分層值域sample_list = [] # 定義空列表,用於存放臨時分層數據sample_data = [] # 定義空列表,用於存放最終抽樣數據sample_dict = {} # 定義空字典,用來顯示各分層樣本數量for label_data in label_data_unique: # 遍歷每個分層標籤 for data_tmp in data2: # 讀取每條數據 if data_tmp[-1] == label_data: # 如果數據最後一列等於標籤 sample_list.append(data_tmp) # 將數據加入到分層數據中 each_sample_data = random.sample(sample_list, each_sample_count) # 對每層數據都隨機抽樣 sample_data.extend(each_sample_data) # 將抽樣數據追加到總體樣本集 sample_dict[label_data] = len(each_sample_data) # 樣本集統計結果print (sample_dict) # 列印輸出樣本集統計結果# 整群抽樣data3 = np.loadtxt(data4.txt) # 導入已經劃分好整群的數據集label_data_unique = np.unique(data3[:, -1]) # 定義整群標籤值域print (label_data_unique) # 列印輸出所有整群標籤sample_label = random.sample(label_data_unique,2) # 隨機抽取2個整群sample_data = [] # 定義空列表,用來存儲最終抽樣數據for each_label in sample_label: # 遍歷每個整群標籤值域 for data_tmp in data3: # 遍歷每個樣本 if data_tmp[-1] == each_label: # 判斷樣本是否屬於抽樣整群 sample_data.append(data_tmp) # 樣本添加到最終抽樣數據集print (sample_label) # 列印輸出樣本整群標籤print (len(sample_data)) # 列印輸出總抽樣數據記錄條數
整個示例代碼以空行分為5部分。
第一部分導入需要的庫,這裡用到了Python內置標準庫random以及第三方庫Numpy,前者用於做隨機抽樣,後者用於讀取文件並作數據切片使用。
第二部分實現了簡單隨機抽樣。首先通過Numpy的loadtxt方法讀取數據文件;然後使用Random庫中的sample方法做數據抽樣,指定抽樣總體數據為data,抽樣數量為2000;最後列印輸出前2條數據和總抽樣樣本量。返回結果如下:
[array([ -7.66339337, -10.13735724, 5.57419663, 3.27726596, 5.06304001]),array([ -4.24826325, 10.1271918 , 3.83244343, 1.82416448, -5.94805592])]2000
第三部分實現了等距抽樣。
首先使用Numpy的loadtxt方法讀取數據文件;
然後指定抽樣樣本量為2000,並通過讀取原始數據的形狀找到最大樣本量邊界,這可以用來作為循環的終止條件之一;
接著通過最大樣本量除抽樣樣本量得到抽樣間距;
建立一個空列表用於存儲最終抽樣結果數據,通過一個變數i做循環增長並用來做索引遞增,然後進入到抽樣條件判斷過程:
- 當樣本量小於等於指定抽樣數量並且矩陣索引在有效範圍內時做處理,這裡需要注意的是索引從0開始,因此最大數量值減去1得到循環邊界,否則會報索引溢出錯誤。
- 通過列表的append方法不斷追加通過間距得到的新增樣本,在本節後面的方法中還會提到列表追加的extend方法,前者用於每次追加1個元素,後者用於批量追加多個元素。
- i += 1指的是每次循環都增加1,可以寫成i = i + 1。
- 最後列印輸出前2條數據和抽樣樣本量。返回結果如下:
[array([-3.08057779, 8.09020329, 2.02732982, 2.92353937,-6.06318211]), array([-2.11984871, 7.74916701, 5.7318711 , 4.75148273, -5.68598747])]2000
第四部分實現了分層抽樣。
首先使用Numpy的loadtxt方法導入帶有分層邏輯的數據。在該示例中,讀取的數據文件中包含了分類標籤,放在最後一列。該列分類標籤用於做分層抽樣的標識。
接著通過unique方法獲取分層(分類標籤)的值域,用於後續做循環處理。
然後分別定義了空的用於存放臨時分層數據、最終抽樣數據、顯示各分層樣本數量的空列表和空字典。
下面進入正式的主循環過程,實現分層抽樣:
- 遍歷每個分層標籤,用來做數據的分層劃分,數據一共分為2類標籤(0和1);
- 讀取每條數據並判斷數據的分層標籤是否與分層標籤相同,如果是則數據加入到各分層數據列表中。
- 當每個分層標籤處理完成後會得到該分層標籤下的所有數據,此時使用Python內置的random庫的sample方法進行抽樣。由於抽樣結果是一個列表,因此這裡使用extend(而不是append)批量追加到最終抽樣數據列表中。然後將每個分層標籤得到的樣本數量,通過len方法對列表長度進行統計,並列印輸出各個分層對應的樣本數量。結果是每個分層都按照指定數量抽取樣本,如下:
{0.0: 200, 1.0: 200}
第五部分實現了整群抽樣。
首先使用Numpy的loadtxt方法導入導入已經劃分好整群的數據集。在該示例中,讀取的數據文件中的最後一列存放了不同整群的標識,整群一共被劃分為4個群組,標識分別為0/1/2/3。
接著通過unique方法獲取整群標籤的值域,用於基於整群的抽樣。列印輸出結果如下:
[ 0. 1. 2. 3.]
然後使用Random的sample方法從整群標籤中進行抽樣,這裡定義抽取2個整群。
最後將所有屬於抽取到的整群下的數據進行讀取和追加並得到最終樣本集,列印輸出樣本集的整群標籤和總樣本數量的結果如下:
[3.0, 1.0]502
提示 由於是隨機概率抽樣,因此讀者使用代碼抽取到的樣本很可能跟筆者書中示例的不一致,這屬於正常現象;另外,讀者多次隨機抽樣程序,也可能得到的不一樣的結果。
上述過程中,主要需要考慮的關鍵點是:
- 如何根據不同的數據特點、建模需求、業務背景綜合考慮抽樣方法,得到最適合的結果
代碼實操小結:本小節示例中,主要用了幾個知識點:
- 使用Numpy的loadtxt方法讀取數據文件
- 使用內置標準庫Random庫中的sample方法做數據抽樣
- 對列表通過索引做截取、通過len方法做長度統計、通過append和extend做追加等操作
- 字典賦值操作
- 使用Numpy的unique方法獲得唯一值
- for和while循環,用來遍歷一個可迭代的對象
- if條件語句的使用,尤其是單條件和多條件判斷
推薦閱讀:
TAG:利用Python進行數據分析書籍 | 數據化運營 | 數據分析 |