ChristophS
commited on
Commit
•
d10416d
1
Parent(s):
470e155
filter modification
Browse files- ErrorHandler.py +23 -0
- Visualization.py +67 -23
- app.py +9 -10
- requirements.txt +1 -1
ErrorHandler.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
import datetime
|
4 |
+
|
5 |
+
class ErrorHandler:
|
6 |
+
def __init__(self,):
|
7 |
+
pass
|
8 |
+
|
9 |
+
def time_input_validation(self, from_date:datetime, to_date:datetime, agg_level):
|
10 |
+
"""
|
11 |
+
Method for validation of user input for filtering the time data of the dashboard
|
12 |
+
:param from_date: date of starting the time range aggregation
|
13 |
+
:param to_date: last date of selected time range
|
14 |
+
:param agg_level: time aggregation level
|
15 |
+
"""
|
16 |
+
# check date logic
|
17 |
+
if to_date < from_date:
|
18 |
+
st.error("Start-Datum muss vor dem End-datum sein.")
|
19 |
+
st.stop()
|
20 |
+
# check size of time range
|
21 |
+
if (to_date-from_date).days < agg_level:
|
22 |
+
st.error("Zwischen dem Start- und End-Datum sollte mindestens ein Zeitinterval liegen.")
|
23 |
+
st.stop()
|
Visualization.py
CHANGED
@@ -1,23 +1,48 @@
|
|
1 |
import streamlit as st
|
2 |
import pandas as pd
|
|
|
3 |
import folium
|
4 |
import plotly.graph_objects as go
|
|
|
5 |
|
6 |
class Visualization:
|
7 |
def __init__(self, df):
|
8 |
self.weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag']
|
9 |
# time series metrics
|
10 |
-
self.ts_metrics = {'
|
11 |
-
|
12 |
-
|
13 |
# metrics for hour and weekday plot
|
14 |
-
self.hw_metrics = {'Anzahl
|
15 |
-
'
|
16 |
#
|
17 |
self.df:pd.DataFrame = df
|
18 |
-
self.min_date = df['date'].min()
|
19 |
-
self.max_date = df['date'].max()
|
20 |
self.min_rides = 10
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
|
22 |
def filter_component(self, st_obj=st):
|
23 |
"""
|
@@ -26,21 +51,37 @@ class Visualization:
|
|
26 |
"""
|
27 |
# buold filter container
|
28 |
container = st_obj.container(border=True)
|
29 |
-
cols = container.columns([5,
|
30 |
# interval filter
|
31 |
-
self.selected_time = cols[0].radio('
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
# time range filter
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
|
|
|
|
|
|
|
|
40 |
# metric filter
|
|
|
41 |
if self.selected_time == 'Zeitinterval':
|
42 |
metrics = self.ts_metrics
|
43 |
self.time_column = 'date'
|
|
|
44 |
elif self.selected_time == 'Wochentage':
|
45 |
metrics = self.hw_metrics
|
46 |
self.time_column = 'weekday'
|
@@ -48,19 +89,21 @@ class Visualization:
|
|
48 |
metrics = self.hw_metrics
|
49 |
self.time_column = 'hour'
|
50 |
# metric filter
|
51 |
-
self.selected_metric = container.radio('Metriken:', metrics, horizontal=True)
|
52 |
self.selected_key = metrics[self.selected_metric]
|
|
|
|
|
53 |
|
54 |
def aggregate_data(self):
|
55 |
"""
|
56 |
Method for filtering the data for visualization
|
57 |
"""
|
58 |
# select data in seleced time range
|
59 |
-
data = self.df[self.df['date'].between(
|
60 |
# aggregate data on time level
|
61 |
if self.selected_time == 'Zeitinterval':
|
62 |
data = data.groupby(['direction'])
|
63 |
-
data = data.resample(f'{self.agg_level}d', on='date', origin=
|
64 |
elif self.selected_time == 'Tageszeit':
|
65 |
data = data.groupby(['direction', 'hour'])
|
66 |
else:
|
@@ -83,7 +126,7 @@ class Visualization:
|
|
83 |
plot = pd.DataFrame(range(24), columns=['number'], index=range(24))
|
84 |
df = df.merge(plot, left_on='hour', right_on='number', how='outer')
|
85 |
else:
|
86 |
-
date_series = pd.date_range(
|
87 |
plot = pd.DataFrame(date_series, columns=['number'])
|
88 |
df = df.merge(plot, left_on='date', right_on='number', how='outer')
|
89 |
# fill misssing data and sort values for right visualization
|
@@ -132,8 +175,9 @@ class Visualization:
|
|
132 |
:return m: folium map object
|
133 |
"""
|
134 |
point = gdf.at[0,'geometry'].centroid
|
135 |
-
m = folium.Map(location=[point.y, point.x], zoom_start=16)
|
136 |
folium.Choropleth(gdf.at[0,'geometry'], fill_color='blue', line_color='blue').add_to(m)
|
|
|
137 |
return m
|
138 |
|
139 |
def timeline_plot_plotly(self):
|
@@ -172,7 +216,6 @@ class Visualization:
|
|
172 |
min_value = min(value_bounds[0])
|
173 |
max_value = max(value_bounds[1])
|
174 |
for date in set(low_rides_dates):
|
175 |
-
|
176 |
fig.add_shape(dict(type="rect",
|
177 |
x0=date-width, y0=min_value-abs(.25*min_value),
|
178 |
x1=date+width, y1=max_value+abs(.25*max_value),
|
@@ -184,7 +227,8 @@ class Visualization:
|
|
184 |
xaxis_title=self.selected_time,
|
185 |
yaxis_title=self.selected_metric,
|
186 |
legend=dict( y=1.02, xanchor="right", x=1, traceorder="normal", orientation="h"),
|
187 |
-
showlegend=True
|
|
|
188 |
)
|
189 |
|
190 |
return fig
|
|
|
1 |
import streamlit as st
|
2 |
import pandas as pd
|
3 |
+
from shapely.geometry import Point
|
4 |
import folium
|
5 |
import plotly.graph_objects as go
|
6 |
+
from ErrorHandler import ErrorHandler
|
7 |
|
8 |
class Visualization:
|
9 |
def __init__(self, df):
|
10 |
self.weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag']
|
11 |
# time series metrics
|
12 |
+
self.ts_metrics = {'Anomaliegrad':'anomaly', 'Anzahl Durchfahrten':'ride_id', 'Bremsungen':'breaks',
|
13 |
+
'Erschütterungsgrad':'logVScore', 'Geschwindigkeit':'speed', 'Normalisierte Geschwindigkeit':'norm_speed',
|
14 |
+
'Wartezeit':'waiting', 'Zeitverlust':'time_loss', 'kumulierte Durchfahrten':'n_rides'}
|
15 |
# metrics for hour and weekday plot
|
16 |
+
self.hw_metrics = {'Anzahl Durchfahrten':'ride_id', 'Bremsungen':'breaks', 'Geschwindigkeit':'speed',
|
17 |
+
'Normalisierte Geschwindigkeit':'norm_speed', 'Wartezeit':'waiting', 'Zeitverlust':'time_loss'}
|
18 |
#
|
19 |
self.df:pd.DataFrame = df
|
|
|
|
|
20 |
self.min_rides = 10
|
21 |
+
if 'from_date' not in st.session_state:
|
22 |
+
self.init_session_state(df)
|
23 |
+
|
24 |
+
self.errorhandler = ErrorHandler()
|
25 |
+
|
26 |
+
def init_session_state(self, df):
|
27 |
+
"""
|
28 |
+
Method for initialisation of the session state for the dashboard
|
29 |
+
"""
|
30 |
+
st.session_state['min_date'] = pd.to_datetime(df['date'].min())
|
31 |
+
st.session_state['from_date'] = pd.to_datetime(df['date'].min())
|
32 |
+
st.session_state['max_date'] = pd.to_datetime(df['date'].max())
|
33 |
+
st.session_state['to_date'] = pd.to_datetime(df['date'].max())
|
34 |
+
st.session_state['agg_level'] = 7 #days
|
35 |
+
st.session_state['max_level'] = (st.session_state['to_date']-st.session_state['from_date']).days
|
36 |
+
|
37 |
+
def set_date_input_clb(self,):
|
38 |
+
st.session_state['from_date'] = max(st.session_state['min_date'], pd.to_datetime(st.session_state['from_date_input']))
|
39 |
+
st.session_state['to_date'] = min(st.session_state['max_date'], pd.to_datetime(st.session_state['to_date_input']))
|
40 |
+
|
41 |
+
st.session_state['max_level'] = (st.session_state['to_date']-st.session_state['from_date']).days
|
42 |
+
if st.session_state['max_level'] > st.session_state['input_agg_level']:
|
43 |
+
st.session_state['agg_level'] = st.session_state['input_agg_level']
|
44 |
+
else:
|
45 |
+
st.session_state['agg_level'] = st.session_state['max_level']
|
46 |
|
47 |
def filter_component(self, st_obj=st):
|
48 |
"""
|
|
|
51 |
"""
|
52 |
# buold filter container
|
53 |
container = st_obj.container(border=True)
|
54 |
+
cols = container.columns([5,2,1,1])
|
55 |
# interval filter
|
56 |
+
self.selected_time = cols[0].radio('Zeitbereich:', ['Zeitinterval', 'Tageszeit', 'Wochentage'], horizontal=True)
|
57 |
+
self.agg_level = 0
|
58 |
+
if self.selected_time == 'Zeitinterval':
|
59 |
+
# date aggrgation level
|
60 |
+
cols[1].number_input('Zeitinterval in Tagen',
|
61 |
+
value=st.session_state['agg_level'], step=1,
|
62 |
+
min_value=1, max_value=st.session_state['max_level'],
|
63 |
+
key='input_agg_level', on_change=self.set_date_input_clb,
|
64 |
+
help=self.helper_aggregation_level())
|
65 |
+
self.agg_level = st.session_state['agg_level']
|
66 |
# time range filter
|
67 |
+
cols[2].date_input('Von', value = st.session_state['from_date'],
|
68 |
+
min_value=st.session_state['min_date'],
|
69 |
+
max_value=st.session_state['to_date']-pd.Timedelta(f"{self.agg_level}d"),
|
70 |
+
format="DD.MM.YYYY", key='from_date_input', on_change=self.set_date_input_clb,
|
71 |
+
help=self.helper_date_selection('Start'))
|
72 |
+
|
73 |
+
cols[3].date_input('Bis', value = st.session_state['to_date'],
|
74 |
+
min_value=st.session_state['from_date']+pd.Timedelta(f"{self.agg_level}d") ,
|
75 |
+
max_value=st.session_state['max_date'],
|
76 |
+
format="DD.MM.YYYY", key='to_date_input', on_change=self.set_date_input_clb,
|
77 |
+
help=self.helper_date_selection('End'))
|
78 |
+
|
79 |
# metric filter
|
80 |
+
metric_index = 0
|
81 |
if self.selected_time == 'Zeitinterval':
|
82 |
metrics = self.ts_metrics
|
83 |
self.time_column = 'date'
|
84 |
+
metric_index = 3
|
85 |
elif self.selected_time == 'Wochentage':
|
86 |
metrics = self.hw_metrics
|
87 |
self.time_column = 'weekday'
|
|
|
89 |
metrics = self.hw_metrics
|
90 |
self.time_column = 'hour'
|
91 |
# metric filter
|
92 |
+
self.selected_metric = container.radio('Metriken:', metrics, horizontal=True, index=metric_index)
|
93 |
self.selected_key = metrics[self.selected_metric]
|
94 |
+
# controll user input
|
95 |
+
self.errorhandler.time_input_validation(st.session_state['from_date'] , st.session_state['to_date'], self.agg_level)
|
96 |
|
97 |
def aggregate_data(self):
|
98 |
"""
|
99 |
Method for filtering the data for visualization
|
100 |
"""
|
101 |
# select data in seleced time range
|
102 |
+
data = self.df[self.df['date'].between(st.session_state['from_date'] , st.session_state['to_date'])].reset_index(drop=True)
|
103 |
# aggregate data on time level
|
104 |
if self.selected_time == 'Zeitinterval':
|
105 |
data = data.groupby(['direction'])
|
106 |
+
data = data.resample(f'{self.agg_level}d', on='date', origin=st.session_state['min_date'])
|
107 |
elif self.selected_time == 'Tageszeit':
|
108 |
data = data.groupby(['direction', 'hour'])
|
109 |
else:
|
|
|
126 |
plot = pd.DataFrame(range(24), columns=['number'], index=range(24))
|
127 |
df = df.merge(plot, left_on='hour', right_on='number', how='outer')
|
128 |
else:
|
129 |
+
date_series = pd.date_range(st.session_state['min_date'], st.session_state['max_date'], freq=f'{self.agg_level}d')
|
130 |
plot = pd.DataFrame(date_series, columns=['number'])
|
131 |
df = df.merge(plot, left_on='date', right_on='number', how='outer')
|
132 |
# fill misssing data and sort values for right visualization
|
|
|
175 |
:return m: folium map object
|
176 |
"""
|
177 |
point = gdf.at[0,'geometry'].centroid
|
178 |
+
m = folium.Map(location=[point.y, point.x], zoom_start=16, tiles='CartoDB Positron')
|
179 |
folium.Choropleth(gdf.at[0,'geometry'], fill_color='blue', line_color='blue').add_to(m)
|
180 |
+
folium.FitOverlays().add_to(m)
|
181 |
return m
|
182 |
|
183 |
def timeline_plot_plotly(self):
|
|
|
216 |
min_value = min(value_bounds[0])
|
217 |
max_value = max(value_bounds[1])
|
218 |
for date in set(low_rides_dates):
|
|
|
219 |
fig.add_shape(dict(type="rect",
|
220 |
x0=date-width, y0=min_value-abs(.25*min_value),
|
221 |
x1=date+width, y1=max_value+abs(.25*max_value),
|
|
|
227 |
xaxis_title=self.selected_time,
|
228 |
yaxis_title=self.selected_metric,
|
229 |
legend=dict( y=1.02, xanchor="right", x=1, traceorder="normal", orientation="h"),
|
230 |
+
showlegend=True,
|
231 |
+
xaxis_range=[st.session_state['from_date'], st.session_state['to_date']]
|
232 |
)
|
233 |
|
234 |
return fig
|
app.py
CHANGED
@@ -16,23 +16,22 @@ def main(edge_id):
|
|
16 |
# head metrics of the shapes
|
17 |
cols = st.columns([4,1])
|
18 |
metric_cols = cols[0].columns(5)
|
19 |
-
metric_cols[0].container(border=True).metric('
|
20 |
metric_cols[0].markdown(' ')
|
21 |
-
metric_cols[1].container(border=True).metric('Geschwindigkeit
|
22 |
-
metric_cols[2].container(border=True).metric('
|
23 |
-
metric_cols[3].container(border=True).metric('Zeitverlust
|
24 |
-
metric_cols[4].container(border=True).metric('Wartezeit
|
25 |
# show shape on map
|
26 |
with cols[1]:
|
27 |
-
st_folium(visu.create_map(geom), returned_objects=[],
|
|
|
28 |
# create user filter and aggregate data based on the filter parameter
|
29 |
visu.filter_component(cols[0])
|
30 |
visu.aggregate_data()
|
31 |
# timeline plot
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
|
37 |
if __name__ == '__main__':
|
38 |
# handle empty query parameter or processing edge/osm id
|
|
|
16 |
# head metrics of the shapes
|
17 |
cols = st.columns([4,1])
|
18 |
metric_cols = cols[0].columns(5)
|
19 |
+
metric_cols[0].container(border=True).metric('Durchfahrten:', visu.int2str(data['ride_id'].sum()))
|
20 |
metric_cols[0].markdown(' ')
|
21 |
+
metric_cols[1].container(border=True).metric('Geschwindigkeit [km/h]', f"{visu.float2str(data['speed'].mean())}")
|
22 |
+
metric_cols[2].container(border=True).metric('Wunschgeschwindigkeit [%]', f"{visu.float2str(100*data['norm_speed'].mean())}")
|
23 |
+
metric_cols[3].container(border=True).metric('Zeitverlust [Sekunden]', f"{visu.float2str(data['time_loss'].mean())}")
|
24 |
+
metric_cols[4].container(border=True).metric('Wartezeit [Sekunden]', f"{visu.float2str(100*data['waiting'].mean())}")
|
25 |
# show shape on map
|
26 |
with cols[1]:
|
27 |
+
st_folium(visu.create_map(geom), returned_objects=[],
|
28 |
+
key=f'Map', use_container_width=True)
|
29 |
# create user filter and aggregate data based on the filter parameter
|
30 |
visu.filter_component(cols[0])
|
31 |
visu.aggregate_data()
|
32 |
# timeline plot
|
33 |
+
cols[0].markdown(f"{visu.selected_metric} pro {visu.selected_time}", help=visu.helper_plot_regions())
|
34 |
+
cols[0].plotly_chart(visu.timeline_plot_plotly(), use_container_width=True)
|
|
|
|
|
35 |
|
36 |
if __name__ == '__main__':
|
37 |
# handle empty query parameter or processing edge/osm id
|
requirements.txt
CHANGED
@@ -14,7 +14,7 @@ colorlog==6.7.0
|
|
14 |
contourpy==1.1.1
|
15 |
cycler==0.12.1
|
16 |
fiona==1.9.5
|
17 |
-
folium==0.
|
18 |
fonttools==4.43.1
|
19 |
geopandas==0.14.0
|
20 |
gitdb==4.0.11
|
|
|
14 |
contourpy==1.1.1
|
15 |
cycler==0.12.1
|
16 |
fiona==1.9.5
|
17 |
+
folium==0.16.0
|
18 |
fonttools==4.43.1
|
19 |
geopandas==0.14.0
|
20 |
gitdb==4.0.11
|