在数据分析中,利用箱型图的方法对异常数据进行过滤,是一种很快速、很有效的异常数据处理方法。
箱形图(英文:Box plot),又称为盒须图、盒式图、盒状图或箱线图,是一种用作显示一组数据分散情况资料的统计图。因型状如箱子而得名。在各种领域也经常被使用,常见于品质管理,快速识别异常值。
箱形图最大的优点就是不受异常值的影响,能够准确稳定地描绘出数据的离散分布情况,同时也利于数据的清洗。

箱型图可以通过程序设置一个识别异常值的标准,即大于或小于箱型图设定的上下界的数值则识别为异常值,箱型图如下图所示:

python箱线图异常值 python箱形图各部分含义_数据分析


其中,有5个重要的因素需要理解:

  1. 上四分位数U:表示所有样本中只有1/4的数值大于U ,即从大到小排序时U处于25%处。
  2. 下四分位数 L:表示所有样本中只有1/4的数值小于L,即从大到小排序时L处于75%处。
  3. 中位数Q:表示一组数由小到大排列处于中间位置的数,若序列数为偶数个,该组的中位数为中间两个数的平均数。
  4. 上限:表示非异常范围内的最大值,四分位距为 IQR=U-L,则上限为 U+1.5IQR。
  5. 下限:表示非异常范围内的最小值,下限为 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)   # 正常数据

以实际数据进行测试,正常值和异常值的分布如图所示:

python箱线图异常值 python箱形图各部分含义_python箱线图异常值_02


完整代码如下:

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()