Top 4 Python libraries to build interactive timeseries plots | by Shoumik Goswami | Towards Data Science

One of the most prevalent data types encountered while analyzing data is timeseries. From identifying trends to understanding ’cause and effect’ behaviors, timeseries analysis is one of the most popular ways of understanding user behaviors, buying patterns, and much more.
In Python, we often start by plotting a simple line curve using Matplotlib or Seaborn, which are perfect, if you are working with just one categorical variable changing over time. But often you’ll need to show multiple categorical variables together e.g. a list of stocks for market data, or regions/locations for sales data.
In such cases, you can either show all your series in the same plot or create a separate plot for each series. However, these options are hard to make sense of and take up a lot of space.
This is where advanced visualization libraries like Plotly, Bokeh, and Altair come into the picture, as they allow you to create interactive plots and dashboards using features like dropdowns, sliders, buttons, etc., which help your users explore complex data.
In this article, we will explore two interactive elements for timeseries visualization -
Dropdown menus let you toggle between different series in the same plot
Date range sliders allowing you to observe trends between specific periods
All codes used in the below examples can be found here.
Disclaimer: This article is written in collaboration with Datapane and I work on the datapane team as a community evangelist.
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.
All three libraries i.e. Plotly, Bokeh, and Altair allow you to add a dropdown menu to the plots but each of them has its pros and cons.
As a bonus, I will also show you a way to do this using Matplotlib or Seaborn, which do not support interactive components out of the box.
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.
For the purposes of this tutorial, we are going to use Covid 19 dataset which can be accessed here.
We will create a plot to visualize the spread of Covid 19 across the globe and add a dropdown to change the country within the same plot.
buttons = []i = 0fig3 = go.Figure()country_list = list(df['country'].unique())for country in country_list:    fig3.add_trace(        go.Scatter(            x = df['date'][df['country']==country],            y = df['confirmed'][df['country']==country],            name = country, visible = (i==0)        )    )    for country in country_list:    args = [False] * len(country_list)    args[i] = True        #create a button object for the country we are on    button = dict(label = country,                  method = "update",                  args=[{"visible": args}])        #add the button to our list of buttons    buttons.append(button)        #i is an iterable used to tell our "args" list which value to set to True    i+=1    fig3.update_layout(updatemenus=[dict(active=0,                                    type="dropdown",                                    buttons=buttons,                                    x = 0,                                    y = 1.1,                                    xanchor = 'left',                                    yanchor = 'bottom'),                              ])fig3.update_layout(    autosize=False,    width=1000,    height=800,)
And you will have a nice-looking dropdown added to the time-series plot.
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.
cols1=df.loc[:, ['country','date', 'confirmed']]cols2 = cols1[cols1['country'] == 'Afghanistan' ]Overall = ColumnDataSource(data=cols1)Curr=ColumnDataSource(data=cols2)#plot and the menu is linked with each other by this callback functioncallback = CustomJS(args=dict(source=Overall, sc=Curr), code="""var f = cb_obj.valuesc.data['date']=[]sc.data['confirmed']=[]for(var i = 0; i <= source.get_length(); i++){	if (source.data['country'][i] == f){		sc.data['date'].push(source.data['date'][i])		sc.data['confirmed'].push(source.data['confirmed'][i])	 }}      sc.change.emit();""")menu = Select(options=country_list,value='Afghanistan', title = 'Country')  # drop down menubokeh_p=figure(x_axis_label ='date', y_axis_label = 'confirmed', y_axis_type="linear",x_axis_type="datetime") #creating figure objectbokeh_p.line(x='date', y='confirmed', color='green', source=Curr) # plotting the data using glyph circlemenu.js_on_change('value', callback) # calling the function on change of selectionlayout=column(menu, bokeh_p) # creating the layoutshow(layout)
This is how the plot will look like -
Altair is similar to Plotly when it comes to building visualizations in a pure pythonic way. While Altair offers a number of interactive components, it is a bit difficult to arrange the components in a user-friendly layout. You can read more about interactive components with Altair here.
We will now create the same plot using Altair and add a dropdown menu to the plot.
input_dropdown = alt.binding_select(options=country_list)selection = alt.selection_single(fields=['country'], bind=input_dropdown, name='Country')alt_plot = alt.Chart(df).mark_line().encode(    x='date',    y='confirmed',    tooltip='confirmed').add_selection(    selection).transform_filter(    selection)alt_plot
This is how the plot will look like -
If you want to use a non-interactive library like Matplotlib or Seaborn, you can add interactivity using Datapane, which is a library for creating and hosting data reports. Datapane offers some advanced layout blocks like dp.Select which let you mimic the interactive filtering ability, like this:
plot_list = []plt.figure(figsize=(10, 5), dpi=300)for country in country_list:    plot = dp.Plot(df[df['country']==country].plot.scatter(x='date', y='confirmed'), label=country)    plot_list.append(plot)report = dp.Report(    dp.Text('''## Dropdown using Datapane'''),    dp.Select(blocks = plot_list))report.preview()
This is how it will look like -
Please note that you cannot add Date Range sliders to your plots using Datapane currently. If you are new to Datapane, you can read more about different blocks here.
Another interactive component that comes really handy while working with timeseries plots is a date range slider or a slider in general.
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.
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.
buttons = []i = 0fig3 = go.Figure()country_list = list(df['country'].unique())for country in country_list:    fig3.add_trace(        go.Scatter(            x = df['date'][df['country']==country],            y = df['confirmed'][df['country']==country],            name = country, visible = (i==0)        )    )    for country in country_list:    args = [False] * len(country_list)    args[i] = True        #create a button object for the country we are on    button = dict(label = country,                  method = "update",                  args=[{"visible": args}])        #add the button to our list of buttons    buttons.append(button)        #i is an iterable used to tell our "args" list which value to set to True    i+=1    fig3.update_layout(updatemenus=[dict(active=0,                                    type="dropdown",                                    buttons=buttons,                                    x = 0,                                    y = 1.1,                                    xanchor = 'left',                                    yanchor = 'bottom'),                              ])fig3.update_layout(    autosize=False,    width=1000,    height=800,)
This will give you something like this -
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.
cols1=df.loc[:, ['country','date', 'confirmed']]cols2 = cols1[cols1['country'] == 'Afghanistan' ]Overall = ColumnDataSource(data=cols1)Curr=ColumnDataSource(data=cols2)#plot and the menu is linked with each other by this callback functioncallback = CustomJS(args=dict(source=Overall, sc=Curr), code="""var f = cb_obj.valuesc.data['date']=[]sc.data['confirmed']=[]for(var i = 0; i <= source.get_length(); i++){	if (source.data['country'][i] == f){		sc.data['date'].push(source.data['date'][i])		sc.data['confirmed'].push(source.data['confirmed'][i])	 }}      sc.change.emit();""")menu = Select(options=country_list,value='Afghanistan', title = 'Country')  # drop down menubokeh_p=figure(x_axis_label ='date', y_axis_label = 'confirmed', y_axis_type="linear",x_axis_type="datetime") #creating figure object bokeh_p.line(x='date', y='confirmed', color='green', source=Curr) # plotting the data using glyph circlemenu.js_on_change('value', callback) # calling the function on change of selectiondate_range_slider = DateRangeSlider(value=(min(df['date']), max(df['date'])),                                    start=min(df['date']), end=max(df['date']))date_range_slider.js_link("value", bokeh_p.x_range, "start", attr_selector=0)date_range_slider.js_link("value", bokeh_p.x_range, "end", attr_selector=1)layout = column(menu, date_range_slider, bokeh_p)show(layout) # displaying the layout
This is how it will look like -
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 very 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.
input_dropdown = alt.binding_select(options=country_list)selection = alt.selection_single(fields=['country'], bind=input_dropdown, name='Country')def timestamp(t):  return pd.to_datetime(t).timestamp() * 1000slider = alt.binding_range(    step=30 * 24 * 60 * 60 * 1000, # 30 days in milliseconds    min=timestamp(min(df['date'])),    max=timestamp(max(df['date'])))select_date = alt.selection_single(    fields=['date'],    bind=slider,    init={'date': timestamp(min(df['date']))},    name='slider')alt_plot = alt.Chart(df).mark_line().encode(    x='date',    y='confirmed',    tooltip='confirmed').add_selection(    selection).transform_filter(    selection).add_selection(select_date).transform_filter(    "(year(datum.date) == year(slider.date[0])) && "    "(month(datum.date) == month(slider.date[0]))")alt_plot
This is how it will look like -
Now having worked with all three libraries, we are in a state to compare them and share our views on which is the best python library to create interactive visualizations.
Pros -
Easy to use and works seamlessly with Python.
Layouts can be designed as per requirements to ensure the menus appear in the right areas of the plot.
Easy to view and embed Plotly plots.
Cons -
Does not offer many interactive components.
Has a steep learning curve to make the interactive components work.
Pros -
Plots and interactive widgets created with Bokeh look aesthetically pleasing.
Since Bokeh is more directed towards creating dashboards, it is easier to create layouts using multiple components.
A number of interactive widgets can be added to the plots.
Cons -
In order to make the widgets work, you need to know a bit of JavaScript to be able to create the CustomJS callbacks.
Lack of proper documentation with sample codes when it comes to creating these JavaScript callbacks.
Difficult to embed these plots unless you are working with Bokeh Server.
Pros -
Altair offers more options of interactive components compared to Plotly.
Easy to build interactive components using pure Python.
Requires fewer lines of code to make the interactive components work.
Cons -
It is difficult to design and create visually appealing layouts using Altair.
Working with timeseries data is not straightforward as Vega does not support timeseries data out of the box and some transformations are required to make it work.
Overall, if you are getting started with interactive plots using Python, Plotly can be a good choice to create simple plots with limited interactive components.
But if you are looking to add a number of interactive components to your plots, then Altair is something you should really try your hands on. The best part is you can add several interactive components with just Python codes.
If you are looking to build extremely complex dashboards which are interactive, you can use Bokeh. But bear in mind that you will need some JavaScript know-how to make them work.
Datapane can be a good option if you want to stick to non-interactive visualization libraries like Matplotlib and Seaborn and still want some form of interactivity like having just a dropdown menu.
I hope you find this tutorial along with the sample codes useful. Do share your thoughts or feedback in the comments below.