From ef22a6e18dc4dc9f8220748dc220af80dc4131ad Mon Sep 17 00:00:00 2001 From: markferry Date: Mon, 18 Dec 2017 20:21:27 +0000 Subject: [PATCH] Fix statistics sensor mean and median when only one sample is available. (#11180) * Fix statistics sensor mean and median when only one sample is available. With only one data point stddev and variance throw an exception. This would clear the (valid) mean and median calculations. Separate the try..catch blocks for one-or-more and two-or-more stats so that this doesn't happen. Test this with a new sampling_size_1 test. * test_statistics trivial whitespace fix --- homeassistant/components/sensor/statistics.py | 9 +++-- tests/components/sensor/test_statistics.py | 35 ++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/statistics.py b/homeassistant/components/sensor/statistics.py index a6932e2aebb..19281d36d88 100644 --- a/homeassistant/components/sensor/statistics.py +++ b/homeassistant/components/sensor/statistics.py @@ -175,15 +175,20 @@ class StatisticsSensor(Entity): self._purge_old() if not self.is_binary: - try: + try: # require only one data point self.mean = round(statistics.mean(self.states), 2) self.median = round(statistics.median(self.states), 2) + except statistics.StatisticsError as err: + _LOGGER.error(err) + self.mean = self.median = STATE_UNKNOWN + + try: # require at least two data points self.stdev = round(statistics.stdev(self.states), 2) self.variance = round(statistics.variance(self.states), 2) except statistics.StatisticsError as err: _LOGGER.error(err) - self.mean = self.median = STATE_UNKNOWN self.stdev = self.variance = STATE_UNKNOWN + if self.states: self.total = round(sum(self.states), 2) self.min = min(self.states) diff --git a/tests/components/sensor/test_statistics.py b/tests/components/sensor/test_statistics.py index bfb8fb61f9b..48ebf720633 100644 --- a/tests/components/sensor/test_statistics.py +++ b/tests/components/sensor/test_statistics.py @@ -3,7 +3,8 @@ import unittest import statistics from homeassistant.setup import setup_component -from homeassistant.const import (ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS) +from homeassistant.const import ( + ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, STATE_UNKNOWN) from homeassistant.util import dt as dt_util from tests.common import get_test_home_assistant from unittest.mock import patch @@ -106,6 +107,38 @@ class TestStatisticsSensor(unittest.TestCase): self.assertEqual(3.8, state.attributes.get('min_value')) self.assertEqual(14, state.attributes.get('max_value')) + def test_sampling_size_1(self): + """Test validity of stats requiring only one sample.""" + assert setup_component(self.hass, 'sensor', { + 'sensor': { + 'platform': 'statistics', + 'name': 'test', + 'entity_id': 'sensor.test_monitored', + 'sampling_size': 1, + } + }) + + for value in self.values[-3:]: # just the last 3 will do + self.hass.states.set('sensor.test_monitored', value, + {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) + self.hass.block_till_done() + + state = self.hass.states.get('sensor.test_mean') + + # require only one data point + self.assertEqual(self.values[-1], state.attributes.get('min_value')) + self.assertEqual(self.values[-1], state.attributes.get('max_value')) + self.assertEqual(self.values[-1], state.attributes.get('mean')) + self.assertEqual(self.values[-1], state.attributes.get('median')) + self.assertEqual(self.values[-1], state.attributes.get('total')) + self.assertEqual(0, state.attributes.get('change')) + self.assertEqual(0, state.attributes.get('average_change')) + + # require at least two data points + self.assertEqual(STATE_UNKNOWN, state.attributes.get('variance')) + self.assertEqual(STATE_UNKNOWN, + state.attributes.get('standard_deviation')) + def test_max_age(self): """Test value deprecation.""" mock_data = {