[PYTHON] Interactive visualization with ipywidgets and Bokeh

Why interactive

If you want to output a graph with some parameters, the following methods can be considered as a method to check each time the parameters are changed.

  1. Input the argument of the output function each time and output the graph
  2. Output all graphs of the parameters you want to change
  3. Interactively set parameters and output graph

The 1 method is a bit cumbersome to type. Even if it is on the IPython console, execute the function with Jupyter notebook and rewrite the argument of the executed function ... Yes, it is troublesome. How about the 2 method? It seems practical enough to output two or three graphs at once and compare them. However, in a situation where there are 100 such parameters, it seems difficult to check with the human eye. So, let's implement the 3 method.

Install ipywidgets

It's easy to use ʻipywidgets to implement an interactive UI with Jupyter notebook`. The installation method is as follows.

For pip

pip install ipywidgets
jupyter nbextension enable --py widgetsnbextension

For conda

conda install -c conda-forge ipywidgets

ipywidgets.interact

You can create an interactive UI just by decorating ʻipywidgets.interact to the desired function. In the example below, the stock price is acquired and the moving average of the closing price for n days is output to the graph. If you give a numerical value to the argument of ʻinteract, a slider will be displayed on the Jupyter notebook, and adjusting this will change the parameter of the moving average. n = (5, 30) sets the minimum value to 5 and the maximum value to 30. You can also set n = 10, which is the default value without any restrictions.

%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

In this way, it is possible to visually search for the appropriate value of the parameter in the UI.

As another example, let's change the graph type. If you give a tuple or list to the argument of ʻinteract`, a drop-down menu will be displayed. Let's use this to change the graph type in the UI.

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

chart_type.gif

By preparing the UI and options in advance in this way, it is possible to intuitively encourage operations even for those who do not understand the program.

Learn more about ipywidgets

See also in Documentation, but for a list of UIs provided by ʻipywidgets, see ʻipywidgets.widgets.Widget.widget_types. Is possible.

import ipywidgets as widgets
widgets.Widget.widget_types

There are only this ...

{'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}

As a matter of fact, I don't have the physical strength to explain all this, so I will use ʻipywidgets.widgets.Dropdown` as an example.

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)

The dropdown is displayed in ʻIPython.display.display and the event is handled in the ʻobserve method. The keyword argument names is passed the properties to pass to the ʻon_value_changefunction. Normally use'value'. When the dropdown value changes, ʻon_value_change is called to print the value of the'value' property.

Try combining it with Bokeh's push_notebook

Bokeh has a convenient method called bokeh.io.push_notebook, which allows you to handle graphs that have already been output and push changes to their contents. Let's dynamically change the color of the graph object using the dropdown mentioned above.

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

The points here are the following two points.

In this way, Bokeh allows you to make changes to the graph object on Jupyter notebook. The above-mentioned matplotlib ran the process of redrawing the entire graph, but Bokeh allows you to change only the necessary parts, so you can lighten the process of dynamic graphs.

Compared to matplotlib, Bokeh has a function that is conscious of Jupyter notebook from the beginning because it is a latecomer, and it seems that it can be said that it is a visualization tool that is compatible with Jupyter notebook.

Bonus (main story named)

This was the introduction. This article is the 16th day of jupyter notebook Advent Calendar 2016. The 15th day was Yakiu no Hito, so I will continue to try Yakiu.

Let's make a simple pitching machine on the assumption that there is a pitcher that can dynamically change the position of the circle on the graph and throw it in the upper, middle and lower parts.

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 height
STRIKE_ZONE_WIDTH = 432  #Strike zone width
STRIKE_ZONE_DIV = 3  #Divide into 3 and throw
zone_low = (0, int(STRIKE_ZONE_HEIGHT / STRIKE_ZONE_DIV))  #Low range
zone_mid = (zone_low[1], zone_low[1] * 2)  #Middle range
zone_high = (zone_mid[1], STRIKE_ZONE_HEIGHT)  #Higher range


#Decide the course
def get_cause(zone):
    return randint(0, STRIKE_ZONE_WIDTH), randint(*zone)


#Throw to a fixed course
def pitch(zone):
    x, y = get_cause(zone)
    r.data_source.data['x'] = [x]
    r.data_source.data['y'] = [y]
    #This is the point! Push to the specified handle
    push_notebook(handle=t)


#What happens when you click the button
def on_button_clicked(b):
    cause_dict = {'High': zone_high, 'Mid': zone_mid, 'Low': zone_low}
    pitch(cause_dict[b.description])

#Create a button object
h = widgets.Button(description="High")
m = widgets.Button(description="Mid")
l = widgets.Button(description="Low")

#Handling events when clicked
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))
#initial value
r = p.circle([STRIKE_ZONE_WIDTH / 2], [STRIKE_ZONE_HEIGHT / 2], size=20)
# notebook_handle=By adding True, you can operate it later.
t = show(p, notebook_handle=True)
display(h)
display(m)
display(l)

pitch.gif

For buttons, you can pass control with an event handler called ʻon_click`. See the Widget Events documentation (https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html) for more information. This time, the coordinates of the course were decided by random numbers, but if you insert the actual data and add the information of the ball type and ball speed, I think that you can implement the oleore one ball bulletin that is not inferior to the live baseball site. Previous person did the actual data collection, so if you are the one who is the one, please try it.

Summary

It's been a little longer, but here's a summary of what I wanted to tell you this time.

ʻIpywidgets and Bokehare good tools forJupyter notebook`, but they have a low awareness. I would be grateful if anyone could think of using this as an opportunity.

Recommended Posts

Interactive visualization with ipywidgets and Bokeh
Adjust widget styles and layouts with ipywidgets
Implement "Data Visualization Design # 3" with pandas and matplotlib
"Learning word2vec" and "Visualization with Tensorboard" on Colaboratory
Overview and tips of seaborn with statistical data visualization
Data visualization with pandas
With and without WSGI
Logistics visualization with Python
A simple interactive music player made with Chuck and OpenPose
Using Python with SPSS Modeler extension nodes ① Setup and visualization
Using MLflow with Databricks ② --Visualization of experimental parameters and metrics -
With me, cp, and Subprocess
Programming with Python and Tkinter
Encryption and decryption with Python
Understand t-SNE and improve visualization
Working with tkinter and mouse
Python and hardware-Using RS232C with Python-
Super-resolution with SRGAN and ESRGAN
group_by with sqlalchemy and sum
python with pyenv and venv
With me, NER and Flair
Works with Python and R
Use Bokeh with IPython Notebook