在数据处理的过程中,一般都需要进行数据的清洗工作,如数据集是否存在重复、是否存在缺失、数据是否具有完整性和一致性、数据中是否存在异常值等。当发现数据中存在如上可能的问题时,都需要有针对性地处理,本节介绍如何识别和处理重复观测、缺失值和异常值。
重复观测是指观测行存在重复的现象,重复观测的存在会影响数据分析和挖掘结果的准确性,所以在数据分析和建模之前需要进行观测的重复性检验,如果存在重复观测,还需要进行重复项的删除。 在搜集数据过程中,可能会存在重复观测的出现,例如通过网络爬虫,就比较容易产生重复数据。
如图所示,就是通过网络爬虫获得某APP市场中电商类APP的部分下载数据,通过肉眼,是能够发现这10行数据中的重复项的,例如,唯品会出现了两次、当当出现了三次。如果搜集上来的数据不是10行,而是10万行,就无法通过肉眼的方式检测数据是否存在重复项了。下面介绍如何运用Pandas对读入的数据进行重复项检查,以及如何删除数据中的重复项。
检测数据集的记录是否存在重复,Pandas中使用duplicated方法,该方法返回的是数据行每一行的检验结果,即每一行返回一个bool值。使用drop_duplicates方法移除重复值。
数据缺失在大部分数据分析应用中都很常见,Pandas使用浮点值NaN表示浮点或非浮点数组中的缺失数据,Python内置的None值也会被当作缺失值处理。Pandas中使用方法isnull检测是否为缺失值,检测对象的每个元素返回一个bool值。
缺失值的处理可以采用三种方法,分别是过滤法、填充法和插值法。过滤法又称删除法,是指当缺失的观测比例非常低时(如5%以内),直接删除存在缺失的观测;或者当某些变量的缺失比例非常高时(如85%以上),直接删除这些缺失的变量。填充法又称替换法,是指用某种常数直接替换那些缺失值,例如,对连续变量而言,可以使用均值或中位数替换;对于离散变量,可以使用众数替换。插值法是指根据其他非缺失的变量或观测来预测缺失值,常见的插值法有线性插值法、 近邻插值法、拉格朗日插值法等。
数据过滤dropna方法的语法格式如下:
dropna(axis=0, how='any', thresh=None)
其中:
当数据中出现缺失值时,还可以用其他的数值进行填充。常用的方法是fillna,其基本语法格式为 fillna(value=None, method=None, axis=None, inplace=False) 其中value值除了基本类型外,还可以使用字典,这样可以实现对不同的列填充不同的值。method表示采用的填补数据的方法,默认是None。
当出现缺失值时,也可以使用插值法对缺失值进行插补。其中的插值方法可以用:'linear','nearest','zero','slinear','quadratic','cubic','spline','barycentric','polynomial'。
异常值(Outlier)是指那些远离正常值的观测,即“不合群”观测。异常值的出现会对模型的创建和预测产生严重的后果。当然异常值也不一定都是坏事,有些情况下,通过寻找异常值就能够给业务带来良好的发展,如销毁“钓鱼”网站,关闭“薅羊毛”用户的权限等。
对于异常值的检测, 一般采用两种方法, 一种是标准差法, 另一种是箱线图 判别法。标准差法的判别公式是 outlier $>\bar{x}+n \sigma$ 或 outlier $<\bar{x}-n \sigma$, 其中 $\bar{x}$ 为样本均值, $\sigma$ 为样本标准差, 当 $n=2$ 时, 满足条件的观测就是异常值, 当 $n=3$ 时, 满足条件的观测即是极端异常值。箱线图的判别公式是 outlier $>Q_{3}+n I Q R$ 或 outlier $<Q_{1}-n I Q R$, 其中 $Q_{1}$ 为下四分位数 $(25 \%), Q_{3}$ 为上四分位数 $(75 \%), I Q R$ 为上四分位数 与下四分位数的差, 当 $n=1.5$ 时, 满足条件的观测为异常值, 当 $n=3$ 时, 满 足条件的观测即为极端异常值。
这两种方法的选择标准如下, 如果数据近似服从正态分布, 因为数据的 分布相对比较对称, 优先选择标准差法。否则优先选择箱线图法, 因为分位 数并不会受到极端值的影响。当数据存在异常时, 若异常观测的比例不太大 一般可以使用删除法将异常值删除; 也可以使用替换法, 可以考虑使用低于 判别上限的最大值替换上端异常值、高于判别下限的最小值替换下端异常 值, 或使用均值或中位数替换等。
太阳黑子个数文件sunspots.csv数据用Excel软件打开后的格式如图所示,时间范围是1700年~1988年,总共289个记录,识别并处理其中的异常值。
参考文献
import pandas as pd
a=pd.read_excel("data/duplicated.xlsx")
print("是否存在重复观测:",any(a.duplicated())) #输出:True
a.drop_duplicates(inplace=True) #inplace=True时,直接删除a中的重复数据
f=pd.ExcelWriter('Pdata4_26_2.xlsx') #创建文件对象
a.to_excel(f) #把a写入新Excel文件中
f.save() #保存文件,数据才真正写入Excel文件
是否存在重复观测: True
from numpy import NaN
from pandas import Series
data=Series([10.0, None, 20, NaN, 30])
print(data.isnull()) #输出每个元素的检测结果
print("是否存在缺失值:", any(data.isnull())) #输出:True
0 False 1 True 2 False 3 True 4 False dtype: bool 是否存在缺失值: True
from pandas import read_excel
a=read_excel("data/missing.xlsx",usecols=range(1,4))
b1=a.dropna() #删除所有的缺失值
b2=a.dropna(axis=1, thresh=9) #删除有效数据个数小于9的列
b3=a.drop('用户B', axis=1) #删除用户B的数据
print(b1,'\n---------------\n',b2,'\n---------------\n',b3)
用户A 用户B 用户C 0 235.83 324.03 478.320 1 236.27 325.63 515.450 2 238.05 328.08 517.090 6 237.41 391.26 516.230 8 237.61 388.02 435.350 9 238.03 206.43 487.675 --------------- 用户A 用户B 0 235.83 324.03 1 236.27 325.63 2 238.05 328.08 3 235.90 NaN 4 236.76 268.82 5 NaN 404.04 6 237.41 391.26 7 238.65 380.81 8 237.61 388.02 9 238.03 206.43 --------------- 用户A 用户C 0 235.83 478.320 1 236.27 515.450 2 238.05 517.090 3 235.90 514.890 4 236.76 NaN 5 NaN 486.090 6 237.41 516.230 7 238.65 NaN 8 237.61 435.350 9 238.03 487.675
from pandas import read_excel
a=read_excel("data/missing2.xlsx")
b1=a.fillna(0) #用0填补所有的缺失值
b2=a.fillna(method='ffill') #用前一行的值填补缺失值
b3=a.fillna(method='bfill') #用后一行的值填补,最后一行缺失值不处理
b4=a.fillna(value={'gender':a.gender.mode()[0], 'age':a.age.mean(), #年龄使用均值替换
'income':a.income.median()}) #收入使用中位数替换
from pandas import read_excel
import numpy as np
a=read_excel("data/missing2.xlsx")
b=a.fillna(value={'gender':a.gender.mode()[0], #性别使用众数替换
'age':a.age.interpolate(method='polynomial', order=2),
#年龄使用二次多项式插值替换
'income':a.income.interpolate()}) #收入使用线性插值替换
b
uid | regit_date | gender | age | income | |
---|---|---|---|---|---|
0 | 81200457 | 2016-10-30 | M | 23.000000 | 6500.0 |
1 | 81201135 | 2016-11-08 | M | 27.000000 | 10300.0 |
2 | 80043782 | 2016-10-13 | F | 27.875282 | 13500.0 |
3 | 84639281 | 2017-04-17 | M | 26.000000 | 6000.0 |
4 | 73499801 | 2016-03-21 | M | 21.748310 | 4500.0 |
5 | 72399510 | 2016-01-18 | M | 19.000000 | 7250.0 |
6 | 63881943 | 2015-10-07 | M | 21.000000 | 10000.0 |
7 | 35442690 | 2015-04-10 | F | 23.939144 | 5800.0 |
8 | 77638351 | 2016-07-12 | M | 25.000000 | 18000.0 |
9 | 85200189 | 2017-05-18 | M | 22.000000 | 18000.0 |
from pandas import read_csv
import numpy as np
import matplotlib.pyplot as plt
a=read_csv("data/sunspots.csv")
mu=a.counts.mean() #计算黑子个数年平均值
s=a.counts.std() #计算黑子个数标准差
print("标准差法异常值上限检测:",any(a.counts>mu+2*s)) #输出:True
print("标准差法异常值下限检测:",any(a.counts<mu-2*s)) #输出:False
标准差法异常值上限检测: True 标准差法异常值下限检测: False
Q1=a.counts.quantile(0.25) #计算下四分位数
Q3=a.counts.quantile(0.75) #计算上四分位数
IQR=Q3-Q1
print("箱线图法异常值上限检测:",any(a.counts>Q3+1.5*IQR)) #输出:True
print("箱线图法异常值下限检测:",any(a.counts<Q1-1.5*IQR)) #输出:False
plt.style.use('ggplot') #设置绘图风格
a.counts.plot(kind='hist',bins=30,density=True) #绘制直方图
a.counts.plot(kind='kde') #绘制核密度曲线
plt.show()
print("异常值替换前的数据统计特征",a.counts.describe())
UB=Q3+1.5*IQR;
st=a.counts[a.counts<UB].max() #找出低于判别上限的最大值
print("判别异常值的上限临界值为:",UB)
print("用以替换异常值的数据为:",st)
a.loc[a.counts>UB, 'counts']=st #替换超过判别上限异常值
print("异常值替换后的数据统计特征",a.counts.describe())
箱线图法异常值上限检测: True 箱线图法异常值下限检测: False
异常值替换前的数据统计特征 count 289.000000 mean 48.613495 std 39.474103 min 0.000000 25% 15.600000 50% 39.000000 75% 68.900000 max 190.200000 Name: counts, dtype: float64 判别异常值的上限临界值为: 148.85000000000002 用以替换异常值的数据为: 141.7 异常值替换后的数据统计特征 count 289.000000 mean 48.066090 std 37.918895 min 0.000000 25% 15.600000 50% 39.000000 75% 68.900000 max 141.700000 Name: counts, dtype: float64