Fancy world of ipywidgets

Alper Aydın
CodeX
Published in
6 min readAug 8, 2021
Photo by Vas Soshnikov on Unsplash

As data scientists, we always need to explain our findings to the audiences in a way that they don’t get lost in the codes. We don’t have much choices to do this, our best friends are the notebooks, namely, the Jupyter Notebooks.

Jupyter notebooks really provide a good environment to work on a problem and document it along the way, plus present the results to the non-technical stakeholders in a way that they feel like they are reading a blog post. This is awesome, at least in theory. But the case is, some data scientists (it’s me) who can’t stop digging the data and try numerous alternative models and parameters hoping to find tiny little increases in the metrics, leave the findings around and have trouble demonstrating the real point.

Although knowing that ipywidgets are around for years, I had experienced them for the last couple of months and saw that they really carry the notebooks to an ultimate level. Now I can’t stop myself importing the library by default to every new notebook. I’m not going to write how it is installed, you can find it here. Instead I want to demonstrate my experiences and show that how it can be a life changer in your way of working.

Now, let’s start with basics, import the libraries and add some interactivity. The most easy way of adding interactivity to your code is the @interact decorator. This decorator allows to play with parameters without the necessity to run the cell each time. Let’s see how we can do this.

from ipywidgets import interact
@interact
def calculate(x=2):
print(x**2)

Yeah, I see, you won’t be able to demonstrate much with this. Let’s keep on.

There are almost always two types of people in a meeting, chart addicted and table addicted ones. So we may need to display a chart assisted with a table in such cases. But displaying them one over another is not the most beautiful way. So take a look at this.

from ipywidgets import interact, HBox, VBox, Output
import pandas as pd
import matplotlib.pyplot as plt
@interact
def chart_and_table(filterby=['A','B','C']):
df = pd.DataFrame({'A':[23,45,26,43,67], 'B':[25,65,85,74,56], 'C':[23,65,87,89,65]})

o1, o2 = Output(), Output()
scene = HBox([o1,o2])
display(scene)

with o1:
df[[filterby]].plot(kind='bar')
plt.show()
with o2:
display(df[[filterby]])

We first create a dummy dataframe to display. Then created two Output objects to display chart and the table one by one. Then put them in a HBox object which allows us to display widgets horizontally stacked. Here the stacked widgets are outputs. We can add as many objects and combine them with VBox to display any number of outputs that you need. One example is as follows:

from ipywidgets import interact, HBox, VBox, Output, HTML, Layout
import pandas as pd
import matplotlib.pyplot as plt
@interact
def chart_and_table(filterby=['A','B','C']):
df = pd.DataFrame({'A':[23,45,26,43,67], 'B':[25,65,85,74,56], 'C':[23,65,87,89,65]})

style="""
<style>
.style_a {background-color:#fafaaa}
.style_b {background-color:#faaafa}
.style_c {background-color:#aafafa}
</style>
"""
display(HTML(style))

o1 = Output(layout=Layout(width='400px'))
o2 = Output(layout=Layout(width='200px'))
o3 = Output()
o4 = Output()
scene = HBox([o1,
o2,
VBox([o3, o4])
])
display(scene)

with o1:
df[[filterby]].plot(kind='bar')
plt.show()
with o2:
display(df[[filterby]])
with o3:
if filterby=='A':
o3.add_class('style_a')
elif filterby=='B':
o3.add_class('style_b')
elif filterby=='C':
o3.add_class('style_c')
print('The selected filter is :', filterby)
with o4:
display(df)

You see, in a single cell output we can display four different types of information. This brings a big opportunity to explain our findings without scrolling between cells. Here, in addition to the previous display we added an HTML object which not only allows us display any HTML code inside the display outputs, but also lets us insert css styles. Inside the HTML object, we placed some css styles, we will use these styles to change the color of the output according to the selected filter. Then we added Layout objects with layout parameters to set the width of the outputs. And finally, we add the style class to the output object according the selected filter.

So far so good. We can set up a tiny world inside a cell to play with our parameters. Once you get the feeling of this freedom, you will desire more. Now, lets see some more interactivity. This time we will combine widgets’ actions with each other. Let’s see.

from ipywidgets import interact, HBox, VBox, Output, HTML, Dropdown, Button, Layout, Label
from IPython.display import display, clear_output
import pandas as pd
import matplotlib.pyplot as plt
import datetime as dt
class demo():
def __init__(self):
self.df = pd.DataFrame({'A':[23,45,26,43,67], 'B':[25,65,85,74,56], 'C':[23,65,87,89,65]})

style="""
<style>
/* enlarges the default jupyter cell outputs, can revert by Cell->Current Outputs->Clear */
.container { width:1020 !important; }

/* styles for output widgets */
.o2 {width:400px; border:1px solid #ddd}
.o3 {width:400px; border:1px solid #ddd}
.o4 {width:400px; border:1px solid #ddd}
.o5 {width:800px; }
.o5 span {color:red !important}

/* custom styles for testing */
.style_A {background-color:#fafaaa}
.style_B {background-color:#faaafa}
.style_C {background-color:#aafafa}
</style>
"""
display(HTML(style))
self.o1 = Output(layout=Layout(width='400px'))

self.o2 = Output()
self.o2.add_class('o2')
self.o3 = Output()
self.o3.add_class('o3')
self.o4 = Output()
self.o4.add_class('o4')
self.o5 = Output()
self.o5.add_class('o5')
# create a scene for displaying the outputs,
# Output1 on the top row, 2,3, and 4 stacked horizontally in the second row
scene = VBox([self.o1,
HBox([self.o2, self.o3, self.o4]),
self.o5
])
display(scene)

with self.o1:
display(HTML('<h2>Demo</h2>'))

with self.o2:
self.dd_filter = Dropdown(description='Select Filter', options=['', 'A','B','C'])
self.dd_filter.observe(self.fill_values)

self.dd_values = Dropdown(description='Select Value')

self.btn = Button(description='Run')
self.btn.on_click(self.display_results)

display(self.dd_filter, self.dd_values, self.btn)


def fill_values(self,x):
if x['type'] == 'change' and x['name'] == 'value':
filter_by = x['owner'].value

if filter_by=='':
filter_values = []
else:
filter_values = self.df[filter_by].values
self.dd_values.options = filter_values

def display_results(self, x):
filter_by = self.dd_filter.value
filter_val = self.dd_values.value

if filter_by=='':
with self.o3:
clear_output()
print('Please select filter')
return

df_filtered = self.df[self.df[filter_by]>=filter_val]

with self.o3:
clear_output()
lbl = Label(value=f'Filter by {filter_by}, with {filter_val}, found {df_filtered.shape[0]} observation(s)')
lbl.add_class(f'style_{filter_by}')
display(lbl)
display(df_filtered)

with self.o4:
clear_output()
df_filtered.plot(kind='bar')
plt.show()

with self.o5:
clear_output()
display(Label(value= f'Code last run {dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}' ))


d = demo()

This time we go further and take advantage of object oriented programming. We create a class object to store all the widgets and maintain the interaction between them. Our class is made up of three functions.

First is “__init__” function which is called once the class is initialized with d=demo() line. In this function, we first construct our dataframe and assign it to a df variable and make it accessible within the whole class by adding “self.” keyword in front. Then we create the styles and output widgets and display them as we had done in previous example. Again, we put “self.” in front of each widget to provide accessibility within the class. Unlike the previous example, we create Dropdown widgets manually instead of @interact decorator, and set “observe” parameter in order to be able to track its select event. We pass a function called “fill_values” to this parameter and will fill the second dropdown values according to the selected column of the dataframe. Once the user select a value from this dropdown “fill_values” function is called and fill the second dropdown with the values under this column.

We then add a button to trigger the main job, “display_results”. Once we click this button, the “display_results” function is called and first read the values from the dropdowns, filter the dataframe accordingly, clear existig outputs inside the outputs 3,4, and 5 and fill them with the desired outputs.

Finally, with the help of Jupyter Notebook extension ‘nbextensions’ hide input extension, whole notebook can be displayed as follows.

Conclusion

As you can see, you are limited with only your imagination. You can make really impressive demonstrations of your findings and easily keep the focus of your audiences without getting lost inside your sketch cells.

Below I drop a screenshot of my latest demo, hope it helps to provide some intuition for how fantastic these widgets can be used.

References

--

--