Have statistics functions return a meaningful, non-none result even if only one value is available (#127305)

* have statistics functions return a meaningful, non-none result even if only one value is available

* improved code coverage
This commit is contained in:
unfug-at-github 2024-10-23 16:02:46 +02:00 committed by GitHub
parent 487593af38
commit 9ec4881d8d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 31 additions and 13 deletions

View File

@ -735,6 +735,8 @@ class StatisticsSensor(SensorEntity):
# Statistics for numeric sensor
def _stat_average_linear(self) -> StateType:
if len(self.states) == 1:
return self.states[0]
if len(self.states) >= 2:
area: float = 0
for i in range(1, len(self.states)):
@ -748,6 +750,8 @@ class StatisticsSensor(SensorEntity):
return None
def _stat_average_step(self) -> StateType:
if len(self.states) == 1:
return self.states[0]
if len(self.states) >= 2:
area: float = 0
for i in range(1, len(self.states)):
@ -803,12 +807,12 @@ class StatisticsSensor(SensorEntity):
return None
def _stat_distance_95_percent_of_values(self) -> StateType:
if len(self.states) >= 2:
if len(self.states) >= 1:
return 2 * 1.96 * cast(float, self._stat_standard_deviation())
return None
def _stat_distance_99_percent_of_values(self) -> StateType:
if len(self.states) >= 2:
if len(self.states) >= 1:
return 2 * 2.58 * cast(float, self._stat_standard_deviation())
return None
@ -835,17 +839,23 @@ class StatisticsSensor(SensorEntity):
return None
def _stat_noisiness(self) -> StateType:
if len(self.states) == 1:
return 0.0
if len(self.states) >= 2:
return cast(float, self._stat_sum_differences()) / (len(self.states) - 1)
return None
def _stat_percentile(self) -> StateType:
if len(self.states) == 1:
return self.states[0]
if len(self.states) >= 2:
percentiles = statistics.quantiles(self.states, n=100, method="exclusive")
return percentiles[self._percentile - 1]
return None
def _stat_standard_deviation(self) -> StateType:
if len(self.states) == 1:
return 0.0
if len(self.states) >= 2:
return statistics.stdev(self.states)
return None
@ -856,6 +866,8 @@ class StatisticsSensor(SensorEntity):
return None
def _stat_sum_differences(self) -> StateType:
if len(self.states) == 1:
return 0.0
if len(self.states) >= 2:
return sum(
abs(j - i)
@ -864,6 +876,8 @@ class StatisticsSensor(SensorEntity):
return None
def _stat_sum_differences_nonnegative(self) -> StateType:
if len(self.states) == 1:
return 0.0
if len(self.states) >= 2:
return sum(
(j - i if j >= i else j - 0)
@ -885,6 +899,8 @@ class StatisticsSensor(SensorEntity):
return None
def _stat_variance(self) -> StateType:
if len(self.states) == 1:
return 0.0
if len(self.states) >= 2:
return statistics.variance(self.states)
return None
@ -892,6 +908,8 @@ class StatisticsSensor(SensorEntity):
# Statistics for binary sensor
def _stat_binary_average_step(self) -> StateType:
if len(self.states) == 1:
return 100.0 * int(self.states[0] is True)
if len(self.states) >= 2:
on_seconds: float = 0
for i in range(1, len(self.states)):

View File

@ -1013,7 +1013,7 @@ async def test_state_characteristics(hass: HomeAssistant) -> None:
"source_sensor_domain": "sensor",
"name": "average_linear",
"value_0": STATE_UNKNOWN,
"value_1": STATE_UNKNOWN,
"value_1": 6.0,
"value_9": 10.68,
"unit": "°C",
},
@ -1021,7 +1021,7 @@ async def test_state_characteristics(hass: HomeAssistant) -> None:
"source_sensor_domain": "sensor",
"name": "average_step",
"value_0": STATE_UNKNOWN,
"value_1": STATE_UNKNOWN,
"value_1": 6.0,
"value_9": 11.36,
"unit": "°C",
},
@ -1113,7 +1113,7 @@ async def test_state_characteristics(hass: HomeAssistant) -> None:
"source_sensor_domain": "sensor",
"name": "distance_95_percent_of_values",
"value_0": STATE_UNKNOWN,
"value_1": STATE_UNKNOWN,
"value_1": 0.0,
"value_9": float(round(2 * 1.96 * statistics.stdev(VALUES_NUMERIC), 2)),
"unit": "°C",
},
@ -1121,7 +1121,7 @@ async def test_state_characteristics(hass: HomeAssistant) -> None:
"source_sensor_domain": "sensor",
"name": "distance_99_percent_of_values",
"value_0": STATE_UNKNOWN,
"value_1": STATE_UNKNOWN,
"value_1": 0.0,
"value_9": float(round(2 * 2.58 * statistics.stdev(VALUES_NUMERIC), 2)),
"unit": "°C",
},
@ -1161,7 +1161,7 @@ async def test_state_characteristics(hass: HomeAssistant) -> None:
"source_sensor_domain": "sensor",
"name": "noisiness",
"value_0": STATE_UNKNOWN,
"value_1": STATE_UNKNOWN,
"value_1": 0.0,
"value_9": float(round(sum([3, 4.8, 10.2, 1.2, 5.4, 2.5, 7.3, 8]) / 8, 2)),
"unit": "°C",
},
@ -1169,7 +1169,7 @@ async def test_state_characteristics(hass: HomeAssistant) -> None:
"source_sensor_domain": "sensor",
"name": "percentile",
"value_0": STATE_UNKNOWN,
"value_1": STATE_UNKNOWN,
"value_1": 6.0,
"value_9": 9.2,
"unit": "°C",
},
@ -1177,7 +1177,7 @@ async def test_state_characteristics(hass: HomeAssistant) -> None:
"source_sensor_domain": "sensor",
"name": "standard_deviation",
"value_0": STATE_UNKNOWN,
"value_1": STATE_UNKNOWN,
"value_1": 0.0,
"value_9": float(round(statistics.stdev(VALUES_NUMERIC), 2)),
"unit": "°C",
},
@ -1193,7 +1193,7 @@ async def test_state_characteristics(hass: HomeAssistant) -> None:
"source_sensor_domain": "sensor",
"name": "sum_differences",
"value_0": STATE_UNKNOWN,
"value_1": STATE_UNKNOWN,
"value_1": 0.0,
"value_9": float(
sum(
[
@ -1214,7 +1214,7 @@ async def test_state_characteristics(hass: HomeAssistant) -> None:
"source_sensor_domain": "sensor",
"name": "sum_differences_nonnegative",
"value_0": STATE_UNKNOWN,
"value_1": STATE_UNKNOWN,
"value_1": 0.0,
"value_9": float(
sum(
[
@ -1259,7 +1259,7 @@ async def test_state_characteristics(hass: HomeAssistant) -> None:
"source_sensor_domain": "sensor",
"name": "variance",
"value_0": STATE_UNKNOWN,
"value_1": STATE_UNKNOWN,
"value_1": 0.0,
"value_9": float(round(statistics.variance(VALUES_NUMERIC), 2)),
"unit": "°C²",
},
@ -1267,7 +1267,7 @@ async def test_state_characteristics(hass: HomeAssistant) -> None:
"source_sensor_domain": "binary_sensor",
"name": "average_step",
"value_0": STATE_UNKNOWN,
"value_1": STATE_UNKNOWN,
"value_1": 100.0,
"value_9": 50.0,
"unit": "%",
},