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