Add circular mean to statistics integration (#98930)
* Add circular mean Add support for circular mean for sensors in units of degrees, e.g. direction data. * Update test_sensor.py * Update sensor.py * Remove whitespace * Revert to degC * Fix: shift atan2 output to positive degrees * Add new dedicated test * Simplify test
This commit is contained in:
parent
3018d4edb9
commit
35be5957c3
2 changed files with 57 additions and 0 deletions
|
@ -6,6 +6,7 @@ from collections.abc import Callable
|
|||
import contextlib
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
import math
|
||||
import statistics
|
||||
from typing import Any, cast
|
||||
|
||||
|
@ -82,6 +83,7 @@ STAT_DISTANCE_95P = "distance_95_percent_of_values"
|
|||
STAT_DISTANCE_99P = "distance_99_percent_of_values"
|
||||
STAT_DISTANCE_ABSOLUTE = "distance_absolute"
|
||||
STAT_MEAN = "mean"
|
||||
STAT_MEAN_CIRCULAR = "mean_circular"
|
||||
STAT_MEDIAN = "median"
|
||||
STAT_NOISINESS = "noisiness"
|
||||
STAT_PERCENTILE = "percentile"
|
||||
|
@ -111,6 +113,7 @@ STATS_NUMERIC_SUPPORT = {
|
|||
STAT_DISTANCE_99P,
|
||||
STAT_DISTANCE_ABSOLUTE,
|
||||
STAT_MEAN,
|
||||
STAT_MEAN_CIRCULAR,
|
||||
STAT_MEDIAN,
|
||||
STAT_NOISINESS,
|
||||
STAT_PERCENTILE,
|
||||
|
@ -160,6 +163,7 @@ STATS_NUMERIC_RETAIN_UNIT = {
|
|||
STAT_DISTANCE_99P,
|
||||
STAT_DISTANCE_ABSOLUTE,
|
||||
STAT_MEAN,
|
||||
STAT_MEAN_CIRCULAR,
|
||||
STAT_MEDIAN,
|
||||
STAT_NOISINESS,
|
||||
STAT_PERCENTILE,
|
||||
|
@ -681,6 +685,13 @@ class StatisticsSensor(SensorEntity):
|
|||
return statistics.mean(self.states)
|
||||
return None
|
||||
|
||||
def _stat_mean_circular(self) -> StateType:
|
||||
if len(self.states) > 0:
|
||||
sin_sum = sum(math.sin(math.radians(x)) for x in self.states)
|
||||
cos_sum = sum(math.cos(math.radians(x)) for x in self.states)
|
||||
return (math.degrees(math.atan2(sin_sum, cos_sum)) + 360) % 360
|
||||
return None
|
||||
|
||||
def _stat_median(self) -> StateType:
|
||||
if len(self.states) > 0:
|
||||
return statistics.median(self.states)
|
||||
|
|
|
@ -22,6 +22,7 @@ from homeassistant.components.statistics.sensor import StatisticsSensor
|
|||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
DEGREE,
|
||||
SERVICE_RELOAD,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
|
@ -920,6 +921,14 @@ async def test_state_characteristics(hass: HomeAssistant) -> None:
|
|||
"value_9": float(round(sum(VALUES_NUMERIC) / len(VALUES_NUMERIC), 2)),
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "mean_circular",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": float(VALUES_NUMERIC[-1]),
|
||||
"value_9": 10.76,
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "median",
|
||||
|
@ -1207,6 +1216,43 @@ async def test_state_characteristics(hass: HomeAssistant) -> None:
|
|||
)
|
||||
|
||||
|
||||
async def test_state_characteristic_mean_circular(hass: HomeAssistant) -> None:
|
||||
"""Test the mean_circular state characteristic using angle data."""
|
||||
values_angular = [0, 10, 90.5, 180, 269.5, 350]
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"sensor",
|
||||
{
|
||||
"sensor": [
|
||||
{
|
||||
"platform": "statistics",
|
||||
"name": "test_sensor_mean_circular",
|
||||
"entity_id": "sensor.test_monitored",
|
||||
"state_characteristic": "mean_circular",
|
||||
"sampling_size": 6,
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for angle in values_angular:
|
||||
hass.states.async_set(
|
||||
"sensor.test_monitored",
|
||||
str(angle),
|
||||
{ATTR_UNIT_OF_MEASUREMENT: DEGREE},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.test_sensor_mean_circular")
|
||||
assert state is not None
|
||||
assert state.state == "0.0", (
|
||||
"value mismatch for characteristic 'sensor/mean_circular' - "
|
||||
f"assert {state.state} == 0.0"
|
||||
)
|
||||
|
||||
|
||||
async def test_invalid_state_characteristic(hass: HomeAssistant) -> None:
|
||||
"""Test the detection of wrong state_characteristics selected."""
|
||||
assert await async_setup_component(
|
||||
|
|
Loading…
Add table
Reference in a new issue