Python中處理缺失值的有效方法詳解
01 引言
在 Python 開發(fā)中,我們常常會遇到需要表示“缺失值”的場景。無論是處理 API 返回的數(shù)據(jù)、解析用戶輸入,還是管理緩存狀態(tài),開發(fā)者們的第一反應往往是使用 None。然而,隨著代碼規(guī)模的增長和業(yè)務邏輯的復雜化,None 的濫用卻可能悄無聲息地埋下隱患。
比如在這樣一個場景:你正在編寫一個用戶信息處理函數(shù),當用戶未提供郵箱時,你希望回退到一個默認地址。于是,你寫下了這樣的代碼:
def send_notification(to=None): if to is None: to = "default@example.com" # 發(fā)送郵件...
表面上,這段代碼邏輯清晰,但問題在于,None 在這里承載了多重含義:它可能表示用戶確實沒有提供郵箱,也可能是某個中間步驟尚未初始化數(shù)據(jù),甚至可能是開發(fā)者故意傳遞的合法值。這種語義上的模糊性,會在后續(xù)維護中逐漸顯現(xiàn)出它的破壞力——比如,當另一個開發(fā)者試圖區(qū)分“未設置郵箱”和“主動取消訂閱”時,None 根本無法提供足夠的信息。
更糟糕的是,None 與 Python 中其他“假值”(如空字符串""、數(shù)字 0、布爾值 False)的行為高度相似。一個本應檢查數(shù)據(jù)是否存在的條件判斷,可能因為某個意外傳入的 0 而錯誤地執(zhí)行了回退邏輯。這類問題在調(diào)試時尤其棘手,因為日志中只會顯示一個孤零零的 None,而無法告訴你它究竟代表什么。
這就是為什么我們需要更好的工具。與其依賴None
這一模糊的占位符,Python 開發(fā)者可以通過**自定義哨兵對象(Sentinel Objects)**來明確表達意圖。哨兵對象是獨一無二的實例,專門用于標記“缺失”或“未初始化”狀態(tài),既避免了與合法值的沖突,又能讓代碼邏輯一目了然。接下來的內(nèi)容,我們將深入探討如何用哨兵對象重構(gòu)代碼,從而讓缺失值的處理變得更安全、更可維護。
02 None 的局限性
2.1 一個值,多重含義
None 最常見的濫用場景之一,就是被迫承擔多種不同的含義。例如,在初始化一個對象屬性時,開發(fā)者可能會這樣寫:
class UserProfile: def __init__(self): self.cache = None # 表示"稍后填充"
這里的 None 僅僅表示“緩存尚未加載”,但同一段代碼的其他部分可能會誤以為 None 代表“緩存已被清空”或“緩存不可用”。這種模糊性使得代碼的可讀性下降,尤其是在團隊協(xié)作時,不同的開發(fā)者可能會對 None 的含義做出不同的假設。
更復雜的情況出現(xiàn)在 API 或數(shù)據(jù)處理中。假設我們有一個函數(shù),負責解析用戶的訂閱狀態(tài):
def get_subscription_status(user): status = user.get("subscription", None) if status is None: return "inactive" # 是用戶未訂閱,還是數(shù)據(jù)缺失?
此時,None 可能代表兩種完全不同的情況:
- 數(shù)據(jù)缺失(用戶記錄中沒有 subscription 字段);
- 顯式取消(用戶主動退訂,字段被設為 None)。 如果業(yè)務邏輯要求區(qū)分這兩種情況,None 顯然無法勝任。
2.2 與 Python 假值的沖突
None 的另一個問題在于,它和 Python 中的其他“假值”(falsy values)行為相似,容易導致意外的邏輯錯誤。例如:
def process_value(value): if not value: # 不僅檢查None,還會過濾0、""、False等 value = default_value
2.3 難以追溯的空值來源
當系統(tǒng)出現(xiàn)問題時,日志中的 None 往往無法提供足夠的上下文。例如,在數(shù)據(jù)處理流水線中,某個字段突然變成 None,開發(fā)者需要排查:
- 是上游數(shù)據(jù)源遺漏了這個字段?
- 是某個中間步驟顯式清空了它?
- 還是代碼邏輯錯誤地覆蓋了原有值?
如果使用自定義哨兵,就能在日志中清晰區(qū)分不同的空值狀態(tài),大幅縮短調(diào)試時間。
03 哨兵對象解決方案
在認識到 None 的種種局限性后,我們需要一種更精確、更安全的替代方案。哨兵對象(Sentinel Objects)正是為此而生,它通過創(chuàng)建一個獨特的、不可混淆的對象實例,為缺失值提供了明確的語義表達。
哨兵對象最簡單的實現(xiàn)方式就是創(chuàng)建一個普通的 object 實例:
MISSING = object()
由于 Python 中每個 object()都會生成一個全新的唯一標識,MISSING 對象不會與任何其他值產(chǎn)生沖突。在實際使用時,我們可以清晰地表達意圖:
def get_config_value(key): value = config.get(key, MISSING) if value is MISSING: raise ConfigError(f"Missing required config: {key}")
這種方式的優(yōu)勢顯而易見:首先,它完全避免了與 None、False、0 等其他“假值”的混淆;其次,代碼的意圖變得極其明確 - 我們不是在檢查某個值是否為 None,而是在確認這個配置項是否真的存在。
為了使哨兵對象在調(diào)試和日志記錄時更加友好,我們可以進一步優(yōu)化其實現(xiàn):
class _Missing: def __repr__(self): return "<MISSING>" MISSING = _Missing()
這個增強版本在打印或記錄日志時,會顯示有意義的<MISSING>
標識,而不是默認的 object 表示形式。
我們還可以創(chuàng)建哨兵家族:
class Sentinel: def __init__(self, name): self.name = name def __repr__(self): return f"<{self.name}>" MISSING = Sentinel("MISSING") UNSET = Sentinel("UNSET") DELETED = Sentinel("DELETED")
Python 內(nèi)置的 Ellipsis 對象(...)也可以作為輕量級的哨兵值使用:
def process_data(data=...): if data is ...: data = load_default_data()
Ellipsis 作為哨兵有其獨特優(yōu)勢:它是 Python 內(nèi)置的單例對象,內(nèi)存占用極?。辉陬愋吞崾局幸灿刑囟ㄓ猛?,因此對類型檢查器友好。不過需要注意的是,過度使用 Ellipsis 可能會降低代碼可讀性,建議在團隊內(nèi)部達成明確的使用約定。
04 方法補充
python實現(xiàn)數(shù)據(jù)集缺失值處理
1. 直接刪除
當缺失值的個數(shù)只占整體很小一部分的時候,可直接刪除缺失值。但是如果缺失值占比上升,這種缺失值處理方法誤差就很大了。在采用刪除法處理缺失值時,需要首先檢測樣本總體中缺失值的個數(shù)。python中統(tǒng)計缺失值的方法如下:
import numpy as np import pandas as pd data = pd.read_csv('data.csv',encoding='GBK') # 將空值形式的缺失值轉(zhuǎn)換成可識別的類型 data = data.replace(' ', np.NaN) print(data.columns)#['id', 'label', 'a', 'b', 'c', 'd'] #將每列中缺失值的個數(shù)統(tǒng)計出來 null_all = data.isnull().sum() #id 0 #label 0 #a 7 #b 3 #c 3 #d 8 #查看a列有缺失值的數(shù)據(jù) a_null = data[pd.isnull(data['a'])] #a列缺失占比 a_ratio = len(data[pd.isnull(data['a'])])/len(data) #0.0007 #丟棄缺失值,將存在缺失值的行丟失 new_drop = data.dropna(axis=0) print(new_drop.shape)#(9981,6) #丟棄某幾列有缺失值的行 new_drop2 = data.dropna(axis=0, subset=['a','b']) print(new_drop2.shape)#(9990,6)
上述數(shù)據(jù)缺失值較少,可直接刪除。注意,在計算缺失值時,對于缺失值不是NaN的要用replace()函數(shù)替換成NaN格式,否則pd.isnull()檢測不出來。
2.使用一個全局常量填充缺失值
可以用一個常數(shù)('Unknow’或者負無限大)來填充缺失值。但是如果缺失值較多,都用’Unknow’來填充的話,數(shù)據(jù)挖掘程序會覺得’Unknow’是一個有趣的概念。該方法很簡單,但十分不可靠。python實現(xiàn)如下:
#用0填充缺失值 fill_data = data.fillna('Unknow') print(fill_data.isnull().sum()) #out id 0 label 0 a 0 b 0 c 0 d 0
3.均值、眾數(shù)、中位數(shù)填充
根據(jù)樣本之間的相似性填補缺失值是指用這些缺失值最可能的值來填補它們,通常使用能代表變量中心趨勢的值進行填補,代表變量中心趨勢的指標包括平均值、中位數(shù)、眾數(shù)等,那么我們采用哪些指標來填補缺失值呢?
分布類型 | 填充值 | 原因 |
---|---|---|
近正態(tài)分布 | 平均值 | 所有觀測值都較好地聚集在平均值周圍 |
偏態(tài)分布 | 中位數(shù) | 偏態(tài)分布的大部分值都聚集在變量分布的一側(cè),中位數(shù)是更好地代表數(shù)據(jù)中心趨勢的指標 |
有離群點的分布 | 中位數(shù) | 中位數(shù)是更好地代表數(shù)據(jù)中心趨勢的指標 |
名義變量 | 眾數(shù) | 名義變量無大小、順序之分,不能加減乘除。如性別 |
python實現(xiàn)如下:
#均值填充 data['a'] = data['a'].fillna(data['a'].mean()) #中位數(shù)填充 data['a'] = data['a'].fillna(data['a'].median()) #眾數(shù)填充 data['a'] = data['a'].fillna(stats.mode(data['a'])[0][0]) #用前一個數(shù)據(jù)進行填充 data['a'] = data['a'].fillna(method='pad') #用后一個數(shù)據(jù)進行填充 data['a'] = data['a'].fillna(method='bfill')
Imputer提供了缺失數(shù)值處理的基本策略,比如使用缺失數(shù)值所在行或列的均值、中位數(shù)、眾數(shù)來替代缺失值。
from sklearn.preprocessing import Imputer imr = Imputer(missing_values='NaN', strategy='mean', axis=0) imr = imr.fit(data.values) imputed_data = pd.DataFrame(imr.transform(data.values)) print(imputed_data[0:15])
參數(shù) | 描述 |
---|---|
missing_values | int或’NaN’,默認NaN(String類型) |
strategy | mean,默認平均值填補;可選,median(中位數(shù)),most_frequent(眾數(shù)) |
axis | 指定軸向。axis=0,列向(默認);axis=1,行向 |
verbose | int默認值為0 |
copy | 默認True:創(chuàng)建數(shù)據(jù)集的副本;False:在任何地方都可進行插值 |
4. 插值法、KNN填充
插值法
interpolate()插值法,計算的是缺失值前一個值和后一個值的平均數(shù)。
data['a'] = data['a'].interpolate()
KNN填充
from fancyimpute import KNN fill_knn = KNN(k=3).fit_transform(data) data = pd.DataFrame(fill_knn) print(data.head()) #out 0 1 2 3 4 5 0 111.0 0.0 2.0 360.0 4.000000 1.0 1 112.0 1.0 9.0 1080.0 3.000000 1.0 2 113.0 1.0 9.0 1080.0 2.000000 1.0 3 114.0 0.0 1.0 360.0 *3.862873 *1.0 4 115.0 0.0 1.0 270.0 5.000000 1.0
5.隨機森林填充
from sklearn.ensemble import RandomForestRegressor #提取已有的數(shù)據(jù)特征 process_df = data.ix[:, [1, 2, 3, 4, 5]] # 分成已知該特征和未知該特征兩部分 known = process_df[process_df.c.notnull()].as_matrix() uknown = process_df[process_df.c.isnull()].as_matrix() # X為特征屬性值 X = known[:, 1:3] # print(X[0:10]) # Y為結(jié)果標簽 y = known[:, 0] print(y) # 訓練模型 rf = RandomForestRegressor(random_state=0, n_estimators=200, max_depth=3, n_jobs=-1) rf.fit(X, y) # 預測缺失值 predicted = rf.predict(uknown[:, 1:3]) print(predicted) #將預測值填補原缺失值 data.loc[(data.c.isnull()), 'c'] = predicted print(data[0:10])
到此這篇關于Python中處理缺失值的有效方法詳解的文章就介紹到這了,更多相關Python處理缺失值內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
django admin 自定義替換change頁面模板的方法
今天小編就為大家分享一篇django admin 自定義替換change頁面模板的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08Python實現(xiàn)監(jiān)控遠程主機實時數(shù)據(jù)的示例詳解
這篇文章主要為大家詳細介紹了Python如何使用Socket庫和相應的第三方庫來監(jiān)控遠程主機的實時數(shù)據(jù),比如CPU使用率、內(nèi)存使用率、網(wǎng)絡帶寬等,感興趣的可以了解一下2023-04-04Django 導出項目依賴庫到 requirements.txt過程解析
這篇文章主要介紹了Django 導出項目依賴庫到 requirements.txt過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-08-08解決pycharm remote deployment 配置的問題
今天小編就為大家分享一篇解決pycharm remote deployment 配置的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-06-06Python使用BeautifulSoup進行XPath和CSS選擇器定位
在 Python 中,BeautifulSoup 是一個常用的 HTML 和 XML 解析庫,它允許我們輕松地定位和提取網(wǎng)頁中的特定元素,本文將詳細介紹如何在 BeautifulSoup 中使用 XPath 和 CSS 選擇器定位 HTML 元素,并提供示例代碼以幫助新手理解這些概念,需要的朋友可以參考下2024-11-11Python通用驗證碼識別OCR庫之ddddocr驗證碼識別
dddd_ocr也是一個用于識別驗證碼的開源庫,又名帶帶弟弟ocr,爬蟲界大佬sml2h3開發(fā),識別效果也是非常不錯,下面這篇文章主要給大家介紹了關于Python通用驗證碼識別OCR庫之ddddocr驗證碼識別的相關資料,需要的朋友可以參考下2022-05-05