Matplotlib和散点图
Matplotlib是一个用于绘制数据可视化图形的Python库。学习Matplotlib是探索数据可视化领域的重要一步。
散点图是指在回归分析中,数据点在直角坐标系平面上的分布图,散点图表示因变量随 自变量而变化的大致趋势,据此可以选择合适的函数对数据点进行拟合。
交互式散点图,指用户可以通过多种方式与之交互。这包括能够批量选择和突出显示组中的单个散点,以及对高亮显示的点执行操作(例如删除、复制、移动)等功能。
Highlighter Class
首先,在单独的文件中创建 Highlighter 类。将逐一讨论每种方法的创建。
import pandas as pd
import numpy as np
from matplotlib.widgets import RectangleSelector
import keyboard
class Highlighter(object):
def __init__(self, canvas, ax, x, y):
self.canvas = canvas
self.ax = ax
self.x, self.y = x, y
self.mask = np.zeros(x.shape, dtype=bool)
self._highlight = self.ax.scatter([], [], s=50, color='yellow', zorder=10)
self.selector = RectangleSelector(self.ax, self.select, useblit=True, state_modifier_keys={"center": "alt"})
Highlighter 类的核心部分是 Rectangle Selector,它是一个 matplotlib 功能,用于创建拖动工具。这仅在为拖动工具创建 UI 时有用,但是,实际的选择、突出显示和其他功能将来自我们。
此函数的另一个重要部分是self.mask
属性,它将是一个 0 和 1 值的列表,其长度与散点图中的点数相同。这将跟踪我们的积分当前是否被选中。此列表的 index-2 处的值为 1 表示我们图中的第三个散点当前突出显示。可以同时选择多个点。
def update(self, x, y):
self.x, self.y = x, y
self.mask = np.zeros(x.shape, dtype=bool)
接下来是update
函数,每当我们想用新值更新散点图(或删除一些值)时,都会调用该函数。我们更新 highlighter 类中的 x 和 y 值,并将蒙版的大小调整为散点图中的新点数。请注意,这不会影响实际的散点图,其代码将位于主应用程序中的其他位置。
def select(self, event1, event2):
prevOffsets = []
prevMask = None
if keyboard.is_pressed('ctrl'):
prevOffsets = self._highlight.get_offsets()
prevMask = self.mask
self.clear_highlights()
self.mask |= self.inside(event1, event2)
xy = np.column_stack([self.x[self.mask], self.y[self.mask]])
if len(prevOffsets) > 0:
xy = np.concatenate((xy, prevOffsets))
self.mask |= prevMask
if len(xy):
self._highlight.set_offsets(xy)
self.canvas.draw_idle()
最重要的功能是select方法,每次使用矩形选择器创建的拖动工具时,矩形选择器都会调用该方法。之所以调用它,是因为我们将Highlighter类的select函数作为第二个参数传递给了矩形选择器的构造函数。
此函数执行以下操作:
1.跟踪以前突出显示的值,以便在按下CTRL键时可以进行多阶段拖动选择。
2.调用inside方法,该方法返回一个布尔值列表,表示哪些点在所选区域内,哪些点不在。掩码属性被分配了这些值。inside方法使用两个事件计算此列表,第一个事件是拖动的开始位置(按住鼠标的位置)和结束位置(释放鼠标的地方)。
def inside(self, event1, event2):
"""Returns a boolean mask of the points inside the rectangle defined by
event1 and event2."""
x0, x1 = sorted([event1.xdata, event2.xdata])
y0, y1 = sorted([event1.ydata, event2.ydata])
mask = ((self.x > x0) & (self.x < x1) &
(self.y > y0) & (self.y < y1))
return mask
3.使用掩码过滤x和y值,并将其存储在xy变量中,该变量将成为需要突出显示的点的坐标。最后,调用set_offsets方法,该方法将处理实际的突出显示。
def set_offsets(self, xy):
self._highlight.remove()
self._highlight = self.ax.scatter([], [], s=50, color='yellow', zorder=10)
if len(xy):
self._highlight.set_offsets(xy)
self.canvas.draw_idle()
这是通过在要选择的点的位置上绘制黄色散点来实现的。或者,您可以修改代码,单独更新选定的散点,并将其颜色更改为黄色。
def clear_highlights(self):
self._highlight.remove()
self._highlight = self.ax.scatter([], [], s=50, color='yellow', zorder=10)
self.mask = np.zeros(self.x.shape, dtype=bool)
self.canvas.draw_idle()
此类中还使用了clear_highlights方法来清除现有的高光。
highlights类-完整代码
import pandas as pd
import numpy as np
from matplotlib.widgets import RectangleSelector
import keyboard
class Highlighter(object):
def __init__(self, canvas, ax, x, y, ):
self.canvas = canvas
self.ax = ax
self.x, self.y = x, y
self.mask = np.zeros(x.shape, dtype=bool)
self._highlight = self.ax.scatter([], [], s=50, color='yellow', zorder=10)
self.selector = RectangleSelector(self.ax, self.select, useblit=True, state_modifier_keys={"center": "alt"})
def update(self, x, y):
self.x, self.y = x, y
self.mask = np.zeros(x.shape, dtype=bool)
def select(self, event1, event2):
prevOffsets = []
prevMask = None
if keyboard.is_pressed('ctrl'):
prevOffsets = self._highlight.get_offsets()
prevMask = self.mask
self.clear_highlights()
self.mask |= self.inside(event1, event2)
xy = np.column_stack([self.x[self.mask], self.y[self.mask]])
if len(prevOffsets) > 0:
xy = np.concatenate((xy, prevOffsets))
self.mask |= prevMask
if len(xy):
self._highlight.set_offsets(xy)
self.canvas.draw_idle()
def clear_highlights(self):
self._highlight.remove()
self._highlight = self.ax.scatter([], [], s=50, color='yellow', zorder=10)
self.mask = np.zeros(self.x.shape, dtype=bool)
self.canvas.draw_idle()
def set_offsets(self, xy):
self._highlight.remove()
self._highlight = self.ax.scatter([], [], s=50, color='yellow', zorder=10)
if len(xy):
self._highlight.set_offsets(xy)
self.canvas.draw_idle()
def check_inside(self, point1, point2):
"""Returns a boolean mask of the points inside the rectangle defined by
event1 and event2."""
x0, x1 = sorted([point1[0], point2[0]])
y0, y1 = sorted([point1[1], point2[1]])
mask = ((self.x > x0) & (self.x < x1) &
(self.y > y0) & (self.y < y1))
return mask
def inside(self, event1, event2):
"""Returns a boolean mask of the points inside the rectangle defined by
event1 and event2."""
x0, x1 = sorted([event1.xdata, event2.xdata])
y0, y1 = sorted([event1.ydata, event2.ydata])
mask = ((self.x > x0) & (self.x < x1) &
(self.y > y0) & (self.y < y1))
return mask
GUI 应用程序
这是我们的驱动程序应用程序的代码。我们使用 Tkinter GUI 开发了一个应用程序来包装我们的 Matplotlib 图形。您不需要执行此操作,并且可以相应地修改代码(不会进行一些小的更改)。但是,预计任何需要此类高级功能的图形应用程序也将有一个 GUI 库(因为 matplotlib GUI 是有限的)。
这里唯一需要注意的方法是 delete 方法,它处理所选点的删除。您可以使用 delete 方法作为执行其他操作(如复制、粘贴、移动等)的模板。
import tkinter as tk
from tkinter import ttk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from Highlighter import Highlighter
import pandas as pd
import random
class MatplotlibApp:
def __init__(self, master):
self.master = master
self.master.title("Matplotlib App")
self.data = pd.DataFrame(columns=["x", "y"])
self.points = []
self.create_widgets()
def create_widgets(self):
self.fig = Figure(figsize=(5, 4), dpi=100)
self.ax = self.fig.add_subplot(111)
self.ax.set_xlim(-1, 11)
self.ax.set_ylim(-1, 11)
self.canvas = FigureCanvasTkAgg(self.fig, master=self.master)
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.canvas.get_tk_widget().bind("<Delete>", self.delete)
self.canvas.draw()
tk.Button(self.master, text="Add Random Point", command=self.add).pack(padx=20, pady=20)
self.highlighter = Highlighter(self.canvas, self.ax, self.data["x"], self.data["y"])
def add(self):
x, y = [random.randint(0, 10), random.randint(0, 10)]
df = pd.DataFrame([[x, y]], columns=["x", "y"])
self.data = pd.concat([self.data, df], ignore_index=True)
self.points.append(self.ax.scatter(x, y, color="blue"))
self.highlighter.update(self.data["x"], self.data["y"])
self.canvas.draw()
def delete(self, event):
self.selected_regions = self.highlighter.mask
self.data = self.data[~self.selected_regions].reset_index(drop=True)
for i, artist in enumerate(self.points):
if self.selected_regions[i]:
artist.remove()
self.points = [artist for artist, m in zip(self.points, self.selected_regions) if m != 1]
self.highlighter.update(self.data["x"], self.data["y"])
self.highlighter.clear_highlights()
self.canvas.draw()
def main():
root = tk.Tk()
app = MatplotlibApp(root)
root.mainloop()
if __name__ == "__main__":
main()
标签:canvas,self,mask,散点图,Matplotlib,xy,交互式,ax,def
From: https://blog.51cto.com/u_14249042/11991719