Have you ever created a Python-based Jupyter notebook and analyzed data that you wanted to explore in many different ways? For example, you might want to look at a data graph, but filter it in ten different ways. Is there any way you can look at these 10 different outcomes?
- Copy and paste a cell, change the filtering for each cell, and then execute that cell. You end up with ten different cells, ten different values.
- Modify the same cell, execute it and view the results, and then modify it again, ten times.
- Parameterize the notebook (perhaps using a tool like Papermill) and execute the entire notebook with ten different sets of parameters.
- Some combination of the above.
None of this is ideal if we want the ability to quickly interact and explore data. These options are also prone to typos or a lot of extra editing. They may work well for the original notebook developer, but letting a user who doesn’t understand Python syntax modify variables and re-execute cells may not be the best choice. What if you could give users a simple form, add a button, and they could modify the form and see the results they wanted?
It turns out you can do this very easily in Jupyter without having to create a full WebApp. This can be achieved by [ipywidgets] (https://ipywidgets.readthedocs.io/en/latest/index.html), also known as a widget. In this article, I’ll show you the basics of building a few simple forms to view and analyze some data.
What is a widget?
Jupyter widgets are special bits of code that will embed JavaScript and HTML in your notebook and visually appear in your browser when executed in your notebook. These components allow users to interact with widgets. These widgets can execute code on certain operations, allowing you to update cells without the user having to re-execute or even modify any code.
Begin to use
First, you need to make sure that ipyWidgets are installed in your environment. This will depend in part on the Jupyter environment you use. For earlier installations of Jupyter and JupyterLab, be sure to check the documentation for details. But for a basic installation, just use PIP
pip install ipywidgets
Copy the code
Or for conda
conda install -c conda-forge ipywidgets
Copy the code
In most cases, this should be all you need to do to make things work.
example
Instead of looking at all the widgets at once and diving into the details, let’s grab some interesting data and explore it manually. We will then use widgets to interact more with these data explorations. Let’s grab some data from the Chicago Data Portal — specifically their dataset of currently active business licenses. Note that if you just run the code below, you only get 1000 lines of data. See the documentation to see how to get all the data.
Note: All of this code was written in The Jupyter notebook using Python 3.8.6. While this article shows the output, the best way to experience gadgets is to interact with them in your own environment. You can download a notebook of this article here.
import pandas as pd
df = pd.read_csv('https://data.cityofchicago.org/resource/uupf-x98q.csv')
df[['LEGAL NAME', 'ZIP CODE', 'BUSINESS ACTIVITY']].head()
Copy the code
As we can see from the data, the business activity is quite lengthy, but postcodes are an easy way to do some simple searching and filtering of the data. For our small data set, let’s just grab zip codes with 20 or more businesses.
zips = df.groupby('ZIP CODE').count()['ID'].sort_values(ascending=False)
zips = list(zips[zips > 20].index)
zips
Copy the code
[60618, 60622, 60639, 60609, 60614, 60608, 60619, 60607]
Copy the code
Now, a reasonable solution for filtering data might be to create a report filtered by zip code, showing the legal name and address of the business, sorted by the expiration date of the license. This is a fairly simple (if confusing) expression in PANDAS. For example, in this data set, we could take the top zip code and look at several columns like this.
df.loc[df['ZIP CODE'] == zips[0]].sort_values(by='LICENSE TERM EXPIRATION DATE', ascending=False)[['LEGAL NAME', 'ADDRESS', 'LICENSE TERM EXPIRATION DATE']]
Copy the code
Now, what if someone wants to be able to run this report for different zip codes, see different columns, and sort by other columns? The user must feel comfortable editing the above cell, rerunking it, and possibly executing other cells to find column names and other values.
Use small parts
Instead, we can use widgets to create a form that allows the interaction to be performed visually. In this article, you’ll learn enough widgets to build a form and display the results dynamically.
Gadget types
Because most of us are familiar with forms in web browsers, it makes sense to think of widgets as part of a typical form. Widgets can represent numeric, Boolean, or text values. They can be selectors for pre-existing lists, or they can accept free text (or cipher text). You can also use them to display formatted output or images. The complete list of widgets describes them in more detail. You can also create your own custom widgets, but for our purposes, we’ll be able to do all the work with standard widgets.
A widget is just an object that once created can be displayed in a Jupyter notebook. It will present itself (and its underlying content) and (potentially) allow for user interaction.
Make a form
For our table, we will need to collect four pieces of information.
- Zip code to filter
- The column to sort
- Is the sort ascending or descending
- Columns to display.
These four pieces of information will be captured by the following form elements.
- A selection drop-down menu
- A selection drop-down box
- A check box
- A multiple selection list
These three widgets will provide a quick introduction to one widget, and once you know how to instantiate and use one widget, the others are similar. Before we can create a widget, we need to import the library. Let’s take a look at the drop-down menu first.
import ipywidgets as widgets
widgets.Dropdown(
options=zips,
value=zips[0],
description='Zip Code:',
disabled=False,
)
Copy the code
Of course, just creating an object doesn’t allow us to use it, so we need to assign it to a variable and display can be used to render it, as we saw above.
zips_dropdown = widgets.Dropdown(
options=zips,
value=zips[0],
description='Zip Code:',
disabled=False,
)
display(zips_dropdown)
Copy the code
We can easily do the same thing for columns.
columns_dropdown = widgets.Dropdown(
options=df.columns,
value=df.columns[4],
description='Sort Column:',
disabled=False,
)
display(columns_dropdown)
Copy the code
For booleans, you have several options. You can make a CheckBox or a ToggleButton. I’ll take the first one.
sort_checkbox = widgets.Checkbox( value=False, description='Ascending? ', disabled=False) display(sort_checkbox)Copy the code
Finally, for this example, we want to be able to select all the columns we want to see in the output. We’re going to use SelectMultiple. Note if you use shift and CTRL (or Command on Mac) keys to select multiple options.
columns_selectmultiple = widgets.SelectMultiple(
options=df.columns,
value=['LEGAL NAME'],
rows=10,
description='Visible:',
disabled=False
)
display(columns_selectmultiple)
Copy the code
Finally, we will display a button that we can click to force an update. Note that we won’t need this in the end, there’s an easier way to interact with our elements, but buttons are useful in many cases).
button = widgets.Button(
description='Run',
disabled=False,
button_style='', # 'success', 'info', 'warning', 'danger' or ''
tooltip='Run report',
icon='check' # (FontAwesome names without the `fa-` prefix)
)
display(button)
Copy the code
Process the output
Before we attach a button to a function, we need to make sure we capture the output of our function. If we want to view a DataFrame, or print text, or record some information to stdout, we need to be able to capture that information and erase it if necessary. This is what the Output widget does. Note that you don’t have to use output widgets, but if you want your output to appear in a cell, you will need to use this. The cells that display the Output widget display the results.
out = widgets.Output(layout={'border': '1px solid black'})
Copy the code
Let’s connect it all together
Now that we have generated all the user interface components, how do we display them all in one place and hook them to the generated actions?
First, let’s create a simple layout that puts all the items together.
box = widgets.VBox([zips_dropdown, columns_dropdown, sort_checkbox, columns_selectmultiple, button])
display(box)
Copy the code
Handle events
For widgets that generate events, you can provide a function that receives events. For a Button, the event is on_click, which requires a function that will take one argument, the Button itself. If we use the Output created above (as a context manager using the with statement), clicking on the Button will cause the text “Button Clicked “to be appended to the cell Output. Note that the cell that receives the Output will be where the Output will be rendered.
def on_button_clicked(b):
with out:
print("Button clicked.")
button.on_click(on_button_clicked, False)
Copy the code
A better way to connect things
The above example is simple, but does not tell us how to get values from other inputs. Another way is to use interact. It can be used either as a function or as a function decorator, automatically creating widgets that allow you to interactively change the input to the function. Based on the named parameter type, it will generate a widget that allows you to change the value. Interact is a fast way to provide user interaction around a function. This function is called each time the widget is updated. When you move the slider, if the checkbox is selected, the square of the number will be printed out, otherwise the number will just be printed out, unchanged.
def my_function2(x, y):
if y:
print(x*x)
else:
print(x)
interact(my_function2,x=10,y=False);
Copy the code
Note that you can provide more information to Interact to provide more appropriate user interface elements (see the examples in the documentation). But now that we’ve made the widgets, we can just use those widgets instead. The best way to do this is to use another function, interactive. Interactive, just like interactive, but allows you to interact with the widgets you create (or provide them directly) and display values when you want. Since we’ve made some widgets, we can simply have Interactive learn about each widget by providing it as a keyword argument. The first argument is a function whose arguments need to match the subsequent keyword arguments in order to interact. This function is called every time we change a value in the form, and the value comes from the form widget. With just a few lines of code, we now have an interactive tool for viewing and filtering this data.
But first, I’ll make a cell with output to receive the display.
report_output = widgets.Output()
display(report_output)
Copy the code
from ipywidgets import interactive
def filter_function(zipcode, sort_column, sort_ascending, view_columns):
filtered = df.loc[df['ZIP CODE'] == zipcode].sort_values(by=sort_column, ascending=sort_ascending)[list(view_columns)]
with report_output:
report_output.clear_output()
display(filtered)
interactive(filter_function, zipcode=zips_dropdown, sort_column=columns_dropdown,
sort_ascending=sort_checkbox, view_columns=columns_selectmultiple)
Copy the code
Now the same table created earlier above is rendered in the cell. Which cell the output will appear on the display(report_output) line. When you modify any form element, the resulting filtered DataFrame will be displayed in that cell.
conclusion
This is just a quick overview of using ipywidgets to make Jupyter notebooks more interactive. Even if you’re comfortable editing Python code and re-executing cells to update and explore data, widgets can be a great way to make that exploration more dynamic and convenient, while also being less error-prone. If you need to share a notebook with someone who isn’t used to editing Python code, widgets can be a lifesaver and really help bring data to life.
Just reading about these gadgets is not as much fun as running the examples and using them yourself. Try these examples, then try using the parts in your own notebook.
The postHow to use ipywidgets to make your Jupyter notebook interactiveappeared first onwrighters.io.