導讀:抽樣是從整體樣本中通過一定的方法選擇一部分樣本。抽樣是數據處理的基本步驟之一,也是科學實驗、質量檢驗、社會調查普遍采用的一種經濟有效的工作和研究方法。
作者:宋天龍
如需轉載請聯系華章科技
01 什么時候需要抽樣
抽樣工作在數據獲取較少或處理大量數據比較困難的時期非常流行,這主要有以下幾方面原因:
- 數據計算資源不足。計算機軟硬件的限制是導致抽樣產生的基本原因之一,尤其是在數據密集的生物、科學工程等領域,不抽樣往往無法對海量數據進行計算。
- 數據采集限制。很多時候抽樣從數據采集端便已經開始,例如做社會調查必須采用抽樣方法進行研究,因為根本無法對所有人群做調查。
- 時效性要求。抽樣帶來的是以局部反映全局的思路,如果方法正確,可以以極小的數據計算量來實現對整體數據的統計分析,在時效性上會大大增強。
如果存在上述條件限制或有類似強制性要求,那么抽樣工作仍然必不可少。
但是在當前數據化運營的大背景下,數據計算資源充足、數據采集端可以采集更多的數據并且可以通過多種方式滿足時效性的要求,抽樣工作是否就沒有必要了?其實不是的,即使上述限制條件都滿足,還有很多場景依然需要通過抽樣方法來解決具體問題。
- 通過抽樣來實現快速的概念驗證。數據工作中可能會包括創新性或常識性項目,對于這類項目進行快速驗證、迭代和交付結論往往是概念驗證的關鍵,通過抽樣方法帶來的不僅是計算效率的提升,還有前期數據準備、數據預處理、算法實現等各個方面的開發,以及服務器、硬件的配套方案的部署等內容的可行性、簡單化和可操作性。
- 通過抽樣來解決樣本不均衡問題。通過欠抽樣、過抽樣以及組合/集成的方法解決不均衡的問題,這個過程就用到了抽樣方法。
- 無法實現對全部樣本覆蓋的數據化運營場景。典型場景包括市場研究、客戶線下調研分析、產品品質檢驗、用戶電話滿意度調查等,在這些場景下無法實現對所有樣本的采集、分析、處理和建模。
- 定性分析的工作需要。在定性分析工作中,通常不需要定量分析時的完整假設、精確數據和復雜統計分析過程,更多的是采用訪問、觀察和文獻法收集資料并通過主觀理解和定性分析找到問題答案,該過程中主要依靠人自身的能力而非密集的計算機能力來完成研究工作。如果不使用抽樣方法,那么定性分析將很難完成。
02 如何進行抽樣
抽樣方法從整體上分為非概率抽樣和概率抽樣兩種。非概率抽樣不是按照等概率的原則進行抽樣,而是根據人類的主觀經驗和狀態進行判斷;概率抽樣則是以數學概率論為基礎,按照隨機的原則進行抽樣。本節以下內容介紹的抽樣方法屬于概率抽樣。
1. 簡單隨機抽樣
該抽樣方法是按等概率原則直接從總樣本中抽取n個樣本,這種隨機抽樣方法簡單、易于操作,但是它并不能保證樣本能完美代表總體。這種抽樣的基本前提是所有樣本個體都是等概率分布的,但真實情況卻是多數樣本都不是或無法判斷是否是等概率分布的。
在簡單隨機抽樣中,得到的結果是不重復的樣本集,還可以使用有放回的簡單隨機抽樣,這樣得到的樣本集中會存在重復數據。該方法適用于個體分布均勻的場景。
2. 等距抽樣
等距抽樣是先將總體中的每個個體按順序編號,然后計算出抽樣間隔,再按照固定抽樣間隔抽取個體。
這種操作方法易于理解、簡便易行,但當總體樣本的分布呈現明顯的分布規律時容易產生偏差,例如增減趨勢、周期性規律等。該方法適用于個體分布均勻或呈現明顯的均勻分布規律,無明顯趨勢或周期性規律的數據。
3. 分層抽樣
分層抽樣是先將所有個體樣本按照某種特征劃分為幾個類別,然后從每個類別中使用隨機抽樣或等距抽樣的方法選擇個體組成樣本。這種操作方法能明顯降低抽樣誤差,并且便于針對不同類別的數據樣本進行單獨研究,因此是一種較好的實現方法。該方法適用于帶有分類邏輯的屬性、標簽等特征的數據。
4. 整群抽樣
整群抽樣是先將所有樣本分為幾個小群體集,然后隨機抽樣幾個小群體集來代表總體。
這種操作方法與之前的3種方法的差異點在于該方法抽取的是小群體集,而不是每個數據個體本身。該方法雖然簡單易行,但是樣本的分布受限于小群體集的劃分,抽樣誤差較大。這種方法適用于小群體集的特征差異比較小的數據,并且對劃分小群體集有更高要求。
03 抽樣需要注意的幾個問題
1. 數據抽樣要能反映運營背景
數據能正確反映運營背景,這看起來非常簡單,但實際上需要數據工作者對于運營環節和流程非常熟悉才有可能實現。以下是常見的抽樣不能反映運營背景的情況。
- 數據時效性問題:使用過時的數據(例如1年前的數據)來分析現在的運營狀態。
- 缺少關鍵因素數據:沒有將運營分析涉及的主要因素所產生的數據放到抽樣數據中,導致無法根據主要因素產生有效結論,模型效果差,例如抽樣中沒有覆蓋大型促銷活動帶來的銷售增長。
- 不具備業務隨機性:有意/無意多抽取或覆蓋特定數據場景,使得數據明顯趨向于特定分布規律,例如在做社會調查時使用北京市的抽樣數據來代表全國。
- 沒有考慮業務增長性:在成長型公司中,公司的發展不都是呈現線性趨勢的,很多時候會呈現指數趨勢。這時需要根據這種趨勢來使業務滿足不同增長階段的分析需求,而不只是集中于增長爆發區間。
- 沒有考慮數據來源的多樣性:只選擇某一來源的數據做抽樣,使得數據的分布受限于數據源。例如在做各分公司的銷售分析時,僅將北方大區的數據納入其中做抽樣,而忽視了其他大區的數據,其結果必然有所偏頗。
- 業務數據可行性問題:很多時候,由于受到經費、權限、職責等方面的限制,在數據抽樣方面無法按照數據工作要求來執行,此時要根據運營實際情況調整。這點往往被很多數據工作者忽視。
2. 數據抽樣要能滿足數據分析和建模需求
數據抽樣必須兼顧后續的其他數據處理工作,尤其是分析和建模需求。這時需要注意以下幾個方面的問題。
(1)抽樣樣本量的問題
對于大多數數據分析建模而言,數據規模越大,模型擬合結果越準確。但到底如何定義數據量的大小,筆者根據不同類型的數據應用總結為以下幾個維度:
- 以時間為維度分布的,至少包含一個能滿足預測的完整業務周期。例如,做月度銷售預測的,至少包含12個月的數據;做日銷售預測的,至少包含30天的數據,如果一天中包含特定周期,則需要重復多個周期。同時,時間性特征的要充分考慮季節性、波動性、節假日等特殊規律,這些都要盡量包含在抽樣數據中。
- 做預測(包含分類和回歸)分析建模的,需要考慮特征數量和特征值域(非數值型)的分布,通常數據記錄數要同時是特征數量和特征值域的100倍以上。例如數據集有5個特征,假如每個特征有2個值域,那么數據記錄數需要至少在1000(100×5×2)條以上。
- 做關聯規則分析建模的,根據關聯前后項的數量(每個前項或后項可包含多個要關聯的主體,例如品牌+商品+價格關聯),每個主體需要至少1000條數據。例如只做單品銷售關聯,那么單品的銷售記錄需要在1000條以上;如果要同時做單品+品牌的關聯,那么需要至少2000條數據。
- 對于異常檢測類分析建模的,無論是監督式還是非監督式建模,由于異常數據本來就是小概率分布的,因此異常數據記錄一般越多越好。
以上的數據記錄數不是固定的,在實際工作時,如果沒有特定時間要求,筆者一般會選擇一個適中的樣本量做分析,此時應綜合考慮特征數、特征值域分布數、模型算法適應性、建模需求等;如果是面向機器計算的工作項目,一般會選擇盡量多的數據參與計算,而有關算法實時性和效率的問題會讓技術和運維人員配合實現,例如提高服務器配置、擴大分布式集群規模、優化底層程序代碼、使用實時計算的引擎和機制等。
(2)抽樣樣本在不同類別中的分布問題
做分類分析建模問題時,不同類別下的數據樣本需要均衡分布。
抽樣樣本能準確代表全部整體特征:
- 非數值型的特征值域(例如各值頻數相對比例、值域范圍等)分布需要與總體一致。
- 數值型特征的數據分布區間和各個統計量(如均值、方差、偏度等)需要與整體數據分布區間一致。
- 缺失值、異常值、重復值等特殊數據的分布要與整體數據分布一致。
異常檢測類數據的處理:
- 對于異常檢測類的應用要包含全部異常樣本。對于異常檢測類的分析建模,本來異常數據就非常稀少,因此抽樣時要優先將異常數據包含進去。
- 對于需要去除非業務因素的數據異常,如果有類別特征需要與類別特征分布一致;如果沒有類別特征,屬于非監督式的學習,則需要與整體分布一致。
04 代碼實操:Python數據抽樣
本示例中,將使用random包以及自定義代碼實現抽樣處理。數據源文件data2.txt、data3.txt和data4.txt位于“附件-chapter3”中。
整個示例代碼分為5部分。
第1部分:導入需要的庫
import random # 導入標準庫 import numpy as np # 導入第三方庫
這里用到了Python內置標準庫random以及第三方庫Numpy,前者用于做隨機抽樣,后者用于讀取文件并做數據切片使用。
第2部分:實現簡單隨機抽樣
data = np.loadtxt('data3.txt') # 導入普通數據文件 data_sample = data[random.sample([i for i in range(len(data))], 2000)] # 隨機抽取2000個樣本 print(data_sample[:2]) # 打印輸出前2條數據 print(len(data_sample)) # 打印輸出抽樣樣本量
首先通過Numpy的loadtxt方法讀取數據文件。
然后使用Random庫中的sample方法做數據抽樣。由于sample庫要求抽取的對象是一個序列或set,因此這里使用了一個列表推導式直接基于data數據集的記錄數生成索引列表,然后再返回給sample隨機抽樣,抽樣數量為2000;最后從data中直接基于索引獲得隨機抽樣后的結果。
打印輸出前2條數據和總抽樣樣本量。返回結果如下:
[[-4.59501348 8.82741653 4.40096599 3.40332532 -6.54589933] [-7.23173404 -8.92692519 6.82830873 3.0378005 4.64450399]] 2000
- 相關知識點:Python中的列表推導式
本示例中,我們使用了列表推導式來生成data的索引列表。傳統方法的實現可以這樣寫:
ind = [] for i in range(len(data)): ind.Append(i)
而這里的列表推導式的寫法[i for i in range(len(data))]除了在語法上更加簡潔和優雅外,在性能上同樣會有提升。我們通過如下實驗做簡單測試,對從0到1000000的每個數求平方然后添加到列表。兩種方法如下:
# 方法1:傳統方法 import time t0=time.time() # 開始時間 ind = [] for i in range(1000000): sqr_values = i*i ind.append(sqr_values) t1 = time.time() # 結束時間 print(t1-t0) # 打印時間 # 方法2:列表推導式 import time t0=time.time() # 開始時間 sqr_values = [i*i for i in range(1000000)] t1 = time.time() # 結束時間 print(t1-t0) # 打印時間
上述代碼執行后的輸出結果分別是:
0.39202237129211426 0.12700724601745605
上面只是簡單的計算邏輯并且數據量也不大,如果配合大數據量以及更復雜的運算,那么效率提升會非常明顯。與之類似的還有生成器表達式、字典推導式,都是很Pythonic的實現方法。
第3部分:實現等距抽樣
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[int(i * width)]) # 新增樣本 i += 1 # 自增長 print(data_sample[:2]) # 打印輸出前2條數據 print(len(data_sample)) # 打印輸出樣本數量
首先使用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
第4部分:實現分層抽樣
data2 = np.loadtxt('data2.txt') # 導入帶有分層邏輯的數據 each_sample_count = 200 # 定義每個分層的抽樣數量 label_data_unique = np.unique(data2[:, -1]) # 定義分層值域 sample_data = [] # 定義空列表,用于存放最終抽樣數據 sample_dict = {} # 定義空字典,用來顯示各分層樣本數量 for label_data in label_data_unique: # 遍歷每個分層標簽 sample_list = [] # 定義空列表,用于存放臨時分層數據 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) # 打印輸出樣本集統計結果
首先使用Numpy的loadtxt方法導入帶有分層邏輯的數據。在該示例中,讀取的數據文件中包含了分類標簽,放在最后一列。該列分類標簽用于做分層抽樣的標識。接著通過unique方法獲取分層(分類標簽)的值域,用于后續做循環處理。然后分別定義了用于存放臨時分層數據、最終抽樣數據、顯示各分層樣本數量的空列表和空字典。
下面進入正式的主循環過程,實現分層抽樣:
- 遍歷每個分層標簽,用來做數據的分層劃分,數據一共分為2類標簽(0和1)。
- 讀取每條數據并判斷數據的分層標簽是否與分層標簽相同,如果是則將數據加入各分層數據列表中。
- 當每個分層標簽處理完成后會得到該分層標簽下的所有數據,此時使用Python內置的random庫的sample方法進行抽樣。由于抽樣結果是一個列表,因此這里使用extend(而不是append)批量追加到最終抽樣數據列表中。然后將每個分層標簽得到的樣本數量,通過len方法對列表長度進行統計,并打印輸出各個分層對應的樣本數量。結果是每個分層都按照指定數量抽取樣本,輸出如下:
{0.0: 200, 1.0: 200}
第5部分:實現整群抽樣
data3 = np.loadtxt('data4.txt') # 導入已經劃分好整群的數據集 label_data_unique = np.unique(data3[:, -1]) # 定義整群標簽值域 print(label_data_unique) # 打印輸出所有整群標簽 sample_label = random.sample(set(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)) # 打印輸出總抽樣數據記錄條數
首先使用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條件語句的使用,尤其是單條件和多條件判斷。
關于作者:宋天龍,深大數據技術專家,觸脈咨詢合伙人兼副總裁,前Webtrekk中國區技術和咨詢負責人(德國最大在線數據分析服務提供商)。擅長數據挖掘、建模、分析與運營,精通端到端數據價值場景設計、業務需求轉換、數據結構梳理、數據建模與學習以及數據工程交付。在電子商務、零售、銀行、保險等多個行業擁有豐富的數據項目工作經驗。