• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Python实现Matplotlib库,可视化鼠标交互删除数据集的异常点

武飞扬头像
Natusme
帮助1

任务描述:

最近大三暑假,在某公司测试组实习,被布置了一个小任务:

有二维数据集绘散点图如下:

学新通

图中红圈部分表示异常点。这些异常点会对数据分析造成影响,所以必须对这些异常点进行删除,删除完毕以后再开展进一步分析工作。

常规删除的方法可以采用DBSCAN、LOF、KNN等聚类方法,但对于特定数据集,采用人工交互删除的方法可以使结果更准确。主要采用Python的Matplotlib库进行实现。

实现思路

1. 根据给定数据集,采用matplotlib.pyplot进行绘图,绘图方法参考资料https://zhuanlan.zhihu.com/p/93423829。(强烈推荐阅读,讲解较为详细。)

2. 利用matplotlib的鼠标响应事件,分别规定鼠标左键按下、鼠标左键按下状态下移动、鼠标左键释放等响应事件。可以设置为(1)鼠标左键按下后触发flag为True。(2)鼠标左键按下状态下(flag为True时)移动时将光标位置坐标保存,从而可以框选数据点。(3)鼠标左键释放后关闭flag为False。从而可以将想要删除的数据点框选,获取此多边形边框的边线点集合。参考资料https://blog.csdn.net/weixin_40400335/article/details/127192570?utm_medium=distribute.pc_relevant.none-task-blog-2~default~百度js_utm_term~default-8-127192570-blog-89040722.235^v38^pc_relevant_anti_t3_base&spm=1001.2101.3001.4242.5&utm_relevant_index=11

3. 利用matplotlib.path库,根据得到的边线点集合,构建多边形,将原始数据集每个点遍历判断是否在多边形内(判断方法:参考资料https://blog.csdn.net/u011119817/article/details/119986727)将在多边形内的数据点从数据集中删除。

4. 要求实时展现删除异常点后的当前数据集。得到删除异常点后的数据集后,先使用 ax.cla() 函数将画布清空,再重新绘制当前数据集的散点图,再使用 fig.canvas.draw() 更新画布即可。

5. 要求可以连续框选、删除。采用matplotlib的按钮功能,按钮功能参考资料:https://blog.csdn.net/weixin_40400335/article/details/127192570?utm_medium=distribute.pc_relevant.none-task-blog-2~default~百度js_utm_term~default-8-127192570-blog-89040722.235^v38^pc_relevant_anti_t3_base&spm=1001.2101.3001.4242.5&utm_relevant_index=11设置一个clean按钮,每次框选后,点击clean按钮,可以将数据集中选中的部分点删除,同时将多边形边框的边线点集合清空,以便继续进行框选、删除的操作。

6. 为了功能便捷,设置stepback和repaint按钮。分别为撤销一步框选删除操作,以及恢复原始数据集操作。

代码实现:

  1.  
    import matplotlib.pyplot as plt
  2.  
    import pandas as pd
  3.  
    import numpy as np
  4.  
    from matplotlib.backend_bases import MouseButton
  5.  
    from matplotlib.widgets import Button
  6.  
    import matplotlib.path as mplPath
  7.  
     
  8.  
     
  9.  
    class removeAnomalyPoints:
  10.  
    def __init__(self, fig, ax, dataset):
  11.  
    self.fig = fig
  12.  
    self.ax = ax
  13.  
    self.dataset_orig = dataset #原始数据集
  14.  
    self.dataset_last = dataset #保存上一步的数据集,便于stepback操作
  15.  
    self.dataset_now = dataset #用于操作的数据集,实时维护
  16.  
    self.dataset_now_withLabel = []
  17.  
    self.roi = []
  18.  
    self.press = False
  19.  
    self.ax.scatter(dataset[:, 0], dataset[:, 1])
  20.  
    self.button1_axes = plt.axes([0.5, 0.9, 0.1, 0.075])
  21.  
    self.button1 = Button(self.button1_axes, 'clean')
  22.  
    self.button2_axes = plt.axes([0.8, 0.9, 0.1, 0.075])
  23.  
    self.button2 = Button(self.button2_axes, 'repaint')
  24.  
    self.button3_axes = plt.axes([0.65, 0.9, 0.1, 0.075])
  25.  
    self.button3 = Button(self.button3_axes, 'stepback')
  26.  
     
  27.  
    def on_press(self, event):
  28.  
    if event.inaxes: # 判断鼠标是否在axes内
  29.  
    if event.button == MouseButton.LEFT: # 判断按下的是否为鼠标左键
  30.  
    # print("Start drawing")
  31.  
    self.press = True
  32.  
     
  33.  
    def on_move(self,event):
  34.  
    if event.inaxes:
  35.  
    if self.press == True:
  36.  
    x = event.xdata
  37.  
    y = event.ydata
  38.  
    self.roi.append([x,y])
  39.  
    self.ax.plot(x, y, '.', c='r') # 画点
  40.  
    self.fig.canvas.draw() # 更新画布
  41.  
     
  42.  
    def on_release(self,event):
  43.  
    if self.press == True:
  44.  
    self.press = False
  45.  
     
  46.  
    def get_dataset_now(self):
  47.  
    return self.dataset_now
  48.  
     
  49.  
    def get_roi_now(self):
  50.  
    return self.roi
  51.  
     
  52.  
    def get_deleted_dataset_now(self): #获取删除区域点后的dataset_now
  53.  
    label_now = np.zeros(len(self.dataset_now)) #label_now长度始终与self.dataset_now保持一致
  54.  
    self.roi = self.get_roi_now()
  55.  
    poly_path = mplPath.Path(self.roi)
  56.  
     
  57.  
    for i in range(len(self.dataset_now)):
  58.  
    if poly_path.contains_point(self.dataset_now[i]):
  59.  
    label_now[i] = 1
  60.  
    else:
  61.  
    label_now[i] = 0
  62.  
     
  63.  
    self.dataset_now_withLabel = np.c_[self.dataset_now, label_now]
  64.  
    deleted_dataset_now_withLabel = np.delete(self.dataset_now_withLabel, np.where(self.dataset_now_withLabel[:, 2] == 1), axis=0)
  65.  
    self.dataset_last = self.dataset_now
  66.  
    self.dataset_now = deleted_dataset_now_withLabel[:,:2]
  67.  
    return self.dataset_now
  68.  
     
  69.  
    def renew_img(self,event):
  70.  
    try:
  71.  
    self.get_deleted_dataset_now() #执行此函数,完成对self.dataset_now的更新
  72.  
    self.ax.cla()
  73.  
    self.ax.scatter(self.dataset_now[:, 0], self.dataset_now[:, 1])
  74.  
    self.roi = []
  75.  
    self.fig.canvas.draw() # 更新画布
  76.  
     
  77.  
    except:
  78.  
    print("Haven't selected area yet!") #异常处理
  79.  
     
  80.  
    def clean_to_orig(self,event):
  81.  
    self.ax.cla()
  82.  
    self.dataset_now = self.dataset_orig
  83.  
    self.dataset_last = self.dataset_orig
  84.  
    self.ax.scatter(self.dataset_now[:, 0], self.dataset_now[:, 1])
  85.  
    self.roi = []
  86.  
    self.fig.canvas.draw() # 更新画布
  87.  
     
  88.  
    def stepback(self,event):
  89.  
    self.dataset_now = self.dataset_last
  90.  
    self.dataset_last = self.dataset_now
  91.  
    self.ax.cla()
  92.  
    self.ax.scatter(self.dataset_now[:, 0], self.dataset_now[:, 1])
  93.  
    self.roi = []
  94.  
    self.fig.canvas.draw() # 更新画布
  95.  
     
  96.  
    def connect(self):
  97.  
    self.fig.canvas.mpl_connect("button_press_event", self.on_press)
  98.  
    self.fig.canvas.mpl_connect("button_release_event", self.on_release)
  99.  
    self.fig.canvas.mpl_connect("motion_notify_event", self.on_move)
  100.  
    self.fig.canvas.mpl_connect("key_press_event", self.clean_to_orig)
  101.  
    self.button1.on_clicked(self.renew_img)
  102.  
    self.button2.on_clicked(self.clean_to_orig)
  103.  
    self.button3.on_clicked(self.stepback)
学新通

代码简介:

removeAnomalyPoints类中,可以对传入的数据集dataset进行上述处理。

初始化时:

        (1)保存三个数据集,dataset_orig为原始数据集,在整个流程中都不更改;dataset_now为当前数据集(用于操作,删除异常点);dataset_last用于存上一步的数据集,每次dataset_now被修改之前,将dataset_now存入dataset_last。

        (2)self.roi用于存每次框选时的边界点集合。

        (3)原始数据集绘制散点图。

        (4)设置按钮位置、大小,初始化按钮对象。

成员函数:

        (1)on_press,on_move,on_release函数为鼠标触发响应的回调函数,在on_move中对鼠标按下状态下移动选的框保存边界点集合,并进行绘图(散点)。

        (2)get_dataset_now和get_roi_now分别为获取当前类内成员的函数,便于外部调用。

        (3)get_deleted_dataset_now用于接收框选的边界点集合,进行数据集点是否在框内的判断并写入label,根据label删除异常点得到新数据集dataset_now。

        (4)renew_img是clean按钮的回调函数,此函数中执行get_deleted_dataset_now获取最新的数据集,将当前数据集绘制散点图。

        (5)clean_to_orig和stepback分别是repaint和stepback按钮的回调函数,分别执行重置和撤销一步的操作。

        (6)connect定义了各种鼠标、按钮响应事件的触发。

功能展示:

1. 生成数据集:由以下generate_data函数生成规模为1000的二维数据集,其中800个点符合二次函数关系,另外200个点为异常点。

  1.  
    def generate_data():
  2.  
    # 生成二次函数数据点
  3.  
    num_quadratic = 800 # 二次函数数据点数量
  4.  
    x_quadratic = np.linspace(-10, 10, num_quadratic)
  5.  
    y_quadratic = x_quadratic ** 2 np.random.randn(num_quadratic) * 10
  6.  
     
  7.  
    # 生成异常点
  8.  
    num_outliers = 200 # 异常点数量
  9.  
    x_outliers = np.random.uniform(-10, 10, num_outliers)
  10.  
    y_outliers = np.random.uniform(-300, 300, num_outliers)
  11.  
     
  12.  
    # 合并数据点
  13.  
    x_data = np.concatenate((x_quadratic, x_outliers))
  14.  
    y_data = np.concatenate((y_quadratic, y_outliers))
  15.  
     
  16.  
    # 打乱数据点顺序
  17.  
    indices = np.random.permutation(x_data.shape[0])
  18.  
    x_data = x_data[indices]
  19.  
    y_data = y_data[indices]
  20.  
     
  21.  
    return x_data, y_data
学新通

2. 编写如下的主程序:

  1.  
    if __name__ == '__main__':
  2.  
    ##1.以下部分进行异常点去除
  3.  
    data_x, data_y = generate_data() # 生成一个含异常点的二次函数数据集
  4.  
    data = np.c_[data_x, data_y]
  5.  
    dataset_orig = np.array(data) # dataset_orig是n*2, array类
  6.  
    fig1, ax1 = plt.subplots()
  7.  
    img = removeAnomalyPoints(fig1, ax1, dataset_orig)
  8.  
    img.connect()
  9.  
    plt.show()
  10.  
    print("The removal of the anomaly has been completed!")

注意:创建removeAnomalyPoints类对象img后,需要调用img.connect()函数,使能通过监听对鼠标键盘的操作做出响应。

3. 效果如下:

学新通

        (1)每次框选最好画为闭合多边形框,而且每画一个框就需要点一次clean进行删除。

        (2)stepback只可以回退一步。

        (3)repaint可以将程序恢复至刚运行未做任何操作的状态。

补充说明:

1. 参考链接中判断点在多边形内部有两种python方法,第一种是利用matplotlib.path库相关函数,第二种是利用shapely库相关函数,第一种速度更快。

2. 由于matplotlib.path库生成框选多边形时的限制,删除异常点时,约定需要每次画一个闭合多边形框后就点按钮清除一次。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhhacjkh
系列文章
更多 icon
同类精品
更多 icon
继续加载