數(shù)據(jù)科學(xué) IPython 筆記本 7.7 處理缺失數(shù)據(jù)

7.7 處理缺失數(shù)據(jù)

原文:Handling Missing Data

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

本節(jié)是《Python 數(shù)據(jù)科學(xué)手冊》(Python Data Science Handbook)的摘錄析孽。

許多教程中的數(shù)據(jù)與現(xiàn)實世界中的數(shù)據(jù)之間的差異在于埠褪,真實世界的數(shù)據(jù)很少是干凈和同構(gòu)的睛约。特別是怔锌,許多有趣的數(shù)據(jù)集缺少一些數(shù)據(jù)氯材。為了使事情變得更復(fù)雜兆沙,不同的數(shù)據(jù)源可能以不同的方式標(biāo)記缺失數(shù)據(jù)荣挨。

在本節(jié)中男韧,我們將討論缺失數(shù)據(jù)的一些一般注意事項,討論 Pandas 如何選擇來表示它默垄,并演示一些處理 Python 中的缺失數(shù)據(jù)的 Pandas 內(nèi)置工具此虑。在整本書中,我們將缺失數(shù)據(jù)稱為空值或NaN值口锭。

缺失數(shù)據(jù)慣例中的權(quán)衡

許多方案已經(jīng)開發(fā)出來朦前,來指示表格或DataFrame中是否存在缺失數(shù)據(jù)。通常鹃操,它們圍繞兩種策略中的一種:使用在全局表示缺失值的掩碼韭寸,或選擇表示缺失條目的標(biāo)記值。

在掩碼方法中荆隘,掩碼可以是完全獨立的布爾數(shù)組恩伺,或者它可以在數(shù)據(jù)表示中占用一個比特,在本地表示值的空狀態(tài)椰拒。

在標(biāo)記方法中晶渠,標(biāo)記值可能是某些特定于數(shù)據(jù)的慣例凰荚,例如例如使用-9999或某些少見的位組合來表示缺失整數(shù)值,或者它可能是更全局的慣例褒脯,例如使用NaN(非數(shù)字)表示缺失浮點值便瑟,這是一個特殊值,它是 IEEE 浮點規(guī)范的一部分憨颠。

這些方法都沒有權(quán)衡:使用單獨的掩碼數(shù)組需要分配額外的布爾數(shù)組胳徽,這會增加存儲和計算的開銷。標(biāo)記值減少了可以表示的有效值的范圍爽彤,并且可能需要 CPU 和 GPU 算法中的額外(通常是非最優(yōu)的)邏輯养盗。 像NaN這樣的常見特殊值不適用于所有數(shù)據(jù)類型。

在大多數(shù)情況下适篙,不存在普遍最佳選擇往核,不同的語言和系統(tǒng)使用不同的慣例。例如嚷节,R 語言使用每種數(shù)據(jù)類型中的保留位組合聂儒,作為表示缺失數(shù)據(jù)的標(biāo)記值,而 SciDB 系統(tǒng)使用表示 NA 狀態(tài)的額外字節(jié)硫痰,附加到每個單元衩婚。

Pandas 中的缺失數(shù)據(jù)

Pandas 處理缺失值的方式受到其對 NumPy 包的依賴性的限制,NumPy 包沒有非浮點數(shù)據(jù)類型的 NA 值的內(nèi)置概念效斑。

Pandas 可以遵循 R 的指導(dǎo)非春,為每個單獨的數(shù)據(jù)類型指定位組合來表示缺失值,但這種方法結(jié)果相當(dāng)笨拙缓屠。雖然 R 包含四種基本數(shù)據(jù)類型奇昙,但 NumPy 支持更多:例如,R 具有單個整數(shù)類型敌完,但是一旦考慮到編碼的可用精度储耐,簽名和字節(jié)順序,NumPy 支持十四個基本整數(shù)類型滨溉。

在所有可用的 NumPy 類型中保留特定的位組合什湘,將產(chǎn)生各種類型的各種操作的大量開銷,甚至可能需要 NumPy 包的新分支业踏。 此外禽炬,對于較小的數(shù)據(jù)類型(例如 8 位整數(shù)),犧牲一個位用作掩碼勤家,將顯著減小它可以表示的值的范圍腹尖。

NumPy 確實支持掩碼數(shù)組嗎?也就是說,附加了一個獨立的布爾掩碼數(shù)組的數(shù)組热幔,用于將數(shù)據(jù)標(biāo)記為“好”或“壞”乐设。Pandas 可能源于此,但是存儲绎巨,計算和代碼維護(hù)的開銷近尚,使得這個選擇變得沒有吸引力。

考慮到這些約束场勤,Pandas 選擇使用標(biāo)記來丟失數(shù)據(jù)戈锻,并進(jìn)一步選擇使用兩個已經(jīng)存在的 Python 空值:特殊浮點值NaN和 Python None對象。我們將要看到和媳,這種選擇有一些副作用格遭,但實際上在大多數(shù)相關(guān)情況下,最終都是很好的妥協(xié)留瞳。

None:Python 風(fēng)格的缺失數(shù)據(jù)

Pandas 使用的第一個標(biāo)記值是None拒迅,這是一個 Python 單例對象,通常用于 Python 代碼中的缺失數(shù)據(jù)她倘。因為它是一個 Python 對象璧微,所以None不能用于任何 NumPy/Pandas 數(shù)組,只能用于數(shù)據(jù)類型為'object'的數(shù)組(即 Python 對象數(shù)組):

import numpy as np
import pandas as pd

vals1 = np.array([1, None, 3, 4])
vals1

# array([1, None, 3, 4], dtype=object)

這個dtype = object意味著硬梁,它是最好的公共類型表示前硫。NumPy 可以推斷出,數(shù)組的內(nèi)容是 Python 對象荧止。雖然這種對象數(shù)組對于某些目的很有用开瞭,但是對數(shù)據(jù)的任何操作都將在 Python 層面完成,與具有原生類型的數(shù)組的常見快速操作相比罩息,其開銷要大得多:

for dtype in ['object', 'int']:
    print("dtype =", dtype)
    %timeit np.arange(1E6, dtype=dtype).sum()
    print()
    
'''
dtype = object
10 loops, best of 3: 78.2 ms per loop

dtype = int
100 loops, best of 3: 3.06 ms per loop
'''

在數(shù)組中使用 Python 對象也意味著,如果你在一個帶有None值的數(shù)組中執(zhí)行sum()min()之類的聚合个扰,你通常會得到錯誤:

vals1.sum()

'''
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-4-749fd8ae6030> in <module>()
----> 1 vals1.sum()


/Users/jakevdp/anaconda/lib/python3.5/site-packages/numpy/core/_methods.py in _sum(a, axis, dtype, out, keepdims)
     30 
     31 def _sum(a, axis=None, dtype=None, out=None, keepdims=False):
---> 32     return umr_sum(a, axis, dtype, out, keepdims)
     33 
     34 def _prod(a, axis=None, dtype=None, out=None, keepdims=False):


TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
'''

這反映了一個事實瓷炮,即整數(shù)和None之間的加法是未定義的。

NaN:缺失的數(shù)值數(shù)據(jù)

另一個缺失的數(shù)據(jù)表示递宅,NaN(“非數(shù)字”的首字母縮寫)是不同的娘香;它是所有系統(tǒng)都識別的特殊浮點值,使用標(biāo)準(zhǔn) IEEE 浮點表示:

vals2 = np.array([1, np.nan, 3, 4]) 
vals2.dtype

# dtype('float64')

請注意办龄,NumPy 為此數(shù)組選擇了一個原生浮點類型:這意味著與之前的對象數(shù)組不同烘绽,此數(shù)組支持推送到編譯代碼中的快速操作。你應(yīng)該知道NaN有點像數(shù)據(jù)病毒 - 它會感染它觸及的任何其他對象俐填。無論操作如何安接,NaN的算術(shù)結(jié)果都是另一個NaN

1 + np.nan

# nan

0 *  np.nan

# nan

請注意,這意味著值的聚合是定義良好的(即英融,它們不會導(dǎo)致錯誤)盏檐,但并不總是有用:

vals2.sum(), vals2.min(), vals2.max()

# (nan, nan, nan)

NumPy 確實提供了一些忽略這些缺失值的特殊聚合:

np.nansum(vals2), np.nanmin(vals2), np.nanmax(vals2)

# (8.0, 1.0, 4.0)

請記住歇式,NaN是一個特殊浮點值;整數(shù)胡野,字符串或其他類型沒有等效的NaN值材失。

Pandas 中的NaNNone

NaNNone都有它們的位置,并且 Pandas 的構(gòu)建是為了幾乎可以互換地處理這兩個值硫豆,在適當(dāng)?shù)臅r候在它們之間進(jìn)行轉(zhuǎn)換:

pd.Series([1, np.nan, 2, None])

'''
0    1.0
1    NaN
2    2.0
3    NaN
dtype: float64
'''

對于沒有可用標(biāo)記值的類型龙巨,當(dāng)存在 NA 值時,Pandas 會自動進(jìn)行類型轉(zhuǎn)換熊响。例如旨别,如果我們將整數(shù)數(shù)組中的值設(shè)置為np.nan,它將自動向上轉(zhuǎn)換為浮點類型來兼容 NA:

x = pd.Series(range(2), dtype=int)
x

'''
0    0
1    1
dtype: int64
'''

x[0] = None
x

'''
0    NaN
1    1.0
dtype: float64
'''

請注意耘眨,除了將整數(shù)數(shù)組轉(zhuǎn)換為浮點數(shù)外昼榛,Pandas 還會自動將None轉(zhuǎn)換為NaN值。(請注意剔难,有人建議未來向 Pandas 添加原生整數(shù) NA胆屿;截至本文撰寫時,尚未包含此內(nèi)容偶宫。)

雖然與 R 等領(lǐng)域特定語言中非迹,更為統(tǒng)一的 NA 值方法相比,這種黑魔法可能會有些笨拙纯趋,但 Pandas 標(biāo)記值方法在實踐中運作良好憎兽,根據(jù)我的經(jīng)驗,很少會產(chǎn)生問題吵冒。

下表列出了引入 NA 值時 Pandas 中的向上轉(zhuǎn)換慣例:

類型 儲存 NA 時的慣例 NA 標(biāo)記值
floating 不變 np.nan
object 不變 Nonenp.nan
integer 轉(zhuǎn)換為float64 np.nan
boolean 轉(zhuǎn)換為object Nonenp.nan

請記住纯命,在 Pandas 中,字符串?dāng)?shù)據(jù)始終與object dtype一起存儲痹栖。

空值上的操作

正如我們所看到的亿汞,Pandas 將NoneNaN視為基本可互換的,用于指示缺失值或空值揪阿。為了促進(jìn)這個慣例疗我,有幾種有用的方法可用于檢測,刪除和替換 Pandas 數(shù)據(jù)結(jié)構(gòu)中的空值南捂。他們是:

  • isnull(): 生成表示缺失值的布爾掩碼
  • notnull(): isnull()的反轉(zhuǎn)
  • dropna(): 返回數(shù)據(jù)的過濾后版本
  • fillna(): 返回數(shù)據(jù)的副本吴裤,填充了缺失值

我們將結(jié)束本節(jié),簡要探討和演示這些例程溺健。

檢測控制

Pandas 數(shù)據(jù)結(jié)構(gòu)有兩種有用的方法來檢測空數(shù)據(jù):isnull()notnull()麦牺。任何一個都返回數(shù)據(jù)上的布爾掩碼。例如:

data = pd.Series([1, np.nan, 'hello', None])

data.isnull()

'''
0    False
1     True
2    False
3     True
dtype: bool
'''

如“數(shù)據(jù)索引和選擇”中所述,布爾掩碼可以直接用作SeriesDataFrame的索引:

data[data.notnull()]

'''
0        1
2    hello
dtype: object
'''

isnull()notnull()方法為DataFrame生成類似的布爾結(jié)果枕面。

刪除空值

除了之前使用的掩碼之外愿卒,還有一些方便的方法,dropna()(刪除 NA 值)和fillna()(填充 NA 值)潮秘。 對于Series琼开,結(jié)果很簡單:

data.dropna()

'''
0        1
2    hello
dtype: object
'''

對于DataFrame,還有更多選項枕荞」窈颍考慮以下DataFrame

df = pd.DataFrame([[1,      np.nan, 2],
                   [2,      3,      5],
                   [np.nan, 4,      6]])
df
0 1 2
0 1.0 NaN 2
1 2.0 3.0 5
2 NaN 4.0 6

我們不能從DataFrame中刪除單個值;我們只能刪除完整行或完整列躏精。取決于應(yīng)用渣刷,你可能需要其中一個,因此dropna()DataFrame提供了許多選項矗烛。

默認(rèn)情況下辅柴,dropna()將刪除包含空值的所有行:

df.dropna()
0 1 2
1 2.0 3.0 5

或者,你可以沿不同的軸刪除 NA 值; axis = 1刪除包含空值的所有列:

df.dropna(axis='columns')
2
0 2
1 5
2 6

但這也會丟掉一些好的數(shù)據(jù); 你可能更愿意刪除全部為 NA 值或大多數(shù)為 NA 值的行或列瞭吃。這可以通過howthresh參數(shù)來指定碌嘀,這些參數(shù)能夠精確控制允許通過的空值數(shù)量。

默認(rèn)值是how ='any'歪架,這樣任何包含空值的行或列(取決于axis關(guān)鍵字)都將被刪除股冗。你也可以指定how ='all',它只會丟棄全部為空值的行/列:

df[3] = np.nan
df
0 1 2 3
0 1.0 NaN 2 NaN
1 2.0 3.0 5 NaN
2 NaN 4.0 6 NaN
df.dropna(axis='columns', how='all')
0 1 2
0 1.0 NaN 2
1 2.0 3.0 5
2 NaN 4.0 6

對于更細(xì)粒度的控制和蚪,thresh參數(shù)允許你為要保留的行/列指定最小數(shù)量的非空值:

df.dropna(axis='rows', thresh=3)
0 1 2 3
1 2.0 3.0 5 NaN

這里刪除了第一行和最后一行止状,因為它們只包含兩個非空值。

填充空值

有時比起刪除 NA 值攒霹,你寧愿用有效值替換它們怯疤。這個值可能是單個數(shù)字,如零催束,或者可能是某種良好的替換或插值旅薄。你可以將isnull()方法用作掩碼,原地執(zhí)行此操作泣崩,但因為它是如此常見的操作,Pandas 提供fillna()方法洛口,該方法返回數(shù)組的副本矫付,其中空值已替換。

考慮下面的Series:

data = pd.Series([1, np.nan, 2, None, 3], index=list('abcde'))
data

'''
a    1.0
b    NaN
c    2.0
d    NaN
e    3.0
dtype: float64
'''

我們可以使用單個值填充 NA 條目第焰,例如零:

data.fillna(0)

'''
a    1.0
b    0.0
c    2.0
d    0.0
e    3.0
dtype: float64
'''

我們可以指定前向填充來傳播前一個值:

# 向前填充
data.fillna(method='ffill')

'''
a    1.0
b    1.0
c    2.0
d    2.0
e    3.0
dtype: float64
'''

或者我們可以指定反向填充买优,來向后傳播下一個值:

# 向后填充
data.fillna(method='bfill')

'''
a    1.0
b    2.0
c    2.0
d    3.0
e    3.0
dtype: float64
'''

對于DataFrame,選項也類似,但我們也可以指定axis杀赢,沿著該軸進(jìn)行填充:

df
0 1 2 3
0 1.0 NaN 2 NaN
1 2.0 3.0 5 NaN
2 NaN 4.0 6 NaN
df.fillna(method='ffill', axis=1)
0 1 2 3
0 1.0 1.0 2.0 2.0
1 2.0 3.0 5.0 5.0
2 NaN 4.0 6.0 6.0

請注意烘跺,如果在前向填充期間前一個值不可用,則 NA 值仍然存在脂崔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末滤淳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子砌左,更是在濱河造成了極大的恐慌脖咐,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汇歹,死亡現(xiàn)場離奇詭異屁擅,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)产弹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門派歌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人痰哨,你說我怎么就攤上這事胶果。” “怎么了作谭?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵稽物,是天一觀的道長。 經(jīng)常有香客問我折欠,道長贝或,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任锐秦,我火速辦了婚禮咪奖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘酱床。我一直安慰自己羊赵,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布扇谣。 她就那樣靜靜地躺著昧捷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪罐寨。 梳的紋絲不亂的頭發(fā)上靡挥,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機(jī)與錄音鸯绿,去河邊找鬼跋破。 笑死簸淀,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的毒返。 我是一名探鬼主播租幕,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拧簸!你這毒婦竟也來了劲绪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤狡恬,失蹤者是張志新(化名)和其女友劉穎珠叔,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弟劲,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡祷安,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了兔乞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汇鞭。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖庸追,靈堂內(nèi)的尸體忽然破棺而出霍骄,到底是詐尸還是另有隱情,我是刑警寧澤淡溯,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布读整,位于F島的核電站,受9級特大地震影響咱娶,放射性物質(zhì)發(fā)生泄漏米间。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一膘侮、第九天 我趴在偏房一處隱蔽的房頂上張望屈糊。 院中可真熱鬧,春花似錦琼了、人聲如沸逻锐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽昧诱。三九已至,卻和暖如春所袁,著一層夾襖步出監(jiān)牢的瞬間盏档,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工纲熏, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留妆丘,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓局劲,卻偏偏與公主長得像勺拣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鱼填,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容