教學中的數(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值將會保留。