文章目录
《Python数据科学手册》读书笔记
处理缺失值
缺失值主要有三种形式: null、 NaN 或 NA。
选择处理缺失值的方法
在数据表或 DataFrame 中有很多识别缺失值的方法。 一般情况下可以
分为两种: 一种方法是通过一个覆盖全局的掩码表示缺失值, 另一种方
法是用一个标签值(sentinel value) 表示缺失值。
在掩码方法中, 掩码可能是一个与原数组维度相同的完整布尔类型数
组, 也可能是用一个比特(0 或 1) 表示有缺失值的局部状态。
在标签方法中, 标签值可能是具体的数据(例如用 -9999 表示缺失的整
数) , 也可能是些极少出现的形式。 另外, 标签值还可能是更全局的
值, 比如用 NaN(不是一个数) 表示缺失的浮点数, 它是 IEEE 浮点数
规范中指定的特殊字符。
使用这两种方法之前都需要先综合考量: 使用单独的掩码数组会额外出
现一个布尔类型数组, 从而增加存储与计算的负担; 而标签值方法缩小
了可以被表示为有效值的范围, 可能需要在 CPU 或 GPU 算术逻辑单元
中增加额外的(往往也不是最优的) 计算逻辑。 通常使用的 NaN 也不能
表示所有数据类型。
Pandas的缺失值
Pandas 用标签方法表示缺失值,
包括两种 Python 原有的缺失值: 浮点数据类型的 NaN 值, 以及 Python
的 None 对象。
- None: Python对象类型的缺失值
Pandas 可以使用的第一种缺失值标签是 None, 它是一个 Python 单
体对象, 经常在代码中表示缺失值。 由于 None 是一个 Python 对
象, 所以不能作为任何 NumPy / Pandas 数组类型的缺失值, 只能用
于 ‘object’ 数组类型) :
import numpy as np
import pandas as pd
vals1 = np.array([1, None, 3, 4])
vals1
array([1, None, 3, 4], dtype=object)
这里 dtype=object 表示 NumPy 认为由于这个数组是 Python 对象
构成的, 因此将其类型判断为 object。 虽然这种类型在某些情景
中非常有用, 对数据的任何操作最终都会在 Python 层面完成, 但
是在进行常见的快速操作时, 这种类型比其他原生类型数组要消耗
更多的资源:
for dtype in ['object', 'int']:
print("dtype =", dtype)
%timeit np.arange(1E6, dtype=dtype).sum()
print()
dtype = object
178 ms ± 36 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
dtype = int
5.08 ms ± 671 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
使用 Python 对象构成的数组就意味着如果你对一个包含 None 的数
组进行累计操作, 如 sum() 或者 min(), 那么通常会出现类型错
误:
这就是说, 在 Python 中没有定义整数与 None 之间的加法运算。
- NaN: 数值类型的缺失值
另一种缺失值的标签是 NaN( 全称 Not a Number, 不是一个数
字)
vals2 = np.array([1, np.nan, 3, 4])
vals2.dtype
dtype('float64')
请注意, NumPy 会为这个数组选择一个原生浮点类型, 这意味着
和之前的 object 类型数组不同, 这个数组会被编译成 C 代码从而
实现快速操作。
无论和 NaN 进行何种操作, 最终结果都是
NaN:
1 + np.nan
nan
0 * np.nan
nan
累计操作的结果定义不会抛出异常
vals2.sum(), vals2.min(), vals2.max()
C:\ProgramData\Anaconda3\lib\site-packages\numpy\core\_methods.py:32: RuntimeWarning: invalid value encountered in reduce
return umr_minimum(a, axis, None, out, keepdims, initial)
C:\ProgramData\Anaconda3\lib\site-packages\numpy\core\_methods.py:28: RuntimeWarning: invalid value encountered in reduce
return umr_maximum(a, axis, None, out, keepdims, initial)
(nan, nan, nan)
NumPy 也提供了一些特殊的累计函数, 它们可以忽略缺失值的影
响:
np.nansum(vals2), np.nanmin(vals2), np.nanmax(vals2)
(8.0, 1.0, 4.0)
谨记, NaN 是一种特殊的浮点数, 不是整数、 字符串以及其他数据
类型。
- Pandas中NaN与None的差异
虽然 NaN 与 None 各有各的用处, 但是 Pandas 把它们看成是可以等
价交换的, 在适当的时候会将两者进行替换:
pd.Series([1, np.nan, 2, None])
0 1.0
1 NaN
2 2.0
3 NaN
dtype: float64
Pandas 会将没有标签值的数据类型自动转换为 NA。 例如, 当我们
将整型数组中的一个值设置为 np.nan 时, 这个值就会强制转换成
浮点数缺失值 NA。
x = pd.Series(range(2), dtype=int)
x
0 0
1 1
dtype: int32
x[0] = None
x
0 NaN
1 1.0
dtype: float64
除了将整型数组的缺失值强制转换为浮点数, Pandas 还会
自动将 None 转换为 NaN。
Pandas对不同类型缺失值的转换规则
类型 | 缺失值转换规则 | NA标签值 |
---|---|---|
floating 浮点型 | 无变化 | np.nan |
object 对象类型 | 无变化 | None 或 np.nan |
integer 整数类型 | 强制转换为 float64 | np.nan |
floating 浮点型 | 无变化 | np.nan |
boolean 布尔类型 | 强制转换为 object | None 或 np.nan |
需要注意的是, Pandas 中字符串类型的数据通常是用 object 类型
存储的。
处理缺失值
Pandas 基本上把 None 和 NaN 看成是可以等价交换的缺
失值形式。 为了完成这种交换过程, Pandas 提供了一些方法来发现、 剔
除、 替换数据结构中的缺失值, 主要包括以下几种。
-
isnull()
创建一个布尔类型的掩码标签缺失值。 -
notnull()
与 isnull() 操作相反。 -
dropna()
返回一个剔除缺失值的数据。 -
fillna()
返回一个填充了缺失值的数据副本。
- 发现缺失值
Pandas 数据结构有两种有效的方法可以发现缺失值: isnull() 和
notnull()。 每种方法都返回布尔类型的掩码数据
data = pd.Series([1, np.nan, 'hello', None])
data.isnull()
0 False
1 True
2 False
3 True
dtype: bool
布尔类型掩码数组可以直接作为 Series
或 DataFrame 的索引使用:
data[data.notnull()]
0 1
2 hello
dtype: object
在 Series 里使用的 isnull() 和 notnull() 同样适用于
DataFrame, 产生的结果同样是布尔类型。
- 剔除缺失值
除了前面介绍的掩码方法, 还有两种很好用的缺失值处理方法, 分
别是 dropna()(剔除缺失值) 和 fillna()(填充缺失值) 。
data.dropna()
0 1
2 hello
dtype: object
而在 DataFrame 上使用它们时需要设置一些参数, 例如下面的
DataFrame:
df = pd.DataFrame([[1, np.nan, 2],
[2, 3, 5],
[np.nan, 4, 6]])
df
0 | 1 | 2 | |
---|---|---|---|
0 | 1.0 | NaN | 2 |
1 | 2.0 | 3.0 | 5 |
2 | NaN | 4.0 | 6 |
没法从 DataFrame 中单独剔除一个值, 要么是剔除缺失值所
在的整行, 要么是整列。 根据实际需求, 有时你需要剔除整行, 有
时可能是整列。
默认情况下, dropna() 会剔除任何包含缺失值的整行数据:
df.dropna()
0 | 1 | 2 | |
---|---|---|---|
1 | 2.0 | 3.0 | 5 |
可以设置按不同的坐标轴剔除缺失值, 比如 axis=1(或
axis=‘columns’) 会剔除任何包含缺失值的整列数据:
df.dropna(axis='columns')
2 | |
---|---|
0 | 2 |
1 | 5 |
2 | 6 |
但是这么做也会把非缺失值一并剔除, 因为可能有时候只需要剔除
全部是缺失值的行或列, 或者绝大多数是缺失值的行或列。 这些需
求可以通过设置 how 或 thresh 参数来满足, 它们可以设置剔除行
或列缺失值的数量阈值。
默认设置是 how=‘any’, 也就是说只要有缺失值就剔除整行或整列
(通过 axis 设置坐标轴) 。 你还可以设置 how=‘all’, 这样就只
会剔除全部是缺失值的行或列了:
df[3] = np.nan
df
0 | 1 | 2 | 3 | |
---|---|---|---|---|
0 | 1.0 | NaN | 2 | NaN |
1 | 2.0 | 3.0 | 5 | NaN |
2 | NaN | 4.0 | 6 | NaN |
df.dropna(axis='columns', how='all')
0 | 1 | 2 | |
---|---|---|---|
0 | 1.0 | NaN | 2 |
1 | 2.0 | 3.0 | 5 |
2 | NaN | 4.0 | 6 |
还可以通过 thresh 参数设置行或列中非缺失值的最小数量, 从而
实现更加个性化的配置:
df.dropna(axis='rows', thresh=3)
0 | 1 | 2 | 3 | |
---|---|---|---|---|
1 | 2.0 | 3.0 | 5 | NaN |
第 1 行与第 3 行被剔除了, 因为它们只包含两个非缺失值。
- 填充缺失值
有时候可能并不想移除缺失值, 而是想把它们替换成有效的数
值。 有效的值可能是像 0、 1、 2 那样单独的值, 也可能是经过填充
或转换得到的。 虽然你可以通过isnull() 方法建立掩码来填充缺失值,Pandas 为此专门提供
了一个 fillna() 方法, 它将返回填充了缺失值后的数组副本。
data = pd.Series([1, np.nan, 2, None, 3], index=list('abcde'))
data
a 1.0
b NaN
c 2.0
d NaN
e 3.0
dtype: float64
将用一个单独的值来填充缺失值, 例如用 0:
data.fillna(0)
a 1.0
b 0.0
c 2.0
d 0.0
e 3.0
dtype: float64
可以用缺失值前面的有效值来从前往后填充(forward-fill) :
# 从前往后填充
data.fillna(method='ffill')
a 1.0
b 1.0
c 2.0
d 2.0
e 3.0
dtype: float64
也可以用缺失值后面的有效值来从后往前填充(back-fill) :
# 从后往前填充
data.fillna(method='bfill')
a 1.0
b 2.0
c 2.0
d 3.0
e 3.0
dtype: float64
DataFrame 的操作方法与 Series 类似, 只是在填充时需要设置坐
标轴参数 axis:
df
0 | 1 | 2 | 3 | |
---|---|---|---|---|
0 | 1.0 | NaN | 2 | NaN |
1 | 2.0 | 3.0 | 5 | NaN |
2 | NaN | 4.0 | 6 | NaN |
df.fillna(method='ffill', axis=1)
需要注意的是, 假如在从前往后填充时, 需要填充的缺失值前面没
有值, 那么它就仍然是缺失值。