常见的数据异常值有空值、重复值、经验异常值、分布异常值等。下面分别讨论通过Pandas的一般处理方式。
1.空值处理
df
Out[1]:
商品名称 地区 销量
0 李老吉 北京 10.0
1 娃啥啥 上海 13.0
2 康帅傅 广州 NaN
(1)判断空值
df.isna()
Out[2]:
商品名称 地区 销量
0 False False False
1 False False False
2 False False True
(2)判断一列是否全为空
df
Out[1]:
name age
0 bob NaN
1 tim NaN
2 lucy NaN
默认是对所有列进行判断,也可先选择列再进行判断
df.isna().all()
Out[2]:
name age
False True
(3)df.isna和df.isnull
有说这个设计是借鉴R语言,na和null是两个不同对象。而再python里,pandas是基于numpy实现的。numpy的空值就只有一个np.nan,因此这两个是一样的,因为两个方法也是一样的。查看源码也可验证
(4)丢弃空值
参数解释:表示在行的方向上,只要有一个空值则舍弃这一行数据。如果只想删除全为空的行,则传入how='all'。默认原df不会改变,可使用新的变量接收返回值。inplace=True表示原df改变。
df.dropna(axis=0, how='any', inplace=True)
Out[3]:
商品名称 地区 销量
0 李老吉 北京 10.0
1 娃啥啥 上海 13.0
(5)填充空值
①使用一个固定值填充
# 空值填充为0
df.fillna(0)
Out[4]:
商品名称 地区 销量
0 李老吉 北京 10.0
1 娃啥啥 上海 13.0
2 康帅傅 广州 0.0
②向前或向后填充
一般针对时序数据,且某一刻数据和前后时刻差别不大
df
ut[1]:
时间 温度
0 00:00 5.0
1 00:10 4.9
2 00:20 NaN
3 00:30 4.7
# 向前填充,即复制上一个数据;bfill为向后填充;也可以直接使用df.ffill()
# limit=1表示只填充一个空值
df.fillna(method='ffill', limit=1)
Out[2]:
时间 温度
0 00:00 5.0
1 00:10 4.9
2 00:20 4.9
3 00:30 4.7
③使用整体数据的均值、最值等填充
df.fillna(df['温度'].mean())
Out[3]:
时间 温度
0 00:00 5.000000
1 00:10 4.900000
2 00:20 4.866667
3 00:30 4.700000
2.重复值处理
(1)判断重复行
可以看到,第二次出现的重复行会被标记为True
df
Out[1]:
商品名称 地区 销量
0 李老吉 北京 15
1 娃啥啥 上海 13
2 康帅傅 广州 28
3 娃啥啥 上海 13
df.duplicated()
Out[2]:
0 False
1 False
2 False
3 True
(2)删除重复行
df.drop_duplicates()
Out[1]:
商品名称 地区 销量
0 李老吉 北京 15
1 娃啥啥 上海 13
2 康帅傅 广州 28
默认是,当这一行与前面某一行所有元素都重复才删除。否则,需要指定判断重复的标志列。默认保留第一行重复值,也可指定保留最后一行。
df.drop_duplicates(['商品名称'], keep='last')
Out[2]:
商品名称 地区 销量
0 李老吉 北京 15
2 康帅傅 广州 28
3 娃啥啥 上海 13
3.经验异常值处理
经验指的是行业经验,即要结合具体业务。还是以气温为例子。放在全球来讲,气温最高也就50左右。那么可以简单认为超过60的气温数据就是异常。结合上一章讲的数据过滤即可实现。
df
Out[1]:
城市 温度
0 北京 -5
1 上海 5
2 广州 15
3 基加利 75
# 只选择温度小于60的
df[df['温度']<60]
Out[2]:
城市 温度
0 北京 -5
1 上海 5
2 广州 15
4.分布异常
这是我自己定义的一个概念,类似离群点的概念。即某几个数据和整体数据距离较远。且往往这几个数据没有明显偏离经验值。所以不能通过上面的方法筛选。实现方法:针对高维数据,可以使用离群算法;针对某一维数据,可以直接使用四分位数的概念。
# 温度整体在-5℃左右,因此5℃将会被剔除
df = pd.DataFrame({'时间': ['00:00', '00:10', '00:20', '00:30', '00:40', '00:50', '01:00', '01:20'],
'温度': [-5, -6, -7, -6, -5, -4, 5, -4]})
# 上四分位数
up_q = df['温度'].quantile(0.75)
# 下四分位数
low_q = df['温度'].quantile(0.25)
# k=1.5是个经验值,根据整体数据的离散程度调节。一般范围[1.5, 3)
dis = 1.5*(up_q - low_q)
# 上边界
up_limit = up_q + dis
# 下边界
low_limit = low_q - dis
print(df[df['温度'] < up_limit][df['温度'] > low_limit])
# 输出:
时间 温度
0 00:00 -5
1 00:10 -6
2 00:20 -7
3 00:30 -6
4 00:40 -5
5 00:50 -4
7 01:20 -4
5.替换异常值
其实,前面讲的向前、向后填充也算是一种替换。不过,Pandas还提供了其他替换方式。
(1)通过df.replace
df
Out[1]:
商品名称 地区 销量
0 李老吉 北京 15.0
1 娃啥啥 上海 13.0
2 康帅傅 广州 28.0
3 娃啥啥 上海 NaN
# 空值替换为0
df.replace(np.nan, 0)
Out[2]:
商品名称 地区 销量
0 李老吉 北京 15.0
1 娃啥啥 上海 13.0
2 康帅傅 广州 28.0
3 娃啥啥 上海 0.0
如果想对不同的异常值进行不同的替换,只需传入两个列表,替换前后元素做好对应
df
Out[1]:
商品名称 地区 销量
0 李老吉 北京 0.0
1 娃啥啥 上海 13.0
2 康帅傅 广州 28.0
3 娃啥啥 上海 NaN
# 0替换为-1;空值替换为-2
df.replace([0, np.nan], [-1, -2])
Out[2]:
商品名称 地区 销量
0 李老吉 北京 -1.0
1 娃啥啥 上海 13.0
2 康帅傅 广州 28.0
3 娃啥啥 上海 -2.0
(2) 通过筛选赋值
df = pd.DataFrame({'商品名称': ['李老吉', '娃啥啥', '康帅傅', '娃啥啥'],
'地区': ['北京', '上海', '广州', '上海'],
'销量': [28, 13, 28, 99]})
# 将销量大于50的替换为50
df['销量'][df['销量']>50] = 50
df
Out[2]:
商品名称 地区 销量
0 李老吉 北京 28
1 娃啥啥 上海 13
2 康帅傅 广州 28
3 娃啥啥 上海 50