使用ipywidgets和Bokeh进行交互式可视化


为什么要互动

如果要输出带有某些参数的图形,可以将以下方法视为每次更改参数时都要检查的方法。

  • 每次输入输出函数的参数并输出图形
  • 输出要更改的参数的所有图形
  • 交互式设置参数和输出图
  • 1方法的键入有点麻烦。即使它在IPython控制台上,也要使用Jupyter notebook执行该函数并重写已执行函数的参数???是的,这很麻烦。
    2方法怎么样?一次输出几个图并进行比较似乎很实用。
    然而,在有100个这样的参数的情况下,似乎很难用肉眼检查。
    因此,让我们实现3方法。

    安装ipywidgets

    使用ipywidgets使用

    Jupyter notebook来实现交互式UI很容易。
    安装方法如下。

    对于点

    1
    2
    pip install ipywidgets
    jupyter nbextension enable --py widgetsnbextension

    对于康达

    1
    conda install -c conda-forge ipywidgets

    ipywidgets.interact

    只需将ipywidgets.interact装饰为所需功能即可创建交互式UI。
    在下面的示例中,获取了股票价格,并将n天收盘价的移动平均值输出到该图。
    如果为interact的参数提供数值,则会在Jupyter notebook上显示一个滑块,并对其进行调整将更改移动平均值的参数。
    n=(5, 30)将最小值设置为5,将最大值设置为30
    您还可以设置n=10,并且仅设置默认值,没有任何限制。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    %matplotlib inline

    from pandas_datareader.data import DataReader
    from ipywidgets import interact

    price = DataReader('^GSPC', 'yahoo', start='2016-01-01', end='2016-12-31')


    @interact(n=(5, 30))
    def plot_rolling_mean(n):
        price['Adj Close'].plot()
        price['Adj Close'].rolling(n).mean().plot()

    rolling_mean.gif

    这样,可以在UI中直观地搜索参数的适当值。

    另一个示例,让我们更改图形类型。
    如果为interact的参数提供一个元组或列表,将显示一个下拉菜单。
    让我们使用它来更改UI中的图形类型。

    1
    2
    3
    @interact(kind=['line', 'area'])
    def plot_line_or_band(kind):
        price['Adj Close'].plot(kind=kind)

    chart_type.gif

    通过以这种方式预先准备UI和选项,即使对于不了解程序的用户,也可以直观地鼓励操作。

    进一步了解ipywidgets

    文档中可以看到,您可以看到ipywidgets.widgets.Widget.widget_typesipywidgets提供的UI列表。

    1
    2
    import ipywidgets as widgets
    widgets.Widget.widget_types

    只有这个???

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    {'Jupyter.Accordion': ipywidgets.widgets.widget_selectioncontainer.Accordion,
     'Jupyter.BoundedFloatText': ipywidgets.widgets.widget_float.BoundedFloatText,
     'Jupyter.BoundedIntText': ipywidgets.widgets.widget_int.BoundedIntText,
     'Jupyter.Box': ipywidgets.widgets.widget_box.Box,
     'Jupyter.Button': ipywidgets.widgets.widget_button.Button,
     'Jupyter.Checkbox': ipywidgets.widgets.widget_bool.Checkbox,
     'Jupyter.ColorPicker': ipywidgets.widgets.widget_color.ColorPicker,
     'Jupyter.Controller': ipywidgets.widgets.widget_controller.Controller,
     'Jupyter.ControllerAxis': ipywidgets.widgets.widget_controller.Axis,
     'Jupyter.ControllerButton': ipywidgets.widgets.widget_controller.Button,
     'Jupyter.Dropdown': ipywidgets.widgets.widget_selection.Dropdown,
     'Jupyter.FlexBox': ipywidgets.widgets.widget_box.FlexBox,
     'Jupyter.FloatProgress': ipywidgets.widgets.widget_float.FloatProgress,
     'Jupyter.FloatRangeSlider': ipywidgets.widgets.widget_float.FloatRangeSlider,
     'Jupyter.FloatSlider': ipywidgets.widgets.widget_float.FloatSlider,
     'Jupyter.FloatText': ipywidgets.widgets.widget_float.FloatText,
     'Jupyter.HTML': ipywidgets.widgets.widget_string.HTML,
     'Jupyter.Image': ipywidgets.widgets.widget_image.Image,
     'Jupyter.IntProgress': ipywidgets.widgets.widget_int.IntProgress,
     'Jupyter.IntRangeSlider': ipywidgets.widgets.widget_int.IntRangeSlider,
     'Jupyter.IntSlider': ipywidgets.widgets.widget_int.IntSlider,
     'Jupyter.IntText': ipywidgets.widgets.widget_int.IntText,
     'Jupyter.Label': ipywidgets.widgets.widget_string.Label,
     'Jupyter.PlaceProxy': ipywidgets.widgets.widget_box.PlaceProxy,
     'Jupyter.Play': ipywidgets.widgets.widget_int.Play,
     'Jupyter.Proxy': ipywidgets.widgets.widget_box.Proxy,
     'Jupyter.RadioButtons': ipywidgets.widgets.widget_selection.RadioButtons,
     'Jupyter.Select': ipywidgets.widgets.widget_selection.Select,
     'Jupyter.SelectMultiple': ipywidgets.widgets.widget_selection.SelectMultiple,
     'Jupyter.SelectionSlider': ipywidgets.widgets.widget_selection.SelectionSlider,
     'Jupyter.Tab': ipywidgets.widgets.widget_selectioncontainer.Tab,
     'Jupyter.Text': ipywidgets.widgets.widget_string.Text,
     'Jupyter.Textarea': ipywidgets.widgets.widget_string.Textarea,
     'Jupyter.ToggleButton': ipywidgets.widgets.widget_bool.ToggleButton,
     'Jupyter.ToggleButtons': ipywidgets.widgets.widget_selection.ToggleButtons,
     'Jupyter.Valid': ipywidgets.widgets.widget_bool.Valid,
     'jupyter.DirectionalLink': ipywidgets.widgets.widget_link.DirectionalLink,
     'jupyter.Link': ipywidgets.widgets.widget_link.Link}

    我没有足够的体力来解释所有这些,因此我将以ipywidgets.widgets.Dropdown为例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from IPython.display import display
    d = widgets.Dropdown(options=['red', 'green', 'blue'], value='blue')


    def on_value_change(change):
        print(change['new'])


    d.observe(on_value_change, names='value')

    display(d)

    下拉列表由

    IPython.display.display显示,并且事件由observe方法处理。
    关键字参数names被传递给属性,以传递给on_value_change函数。通常使用'value'
    下拉值更改时,将调用on_value_change并打印'value'属性的值。

    尝试将其与Bokeh的push_notebook结合使用

    Bokeh有一个方便的方法,称为bokeh.io.push_notebook,它使您可以处理已经输出的图形并将更改推入其内容。
    让我们使用上述下拉列表动态更改图形对象的颜色。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    from IPython.display import display
    import ipywidgets as widgets
    from bokeh.io import output_notebook, push_notebook
    from bokeh.plotting import figure, show

    d = widgets.Dropdown(options=['red', 'green', 'blue'], value='blue')


    def on_value_change(change):
        r.glyph.fill_color = change['new']
        push_notebook(handle=t)


    d.observe(on_value_change, names='value')

    p = figure(width=250, height=250)
    r = p.circle(1, 1, size=20, line_color=None)

    output_notebook()

    display(d)
    t = show(p, notebook_handle=True)

    change_color.gif

    这里的要点是以下两点。

    • t = show(p, notebook_handle=True)show方法的关键字参数中设置notebook_handle=True,以使图形可处理。

    • 将更改推送到上面在push_notebook(handle=t)中设置的处理程序

    这样,Bokeh允许您更改Jupyter notebook上的图形对象。
    在上述的matplotlib中,运行了重绘整个图形的过程,但是在Bokeh中,只能更改必要的部分,因此可以简化动态图形的过程。

    matplotlib相比,

    Bokeh具有从一开始就意识到Jupyter notebook的功能,因为它是后来者,可以说它是与Jupyter notebook兼容的可视化工具。

    奖金(主要故事命名)

    这是介绍。
    本文是jupyter笔记本Advent Calendar 2016的第16天。
    由于第15天是个小孩子,因此我将继续尝试该故事。

    假设有一个可以动态改变图形上圆的位置并将其扔到上部,中部和下部的投手,让我们制作一个简单的投球机。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    from random import randint
    from bokeh.io import output_notebook, push_notebook
    from bokeh.plotting import figure, show
    from IPython.display import display
    import ipywidgets as widgets


    STRIKE_ZONE_HEIGHT = 750  # ストライクゾーンの高さ
    STRIKE_ZONE_WIDTH = 432  # ストライクゾーンの幅
    STRIKE_ZONE_DIV = 3  # 3分割して投げ分ける
    zone_low = (0, int(STRIKE_ZONE_HEIGHT / STRIKE_ZONE_DIV))  # 低めのレンジ
    zone_mid = (zone_low[1], zone_low[1] * 2)  # 真ん中のレンジ
    zone_high = (zone_mid[1], STRIKE_ZONE_HEIGHT)  # 高めのレンジ


    # コースを決定
    def get_cause(zone):
        return randint(0, STRIKE_ZONE_WIDTH), randint(*zone)


    # 決められたコースに投げる
    def pitch(zone):
        x, y = get_cause(zone)
        r.data_source.data['x'] = [x]
        r.data_source.data['y'] = [y]
        # ここがポイント!指定したハンドルにpush
        push_notebook(handle=t)


    # ボタンをクリックしたときの動作
    def on_button_clicked(b):
        cause_dict = {'High': zone_high, 'Mid': zone_mid, 'Low': zone_low}
        pitch(cause_dict[b.description])

    # ボタンオブジェクトを作成
    h = widgets.Button(description="High")
    m = widgets.Button(description="Mid")
    l = widgets.Button(description="Low")

    # クリックしたときのイベントをハンドリング
    h.on_click(on_button_clicked)
    m.on_click(on_button_clicked)
    l.on_click(on_button_clicked)


    output_notebook()


    p = figure(
        width=250,
        height=250,
        x_range=(0, STRIKE_ZONE_WIDTH),
        y_range=(0, STRIKE_ZONE_HEIGHT))
    # 初期値
    r = p.circle([STRIKE_ZONE_WIDTH / 2], [STRIKE_ZONE_HEIGHT / 2], size=20)
    # notebook_handle=Trueをつけることで、後で操作できるように
    t = show(p, notebook_handle=True)
    display(h)
    display(m)
    display(l)

    pitch.gif

    对于

    按钮,可以使用名为on_click的事件处理程序传递控制。
    查看窗口小部件事件文档以获取更多详细信息。
    这次,路线的坐标是由随机数决定的,但是如果您插入实际数据并添加球类型和球速度的信息,我认为您可以实施不差劣质的单球速攻新闻到现场棒球现场。
    前一个人进行了实际的数据收集,因此如果您是那个人,请尝试一下。

    概要

    已经有些久了,但这是我这次想告诉你的摘要。

    • 您只需装饰interact即可非常轻松地创建UI

    • 稍微精巧的人可以从widgets制作出理想的人
    • Bokeh中的push_notebook使创建动态图更加容易

    ipywidgetsBokehJupyter notebook的良好工具,但意识不足。
    如果有人可以考虑将其作为机会,我将不胜感激。