Adding interactive filters

Introduction

This guide will show you how to build interactive timeseries plots using advanced visualization libraries like Plotly, Bokeh, and Altair. We'll focus on these two components:
    1.
    Dropdown menus let you toggle between different series in the same plot
    2.
    Date range sliders allowing you to observe trends between specific periods

Dropdown menu

A dropdown menu is really handy if you have a lot of categories in the data, e.g. stocks or countries, and you want to observe the trends using a line plot in the same plot or figure. This saves you from creating several plots in a loop.
In this tutorial, we'll use a Covid 19 dataset which shows the covid caseload across different countries and add a dropdown to change the country within the same plot.

Altair

You can read more about interactive components with Altair here. Creating the plot with Altair involves binding a country list to a selection component:
1
import pandas as pd
2
import datapane as dp
3
import altair as alt
4
alt.data_transformers.disable_max_rows()
5
6
df = pd.read_csv("./covid_19_clean_complete.csv")
7
8
df['Date'] = pd.to_datetime(df['Date'])
9
country_list = list(df['Country/Region'].unique())
10
11
input_dropdown = alt.binding_select(options=country_list)
12
selection = alt.selection_single(fields=['Country/Region'], bind=input_dropdown, name='Country')
13
14
alt_plot = alt.Chart(df).mark_line().encode(
15
x='Date',
16
y='Confirmed',
17
tooltip='Confirmed'
18
).add_selection(
19
selection
20
).transform_filter(
21
selection
22
)
23
24
alt_plot
Copied!
Running that code gives us:

Plotly

Plotly offers a range of interactive options which are called Custom Controls. The best part about these controls is that they can be added to the plots purely in pythonic code. Let's visualize the same plot in Plotly:
1
import pandas as pd
2
import plotly.graph_objects as go
3
import plotly.express as px
4
5
6
df = pd.read_csv("./covid_19_clean_complete.csv")
7
8
fig = go.Figure()
9
10
country_list = list(df['Country/Region'].unique())
11
12
for country in country_list:
13
fig.add_trace(
14
go.Scatter(
15
x = df['Date'][df['Country/Region']==country],
16
y = df['Confirmed'][df['Country/Region']==country],
17
name = country, visible = True
18
)
19
)
20
21
buttons = []
22
23
for i, country in enumerate(country_list):
24
args = [False] * len(country_list)
25
args[i] = True
26
27
button = dict(label = country,
28
method = "update",
29
args=[{"visible": args}])
30
31
buttons.append(button)
32
33
fig.update_layout(
34
updatemenus=[dict(
35
active=0,
36
type="dropdown",
37
buttons=buttons,
38
x = 0,
39
y = 1.1,
40
xanchor = 'left',
41
yanchor = 'bottom'
42
)],
43
autosize=False,
44
width=1000,
45
height=800
46
)
Copied!
And you will have a nice-looking dropdown added to the time-series plot:

Bokeh

Bokeh has components called widgets which can be used to add several interactive components to your plots. Widgets are primarily aimed at creating dashboard components hosted on the Bokeh server. You can read more about widgets here.
Keep in mind that in order to create widgets for standalone HTML files or even while working with Jupyter notebook, you will need to use CustomJS callbacks. This requires a bit of JavaScript knowledge to get the dropdown working properly. If you want to do it the pure pythonic way, you have to use the Bokeh server to make the widgets work.
We will replicate the same use-case as above using Bokeh dropdowns.
1
import pandas as pd
2
import datapane as dp
3
from bokeh.io import output_file, show, output_notebook, save
4
from bokeh.models import ColumnDataSource, Select, DateRangeSlider
5
from bokeh.plotting import figure, show
6
from bokeh.models import CustomJS
7
from bokeh.layouts import row,column
8
9
df = pd.read_csv("./covid_19_clean_complete.csv")
10
country_list = list(df['Country/Region'].unique())
11
12
df['Date'] = pd.to_datetime(df['Date'])
13
14
cols1 = df[['Country/Region','Date', 'Confirmed']]
15
cols2 = cols1[cols1['Country/Region'] == 'Afghanistan']
16
17
Overall = ColumnDataSource(data=cols1)
18
Curr = ColumnDataSource(data=cols2)
19
20
#plot and the menu is linked with each other by this callback function
21
callback = CustomJS(args=dict(source=Overall, sc=Curr), code="""
22
var f = cb_obj.value
23
sc.data['Date']=[]
24
sc.data['Confirmed']=[]
25
for(var i = 0; i <= source.get_length(); i++){
26
if (source.data['Country/Region'][i] == f){
27
sc.data['Date'].push(source.data['Date'][i])
28
sc.data['Confirmed'].push(source.data['Confirmed'][i])
29
}
30
}
31
32
sc.change.emit();
33
""")
34
35
menu = Select(options=country_list,value='Afghanistan', title = 'Country') # drop down menu
36
37
bokeh_p=figure(x_axis_label ='Date', y_axis_label = 'Confirmed', y_axis_type="linear",x_axis_type="datetime") #creating figure object
38
bokeh_p.line(x='Date', y='Confirmed', color='green', source=Curr) # plotting the data using glyph circle
39
40
menu.js_on_change('value', callback) # calling the function on change of selection
41
layout=column(menu, bokeh_p) # creating the layout
42
show(layout)
Copied!
This is how the plot will look:

Matplotlib/Seaborn

If you want to use a non-interactive library like Matplotlib or Seaborn, you can use the dp.Select block to mimic the interactive filter ability, like this:
1
import pandas as pd
2
import matplotlib.pyplot as plt
3
import datapane as dp
4
5
df = pd.read_csv("./covid_19_clean_complete.csv")
6
country_list = list(df['Country/Region'].unique())[:10]
7
8
plt.figure(figsize=(10, 5), dpi=300)
9
10
plot_list = ([dp.Plot(df[df['Country/Region']==country].plot.line(x='Date', y='Confirmed'), label=country)
11
for country in country_list])
12
13
report = dp.Report(
14
dp.Select(blocks = plot_list)
15
).upload(name="Matplotlib example")
Copied!
In this example, we've restricted it to 10 plots.

Date range slider

Another interactive component that comes really handy (especially while working with timeseries plots) is a date range slider.
Since most of the timeseries plots have a date range in the X-axis, a slider allows you to dynamically change the period and view only a section of the plot to understand the trends for that particular period.

Altair

With Altair, similar to Plotly, you can use the generic slider to use as a Date Range Slider. However, keep in mind that Vega considers the timeseries data in milliseconds and it is difficult to show the date information in the slider. It works if you have yearly data - but if the data is broken into days and months, it is tricky to make it work.
1
import altair as alt
2
import pandas as pd
3
alt.data_transformers.disable_max_rows()
4
5
df = pd.read_csv("./covid_19_clean_complete.csv")
6
df['Date'] = pd.to_datetime(df['Date'])
7
country_list = list(df['Country/Region'].unique())
8
9
input_dropdown = alt.binding_select(options=country_list)
10
selection = alt.selection_single(fields=['Country/Region'], bind=input_dropdown, name='Country')
11
12
def timestamp(t):
13
return pd.to_datetime(t).timestamp() * 1000
14
15
slider = alt.binding_range(
16
step = 30 * 24 * 60 * 60 * 1000, # 30 days in milliseconds
17
min=timestamp(min(df['Date'])),
18
max=timestamp(max(df['Date']))
19
)
20
21
select_date = alt.selection_single(
22
fields=['Date'],
23
bind=slider,
24
init={'Date': timestamp(min(df['Date']))},
25
name='slider'
26
)
27
28
alt_plot = alt.Chart(df).mark_line().encode(
29
x='Date',
30
y='Confirmed',
31
tooltip='Confirmed'
32
).add_selection(
33
selection
34
).transform_filter(
35
selection
36
).add_selection(select_date).transform_filter(
37
"(year(datum.date) == year(slider.Date[0])) && "
38
"(month(datum.date) == month(slider.Date[0]))"
39
)
40
41
alt_plot
Copied!
This is how it will look:

Plotly

Plotly has a generic slider component that can be used to change the data corresponding to any axis. While it does not have a specific slider for timeseries data, the generic slider can be used to create a date range slider. You can read more about sliders here.
To create a slider, we will take the same timeseries plot created previously with the dropdown menu and add a slider component below the plot - just a single extra parameter xaxis1_rangeslider_visible!
1
import pandas as pd
2
import plotly.graph_objects as go
3
import plotly.express as px
4
5
df = pd.read_csv("./covid_19_clean_complete.csv")
6
7
fig = go.Figure()
8
9
country_list = list(df['Country/Region'].unique())
10
11
for country in country_list:
12
fig.add_trace(
13
go.Scatter(
14
x = df['Date'][df['Country/Region']==country],
15
y = df['Confirmed'][df['Country/Region']==country],
16
name = country, visible = True
17
)
18
)
19
20
buttons = []
21
22
for i, country in enumerate(country_list):
23
args = [False] * len(country_list)
24
args[i] = True
25
26
#create a button object for the country we are on
27
button = dict(label = country,
28
method = "update",
29
args=[{"visible": args}])
30
31
#add the button to our list of buttons
32
buttons.append(button)
33
34
fig.update_layout(
35
updatemenus=[dict(
36
active=0,
37
type="dropdown",
38
buttons=buttons,
39
x = 0,
40
y = 1.1,
41
xanchor = 'left',
42
yanchor = 'bottom'
43
)],
44
autosize=False,
45
width=1000,
46
height=800,
47
xaxis1_rangeslider_visible = True
48
)
49
50
fig
Copied!
This will give you something like this:

Bokeh

Similar to the Dropdown widget, Bokeh has a Date Range Slider widget specifically to work with timeseries data. This widget is different from the generic Range Slider widget. In order to make this widget work, a CustomJS callback is required.
1
cols1=df.loc[:, ['country','date', 'confirmed']]
2
cols2 = cols1[cols1['country'] == 'Afghanistan' ]
3
4
Overall = ColumnDataSource(data=cols1)
5
Curr=ColumnDataSource(data=cols2)#plot and the menu is linked with each other by this callback function
6
7
callback = CustomJS(
8
args=dict(source=Overall, sc=Curr),
9
code="""
10
var f = cb_obj.value
11
sc.data['date']=[]
12
sc.data['confirmed']=[]
13
for(var i = 0; i <= source.get_length(); i++){
14
if (source.data['country'][i] == f){
15
sc.data['date'].push(source.data['date'][i])
16
sc.data['confirmed'].push(source.data['confirmed'][i])
17
}
18
}
19
20
sc.change.emit();
21
"""
22
)
23
24
menu = Select(options=country_list,value='Afghanistan', title = 'Country') # drop down menu
25
bokeh_p=figure(x_axis_label ='date', y_axis_label = 'confirmed', y_axis_type="linear",x_axis_type="datetime") #creating figure object
26
bokeh_p.line(x='date', y='confirmed', color='green', source=Curr) # plotting the data using glyph circle
27
28
menu.js_on_change('value', callback) # calling the function on change of selection
29
30
date_range_slider = DateRangeSlider(value=(min(df['date']), max(df['date'])),
31
start=min(df['date']), end=max(df['date']))
32
33
date_range_slider.js_link("value", bokeh_p.x_range, "start", attr_selector=0)
34
date_range_slider.js_link("value", bokeh_p.x_range, "end", attr_selector=1)
35
layout = column(menu, date_range_slider, bokeh_p)
36
show(layout) # displaying the layout
Copied!
This is how it will look like:

Last modified 7d ago