缺失数据的处理

1. 缺失数据的表现
对于一些数据,可能某些值是空的,是缺失的。pandas中可以有多种处理缺失数据的方式。在pandas中,缺失数据的表现是NaN(Not a Number),可以使用isnull()函数检测出是否有数据缺失。

In [4]: data = pd.Series(['aaa','bbb',np.nan,'ddd'])In [5]: dataOut[5]:0    aaa1    bbb2    NaN3    ddddtype: objectIn [6]: data.isnull()Out[6]:0    False1    False2     True3    Falsedtype: bool

isnull返回一个布尔型的对象,这些布尔值表示哪些值是缺失值NaN,该对象返回的类型和原类型一样。

处理缺失数据的方式有多种,常见为删除缺失数据与填充缺失数据。

2. 丢弃缺失数据
pandas的dropna方法可以有效的删除掉缺失数据,对于Series来说,dropna方法返回一个仅含非空数据和索引值的Series:

In [7]: dataOut[7]:0    aaa1    bbb2    NaN3    ddddtype: objectIn [8]: data.dropna()Out[8]:0    aaa1    bbb3    ddddtype: object

对于DataFrame,dropna可以删除所有含有空值的行或列,也可以根据个人删除指定的行或列,dropna默认会删除所有含有NaN的行:

In [17]: dfOut[17]:  name  age area0   aa   18   北京1  NaN   18   广州2   cc   20  NaNIn [18]: df.dropna()Out[18]:  name  age area0   aa   18   北京

传入how='all',那么dropna只会删除一行全是缺失值的行:

In [19]: df.loc[3]=[np.nan,np.nan,np.nan]In [20]: dfOut[20]:  name   age area0   aa  18.0   北京1  NaN  18.0   广州2   cc  20.0  NaN3  NaN   NaN  NaNIn [21]: df.dropna(how='all')Out[21]:  name   age area0   aa  18.0   北京1  NaN  18.0   广州2   cc  20.0  NaN

如果要删除有缺失值的一列,只需要传入axis=1:

In [25]: df['avg'] = [20,30,40,50]In [26]: dfOut[26]:  name   age area  avg0   aa  18.0   北京   201  NaN  18.0   广州   302   cc  20.0  NaN   403  NaN   NaN  NaN   50In [27]: df.dropna(axis=1)Out[27]:   avg0   201   302   403   50

同样,dropna(axis=1,how='all') 只会删除整列都是缺失值的那一列。
如果想留下部分含有缺失值的数据作为观察,或者说只想删除部分数据,那么就需要使用thresh参数了,保留至少有n个非NaN数据的行/列:

In [52]: dfOut[52]:  name   age area  avg0   aa  18.0   北京   20 # 这一行4个值都不是NaN值,非NaN值为41  NaN  18.0   广州   30 # 有1个NaN值,非NaN值为32   cc  20.0  NaN   40 # 有1个NaN值,非NaN值为33  NaN   NaN  NaN   50 # 有3个NaN值,非NaN值为1In [53]: df.dropna(thresh=4) # 选取非空值至少有4个的行Out[53]:  name   age area  avg0   aa  18.0   北京   20In [54]: df.dropna(thresh=2) Out[54]:  name   age area  avg0   aa  18.0   北京   201  NaN  18.0   广州   302   cc  20.0  NaN   40In [55]: df.dropna(thresh=4,axis=1) # 选取非空值至少有4个的列Out[55]:   avg0   201   302   403   50

3. 填充缺失数据
对于缺失数据不想丢弃而想填充新的数据,fillna()方法也许是首要选择。通过一个常数调用fillna就会将缺失值替换为那个常数值:

In [62]: dfOut[62]:  name   age area  avg0   aa  18.0   北京   201  NaN  18.0   广州   302   cc  20.0  NaN   403  NaN   NaN  NaN   50In [64]: df.fillna(0)Out[64]:  name   age area  avg0   aa  18.0   北京   201    0  18.0   广州   302   cc  20.0    0   403    0   0.0    0   50

若是通过一个字典调用fillna,就可以实现对不同的列填充不同的值:

In [61]: df.fillna({'name':'dd','age':22,'area':'深圳'})Out[61]:  name   age area  avg0   aa  18.0   北京   201   dd  18.0   广州   302   cc  20.0   深圳   403   dd  22.0   深圳   50

fillna默认会返回新对象,但也可以对现有对象进行就地修改:

In [65]: df.fillna(0,inplace=True)In [66]: dfOut[66]:  name   age area  avg0   aa  18.0   北京   201    0  18.0   广州   302   cc  20.0    0   403    0   0.0    0   50

数据去重

数据中也有可能出现多行数据重复的情况,DataFrame的duplicated方法返回一个布尔型的对象,表示各行是否是重复的行,它每次都会对比前面出现的行,如果出现与前面出现的行一样的行,那么这一行就是重复行:

In [68]: dataOut[68]:    k1  k20  one   11  two   12  one   23  two   34  one   35  two   46  two   4 # 这一行与前一行重复,由于这一行在第5行后,所以会被判断为重复行In [69]: data.duplicated()Out[69]:0    False1    False2    False3    False4    False5    False 6     True dtype: bool

drop_duplicates方法,它会返回一个DataFrame,重复的数组会标为False并且删除:

In [70]: data.drop_duplicates()Out[70]:    k1  k20  one   11  two   12  one   23  two   34  one   35  two   4

上面两个方法默认会判断全部列,如果只想根据一列进行重复值判断,可以传入包含列索引的列表:

In [75]: dataOut[75]:    k1  k2  v10  one   1   01  two   1   12  one   2   23  two   3   34  one   3   45  two   4   56  two   4   6In [76]: data.drop_duplicates(['k1'])Out[76]:    k1  k2  v10  one   1   01  two   1   1

duplicated和drop_duplicates默认保留的是第一个出现的值组合。传入keep='last'则保留最后一个:

In [51]: data.drop_duplicates(['k1', 'k2'], keep='last')Out[51]:     k1  k2  v10  one   1   01  two   1   12  one   2   23  two   3   34  one   3   46  two   4   6

数据替换

有的时候需求是将数据框的某个值替换掉,pandas提供了replace方法,注意它与字符串的replace方法不一样。
pandas的replace方法操作的对象是DataFrame的值。

In [83]: dataOut[83]:   age name0  -20   小明1   20   老王2   23   苹果In [84]: data.replace(-20,0) # 将-20替换为0Out[84]:   age name0    0   小明1   20   老王2   23   苹果

如果要让替换值有不同的值,要传入一个列表

In [88]: data.replace(['小明','老王'],['猕猴桃','西瓜'])Out[88]:    age name0  -20  猕猴桃1   20   西瓜2   23   苹果

替换多个值:

In [90]: data.replace(['小明','老王'],'苹果')Out[90]:   age name0  -20   苹果1   20   苹果2   23   苹果

数据文本函数

假如有如下数据:

In [94]: dataOut[94]:     position   avg              brief0    Java开发    10     ['开发','工程师','高新']1    数据分析师   13      ['分析师','大数据']2    算法工程师   15        ['算法','数学']

需求是在avg的列的值加上字母k,去掉brief列的值的方括号和单引号。pandas中有很多种方法可以实现,下面介绍一种简单的方法。

+k需求:

In [98]: data.avgOut[98]:0    101    132    15Name: avg, dtype: int64In [99]: data.avg.astype('str')+'k'Out[99]:0    10k1    13k2    15kName: avg, dtype: objectIn [100]: data['avg'] =  data.avg.astype('str')+'k'In [101]: dataOut[101]:  position  avg              brief0   Java开发  10k  ['开发','工程师','高新']1    数据分析师  13k      ['分析师','大数据']2    算法工程师  15k        ['算法','数学']

去除方括号和去除单引号需求

In [104]: data.briefOut[104]:0    ['开发','工程师','高新']1        ['分析师','大数据']2          ['算法','数学']Name: brief, dtype: objectIn [105]: data.brief.str[1:-1]Out[105]:0    '开发','工程师','高新'1        '分析师','大数据'2          '算法','数学'Name: brief, dtype: objectIn [106]: data.brief.str[1:-1].str.replace("'","")Out[106]:0    开发,工程师,高新1      分析师,大数据2        算法,数学Name: brief, dtype: object