Introduction
I was recently interested in learning more about the Panel framework for Python. Panel is a powerful data exploration and web app framework that makes it easy to create interactive visualizations and dashboards. I decided to create a demo to showcase the capabilities of Panel.
For my demo, I used data from Kaggle on electric vehicles in the state of Washington. The data includes information on the make, model, year, mileage, and range and other unique data of each vehicle. I also used a Bootstrap admin template as a starting point for the layout of my demo.
The final result looks like this:
You can find the web application demo runnning here.
My Github repository of this code can be found here.
Development
The demo consists of three pages:
- A main dashboard that displays summary statistics of the data, as well as a table of all the data.
- A bar graph that shows the distribution of electric vehicle manufacturers in Washington state.
- A map that shows the distribution of electric vehicles by county.
Main Dashboard
The main dashboard displays the following summary statistics:
- The total number of manufacturers
- The average range of electric vehicles
- The most popular manufacturer
- The least popular manufacturer
The dashboard also displays a table of all the data. The table is sortable and searchable, making it easy to find specific information.
EV Popularity
The EV popularity page displays a bar graph that shows the distribution of electric vehicles by manufacturer. The graph is interactive, allowing users to hover over each bar to see the exact number of vehicles for that manufacturer.
County Info
The county info page displays a map of Washington state. The map is divided into polygons, each of which represents a county. Users can zoom in on any county to see the number of electric vehicles in that county.
The Code
The demo is written in Python. The main code is responsible for loading the data, creating the pages, and adding the widgets. The specific code for each page is as follows:
- app.py: The main code.
import panel as pn
from panel.template import FastListTemplate
from EVBrandsDashboard import EVBrandsDashboard
from MainDashboard import MainDashboard
from CountyInfoDashboard import CountyInfoDashboard
def main():
pn.state.clear_caches()
main_area = pn.Column("")
pn.extension('echarts','tabulator')
pn.config.sizing_mode="stretch_width"
# Instantiate pages and add them to the pages dictionarqy
sidebar = None
data_source_info = pn.pane.Markdown("""
***The data describes EV cars in washington state split by makers and regions***
***Data is taken from [Kaggle](https://www.kaggle.com/datasets/qnqfbqfqo/electric-vehicle-population-in-washington-state)!***
""")
right_click_here = pn.pane.Markdown("""
***Right click the Kaggle link to view the data source
""")
a_list = ['MainDashboard','MakeAggDashboard','CountyInfo']
tag_param = pn.Param(name='tag')
setattr(tag_param, 'value', a_list)
setattr(tag_param, 'type','list')
pages = {
"MainDashboard" : MainDashboard(),
"MakeAggDashboard": EVBrandsDashboard(metric_name="Make",index_col="Make",values_col="County",values_header="Make",y_title="Most Popular Car Makers"),
"CountyInfo" : CountyInfoDashboard(),
}
radio_button_group = pn.widgets.RadioButtonGroup(name='Index',
options=['Main','EV Popularity','County Info'],
#behaviour="radio",
button_type='primary',
orientation='vertical',
)
def update(event):
main_area.clear()
obj = pages.get("MainDashboard")
if event.type == "changed":
if event.new == "Main":
obj = pages.get("MainDashboard")
elif event.new == "EV Popularity":
obj = pages.get("MakeAggDashboard")
elif event.new == "County Info":
obj = pages.get("CountyInfo")
main_area.append(obj.view())
else:
main_area.append(obj.view())
#main_area.append(pages[radio_button_group.tag[radio_button_group.values.index(radio_button_group.value)]].view())
radio_button_group.param.watch(update,'value',onlychanged=True)
radio_button_group.param.trigger('value')
sidebar = pn.Column(pn.Row(radio_button_group),pn.Row(data_source_info),pn.Row(right_click_here))
template = FastListTemplate(
title="Washington EV Cars",
sidebar=[sidebar],
main=[main_area],
)
pn.state.cache["template"] = template
pn.state.cache["modal"] = template.modal
# Serve the Panel app
template.servable()
main()
- Maindashboard.py: The main dashboard uses a Panel Tabs widget to display the summary statistics and the table of data.
from Dashboard import Dashboard
import panel as pn
class MainDashboard(Dashboard):
def topmost_and_least_popular_makers(self):
# Group the DataFrame by the car manufacturer column and count the occurrences
count_df = self.df['Make'].value_counts()
# Get the most popular car manufacturer
most_popular_car_manufacturer = count_df.index[0]
least_popular_car_manufacturer = count_df.index[count_df.size-1]
return { "mpcm" : most_popular_car_manufacturer,
"lpcm" : least_popular_car_manufacturer }
def __init__(self):
fs = "48px"
fst = "20px"
df_tabulator = pn.widgets.Tabulator(self.df, name='wacca', page_size=10)
total_car_types = self.df['Make'].nunique()
avg_range = self.df['Electric Range'].mean()
topmost_and_least_popular_maker = self.topmost_and_least_popular_makers()
row = pn.Row(
pn.indicators.Number(name='Total Car Types', value=total_car_types, format='{value}', font_size=fs, title_size=fst),
pn.indicators.Number(name='Avg. Range', value=avg_range, format='{value:.2f}', font_size=fs, title_size=fst),
pn.indicators.String(name="Most Popular Maker",value=topmost_and_least_popular_maker["mpcm"],font_size=fs,title_size=fst),
pn.indicators.String(name='Least Popular Maker', value=topmost_and_least_popular_maker["lpcm"], font_size=fs, title_size=fst)
)
col = pn.Column(row, pn.Row(df_tabulator))
self.content = col
def view(self):
return self.content
- EVBrandsDashboard.py: The EV popularity page uses a Panel BarContainer widget to display the bar graph.
from Dashboard import Dashboard
import pandas as pd
import panel as pn
import numpy as np
class EVBrandsDashboard(Dashboard):
def __init__(self,metric_name,index_col, values_col,values_header,y_title):
pivot_table=pd.pivot_table(self.df, index=index_col, values=values_col, aggfunc=np.count_nonzero)
obj = self.echarts_bar_chart(np.array(pivot_table.index.values),
np.array(pivot_table[values_col].values),
y_title)
col = pn.Column(obj)
self.content = col
def echarts_bar_chart(self,x,y,y_title):
echart_bar = {
'title': {
'text': y_title
},
'tooltip': {},
'legend': {
'data':['Index']
},
'xAxis': {
'data': x.tolist()
},
'yAxis': {},
'series': [{
'name': 'Make',
'type': 'bar',
'data': y.tolist()
}],
}
echart_pane = pn.pane.ECharts(echart_bar, height=480, width=640)
return echart_pane
def view(self):
return self.content
- CountyInfoDashboard.py: The county info page uses a Panel Map widget to display the map.
from Dashboard import Dashboard
import panel as pn
import re
import folium
from folium.plugins import FastMarkerCluster
from functools import wraps
from time import perf_counter
def init(self):
m = folium.Map(location=[47.420664984, -120.321832046], zoom_start=7) ## map of Washington
num_rows = self.df.shape[0]
cluster_size = num_rows
count = int(num_rows / cluster_size) + 1
marker_cluster = list([None] * count)
#marker_cluster = MarkerCluster().add_to(m)
folium_pane = pn.pane.plot.Folium(m, height=600)
col_name = "Vehicle Location"
i = 0
cluster = 0
locations = list()
for row in self.df.iterrows():
i += 1
if i == 1:
pass
elif i > cluster_size:
cluster += 1
i = 0
try:
string = re.sub(r"POINT\s*\(|\)", "",row[1]['Vehicle Location'])
except TypeError:
# Split the string into two float numbers.
if not isinstance(string, str):
continue
try:
s_longitude, s_latitude = string.split(" ")
longitude = float(s_longitude)
latitude = float(s_latitude)
locations.append((latitude,longitude))
except Exception:
continue
num_rows -= 1
marker_cluster[cluster] = FastMarkerCluster(locations).add_to(m)
folium_pane.object = m
row = pn.Column(folium_pane)
self.content = row
class CountyInfoDashboard(Dashboard):
pn.extension(sizing_mode="stretch_width")
def __init__(self):
init(self)
def view(self):
return self.content
Conclusion
I was pleased with the results of my demo. I was able to create a visually appealing and informative application that showcases the capabilities of Panel out of rich data I got from Kaggle. I am confident that this demo will be a valuable resource for anyone who is interested in learning more about Panel.
Additional Details
In addition to the code that I provided, I also used the following libraries and tools to create my demo:
- pandas for loading and manipulating the data
- Bootstrap for the layout of the pages
- Folium for the drawing of the map of Washington and the layers above it
Challenges and Lessons Learned
While creating my demo, I faced a few challenges. One challenge was to handle the map rendering using the folium library at a short time span. The reason for this delay was that I had to add more than 150,000 markers to the map. At the beginning it took more than 5 minutes to load it!. Eventually the problem was solved using the FastMarkerCluster object instead of using the a standard Marker and Marker cluster.
Another challenge was dealing with the size of the data set. The data set I used for my demo contained over 100,000 rows. This made it challenging to load the data into memory and manipulate it. I found it helpful to use a data processing library like pandas to help me manage the data.
Despite these challenges, I was able to overcome them and create a successful demo. I learned a lot about the Panel library and data visualization in the process. Here are a few lessons I learned:
- Panel is a powerful tool that can be used to create a variety of data visualizations and dashboards.
- It is important to learn how to use the Panel library effectively.
- Data processing libraries can be helpful for managing large data sets.
Conclusion
I am proud of the demo I created. It is a valuable resource for anyone who is interested in learning more about the Panel library. I am excited to continue learning about Panel and using it to create even more impressive data visualizations and dashboards.