Commit 246ee412 authored by AlaskaMapScience's avatar AlaskaMapScience

Add rolling averaging option to timeseries report

parent dfab81b9
...@@ -6,6 +6,7 @@ from datetime import datetime ...@@ -6,6 +6,7 @@ from datetime import datetime
import pytz, calendar, time, math import pytz, calendar, time, math
from dateutil import parser from dateutil import parser
import numpy as np import numpy as np
import pandas as pd
from django.conf import settings from django.conf import settings
...@@ -94,7 +95,7 @@ def histogram_from_series(pandas_series): ...@@ -94,7 +95,7 @@ def histogram_from_series(pandas_series):
# to 4 significant figures # to 4 significant figures
return list(zip(avg_bins, cts)) return list(zip(avg_bins, cts))
def resample_timeseries(pandas_dataframe, averaging_hours, drop_na=True): def resample_timeseries(pandas_dataframe, averaging_hours, use_rolling_averaging=False, drop_na=True):
''' '''
Returns a new pandas dataframe that is resampled at the specified "averaging_hours" Returns a new pandas dataframe that is resampled at the specified "averaging_hours"
interval. If the 'averaging_hours' parameter is fractional, the averaging time interval. If the 'averaging_hours' parameter is fractional, the averaging time
...@@ -117,7 +118,20 @@ def resample_timeseries(pandas_dataframe, averaging_hours, drop_na=True): ...@@ -117,7 +118,20 @@ def resample_timeseries(pandas_dataframe, averaging_hours, drop_na=True):
} }
params = interval_lookup.get(averaging_hours, {'rule':str(int(averaging_hours * 60)) + 'min', 'loffset':str(int(averaging_hours * 30)) + 'min'}) params = interval_lookup.get(averaging_hours, {'rule':str(int(averaging_hours * 60)) + 'min', 'loffset':str(int(averaging_hours * 30)) + 'min'})
if not use_rolling_averaging:
new_df = pandas_dataframe.resample(rule=params['rule'], loffset=params['loffset'],label='left').mean() new_df = pandas_dataframe.resample(rule=params['rule'], loffset=params['loffset'],label='left').mean()
else:
# resample to consistent interval
original_interval = pandas_dataframe.index.to_series().diff().quantile(.05)
new_df = pandas_dataframe.resample(rule=original_interval).median()
# apply the rolling averaging
new_df = new_df.rolling(int(pd.Timedelta(hours=averaging_hours) / original_interval),center=True,min_periods=1).mean()
# downsample the result if there are more than 1000 values
if len(new_df) > 1000:
new_df = new_df.resample(rule=(pandas_dataframe.index[-1] - pandas_dataframe.index[0]) / 1000).mean()
if drop_na: if drop_na:
new_df = new_df.dropna() new_df = new_df.dropna()
......
...@@ -10,7 +10,7 @@ class TimeSeries(basechart.BaseChart): ...@@ -10,7 +10,7 @@ class TimeSeries(basechart.BaseChart):
""" """
# see BaseChart for definition of these constants # see BaseChart for definition of these constants
CTRLS = 'refresh, ctrl_sensor, ctrl_avg, ctrl_occupied, time_period_group, get_embed_link' CTRLS = 'refresh, ctrl_sensor, ctrl_avg, ctrl_use_rolling_averaging, ctrl_occupied, time_period_group, get_embed_link'
MULTI_SENSOR = 1 MULTI_SENSOR = 1
def result(self): def result(self):
...@@ -28,8 +28,15 @@ class TimeSeries(basechart.BaseChart): ...@@ -28,8 +28,15 @@ class TimeSeries(basechart.BaseChart):
# get the requested averaging interval in hours # get the requested averaging interval in hours
if 'averaging_time' in self.request_params: if 'averaging_time' in self.request_params:
averaging_hours = float(self.request_params['averaging_time']) averaging_hours = float(self.request_params['averaging_time'])
if 'use_rolling_averaging' in self.request_params:
use_rolling_averaging = True
else:
use_rolling_averaging = False
else: else:
averaging_hours = 0 averaging_hours = 0
use_rolling_averaging = False
# determine the start time for selecting records and loop through the selected # determine the start time for selecting records and loop through the selected
# records to get the needed dataset # records to get the needed dataset
...@@ -51,7 +58,7 @@ class TimeSeries(basechart.BaseChart): ...@@ -51,7 +58,7 @@ class TimeSeries(basechart.BaseChart):
if not df.empty: if not df.empty:
# perform average (if requested) # perform average (if requested)
if averaging_hours: if averaging_hours:
df = bmsapp.data_util.resample_timeseries(df,averaging_hours) df = bmsapp.data_util.resample_timeseries(df,averaging_hours,use_rolling_averaging)
# create lists for plotly # create lists for plotly
if np.absolute(df.val.values).max() < 10000: if np.absolute(df.val.values).max() < 10000:
......
...@@ -108,7 +108,7 @@ process_chart_change = -> ...@@ -108,7 +108,7 @@ process_chart_change = ->
# start by hiding all input controls # start by hiding all input controls
set_visibility(['refresh', 'ctrl_sensor', 'ctrl_avg', 'ctrl_avg_export', set_visibility(['refresh', 'ctrl_sensor', 'ctrl_avg', 'ctrl_avg_export',
'ctrl_normalize', 'ctrl_occupied', 'xy_controls', 'time_period_group', 'ctrl_normalize', 'ctrl_occupied', 'xy_controls', 'time_period_group',
'download_many', 'get_embed_link'], false) 'download_many', 'get_embed_link','ctrl_use_rolling_averaging'], false)
# get the chart option control that is selected. Then use the data # get the chart option control that is selected. Then use the data
# attributes of that option element to configure the user interface. # attributes of that option element to configure the user interface.
...@@ -320,7 +320,7 @@ $ -> ...@@ -320,7 +320,7 @@ $ ->
$("#select_chart").change process_chart_change $("#select_chart").change process_chart_change
# Set up change handlers for inputs. # Set up change handlers for inputs.
ctrls = ['averaging_time', 'averaging_time_export', 'normalize', 'show_occupied', ctrls = ['averaging_time', 'averaging_time_export', 'normalize', 'use_rolling_averaging', 'show_occupied',
'select_sensor', 'select_sensor_x', 'select_sensor_y', 'averaging_time_xy', 'div_date', 'select_sensor', 'select_sensor_x', 'select_sensor_y', 'averaging_time_xy', 'div_date',
'start_date', 'end_date'] 'start_date', 'end_date']
$("##{ctrl}").change inputs_changed for ctrl in ctrls $("##{ctrl}").change inputs_changed for ctrl in ctrls
......
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
process_chart_change = function () { process_chart_change = function () {
var multi, selected_chart_option, sensor_val, single, vis_ctrls; var multi, selected_chart_option, sensor_val, single, vis_ctrls;
set_visibility(['refresh', 'ctrl_sensor', 'ctrl_avg', 'ctrl_avg_export', 'ctrl_normalize', 'ctrl_occupied', 'xy_controls', 'time_period_group', 'download_many', 'get_embed_link'], false); set_visibility(['refresh', 'ctrl_sensor', 'ctrl_avg', 'ctrl_avg_export', 'ctrl_normalize', 'ctrl_use_rolling_averaging', 'ctrl_occupied', 'xy_controls', 'time_period_group', 'download_many', 'get_embed_link'], false);
selected_chart_option = $("#select_chart").find("option:selected"); selected_chart_option = $("#select_chart").find("option:selected");
vis_ctrls = selected_chart_option.data("ctrls").split(","); vis_ctrls = selected_chart_option.data("ctrls").split(",");
set_visibility(vis_ctrls, true); set_visibility(vis_ctrls, true);
...@@ -299,7 +299,7 @@ ...@@ -299,7 +299,7 @@
$("#select_group").change(update_bldg_list); $("#select_group").change(update_bldg_list);
$("#select_bldg").change(update_chart_sensor_lists); $("#select_bldg").change(update_chart_sensor_lists);
$("#select_chart").change(process_chart_change); $("#select_chart").change(process_chart_change);
ctrls = ['averaging_time', 'averaging_time_export', 'normalize', 'show_occupied', 'select_sensor', 'select_sensor_x', 'select_sensor_y', 'averaging_time_xy', 'div_date', 'start_date', 'end_date']; ctrls = ['averaging_time', 'averaging_time_export', 'normalize', 'use_rolling_averaging', 'show_occupied', 'select_sensor', 'select_sensor_x', 'select_sensor_y', 'averaging_time_xy', 'div_date', 'start_date', 'end_date'];
for (i = 0, len = ctrls.length; i < len; i++) { for (i = 0, len = ctrls.length; i < len; i++) {
ctrl = ctrls[i]; ctrl = ctrls[i];
$("#" + ctrl).change(inputs_changed); $("#" + ctrl).change(inputs_changed);
......
...@@ -91,6 +91,13 @@ ...@@ -91,6 +91,13 @@
Shade Occupied Periods Shade Occupied Periods
</label> </label>
</div> </div>
<div id="ctrl_use_rolling_averaging" class="form-check mb-4 ml-3">
<input class="form-check-input" type="checkbox" value="" id="use_rolling_averaging"
name="use_rolling_averaging">
<label class="form-check-label" for="use_rolling_averaging">
Use Rolling Averaging
</label>
</div>
<div id="ctrl_normalize" class="form-check mb-4 ml-3"> <div id="ctrl_normalize" class="form-check mb-4 ml-3">
<input class="form-check-input" type="checkbox" value="" id="normalize" <input class="form-check-input" type="checkbox" value="" id="normalize"
name="normalize"> name="normalize">
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment