4.處理缺失數(shù)據(jù)

教學中的數(shù)據(jù)和實際數(shù)據(jù)的區(qū)別在于陌粹,實際數(shù)據(jù)很少是干凈整齊的。許多有趣的數(shù)據(jù)集都有某種程度上的數(shù)據(jù)缺失福压。更糟糕的是掏秩,不同的數(shù)據(jù)源數(shù)據(jù)丟失的方式也不同。
本章荆姆,我們將會探討對缺失數(shù)據(jù)的一般性考慮蒙幻,討論Pandas是怎樣選擇表達的,我們也會演示幾種用來處理缺失數(shù)據(jù)的Pandas內(nèi)置工具胆筒。在整本書中邮破,我們一般將缺失數(shù)據(jù)稱為NULL、NA或NA值仆救。

對缺失數(shù)據(jù)約定的權(quán)衡

對于在表格和DataFrame中缺失數(shù)據(jù)的表示有好幾種方案抒和。通常情況下,它們都是基于兩種策略:使用一個用于全局指示缺失值的掩碼彤蔽,或者選擇一個表示缺失條目的哨兵值摧莽。
使用掩碼方法,掩碼可能是一個獨立完整的布爾數(shù)組顿痪,或者在數(shù)據(jù)表達中引入一個適當?shù)腷it位來局部的指示空值狀態(tài)镊辕。
使用哨兵方法,哨兵值可以是一些約定好的特殊數(shù)值员魏,比如為表示一個缺失的整型數(shù)值丑蛤,可以使用-9999或少見的bit模式,也可以是更全局性的約定撕阎,例如使用NaN來表示一缺失的浮點型數(shù)值受裹,NaN是IEEE浮點規(guī)范中的一個特殊值。
所有的方法都需要一些權(quán)衡:使用獨立的掩碼數(shù)組需要分配額外的布爾數(shù)組空間,這將對儲存和計算都增加負擔棉饶。哨兵值減少了可以有效表達的數(shù)據(jù)范圍厦章,也可能在CPU和GPU的計算時需要另外的邏輯。像NaN這樣普通的特殊值并不是對所有數(shù)據(jù)類型都適用照藻。
在大多數(shù)情況下袜啃,并沒有一個普遍的最優(yōu)選擇存在,不同的語言和系統(tǒng)使用不同的約定幸缕。例如R語言在每種數(shù)據(jù)類型里面使用保留的bit模式來表明缺失的數(shù)據(jù)群发,而SciDB系統(tǒng)對每個單位附加額外的字節(jié)來指示缺失狀態(tài)。

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

Pandas處理缺失數(shù)據(jù)的方法受到它所依賴的NumPy包的約束发乔,NumPy里面對非浮點型數(shù)據(jù)并沒有內(nèi)置的缺失值表達熟妓。
Pandas可以采用R語言的bit模式來標記各種數(shù)據(jù)類型的空值,但這種方法被證明是相當?shù)牟环奖憷干小語言只包含四種基本數(shù)據(jù)類型起愈,而NumPy支持的數(shù)據(jù)類型要多得多:例如,R有一個但整型類型译仗,但考慮到可用精度抬虽,符號性和字節(jié)的編碼順序,NumPy支持基本整型類型多達14種纵菌。為所有可用的NumPy類型保留一個bit模式阐污,將會導致大量的負擔來處理不同類型的不同操作,很有可能需要NumPy包引出一個新的分支來支持产艾。另外疤剑,對于小規(guī)模的數(shù)據(jù)類型(如8位的整數(shù)),犧牲其中一個bit作為掩碼闷堡,將會顯著的減少它所能表示的數(shù)據(jù)范圍隘膘。
NumPy也不支持掩碼數(shù)組,就是那種用來表示是好數(shù)據(jù)還是壞數(shù)據(jù)的獨立布爾數(shù)據(jù)杠览。Pandas可以繼承這些弯菊,但是存儲空間,計算和代碼維護的額外代價使這種方法不使一個吸引人的選項踱阿。
考慮到這些限制管钳,Pandas選擇使用哨兵值來表示缺失數(shù)據(jù),并且選用了兩種Python里面已經(jīng)存在的空值:特殊的浮點值NaN和Python的None對象软舌。如我們將看到的才漆,這種選擇有一些副作用,但實際上它是在考慮到各種利益情況下的一個較好的折中佛点。

None:Python式的缺失數(shù)據(jù)

Pandas使用的第一哨兵值是None,一個通常用在Python代碼種表示缺失的單態(tài)對象醇滥。因為它是一個Python對象黎比,None不能用在任何專屬的NumPy/Pandas數(shù)組中,而只能用在對象類型位‘object’的數(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ù)組中可以推斷出最通用的數(shù)據(jù)類型是Python對象鸳玩。盡管這種對象數(shù)組在某些情況下有用阅虫,但任何數(shù)據(jù)操作都是在Python層面的,這種操作比對原生類型的快速操作要慢很多不跟。

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'

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

另一個缺失數(shù)據(jù)表示,NaN窝革,就有些不同购城;它是一個能夠被所有使用IEEE浮點表達式系統(tǒng)識別的特殊的浮點值。

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

注意NumPy為這個數(shù)組選擇一個原生的浮點類型:這意味著與之前的的對象數(shù)組不同虐译,這個浮點數(shù)組支持在編譯代碼中的快速操作工猜。你可能意識到NaN有點像數(shù)據(jù)病毒--它能感染任何它所接觸的對象。無論是什么操作菱蔬,與NaN計算的結(jié)果都是NaN:

1 + np.nan
nan
0 *  np.nan
nan

注意,這意味著對那些數(shù)據(jù)的聚合操作都是有效的(它們不會產(chǎn)生錯誤)史侣,只是沒什么用處:

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

NumPy提供了一些特殊的聚合方法來忽略那些缺失的數(shù)據(jù):

np.nansum(vals2), np.nanmin(vals2), np.nanmax(vals2)
(8.0, 1.0, 4.0)

記住NaN是一個特殊的浮點值拴泌;對于整型,字符串和其他類型并沒有對應的NaN值:

Pandas中的NaN和None

NaN和None在Pandas中都有使用惊橱,但對它們的處理被設計成幾乎可以互換蚪腐,在合適的情況下對它們進行轉(zhuǎn)換:

pd.Series([1, np.nan, 2, None])
0    1.0
1    NaN
2    2.0
3    NaN
dtype: float64

對于那些沒有哨兵值的類型,但空值出現(xiàn)時税朴,Pandas會自動的進行類型轉(zhuǎn)換回季。例如,我們給一個整型數(shù)組中的值設置為np.nan正林,整個數(shù)組將會自動的向上轉(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ù)組轉(zhuǎn)換為浮點型泡一,Pandas也自動的把None轉(zhuǎn)換為NaN值
這種轉(zhuǎn)換與特定語言(如R語言)的統(tǒng)一的方法比起來看上去不那么優(yōu)雅,但在實際的使用過程中觅廓,Pandas的哨兵/轉(zhuǎn)換方法工作的相當好鼻忠,很少導致問題。
下表列出了Pandas中當出現(xiàn)NA值時的轉(zhuǎn)型約定:

Typeclass Conversion When Storing NAs NA Sentinel Value
floating No change np.nan
object No change None or np.nan
integer Cast to float64 np.nan
boolean Cast to object None or np.nan

記住杈绸,在Pandas上帖蔓,字符串數(shù)據(jù)總時被存儲為對象類型。

空值操作

如我們看到的瞳脓,Pandas把None和NaN在表示缺失或空值時當成時基本上可互換的塑娇。為了簡化這種約定,有幾個有用的方法用于檢測劫侧,移除以及替換Pandas數(shù)據(jù)結(jié)構(gòu)中的空值埋酬。它們是:

  • isnull(): 生成布爾掩碼用來指示缺失的數(shù)據(jù)
  • notnull(): isnull()的反操作
  • dropna(): 返回數(shù)據(jù)過濾后的版本
  • fillna(): 返回缺失數(shù)據(jù)被填充后的數(shù)據(jù)拷貝

我們將對這些函數(shù)進行簡要探索和示范,然后結(jié)束這一部分。

空值檢測

Pandas數(shù)據(jù)結(jié)構(gòu)有兩個有用的方法用于檢測空值:isnull()和notnull()奇瘦。兩者都在數(shù)據(jù)上返回布爾掩碼棘催。例如:

data = pd.Series([1, np.nan, 'hello', None])
data.isnull()
0    False
1     True
2    False
3     True
dtype: bool

我們在 Data Indexing and Selection 提到過,布爾掩碼也可以直接被用作Series或FataFrame的索引:

data[data.notnull()]
0        1
2    hello
dtype: object

isnull()和notnull()方法對于DataFrame產(chǎn)生相似的布爾結(jié)果耳标。

去掉空值

除了之前用過的掩碼手段醇坝,還有很方便的函數(shù),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中的單個值宋距;我們只能去掉整行或整列。在不同的應用環(huán)境症脂,你可能想用不同的方式谚赎,所以dropna()為DataFrame提供了許多選項。
默認情況下诱篷,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值的行或列闸盔,或者大部分是NA值的行或列×帐。可以通過指定參數(shù)how或者thresh來精確的控制允許的空值數(shù)目迎吵。
how的默認值是‘a(chǎn)ny’,因此任何包含空值的行或列都會被去掉针贬。你可以指定how=‘a(chǎn)ll’击费,這樣就只是會去掉全部是空值的行/列:

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

為了細粒度的控制,thresh參數(shù)允許你規(guī)定可以保留行/列所需要的最少非空數(shù)據(jù)數(shù)目:

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

第一行和最后一行被去掉了桦他,因為它們只包含兩個非空數(shù)據(jù)荡灾。

填充空值

有時候與其去掉NA值,我們寧愿把它們換成有效的值瞬铸。這個值可能是像0那樣的單個數(shù)字批幌,或者有效值的插值∩そ冢可以通過使用isnull()方法作為過濾條件來原地替換荧缘,但是因為這個操作很常用,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

我們可以用單個數(shù)值如0來替換空值:

data.fillna(0)
a    1.0
b    0.0
c    2.0
d    0.0
e    3.0
dtype: float64

我們可以指定前值填充方法來使用空值前面的數(shù)據(jù)作為替換:

# forward-fill
data.fillna(method='ffill')
a    1.0
b    1.0
c    2.0
d    2.0
e    3.0
dtype: float64

或者我們可以指定后值填充方法使用空值后面的值作為替換值:

# back-fill
data.fillna(method='bfill')
a    1.0
b    2.0
c    2.0
d    3.0
e    3.0
dtype: float64

對于DataFrames信姓,這些選項是類似的,但我們也可以指定發(fā)生填充的軸向:

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閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異磅崭,居然都是意外死亡儿子,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進店門砸喻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柔逼,“玉大人,你說我怎么就攤上這事割岛∽菔粒” “怎么了递递?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵宣增,是天一觀的道長土匀。 經(jīng)常有香客問我帚湘,道長檬姥,這世上最難降的妖魔是什么俏拱? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任埃篓,我火速辦了婚禮雷恃,結(jié)果婚禮上疆股,老公的妹妹穿的比我還像新娘。我一直安慰自己倒槐,他們只是感情好旬痹,可當我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著讨越,像睡著了一般两残。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上把跨,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天人弓,我揣著相機與錄音,去河邊找鬼着逐。 笑死崔赌,一個胖子當著我的面吹牛意蛀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播健芭,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼县钥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了慈迈?” 一聲冷哼從身側(cè)響起若贮,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吩翻,沒想到半個月后兜看,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡狭瞎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年细移,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熊锭。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡弧轧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出碗殷,到底是詐尸還是另有隱情精绎,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布锌妻,位于F島的核電站代乃,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏仿粹。R本人自食惡果不足惜搁吓,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吭历。 院中可真熱鬧堕仔,春花似錦、人聲如沸晌区。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽朗若。三九已至恼五,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哭懈,已是汗流浹背唤冈。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留银伟,地道東北人你虹。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓绘搞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親傅物。 傳聞我的和親對象是個殘疾皇子夯辖,可洞房花燭夜當晚...
    茶點故事閱讀 45,107評論 2 356

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