Ever found yourself running the analysis code again and again, each time with a change of parameter, then scrolling endlessly past plots in a never-ending jupyter notebook, trying to get some feeling of how this change affected the whole analysis? Haven’t you been dreaming of viewing all the plots on one screen and easily changing the parameters in one click?
If the answer is Yes – then this post is for you.
As data scientists, many of us use jupyter notebooks to visualize our data, for example, during exploratory data analysis (EDA) or model evaluation. In these stages, the ability to play with parameters and immediately view the results is crucial to our productivity. Visualization packages such as Plotly and Bokeh are a big step in this direction of interactive plots, but it is not enough. What if I want to change parameters in the preprocessing pipeline and immediately see how it affects the analysis plots?
What Will You Learn?
- How to turn your analysis into an interactive visualization tool that lives inside your jupyter notebook (or runs from an IDE and viewed in your browser).
- How to build a dashboard for error analysis in order to evaluate the performance of a regression model, as a use-case.
- You will be able to implement and build interactive visualization tools for other use-cases, such as exploratory data analysis.
Process Overview
We will use the Panel package – a python library that lets you create custom interactive web apps and dashboards by connecting user-defined widgets to plots, images, tables, or text.
We will follow these steps for creating an interactive visualization tool:

Part A: Planning
Investing in planning will save you a lot of time later. Keep in mind that in the end, this is a tool you develop for yourself to get insights from the data faster and better. My advice is to have the following questions in mind while you are doing the analysis so you can later easily choose the most informative plots and tables to display in the dashboard.
-
What do we want to achieve in error analysis?When we evaluate our model performance, besides inspecting the performance on the metrics we defined (e.g. RMSE, R², etc.), we would like to understand where exactly the model is prone to larger errors. An error analysis planned right can: ****1. Guide us how to improve our model, for example, by modifying the loss function.
- Help us estimate the risk of an error if we base our decisions on model predictions.
-
What data do we need for that? Here we will use:a. "actual" – ground truth values b. "predicted" – values predicted by the model. c. "error" – the difference between the actual and predicted d. "error relative to actual" – the error divided by the actual value. e. "error relative to predicted – the error divided by the predicted value.
-
Which panels do I want to see in the dashboard? a. Scatter Plot: I always prefer to first look at the raw data of the "actual" vs. "predicted" to visualize how good the prediction is. Does the model over-estimate or under-estimate in certain ranges of the actual values? I also like to see the error vs. the actual or the predicted values – is the error uniform in different values of the actual? Hence, I would like the scatter plot to be able to show inputs from different columns of the dataframe. b. Boxplot: to see the distribution of the error. Is the error symmetric? or does it tend to overestimate/underestimate? c. Performance metrics table: relevant performance metrics for a regression problem. d. Descriptive stats table: showing, for example, the mean, std, median, and different percentiles of the actual, predicted and error values.
- Which parameters or variables do I want interactive?Here you need to think which variables in the plots or tables you would like to toss around in the dashboard. For example, a widget that can change the input data in the scatter plot or a slider that sets the cutoff value for filtering the data. The changes will of course immediately affect the plots.
- What do I want the layout to look like?Here all the previous parts of the planning come into play. I highly recommend sketching the dashboard layout (a paper and a pencil can work here just fine) before you start implementing it!
Part B: So How Do We Create It?
Panel is in general quite intuitive and does many things behind the scenes for you. However, when you want to create a more complex dashboard and you want more control over the components it can get less intuitive (and send you digging deep in the documentation for hours…). I found the most convenient way was working with the Panel integration of another package called "Param". Let’s see how it works:
There are two kinds of components in the dashboard: 1. Parameters – the variables that the user can set interactively through the widgets. 2. Panels – the plots, tables, images, etc. that are presented
The way these components are represented is as follows: We create a class for our dashboard (we will refer to it as DashboardComponents class throughout this post), where the attributes are the Parameters, and the methods use these attributes to create the Panels. Panel package knows how to wrap these parameters with a widget and re-execute the methods when an input parameter is changed by the user.
The illustration below shows how the attributes and methods of the DashboardComponents class map to the widgets and panels presented in the dashboard:

Steps:
- Create a DashboardComponents class.
- Set attributes that would serve as our Parameters.
- Define the methods for creating the Panels to display in the dashboard.
Parameters (attributes):
Panel works with a widely-used package called Param, such that our DashboardComponents class inherits from the param.Parameterized class, and its attributes are instances of Param classes. They define the type of parameter and optional values it can take.
There are many types of parameters that you can set (e.g. range of integers or floats, selection from list, date, time. For more options look here). In the code below, I used param.ObjectSelector to select column name from a list, and param.Number to choose a float value from a predefined range.
Each type of parameter accepts a set of inputs. For example, for ObjectSelector you will have to set the default value and a list of objects for the user to choose from. For Number you will have to set the default value and a range of values to choose from. You can also set a "label" to change the displayed text (see code example of class attributes "X" and "Y"). The default text is the name of the attribute. For additional arguments see the Param package API.
We initialize the class instance with an input dataframe and then update the parameters’ optional- and default-values based on the input dataframe.
How to change the default widget?
Panel recognizes the parameters and wraps them with a default widget. It provides an option to change the widget and design it. For example, instead of a drop-down menu, we can have a "radio-button" widget. This is done by creating a function that wraps the parameter with the widget of your choice, either as a class method (see code example) or outside the class definition.
Panels (instance methods):
Panels are the plots, tables, images, etc. that are presented in the dashboard.
Note that a plot function should return the fig object rather than fig.show()
.
Panel can wrap the figure and table objects, enabling us to add arguments to control their layout. See for example:
pn.pane.Plotly(fig, width=400)
pn.pane.DataFrame(metrics_df, width=1350)
For examples to Panel wrappers for other objects (e.g. GIF, audio, Matplotlib, Markdown), look here.
I highly recommend writing generic functions for the plots and tables in a separate file and calling them from the instance methods. To keep the flow I did not present mine here but you can find them named utils.py in the github repository related to this post.
Here you can see my example code for the instance methods:
What if we want a parameter to dynamically change depending on the value of another parameter? It turns out that this is not trivial, as reassigning the class attributes will not do here. I will save you the trouble of digging deep in the Panel documentation: the way to do it is to define an update method and use a decorator that will call this function to adjust the one parameter whenever the other changes. In my example, I wanted the optional range of cutoff_value to change based on the column we choose to filter by. I assigned the class attribute "cutoff_value" with some default values and defined an update method:
Part C: How to Assemble All Components and Set the Layout of the Dashboard?
Panel provides several layouts for Panel objects (examples here). I found the most useful are panel.Column and panel.Row, which allow placing panel objects in a way similar to matplotlib’s subplots.
Here are the steps:
- We create an instance of our DashboardComponents class.
-
We create a panel object that receives the dashboard components from that instance (call class attributes for parameter widgets and instance methods for plots or tables). For a complex layout we might use a panel object that is composed of several panel objects (see the panel object "dashboard" in my example code below)
-
To display the dashboard in a jupyter notebook we would just call the panel object ("dashboard" in my example). In an IDE we would call "dashboard.show()" and it would open the dashboard as a tab in our browser.
Final Words
I hope this post gave you the theoretical background and the practical tools, and that now you also feel that you can build your own interactive visualization tool in your jupyter notebook. So go ahead and build your Data Exploration Dashboard or whatever tool that would make you an even more awesome data scientist! (but don’t forget to share it! and if you share it in the comments to this post my satisfaction will be complete :))
Acknowledgements
I am thankful beyond words to Dalya Gartzman, Tom Ron and Talia Shrem for their helpful comments on the draft.
References:
- A basic implementation of the Panel-Param integration which helped me get started
- Introduction to Panel package
For your convenience, you can find the complete code here
