FantasticTony commited on
Commit
4830705
·
1 Parent(s): 290ce05

:sparkles: Add Plotly and interactivity enhancements to dashboard

Browse files

Integrated 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.

Files changed (3) hide show
  1. app.py +98 -69
  2. poetry.lock +31 -1
  3. 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 sidebar options to filter by county and make. The filtered dataset is displayed in the
27
- table, and corresponding charts will help you explore the distribution of electric vehicle types, their electric ranges,
28
- and model years. The map also provides a visualization of the locations of the vehicles in the selected county.
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
- st.subheader('Distribution of Electric Vehicle Types')
46
- ev_type_count = filtered_data['Electric Vehicle Type'].value_counts().reset_index()
47
- ev_type_count.columns = ['Electric Vehicle Type', 'Count']
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 = "bd2d59f3fedd9cab57202c080d3ce049a5ad2a45273c567e6aad87cd58d1ebca"
 
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]