Commit ·
4830705
1
Parent(s): 290ce05
:sparkles: Add Plotly and interactivity enhancements to dashboard
Browse filesIntegrated Plotly and streamlit-plotly-events for improved data visualization and interactivity in the dashboard. Updated the pyproject.toml and poetry.lock files to include these new dependencies. The electric vehicle data dashboard now features interactive charts and maps, allowing users to click on data points to filter and explore the information more effectively.
- app.py +98 -69
- poetry.lock +31 -1
- pyproject.toml +2 -0
app.py
CHANGED
|
@@ -1,115 +1,134 @@
|
|
| 1 |
-
# INSTRUCTIONS:
|
| 2 |
-
# 1. Open a "Terminal" by: View --> Terminal OR just the "Terminal" through the hamburger menu
|
| 3 |
-
# 2. run in terminal with: streamlit run app.py
|
| 4 |
-
# 3. click the "Open in Browser" link that pops up OR click on "Ports" and copy the URL
|
| 5 |
-
# 4. Open a Simple Browswer with View --> Command Palette --> Simple Browser: Show
|
| 6 |
-
# 5. use the URL from prior steps as intput into this simple browser
|
| 7 |
-
|
| 8 |
-
import altair as alt
|
| 9 |
import pandas as pd
|
|
|
|
| 10 |
import pydeck as pdk
|
| 11 |
import streamlit as st
|
|
|
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
st.title('Group 5 Final Project - Part 2 (Dashboard)')
|
| 14 |
st.text("Group Members: Nabeel Bashir, Tony An, Devansh Kumar, Jiajun Li")
|
| 15 |
|
|
|
|
| 16 |
st.text("The URL for this app is: https://huggingface.co/spaces/fa24-is445-group5/Final_Project_Part2")
|
| 17 |
|
|
|
|
| 18 |
ev_data = pd.read_csv('data/Electric_Vehicle_Population_Data.csv')
|
| 19 |
|
|
|
|
| 20 |
st.title('Electric Vehicle Population Data Dashboard')
|
| 21 |
st.markdown('''Explore the electric vehicle data interactively using this dashboard.
|
| 22 |
This dashboard helps experts and stakeholders understand the distribution and characteristics of electric vehicles
|
| 23 |
in various counties and cities.''')
|
| 24 |
|
|
|
|
| 25 |
st.markdown('''### How to Use This Dashboard
|
| 26 |
-
To explore the dataset, use the
|
| 27 |
-
|
| 28 |
-
|
| 29 |
|
| 30 |
All charts are interactive. You can click on bars to highlight corresponding data points in other charts, making it easier to discover patterns and relationships.
|
| 31 |
|
| 32 |
The map is also interactive; you can zoom in, rotate, and click on points to see additional information about each vehicle.''')
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
county_list = ev_data['County'].unique()
|
| 35 |
make_list = ev_data['Make'].unique()
|
| 36 |
|
| 37 |
selected_county = st.sidebar.selectbox('Select County', county_list)
|
| 38 |
selected_make = st.sidebar.multiselect('Select Make', make_list, default=make_list)
|
| 39 |
|
|
|
|
| 40 |
filtered_data = ev_data[(ev_data['County'] == selected_county) & (ev_data['Make'].isin(selected_make))]
|
| 41 |
|
|
|
|
| 42 |
st.subheader('Filtered Data')
|
| 43 |
st.write(filtered_data)
|
| 44 |
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
bar_chart = alt.Chart(ev_type_count).mark_bar().encode(
|
| 50 |
-
x=alt.X('Electric Vehicle Type', sort='-y', title='Electric Vehicle Type'),
|
| 51 |
-
y=alt.Y('Count', title='Number of Vehicles'),
|
| 52 |
-
tooltip=['Electric Vehicle Type', 'Count'],
|
| 53 |
-
color='Electric Vehicle Type'
|
| 54 |
-
).properties(
|
| 55 |
-
width=600,
|
| 56 |
-
height=400,
|
| 57 |
-
title='Distribution of Electric Vehicle Types in Selected County'
|
| 58 |
-
).interactive()
|
| 59 |
-
|
| 60 |
-
st.altair_chart(bar_chart)
|
| 61 |
-
|
| 62 |
-
st.subheader('Electric Range Distribution')
|
| 63 |
-
electric_range_chart = alt.Chart(filtered_data).mark_bar().encode(
|
| 64 |
-
alt.X('Electric Range', bin=alt.Bin(maxbins=30), title='Electric Range (Miles)'),
|
| 65 |
-
alt.Y('count()', title='Frequency'),
|
| 66 |
-
tooltip=['count()'],
|
| 67 |
-
color=alt.value('steelblue')
|
| 68 |
-
).properties(
|
| 69 |
-
width=600,
|
| 70 |
-
height=400,
|
| 71 |
-
title='Electric Range Distribution in Selected County'
|
| 72 |
-
).interactive()
|
| 73 |
-
|
| 74 |
-
st.altair_chart(electric_range_chart)
|
| 75 |
-
|
| 76 |
-
st.subheader('Vehicle Count by Model Year')
|
| 77 |
-
model_year_count = filtered_data['Model Year'].value_counts().reset_index()
|
| 78 |
-
model_year_count.columns = ['Model Year', 'Count']
|
| 79 |
-
|
| 80 |
-
year_chart = alt.Chart(model_year_count).mark_bar().encode(
|
| 81 |
-
x=alt.X('Model Year:N', sort='-y', title='Model Year'),
|
| 82 |
-
y=alt.Y('Count', title='Number of Vehicles'),
|
| 83 |
-
tooltip=['Model Year', 'Count'],
|
| 84 |
-
color='Model Year:N'
|
| 85 |
-
).properties(
|
| 86 |
-
width=600,
|
| 87 |
-
height=400,
|
| 88 |
-
title='Number of Vehicles by Model Year'
|
| 89 |
-
).interactive()
|
| 90 |
-
|
| 91 |
-
st.altair_chart(year_chart)
|
| 92 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
st.subheader('Map of Vehicle Locations')
|
| 94 |
-
ev_data[['Longitude', 'Latitude']] = ev_data['Vehicle Location'].str.extract(r'POINT \((-?\d+\.\d+) (-?\d+\.\d+)\)')
|
| 95 |
-
ev_data['Latitude'] = pd.to_numeric(ev_data['Latitude'], errors='coerce')
|
| 96 |
-
ev_data['Longitude'] = pd.to_numeric(ev_data['Longitude'], errors='coerce')
|
| 97 |
-
|
| 98 |
-
filtered_data[['Longitude', 'Latitude']] = filtered_data['Vehicle Location'].str.extract(r'POINT \((-?\d+\.\d+) (-?\d+\.\d+)\)')
|
| 99 |
-
filtered_data['Latitude'] = pd.to_numeric(filtered_data['Latitude'], errors='coerce')
|
| 100 |
-
filtered_data['Longitude'] = pd.to_numeric(filtered_data['Longitude'], errors='coerce')
|
| 101 |
-
|
| 102 |
filtered_data = filtered_data.dropna(subset=['Latitude', 'Longitude'])
|
| 103 |
filtered_data.rename(columns={'Latitude': 'latitude', 'Longitude': 'longitude'}, inplace=True)
|
| 104 |
-
# st.map(filtered_data[['latitude', 'longitude']])
|
| 105 |
|
|
|
|
| 106 |
st.pydeck_chart(pdk.Deck(
|
| 107 |
-
# map_style='mapbox://styles/mapbox/light-v9',
|
| 108 |
initial_view_state=pdk.ViewState(
|
| 109 |
latitude=filtered_data['latitude'].mean(),
|
| 110 |
longitude=filtered_data['longitude'].mean(),
|
| 111 |
zoom=10,
|
| 112 |
-
# pitch=50,
|
| 113 |
),
|
| 114 |
layers=[
|
| 115 |
pdk.Layer(
|
|
@@ -129,3 +148,13 @@ st.pydeck_chart(pdk.Deck(
|
|
| 129 |
}
|
| 130 |
}
|
| 131 |
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import pandas as pd
|
| 2 |
+
import plotly.express as px
|
| 3 |
import pydeck as pdk
|
| 4 |
import streamlit as st
|
| 5 |
+
from streamlit_plotly_events import plotly_events
|
| 6 |
|
| 7 |
+
# Group 5 Final Project - Part 2 Dashboard
|
| 8 |
+
# Group Members: Nabeel Bashir, Tony An, Devansh Kumar, Jiajun Li
|
| 9 |
+
|
| 10 |
+
# Streamlit application setup
|
| 11 |
st.title('Group 5 Final Project - Part 2 (Dashboard)')
|
| 12 |
st.text("Group Members: Nabeel Bashir, Tony An, Devansh Kumar, Jiajun Li")
|
| 13 |
|
| 14 |
+
# Project URL
|
| 15 |
st.text("The URL for this app is: https://huggingface.co/spaces/fa24-is445-group5/Final_Project_Part2")
|
| 16 |
|
| 17 |
+
# Load the electric vehicle dataset
|
| 18 |
ev_data = pd.read_csv('data/Electric_Vehicle_Population_Data.csv')
|
| 19 |
|
| 20 |
+
# Dashboard introduction
|
| 21 |
st.title('Electric Vehicle Population Data Dashboard')
|
| 22 |
st.markdown('''Explore the electric vehicle data interactively using this dashboard.
|
| 23 |
This dashboard helps experts and stakeholders understand the distribution and characteristics of electric vehicles
|
| 24 |
in various counties and cities.''')
|
| 25 |
|
| 26 |
+
# Dashboard usage explanation
|
| 27 |
st.markdown('''### How to Use This Dashboard
|
| 28 |
+
To explore the dataset, use the global filter options in the sidebar to filter by county and make. These filters will update all the visualizations, providing a focused view of the data.
|
| 29 |
+
|
| 30 |
+
Alternatively, you can select a specific vehicle make directly in the "Vehicle Count by Make" chart. This selection will link to and update the rest of the visualizations to display data relevant to the selected make.
|
| 31 |
|
| 32 |
All charts are interactive. You can click on bars to highlight corresponding data points in other charts, making it easier to discover patterns and relationships.
|
| 33 |
|
| 34 |
The map is also interactive; you can zoom in, rotate, and click on points to see additional information about each vehicle.''')
|
| 35 |
|
| 36 |
+
# Extract latitude and longitude from 'Vehicle Location' column
|
| 37 |
+
ev_data[['Longitude', 'Latitude']] = ev_data['Vehicle Location'].str.extract(r'POINT \((-?\d+\.\d+) (-?\d+\.\d+)\)')
|
| 38 |
+
ev_data['Latitude'] = pd.to_numeric(ev_data['Latitude'], errors='coerce')
|
| 39 |
+
ev_data['Longitude'] = pd.to_numeric(ev_data['Longitude'], errors='coerce')
|
| 40 |
+
|
| 41 |
+
# Sidebar filter options
|
| 42 |
county_list = ev_data['County'].unique()
|
| 43 |
make_list = ev_data['Make'].unique()
|
| 44 |
|
| 45 |
selected_county = st.sidebar.selectbox('Select County', county_list)
|
| 46 |
selected_make = st.sidebar.multiselect('Select Make', make_list, default=make_list)
|
| 47 |
|
| 48 |
+
# Filter data based on sidebar selections
|
| 49 |
filtered_data = ev_data[(ev_data['County'] == selected_county) & (ev_data['Make'].isin(selected_make))]
|
| 50 |
|
| 51 |
+
# Display filtered data
|
| 52 |
st.subheader('Filtered Data')
|
| 53 |
st.write(filtered_data)
|
| 54 |
|
| 55 |
+
# Interactive Bar Chart - Vehicle Count by Make
|
| 56 |
+
st.subheader('Vehicle Count by Make')
|
| 57 |
+
make_counts = filtered_data['Make'].value_counts().reset_index()
|
| 58 |
+
make_counts.columns = ['Make', 'Count']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
+
def create_bar_chart(selected_make=None):
|
| 61 |
+
# If a make is selected, highlight it in blue, otherwise default to green
|
| 62 |
+
if selected_make:
|
| 63 |
+
make_counts['Color'] = make_counts['Make'].apply(
|
| 64 |
+
lambda x: 'blue' if x == selected_make else 'green'
|
| 65 |
+
)
|
| 66 |
+
color_map = make_counts.set_index('Make')['Color']
|
| 67 |
+
else:
|
| 68 |
+
color_map = 'green'
|
| 69 |
+
|
| 70 |
+
# Create a bar chart showing vehicle count by make
|
| 71 |
+
chart = px.bar(
|
| 72 |
+
make_counts,
|
| 73 |
+
x='Make',
|
| 74 |
+
y='Count',
|
| 75 |
+
title="Vehicle Count by Make",
|
| 76 |
+
labels={'Make': 'Vehicle Make', 'Count': 'Number of Vehicles'},
|
| 77 |
+
color='Make',
|
| 78 |
+
color_discrete_map=color_map if isinstance(color_map, dict) else None
|
| 79 |
+
)
|
| 80 |
+
chart.update_layout(clickmode='event+select') # Enable click interactions
|
| 81 |
+
return chart
|
| 82 |
+
|
| 83 |
+
# Display the bar chart and capture click data
|
| 84 |
+
bar_chart = create_bar_chart(st.session_state.get('selected_make', None))
|
| 85 |
+
clicked_points = plotly_events(bar_chart, click_event=True, hover_event=False, select_event=False)
|
| 86 |
+
|
| 87 |
+
# Update the selected make based on user interaction
|
| 88 |
+
if clicked_points:
|
| 89 |
+
st.session_state['selected_make'] = clicked_points[0]['x'] # Get clicked make
|
| 90 |
+
else:
|
| 91 |
+
st.session_state['selected_make'] = None
|
| 92 |
+
|
| 93 |
+
# Display selected make
|
| 94 |
+
selected_make = st.session_state['selected_make']
|
| 95 |
+
if selected_make:
|
| 96 |
+
st.write(f"Selected Make: {selected_make}")
|
| 97 |
+
|
| 98 |
+
# Filter data for the line chart based on selected make
|
| 99 |
+
if selected_make:
|
| 100 |
+
filtered_data = ev_data[ev_data['Make'] == selected_make]
|
| 101 |
+
else:
|
| 102 |
+
filtered_data = ev_data
|
| 103 |
+
|
| 104 |
+
# Group data by model year for the filtered data
|
| 105 |
+
model_year_data = filtered_data.groupby('Model Year').size().reset_index(name='Count')
|
| 106 |
+
|
| 107 |
+
# Create the line chart for model count by year
|
| 108 |
+
st.subheader(f"Number of Vehicles by Model Year for {selected_make if selected_make else 'All Makes'}")
|
| 109 |
+
line_chart = px.line(
|
| 110 |
+
model_year_data,
|
| 111 |
+
x='Model Year',
|
| 112 |
+
y='Count',
|
| 113 |
+
title=f"Number of Vehicles by Model Year for {selected_make if selected_make else 'All Makes'}",
|
| 114 |
+
labels={'Model Year': 'Year', 'Count': 'Number of Vehicles'},
|
| 115 |
+
markers=True
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
# Display the line chart
|
| 119 |
+
st.plotly_chart(line_chart, use_container_width=True)
|
| 120 |
+
|
| 121 |
+
# Interactive Map of Vehicle Locations
|
| 122 |
st.subheader('Map of Vehicle Locations')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
filtered_data = filtered_data.dropna(subset=['Latitude', 'Longitude'])
|
| 124 |
filtered_data.rename(columns={'Latitude': 'latitude', 'Longitude': 'longitude'}, inplace=True)
|
|
|
|
| 125 |
|
| 126 |
+
# Create an interactive map using PyDeck
|
| 127 |
st.pydeck_chart(pdk.Deck(
|
|
|
|
| 128 |
initial_view_state=pdk.ViewState(
|
| 129 |
latitude=filtered_data['latitude'].mean(),
|
| 130 |
longitude=filtered_data['longitude'].mean(),
|
| 131 |
zoom=10,
|
|
|
|
| 132 |
),
|
| 133 |
layers=[
|
| 134 |
pdk.Layer(
|
|
|
|
| 148 |
}
|
| 149 |
}
|
| 150 |
))
|
| 151 |
+
|
| 152 |
+
# Contextual datasets
|
| 153 |
+
st.markdown('''### Contextual Datasets
|
| 154 |
+
A potentially useful contextual dataset could be the [Electric Charging Stations Locations](https://afdc.energy.gov/fuels/electricity_locations.html).
|
| 155 |
+
This dataset will provide information about the availability of charging stations in each county, allowing for a deeper
|
| 156 |
+
analysis of the convenience and accessibility of electric vehicles in different regions.''')
|
| 157 |
+
|
| 158 |
+
# Dataset size comment
|
| 159 |
+
st.markdown('''### Dataset Size
|
| 160 |
+
This dataset is uploaded huggingface repo using Git LFS, so there is no need to revise the plan for hosting this data.''')
|
poetry.lock
CHANGED
|
@@ -686,6 +686,21 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa
|
|
| 686 |
typing = ["typing-extensions"]
|
| 687 |
xmp = ["defusedxml"]
|
| 688 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 689 |
[[package]]
|
| 690 |
name = "protobuf"
|
| 691 |
version = "5.29.0"
|
|
@@ -1039,6 +1054,21 @@ watchdog = {version = ">=2.1.5,<7", markers = "platform_system != \"Darwin\""}
|
|
| 1039 |
[package.extras]
|
| 1040 |
snowflake = ["snowflake-connector-python (>=2.8.0)", "snowflake-snowpark-python[modin] (>=1.17.0)"]
|
| 1041 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1042 |
[[package]]
|
| 1043 |
name = "tenacity"
|
| 1044 |
version = "9.0.0"
|
|
@@ -1183,4 +1213,4 @@ watchmedo = ["PyYAML (>=3.10)"]
|
|
| 1183 |
[metadata]
|
| 1184 |
lock-version = "2.0"
|
| 1185 |
python-versions = "^3.12"
|
| 1186 |
-
content-hash = "
|
|
|
|
| 686 |
typing = ["typing-extensions"]
|
| 687 |
xmp = ["defusedxml"]
|
| 688 |
|
| 689 |
+
[[package]]
|
| 690 |
+
name = "plotly"
|
| 691 |
+
version = "5.24.1"
|
| 692 |
+
description = "An open-source, interactive data visualization library for Python"
|
| 693 |
+
optional = false
|
| 694 |
+
python-versions = ">=3.8"
|
| 695 |
+
files = [
|
| 696 |
+
{file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"},
|
| 697 |
+
{file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"},
|
| 698 |
+
]
|
| 699 |
+
|
| 700 |
+
[package.dependencies]
|
| 701 |
+
packaging = "*"
|
| 702 |
+
tenacity = ">=6.2.0"
|
| 703 |
+
|
| 704 |
[[package]]
|
| 705 |
name = "protobuf"
|
| 706 |
version = "5.29.0"
|
|
|
|
| 1054 |
[package.extras]
|
| 1055 |
snowflake = ["snowflake-connector-python (>=2.8.0)", "snowflake-snowpark-python[modin] (>=1.17.0)"]
|
| 1056 |
|
| 1057 |
+
[[package]]
|
| 1058 |
+
name = "streamlit-plotly-events"
|
| 1059 |
+
version = "0.0.6"
|
| 1060 |
+
description = "Plotly chart component for Streamlit that also allows for events to bubble back up to Streamlit."
|
| 1061 |
+
optional = false
|
| 1062 |
+
python-versions = ">=3.6"
|
| 1063 |
+
files = [
|
| 1064 |
+
{file = "streamlit-plotly-events-0.0.6.tar.gz", hash = "sha256:1fe25dbf0e5d803aeb90253be04d7b395f5bcfdf3c654f96ff3c19424e7f9582"},
|
| 1065 |
+
{file = "streamlit_plotly_events-0.0.6-py3-none-any.whl", hash = "sha256:e63fbe3c6a0746fdfce20060fc45ba5cd97805505c332b27372dcbd02c2ede29"},
|
| 1066 |
+
]
|
| 1067 |
+
|
| 1068 |
+
[package.dependencies]
|
| 1069 |
+
plotly = ">=4.14.3"
|
| 1070 |
+
streamlit = ">=0.63"
|
| 1071 |
+
|
| 1072 |
[[package]]
|
| 1073 |
name = "tenacity"
|
| 1074 |
version = "9.0.0"
|
|
|
|
| 1213 |
[metadata]
|
| 1214 |
lock-version = "2.0"
|
| 1215 |
python-versions = "^3.12"
|
| 1216 |
+
content-hash = "6caa9520ca582a0ad3e2e5b651aee6f48bd78d20c6a544eef764c6c8eb5249cd"
|
pyproject.toml
CHANGED
|
@@ -12,6 +12,8 @@ streamlit = "^1.40.0"
|
|
| 12 |
altair = "^5.4.1"
|
| 13 |
vega-datasets = "^0.9.0"
|
| 14 |
pydeck = "^0.9.1"
|
|
|
|
|
|
|
| 15 |
|
| 16 |
|
| 17 |
[build-system]
|
|
|
|
| 12 |
altair = "^5.4.1"
|
| 13 |
vega-datasets = "^0.9.0"
|
| 14 |
pydeck = "^0.9.1"
|
| 15 |
+
plotly = "^5.24.1"
|
| 16 |
+
streamlit-plotly-events = "^0.0.6"
|
| 17 |
|
| 18 |
|
| 19 |
[build-system]
|