python原生的None和pandas, numpy中的numpy.NaN盡管在功能上都是用來標示空缺數據。但它們的行為在很多場景下確有一些相當大的差異。由於不熟悉這些差異,曾經給我的工作帶來過不少麻煩。 特此整理了一份詳細的實驗,比較None和NaN在不同場景下的差異。
實驗的結果有些在意料之內,有些則讓我大跌眼鏡。希望讀者看過此文后會None和NaN這對“小妖精”有更深的理解。
為了理解本文的內容,希望本文的讀者需要對pandas的Series使用有一定的經驗。
首先,導入所需的庫
In[2]:
|
1
2
3
|
from
numpy
import
NaN
from
pandas
import
Series
,
DataFrame
import
numpy
as
np
|
數據類型?
None是一個python特殊的數據類型, 但是NaN卻是用一個特殊的float
In[3]:
|
1
|
type
(
None
)
|
Out[3]:
|
1
|
NoneType
|
In[4]:
|
1
|
type
(
NaN
)
|
Out[4]:
|
1
|
float
|
能作為dict的key?
In[5]:
|
1
|
{
None
:
1
}
|
Out[5]:
|
1
|
{
None
:
1
}
|
In[6]:
|
1
|
{
NaN
:
1
}
|
Out[6]:
|
1
|
{
nan
:
1
}
|
In[7]:
|
1
|
{
None
:
1
,
NaN
:
2
}
|
Out[7]:
|
1
|
{
nan
:
2
,
None
:
1
}
|
都可以,而且會被認為是不同的key
Series函數中的表現
Series.map
In[8]:
|
1
2
|
s
=
Series
(
[
None
,
NaN
,
'a'
]
)
s
|
Out[8]:
|
1
2
3
4
|
0
None
1
NaN
2
a
dtype
:
object
|
In[9]:
|
1
|
s
.
map
(
{
None
:
1
,
'a'
:
'a'
}
)
|
Out[9]:
|
1
2
3
4
|
0
1
1
1
2
a
dtype
:
object
|
可以看到None和NaN都會替換成了1
In[10]:
|
1
|
s
.
map
(
{
NaN
:
1
,
'a'
:
'a'
}
)
|
Out[10]:
|
1
2
3
4
|
0
1
1
1
2
a
dtype
:
object
|
同樣None和NaN都會替換成了1
In[11]:
|
1
|
s
.
map
(
{
NaN
:
2
,
'None'
:
1
,
'a'
:
'a'
}
)
|
Out[11]:
|
1
2
3
4
|
0
2
1
2
2
a
dtype
:
object
|
將None替換成1的要求被忽略了
In[12]:
|
1
|
s
.
map
(
{
'None'
:
1
,
NaN
:
2
,
'a'
:
'a'
}
)
|
Out[12]:
|
1
2
3
4
|
0
2
1
2
2
a
dtype
:
object
|
將NaN替換成1的要求被忽略了
總結: 用Series.map對None進行替換時,會“順便”把NaN也一起替換掉;NaN也會順便把None替換掉。
如果None和NaN分別定義了不同的映射數值,那么只有一個會生效。
Series.replace中的表現
In[13]:
|
1
2
|
s
=
Series
(
[
None
,
NaN
,
'a'
]
)
s
|
Out[13]:
|
1
2
3
4
|
0
None
1
NaN
2
a
dtype
:
object
|
In[14]:
|
1
|
s
.
replace
(
[
NaN
]
,
9
)
|
Out[14]:
|
1
2
3
4
|
0
9
1
9
2
a
dtype
:
object
|
In[15]:
|
1
|
s
.
replace
(
[
None
]
,
9
)
|
Out[15]:
|
1
2
3
4
|
0
9
1
9
2
a
dtype
:
object
|
和Series.map的情況類似,指定了None的替換值后,NaN會被替換掉;反之亦然。
對函數的支持
numpy有不少函數可以自動處理NaN。
In[16]:
|
1
|
np
.
nansum
(
[
1
,
2
,
NaN
]
)
|
Out[16]:
|
1
|
3.0
|
但是None不能享受這些函數的便利,如果數據包含的None的話會報錯
In[17]:
|
1
2
3
4
|
try
:
np
.
nansum
(
[
1
,
2
,
None
]
)
except
Exception
as
e
:
print
(
type
(
e
)
,
e
)
|
unsupported operand type(s) for +: ‘int’ and ‘NoneType’
pandas中也有不少函數支持NaN卻不支持None。(畢竟pandas的底層是numpy)
In[18]:
|
1
2
|
import
pandas
as
pd
pd
.
cut
(
Series
(
[
NaN
]
)
,
[
1
,
2
]
)
|
Out[18]:
|
1
2
3
|
0
NaN
dtype
:
category
Categories
(
1
,
object
)
:
[
(
1
,
2
]
]
|
In[19]:
|
1
2
3
4
5
|
import
pandas
as
pd
try
:
pd
.
cut
(
Series
(
[
None
]
)
,
[
1
,
2
]
)
except
Exception
as
e
:
print
(
type
(
e
)
,
e
)
|
unorderable types: int() > NoneType()
對容器數據類型的影響
混入numpy.array的影響
如果數據中含有None,會導致整個array的類型變成object。
In[20]:
|
1
|
np
.
array
(
[
1
,
None
]
)
.
dtype
|
Out[20]:
|
1
|
dtype
(
'O'
)
|
而np.NaN盡管會將原本用int類型就能保存的數據轉型成float,但不會帶來上面這個問題。
In[21]:
|
1
|
np
.
array
(
[
1
,
NaN
]
)
.
dtype
|
Out[21]:
|
1
|
dtype
(
'float64'
)
|
混入Series的影響
下面的結果估計大家能猜到
In[22]:
|
1
|
Series
(
[
1
,
NaN
]
)
|
Out[22]:
|
1
2
3
|
0
1.0
1
NaN
dtype
:
float64
|
下面的這個就很意外的吧
In[23]:
|
1
|
Series
(
[
1
,
None
]
)
|
Out[23]:
|
1
2
3
|
0
1.0
1
NaN
dtype
:
float64
|
pandas將None自動替換成了NaN!
In[24]:
|
1
|
Series
(
[
1.0
,
None
]
)
|
Out[24]:
|
1
2
3
|
0
1.0
1
NaN
dtype
:
float64
|
卻是Object類型的None被替換成了float類型的NaN。 這么設計可能是因為None無法參與numpy的大多數計算, 而pandas的底層又依賴於numpy,因此做了這樣的自動轉化。
不過如果本來Series就只能用object類型容納的話, Series不會做這樣的轉化工作。
In[25]:
|
1
|
Series
(
[
'a'
,
None
]
)
|
Out[25]:
|
1
2
3
|
0
a
1
None
dtype
:
object
|
如果Series里面都是None的話也不會做這樣的轉化
In[26]:
|
1
|
Series
(
[
None
,
None
]
)
|
Out[26]:
|
1
2
3
|
0
None
1
None
dtype
:
object
|
其它的數據類型是bool時,也不會做這樣的轉化。
In[27]:
|
1
|
Series
(
[
True
,
False
,
None
]
)
|
Out[27]:
|
1
2
3
4
|
0
True
1
False
2
None
dtype
:
object
|
等值性判斷
單值的等值性比較
下面的實驗中None和NaN的表現會作為后面的等值性判斷的基准(后文稱為基准)
In[28]:
|
1
|
None
==
None
|
Out[28]:
|
1
|
True
|
In[29]:
|
1
|
NaN
==
NaN
|
Out[29]:
|
1
|
False
|
In[30]:
|
1
|
None
==
NaN
|
Out[30]:
|
1
|
False
|
在tuple中的情況
這個不奇怪
In[31]:
|
1
|
(
1
,
None
)
==
(
1
,
None
)
|
Out[31]:
|
1
|
True
|
這個也不意外
In[32]:
|
1
|
(
1
,
None
)
==
(
1
,
NaN
)
|
Out[32]:
|
1
|
False
|
但是下面這個實驗NaN的表現和基准不一致
In[33]:
|
1
|
(
1
,
NaN
)
==
(
1
,
NaN
)
|
Out[33]:
|
1
|
True
|
在numpy.array中的情況
In[34]:
|
1
|
np
.
array
(
[
1
,
None
]
)
==
np
.
array
(
[
1
,
None
]
)
|
Out[34]:
|
1
|
array
(
[
True
,
True
]
,
dtype
=
bool
)
|
In[35]:
|
1
|
np
.
array
(
[
1
,
NaN
]
)
==
np
.
array
(
[
1
,
NaN
]
)
|
Out[35]:
|
1
|
array
(
[
True
,
False
]
,
dtype
=
bool
)
|
In[36]:
|
1
|
np
.
array
(
[
1
,
NaN
]
)
==
np
.
array
(
[
1
,
None
]
)
|
Out[36]:
|
1
|
array
(
[
True
,
False
]
,
dtype
=
bool
)
|
和基准的表現一致。
但是大部分情況我們希望上面例子中, 我們希望左右兩邊的array被判定成一致。這時可以用numpy.testing.assert_equal函數來處理。 注意這個函數的表現同assert, 不會返回True, False, 而是無反應或者raise Exception
In[37]:
|
1
|
np
.
testing
.
assert_equal
(
np
.
array
(
[
1
,
NaN
]
)
,
np
.
array
(
[
1
,
NaN
]
)
)
|
它也可以處理兩邊都是None的情況
In[38]:
|
1
|
np
.
testing
.
assert_equal
(
np
.
array
(
[
1
,
None
]
)
,
np
.
array
(
[
1
,
None
]
)
)
|
但是一邊是None,一邊是NaN時會被認為兩邊不一致, 導致AssertionError
In[39]:
|
1
2
3
4
|
try
:
np
.
testing
.
assert_equal
(
np
.
array
(
[
1
,
NaN
]
)
,
np
.
array
(
[
1
,
None
]
)
)
except
Exception
as
e
:
print
(
type
(
e
)
,
e
)
|
|
1
2
3
4
5
6
|
&
lt
;
class
'assertionerror'
=
""
&
gt
;
Arrays
are
not
equal
(
mismatch
50.0
%
)
x
:
array
(
[
1.
,
nan
]
)
y
:
array
(
[
1
,
None
]
,
dtype
=
object
)
|
在Series中的情況
下面兩個實驗中的表現和基准一致
In[40]:
|
1
|
Series
(
[
NaN
,
'a'
]
)
==
Series
(
[
NaN
,
'a'
]
)
|
Out[40]:
|
1
2
3
|
0
False
1
True
dtype
:
bool
|
In[41]:
|
1
|
Series
(
[
None
,
'a'
]
)
==
Series
(
[
NaN
,
'a'
]
)
|
Out[41]:
|
1
2
3
|
0
False
1
True
dtype
:
bool
|
但是None和基准的表現不一致。
In[42]:
|
1
|
Series
(
[
None
,
'a'
]
)
==
Series
(
[
None
,
'a'
]
)
|
Out[42]:
|
1
2
3
|
0
False
1
True
dtype
:
bool
|
和array類似,Series也有專門的函數equals用於判斷兩邊的Series是否整體看相等
In[43]:
|
1
|
Series
(
[
None
,
'a'
]
)
.
equals
(
Series
(
[
NaN
,
'a'
]
)
)
|
Out[43]:
|
1
|
True
|
In[44]:
|
1
|
Series
(
[
None
,
'a'
]
)
.
equals
(
Series
(
[
None
,
'a'
]
)
)
|
Out[44]:
|
1
|
True
|
In[45]:
|
1
|
Series
(
[
NaN
,
'a'
]
)
.
equals
(
Series
(
[
NaN
,
'a'
]
)
)
|
Out[45]:
|
1
|
True
|
比numpy.testing.assert_equals更智能些, 三種情況下都能恰當的處理
在DataFrame merge中的表現
兩邊的None會被判為相同
In[46]:
|
1
2
3
|
a
=
DataFrame
(
{
'A'
:
[
None
,
'a'
]
}
)
b
=
DataFrame
(
{
'A'
:
[
None
,
'a'
]
}
)
a
.
merge
(
b
,
on
=
'A'
,
how
=
'outer'
)
|
Out[46]:
| A | |
|---|---|
| 0 | None |
| 1 | a |
兩邊的NaN會被判為相同
In[47]:
|
1
2
3
|
a
=
DataFrame
(
{
'A'
:
[
NaN
,
'a'
]
}
)
b
=
DataFrame
(
{
'A'
:
[
NaN
,
'a'
]
}
)
a
.
merge
(
b
,
on
=
'A'
,
how
=
'outer'
)
|
Out[47]:
| A | |
|---|---|
| 0 | NaN |
| 1 | a |
無論兩邊都是None,都是NaN,還是都有,相關的列都會被正確的匹配。 注意一邊是None,一邊是NaN的時候。會以左側的結果為准。
In[48]:
|
1
2
3
|
a
=
DataFrame
(
{
'A'
:
[
None
,
'a'
]
}
)
b
=
DataFrame
(
{
'A'
:
[
NaN
,
'a'
]
}
)
a
.
merge
(
b
,
on
=
'A'
,
how
=
'outer'
)
|
Out[48]:
| A | |
|---|---|
| 0 | None |
| 1 | a |
In[49]:
|
1
2
3
|
a
=
DataFrame
(
{
'A'
:
[
NaN
,
'a'
]
}
)
b
=
DataFrame
(
{
'A'
:
[
None
,
'a'
]
}
)
a
.
merge
(
b
,
on
=
'A'
,
how
=
'outer'
)
|
Out[49]:
| A | |
|---|---|
| 0 | NaN |
| 1 | a |
注意
這和空值在postgresql等sql數據庫中的表現不一樣, 在數據庫中, join時兩邊的空值會被判定為不同的數值
在groupby中的表現
In[50]:
|
1
2
|
d
=
DataFrame
(
{
'A'
:
[
1
,
1
,
1
,
1
,
2
]
,
'B'
:
[
None
,
None
,
'a'
,
'a'
,
'b'
]
}
)
d
.
groupby
(
[
'A'
,
'B'
]
)
.
apply
(
len
)
|
Out[50]:
|
1
2
3
4
|
A
B
1
a
2
2
b
1
dtype
:
int64
|
可以看到(1, NaN)對應的組直接被忽略了
In[51]:
|
1
2
|
d
=
DataFrame
(
{
'A'
:
[
1
,
1
,
1
,
1
,
2
]
,
'B'
:
[
None
,
None
,
'a'
,
'a'
,
'b'
]
}
)
d
.
groupby
(
[
'A'
,
'B'
]
)
.
apply
(
len
)
|
Out[51]:
|
1
2
3
4
|
A
B
1
a
2
2
b
1
dtype
:
int64
|
(1,None)的組也被直接忽略了
In[52]:
|
1
2
|
d
=
DataFrame
(
{
'A'
:
[
1
,
1
,
1
,
1
,
2
]
,
'B'
:
[
None
,
NaN
,
'a'
,
'a'
,
'b'
]
}
)
d
.
groupby
(
[
'A'
,
'B'
]
)
.
apply
(
len
)
|
Out[52]:
|
1
2
3
4
|
A
B
1
a
2
2
b
1
dtype
:
int64
|
那么上面這個結果應該沒啥意外的
總結
DataFrame.groupby會忽略分組列中含有None或者NaN的記錄
支持寫入數據庫?
往數據庫中寫入時NaN不可處理,需轉換成None,否則會報錯。這個這里就不演示了。
相信作為pandas老司機, 至少能想出兩種替換方法。
In[53]:
|
1
2
|
s
=
Series
(
[
None
,
NaN
,
'a'
]
)
s
|
Out[53]:
|
1
2
3
4
|
0
None
1
NaN
2
a
dtype
:
object
|
方案1
In[54]:
|
1
|
s
.
replace
(
[
NaN
]
,
None
)
|
Out[54]:
|
1
2
3
4
|
0
None
1
None
2
a
dtype
:
object
|
方案2
In[55]:
|
1
2
|
s
[
s
.
isnull
(
)
]
=
None
s
|
Out[55]:
|
1
2
3
4
|
0
None
1
None
2
a
dtype
:
object
|
然而這么就覺得完事大吉的話就圖樣圖森破了, 看下面的例子
In[56]:
|
1
2
|
s
=
Series
(
[
NaN
,
1
]
)
s
|
Out[56]:
|
1
2
3
|
0
NaN
1
1.0
dtype
:
float64
|
In[57]:
|
1
|
s
.
replace
(
[
NaN
]
,
None
)
|
Out[57]:
|
1
2
3
|
0
NaN
1
1.0
dtype
:
float64
|
In[58]:
|
1
2
|
s
[
s
.
isnull
(
)
]
=
None
s
|
Out[58]:
|
1
2
3
|
0
NaN
1
1.0
dtype
:
float64
|
當其他數據是int或float時,Series又一聲不吭的自動把None替換成了NaN。
這時候可以使用第三種方法處理
In[59]:
|
1
|
s
.
where
(
s
.
notnull
(
)
,
None
)
|
Out[59]:
|
1
2
3
|
0
None
1
1
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對None | NaN對NaN | None對NaN | |
|---|---|---|---|
| 單值 | True | False | False |
| tuple(整體) | True | True | False |
| np.array(逐個) | True | False | False |
| Series(逐個) | False | False | False |
| assert_equals | True | True | False |
| Series.equals | True | True | True |
| merge | True | True | True |
由於等值性比較方面,None和NaN在各場景下表現不太一致,相對來說None表現的更穩定。
為了不給自己惹不必要的麻煩和額外的記憶負擔。 實踐中,建議遵循以下三個原則即可
- 在用pandas和numpy處理數據階段將None,NaN統一處理成NaN,以便支持更多的函數。
- 如果要判斷Series,numpy.array整體的等值性,用專門的Series.equals,numpy.array函數去處理,不要自己用
==判斷 * - 如果要將數據導入數據庫,將NaN替換成None
轉自:http://python.jobbole.com/87266/
python原生的None和pandas, numpy中的numpy.NaN盡管在功能上都是用來標示空缺數據。但它們的行為在很多場景下確有一些相當大的差異。由於不熟悉這些差異,曾經給我的工作帶來過不少麻煩。 特此整理了一份詳細的實驗,比較None和NaN在不同場景下的差異。
實驗的結果有些在意料之內,有些則讓我大跌眼鏡。希望讀者看過此文后會None和NaN這對“小妖精”有更深的理解。
為了理解本文的內容,希望本文的讀者需要對pandas的Series使用有一定的經驗。
首先,導入所需的庫
In[2]:
|
1
2
3
|
from
numpy
import
NaN
from
pandas
import
Series
,
DataFrame
import
numpy
as
np
|
數據類型?
None是一個python特殊的數據類型, 但是NaN卻是用一個特殊的float
In[3]:
|
1
|
type
(
None
)
|
Out[3]:
|
1
|
NoneType
|
In[4]:
|
1
|
type
(
NaN
)
|
Out[4]:
|
1
|
float
|
能作為dict的key?
In[5]:
|
1
|
{
None
:
1
}
|
Out[5]:
|
1
|
{
None
:
1
}
|
In[6]:
|
1
|
{
NaN
:
1
}
|
Out[6]:
|
1
|
{
nan
:
1
}
|
In[7]:
|
1
|
{
None
:
1
,
NaN
:
2
}
|
Out[7]:
|
1
|
{
nan
:
2
,
None
:
1
}
|
都可以,而且會被認為是不同的key
Series函數中的表現
Series.map
In[8]:
|
1
2
|
s
=
Series
(
[
None
,
NaN
,
'a'
]
)
s
|
Out[8]:
|
1
2
3
4
|
0
None
1
NaN
2
a
dtype
:
object
|
In[9]:
|
1
|
s
.
map
(
{
None
:
1
,
'a'
:
'a'
}
)
|
Out[9]:
|
1
2
3
4
|
0
1
1
1
2
a
dtype
:
object
|
可以看到None和NaN都會替換成了1
In[10]:
|
1
|
s
.
map
(
{
NaN
:
1
,
'a'
:
'a'
}
)
|
Out[10]:
|
1
2
3
4
|
0
1
1
1
2
a
dtype
:
object
|
同樣None和NaN都會替換成了1
In[11]:
|
1
|
s
.
map
(
{
NaN
:
2
,
'None'
:
1
,
'a'
:
'a'
}
)
|
Out[11]:
|
1
2
3
4
|
0
2
1
2
2
a
dtype
:
object
|
將None替換成1的要求被忽略了
In[12]:
|
1
|
s
.
map
(
{
'None'
:
1
,
NaN
:
2
,
'a'
:
'a'
}
)
|
Out[12]:
|
1
2
3
4
|
0
2
1
2
2
a
dtype
:
object
|
將NaN替換成1的要求被忽略了
總結: 用Series.map對None進行替換時,會“順便”把NaN也一起替換掉;NaN也會順便把None替換掉。
如果None和NaN分別定義了不同的映射數值,那么只有一個會生效。
Series.replace中的表現
In[13]:
|
1
2
|
s
=
Series
(
[
None
,
NaN
,
'a'
]
)
s
|
Out[13]:
|
1
2
3
4
|
0
None
1
NaN
2
a
dtype
:
object
|
In[14]:
|
1
|
s
.
replace
(
[
NaN
]
,
9
)
|
Out[14]:
|
1
2
3
4
|
0
9
1
9
2
a
dtype
:
object
|
In[15]:
|
1
|
s
.
replace
(
[
None
]
,
9
)
|
Out[15]:
|
1
2
3
4
|
0
9
1
9
2
a
dtype
:
object
|
和Series.map的情況類似,指定了None的替換值后,NaN會被替換掉;反之亦然。
對函數的支持
numpy有不少函數可以自動處理NaN。
In[16]:
|
1
|
np
.
nansum
(
[
1
,
2
,
NaN
]
)
|
Out[16]:
|
1
|
3.0
|
但是None不能享受這些函數的便利,如果數據包含的None的話會報錯
In[17]:
|
1
2
3
4
|
try
:
np
.
nansum
(
[
1
,
2
,
None
]
)
except
Exception
as
e
:
print
(
type
(
e
)
,
e
)
|
unsupported operand type(s) for +: ‘int’ and ‘NoneType’
pandas中也有不少函數支持NaN卻不支持None。(畢竟pandas的底層是numpy)
In[18]:
|
1
2
|
import
pandas
as
pd
pd
.
cut
(
Series
(
[
NaN
]
)
,
[
1
,
2
]
)
|
Out[18]:
|
1
2
3
|
0
NaN
dtype
:
category
Categories
(
1
,
object
)
:
[
(
1
,
2
]
]
|
In[19]:
|
1
2
3
4
5
|
import
pandas
as
pd
try
:
pd
.
cut
(
Series
(
[
None
]
)
,
[
1
,
2
]
)
except
Exception
as
e
:
print
(
type
(
e
)
,
e
)
|
unorderable types: int() > NoneType()
對容器數據類型的影響
混入numpy.array的影響
如果數據中含有None,會導致整個array的類型變成object。
In[20]:
|
1
|
np
.
array
(
[
1
,
None
]
)
.
dtype
|
Out[20]:
|
1
|
dtype
(
'O'
)
|
而np.NaN盡管會將原本用int類型就能保存的數據轉型成float,但不會帶來上面這個問題。
In[21]:
|
1
|
np
.
array
(
[
1
,
NaN
]
)
.
dtype
|
Out[21]:
|
1
|
dtype
(
'float64'
)
|
混入Series的影響
下面的結果估計大家能猜到
In[22]:
|
1
|
Series
(
[
1
,
NaN
]
)
|
Out[22]:
|
1
2
3
|
0
1.0
1
NaN
dtype
:
float64
|
下面的這個就很意外的吧
In[23]:
|
1
|
Series
(
[
1
,
None
]
)
|
Out[23]:
|
1
2
3
|
0
1.0
1
NaN
dtype
:
float64
|
pandas將None自動替換成了NaN!
In[24]:
|
1
|
Series
(
[
1.0
,
None
]
)
|
Out[24]:
|
1
2
3
|
0
1.0
1
NaN
dtype
:
float64
|
卻是Object類型的None被替換成了float類型的NaN。 這么設計可能是因為None無法參與numpy的大多數計算, 而pandas的底層又依賴於numpy,因此做了這樣的自動轉化。
不過如果本來Series就只能用object類型容納的話, Series不會做這樣的轉化工作。
In[25]:
|
1
|
Series
(
[
'a'
,
None
]
)
|
Out[25]:
|
1
2
3
|
0
a
1
None
dtype
:
object
|
如果Series里面都是None的話也不會做這樣的轉化
In[26]:
|
1
|
Series
(
[
None
,
None
]
)
|
Out[26]:
|
1
2
3
|
0
None
1
None
dtype
:
object
|
其它的數據類型是bool時,也不會做這樣的轉化。
In[27]:
|
1
|
Series
(
[
True
,
False
,
None
]
)
|
Out[27]:
|
1
2
3
4
|
0
True
1
False
2
None
dtype
:
object
|
等值性判斷
單值的等值性比較
下面的實驗中None和NaN的表現會作為后面的等值性判斷的基准(后文稱為基准)
In[28]:
|
1
|
None
==
None
|
Out[28]:
|
1
|
True
|
In[29]:
|
1
|
NaN
==
NaN
|
Out[29]:
|
1
|
False
|
In[30]:
|
1
|
None
==
NaN
|
Out[30]:
|
1
|
False
|
在tuple中的情況
這個不奇怪
In[31]:
|
1
|
(
1
,
None
)
==
(
1
,
None
)
|
Out[31]:
|
1
|
True
|
這個也不意外
In[32]:
|
1
|
(
1
,
None
)
==
(
1
,
NaN
)
|
Out[32]:
|
1
|
False
|
但是下面這個實驗NaN的表現和基准不一致
In[33]:
|
1
|
(
1
,
NaN
)
==
(
1
,
NaN
)
|
Out[33]:
|
1
|
True
|
在numpy.array中的情況
In[34]:
|
1
|
np
.
array
(
[
1
,
None
]
)
==
np
.
array
(
[
1
,
None
]
)
|
Out[34]:
|
1
|
array
(
[
True
,
True
]
,
dtype
=
bool
)
|
In[35]:
|
1
|
np
.
array
(
[
1
,
NaN
]
)
==
np
.
array
(
[
1
,
NaN
]
)
|
Out[35]:
|
1
|
array
(
[
True
,
False
]
,
dtype
=
bool
)
|
In[36]:
|
1
|
np
.
array
(
[
1
,
NaN
]
)
==
np
.
array
(
[
1
,
None
]
)
|
Out[36]:
|
1
|
array
(
[
True
,
False
]
,
dtype
=
bool
)
|
和基准的表現一致。
但是大部分情況我們希望上面例子中, 我們希望左右兩邊的array被判定成一致。這時可以用numpy.testing.assert_equal函數來處理。 注意這個函數的表現同assert, 不會返回True, False, 而是無反應或者raise Exception
In[37]:
|
1
|
np
.
testing
.
assert_equal
(
np
.
array
(
[
1
,
NaN
]
)
,
np
.
array
(
[
1
,
NaN
]
)
)
|
它也可以處理兩邊都是None的情況
In[38]:
|
1
|
np
.
testing
.
assert_equal
(
np
.
array
(
[
1
,
None
]
)
,
np
.
array
(
[
1
,
None
]
)
)
|
但是一邊是None,一邊是NaN時會被認為兩邊不一致, 導致AssertionError
In[39]:
|
1
2
3
4
|
try
:
np
.
testing
.
assert_equal
(
np
.
array
(
[
1
,
NaN
]
)
,
np
.
array
(
[
1
,
None
]
)
)
except
Exception
as
e
:
print
(
type
(
e
)
,
e
)
|
|
1
2
3
4
5
6
|
&
lt
;
class
'assertionerror'
=
""
&
gt
;
Arrays
are
not
equal
(
mismatch
50.0
%
)
x
:
array
(
[
1.
,
nan
]
)
y
:
array
(
[
1
,
None
]
,
dtype
=
object
)
|
在Series中的情況
下面兩個實驗中的表現和基准一致
In[40]:
|
1
|
Series
(
[
NaN
,
'a'
]
)
==
Series
(
[
NaN
,
'a'
]
)
|
Out[40]:
|
1
2
3
|
0
False
1
True
dtype
:
bool
|
In[41]:
|
1
|
Series
(
[
None
,
'a'
]
)
==
Series
(
[
NaN
,
'a'
]
)
|
Out[41]:
|
1
2
3
|
0
False
1
True
dtype
:
bool
|
但是None和基准的表現不一致。
In[42]:
|
1
|
Series
(
[
None
,
'a'
]
)
==
Series
(
[
None
,
'a'
]
)
|
Out[42]:
|
1
2
3
|
0
False
1
True
dtype
:
bool
|
和array類似,Series也有專門的函數equals用於判斷兩邊的Series是否整體看相等
In[43]:
|
1
|
Series
(
[
None
,
'a'
]
)
.
equals
(
Series
(
[
NaN
,
'a'
]
)
)
|
Out[43]:
|
1
|
True
|
In[44]:
|
1
|
Series
(
[
None
,
'a'
]
)
.
equals
(
Series
(
[
None
,
'a'
]
)
)
|
Out[44]:
|
1
|
True
|
In[45]:
|
1
|
Series
(
[
NaN
,
'a'
]
)
.
equals
(
Series
(
[
NaN
,
'a'
]
)
)
|
Out[45]:
|
1
|
True
|
比numpy.testing.assert_equals更智能些, 三種情況下都能恰當的處理
在DataFrame merge中的表現
兩邊的None會被判為相同
In[46]:
|
1
2
3
|
a
=
DataFrame
(
{
'A'
:
[
None
,
'a'
]
}
)
b
=
DataFrame
(
{
'A'
:
[
None
,
'a'
]
}
)
a
.
merge
(
b
,
on
=
'A'
,
how
=
'outer'
)
|
Out[46]:
| A | |
|---|---|
| 0 | None |
| 1 | a |
兩邊的NaN會被判為相同
In[47]:
|
1
2
3
|
a
=
DataFrame
(
{
'A'
:
[
NaN
,
'a'
]
}
)
b
=
DataFrame
(
{
'A'
:
[
NaN
,
'a'
]
}
)
a
.
merge
(
b
,
on
=
'A'
,
how
=
'outer'
)
|
Out[47]:
| A | |
|---|---|
| 0 | NaN |
| 1 | a |
無論兩邊都是None,都是NaN,還是都有,相關的列都會被正確的匹配。 注意一邊是None,一邊是NaN的時候。會以左側的結果為准。
In[48]:
|
1
2
3
|
a
=
DataFrame
(
{
'A'
:
[
None
,
'a'
]
}
)
b
=
DataFrame
(
{
'A'
:
[
NaN
,
'a'
]
}
)
a
.
merge
(
b
,
on
=
'A'
,
how
=
'outer'
)
|
Out[48]:
| A | |
|---|---|
| 0 | None |
| 1 | a |
In[49]:
|
1
2
3
|
a
=
DataFrame
(
{
'A'
:
[
NaN
,
'a'
]
}
)
b
=
DataFrame
(
{
'A'
:
[
None
,
'a'
]
}
)
a
.
merge
(
b
,
on
=
'A'
,
how
=
'outer'
)
|
Out[49]:
| A | |
|---|---|
| 0 | NaN |
| 1 | a |
注意
這和空值在postgresql等sql數據庫中的表現不一樣, 在數據庫中, join時兩邊的空值會被判定為不同的數值
在groupby中的表現
In[50]:
|
1
2
|
d
=
DataFrame
(
{
'A'
:
[
1
,
1
,
1
,
1
,
2
]
,
'B'
:
[
None
,
None
,
'a'
,
'a'
,
'b'
]
}
)
d
.
groupby
(
[
'A'
,
'B'
]
)
.
apply
(
len
)
|
Out[50]:
|
1
2
3
4
|
A
B
1
a
2
2
b
1
dtype
:
int64
|
可以看到(1, NaN)對應的組直接被忽略了
In[51]:
|
1
2
|
d
=
DataFrame
(
{
'A'
:
[
1
,
1
,
1
,
1
,
2
]
,
'B'
:
[
None
,
None
,
'a'
,
'a'
,
'b'
]
}
)
d
.
groupby
(
[
'A'
,
'B'
]
)
.
apply
(
len
)
|
Out[51]:
|
1
2
3
4
|
A
B
1
a
2
2
b
1
dtype
:
int64
|
(1,None)的組也被直接忽略了
In[52]:
|
1
2
|
d
=
DataFrame
(
{
'A'
:
[
1
,
1
,
1
,
1
,
2
]
,
'B'
:
[
None
,
NaN
,
'a'
,
'a'
,
'b'
]
}
)
d
.
groupby
(
[
'A'
,
'B'
]
)
.
apply
(
len
)
|
Out[52]:
|
1
2
3
4
|
A
B
1
a
2
2
b
1
dtype
:
int64
|
那么上面這個結果應該沒啥意外的
總結
DataFrame.groupby會忽略分組列中含有None或者NaN的記錄
支持寫入數據庫?
往數據庫中寫入時NaN不可處理,需轉換成None,否則會報錯。這個這里就不演示了。
相信作為pandas老司機, 至少能想出兩種替換方法。
In[53]:
|
1
2
|
s
=
Series
(
[
None
,
NaN
,
'a'
]
)
s
|
Out[53]:
|
1
2
3
4
|
0
None
1
NaN
2
a
dtype
:
object
|
方案1
In[54]:
|
1
|
s
.
replace
(
[
NaN
]
,
None
)
|
Out[54]:
|
1
2
3
4
|
0
None
1
None
2
a
dtype
:
object
|
方案2
In[55]:
|
1
2
|
s
[
s
.
isnull
(
)
]
=
None
s
|
Out[55]:
|
1
2
3
4
|
0
None
1
None
2
a
dtype
:
object
|
然而這么就覺得完事大吉的話就圖樣圖森破了, 看下面的例子
In[56]:
|
1
2
|
s
=
Series
(
[
NaN
,
1
]
)
s
|
Out[56]:
|
1
2
3
|
0
NaN
1
1.0
dtype
:
float64
|
In[57]:
|
1
|
s
.
replace
(
[
NaN
]
,
None
)
|
Out[57]:
|
1
2
3
|
0
NaN
1
1.0
dtype
:
float64
|
In[58]:
|
1
2
|
s
[
s
.
isnull
(
)
]
=
None
s
|
Out[58]:
|
1
2
3
|
0
NaN
1
1.0
dtype
:
float64
|
當其他數據是int或float時,Series又一聲不吭的自動把None替換成了NaN。
這時候可以使用第三種方法處理
In[59]:
|
1
|
s
.
where
(
s
.
notnull
(
)
,
None
)
|
Out[59]:
|
1
2
3
|
0
None
1
1
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對None | NaN對NaN | None對NaN | |
|---|---|---|---|
| 單值 | True | False | False |
| tuple(整體) | True | True | False |
| np.array(逐個) | True | False | False |
| Series(逐個) | False | False | False |
| assert_equals | True | True | False |
| Series.equals | True | True | True |
| merge | True | True | True |
由於等值性比較方面,None和NaN在各場景下表現不太一致,相對來說None表現的更穩定。
為了不給自己惹不必要的麻煩和額外的記憶負擔。 實踐中,建議遵循以下三個原則即可
- 在用pandas和numpy處理數據階段將None,NaN統一處理成NaN,以便支持更多的函數。
- 如果要判斷Series,numpy.array整體的等值性,用專門的Series.equals,numpy.array函數去處理,不要自己用
==判斷 * - 如果要將數據導入數據庫,將NaN替換成None
