上一节介绍了 pandas 中的索引,本节补充关于索引对齐的更多内容。
在 numpy 中对两个数组做运算,如果它们形状既不完全一致,也不满足广播规则,那么会产生错误:
但是对两个类似的 Series 对象做运算时,就不会产生这种错误:
虽然不会产生错误,但是注意到得到的 Series 包含 5 个元素,并且最后一个元素似乎不是数值数据。同时结果的类型由整数变为了浮点数。
这两个现象就包含了本节的内容。接下来逐一介绍。
索引对齐
当在两个 Series 或 DataFrame 对象上做计算时,pandas 会按照索引值配对计算元素,而不是按位置配对:
这实际上是由于 pandas 会在计算过程中对齐两个对象的索引。索引对齐确保计算可以得到合理的结果,并且当处理不完整的数据时也更方便。
例如,以下根据现有的地区面积和人口数据,计算人口密度:
结果数组的索引是两个输入数组索引的并集,并用索引相同的元素做运算。这样一来不需要使两个 Series 都是完整且顺序一致的,也能根据索引完成配对元素的计算。
在计算两个 DataFrame 时,类似的索引对齐规则也同样会出现在列索引中:
| A | B | C | |
|---|---|---|---|
| 0 | 1 | 1 | NaN |
| 1 | 6 | 6 | NaN |
| 2 | 11 | 11 | NaN |
因此,索引对齐就是在计算时根据配对的索引完成元素的运算。如果有一个运算对象缺少该索引,该位置的数据会用 NaN 填充。这是 pandas 表示缺失值的方法,接下来会介绍缺失值的处理方法。
处理缺失值
认识缺失值
在 Python 中,空值一般用 None 对象表示。它是一个特殊的 Python object 对象 ,由 Python 解释器提供并处理。
None 作为一个 Python 对象,并不能兼容任何 numpy 的原生类型。如果在创建数组时包含 None ,那么数组的类型会被强制提升为 object :
然而,使用 object 作为数组类型会严重拖慢计算速度,因为它在底层不但占用更多空间,并且无法通过向量化加速运算:
可以看到即便对于简单的除法运算,两者都有 20 倍的速度差异。除此之外,数组中一旦包含 None 值,那么它就无法参与各种运算,因为 Python 并没有实现 None 和其它类型的运算方法。
不过好在 numpy 提供了另一种缺失值:NaN 。它全称 Not a Number ,即非数字,是一种按照 IEEE 754 标准设计的特殊浮点数:(有关 NaN 与浮点数的更多底层设计可以参见维基百科)
NaN 作为一种浮点数,在大多数编程语言中都可以被处理。浮点类型使得含 NaN 的数组可以使用向量化计算,获得很快的运算速度。
NaN 的一个特性是:它与任何数字的运算结果都是它本身。也就是说无论 NaN 参与何种运算,最终结果都是 NaN :
这种特性使得在对含有 NaN 的数组做聚合处理时,虽然不会引起异常,但结果不一定有效:
为此,numpy 提供了一些以 nan 开头的特殊累计函数,它们可以忽略数组中的缺失值:
处理缺失值
NaN 虽然不像 None 的问题那么明显,但也容易出现奇怪的问题。接下来看看 pandas 中对 NaN 的处理方式。
考虑到空值 None 的副作用太过明显,pandas 会将空值自动转换为 NaN :
Series 和 DataFrame 均可以使用 .isnull() 和 .notnull() 方法来发现缺失值,它们像通用函数一样返回布尔数组,例如:
这种布尔数组可以配合数组索引直接修改缺失值:
pandas 还提供了两种很好用的缺失值处理方式,分别是 .dropna() 和 .fillna() 方法,分别用于剔除缺失值和填充缺失值。在 Series 上使用这些方法比较易懂:
更复杂的情况涉及对 DataFrame 的缺失值处理,因为 DataFrame 增删的最小单元是一行或一列。默认情况下,DataFrame.dropna() 会剔除任何包含缺失值的整行数据。可以设置按不同的坐标轴剔除缺失值,比如 axis=1(或 axis='columns' )会剔除任何包含缺失值的整列数据:
| 0 | 1 | 2 | |
|---|---|---|---|
| 1 | 2.0 | 3.0 | 5 |
这种做法会把非缺失值一并剔除。有时候可能只需要剔除缺失值较多的行或列,这种需求可以通过以下两个参数来满足:
how参数的默认值是'any',表示只要有缺失值就剔除整行或整列。还可以传入'all',从而剔除全部是缺失值的行或列thresh参数用于设置需要保留的行或列中非缺失值的最小数量
剔除缺失值时只关注特定的列也是一种常见的需求,因为有时 DataFrame 只有部分列会参与运算,而其它列无论是否包含缺失值都想保留下了。这时可以通过向 subset 参数传入包含列名的列表来指定剔除缺失值时只关注表的哪些部分。
除此之外,inplace 当然也是 .dropna() 方法具有的参数,说明该方法默认情况下也不修改原有表,而是得到一个剔除缺失值后的副本。
.fillna() 方法用于填充缺失值。对于 Series 而言,该方法就是一个简单的替换:
对于 DataFrame 而言,除了用单个值填充所有的缺失位置外,还可以使用字典为不同列指定不同的填充值:
| 0 | 1 | 2 | |
|---|---|---|---|
| 0 | 1.0 | 20.0 | 2 |
| 1 | 2.0 | 3.0 | 5 |
| 2 | 10.0 | 4.0 | 6 |
.fillna() 方法还有以下两个常用参数:
method:参数指定填充的方法,例如"pad"或"ffill"用缺失值前或上面的有效值填充;"bfill"或"backfill"用缺失值后或下面的有效值填充。默认用自定义行为填充limit:填充的最大数量
可空类型
最后要说明的是,尽管 pandas 为处理 NaN 提供了很多便利的工具,但是尽可能不要向表中引入 NaN 。因为一旦表中出现一个 NaN ,会使得一列的数据类型都变成浮点数:
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 0 | 1.0 | 2 | 3 |
| 1 | 4 | 5.0 | 6 | 7 |
| 2 | 8 | NaN | 10 | 11 |
浮点数可能使得表在处理时出现问题。例如,如果一个整型或布尔数组出现了一个浮点数,那么它便无法用于索引。
为此,pandas 提供了一类特别的可空类型,向可空类型中引入空值并不会使 Series 变为浮点数。目前 pandas 具有的可空类型包括各种长度的 Int 和 Float 类型(注意首字母大写)、string 类型和 boolean 类型。
如果在创建 Series 时使用 dtype 参数指定类型为以上这些可空类型,那么其中的缺失值便会使用特别的缺失值指示器 pd.NA 代替:
pd.NA 不代表任何实际的值,因此可以用在任何数组中而不会改变其原有类型。例如,如果创建一个类型为 boolean 的可空布尔数组,那么它便可用于索引,并且其中的 pd.NA 会被当做 False 用于索引:
但截至 pandas 1.5 ,该功能似乎仍然处于实验阶段。