如何处理python里的Nan和None

0 前言

数据分析师在使用python进行数据分析时,经常会遇到 NanNone 这两个数据缺失值,但它们两并不互相等价,有很多细微的差别。笔者将在下面对 NanNone 进行细致的介绍。

1 什么是 Nan 和 None ?

Nan(not a number) 和 None 都是 python 里的数据缺失值,表示当前某些数据为“空”。

数据缺失值指现有数据集中某个或某些属性的值存在不完全的空值

更准确的说,Nan 是出现在 numpy/pandas 里的缺失值,而 NonePython 的缺失值。某种意义上,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<br />value: True key:True<br />value: True key:True<br />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点:

  1. 为什么会产生 Nan 和 None?
  2. 最终数据的结果,应该是怎样的?

任何的数据,本质上都是业务的体现;因此我们对 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 处理原则

我们可以遵循以下原则进行处理:

  1. 在刚读取到数据后,None 和 Nan 统一处理成 NaN,以便支持更多的函数。

  2. 如果要判断 Series,numpy.array 整体的等值性,用专门的 Series.equals,numpy.array 函数去处理,不要自己用==判断

  3. 如果要将数据导入数据库,将 NaN 替换成 None。

4 总结

  1. None 和 Nan 都是 python 的数据空值,None 更为特殊,Nan 则属于 numpy 产生。两者并不完全等价。

  2. 在大部分的函数里,None 和 Nan 都是被一并处理的,但在 replace 里不行。

  3. 在聚合函数(group by)里,Nan 和 None 都不会被计算。

  4. 在写入数据库时,Nan 会报错,而 None 不会。

  5. 数据清洗的处理原则:

    1. 在刚读取到数据后,None 和 Nan 统一处理成 NaN,以便支持更多的函数。
    2. 如果要判断 Series,numpy.array 整体的等值性,用专门的 Series.equals,numpy.array 函数去处理,不要自己用==判断
    3. 如果要将数据导入数据库,将 NaN 替换成 None。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 2019.5.18 人生苦短,我用Python。最近关于Python的学习是真的多,我也只能多线程地来学习了。今天...
    zackary_shen阅读 1,079评论 0 2
  • pandas 数据分析【转】 frompandasimportSeries, DataFrameimportpan...
    gongdiwudu阅读 1,300评论 0 1
  • 1.DataFrame对象 按照一定顺序排列多列数据,各列数据类型可以有所不同 DataFrame对象有两个索引数...
    杨大菲阅读 1,617评论 0 3
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,474评论 16 22
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,548评论 0 11