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
import pytz, calendar, time, math
from dateutil import parser
import numpy as np
import pandas as pd
from django.conf import settings
......@@ -94,7 +95,7 @@ def histogram_from_series(pandas_series):
# to 4 significant figures
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"
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):
}
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()
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:
new_df = new_df.dropna()
......
......@@ -10,7 +10,7 @@ class TimeSeries(basechart.BaseChart):
"""
# 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
def result(self):
......@@ -28,8 +28,15 @@ class TimeSeries(basechart.BaseChart):
# get the requested averaging interval in hours
if 'averaging_time' in self.request_params:
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:
averaging_hours = 0
use_rolling_averaging = False
# determine the start time for selecting records and loop through the selected
# records to get the needed dataset
......@@ -51,7 +58,7 @@ class TimeSeries(basechart.BaseChart):
if not df.empty:
# perform average (if requested)
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
if np.absolute(df.val.values).max() < 10000:
......
......@@ -108,7 +108,7 @@ process_chart_change = ->
# start by hiding all input controls
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)
'download_many', 'get_embed_link','ctrl_use_rolling_averaging'], false)
# get the chart option control that is selected. Then use the data
# attributes of that option element to configure the user interface.
......@@ -320,7 +320,7 @@ $ ->
$("#select_chart").change process_chart_change
# 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',
'start_date', 'end_date']
$("##{ctrl}").change inputs_changed for ctrl in ctrls
......
......@@ -101,7 +101,7 @@
process_chart_change = function () {
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");
vis_ctrls = selected_chart_option.data("ctrls").split(",");
set_visibility(vis_ctrls, true);
......@@ -299,7 +299,7 @@
$("#select_group").change(update_bldg_list);
$("#select_bldg").change(update_chart_sensor_lists);
$("#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++) {
ctrl = ctrls[i];
$("#" + ctrl).change(inputs_changed);
......
......@@ -91,6 +91,13 @@
Shade Occupied Periods
</label>
</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">
<input class="form-check-input" type="checkbox" value="" id="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