0 前言
数据分析师在使用python进行数据分析时,经常会遇到 Nan 和 None 这两个数据缺失值,但它们两并不互相等价,有很多细微的差别。笔者将在下面对 Nan 和 None 进行细致的介绍。
1 什么是 Nan 和 None ?
Nan(not a number) 和 None 都是 python 里的数据缺失值,表示当前某些数据为“空”。
数据缺失值指现有数据集中某个或某些属性的值存在不完全的空值。
更准确的说,Nan 是出现在 numpy/pandas 里的缺失值,而 None 是 Python 的缺失值。某种意义上,None 是比 Nan 更高级、更彻底的空值。
2 Nan 和 None 的异同点
我们可以通过数据本身、其他类型函数、pandas等方面来比对。
2.1 数据本身
数据类型?
type(None) # NoneType
type(np.nan) # float
我们可以很直接的看到,在 python 里 None 是有着自己的独特类型(NoneType),而 Nan 则是属于 float 类型。
Nan 因为自身属于浮点数(特殊的float),从而能够参加部分运算。但 None 则不能参与任何运算。
等值性?
None == None # TRUE
None == np.nan # False
np.nan == np.nan # False
np.nan is np.nan # True
id(np.nan) == id(np.nan) # True
可以看到在这里,None 和 Nan 的表现也是不同的。
is 用于判断两个变量引用对象是否为同一个, == 用于判断引用变量的值是否相等。
a is b 相当于 id(a)==id(b),id() 能够获取对象的内存地址。
为什么 np.nan == np.nan 结果是 False,这涉及到 python 的另一个坑,有兴趣的同学可以参考这里
2.2 可迭代对象的表现
list 的表现
None 和 Nan 在 list 里的表现和等值性相同:
lst = [np.nan, None]
lst[0] == lst[0] # False
lst[0] == lst[1] # False
lst[1] == lst[1] # True
[np.nan, 1] == [None, 1] # False
dict 的表现
首先看在 key 值的表现:
print([i for i in {np.nan:1, None:1}]) # [nan, None]
{np.nan:1} == {np.nan:1} # True
我们可以看到,无论是 Nan 还是 None,都是可以作为 dict 的 key 值的。且互不相同(dict 不允许有相同 key 值)。
再看看 value 值:
dic = {'a':np.nan, 'b':None, 'c':np.nan, 'd':None}
print(dic['a'] == dic['b']) # False
print(dic['a'] == dic['c']) # False
print(dic['b'] == dic['d']) # True
与等值性、list里表现一致。
tuple 的表现
print((1, None) == (1, None)) # True
print((1, None) == (1, np.nan)) # False
print((1, np.nan) == (1, np.nan)) # True
第一个和第二个的表现都与 list 和 dict 表现一n'p致,但需要注意的是第三个情况,返回的也是True。
tuple 返回 True 的原因是因为,tuple 的比较是 is 而非 =
set 的表现
print({None, 1} == {None, 1}) # True
print({None, 1} == {np.nan, 1}) # False
print({np.nan, 1} == {np.nan, 1}) # True
可以看到 set 与 tuple 是类似的。与 list 和 dict 不同。
总结
对上述内容进行总结:
None 和 None
None 和 Nan
Nan 和 Nan
list
True
False
False
dict
key:True
value: True
key:True
value: True
key:True
value: False
tuple
True
False
True
set
True
False
True
2.3 在 Series 的表现
自身类型
s1 = pd.Series([1, np.nan]) # dtype:float64,其他如常
s2 = pd.Series([1, None]) # dtype:float64,None被替换为NaN
s3 = pd.Series(['a', np.nan]) # dtype:object,NaN不变
s4 = pd.Series(['a', None]) # dtype:object,None不变
print(s1, s2, s3, s4)
我们可以看到,在 s3 里,None 和 NaN 就开始出现彼此的转化了,是 Object 类型的 None 被替换成了 float 类型的 NaN。 这么设计可能是因为 None 在参与 numpy 的运算中效率远不如 Nan,因此做了这样的自动转化。
不过如果本来Series就只能用object类型容纳的话, Series 不会做这样的转化工作。
据 pandas 作者自己在论坛的回应,Nan 的本身就是 浮点型的 None,从而加快计算效率。
用表格来总结:
类型
是否转化
Series([1, np.nan])
float64
无
Series([1, None])
float64
None》NaN
Series(['a', np.nan])
object
无
Series(['a', None])
object
无
等值性
我们先直接用 == 来比对
pd.Series([np.nan,'a']) == pd.Series([np.nan,'a']) # False
pd.Series([None,'a']) == pd.Series([np.nan,'a']) # False
pd.Series([None,'a']) == pd.Series([None,'a']) # False
都是 False。
再试试 Series 自带的 equals :
pd.Series([np.nan,'a']).equals(pd.Series([np.nan,'a'])) # True
pd.Series([np.nan,'a']).equals(pd.Series([None,'a'])) # True
pd.Series([None,'a']).equals(pd.Series([None,'a'])) # True
这次都是 True......
常见的函数
map 函数
s = pd.Series([None, NaN, 'a'])
s
"""
0 None
1 NaN
2 a
dtype: object
"""
s.map({None:1,'a':'a'})
"""
0 1
1 1
2 a
dtype: object
"""
s.map({np.nan:1,'a':'a'})
"""
0 1
1 1
2 a
dtype: object
"""
在这里,map 函数将 None 和 Nan 都替换为 1。无论指定哪一个,都会正常生效。
replace 函数?
s = pd.Series([None, np.NaN, 'a'])
s
"""
0 None
1 NaN
2 a
dtype: object
"""
s.replace([np.NaN],9)
"""
0 9
1 9
2 a
dtype: object
"""
s.replace([None],9)
"""
0 9
1 9
2 a
dtype: object
"""
可以看到 replace 函数与 map 函数类似,会彼此替换掉。
2.4 在 numpy/pandas 的表现
自身类型
同样,我们先来看在 numpy 和 df 里的类型。
np.array([1, 0]).dtype #dtype('int64')
np.array([1, np.nan]).dtype # dtype('float64')
np.array([1, None]).dtype # dtype('O')
d = pd.DataFrame({'A':[1,1,1,1,2],'B':[None,np.NaN,'a','a','b'],'C':[np.nan,None,'a','a','b']})
d['B'] == d['C'] #前两个为False
可以看到 Nan 和 None 对 array 的影响完全不同。Nan 会使 int 类型变成 float,而 None 则会直接更改为 0。至于在 df 里,完全不同。
常见的函数
nansum
np.nansum([1,2,0]) # 3
np.nansum([1,2,np.nan]) # 3.0
np.nansum([1,2,None]) # TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
Nan 与我们的预期一样,仅将数据类型由 int 变为了 float。
查找定位
data = { 'row1' : [1,2,3,4], 'row2' : [None , 'b' , np.nan , 'd'] }
df = pd.DataFrame(data)
print(df.isnull()) # None和Nan都被识别为True
print(df.notnull()) # None和Nan都被识别为False
print(df.fillna(0)) # None和Nan都被填充为0
print(df.replace(np.nan, 1)) # None和Nan都被替换为1
print(df.replace(None, 1)) # 报错
可以看到,pd 里专业用来找 NULL 的可以识别2个;但是 replace 不能识别,使用时需要注意。
merge
a = pd.DataFrame({'A':[None,'a']})
b = pd.DataFrame({'A':[None,'a']})
c = pd.DataFrame({'A':[np.nan,'a']})
d = pd.DataFrame({'A':[np.nan,'a']})
a.merge(b,on='A', how = 'outer') # 能匹配到
c.merge(d,on='A', how = 'outer') # 能匹配到
a.merge(b,on='A', how = 'outer') # 能匹配到,到底展示 Nan 还是 None 看哪个在左边
在 merge 方法里,Nan 和 None 会被识别为同一个值。
聚合(Group by)
d = pd.DataFrame({'A':[1,1,1,1,2],'B':[None,np.nan,'a','a','b']})
d.groupby(['A','B']).apply(len)
"""
None和Nan 都没有被处理
A B
1 a 2
2 b 1
"""
d.groupby(['B']).mean()
"""
None 和 Nan 都没有被处理
B A
a 1
b 2
"""
这里需要注意一点:无论是 Nan 还是 None,在 mean() 里都不会被计算,但如果你将它们替换为了 0 ,就会拉低均值的下限了。具体要不要替换,视具体业务而定。
3 对 Nan 和 None 的处理方法
在处理 Nan 和 None 之前,我们要先想明白2点:
为什么会产生 Nan 和 None?
最终数据的结果,应该是怎样的?
任何的数据,本质上都是业务的体现;因此我们对 Nan 和 None 的处理也是如此,这里笔者列几个常见的原因及对应的处理方法。
3.1 处理原因
无法入库
大部分时候我们要处理这两个值,主要都是因为 Nan 无法入库。这里我们在最后根据直接转化值为 0 或其他即可:
df.fillna(0) # 用于写入字段为 float 类型
df.fillna('其他') # 用于指定要求其他值的
也可以使用 isnull() 等函数进行查找更替,但要小心 replace 处理 NULL 的异常。
便于计算
None 和 NAN 都会对常见的计算造成很大的困扰:
np.nan + 1 # nan
0 + 1 # 1
None + 1 # 报错
随然有很多计算方法,但大家还是很喜欢用简单的四则来直接运算,此时如果不转化 nan 和 None,就会酿出惨剧。
同理:
d = pd.DataFrame({'A':[0,0,3,4,5],'B':[None,np.nan,3,4,5]})
d['A'].mean() # 2,4
d['B'].mean() # 4.0
可以看到,0 并不等价于 Nan/None。
3.2 处理原则
我们可以遵循以下原则进行处理:
在刚读取到数据后,None 和 Nan 统一处理成 NaN,以便支持更多的函数。
如果要判断 Series,numpy.array 整体的等值性,用专门的 Series.equals,numpy.array 函数去处理,不要自己用==判断
如果要将数据导入数据库,将 NaN 替换成 None。
4 总结
None 和 Nan 都是 python 的数据空值,None 更为特殊,Nan 则属于 numpy 产生。两者并不完全等价。
在大部分的函数里,None 和 Nan 都是被一并处理的,但在 replace 里不行。
在聚合函数(group by)里,Nan 和 None 都不会被计算。
在写入数据库时,Nan 会报错,而 None 不会。
数据清洗的处理原则:
在刚读取到数据后,None 和 Nan 统一处理成 NaN,以便支持更多的函数。
如果要判断 Series,numpy.array 整体的等值性,用专门的 Series.equals,numpy.array 函数去处理,不要自己用==判断
如果要将数据导入数据库,将 NaN 替换成 None。