ChristophS commited on
Commit
d10416d
1 Parent(s): 470e155

filter modification

Browse files
Files changed (4) hide show
  1. ErrorHandler.py +23 -0
  2. Visualization.py +67 -23
  3. app.py +9 -10
  4. 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 = {'Erschütterungsgrad':'logVScore', 'Anzahl Fahrten':'ride_id', 'kumulierte Fahrten':'n_rides',
11
- 'Geschwindigkeit':'speed', 'Normalisierte Geschwindigkeit':'norm_speed', 'Bremsungen':'breaks',
12
- 'Anomalien':'anomaly', 'Zeitverlust':'time_loss', 'Wartezeit':'waiting', }
13
  # metrics for hour and weekday plot
14
- self.hw_metrics = {'Anzahl Fahrten':'ride_id', 'Geschwindigkeit':'speed', 'Normalisierte Geschwindigkeit':'norm_speed',
15
- 'Bremsungen':'breaks', 'Zeitverlust':'time_loss', 'Wartezeit':'waiting', }
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,1,1,1])
30
  # interval filter
31
- self.selected_time = cols[0].radio('Zeitinterval:', ['Zeitinterval', 'Tageszeit', 'Wochentage'], horizontal=True)
 
 
 
 
 
 
 
 
 
32
  # time range filter
33
- self.from_date = pd.to_datetime(cols[1].date_input('Von', value=self.min_date,
34
- help=self.helper_date_selection('Start')))
35
- self.to_date = pd.to_datetime(cols[2].date_input('Bis', value=self.max_date,
36
- help=self.helper_date_selection('End')))
37
- # date aggrgation level
38
- self.agg_level = cols[3].number_input('Zeitinterval in Tagen', value=7, step=1, min_value=1,
39
- help=self.helper_aggregation_level())
 
 
 
 
 
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(self.from_date, self.to_date)].reset_index(drop=True)
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=self.min_date)
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(self.min_date, self.max_date, freq=f'{self.agg_level}d')
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('Gesamtzahl Fahrten:', visu.int2str(data['ride_id'].sum()))
20
  metric_cols[0].markdown(' ')
21
- metric_cols[1].container(border=True).metric('Geschwindigkeit:', f"{visu.float2str(data['speed'].mean())}km/h")
22
- metric_cols[2].container(border=True).metric('Erreichte Wunshgeschwindigkeit:', f"{visu.float2str(100*data['norm_speed'].mean())}%")
23
- metric_cols[3].container(border=True).metric('Zeitverlust:', f"{visu.float2str(data['time_loss'].mean())}s")
24
- metric_cols[4].container(border=True).metric('Wartezeit:', f"{visu.float2str(100*data['waiting'].mean())}s")
25
  # show shape on map
26
  with cols[1]:
27
- st_folium(visu.create_map(geom), returned_objects=[], key=f'Map', width=500, height=300,)
 
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
- st.markdown(f"{visu.selected_metric} pro {visu.selected_time}", help=visu.helper_plot_regions())
33
- st.plotly_chart(visu.timeline_plot_plotly(), use_container_width=True)
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.14.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