Pandas处理缺失值
我们在现实生活中真正需要处理的数据并不是十全十美的,往往会出现数据缺失的现象,更为甚者,处理不同数据源缺失值的方法还不同
本节将介绍一些处理缺失值的通用规则,Pandas对缺失值的表现形式以及Pandas子代的几个处理缺失值的工具的用法.
一般来说涉及的缺失值有三种形式:null,NaN和NA
如何标记缺失值
缺失值是数据的一种特殊状态,不代表任何数据,因此我们为了将缺失值和正常数字区分开,就需要来标记缺失值.
一般标记缺失值有两种方法:通过全局的掩码和使用标签值
全局掩码
全局掩码的意思是不对原数组进行任何操作,而是重新开辟一个同样大小,同样形状的布尔数组,用对应位的布尔值来表示数据是否缺失.
例如
list_1=[[1,2,3]
[缺失值,5,6]]
list_1_mask=[[1,1,1],
[0,1,1]]
但是使用全局掩码却会造成一个额外的布尔数组的开辟,当需要处理的数据较大时,会造成空间浪费
使用标签值
使用标签值来区分缺失值的含义就是在正常表示范围内,取某一个值来表示缺失值
例如,4字节16比特的数字,第一位为符号位,取如下二进制数组为缺失值:1111 1111 1111 1111
使用标签值的问题也很大,将会减少我们数字的表示范围,而且会影响编程逻辑,因为正常来说1111 1111 1111 1111是会被当成正常数据参与运算的,但是如果其表示缺失值的话,那么任何一个数字与缺失值相加都会变成缺失值,这就意味着编程时需要额外的逻辑
一般来说,在大多数情况下是不存在最佳选择的,不同的编程语言与不同的系统使用的标记缺失值的方法不同,例如R语言每种数据类型中保留一个比特作为缺失数据的标签值,SciDB系统会在每个单元后面额外增加一个字节表示缺失状态
Pandas的缺失值
Pandas原本可以按照R语言一样,通过比特位来为每一种数据类型标注缺失值.但是R语言本身只包含4种数据类型,而Numpy却支持14种基本数据类型,想要标记每种数据类型的数据是否缺失,需要分出的比特位就太多了,何况编码会极度的不方便
因此综合考虑下,Pandas最终选择使用标签的方法来表示缺失值,包括:浮点数据类型的NaN,Python的None对象
不过Pandas也支持使用全局掩码的方式来表示缺失值
下面将分别介绍Python的None对象和浮点数据类型的NaN
Python的None对象
None是原生Python内置的一个对象,他经常在代码中表示缺失值
由于None是Python的对象,因此不能作为Numpy和Pandas中数组类型的缺失值,只能在Object类型数组(即由Python对象构成的数组)中表示缺失值
例如
array_1=np.array([1,2,3,4])
array_2=np.array([1,2,None,4])
print(array_1.dtype)
print(array_2.dtype)
>>>
int64
object
这里将array_2中的所有元素视为Python的对象,而非经过Numpy加速过的对象,因此所有的处理都将在Python层面完成,这样就导致了速度会很慢,消耗更多的资源
此外,None由于是Python中的对象,但是Python却并没有定义None对象与任何其他的数据之间的运算,因此如果None对象参与运算,Python就会报错
array_1=np.array([1,2,3,4])
array_2=np.array([1,2,None,4])
print(array_1+array_2)
>>>
Traceback (most recent call last):
File "TryPandas.py", line 231, in <module>
print(array_1+array_2)
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
浮点数据类型的NaN
正如其名,NaN本质上是一个浮点数,不过被选取来表示缺失值,这是IEEE所规定的,全球通用的指定的特殊字符
NaN全称是Not a number,在任何系统中都兼容的特殊浮点数
例如
array_1=np.array([1,2,np.nan,4])
print(array_1.dtype)
>>>
float64
可以看到,原本是整型数组的array_1被自动的转化为float64了
既然NaN是一个特殊的浮点数,因此NaN就可以参与运算而不会让编译器报错,只不过任何数组与NaN进行运算都会变成NaN
array_1=np.array([1,2,np.nan,4])
array_2=np.array([1,2,3,4])
print(array_1+array_2)
print(array_1.sum())
print(array_1.min())
>>>
[ 2. 4. nan 8.]
nan
nan
注意,运算不仅包括四则远算,还包括逻辑运算等等
因此,为了避免NaN的污染效益,Numpy提供了一些特殊的累计函数,又称为NaN安全版本,他们可以忽略nan的影响
例如
array_1=np.array([1,2,np.nan,4])
print(np.nansum(array_1))
print(np.nanmin(array_1))
>>>
7.0
1.0
None与NaN的差异
虽然None与NaN各有各的用处,但是Pandas将两者视为等同的,并在适当的时侯会将两者进行替换.
例如
Series_1=pd.Series(range(4),index=list('abcd'))
print(Series_1)
Series_1['a']=None
print(Series_1)
>>>
a 0
b 1
c 2
d 3
dtype: int64
a NaN
b 1.0
c 2.0
d 3.0
dtype: float64
可以发现None被自动转化为NaN
除此以外,虽然Pandas目前只有这两种缺失值,但是GitHub上Pandas的维护人员却有人提议创建Pandas原生的NA来标记缺失值
处理缺失值
我们知道,Pandas基本上把None和NaN看作是可以等价交换的缺失值形式,而Padas提供了用于发现,剔除和替换缺失值的方法,主要包括以下集中
- isnull()
- notnull()
- dropna()
- fillna()
发现缺失值
发现缺失值有两种方法,isnull()和notnull()
两种方法将会返回布尔类型的全局掩码
Series_1=pd.Series([1,2,None,np.nan],index=list('abcd'))
print(Series_1)
print(Series_1.isnull())
print(Series_1.notnull())
>>>
a 1.0
b 2.0
c NaN
d NaN
dtype: float64
a False
b False
c True
d True
dtype: bool
a True
b True
c False
d False
dtype: bool
结合我们之前讲的,可以直接将布尔类型的掩码数组作为花哨的索引使用
Series_1=pd.Series([1,2,None,np.nan],index=list('abcd'))
print(Series_1)
print(Series_1[Series_1.isnull()])
print(Series_1[Series_1.notnull()])
>>>
a 1.0
b 2.0
c NaN
d NaN
dtype: float64
c NaN
d NaN
dtype: float64
a 1.0
b 2.0
dtype: float64
剔除缺失值
Pandas提供了很好的剔除缺失值的方法,就是dropna()方法
对于Series对象,直接调用即可
Series_1=pd.Series([1,None,3,np.nan],index=list('abcd'))
print(Series_1)
print(Series_1.dropna())
>>>
a 1.0
b NaN
c 3.0
d NaN
dtype: float64
a 1.0
c 3.0
dtype: float64
可以发现,所有具有NaN的连带索引都被丢弃了,最终返回一个新的Series对象
对于DataFrame对象,在真实情况中我们可能需要删除NaN所在的行或者列,因此对于DataFrame对象,dropna()方法就有一些参数可以配置
默认情况下,将会剔除NaN所在的整行数据,但是我们可以指定axis参数来剔除列
我们也可以指定how参数来指定整行或整列只要有NaN就丢弃还是整行整列都是NaN才会丢弃
DataFrame_1=pd.DataFrame(np.random.randint(0,10,(4,5)),index=list('abcd'),columns=list('abcde'))
DataFrame_1.iloc[:,2]=np.nan
DataFrame_1.iloc[0,0]=np.nan
+
print(DataFrame_1)
print('')
print(DataFrame_1.dropna())
print('')
print(DataFrame_1.dropna(axis='columns'))
print('')
print(DataFrame_1.dropna(axis=1))
print('')
print(DataFrame_1.dropna(how='any'))
print('')
print(DataFrame_1.dropna(axis='columns',how='all'))
>>>
a b c d e
a NaN 5 NaN 5 4
b 0.0 4 NaN 5 3
c 8.0 0 NaN 1 6
d 2.0 8 NaN 9 9
Empty DataFrame
Columns: [a, b, c, d, e]
Index: []
b d e
a 5 5 4
b 4 5 3
c 0 1 6
d 8 9 9
b d e
a 5 5 4
b 4 5 3
c 0 1 6
d 8 9 9
Empty DataFrame
Columns: [a, b, c, d, e]
Index: []
a b d e
a NaN 5 5 4
b 0.0 4 5 3
c 8.0 0 1 6
d 2.0 8 9 9
填充缺失值
有的时候我们并不想剔除缺失值,因为这样会导致我们的数组形状发生改变.
因此我们有的时候想要保持数组的形状在不改变的基础上,对缺失值进行填充,例如全部填充为0或者均值
我们可以使用isnull()来获取掩码数组,然后利用花哨的索引来直接填充所有的缺失值
Pandas提供了更加高效的fillna()方法来根据需求填充缺失值
对于Series对象,我们可以指定填充值
也可以指定method参数来指定填充方法,可以是从前往后填充ffill(forward-fill),也可以是从后往前bfill(back-fill)
Series_1=pd.Series([0,1,2,None,3,4,np.nan],index=list('abcdefg'))
print(Series_1)
print(Series_1.fillna(0))
print(Series_1.fillna(method='ffill'))
print(Series_1.fillna(method='bfill'))
>>>
a 0.0
b 1.0
c 2.0
d NaN
e 3.0
f 4.0
g NaN
dtype: float64
a 0.0
b 1.0
c 2.0
d 0.0
e 3.0
f 4.0
g 0.0
dtype: float64
a 0.0
b 1.0
c 2.0
d 2.0
e 3.0
f 4.0
g 4.0
dtype: float64
a 0.0
b 1.0
c 2.0
d 3.0
e 3.0
f 4.0
g NaN
dtype: float64
需要注意的是如果填充的前一个或者后一个也是NaN,或者是第一个NaN,填充的结果也是NaN
针对DataFrame对象的填充则可以指定填充的行列
DataFrame_1=pd.DataFrame(np.random.randint(0,10,(4,5)),\
index=list('abcd'),columns=list('abcde'))
DataFrame_1.iloc[:,3]=np.nan
DataFrame_1.iloc[2,2]=None
print(DataFrame_1)
print(DataFrame_1.fillna(0))
print(DataFrame_1.fillna(axis=1,method='ffill'))
print(DataFrame_1.fillna(axis=0,method='bfill'))
>>>
a b c d e
a 8 9 6.0 NaN 6
b 6 4 2.0 NaN 5
c 1 1 NaN NaN 5
d 7 1 4.0 NaN 1
a b c d e
a 8 9 6.0 0.0 6
b 6 4 2.0 0.0 5
c 1 1 0.0 0.0 5
d 7 1 4.0 0.0 1
a b c d e
a 8.0 9.0 6.0 6.0 6.0
b 6.0 4.0 2.0 2.0 5.0
c 1.0 1.0 1.0 1.0 5.0
d 7.0 1.0 4.0 4.0 1.0
a b c d e
a 8 9 6.0 NaN 6
b 6 4 2.0 NaN 5
c 1 1 4.0 NaN 5
d 7 1 4.0 NaN 1