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

