在实际数据处理过程中,缺失值和异常值是最常见的,很少有数据是干净整齐,直接可用的。所以需要进行数据清洗。
在DataFrame中有很多识别缺失值的方法。一般情况下分为两种:
在掩码方法中,掩码可能是一个于原数组维度相同的完整布尔类型数组,也可能是用一个比特(0或1)表示有缺失值.但是,使用单独的掩码数组会额外出现一个布尔类型数组,从而增加存储与计算的负担.
在标签方法中,标签值可能是具体的数据(如-9999代表缺失值),也可能是更全局的值,比如NaN表示确实的浮点数。但是缩小了可以被表示为有效值的范围。而且NaN也不能表示所有数据类型。
Pandas使用标签方法表示缺失值,包括两种Python原有的缺失值:浮点数据类型的NaN值、以及Python的None对象。
Pandas可以使用的第一种缺失值标签是None,他是一个Python单体对象(object),经常在代码中表示缺失值。由于None是一个Python对象,所以不能作为任何Numpy/Pandas数组类型的缺失值,只能用于‘object’数组类型(即由Python对象构成的数组):
In [1]:import pandas as pd
import numpy as np
In [2]:v1 = np.array([1,None,3,4])
v1
Out[2]:array([1, None, 3, 4], dtype=object)
这里dtype=object 表示NumPy 认为由于这个数组是Python 对象构成的,因此将其类型判断为object。虽然这种类型在某些情景中非常有用,对数据的任何操作最终都会在Python 层面完成,但是在进行常见的快速操作时,这种类型比其他原生类型数组要消耗更多的资源:
In [3]:for dtype in ['object', 'int']:
print("dtype =", dtype)
%timeit np.arange(1E6, dtype=dtype).sum()
print()
dtype= object
93 ms ± 5.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
dtype= int
3.89 ms ± 67.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
而且在使用None作为缺失值标签时,如果使用sum()或者min(),通常会发生类型错误:
In [4]:v1.sum()
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
另一种缺失值的标签是NaN(Not a Number),本质上是一个浮点数,这意味着和之前的object类型数组不同,他会被编译成C语言而快速操作(Numpy是基于C语言的,object是基于Python的)。
你可以把NaN看作是一个数据类病毒-------他会将与它接触过的数据同化。无论和NaN进行何种操作,最终结果都是NaN:
In [5]: type(np.nan)
Out[5]: float
In [6]: 1 + np.nan
Out[6]: nan
In [7]: 0 * np.nan
Out[7]: nan
但是在进行累计操作时,虽然不会出现异常,但有时并不是我们想要的结果:
In [8]: v2 = np.array([1,np.nan,3,4])
v2
Out[8]: array([ 1., nan, 3., 4.])
In [9]: v2.sum(), v2.min(), v2.max()
Out[9]: (nan, nan, nan)
Numpy提供了一些特殊的累计函数,他们可以忽略缺失值的影响:
In [10]: np.nansum(v2) # 忽略缺失值,计算有效值的和
Out[10]: 8.0
Pandas 提供了一些方法来发现、剔除、替换数据结构中的缺失值,主要包
括以下几种:
Pandas 数据结构有两种有效的方法可以发现缺失值:isnull() 和notnull()。每种方法都返回布尔类型的掩码数据,例如:
In [11]: data = pd.Series([1, np.nan, 'hello', None])
data
Out[11]: 0 1
1 NaN
2 hello
3 None
dtype: object
In [12]: a = data.isnull()
a
Out[12]: a False
b True
c False
d True
e False
dtype: bool
# 可以将布尔掩码数组作为索引使用
In [13]: data[data.notnull()]
Out[13]: 0 1
2 hello
dtype: object
isnull()和notnull()在DataFrame中同样适用。
除了前面介绍的掩码方法,还有两种很好用的缺失值处理方法,分别是dropna()(剔除缺失值)和fillna()(填充缺失值)。
In [14]:data.dropna()
Out[14]:0 1
2 hello
dtype: object
在DataFrame中,也同样使用,不过需要做一些改变。
In [15]:df = pd.DataFrame([[1, np.nan, 2],
[2, 3, 5],
[np.nan, 4, 6]])
df
Out[15]:
0 1 2
0 1.0 NaN 2
1 2.0 3.0 5
2 NaN 4.0 6
In [16]:df.dropna()
Out[16]:
0 1 2
1 2.0 3.0 5
可以看出,当我们在DataFrame中使用dropna()而不加任何参数的时候,会剔除缺失值所在的整行或者整列。控制参数为axis,默认情况下会剔除任何包含缺失值的整行数据,axis=0。当我们想删除列时,令axis=1(或axis=‘columns’)。
In [17]:df.dropna(axis=1)
Out[17]:
2
0 2
1 5
2 6
但是这么做也会把非缺失值一并剔除,因为可能有时候只需要剔除全部是缺失值的行或列,或者绝大多数是缺失值的行或列。这些需求可以通过设置how 或thresh 参数来满足,它们可以设置剔除行或列缺失值的数量阈值。
默认设置是how=‘any’,也就是说只要有缺失值就剔除整行或整列(通过axis 设置坐标轴)。你还可以设置how=‘all’,这样就只会剔除全部是缺失值的行或列了:
In [18]:df[3] = np.nan
df
Out[18]:
0 1 2 3
0 1.0 NaN 2 NaN
1 2.0 3.0 5 NaN
2 NaN 4.0 6 NaN
In [19]:df.dropna(axis='columns', how='all')
Out[19]:
0 1 2
0 1.0 NaN 2
1 2.0 3.0 5
2 NaN 4.0 6
我们也可以设置一个阈值thresh,当有效值大于某个数值时,就不删除该行或该列。
In [20]:df.dropna(axis='rows', thresh=3)
Out[20]:
0 1 2 3
1 2.0 3.0 5 NaN
其实,有时候我们并不想移除缺失值,特别当数据量较小时,而是想把他们替换成有效地数值。这时候我们使用fillna()方法,它将返回填充了缺失值后的数组副本。
In [21]:data = pd.Series([1, np.nan, 2, None, 3], index=list('abcde'))
data
Out[21]:
a 1.0
b NaN
c 2.0
d NaN
e 3.0
dtype: float64
我们使用一个任意值来填充缺失值:
In [22]:data.fillna(-99)
Out[22]:
a 1.0
b -99.0
c 2.0
d -99.0
e 3.0
dtype: float64
如果我们想要用缺失值前面的有效值进行填充,可以令method=ffill(forward-fill);
如果我们想要用缺失值后面的有效值进行填充,可以令method=bfill(back-fill);
In [23]:data.fillna(method="ffill") # 向前填充
Out[23]:
a 1.0
b 1.0
c 2.0
d 2.0
e 3.0
dtype: float64
In [24]:data.fillna(method="bfill") # 向后填充
Out[24]:
a 1.0
b 2.0
c 2.0
d 3.0
e 3.0
dtype: float64
DataFrame也同样适用,只要加上axis参数:
In [25]:df.fillna(method='ffill', axis=1) # axis=1,代表后一列向前一列看齐
Out[25]:
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
需要注意的是,假如在从前往后填充时,需要填充的缺失值前面没有值,那么它就仍然是缺失值。