A bespoke, polished data science dashboard can be a beautiful thing for anyone
looking to make data-driven decisions. And yet, not every project can afford
setting up elaborate dashboards that cost money and developer time.
In this post, we show you how to construct a quick dashboard using
Panel & Python without ever leaving the comfort of your Jupyter
notebook. Before you start, download the
demonstration video.
For this example, we want to create a dynamic plot dashboard displaying
historical trends of popular (user-supplied) baby names.
While the historical trends of name registrations are not at the forefront of
business decisions, similar name-indexed data queries could involve stock
indexes, product names in your catalog (or a competitor's), or perhaps
predictions of future trends for your named-apparel business.
This baby-name data originally comes from
the US Social Security open data; we have modified it for ease
of use and to off-load some pre-processing needed for the final plots.
For each observation, we have preserved Year, Name, and Gender as features from
the original data. We also have a feature Normalized that represents the
percentage of all names within a given year.
We could add names common to M & F genders—say Dillon (M) & Dillon (F)—but we
leave them distinct here to preserve their individual trends.
We present a random slice of the data below to build intuition.
from IPython.core.display import display, HTML
display(HTML("<style>.container { width:90% !important; }</style>"))
df = pandas.read_csv("SSNames.csv")
df = df.set_index("index")
panel.extension() #this call is neccessary to make panel behave inside jupyter
With our data in hand, we first want to define a plotter
function. This
function produces plots of historical trends of registered baby names over a
range of years with certain scalings (all user-specified). If you're unfamiliar
with Python, it's enough to simply say that this function produces the plot we
want without any interactive components. Without Panel, you can invoke this
function yourself to generate plots in Jupyter using different input values
supplied by hand.
def plotter(names_list, names_list2, years, scale, scale2):
# Handles empty input(s), cleans format/capitalization for matching, builds lists
names_list_f = [x.strip().capitalize() for x in names_list.split(",")]
if len(names_list2) == 0:
names_list2_f = [x.strip().capitalize() for x in names_list2.split(",")]
# Dataframe re-configure based on names input
# source used as preliminary dataframe stripped down to only input names,
# names converted to 'name (F/M)'
df["Name"].map(lambda x: x in names_list_f) & (df["Gender"] == "M")
df["Name"].map(lambda x: x in names_list2_f) & (df["Gender"] == "F")
frames = pandas.concat([test_frames, test_frames2])
frames["NameG"] = frames.apply(
lambda x: x["Name"] + " (" + x["Gender"] + ")", axis=1
frames.drop(["Gender", "Name"], inplace=True, axis=1)
frames.index.name = "year"
listed_names = [i for i in pandas.DataFrame(frames.groupby("NameG"))[0]]
frames[frames["NameG"] == names_list[0]]
.drop(["NameG", "Normalized"], axis=1)
.rename({"Normalized": i}, axis=1)
source = source.fillna(0)
# Conditional axis labeling based on optional checkboxes
# modifies data in source dataframe by optional metric(s)
# finally, returns hvplot objects that contain our final plots
averages = source.join(yearly_average, how="outer")
source[i] = averages[i] / averages["Normalized"]
label = "% of Names / Yearly-Average"
averages2 = source.join(yearly_unique, how="outer")
source[i] = averages2[i] * averages2["Normalized"] / 100000
label = "(% of Names / Yearly-Average) * Yearly-Uniques / 10^5"
label = "% of Names * Yearly-Uniques / 10^5"
if "No (F)" in listed_names:
listed_names.remove("No (F)")
if "No (M)" in listed_names:
listed_names.remove("No (M)")
to_plot = source[(source.index >= years[0]) & (source.index <= years[1])]
If calling plotter
itself were enough, we'd be done. In this case, we want to
share a plotting tool with a boss or friends. This means we want some
interactive elements for our dashboard: widgets.
Widgets are often the basic building blocks of a Panel dashboard. These
components are familiar to anyone who has seen web forms previously. For the
use-case here, we need only three types of Panel widget: TextInput
(to enter
names), a RangeSlider
(to select the years to plot), and Checkbox
(to toggle
optional metrics).
tickers_male = panel.widgets.TextInput(
placeholder="Enter Names Here",
tickers_female = panel.widgets.TextInput(
name="Names (F)", placeholder="Enter Names Here", width=left_panel_width
"Sarah" # Alternative default value in original widget declaration
years_range = panel.widgets.RangeSlider(
checkbox_year = panel.widgets.Checkbox(name="Scale by Yearly Average")
checkbox_nunique = panel.widgets.Checkbox(name="Scale by Yearly Unique Name Entries")
yearly_average = df.reset_index().groupby("year").mean()
# This metric double-counts names both in M and F categories;
# a careful analysis might want to split them up.
df.reset_index().groupby("year").count()["Normalized"]
The function panel.interact
makes it straightforward to connect the plotter
function with the preceding widgets. We simply supply the function (plotter
)
as the first input argument to panel.interact
. The remaining input arguments
associate widgets with plotter
's input arguments. Be certain that the widget
output data types match with plotter
's requirements. For example, the input
years
should be a (sorted) list or tuple of two distinct integers; a
RangeSlider
with specified values for start & stop provides a valid tuple.
Panel also enables arranging elements of your dashboard with Row
and Column
objects. For instance, below, dashboard[0]
contains all the input widgets,
while dashboard[1]
is the output of the plotter.
The addition of .servable()
to your panel layout allows the entire notebook to be called by
$ panel serve <Notebook-Path>
from the command line (provided you have Panel installed). This allows multiple
clients to connect to this service via web browsers.
interact = panel.interact(
names_list2=tickers_female,
html_pane = panel.pane.HTML(
"""<h2 style="text-align:Center">Social Security Names Plotter</h2>""",
sizing_mode="stretch_width",
html_pane2 = panel.pane.HTML(
"""<h4 style="text-align:Center">Names are not case-sensitive, and should be entered like: Andrew, Dillon, Travis <br><br> Hit enter in text field to re-render</h4>""",
sizing_mode="stretch_width",
panel.Column(interact[0], html_pane2),
panel.Column(html_pane, interact[1]),
There you have it—a quick and easy option for dashbording without breaking a
sweat. That's not all that Panel can do, but for a simple framework for getting
a UI wrapped around your functions, it's hard to beat.
Here is a link to the Binder for running this notebook.
Here is the data for this exercise.