处理缺失值选择处理缺失值的方法Pandas的缺失值处理缺失值
《Python数据科学手册》读书笔记
缺失值主要有三种形式:null、 NaN 或 NA。
在数据表或 DataFrame 中有很多识别缺失值的方法。一般情况下可以分为两种:一种方法是通过一个覆盖全局的掩码表示缺失值, 另一种方法是用一个标签值(sentinel value) 表示缺失值。在掩码方法中, 掩码可能是一个与原数组维度相同的完整布尔类型数组, 也可能是用一个比特(0 或 1) 表示有缺失值的局部状态。在标签方法中, 标签值可能是具体的数据(例如用 -9999 表示缺失的整数) , 也可能是些极少出现的形式。另外, 标签值还可能是更全局的值, 比如用 NaN(不是一个数) 表示缺失的浮点数。
Pandas 用标签方法表示缺失值,包括两种 Python 原有的缺失值:
Pandas 可以使用的第一种缺失值标签是 None, 它是一个 Python 单体对象, 经常在代码中表示缺失值。由于 None 是一个 Python 对象, 所以不能作为任何 NumPy / Pandas 数组类型的缺失值,只能用于 'object' 数组类型) :
import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings("ignore")
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
182 ms ± 26 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
dtype = int
7.22 ms ± 720 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
使用 Python 对象构成的数组就意味着如果你对一个包含 None 的数组进行累计操作, 如 sum() 或者 min(), 那么通常会出现类型错误。这就是说, 在 Python 中没有定义整数与 None 之间的加法运算。
另一种缺失值的标签是 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()
(nan, nan, nan)
NumPy 也提供了一些特殊的累计函数, 它们可以忽略缺失值的影响:
np.nansum(vals2), np.nanmin(vals2), np.nanmax(vals2)
(8.0, 1.0, 4.0)
谨记, NaN 是一种特殊的浮点数, 不是整数、 字符串以及其他数据类型。
虽然 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 提供了一些方法来发现、 剔除、 替换数据结构中的缺失值, 主要包括以下几种。
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]])
print(df)
0 1 2
0 1.0 NaN 2
1 2.0 3.0 5
2 NaN 4.0 6
没法从 DataFrame 中单独剔除一个值, 要么是剔除缺失值所在的整行, 要么是整列。根据实际需求, 有时你需要剔除整行, 有时可能是整列。默认情况下, dropna() 会剔除任何包含缺失值的整行数据:
print(df.dropna())
0 1 2
1 2.0 3.0 5
可以设置按不同的坐标轴剔除缺失值, 比如 axis=1(或axis='columns') 会剔除任何包含缺失值的整列数据:
print(df.dropna(axis='columns'))
2
0 2
1 5
2 6
但是这么做也会把非缺失值一并剔除, 因为可能有时候只需要剔除全部是缺失值的行或列, 或者绝大多数是缺失值的行或列。这些需求可以通过设置 how 或 thresh 参数来满足, 它们可以设置剔除行或列缺失值的数量阈值。
默认设置是 how='any', 也就是说只要有缺失值就剔除整行或整列(通过 axis 设置坐标轴) 。你还可以设置 how='all', 这样就只会剔除全部是缺失值的行或列了:
df[3] = np.nan
print(df)
0 1 2 3
0 1.0 NaN 2 NaN
1 2.0 3.0 5 NaN
2 NaN 4.0 6 NaN
print(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 参数设置行或列中非缺失值的最小数量, 从而实现更加个性化的配置:
print(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:
print(df)
0 1 2 3
0 1.0 NaN 2 NaN
1 2.0 3.0 5 NaN
2 NaN 4.0 6 NaN
print(df.fillna(method='ffill', axis=1))
0 1 2 3
0 1.0 1.0 2.0 2.0
1 2.0 3.0 5.0 5.0
2 NaN 4.0 6.0 6.0
需要注意的是, 假如在从前往后填充时, 需要填充的缺失值前面没有值, 那么它就仍然是缺失值。