mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Replace quantiles by percentile characteristic for statistics component (#81027)
* Remove quantiles characteristic * Add percentile characteristic
This commit is contained in:
parent
727dcd6df6
commit
a0b0e4088c
@ -78,7 +78,7 @@ STAT_DISTANCE_ABSOLUTE = "distance_absolute"
|
||||
STAT_MEAN = "mean"
|
||||
STAT_MEDIAN = "median"
|
||||
STAT_NOISINESS = "noisiness"
|
||||
STAT_QUANTILES = "quantiles"
|
||||
STAT_PERCENTILE = "percentile"
|
||||
STAT_STANDARD_DEVIATION = "standard_deviation"
|
||||
STAT_SUM = "sum"
|
||||
STAT_SUM_DIFFERENCES = "sum_differences"
|
||||
@ -107,7 +107,7 @@ STATS_NUMERIC_SUPPORT = {
|
||||
STAT_MEAN,
|
||||
STAT_MEDIAN,
|
||||
STAT_NOISINESS,
|
||||
STAT_QUANTILES,
|
||||
STAT_PERCENTILE,
|
||||
STAT_STANDARD_DEVIATION,
|
||||
STAT_SUM,
|
||||
STAT_SUM_DIFFERENCES,
|
||||
@ -135,7 +135,6 @@ STATS_NOT_A_NUMBER = {
|
||||
STAT_DATETIME_OLDEST,
|
||||
STAT_DATETIME_VALUE_MAX,
|
||||
STAT_DATETIME_VALUE_MIN,
|
||||
STAT_QUANTILES,
|
||||
}
|
||||
|
||||
STATS_DATETIME = {
|
||||
@ -157,6 +156,7 @@ STAT_NUMERIC_RETAIN_UNIT = {
|
||||
STAT_MEAN,
|
||||
STAT_MEDIAN,
|
||||
STAT_NOISINESS,
|
||||
STAT_PERCENTILE,
|
||||
STAT_STANDARD_DEVIATION,
|
||||
STAT_SUM,
|
||||
STAT_SUM_DIFFERENCES,
|
||||
@ -177,13 +177,10 @@ CONF_STATE_CHARACTERISTIC = "state_characteristic"
|
||||
CONF_SAMPLES_MAX_BUFFER_SIZE = "sampling_size"
|
||||
CONF_MAX_AGE = "max_age"
|
||||
CONF_PRECISION = "precision"
|
||||
CONF_QUANTILE_INTERVALS = "quantile_intervals"
|
||||
CONF_QUANTILE_METHOD = "quantile_method"
|
||||
CONF_PERCENTILE = "percentile"
|
||||
|
||||
DEFAULT_NAME = "Stats"
|
||||
DEFAULT_PRECISION = 2
|
||||
DEFAULT_QUANTILE_INTERVALS = 4
|
||||
DEFAULT_QUANTILE_METHOD = "exclusive"
|
||||
ICON = "mdi:calculator"
|
||||
|
||||
|
||||
@ -248,11 +245,8 @@ _PLATFORM_SCHEMA_BASE = PLATFORM_SCHEMA.extend(
|
||||
vol.Optional(CONF_SAMPLES_MAX_BUFFER_SIZE): vol.Coerce(int),
|
||||
vol.Optional(CONF_MAX_AGE): cv.time_period,
|
||||
vol.Optional(CONF_PRECISION, default=DEFAULT_PRECISION): vol.Coerce(int),
|
||||
vol.Optional(
|
||||
CONF_QUANTILE_INTERVALS, default=DEFAULT_QUANTILE_INTERVALS
|
||||
): vol.All(vol.Coerce(int), vol.Range(min=2)),
|
||||
vol.Optional(CONF_QUANTILE_METHOD, default=DEFAULT_QUANTILE_METHOD): vol.In(
|
||||
["exclusive", "inclusive"]
|
||||
vol.Optional(CONF_PERCENTILE, default=50): vol.All(
|
||||
vol.Coerce(int), vol.Range(min=1, max=99)
|
||||
),
|
||||
}
|
||||
)
|
||||
@ -283,8 +277,7 @@ async def async_setup_platform(
|
||||
samples_max_buffer_size=config[CONF_SAMPLES_MAX_BUFFER_SIZE],
|
||||
samples_max_age=config.get(CONF_MAX_AGE),
|
||||
precision=config[CONF_PRECISION],
|
||||
quantile_intervals=config[CONF_QUANTILE_INTERVALS],
|
||||
quantile_method=config[CONF_QUANTILE_METHOD],
|
||||
percentile=config[CONF_PERCENTILE],
|
||||
)
|
||||
],
|
||||
update_before_add=True,
|
||||
@ -303,8 +296,7 @@ class StatisticsSensor(SensorEntity):
|
||||
samples_max_buffer_size: int,
|
||||
samples_max_age: timedelta | None,
|
||||
precision: int,
|
||||
quantile_intervals: int,
|
||||
quantile_method: Literal["exclusive", "inclusive"],
|
||||
percentile: int,
|
||||
) -> None:
|
||||
"""Initialize the Statistics sensor."""
|
||||
self._attr_icon: str = ICON
|
||||
@ -319,8 +311,7 @@ class StatisticsSensor(SensorEntity):
|
||||
self._samples_max_buffer_size: int = samples_max_buffer_size
|
||||
self._samples_max_age: timedelta | None = samples_max_age
|
||||
self._precision: int = precision
|
||||
self._quantile_intervals: int = quantile_intervals
|
||||
self._quantile_method: Literal["exclusive", "inclusive"] = quantile_method
|
||||
self._percentile: int = percentile
|
||||
self._value: StateType | datetime = None
|
||||
self._unit_of_measurement: str | None = None
|
||||
self._available: bool = False
|
||||
@ -700,18 +691,10 @@ class StatisticsSensor(SensorEntity):
|
||||
return cast(float, self._stat_sum_differences()) / (len(self.states) - 1)
|
||||
return None
|
||||
|
||||
def _stat_quantiles(self) -> StateType:
|
||||
if len(self.states) > self._quantile_intervals:
|
||||
return str(
|
||||
[
|
||||
round(quantile, self._precision)
|
||||
for quantile in statistics.quantiles(
|
||||
self.states,
|
||||
n=self._quantile_intervals,
|
||||
method=self._quantile_method,
|
||||
)
|
||||
]
|
||||
)
|
||||
def _stat_percentile(self) -> StateType:
|
||||
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:
|
||||
|
@ -391,7 +391,7 @@ async def test_age_limit_expiry(hass: HomeAssistant):
|
||||
|
||||
|
||||
async def test_precision(hass: HomeAssistant):
|
||||
"""Test correct result with precision set."""
|
||||
"""Test correct results with precision set."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"sensor",
|
||||
@ -433,6 +433,60 @@ async def test_precision(hass: HomeAssistant):
|
||||
assert state.state == str(round(mean, 3))
|
||||
|
||||
|
||||
async def test_percentile(hass: HomeAssistant):
|
||||
"""Test correct results for percentile characteristic."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"sensor",
|
||||
{
|
||||
"sensor": [
|
||||
{
|
||||
"platform": "statistics",
|
||||
"name": "test_percentile_omitted",
|
||||
"entity_id": "sensor.test_monitored",
|
||||
"state_characteristic": "percentile",
|
||||
"sampling_size": 20,
|
||||
},
|
||||
{
|
||||
"platform": "statistics",
|
||||
"name": "test_percentile_default",
|
||||
"entity_id": "sensor.test_monitored",
|
||||
"state_characteristic": "percentile",
|
||||
"sampling_size": 20,
|
||||
"percentile": 50,
|
||||
},
|
||||
{
|
||||
"platform": "statistics",
|
||||
"name": "test_percentile_min",
|
||||
"entity_id": "sensor.test_monitored",
|
||||
"state_characteristic": "percentile",
|
||||
"sampling_size": 20,
|
||||
"percentile": 1,
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for value in VALUES_NUMERIC:
|
||||
hass.states.async_set(
|
||||
"sensor.test_monitored",
|
||||
str(value),
|
||||
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.test_percentile_omitted")
|
||||
assert state is not None
|
||||
assert state.state == str(9.2)
|
||||
state = hass.states.get("sensor.test_percentile_default")
|
||||
assert state is not None
|
||||
assert state.state == str(9.2)
|
||||
state = hass.states.get("sensor.test_percentile_min")
|
||||
assert state is not None
|
||||
assert state.state == str(2.72)
|
||||
|
||||
|
||||
async def test_device_class(hass: HomeAssistant):
|
||||
"""Test device class, which depends on the source entity."""
|
||||
assert await async_setup_component(
|
||||
@ -753,13 +807,11 @@ async def test_state_characteristics(hass: HomeAssistant):
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "quantiles",
|
||||
"name": "percentile",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": STATE_UNKNOWN,
|
||||
"value_9": [
|
||||
round(quantile, 2) for quantile in statistics.quantiles(VALUES_NUMERIC)
|
||||
],
|
||||
"unit": None,
|
||||
"value_9": 9.2,
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
|
Loading…
x
Reference in New Issue
Block a user