Share @ LinkedIn Facebook  matplotlib, ipywidgets
How to link ipywidgets widget with matplotlib chart to dynamically update charts

How to link ipywidgets widget with matplotlib chart to dynamically update charts?

The matplotlib is the most frequently used and quite famous python library for plotting charts. It has been extensively developed over the years and provides extensive API for plotting various charts. All the charts generated by matplotlib are static. When doing exploratory data analysis, its quite common to explore data from various perspectives to understand it better. We can merge matplotlib charts with ipywidgets widgets in order to generate interactive charts which updates itself whenever the values of widget changes.

As a part of this tutorial, we'll be explaining how ipywidgets widgets can be linked to matplotlib charts to make them interactive. We have already covered an extensive tutorial on ipywidgets which is available here:

Please feel free to gro through a tutorial on ipywidgets if needed to better understand the working of widgets.

We'll start by importing necessary libraries.

In [6]:
import pandas as pd
import numpy as np

import matplotlib.pyplot as plt
import ipywidgets as widgets

import warnings
warnings.filterwarnings("ignore")

%matplotlib widget

Please make a note that we have used matplotlib with widget extension by calling jupyter magic command %matplotlib widget. The reason behind this is that we want a matplotlib plot as a widget that can be easily linked to other widgets and easily modified.

Load Datasets

We'll be using the IRIS dataset and apple OHLC dataset for creating various examples.

  • IRIS Dataset: It has information about the measurement of three different types of IRIS flowers. The dataset is easily available from scikit-learn.
  • Apple OHLC Dataset: It has information about apple OHLC price data from Apr 2019 - Mar 2020.

We'll be loading both datasets as pandas dataframe.

In [7]:
from sklearn.datasets import load_iris

iris = load_iris()

iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_df["FlowerType"] = iris.target #[iris.target_names[t] for t in iris.target]

iris_df.head()
Out[7]:
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) FlowerType
0 5.1 3.5 1.4 0.2 0
1 4.9 3.0 1.4 0.2 0
2 4.7 3.2 1.3 0.2 0
3 4.6 3.1 1.5 0.2 0
4 5.0 3.6 1.4 0.2 0
In [8]:
apple_df = pd.read_csv("datasets/AAPL.csv", parse_dates=["Date"])

apple_df.head()
Out[8]:
Date Open High Low Close Adj Close Volume
0 2019-04-05 196.449997 197.100006 195.929993 197.000000 194.454758 18526600
1 2019-04-08 196.419998 200.229996 196.339996 200.100006 197.514709 25881700
2 2019-04-09 200.320007 202.850006 199.229996 199.500000 196.922470 35768200
3 2019-04-10 198.679993 200.740005 198.179993 200.619995 198.027985 21695300
4 2019-04-11 200.850006 201.000000 198.440002 198.949997 196.379578 20900800

Example 1

The first example consists of drawing scatter plot showing the relationship between two measurements of IRIS flower color-encoded by flower type. We'll be creating two dropdowns for trying various combinations of parameters to understand the relationship between all features combinations of the dataset. There are 4 features of the dataset which are petal width, petal length, sepal width, and sepal length. The dropdowns can be used to check the relationship between all of them.

We have created a method named create_scatter() which takes two string parameters which are x and y-axis features names of the dataset that will be used to create a scatter plot.

In [9]:
def create_scatter(feature1, feature2):
    with plt.style.context("ggplot"):
        fig = plt.figure(figsize=(8,4))

        plt.scatter(x = iris_df[feature1],
                    y = iris_df[feature2],
                    c=iris_df["FlowerType"],
                    s=20
                   )

        plt.xlabel(feature1.capitalize())
        plt.ylabel(feature2.capitalize())

        plt.title("%s vs %s"%(feature1.capitalize(), feature2.capitalize()))

The simplest way to convert the above function into an interactive chart with widgets is by using the interact() function from ipywidgets. We just need to pass it function name and list of values for parameter and it'll create UI for us with widgets and chart. It'll even link widgets with a chart so that any changes in widget value will result in updating the chart. We can change various dropdown values and the chart will update accordingly.

In [ ]:
widgets.interact(create_scatter, feature1=iris.feature_names, feature2=iris.feature_names);

How to link ipywidgets widget with matplotlib chart to dynamically update charts?

Example 2

The second example is exactly the same as the previous example with the only change that we can create the same UI as the previous step by keeping the interact() method as annotation of create_scatter() method. All other things are the same. We now don't need to pass the function name to interact() as its already working as a decorator of function.

In [ ]:
@widgets.interact(feature1=iris.feature_names, feature2=iris.feature_names)
def create_scatter(feature1, feature2):
    with plt.style.context("ggplot"):
        fig = plt.figure(figsize=(8,4))

        plt.scatter(x = iris_df[feature1],
                    y = iris_df[feature2],
                    c=iris_df["FlowerType"],
                    s=20
                   )

        plt.xlabel(feature1.capitalize())
        plt.ylabel(feature2.capitalize())

        plt.title("%s vs %s"%(feature1.capitalize(), feature2.capitalize()))

How to link ipywidgets widget with matplotlib chart to dynamically update charts?

Example 3

Our third example is the same as our previous example with one minor change which is the position of widgets. The last two examples created widgets by themselves as well as the decided location of widgets also by themselves. Many times we need more control with ourselves and need to change this default UI to layout individual components differently. We might want to layout widgets at the end of the chart or any other combinations.

One way to do that is by using the interactive_output() function of ipywidgets. We have declared create_scatter() same as previous examples.

In [12]:
def create_scatter(feature1, feature2):
    with plt.style.context("ggplot"):
        fig = plt.figure(figsize=(8,4))

        plt.scatter(x = iris_df[feature1],
                    y = iris_df[feature2],
                    c=iris_df["FlowerType"],
                    s=20
                   )

        plt.xlabel(feature1.capitalize())
        plt.ylabel(feature2.capitalize())

        plt.title("%s vs %s"%(feature1.capitalize(), feature2.capitalize()))

Below we have created two dropdowns using ipywidgets with options as four features of the IRIS dataset.

In [ ]:
feature1_drop = widgets.Dropdown(options=iris.feature_names, value = iris.feature_names[0])
feature1_drop

How to link ipywidgets widget with matplotlib chart to dynamically update charts?

In [ ]:
feature2_drop = widgets.Dropdown(options=iris.feature_names, value = iris.feature_names[1])
feature2_drop

How to link ipywidgets widget with matplotlib chart to dynamically update charts?

We need to pass the function name and dictionary of function parameter and widgets to interactive_output(). It'll wrap output of create_scatter() function into Output widget. The Output widget is a special type of widget in ipywidgets which can be used to show any kind of output in a notebook.

In [15]:
out = widgets.interactive_output(create_scatter, {'feature1': feature1_drop, 'feature2':feature2_drop})
In [16]:
type(out)
Out[16]:
ipywidgets.widgets.widget_output.Output

Below we have created our first UI using the layout facility available in ipywidgets.

We have first laid out two widgets horizontally by using the HBox() constructor of ipywidgets passing them as a list to it. We have then taken this horizontally laid out dropdowns and output widget from the previous step and passed them to the VBox() constructor as a list to lay them vertically.

If you are interested in learning about layout facility available in ipywidgets then we suggest that you go through our tutorial on ipywidgets which will give an in-depth ideas about it.

In [ ]:
widgets.VBox([widgets.HBox([feature1_drop, feature2_drop]), out])

How to link ipywidgets widget with matplotlib chart to dynamically update charts?

Example 4

Our fourth example consists of a line chart depicting the price data of apple. We have created a multi-select widget which lets us select multiple values from Open, High, Low, and Close prices.

In [ ]:
multi_select = widgets.SelectMultiple(options=["Open", "High", "Low", "Close"], value=["Open",])
multi_select

How to link ipywidgets widget with matplotlib chart to dynamically update charts?

Below we are drawing a line chart showing apple open price changes for the period of Apr 2019- Mar 2020. We have wrapped the matplotlib chart into output widgets explained earlier.

In [19]:
output = widgets.Output()

with output:
    with plt.style.context("ggplot"):
        fig = plt.figure(figsize=(8,4))

        plt.plot(apple_df["Date"].values, apple_df["Open"].values)

        plt.xlabel("Date")
        plt.ylabel("Price ($)")
        plt.title("Line Chart for Apple Prices");

Below we have declared a callback function which will be called each time state of multi-select widget changes. Each time we select/de-select values from multi-select, the dictionary will be passed to this function with new and old values. We need to register this function as a callback of multi-select by using the observe() function in order to pass changes in the value of the widget.

We have passed the function name to the observe() method of the widget as well as changes to which parameter of the widget to monitor. We are monitoring the value parameter of multi-select which will have a list of values currently selected.

The update_line_chart() function will clear figure, loop through all selected values of multi-select, and plot a line for each selected value.

In [20]:
def update_line_chart(selected_vals):
    with output:
        with plt.style.context("ggplot"):
            fig.clear()
            for selected in selected_vals["new"]:
                plt.plot(apple_df["Date"].values, apple_df[selected].values)

            plt.xlabel("Date")
            plt.ylabel("Price ($)")
            plt.title("Line Chart for Apple Prices");
            fig.canvas.draw()

multi_select.observe(update_line_chart, names="value")

Below we have laid output multi-select widget and output widget vertically to create our interactive UI.

In [ ]:
widgets.VBox([multi_select, output])

How to link ipywidgets widget with matplotlib chart to dynamically update charts?

Example 5

The fifth example that we'll be demonstrating consists of a scatter plot and slider widget. We'll be plotting the same scatter plot as example 1 but this time, we'll be limiting a number of points appearing on the scatter plot with a slider widget. It might happen that we might need to visualizing only a few points in the dataset if it has a lot of points.

We have created a slider widget with a minimum value of 0 and a maximum value of 150 (no of samples in the dataset).

In [ ]:
slider = widgets.IntSlider(value=50, min=1, max=iris_df.shape[0], continuous_update=False)
slider.style.handle_color="lawngreen"
slider

How to link ipywidgets widget with matplotlib chart to dynamically update charts?

Below we have created scatter plot showing the relationship between sepal length and petal length but we have plotted only the first 50 samples from the dataset. We have wrapped plotting code into the Output widget like the previous example.

In [23]:
output = widgets.Output()

with output:
    with plt.style.context("ggplot"):
        fig = plt.figure()

        scat = plt.scatter(x=iris_df[:50]['sepal length (cm)'],
                           y=iris_df[:50]['petal length (cm)'],
                           c=iris_df[:50]["FlowerType"]
                          )

        plt.xlabel("sepal length (cm)".capitalize())
        plt.ylabel("petal length (cm)".capitalize())
        plt.title("sepal length (cm) vs petal length (cm) Relation")

Below we have created a callback function which will be called each time changes to the slider happens. It'll pass new and old values of the slider to function because we have registered it with the slider widget using observe(). We have included logic into the callback function which takes new slider value and updates the chart with that many points as selected by the slider.

In [24]:
def update_scatter_chart(new_slider_val):
    with output:
        with plt.style.context("ggplot"):
            fig.clear()
            plt.scatter(x=iris_df[:new_slider_val["new"]]['sepal length (cm)'],
                        y=iris_df[:new_slider_val["new"]]['petal length (cm)'],
                        c=iris_df[:new_slider_val["new"]]["FlowerType"])

            plt.xlabel("Date")
            plt.ylabel("Price ($)")
            plt.title("Line Chart for Apple Prices");
            fig.canvas.draw()

slider.observe(update_scatter_chart, names="value")

Below we have created UI for example. We have created a new widget named label which will be added before the slider.

In [ ]:
slider_label = widgets.Label("No. Of Samples to Visualize")
slider_comp = widgets.HBox([slider_label, slider])
widgets.VBox([slider_comp, output])

How to link ipywidgets widget with matplotlib chart to dynamically update charts?

Example 6

The sixth example that we'll use for explanation consist of candlestick chart and dropdown. Our apple OHLC dataset has data from Apr-2019 to Mar 2020. We'll be creating a dropdown which will let us select one month at a time from the dataset.

Below we have created a dropdown that will let us select month and year combinations for which OHLC data is available in the dataframe.

In [ ]:
year_months_idx = list(zip([2019,]*9 + [2020]*3,[ 4,  5,  6,  7,  8,  9, 10, 11, 12,  1,  2,  3]))
year_months_names = list(zip([2019,]*9 + [2020]*3,['April', 'May', 'June', 'July', 'August', 'September', 'October',
                                                   'November', 'December', 'January', 'February', 'March']))

month_dropdown = widgets.Dropdown(options=list(zip(year_months_names, year_months_idx)), value=year_months_idx[0])
month_dropdown

How to link ipywidgets widget with matplotlib chart to dynamically update charts?

Below we have created our candlestick chart for Apr-2019 using the mplfinance library which is based on matplotlib. We have wrapped a chart like previous examples into the Output widget so that it can be easily updated.

In [27]:
import mplfinance as fplt

output = widgets.Output()

with output:
    fplt.plot(
            apple_df.set_index("Date")["2019-04"],
            type='candle',
            style='charles',
            title='Apple CandleStick',
            ylabel='Price ($)',
            volume=True,
            ylabel_lower='Shares\nTraded',
            show_nontrading=True,
            )

Below we have created a callback function which will be called each time someone selects a different values in the dropdown. We are taking new values from the dictionary and updating the candlestick chart with that selection. We have registered a callback function with the widget as explained earlier.

In [28]:
def update_candle_stick(year_month):
    dt = "%d-%d"%(year_month["new"][0], year_month["new"][1])
    temp_df = apple_df.set_index("Date")[dt]
    output.clear_output()

    with output:
        fplt.plot(
            temp_df,
            type='candle',
            style='charles',
            title='Apple CandleStick',
            ylabel='Price ($)',
            volume=True,
            ylabel_lower='Shares\nTraded',
            show_nontrading=True,
            )

    fig.canvas.draw()

month_dropdown.observe(update_candle_stick, names=["value"])

Below we have created a simple UI consisting out dropdown and output widget.

In [ ]:
widgets.VBox([month_dropdown,  output])

How to link ipywidgets widget with matplotlib chart to dynamically update charts?

This ends our small tutorials explaining how to link ipywidgets widget and matplotlib charts. Please feel free to let us know your views in the comments section.

References



Sunny Solanki  Sunny Solanki