处理缺失数据

缺失数据(missing data)在大部分数据分析应用中都很常见。pandas的设计目标之一就是让缺失数据的处理任务尽量轻松。例如,pandas对象上的所有描述统计都排除了缺失数据,正如我们在本章稍早的地方所看到的那样。

pandas使用浮点值NaN(Not a Number)表示浮点和非浮点数组中的缺失数据。它只是一个便于被检测出来的标记而已:

  1. In [229]: string_data = Series(['aardvark', 'artichoke', np.nan, 'avocado'])
  2.  
  3. In [230]: string_data In [231]: string_data.isnull()
  4. Out[230]: Out[231]:
  5. 0 aardvark 0 False
  6. 1 artichoke 1 False
  7. 2 NaN 2 True
  8. 3 avocado 3 False

Python内置的None值也会被当做NA处理:

  1. In [232]: string_data[0] = None
  2.  
  3. In [233]: string_data.isnull()
  4. Out[233]:
  5. 0 True
  6. 1 False
  7. 2 True
  8. 3 False

我不敢说pandas的NA表现形式是最优的,但它确实很简单也很可靠。由于NumPy的数据类型体系中缺乏真正的NA数据类型或位模式,所以它是我能想到的最佳解决方案(一套简单的API以及足够全面的性能特征)。随着NumPy的不断发展,这个问题今后可能会发生变化。

处理缺失数据 - 图1

滤除缺失数据

过滤掉缺失数据的办法有很多种。纯手工操作永远都是一个办法,但dropna可能会更实用一些。对于一个Series,dropna返回一个仅含非空数据和索引值的Series:

  1. In [234]: from numpy import nan as NA
  2.  
  3. In [235]: data = Series([1, NA, 3.5, NA, 7])
  4.  
  5. In [236]: data.dropna()
  6. Out[236]:
  7. 0 1.0
  8. 2 3.5
  9. 4 7.0

当然,也可以通过布尔型索引达到这个目的:

  1. In [237]: data[data.notnull()]
  2. Out[237]:
  3. 0 1.0
  4. 2 3.5
  5. 4 7.0

而对于DataFrame对象,事情就有点复杂了。你可能希望丢弃全NA或含有NA的行或列。dropna默认丢弃任何含有缺失值的行:

  1. In [238]: data = DataFrame([[1., 6.5, 3.], [1., NA, NA],
  2. ...: [NA, NA, NA], [NA, 6.5, 3.]])
  3.  
  4. In [239]: cleaned = data.dropna()
  5.  
  6. In [240]: data In [241]: cleaned
  7. Out[240]: Out[241]:
  8. 0 1 2 0 1 2
  9. 0 1 6.5 3 0 1 6.5 3
  10. 1 1 NaN NaN
  11. 2 NaN NaN NaN
  12. 3 NaN 6.5 3

传入how='all'将只丢弃全为NA的那些行:

  1. In [242]: data.dropna(how='all')
  2. Out[242]:
  3. 0 1 2
  4. 0 1 6.5 3
  5. 1 1 NaN NaN
  6. 3 NaN 6.5 3

要用这种方式丢弃列,只需传入axis=1即可:

  1. In [243]: data[4] = NA
  2.  
  3. In [244]: data In [245]: data.dropna(axis=1, how='all')
  4. Out[244]: Out[245]:
  5. 0 1 2 4 0 1 2
  6. 0 1 6.5 3 NaN 0 1 6.5 3
  7. 1 1 NaN NaN NaN 1 1 NaN NaN
  8. 2 NaN NaN NaN NaN 2 NaN NaN NaN
  9. 3 NaN 6.5 3 NaN 3 NaN 6.5 3

另一个滤除DataFrame行的问题涉及时间序列数据。假设你只想留下一部分观测数据,可以用thresh参数实现此目的:

  1. In [246]: df = DataFrame(np.random.randn(7, 3))
  2. In [247]: df.ix[:4, 1] = NA; df.ix[:2, 2] = NA
  3.  
  4. In [248]: df In [249]: df.dropna(thresh=3)
  5. Out[248]: Out[249]:
  6. 0 1 2 0 1 2
  7. 0 -0.577087 NaN NaN 5 0.332883 -2.359419 -0.199543
  8. 1 0.523772 NaN NaN 6-1.541996 -0.970736 -1.307030
  9. 2 -0.713544 NaN NaN
  10. 3 -1.860761 NaN 0.560145
  11. 4 -1.265934 NaN -1.063512
  12. 5 0.332883 -2.359419 -0.199543
  13. 6 -1.541996 -0.970736 -1.307030

填充缺失数据

你可能不想滤除缺失数据(有可能会丢弃跟它有关的其他数据),而是希望通过其他方式填补那些“空洞”。对于大多数情况而言,fillna方法是最主要的函数。通过一个常数调用fillna就会将缺失值替换为那个常数值:

  1. In [250]: df.fillna(0)
  2. Out[250]:
  3. 0 1 2
  4. 0 -0.577087 0.000000 0.000000
  5. 1 0.523772 0.000000 0.000000
  6. 2 -0.713544 0.000000 0.000000
  7. 3 -1.860761 0.000000 0.560145
  8. 4 -1.265934 0.000000 -1.063512
  9. 5 0.332883 -2.359419 -0.199543
  10. 6 -1.541996 -0.970736 -1.307030

若是通过一个字典调用fillna,就可以实现对不同的列填充不同的值:

  1. In [251]: df.fillna({1: 0.5, 3: -1})
  2. Out[251]:
  3. 0 1 2
  4. 0 -0.577087 0.500000 NaN
  5. 1 0.523772 0.500000 NaN
  6. 2 -0.713544 0.500000 NaN
  7. 3 -1.860761 0.500000 0.560145
  8. 4 -1.265934 0.500000 -1.063512
  9. 5 0.332883 -2.359419 -0.199543
  10. 6 -1.541996 -0.970736 -1.307030

fillna默认会返回新对象,但也可以对现有对象进行就地修改:

  1. # 总是返回被填充对象的引用
  2. In [252]: _ = df.fillna(0, inplace=True)
  3.  
  4. In [253]: df
  5. Out[253]:
  6. 0 1 2
  7. 0 -0.577087 0.000000 0.000000
  8. 1 0.523772 0.000000 0.000000
  9. 2 -0.713544 0.000000 0.000000
  10. 3 -1.860761 0.000000 0.560145
  11. 4 -1.265934 0.000000 -1.063512
  12. 5 0.332883 -2.359419 -0.199543
  13. 6 -1.541996 -0.970736 -1.307030

对reindex有效的那些插值方法也可用于fillna:

  1. In [254]: df = DataFrame(np.random.randn(6, 3))
  2.  
  3. In [255]: df.ix[2:, 1] = NA; df.ix[4:, 2] = NA
  4.  
  5. In [256]: df
  6. Out[256]:
  7. 0 1 2
  8. 0 0.286350 0.377984 -0.753887
  9. 1 0.331286 1.349742 0.069877
  10. 2 0.246674 NaN 1.004812
  11. 3 1.327195 NaN -1.549106
  12. 4 0.022185 NaN NaN
  13. 5 0.862580 NaN NaN
  14.  
  15. In [257]: df.fillna(method='ffill') In [258]: df.fillna(method='ffill', limit=2)
  16. Out[257]: Out[258]:
  17. 0 1 2 0 1 2
  18. 0 0.286350 0.377984 -0.753887 0 0.286350 0.377984 -0.753887
  19. 1 0.331286 1.349742 0.069877 1 0.331286 1.349742 0.069877
  20. 2 0.246674 1.349742 1.004812 2 0.246674 1.349742 1.004812
  21. 3 1.327195 1.349742 -1.549106 3 1.327195 1.349742 -1.549106
  22. 4 0.022185 1.349742 -1.549106 4 0.022185 NaN -1.549106
  23. 5 0.862580 1.349742 -1.549106 5 0.862580 NaN -1.549106

只要稍微动动脑子,你就可以利用fillna实现许多别的功能。比如说,你可以传入Series的平均值或中位数:

  1. In [259]: data = Series([1., NA, 3.5, NA, 7])
  2.  
  3. In [260]: data.fillna(data.mean())
  4. Out[260]:
  5. 0 1.000000
  6. 1 3.833333
  7. 2 3.500000
  8. 3 3.833333
  9. 4 7.000000

表5-13列出了fillna的参数参考。

处理缺失数据 - 图2

处理缺失数据 - 图3