处理缺失数据
缺失数据(missing data)在大部分数据分析应用中都很常见。pandas的设计目标之一就是让缺失数据的处理任务尽量轻松。例如,pandas对象上的所有描述统计都排除了缺失数据,正如我们在本章稍早的地方所看到的那样。
pandas使用浮点值NaN(Not a Number)表示浮点和非浮点数组中的缺失数据。它只是一个便于被检测出来的标记而已:
- In [229]: string_data = Series(['aardvark', 'artichoke', np.nan, 'avocado'])
- In [230]: string_data In [231]: string_data.isnull()
- Out[230]: Out[231]:
- 0 aardvark 0 False
- 1 artichoke 1 False
- 2 NaN 2 True
- 3 avocado 3 False
Python内置的None值也会被当做NA处理:
- In [232]: string_data[0] = None
- In [233]: string_data.isnull()
- Out[233]:
- 0 True
- 1 False
- 2 True
- 3 False
我不敢说pandas的NA表现形式是最优的,但它确实很简单也很可靠。由于NumPy的数据类型体系中缺乏真正的NA数据类型或位模式,所以它是我能想到的最佳解决方案(一套简单的API以及足够全面的性能特征)。随着NumPy的不断发展,这个问题今后可能会发生变化。
滤除缺失数据
过滤掉缺失数据的办法有很多种。纯手工操作永远都是一个办法,但dropna可能会更实用一些。对于一个Series,dropna返回一个仅含非空数据和索引值的Series:
- In [234]: from numpy import nan as NA
- In [235]: data = Series([1, NA, 3.5, NA, 7])
- In [236]: data.dropna()
- Out[236]:
- 0 1.0
- 2 3.5
- 4 7.0
当然,也可以通过布尔型索引达到这个目的:
- In [237]: data[data.notnull()]
- Out[237]:
- 0 1.0
- 2 3.5
- 4 7.0
而对于DataFrame对象,事情就有点复杂了。你可能希望丢弃全NA或含有NA的行或列。dropna默认丢弃任何含有缺失值的行:
- In [238]: data = DataFrame([[1., 6.5, 3.], [1., NA, NA],
- ...: [NA, NA, NA], [NA, 6.5, 3.]])
- In [239]: cleaned = data.dropna()
- In [240]: data In [241]: cleaned
- Out[240]: Out[241]:
- 0 1 2 0 1 2
- 0 1 6.5 3 0 1 6.5 3
- 1 1 NaN NaN
- 2 NaN NaN NaN
- 3 NaN 6.5 3
传入how='all'将只丢弃全为NA的那些行:
- In [242]: data.dropna(how='all')
- Out[242]:
- 0 1 2
- 0 1 6.5 3
- 1 1 NaN NaN
- 3 NaN 6.5 3
要用这种方式丢弃列,只需传入axis=1即可:
- In [243]: data[4] = NA
- In [244]: data In [245]: data.dropna(axis=1, how='all')
- Out[244]: Out[245]:
- 0 1 2 4 0 1 2
- 0 1 6.5 3 NaN 0 1 6.5 3
- 1 1 NaN NaN NaN 1 1 NaN NaN
- 2 NaN NaN NaN NaN 2 NaN NaN NaN
- 3 NaN 6.5 3 NaN 3 NaN 6.5 3
另一个滤除DataFrame行的问题涉及时间序列数据。假设你只想留下一部分观测数据,可以用thresh参数实现此目的:
- In [246]: df = DataFrame(np.random.randn(7, 3))
- In [247]: df.ix[:4, 1] = NA; df.ix[:2, 2] = NA
- In [248]: df In [249]: df.dropna(thresh=3)
- Out[248]: Out[249]:
- 0 1 2 0 1 2
- 0 -0.577087 NaN NaN 5 0.332883 -2.359419 -0.199543
- 1 0.523772 NaN NaN 6-1.541996 -0.970736 -1.307030
- 2 -0.713544 NaN NaN
- 3 -1.860761 NaN 0.560145
- 4 -1.265934 NaN -1.063512
- 5 0.332883 -2.359419 -0.199543
- 6 -1.541996 -0.970736 -1.307030
填充缺失数据
你可能不想滤除缺失数据(有可能会丢弃跟它有关的其他数据),而是希望通过其他方式填补那些“空洞”。对于大多数情况而言,fillna方法是最主要的函数。通过一个常数调用fillna就会将缺失值替换为那个常数值:
- In [250]: df.fillna(0)
- Out[250]:
- 0 1 2
- 0 -0.577087 0.000000 0.000000
- 1 0.523772 0.000000 0.000000
- 2 -0.713544 0.000000 0.000000
- 3 -1.860761 0.000000 0.560145
- 4 -1.265934 0.000000 -1.063512
- 5 0.332883 -2.359419 -0.199543
- 6 -1.541996 -0.970736 -1.307030
若是通过一个字典调用fillna,就可以实现对不同的列填充不同的值:
- In [251]: df.fillna({1: 0.5, 3: -1})
- Out[251]:
- 0 1 2
- 0 -0.577087 0.500000 NaN
- 1 0.523772 0.500000 NaN
- 2 -0.713544 0.500000 NaN
- 3 -1.860761 0.500000 0.560145
- 4 -1.265934 0.500000 -1.063512
- 5 0.332883 -2.359419 -0.199543
- 6 -1.541996 -0.970736 -1.307030
fillna默认会返回新对象,但也可以对现有对象进行就地修改:
- # 总是返回被填充对象的引用
- In [252]: _ = df.fillna(0, inplace=True)
- In [253]: df
- Out[253]:
- 0 1 2
- 0 -0.577087 0.000000 0.000000
- 1 0.523772 0.000000 0.000000
- 2 -0.713544 0.000000 0.000000
- 3 -1.860761 0.000000 0.560145
- 4 -1.265934 0.000000 -1.063512
- 5 0.332883 -2.359419 -0.199543
- 6 -1.541996 -0.970736 -1.307030
对reindex有效的那些插值方法也可用于fillna:
- In [254]: df = DataFrame(np.random.randn(6, 3))
- In [255]: df.ix[2:, 1] = NA; df.ix[4:, 2] = NA
- In [256]: df
- Out[256]:
- 0 1 2
- 0 0.286350 0.377984 -0.753887
- 1 0.331286 1.349742 0.069877
- 2 0.246674 NaN 1.004812
- 3 1.327195 NaN -1.549106
- 4 0.022185 NaN NaN
- 5 0.862580 NaN NaN
- In [257]: df.fillna(method='ffill') In [258]: df.fillna(method='ffill', limit=2)
- Out[257]: Out[258]:
- 0 1 2 0 1 2
- 0 0.286350 0.377984 -0.753887 0 0.286350 0.377984 -0.753887
- 1 0.331286 1.349742 0.069877 1 0.331286 1.349742 0.069877
- 2 0.246674 1.349742 1.004812 2 0.246674 1.349742 1.004812
- 3 1.327195 1.349742 -1.549106 3 1.327195 1.349742 -1.549106
- 4 0.022185 1.349742 -1.549106 4 0.022185 NaN -1.549106
- 5 0.862580 1.349742 -1.549106 5 0.862580 NaN -1.549106
只要稍微动动脑子,你就可以利用fillna实现许多别的功能。比如说,你可以传入Series的平均值或中位数:
- In [259]: data = Series([1., NA, 3.5, NA, 7])
- In [260]: data.fillna(data.mean())
- Out[260]:
- 0 1.000000
- 1 3.833333
- 2 3.500000
- 3 3.833333
- 4 7.000000
表5-13列出了fillna的参数参考。