上一节介绍了 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 ,该功能似乎仍然处于实验阶段。