在数据分析中,利用箱型图的方法对异常数据进行过滤,是一种很快速、很有效的异常数据处理方法。
箱形图(英文:Box plot),又称为盒须图、盒式图、盒状图或箱线图,是一种用作显示一组数据分散情况资料的统计图。因型状如箱子而得名。在各种领域也经常被使用,常见于品质管理,快速识别异常值。
箱形图最大的优点就是不受异常值的影响,能够准确稳定地描绘出数据的离散分布情况,同时也利于数据的清洗。
箱型图可以通过程序设置一个识别异常值的标准,即大于或小于箱型图设定的上下界的数值则识别为异常值,箱型图如下图所示:
其中,有5个重要的因素需要理解:
- 上四分位数U:表示所有样本中只有1/4的数值大于U ,即从大到小排序时U处于25%处。
- 下四分位数 L:表示所有样本中只有1/4的数值小于L,即从大到小排序时L处于75%处。
- 中位数Q:表示一组数由小到大排列处于中间位置的数,若序列数为偶数个,该组的中位数为中间两个数的平均数。
- 上限:表示非异常范围内的最大值,四分位距为 IQR=U-L,则上限为 U+1.5IQR。
- 下限:表示非异常范围内的最小值,下限为 L-1.5IQR。
不过箱型图进行异常数据处理也有它的局限性,比如并不适合巨量且时间跨度很长的数据,因为箱型图实在太客观了,只关注整体数据的分布,但并不会跟踪数据整体的变化趋势,所以这时候可以把数据分段后再逐步进行异常数据处理。
下面就用Python来实现箱型图异常数据处理,主要代码如下:
import pandas as pd
def box_plot_outliers(data_ser, box_scale):
"""
利用箱线图去除异常值
:param data_ser: 接收 pandas.Series 数据格式
:param box_scale: 箱线图尺度,默认用 box_plot(scale=3)进行清洗
:return:
"""
iqr = box_scale * (data_ser.quantile(0.75) - data_ser.quantile(0.25))
# 下阈值
val_low = data_ser.quantile(0.25) - iqr*1.5
# 上阈值
val_up = data_ser.quantile(0.75) + iqr*1.5
# 异常值
outlier = data_ser[(data_ser < val_low) | (data_ser > val_up)]
# 正常值
normal_value = data_ser[(data_ser > val_low) & (data_ser < val_up)]
return outlier, normal_value, (val_low, val_up)
# 过滤异常值
outlier, normal_value, value = box_plot_outliers(data[sensor], 3)
outlier = pd.DataFrame(outlier) # 异常数据
normal_value = pd.DataFrame(normal_value) # 正常数据
以实际数据进行测试,正常值和异常值的分布如图所示:
完整代码如下:
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.dates as mdate
from datetime import datetime, timedelta
from def_files_data import get_all_files, get_path_data
def box_plot_outliers(data_ser, box_scale):
"""
利用箱线图去除异常值
:param data_ser: 接收 pandas.Series 数据格式
:param box_scale: 箱线图尺度,取3
:return:
"""
iqr = box_scale * (data_ser.quantile(0.75) - data_ser.quantile(0.25))
# 下阈值
val_low = data_ser.quantile(0.25) - iqr*0.5
# 上阈值
val_up = data_ser.quantile(0.75) + iqr*0.5
# 异常值
outlier = data_ser[(data_ser < val_low) | (data_ser > val_up)]
# 正常值
normal_value = data_ser[(data_ser > val_low) & (data_ser < val_up)]
return outlier, normal_value, (val_low, val_up)
'''数据处理'''
def read_data(sensor, begin_time, end_time):
path = '../../data/SHMData/梁体位移/{}'.format(sensor)
# 读取所有文件名
files_list = get_all_files(path)
if len(files_list) > 0:
# 获取文件夹内的所有文件数据并拼接起来
data = get_path_data(files_list, begin_time, end_time)
# 过滤异常值
outlier, normal_value, value = box_plot_outliers(data['displacement'], 3)
outlier = pd.DataFrame(outlier)
normal_value = pd.DataFrame(normal_value)
return normal_value, outlier
'''异常值分布图'''
def Outliers_fig(fig_title, fig_num, col_name, xlabel, ylabel):
# 画布大小
fig = plt.figure(figsize=(14, 7), edgecolor='blue')
# 标题
plt.suptitle(fig_title, fontsize=20, x=0.5, y=0.970)
# 调整子图在画布中的位置
plt.subplots_adjust(bottom=0.145, top=0.9000, left=0.075, right=0.990)
ax = fig.add_subplot(fig_num)
ax.plot_date(normal_value.index, normal_value[col_name], 'bo', linewidth=1.5, label='正常值')
ax.plot_date(outlier.index, outlier[col_name], 'ro', linewidth=1.5, label='异常值')
ax.set_xlabel(xlabel, fontsize=18, labelpad=7)
ax.set_ylabel(ylabel, fontsize=18, labelpad=7)
ax.tick_params(labelsize=18, direction='out')
ax.grid(linestyle='--')
ax.legend(fontsize=15)
# 中文显示
mpl.rcParams['font.sans-serif'] = ['SimHei']
mpl.rcParams['axes.unicode_minus'] = False
return ax
'''设置刻度'''
def set_axis(ax):
plt.xlim([begin_time + timedelta(days=-1), end_time + timedelta(days=1)]) # 日期上下限
ax.xaxis.set_major_formatter(mdate.DateFormatter('%m-%d'))
dayLoc = mpl.dates.DayLocator(interval=5)
ax.xaxis.set_major_locator(dayLoc)
ax.xaxis.set_tick_params(rotation=0, labelsize=18)
if __name__ == '__main__':
sensor_list = ['B03-WY-001']
begin_time = datetime(2020, 1, 1, 0, 0, 0)
end_time = datetime(2020, 3, 31, 23, 59, 59)
title_time_begin = begin_time.strftime('%Y-%m')
title_time_end = end_time.strftime('%Y-%m')
# 绘图
for sensor in sensor_list:
normal_value, outlier = read_data(sensor, begin_time, end_time)
fig_title = "{}纵向位移异常值分布图,数据时间:{}——{}".format(sensor, title_time_begin, title_time_end)
Displacement_ax = Outliers_fig(fig_title, 111, 'displacement', '时间(month-day)', '位移(mm)')
set_axis(Displacement_ax)
plt.show()