Feb 28 2022 · Piotr Płoński

The 2 alternative approaches for Jupyter Notebook widgets

Mercury convert Jupyter notebook to web appThe Jupyter notebooks provide great flexibility for developing Python programs. The code is organised in cells, and execution results are presented just below the cell. This approach gains huge trackion among programmers, especially data scientists, around the globe. To make the notebooks more accessible to non-programmers there is possible to add an interactive widgets to the notebook. What is more, such notebook can be shared as standalone web application, which greatly simplifies the sharing. In this article I will present two alternative approaches for adding widgets into Jupyter Notebook, namely ipywidgets+Voila and Mercury.

Ipywidgets with Voila

The first approach is to use ipywidgets and Voila framework. The ipywidgets provides a way to add widgets in the notebook with Python. The widgets need to be connected with code, there are observe, interact methods and handlers. Let's write a simple notbook with ipywidgets and serve it with Voila - as they said, the code is worth thousand words :)

I will create a very similar notebook as Semi Koen in her article Bring your Jupyter Notebook to life with interactive widgets. It will be a simple application to filter the data. The data is about London international visitors. The notebook will filter visitors information based on year and visit purpose. You can access data from the website or my GitHub repository.

Let's load the packages and data:

# needed packages
import pandas as pd
import numpy as np
from ipywidgets import widgets

# load data
df_london = pd.read_csv("data/international-visitors-london-raw.csv")

The next step is to initialize the widgets:

# get list of unique values with ALL string
ALL = 'ALL'
def unique_sorted_values_plus_ALL(array):
    unique = array.unique().tolist()
    unique.sort()
    unique.insert(0, ALL)
    return unique

# initialize widgets
dropdown_year = widgets.Dropdown(options = unique_sorted_values_plus_ALL(df_london.year))
dropdown_purpose = widgets.Dropdown(options = unique_sorted_values_plus_ALL(df_london.purpose))
bounded_num = widgets.BoundedFloatText(min=0, max=100000, value=5, step=1)

# output widget
output = widgets.Output()

The widget construction is simple, create it as an object with class corresponding to the widget type (for example Dropdown or BoundedFloatText). Let's add methods for data filtering based on widgets values:

# set color of values in the data frame 
def colour_ge_value(value, comparison):
    if value >= comparison:
        return 'color: red'
    else:
        return 'color: black'

# filter data
def common_filtering(year, purpose, num):
    output.clear_output()

    if (year == ALL) & (purpose == ALL):
        common_filter = df_london
    elif year == ALL:
        common_filter = df_london[df_london.purpose == purpose]
    elif purpose == ALL:
        common_filter = df_london[df_london.year == year]
    else:
        common_filter = df_london[
            (df_london.year == year) & (df_london.purpose == purpose)
        ]

    with output:
        print("Data set size", common_filter.shape)
        display(
            common_filter.head(20).style.applymap(
                lambda x: colour_ge_value(x, num), subset=["visits", "spend", "nights"]
            )
        )

OK, the code for data filtering is ready. We need one more step. We need to connect widgets with the notebook code. We will use event handlers and oberve method.

# define event handlers
def dropdown_year_eventhandler(change):
    common_filtering(change.new, dropdown_purpose.value, bounded_num.value)


def dropdown_purpose_eventhandler(change):
    common_filtering(dropdown_year.value, change.new, bounded_num.value)


def bounded_num_eventhandler(change):
    common_filtering(dropdown_year.value, dropdown_purpose.value, change.new)

# define observers
dropdown_year.observe(dropdown_year_eventhandler, names="value")
dropdown_purpose.observe(dropdown_purpose_eventhandler, names="value")
bounded_num.observe(bounded_num_eventhandler, names="value")

Is that all? Not, yet, the last step is to display widgets and output:

# display input widgets
display(dropdown_year)
display(dropdown_purpose)
display(bounded_num)

# display output widget
display(output)

The full notebook code is available on the GitHub. Here is how it looks like:

Notebook with ipywidgets

Great, the notebook is ready. We can now use the Voila to serve it as a web application. It's pretty simple, just run:

voila dashboard_ipywidgets.ipynb

It will serve the application at local address http://localhost:8866/. Below is the screenshot from the application.

Voila serving a notebook with ipywidgets

You can interact with widgets and the output will be automatically updated.

Mercury

The second approach is to use the open-source framework called Mercury. The Mercury adds interactive widgets to the notebook based on YAML header (inserted in the first raw cell). We will reuse the code from previous example, except parts with ipywidgets. Let's start from the beginning.

The first step is to define the YAML configuration in the first cell of the notebook. Please make sure that the cell type is selected RAW. Below is the Mercury config used to generate the widgets.

---
title: London visitors
description: International London visitors dashboard
show-code: False
params:
    year: 
        input: select
        label: Please select a year
        value: ALL
        choices: ['ALL', '2002', '2003', '2004', '2005', '2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013', '2014', '2015', '2016', '2017', '2018', '2019', '2020P']
    purpose:
        input: select
        label: Please select a purpose
        value: ALL
        choices: ['ALL', 'Business', 'Holiday', 'Miscellaneous', 'Study', 'VFR']
    num:
        input: numeric
        value: 5
        label: Please select a num filtering
        min: 0
        max: 100000
        step: 1
---

After YAML header definition we just write the Python code.

# import packages
import pandas as pd
import numpy as np
from ipywidgets import widgets

# load data
df_london = pd.read_csv("data/international-visitors-london-raw.csv")

# set constant
ALL = "ALL"

Define variables that corresponds to widgets. Each widget name from YAML is the variable in the Python code:

year = ALL
purpose = ALL
num = 5

Please note that all variables are defined in the one cell. In the cell with variables should be only them. The Mercury framework will update the variables values based on widget values. (No need to write handlers or observers)

The filtering code:

def colour_ge_value(value, comparison):
    if value >= comparison:
        return "color: red"
    else:
        return "color: black"

def common_filtering(year, purpose, num):

    if (year == ALL) & (purpose == ALL):
        common_filter = df_london
    elif year == ALL:
        common_filter = df_london[df_london.purpose == purpose]
    elif purpose == ALL:
        common_filter = df_london[df_london.year == year]
    else:
        common_filter = df_london[
            (df_london.year == year) & (df_london.purpose == purpose)
        ]

    print("Data set size", common_filter.shape)
    return common_filter.head(20).style.applymap(
        lambda x: colour_ge_value(x, num), subset=["visits", "spend", "nights"]
    )

The function call with variables that are connected to widgets:

common_filtering(year, purpose, num)

The whole notebook:

Notebook with ipywidgets

To run notebook as web app in the development mode:

mercury watch dashboard_mercury.ipynb

You will see app running at local address http://127.0.0.1:8000 with similar view as in the screen below:

Mercury serving a notebook

Compare ipywidgets+Voila vs Mercury

TLDR; We can summarize the comparison of ipywidgets+Voila vs Mercury in the table below.

Feature ipywidgets+Voila     Mercury    
Interactive widgets        
Development mode        
Code separation        
Serve multiple apps        

Interactive widgets

Both approaches provides interactive widgets. The ipywidgets have instant change after the widget's value change. Only selected part of the notebook will be executed after widget change. If the recomputed part of the code is computationally heavy then notebook can hang. In the Mercury you need to set all widgets values and click the Run button. After execution request the whole notebook from the top to the bottom is executed. There is no option to set dynamic values in the widgets in the Mercury.

Both approaches provides solution to upload and download files in the notebook. Although, the Mercury has special application view for the files created in the notebook. User can save any file in the special Output files directory and all files in this directory will be available for download (no need to do extra coding).

Development mode

Both approaches have development mode available. You can use ipywidgets during the notebook development. If there is a change in the notebook and you would like to see it in the Voila, then you need to manually refresh the website. In the Mercury framework there is watch command for developing User Interface in the notebook. It monitors changes in the notebook code, and reload the notebook automatically if needed.

GUI code separation from analytics code

It is important in software engineering to keep code clean and in separated parts. In the case of ipywidgtes the code for User Interface is mixed with code used for anaylitics. In complex projects this can be a drawback. In the Mercury framework, all code needed to generate widgets is in the first cell in the YAML header. The widgets values are used as variables in the code. There is clean separation between GUI code and analytics code.

Serve multiple apps

There is no option to serve mutliple notebooks in the Voila framework. You can serve only one notebook. In the Mercury there is built-in app gallery. You can serve as many notebooks-apps as you want. Below is the example screenshot with Mercury app gallery. Each notebook is a separate card with a small preview.

Mercury app gallery

Summary

The Jupyter Notebook is a great tool for development and data analytics. The process of sharing the notebook is cumbersome. Thanks to tools like ipywidgets+Voila and Mercury the results of analysis can be easily shared as web app. This is great news for all non-coding persons that will be able to interact with notebooks via widgets.