处理缺失数据¶
现实中的数据往往有不同程度的不完备,尤其是会出现缺失值。许多流行的数据集也会出现数据缺失,而且处理不同数据源缺失的方法还不相同。
本部分介绍处理缺失值的通用规则,pandas对缺失值的表现形式以及自带处理缺失值的工具。本部分涉及的缺失值表现为 null, NaN,或者 NA 。
选择处理缺失值的方法¶
DataFrame有很多识别缺失值的方法,一种通过覆盖全局的掩码来表示,另一种使用标签方式。
掩码方法可能是原数组维度相同的完整布尔型数组,也可能使用比特值(0和1)表示掩码。在标签方法中,标签可能是具体数字,如使用(-999)表示缺失值。有时标签可能使用更全局的值,如用NaN(不是一个数)表示缺失的浮点型数字,它是IEEE浮点数规范中指定的特殊字符。
Pandas中缺失值¶
Pandas中缺失值延续了Numpy中的方式,并没有为浮点型数据类型提供内置的NA作为缺失值。
NumPy指数掩码方式,可以用原来数组一样的布尔型掩码说明是否存在缺失值,Pandas自然继承了这种方法。但这种方法在计算和运行时会消耗更多资源。所以,Pandas最终选择标签方法表示缺失值。
Pandas使用标签方法包括两种Python原生的缺失值,一种是浮点类型的缺失值NAN,另一种是Python中的None对象。这样运行还是有不错效果。
None: Python对象类型的缺失值¶
Pandas应用的第一个缺失值是 None, 它是Python单体对象,常常在代码中表示缺失值,由于None是一个Python对象,不能作为Numpy/Pandas数组类型的缺失值,只能用于 'object'数组类型 (即由python对象组成的数组):
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 86.3 ms ± 3.7 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) dtype = int 2.07 ms ± 93.5 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
用Python中Object组成的数字意味着对包含None 缺失值的数组进行加总计算,如sum() or min() ,将会出现错误。
vals1.sum()
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[20], line 1 ----> 1 vals1.sum() File d:\anaconda3\Lib\site-packages\numpy\core\_methods.py:49, in _sum(a, axis, dtype, out, keepdims, initial, where) 47 def _sum(a, axis=None, dtype=None, out=None, keepdims=False, 48 initial=_NoValue, where=True): ---> 49 return umr_sum(a, axis, dtype, out, keepdims, initial, where) TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
这说明在整数和None 之间进行加法是没有定义的,无法执行。
NaN: 数值型缺失值¶
另一种替代方法,使用 NaN (愿意为 Not a Number), 可以被系统认定为浮点型缺失值,这里主要原因是NAN符合IEEE 浮点型缺失值的标准:
vals2 = np.array([1, np.nan, 3, 4])
vals2.dtype
dtype('float64')
NumPy会为该数组选择一个原生的浮点类型,该数组会被编译为C代码,以供快速计算或者操作。你也可以把 NaN 看成一个病毒,它将与之直接接触的数据同化。无论和 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 是一种特殊的浮点型数字,不是整形、字符串或者其他数据类型。
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对不同类型缺失值转化规则:
| 类型 | 缺失值转化规则 | NA 标签值 |
|---|---|---|
floating |
无变化 | np.nan |
object |
无变化 | None or np.nan |
integer |
强制转化为 float64 |
np.nan |
boolean |
强制转化为 object |
None or np.nan |
在 Pandas中, 字符串存储为 object 类型。
处理缺失值¶
isnull(): 创建一个布尔类型的掩码标记缺失值notnull(): 与isnull()相反dropna(): 返回一个剔除了缺失值的数据fillna(): 返回一个充填了缺失值的数据
发现缺失值¶
使用一下方法,即在df后面加点调用: isnull() and 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
删除空值¶
dropna()是去除空值的常用方法。
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 |
要么整行,要么整列删除,根据不同情况,需要设置axis参数。默认删除含有缺失值的整行
df.dropna()
| 0 | 1 | 2 | |
|---|---|---|---|
| 1 | 2.0 | 3.0 | 5 |
如果希望删除含有缺失值的列,可以使用 axis=1参数,将含有缺失值的列予以删除。
df.dropna(axis='columns')
| 2 | |
|---|---|
| 0 | 2 |
| 1 | 5 |
| 2 | 6 |
这种删除可能把既有数据予以删除,为了更多发现信息,在去除缺失值时,可以有一些其他设置:一种方法时在dropna方法中增加条件,如使用 how or 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 参数来设定最小非缺失值的数量,在下面例子中,可以看到,不少于3的行才被选择。
df.dropna(axis='rows', thresh=3)
| 0 | 1 | 2 | |
|---|---|---|---|
| 1 | 2.0 | 3.0 | 5 |
DataFrame中的第一行和第三行被删除,因为他们的非缺失值小于thresh=3
缺失值充填¶
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
用前面的数充填缺失值:
# 向前充填
data.ffill()
a 1.0 b 1.0 c 2.0 d 2.0 e 3.0 dtype: float64
我们也可以用后面的值充填缺失值:
# 向后充填
data.bfill()
a 1.0 b 2.0 c 2.0 d 3.0 e 3.0 dtype: float64
在 DataFrame中,这种方法也是类似的,但是需要设定充填的方向,即需要设定 axis 参数,确保充填方向:
df
| 0 | 1 | 2 | |
|---|---|---|---|
| 0 | 1.0 | NaN | 2 |
| 1 | 2.0 | 3.0 | 5 |
| 2 | NaN | 4.0 | 6 |
df.ffill( axis=1)
| 0 | 1 | 2 | |
|---|---|---|---|
| 0 | 1.0 | 1.0 | 2.0 |
| 1 | 2.0 | 3.0 | 5.0 |
| 2 | NaN | 4.0 | 6.0 |
从上面例子来看,向前充填中,最后一行由于后面没有数据,仍然保持空值状态。
#向后充填缺失值
df.bfill(axis=1)
| 0 | 1 | 2 | |
|---|---|---|---|
| 0 | 1.0 | 2.0 | 2.0 |
| 1 | 2.0 | 3.0 | 5.0 |
| 2 | 4.0 | 4.0 | 6.0 |