在Jupyter Python中有2个ipywidget对一个matplotlib绘图执行操作

2024-06-28 20:01:39 发布

您现在位置:Python中文网/ 问答频道 /正文

下面的代码模拟机器学习,线性回归过程。在

它的目的是允许用户在Jupyter笔记本中手动和可视化地进行回归,以更好地感受线性回归过程。在

函数的第一个部分(x,y)生成一个用于执行回归的绘图。在

下一个部分(a,b)生成用于模拟回归的线。在

我希望能够在不重新生成散点图的情况下更改坡度滑块。在

任何指导都是非常有帮助和欢迎的。:-)

import numpy as np
import ipywidgets as widgets
from ipywidgets import interactive
import matplotlib.pyplot as plt    

def scatterplt(rand=3, num_points=20, slope=1):

    x = np.linspace(3, 9, num_points)
    y = np.linspace(3, 9, num_points)

    #add randomness to scatter
    pcent_rand = rand
    pcent_decimal = pcent_rand/100
    x = [n*np.random.uniform(low=1-pcent_decimal, high=1+ pcent_decimal) for n in x]
    y = [n*np.random.uniform(low=1-pcent_decimal, high=1+ pcent_decimal) for n in y]

    #plot regression line
    a = np.linspace(0, 9, num_points)
    b = [(slope * n) for n in a]

    #format & plot the figure
    plt.figure(figsize=(9, 9), dpi=80)
    plt.ylim(ymax=max(x)+1)
    plt.xlim(xmax=max(x)+1)

    plt.scatter(x, y)

    plt.plot(a, b)

    plt.show()


#WIDGETS    

interactive_plot = interactive(scatterplt, 
                 rand = widgets.FloatSlider(
                                value=3,
                                min=0,
                                max=50,
                                step=3,
                                description='Randomness:', num_points=(10, 50, 5)
                                ),
                 num_points = widgets.IntSlider(
                                value=20,
                                min=10,
                                max=50,
                                step=5,
                                description='Number of points:'
                                ),
                 slope=widgets.FloatSlider(
                                value=1,
                                min=-1,
                                max=5,
                                step=0.1,
                                description='Slope'
                                )

                )

interactive_plot

Tags: importplotasnppltwidgetsnummax
3条回答

{{{1}你也可以使用{cd2}代替^{cd2}。 你得到的是一个按钮,让你手动运行功能,一旦你高兴。在

你需要这两条线

from ipywidgets import interactive, interact_manual
interactive_plot = interact_manual(scatterplt,
...

第一次运行时,您应该看到: enter image description here

单击按钮后,将显示完整输出: enter image description here

问题的一部分是很难修改Matplotlib图形中的单个元素,也就是说,从头开始重新绘制整个绘图要容易得多。重新绘制整个图形不会是超快速或平滑的。因此,我将向您展示一个如何在BQplot中执行此操作的示例(如Pascal Bugnion所建议的)。它不是Matplotlib,我猜你可能想要的,但它确实演示了一种方法,可以将斜率和随机性指令以及计算从每个滑块中分离出来,同时仍然使用标准的交互式小部件。在

enter image description here

import bqplot as bq
import numpy as np
import ipywidgets as widgets


def calcSlope(num_points, slope):
    a = np.linspace(0, 9, num_points)
    b = a * slope

    line1.x = a
    line1.y = b


def calcXY(num_points, randNum):
    x = np.linspace(3, 9, num_points)
    y = x

    #add randomness to scatter
    x = np.random.uniform(low=1-randNum/100, high=1+ randNum/100, size=(len(x))) * x
    y = np.random.uniform(low=1-randNum/100, high=1+ randNum/100, size=(len(y))) * y

    #format & plot the figure
    x_sc.min = x.min()
    x_sc.max = x.max() + 1

    scat.x = x
    scat.y = y        



def rand_int(rand):
    calcXY(num_i.children[0].value, rand)

def num_points_int(num_points):
    calcXY(num_points, rand_i.children[0].value)
    calcSlope(num_points, slope_i.children[0].value)

def slope_int(slope):
    calcSlope(num_i.children[0].value, slope)



rand_i = widgets.interactive(rand_int, 
                 rand = widgets.FloatSlider(
                                value=3,
                                min=0,
                                max=50,
                                step=3,
                                description='Randomness:', num_points=(10, 50, 5)
                                )
                              )


num_i = widgets.interactive(num_points_int, 
                 num_points = widgets.IntSlider(
                                value=20,
                                min=10,
                                max=50,
                                step=5,
                                description='Number of points:'
                                )
                              )


slope_i = widgets.interactive(slope_int, 
                 slope=widgets.FloatSlider(
                                value=1,
                                min=-1,
                                max=5,
                                step=0.1,
                                description='Slope'
                                )
                              )


# Create the initial bqplot figure
x_sc = bq.LinearScale()
ax_x = bq.Axis(label='X', scale=x_sc, grid_lines='solid', tick_format='0f')
ax_y = bq.Axis(label='Y', scale=x_sc, orientation='vertical', tick_format='0.2f')

line1 = bq.Lines( scales={'x': x_sc, 'y': x_sc} , colors=['blue'],display_legend = False, labels=['y1'],stroke_width = 1.0)
scat = bq.Scatter(scales={'x': x_sc, 'y': x_sc} , colors=['red'],display_legend = False, labels=['y1'],stroke_width = 1.0)


calcSlope(num_i.children[0].value, slope_i.children[0].value)
calcXY(num_i.children[0].value, rand_i.children[0].value)

m_fig = dict(left=100, top=50, bottom=50, right=100)
fig = bq.Figure(axes=[ax_x, ax_y], marks=[line1,scat], fig_margin=m_fig, animation_duration = 1000)

widgets.VBox([rand_i,num_i,slope_i,fig])

interactive函数并不能真正让您访问这个粒度级别。它总是运行整个scatterplt回调。基本上,interactive的目的是让一类问题变得非常简单一旦你离开了这类问题,它就不再适用了。在

然后你必须回到其他的小部件机器。这在最初可能有点难以理解,因此,为了最小化跳跃,我将首先解释一下interactive在引擎盖下的作用。在

当您调用interactive(func, widget)时,它将创建widget,并在{}更改时绑定回调。回调在Output小部件(docs)中运行funcOutput小部件捕获func的全部输出。interactive然后将widget和输出小部件打包到VBox(一个用于堆叠小部件的容器)。在

回到你现在想做的事情上。您的申请有以下条件:

  1. 我们需要保持某种形式的内部状态:应用程序需要记住随机变量的x和y位置
  2. 我们需要不同的行为来运行基于什么滑块被触发。在

为了满足(1),我们可能应该创建一个类来维护状态。为了满足(2),我们需要根据调用的滑块运行不同的回调。在

像这样的东西似乎能满足你的需要:

import numpy as np
import ipywidgets as widgets
import matplotlib.pyplot as plt

class LinRegressDisplay:

    def __init__(self, rand=3.0, num_points=20, slope=1.0):
        self.rand = rand
        self.num_points = num_points
        self.slope = slope
        self.output_widget = widgets.Output()  # will contain the plot
        self.container = widgets.VBox()  # Contains the whole app
        self.redraw_whole_plot()
        self.draw_app()

    def draw_app(self):
        """
        Draw the sliders and the output widget

        This just runs once at app startup.
        """
        self.num_points_slider = widgets.IntSlider(
            value=self.num_points,
            min=10,
            max=50,
            step=5,
            description='Number of points:'
        )
        self.num_points_slider.observe(self._on_num_points_change, ['value'])
        self.slope_slider = widgets.FloatSlider(
            value=self.slope,
            min=-1,
            max=5,
            step=0.1,
            description='Slope:'
        )
        self.slope_slider.observe(self._on_slope_change, ['value'])
        self.rand_slider = widgets.FloatSlider(
            value=self.rand,
            min=0,
            max=50,
            step=3,
            description='Randomness:', num_points=(10, 50, 5)
        )
        self.rand_slider.observe(self._on_rand_change, ['value'])
        self.container.children = [
            self.num_points_slider,
            self.slope_slider,
            self.rand_slider ,
            self.output_widget
        ]

    def _on_num_points_change(self, _):
        """
        Called whenever the number of points slider changes.

        Updates the internal state, recomputes the random x and y and redraws the plot.
        """
        self.num_points = self.num_points_slider.value
        self.redraw_whole_plot()

    def _on_slope_change(self, _):
        """
        Called whenever the slope slider changes.

        Updates the internal state, recomputes the slope and redraws the plot.
        """
        self.slope = self.slope_slider.value
        self.redraw_slope()

    def _on_rand_change(self, _):
        self.rand = self.rand_slider.value
        self.redraw_whole_plot()

    def redraw_whole_plot(self):
        """
        Recompute x and y random variates and redraw whole plot

        Called whenever the number of points or the randomness changes.
        """
        pcent_rand = self.rand
        pcent_decimal = pcent_rand/100
        self.x = [
            n*np.random.uniform(low=1-pcent_decimal, high=1+pcent_decimal) 
            for n in np.linspace(3, 9, self.num_points)
        ]
        self.y = [
            n*np.random.uniform(low=1-pcent_decimal, high=1+pcent_decimal)
            for n in np.linspace(3, 9, self.num_points)
        ]
        self.redraw_slope()

    def redraw_slope(self):
        """
        Recompute slope line and redraw whole plot

        Called whenever the slope changes.
        """
        a = np.linspace(0, 9, self.num_points)
        b = [(self.slope * n) for n in a]

        self.output_widget.clear_output(wait=True)
        with self.output_widget as f:
            plt.figure(figsize=(9, 9), dpi=80)
            plt.ylim(ymax=max(self.y)+1)
            plt.xlim(xmax=max(self.x)+1)

            plt.scatter(self.x, self.y)
            plt.plot(a, b)
            plt.show()

app = LinRegressDisplay()
app.container  # actually display the widget

最后一点,移动滑块时,动画仍有点刺耳。为了更好的交互性,我建议查看bqplot。特别是,Chakri Cherukuri有一个很好的example of linear regression,这与您正在尝试做的有点相似。在

相关问题 更多 >