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
|
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
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分别定义了不同的映射数值,那么只有一个会生效。
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()
如果数据中含有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'
)
|
下面的结果估计大家能猜到
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
|
这个不奇怪
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
|
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
)
|
下面两个实验中的表现和基准一致
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]: