原文链接:https://junjiecai.github.io/posts/2016/Oct/20/none_vs_nan/

建议从这里下载这篇文章对应的.ipynb文件和相关资源。这样你就能在Jupyter中边阅读,边测试文中的代码。

python原生的None和pandas, numpy中的numpy.NaN尽管在功能上都是用来标示空缺数据。但它们的行为在很多场景下确有一些相当大的差异。由于不熟悉这些差异,曾经给我的工作带来过不少麻烦。 特此整理了一份详细的实验,比较None和NaN在不同场景下的差异。

实验的结果有些在意料之内,有些则让我大跌眼镜。希望读者看过此文后会None和NaN这对“小妖精”有更深的理解。

为了理解本文的内容,希望本文的读者需要对pandas的Series使用有一定的经验。

首先,导入所需的库

 
from numpy import NaN
from pandas import Series, DataFrame
import numpy as np

数据类型?

None是一个python特殊的数据类型, 但是NaN却是用一个特殊的float

 
type(None)
 
NoneType
 
type(NaN)
 
float

能作为dict的key?

 
{None:1}
 
{None: 1}
 
{NaN:1}
 
{nan: 1}
 
{None:1, NaN:2}
 
{nan: 2, None: 1}

都可以,而且会被认为是不同的key

Series函数中的表现

Series.map

 
s = 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

可以看到None和NaN都会替换成了1

 
s.map({NaN:1,'a':'a'})
 
0    1
1 1
2 a
dtype: object

同样None和NaN都会替换成了1

 
s.map({NaN:2,'None':1,'a':'a'})
 
0    2
1 2
2 a
dtype: object

将None替换成1的要求被忽略了

 
s.map({'None':1,NaN:2,'a':'a'})
 
0    2
1 2
2 a
dtype: object

将NaN替换成1的要求被忽略了

总结: 用Series.map对None进行替换时,会“顺便”把NaN也一起替换掉;NaN也会顺便把None替换掉。

如果None和NaN分别定义了不同的映射数值,那么只有一个会生效。

Series.replace中的表现

 
s = Series([None, NaN, 'a'])
s
 
0    None
1 NaN
2 a
dtype: object
 
s.replace([NaN],9)
 
0    9
1 9
2 a
dtype: object
 
s.replace([None],9)
 
0    9
1 9
2 a
dtype: object

和Series.map的情况类似,指定了None的替换值后,NaN会被替换掉;反之亦然。

对函数的支持

numpy有不少函数可以自动处理NaN。

 
np.nansum([1,2,NaN])
 
3.0

但是None不能享受这些函数的便利,如果数据包含的None的话会报错

 
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)

 
import pandas as pd
pd.cut(Series([NaN]),[1,2])
 
0    NaN
dtype: category
Categories (1, object): [(1, 2]]
 
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。

 
np.array([1, None]).dtype
 
dtype('O')

而np.NaN尽管会将原本用int类型就能保存的数据转型成float,但不会带来上面这个问题。

 
np.array([1, NaN]).dtype
 
dtype('float64')

混入Series的影响

下面的结果估计大家能猜到

 
Series([1, NaN])
 
0    1.0
1 NaN
dtype: float64

下面的这个就很意外的吧

 
Series([1, None])
 
0    1.0
1 NaN
dtype: float64

pandas将None自动替换成了NaN!

 
Series([1.0, None])
 
0    1.0
1 NaN
dtype: float64

却是Object类型的None被替换成了float类型的NaN。 这么设计可能是因为None无法参与numpy的大多数计算, 而pandas的底层又依赖于numpy,因此做了这样的自动转化。

不过如果本来Series就只能用object类型容纳的话, Series不会做这样的转化工作。

 
Series(['a', None])
 
0       a
1 None
dtype: object

如果Series里面都是None的话也不会做这样的转化

 
Series([None,None])
 
0    None
1 None
dtype: object

其它的数据类型是bool时,也不会做这样的转化。

 
Series([True, False, None])
 
0     True
1 False
2 None
dtype: object

等值性判断

单值的等值性比较

下面的实验中None和NaN的表现会作为后面的等值性判断的基准(后文称为基准)

 
None == None
 
True
 
NaN == NaN
 
False
 
None == NaN
 
False

在tuple中的情况

这个不奇怪

 
(1, None) == (1, None)
 
True

这个也不意外

 
(1, None) == (1, NaN)
 
False

但是下面这个实验NaN的表现和基准不一致

 
(1, NaN) == (1, NaN)
 
True

在numpy.array中的情况

 
np.array([1,None]) == np.array([1,None])
 
array([ True,  True], dtype=bool)
 
np.array([1,NaN]) == np.array([1,NaN])
 
array([ True, False], dtype=bool)
 
np.array([1,NaN]) == np.array([1,None])
 
array([ True, False], dtype=bool)

和基准的表现一致。 

但是大部分情况我们希望上面例子中, 我们希望左右两边的array被判定成一致。这时可以用numpy.testing.assert_equal函数来处理。 注意这个函数的表现同assert, 不会返回True, False, 而是无反应或者raise Exception

 
np.testing.assert_equal(np.array([1,NaN]), np.array([1,NaN]))

它也可以处理两边都是None的情况

 
np.testing.assert_equal(np.array([1,None]), np.array([1,None]))

但是一边是None,一边是NaN时会被认为两边不一致, 导致AssertionError

 
try:
np.testing.assert_equal(np.array([1,NaN]), np.array([1,None]))
except Exception as e:
print(type(e),e)
 
Arrays are not equal

(mismatch 50.0%)
x: array([ 1., nan])
y: array([1, None], dtype=object)

在Series中的情况

下面两个实验中的表现和基准一致

 
Series([NaN,'a']) == Series([NaN,'a'])
 
0    False
1 True
dtype: bool
 
Series([None,'a']) == Series([NaN,'a'])
 
0    False
1 True
dtype: bool

但是None和基准的表现不一致。

 
Series([None,'a']) == Series([None,'a'])
 
0    False
1 True
dtype: bool

和array类似,Series也有专门的函数equals用于判断两边的Series是否整体看相等

 
Series([None,'a']).equals(Series([NaN,'a']))
 
True
 
Series([None,'a']).equals(Series([None,'a']))
 
True
 
Series([NaN,'a']).equals(Series([NaN,'a']))
 
True

比numpy.testing.assert_equals更智能些, 三种情况下都能恰当的处理

在DataFrame merge中的表现

两边的None会被判为相同

 
a = DataFrame({'A':[None,'a']})
b = DataFrame({'A':[None,'a']})
a.merge(b,on='A', how = 'outer')
 
  A
0 None
1 a

两边的NaN会被判为相同

 
a = DataFrame({'A':[NaN,'a']})
b = DataFrame({'A':[NaN,'a']})
a.merge(b,on='A', how = 'outer')
 
  A
0 NaN
1 a

无论两边都是None,都是NaN,还是都有,相关的列都会被正确的匹配。 注意一边是None,一边是NaN的时候。会以左侧的结果为准。

 
a = DataFrame({'A':[None,'a']})
b = DataFrame({'A':[NaN,'a']})
a.merge(b,on='A', how = 'outer')
 
  A
0 None
1 a
 
a = DataFrame({'A':[NaN,'a']})
b = DataFrame({'A':[None,'a']})
a.merge(b,on='A', how = 'outer')
 
  A
0 NaN
1 a

注意

这和空值在postgresql等sql数据库中的表现不一样, 在数据库中, join时两边的空值会被判定为不同的数值

在groupby中的表现

 
d = DataFrame({'A':[1,1,1,1,2],'B':[None,None,'a','a','b']})
d.groupby(['A','B']).apply(len)
 
A  B
1 a 2
2 b 1
dtype: int64

可以看到(1, NaN)对应的组直接被忽略了

 
d = DataFrame({'A':[1,1,1,1,2],'B':[None,None,'a','a','b']})
d.groupby(['A','B']).apply(len)
 
A  B
1 a 2
2 b 1
dtype: int64

(1,None)的组也被直接忽略了

 
d = DataFrame({'A':[1,1,1,1,2],'B':[None,NaN,'a','a','b']})
d.groupby(['A','B']).apply(len)
 
A  B
1 a 2
2 b 1
dtype: int64

那么上面这个结果应该没啥意外的

总结

DataFrame.groupby会忽略分组列中含有None或者NaN的记录

支持写入数据库?

往数据库中写入时NaN不可处理,需转换成None,否则会报错。这个这里就不演示了。

相信作为pandas老司机, 至少能想出两种替换方法。

 
s = Series([None,NaN,'a'])
s
 
0    None
1 NaN
2 a
dtype: object

方案1

 
s.replace([NaN],None)
 
0    None
1 None
2 a
dtype: object

方案2

 
s[s.isnull()]=None
s
 
0    None
1 None
2 a
dtype: object

然而这么就觉得完事大吉的话就图样图森破了, 看下面的例子

 
s = Series([NaN,1])
s
 
0    NaN
1 1.0
dtype: float64
 
s.replace([NaN], None)
 
0    NaN
1 1.0
dtype: float64
 
s[s.isnull()] = None
s
 
0    NaN
1 1.0
dtype: float64

当其他数据是int或float时,Series又一声不吭的自动把None替换成了NaN。

这时候可以使用第三种方法处理

 
s.where(s.notnull(), None)
 
0    None
1 1
dtype: object

where语句会遍历s中所有的元素,逐一检查条件表达式, 如果成立, 从原来的s取元素; 否则用None填充。 这回没有自动替换成NaN

None vs NaN要点总结

  1. 在pandas中, 如果其他的数据都是数值类型, pandas会把None自动替换成NaN, 甚至能将s[s.isnull()]= None,和s.replace(NaN, None)操作的效果无效化。 这时需要用where函数才能进行替换。

  2. None能够直接被导入数据库作为空值处理, 包含NaN的数据导入时会报错。

  3. numpy和pandas的很多函数能处理NaN,但是如果遇到None就会报错。

  4. 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

pandas numpy处理缺失值,none与nan比较的更多相关文章

  1. python及pandas,numpy等知识点技巧点学习笔记

    python和java,.net,php web平台交互最好使用web通信方式,不要使用Jypython,IronPython,这样的好处是能够保持程序模块化,解耦性好 python允许使用'''.. ...

  2. 数据分析基础之pandas & numpy

    一.jupyter的常用快捷键 - 插入cell: a, b   a是after从后插入  a是before 从前插入 - 删除cell: dd, x 都可以 - 修改cell的模式:m, y - t ...

  3. Python pandas检查数据中是否有NaN的几种方法

    Python pandas: check if any value is NaN in DataFrame # 查看每一列是否有NaN: df.isnull().any(axis=0) # 查看每一行 ...

  4. pandas 之 数据清洗-缺失值

    Abstract During the course fo doing data analysis and modeling, a significant amount of time is spen ...

  5. 机器学习 三剑客 之 pandas + numpy

    机器学习 什么是机器学习? 机器学习是从数据中自动分析获得规律(模型),并利用规律对未知数据进行预测 机器学习存在的目的和价值领域? 领域: 医疗.航空.教育.物流.电商 等... 目的: 让机器学习 ...

  6. python-数据描述与分析2(利用Pandas处理数据 缺失值的处理 数据库的使用)

    2.利用Pandas处理数据2.1 汇总计算当我们知道如何加载数据后,接下来就是如何处理数据,虽然之前的赋值计算也是一种计算,但是如果Pandas的作用就停留在此,那我们也许只是看到了它的冰山一角,它 ...

  7. python pandas/numpy

    import pandas as pdpd.merge(dataframe1,dataframe2,on='common_field',how='outer') replace NaN datafra ...

  8. Python pandas & numpy 笔记

    记性不好,多记录些常用的东西,真·持续更新中::先列出一些常用的网址: 参考了的 莫烦python pandas DOC numpy DOC matplotlib 常用 习惯上我们如此导入: impo ...

  9. 【原创】大叔经验分享(11)python引入模块报错ImportError: No module named pandas numpy

    python应用通常需要一些库,比如numpy.pandas等,安装也很简单,直接通过pip # pip install numpyRequirement already satisfied: num ...

随机推荐

  1. Android开发学习笔记-自定义组合控件

    为了能让代码能够更多的复用,故使用组合控件.下面是我正在写的项目中用到的方法. 1.先写要组合的一些需要的控件,将其封装到一个布局xml布局文件中. <?xml version="1. ...

  2. 如何让form表单在enter键入时不提交

    今天在做我的一个小玩意 在线聊天工具的时候 form表单只有一个text和一个button每当我键入enter的时候就刷新.很是郁闷,直接在form上onsumbit=false.才行. 下面是我查询 ...

  3. 【转】ZooKeeper详细介绍和使用第一节

    一.分布式协调技术 在给大家介绍ZooKeeper之前先来给大家介绍一种技术——分布式协调技术.那么什么是分布式协调技术?那么我来告诉大家,其实分布式协调技术 主要用来解决分布式环境当中多个进程之间的 ...

  4. docker machine介绍和使用

    https://www.cnblogs.com/sparkdev/p/7044950.html https://www.jianshu.com/p/cc3bb8797d3b

  5. HTML5标签canvas制作平面图

    摘要: HTML5规范已经完成了,互联网上已经有数不清的站点使用了HTML5.从现在开始研究HTML5,本文是自己在学习canvas过程中的记录,以备后需. 历史: 这个 HTML 元素是为了客户端矢 ...

  6. Nginx配置中文域名

    今天碰到一个好玩的问题,还以为是nginx的缓存,各种清理就差把nginx卸载了,后来想想不对应该是中文域名的问题,对中文进行编码,搞定,如下: ... server { listen 80; ser ...

  7. vue中使用特殊字体

    有时候为了个性化,可能需要为部分字体添加特殊的font-family 在static文件夹中创建font文件夹,内容如下: css内容如下: @font-face { font-family: vue ...

  8. ios开发之--首页 导航栏隐藏 下一级页面显示,pop回来显示白条

    解决方法,在首页中实现如下两个方法,代码如下: -(void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated] ...

  9. Kafka与Flink集成

    Apache Flink是新一代的分布式流式数据处理框架,它统一的处理引擎既可以处理批数据(batch data)也可以处理流式数据(streaming data).在实际场景中,Flink利用Apa ...

  10. Kafka一些常见资源汇总

    终于下定决心写一点普及类的东西.很多同学对Kafka的使用很感兴趣.如果你想参与到Kafka的项目开发中,很多资源是你必须要提前准备好的.本文罗列了一些常用的Kafka资源,希望对这些develope ...