當前位置:
首頁 > 知識 > Python中NaN和None 的詳細比較

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]:

Python中NaN和None 的詳細比較



兩邊的NaN會被判為相同


In[47]:


a=DataFrame({ A :[NaN, a ]})


b=DataFrame({ A :[NaN, a ]})


a.merge(b,on= A ,how= outer )


Out[47]:

Python中NaN和None 的詳細比較



無論兩邊都是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]:

Python中NaN和None 的詳細比較



In[49]:


a=DataFrame({ A :[NaN, a ]})


b=DataFrame({ A :[None, a ]})


a.merge(b,on= A ,how= outer )


Out[49]:

Python中NaN和None 的詳細比較



注意


這和空值在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表示被判定為相等)

Python中NaN和None 的詳細比較



由於等值性比較方面,None和NaN在各場景下表現不太一致,相對來說None表現的更穩定。


為了不給自己惹不必要的麻煩和額外的記憶負擔。 實踐中,建議遵循以下三個原則即可


在用pandas和numpy處理數據階段將None,NaN統一處理成NaN,以便支持更多的函數。


如果要判斷Series,numpy.array整體的等值性,用專門的Series.equals,numpy.array函數去處理,不要自己用==判斷 *


如果要將數據導入資料庫,將NaN替換成None


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python 的精彩文章:

手把手教你如何用 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!