Building Interactive Plots in Python
Building Interactive Plots in Python
One of the most common forms of data found throughout the analysis is time series. From identification of trends to understanding behaviours of ’cause and effect,’ time series analysis is one of the most frequent approaches to analyse user activity, purchasing habits and more.
In Python we frequently start by drawing a basic line curve using Matplotlib or Seaborn, which is great when you work with only one variable that changes over time. But you frequently require a list of inventories of market data or territories for sales reports, to present many categorical variables collectively.
But you frequently require a list of inventories of market data or territories for sales reports, to present many categorical variables collectively.
If so, you may either present your whole series in the same plot or build a different plot for each season. But these alternatives are difficult to understand and occupy a lot of room.
This allows sophisticated visualisation libraries such as Plotly, Bokeh, and Altair to build dynamic images and dashboards utilising elements like drop-downs, sliders, buttons and others to let your users explore complicated data.
In this post, we investigate the following interactive features for display of time series:
- Drop down menus allow you to switch between series in the same plot
- Date range sliders that allow you to see patterns between eras
All scripts used in the instances below are available here.
Line-plots with Drop-down menus
If there are several data categories, like inventories or nations, and you want to examine the trends using a line plot in the same plot or figure, a dropdown menu is quite convenient. This prevents you from producing several plots in a loop.
All three libraries, that is to say. Plotly, Bokeh and Altair can add a dropdown menu to the plots, but each one has its own advantages and disadvantages.
As a bonus, I will also show you a technique of doing this using Matplotlib or Seaborn that does not allow interactive components.
Date Range slider
A date range slider or a slider in general is another interactive component that comes extremely helpful while working with timer graphs.
Given that most time series plots include a date range in the X-axis, a slider enables you to modify the time period dynamically and just examine a segment of the plot to understand the patterns in that era.
Plotly
Drop-down menus
Plotly includes a number of interactive options known as Custom Controls. The nice thing about these controls is that they can only be pythonically added to the plots.
We will utilise SuperStore datasets for this lesson, which can be found here.
Let us create a plot to visualize the sales pattern across the United States and add a dropdown to change the category to analyze the sales in that category.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
pd.set_option(“display.max_columns”,None)
pd.set_option(“display.max_rows”,None)
import warnings
warnings.filterwarnings(“ignore”)
from IPython.display import Image
sns.set(style=”darkgrid”, palette=”pastel”, color_codes=True)
sns.set_context(“paper”)
from datetime import datetime
#Plotly imports
import plotly.graph_objects as go
import plotly.express as px
import plotly.io as pio
pio.templates.default = “seaborn”
from plotly.subplots import make_subplots
df = pd.read_csv(“/content/sample_data/Sample-Superstore.csv”,encoding= ‘unicode_escape’)
df.head()
df[‘month’] = pd.DatetimeIndex(df[‘Order Date’]).month
df[‘year’] = pd.DatetimeIndex(df[‘Order Date’]).year
df.head()
buttons = []
i = 0
fig3 = go.Figure()
category_list = list(df[‘Category’].unique())
for category in category_list:
fig3.add_trace(
go.Scatter(
x = df[‘year’][df[‘Category’]==category],
y = df[‘Sales’][df[‘Category’]==category],
name = category, visible = (i==0)
)
)
for category in category_list:
args = [False] * len(category_list)
args[i] = True
#create a button object for the country we are on
button = dict(label = category,
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’),
])
After execution of the above coding, a beautiful drop down will be added to the time-series plot.
import datapane as dp
report = dp.Report(
dp.Text(”’## Dropdown using Plotly”’),
dp.Plot(fig3))
report.upload(name=”Dropdown with Plotly”, source_file = “plotly_dropdown.ipynb”)
The above code is used to generate the report of our plot obtained.
Date Range slider
Plotly features a general slider which may be used to modify the data on any axis. The general slider cannot be used to construct a date range slider since it does not have a dedicated slider for time series data.
More about sliders may be read here.
To create a slider, we will take the same time series plot created previously with the dropdown menu and add a slider component below the plot. For implementation ,the same COVID-19 dataset has been utilized. After executing all the prerequisites, the below code aims in providing a slider to the output.
buttons = []
i = 0
fig3 = 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[‘deaths’][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,)
Bokeh
The documentation about various widgets available in Bokeh can be found here.
Bokeh includes components called widgets that may be added to your plots by adding multiple interactive components. The main objective of widgets is to create dashboard components that are hosting on the Bokeh server.
Bear in mind that CustomJS callbacks are needed to construct widgets for standalone HTML files, or even when working with Jupyter notebook. This takes some understanding of JavaScript to get the dropdown to operate properly. If you want to accomplish this purely pythonically, the Bokeh server has to work with the widgets.
We will reproduce the identical example using Bokeh dropdowns as above
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
pd.set_option(“display.max_columns”,None)
pd.set_option(“display.max_rows”,None)
import warnings
warnings.filterwarnings(“ignore”)
from IPython.display import Image
sns.set(style=”darkgrid”, palette=”pastel”, color_codes=True)
sns.set_context(“paper”)
from datetime import datetime
#import datapane as dp
#Bokeh imports
from bokeh.io import output_file, show, output_notebook, save
from bokeh.models import ColumnDataSource, Select, DateRangeSlider
from bokeh.plotting import figure, show
from bokeh.models import CustomJS
from bokeh.layouts import row,column
output_notebook()
df = pd.read_csv(“/content/sample_data/covid_19_clean_complete.csv”,encoding= ‘unicode_escape’)
df.head()
df.rename(columns={‘Date’: ‘date’,
‘Province/State’:’state’,
‘Country/Region’:’country’,
‘Lat’:’lat’, ‘Long’:’long’,
‘Confirmed’: ‘confirmed’,
‘Deaths’:’deaths’,
‘Recovered’:’recovered’
}, inplace=True)
# Active Case = confirmed – deaths – recovered
df[‘active’] = df[‘confirmed’] – df[‘deaths’] – df[‘recovered’]
df[‘date’] = pd.to_datetime(df[‘date’])
country_list = list(df[‘country’].unique())
cols1=df.loc[:, [‘country’,’date’, ‘deaths’]]
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 function
callback = CustomJS(args=dict(source=Overall, sc=Curr), code=”””
var f = cb_obj.value
sc.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 menu
bokeh_p=figure(x_axis_label =’date’, y_axis_label = ‘deaths’, y_axis_type=”linear”,x_axis_type=”datetime”) #creating figure object
bokeh_p.line(x=’date’, y=’deaths’, color=’green’, source=Curr) # plotting the data using glyph circle
menu.js_on_change(‘value’, callback) # calling the function on change of selection
layout=column(menu, bokeh_p) # creating the layout
show(layout)
This is how the plot will look like –
Date Range Slider
Similar to the Dropdown widget, Bokeh offers a Date Range Slider to work with time series data particularly. This widget is different from the general widget Range Slider. A CustomJS callback is required to make this widget operate.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
pd.set_option(“display.max_columns”,None)
pd.set_option(“display.max_rows”,None)
import warnings
warnings.filterwarnings(“ignore”)
from IPython.display import Image
sns.set(style=”darkgrid”, palette=”pastel”, color_codes=True)
sns.set_context(“paper”)
from datetime import datetime
#import datapane as dp
#Bokeh imports
from bokeh.io import output_file, show, output_notebook, save
from bokeh.models import ColumnDataSource, Select, DateRangeSlider
from bokeh.plotting import figure, show
from bokeh.models import CustomJS
from bokeh.layouts import row,column
output_notebook()
df[‘date’] = pd.to_datetime(df[‘date’])
country_list = list(df[‘country’].unique())
cols1=df.loc[:, [‘country’,’date’, ‘deaths’]]
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 function
callback = CustomJS(args=dict(source=Overall, sc=Curr), code=”””
var f = cb_obj.value
sc.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 menu
bokeh_p=figure(x_axis_label =’date’, y_axis_label = ‘deaths’, y_axis_type=”linear”,x_axis_type=”datetime”) #creating figure object
bokeh_p.line(x=’date’, y=’deaths’, color=’green’, source=Curr) # plotting the data using glyph circle
menu.js_on_change(‘value’, callback) # calling the function on change of selection
date_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
Altair
Altair is comparable to Plotly in the production of pure pythonic visuals. Altair includes a variety of interactive components, however organising the components in a user friendly style is somewhat challenging. Here you may read more about Altair’s interactive components.
We are now using Altair to construct the same plot and add a drop down menu to the plot.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
pd.set_option(“display.max_columns”,None)
pd.set_option(“display.max_rows”,None)
import warnings
warnings.filterwarnings(“ignore”)
from IPython.display import Image
sns.set(style=”darkgrid”, palette=”pastel”, color_codes=True)
sns.set_context(“paper”)
from datetime import datetime
#Altair imports
import altair as alt
alt.data_transformers.disable_max_rows()
df = pd.read_csv(‘/content/sample_data/covid_19_clean_complete.csv’)
df.head()
df.rename(columns={‘Date’: ‘date’,
‘Province/State’:’state’,
‘Country/Region’:’country’,
‘Lat’:’lat’, ‘Long’:’long’,
‘Confirmed’: ‘confirmed’,
‘Deaths’:’deaths’,
‘Recovered’:’recovered’
}, inplace=True)
# Active Case = confirmed – deaths – recovered
df[‘active’] = df[‘confirmed’] – df[‘deaths’] – df[‘recovered’]
df[‘date’] = pd.to_datetime(df[‘date’])
country_list = list(df[‘country’].unique())
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=’deaths’,
tooltip=’deaths’
).add_selection(
selection
).transform_filter(
selection
)
alt_plot
Date Range Slider
You may use the general slider with Altair, similar to Plotly, as a Date Range Slider. Be aware that Vega evaluates the time series data in milliseconds and the date information in the slider is quite difficult to present. It works if you have annual data, but if the data are broken in days and months, it is difficult to do this.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
pd.set_option(“display.max_columns”,None)
pd.set_option(“display.max_rows”,None)
import warnings
warnings.filterwarnings(“ignore”)
from IPython.display import Image
sns.set(style=”darkgrid”, palette=”pastel”, color_codes=True)
sns.set_context(“paper”)
from datetime import datetime
import datapane as dp
#Altair imports
import altair as alt
alt.data_transformers.disable_max_rows()
df = pd.read_csv(‘/content/sample_data/covid_19_clean_complete.csv’)
#Renaming the columns for easy usage
df.rename(columns={‘Date’: ‘date’,
‘Province/State’:’state’,
‘Country/Region’:’country’,
‘Lat’:’lat’, ‘Long’:’long’,
‘Confirmed’: ‘confirmed’,
‘Deaths’:’deaths’,
‘Recovered’:’recovered’
}, inplace=True)
# Active Case = confirmed – deaths – recovered
df[‘active’] = df[‘confirmed’] – df[‘deaths’] – df[‘recovered’]
df[‘date’] = pd.to_datetime(df[‘date’])
country_list = list(df[‘country’].unique())
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() * 1000
slider = 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=’deaths’,
tooltip=’deaths’
).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 –
Outcomes
We now work with all three libraries and are in a position to compare them and offer our opinions, which are the finest library of Python to generate interactive infographics.
Plotly
Benefits –
- Easy to use and operate with Python smoothly.
- Layouts may be created to ensure that menus display in the correct locations of the plot.
- Plotly plots may be easily seen and embedded.
Drawbacks
- Plotly plots are easy to read and integrate.
- Not many interactive components available.
- Has a steep slope for interactive components to function.
Bokeh
Benefits –
- Aesthetically attractive are plots and interactive widgets made using Bokeh.
- Since Bokeh is focused mainly towards generating dashboards, layouts with numerous components may easily be created.
- Add to the plots a variety of interactive widgets.
Drawbacks –
- You need to know a little of JavaScript to construct CustomJS Callbacks to make widgets function.
- Lack of sufficient sample code documentation for implementing these JavaScript callbacks.
- Difficult to integrate these plots unless if you work with Bokeh Server.
Altair
Benefits –
- Altair provides more interactive component options than Plotly.
- Easy to create pure Python interactive components.
- Requires less code lines to make interactive components operate.
Drawbacks –
- Design and aesthetically attractive layouts are challenging via Altair.
- It is not easy to deal with time series data, as Vega does not accept time series data from the booth and certain transformations are necessary to make this work.
Conclusion
In general, Plotly can help construct basic charts with minimal interactive components if you start with interactive plots using Python.
But if you want to add a bunch of interactive components to your charts, you should certainly try Altair on it. The finest part is to create several interactive components with Python scripts.
You may use Bokeh if you want to create very sophisticated dashboards that are interactive. But remember that to make them function you will need some JavaScript know-how.
I hope that this lesson and the sample scripts are beneficial for you.
Share your views or feedback in the following comments.