Python中NaN和None 的詳細比較
來源:Exolution
junjiecai.github.io/posts/2016/Oct/20/none_vs_nan/
python原生的None和pandas, numpy中的numpy.NaN儘管在功能上都是用來標示空缺數據。但它們的行為在很多場景下確有一些相當大的差異。由於不熟悉這些差異,曾經給我的工作帶來過不少麻煩。特此整理了一份詳細的實驗,比較None和NaN在不同場景下的差異。
實驗的結果有些在意料之內,有些則讓我大跌眼鏡。希望讀者看過此文後會None和NaN這對「小妖精」有更深的理解。
為了理解本文的內容,希望本文的讀者需要對pandas的Series使用有一定的經驗。
首先,導入所需的庫
In[2]:
fromnumpyimportNaN
frompandasimportSeries,DataFrame
importnumpyasnp
數據類型?
None是一個python特殊的數據類型, 但是NaN卻是用一個特殊的float
In[3]:
type(None)
Out[3]:
NoneType
In[4]:
type(NaN)
Out[4]:
float
能作為dict的key?
In[5]:
Out[5]:
In[6]:
Out[6]:
In[7]:
Out[7]:
都可以,而且會被認為是不同的key
Series函數中的表現
Series.map
In[8]:
s=Series([None,NaN, a ])
s
Out[8]:
None
1NaN
2a
dtype:object
In[9]:
s.map()
Out[9]:
1
11
2a
dtype:object
可以看到None和NaN都會替換成了1
In[10]:
s.map()
Out[10]:
1
11
2a
dtype:object
同樣None和NaN都會替換成了1
In[11]:
s.map()
Out[11]:
2
12
2a
dtype:object
將None替換成1的要求被忽略了
In[12]:
s.map({ None :2,NaN:1, a : a })
Out[12]:
2
12
2a
dtype:object
將NaN替換成1的要求被忽略了
總結: 用Series.map對None進行替換時,會「順便」把NaN也一起替換掉;NaN也會順便把None替換掉。
如果None和NaN分別定義了不同的映射數值,那麼只有一個會生效。
Series.replace中的表現
In[13]:
s=Series([None,NaN, a ])
s
Out[13]:
None
1NaN
2a
dtype:object
In[14]:
s.replace([NaN],9)
Out[14]:
9
19
2a
dtype:object
In[15]:
s.replace([None],9)
Out[15]:
9
19
2a
dtype:object
和Series.map的情況類似,指定了None的替換值後,NaN會被替換掉;反之亦然。
對函數的支持
numpy有不少函數可以自動處理NaN。
In[16]:
np.nansum([1,2,NaN])
Out[16]:
3.0
但是None不能享受這些函數的便利,如果數據包含的None的話會報錯
In[17]:
try:
np.nansum([1,2,None])
exceptExceptionase:
print(type(e),e)
unsupported operand type(s) for +: 『int』 and 『NoneType』
pandas中也有不少函數支持NaN卻不支持None。(畢竟pandas的底層是numpy)
In[18]:
importpandasaspd
pd.cut(Series([NaN]),[1,2])
Out[18]:
NaN
dtype:category
Categories(1,object):[(1,2]]
In[19]:
importpandasaspd
try:
pd.cut(Series([None]),[1,2])
exceptExceptionase:
print(type(e),e)
unorderable types: int() > NoneType()
對容器數據類型的影響
混入numpy.array的影響
如果數據中含有None,會導致整個array的類型變成object。
In[20]:
np.array([1, None]).dtype
Out[20]:
dtype( O )
而np.NaN儘管會將原本用int類型就能保存的數據轉型成float,但不會帶來上面這個問題。
In[21]:
np.array([1, NaN]).dtype
Out[21]:
dtype( float64 )
混入Series的影響
下面的結果估計大家能猜到
In[22]:
1.0
1NaN
dtype:float64
下面的這個就很意外的吧
In[23]:
Series([1, None])
Out[23]:
1.0
1NaN
dtype:float64
pandas將None自動替換成了NaN!
In[24]:
Series([1.0, None])
Out[24]:
1.0
1NaN
dtype:float64
卻是Object類型的None被替換成了float類型的NaN。 這麼設計可能是因為None無法參與numpy的大多數計算, 而pandas的底層又依賴於numpy,因此做了這樣的自動轉化。
不過如果本來Series就只能用object類型容納的話, Series不會做這樣的轉化工作。
In[25]:
Series([ a , None])
Out[25]:
a
1None
dtype:object
如果Series裡面都是None的話也不會做這樣的轉化
In[26]:
Series([None,None])
Out[26]:
None
1None
dtype:object
其它的數據類型是bool時,也不會做這樣的轉化。
In[27]:
Series([True, False, None])
Out[27]:
True
1False
2None
dtype:object
等值性判斷
單值的等值性比較
下面的實驗中None和NaN的表現會作為後面的等值性判斷的基準(後文稱為基準)
In[28]:
None == None
Out[28]:
True
In[29]:
NaN == NaN
Out[29]:
False
In[30]:
None == NaN
Out[30]:
False
在tuple中的情況
這個不奇怪
In[31]:
(1, None) == (1, None)
Out[31]:
True
這個也不意外
In[32]:
(1, None) == (1, NaN)
Out[32]:
False
但是下面這個實驗NaN的表現和基準不一致
In[33]:
(1, NaN) == (1, NaN)
Out[33]:
True
在numpy.array中的情況
In[34]:
np.array([1,None]) == np.array([1,None])
Out[34]:
array([ True, True], dtype=bool)
In[35]:
np.array([1,NaN]) == np.array([1,NaN])
Out[35]:
array([ True, False], dtype=bool)
In[36]:
np.array([1,NaN]) == np.array([1,None])
Out[36]:
array([ True, False], dtype=bool)
和基準的表現一致。
In[37]:
它也可以處理兩邊都是None的情況
In[38]:
但是一邊是None,一邊是NaN時會被認為兩邊不一致, 導致AssertionError
In[39]:
try:
exceptExceptionase:
print(type(e),e)
Arrays arenotequal
(mismatch50.0%)
x:array([1.,nan])
y:array([1,None],dtype=object)
在Series中的情況
下面兩個實驗中的表現和基準一致
In[40]:
Series([NaN, a ]) == Series([NaN, a ])
Out[40]:
False
1True
dtype:bool
In[41]:
Series([None, a ]) == Series([NaN, a ])
Out[41]:
False
1True
dtype:bool
但是None和基準的表現不一致。
In[42]:
Series([None, a ]) == Series([None, a ])
Out[42]:
False
1True
dtype:bool
和array類似,Series也有專門的函數equals用於判斷兩邊的Series是否整體看相等
In[43]:
Series([None, a ]).equals(Series([NaN, a ]))
Out[43]:
True
In[44]:
Series([None, a ]).equals(Series([None, a ]))
Out[44]:
True
In[45]:
Series([NaN, a ]).equals(Series([NaN, a ]))
Out[45]:
True
在DataFrame merge中的表現
兩邊的None會被判為相同
In[46]:
a=DataFrame({ A :[None, a ]})
b=DataFrame({ A :[None, a ]})
a.merge(b,on= A ,how= outer )
Out[46]:
兩邊的NaN會被判為相同
In[47]:
a=DataFrame({ A :[NaN, a ]})
b=DataFrame({ A :[NaN, a ]})
a.merge(b,on= A ,how= outer )
Out[47]:
無論兩邊都是None,都是NaN,還是都有,相關的列都會被正確的匹配。 注意一邊是None,一邊是NaN的時候。會以左側的結果為準。
In[48]:
a=DataFrame({ A :[None, a ]})
b=DataFrame({ A :[NaN, a ]})
a.merge(b,on= A ,how= outer )
Out[48]:
In[49]:
a=DataFrame({ A :[NaN, a ]})
b=DataFrame({ A :[None, a ]})
a.merge(b,on= A ,how= outer )
Out[49]:
注意
這和空值在postgresql等sql資料庫中的表現不一樣, 在資料庫中, join時兩邊的空值會被判定為不同的數值
在groupby中的表現
In[50]:
d=DataFrame({ A :[1,1,1,1,2], B :[None,None, a , a , b ]})
d.groupby([ A , B ]).apply(len)
Out[50]:
AB
1a2
2b1
dtype:int64
可以看到(1, NaN)對應的組直接被忽略了
In[51]:
d=DataFrame({ A :[1,1,1,1,2], B :[None,None, a , a , b ]})
d.groupby([ A , B ]).apply(len)
Out[51]:
AB
1a2
2b1
dtype:int64
(1,None)的組也被直接忽略了
In[52]:
d=DataFrame({ A :[1,1,1,1,2], B :[None,NaN, a , a , b ]})
d.groupby([ A , B ]).apply(len)
Out[52]:
AB
1a2
2b1
dtype:int64
那麼上面這個結果應該沒啥意外的
總結
DataFrame.groupby會忽略分組列中含有None或者NaN的記錄
支持寫入資料庫?
往資料庫中寫入時NaN不可處理,需轉換成None,否則會報錯。這個這裡就不演示了。
相信作為pandas老司機, 至少能想出兩種替換方法。
In[53]:
s=Series([None,NaN, a ])
s
Out[53]:
None
1NaN
2a
dtype:object
方案1
In[54]:
s.replace([NaN],None)
Out[54]:
None
1None
2a
dtype:object
方案2
In[55]:
s[s.isnull()]=None
s
Out[55]:
None
1None
2a
dtype:object
然而這麼就覺得完事大吉的話就圖樣圖森破了, 看下面的例子
In[56]:
s=Series([NaN,1])
s
Out[56]:
NaN
11.0
dtype:float64
In[57]:
s.replace([NaN], None)
Out[57]:
NaN
11.0
dtype:float64
In[58]:
s[s.isnull()]=None
s
Out[58]:
NaN
11.0
dtype:float64
當其他數據是int或float時,Series又一聲不吭的自動把None替換成了NaN。
這時候可以使用第三種方法處理
In[59]:
s.where(s.notnull(), None)
Out[59]:
None
11
dtype:object
where語句會遍歷s中所有的元素,逐一檢查條件表達式, 如果成立, 從原來的s取元素; 否則用None填充。 這回沒有自動替換成NaN
None vs NaN要點總結
在pandas中, 如果其他的數據都是數值類型, pandas會把None自動替換成NaN, 甚至能將s[s.isnull()]= None,和s.replace(NaN, None)操作的效果無效化。 這時需要用where函數才能進行替換。
None能夠直接被導入資料庫作為空值處理, 包含NaN的數據導入時會報錯。
numpy和pandas的很多函數能處理NaN,但是如果遇到None就會報錯。
None和NaN都不能被pandas的groupby函數處理,包含None或者NaN的組都會被忽略。
等值性比較的總結:(True表示被判定為相等)
由於等值性比較方面,None和NaN在各場景下表現不太一致,相對來說None表現的更穩定。
為了不給自己惹不必要的麻煩和額外的記憶負擔。 實踐中,建議遵循以下三個原則即可
在用pandas和numpy處理數據階段將None,NaN統一處理成NaN,以便支持更多的函數。
如果要判斷Series,numpy.array整體的等值性,用專門的Series.equals,numpy.array函數去處理,不要自己用==判斷 *
如果要將數據導入資料庫,將NaN替換成None
※手把手教你如何用 OpenCV+Python 實現人臉識別
※Python 自然語言處理入門
※我與支付寶之間的那些事:基於Python與Face++實現人臉識別
※Python代碼覆蓋性測試入門
※Python基礎新手學習需要注意的技術問題總結
TAG:Python |
※Python NLP庫top6的介紹和比較
※Numba和Cython如何加速Python代碼
※Python中使用Type hinting 和 annotations
※Python中的lambda函數
※Python 中的字典—Python 基礎
※將Python用於NLP:Pattern 庫簡介
※Python 2與Python 3 的區別
※1.5 讓VS Code擁有Python的Shell——「系統終端+IPython」
※Ubuntu下系統在帶Python和conda中安裝的Python共存問題
※Python中Scikit-Learn庫的分類方法總覽
※Python3的range比Python2的xrange功能更強大
※Python-GUI Tkinter模塊
※在Python中使用Elasticsearch
※Python 的 ChatOps 庫:Opsdroid 和 Errbot
※拒絕 Python、C 和 Go,我只用 Node.js!
※Python的for循環
※從Scratch到Python
※對 Python 開發者而言,IPython 仍然是 Jupyter Notebook 的核心
※PPython:PHP 擁抱 Python 的利器
※拒絕 Python、C#和Go,我只用 Node.js!