From 43e0d556d1bbe6fed06bf5a90fa1bea8a8b2c81e Mon Sep 17 00:00:00 2001 From: Dirk Alders Date: Tue, 29 Sep 2020 17:13:38 +0200 Subject: [PATCH] continues_statistic reworked --- __init__.py | 202 ++++++++++++++++++++-------------------------------- 1 file changed, 76 insertions(+), 126 deletions(-) diff --git a/__init__.py b/__init__.py index eeaf227..a51e5a6 100644 --- a/__init__.py +++ b/__init__.py @@ -8,71 +8,90 @@ logger = logging.getLogger(logger_name) class continues_statistic(dict): - KEY_SINGLE_VALUE = '_' - KEY_MEAN_VALUE = 'mean_val' - KEY_MIN_VALUE = 'min_val' - KEY_MAX_VALUE = 'max_val' - KEY_NUM_VALUES = 'num_val' - """ - Value can be a single single numeric value: continues_statistic(num) or multivalue statistic: continues_statistic(val_a=continues_statistic(num_a), val_b=continues_statistic(num_b)). - """ - def __init__(self, *args, **kwargs): - if len(args) == 1 and isinstance(args[0], numbers.Number): - dict.__init__(self) - self[self.KEY_SINGLE_VALUE] = { - self.KEY_MIN_VALUE: args[0], - self.KEY_MEAN_VALUE: args[0], - self.KEY_MAX_VALUE: args[0], - self.KEY_NUM_VALUES: 1, - } + def __init__(self, mean=None, min_val=None, max_val=None, quantifier=None): + dict.__init__(self) + if mean is None: + self.__init_data__(None, None, None, None) + elif min_val is not None and max_val is not None and quantifier is not None: + self.__init_data__(mean, min_val, max_val, quantifier) else: - for key in kwargs: - if isinstance(kwargs[key], numbers.Number): - kwargs[key] = continues_statistic(kwargs[key]) - dict.__init__(self, *args, **kwargs) - for key in self: - if type(self[key]) is not continues_statistic: - data = continues_statistic() - if key == self.KEY_SINGLE_VALUE: - data[self.KEY_SINGLE_VALUE] = self[key] - else: - data[self.KEY_SINGLE_VALUE] = self[key][self.KEY_SINGLE_VALUE] - self[key] = data + self.__init_data__(mean, mean, mean, 1) - @property - def __multivalue__(self): - if len(self.keys()) == 0: - raise AttributeError('No data stored in continues statistic') - elif len(self.keys()) == 1: - if list(self.keys())[0] != self.KEY_SINGLE_VALUE: - raise AttributeError("The single value key needs to be %s." % self.KEY_SINGLE_VALUE) - return False - return True + def __init_data__(self, mean, min_val, max_val, quantifier): + self['quantifier'] = quantifier + self['max_val'] = max_val + self['min_val'] = min_val + self['mean'] = mean + + def expand(self, value): + if self['quantifier'] is None: + self.__init_data__(value, value, value, 1) + else: + cs_sum = self + continues_statistic(value) + for key in self: + self[key] = cs_sum[key] def __add__(self, other): - rv = continues_statistic(self) - if self.__multivalue__: - if self.__multivalue__ != other.__multivalue__ or self.keys() != other.keys(): - raise AttributeError("Instances are incompatible") - else: - for key in rv: - rv[key] += other[key] - else: - rv[self.KEY_SINGLE_VALUE][self.KEY_MEAN_VALUE] = (self[self.KEY_SINGLE_VALUE][self.KEY_MEAN_VALUE] * self[self.KEY_SINGLE_VALUE][self.KEY_NUM_VALUES] + other[self.KEY_SINGLE_VALUE][self.KEY_MEAN_VALUE] * other[self.KEY_SINGLE_VALUE][self.KEY_NUM_VALUES]) / (self[self.KEY_SINGLE_VALUE][self.KEY_NUM_VALUES] + other[self.KEY_SINGLE_VALUE][self.KEY_NUM_VALUES]) - rv[self.KEY_SINGLE_VALUE][self.KEY_MIN_VALUE] = min(self[self.KEY_SINGLE_VALUE][self.KEY_MIN_VALUE], other[self.KEY_SINGLE_VALUE][self.KEY_MIN_VALUE]) - rv[self.KEY_SINGLE_VALUE][self.KEY_MAX_VALUE] = max(self[self.KEY_SINGLE_VALUE][self.KEY_MAX_VALUE], other[self.KEY_SINGLE_VALUE][self.KEY_MAX_VALUE]) - rv[self.KEY_SINGLE_VALUE][self.KEY_NUM_VALUES] = self[self.KEY_SINGLE_VALUE][self.KEY_NUM_VALUES]+other[self.KEY_SINGLE_VALUE][self.KEY_NUM_VALUES] + rv = continues_statistic() + rv['mean'] = (self.mean * self.quantifier + other.mean * other.quantifier) / (self.quantifier + other.quantifier) + rv['min_val'] = min(self.min, other.min) + rv['max_val'] = max(self.max, other.max) + rv['quantifier'] = self.quantifier + other.quantifier return rv - + + @property + def mean(self): + return self.get('mean') + + @property + def min(self): + return self.get('min_val') + + @property + def max(self): + return self.get('max_val') + + @property + def quantifier(self): + return self.get('quantifier') + + def pop(self): + rv = continues_statistic(self.mean, self.min, self.max, self.quantifier) + self.__init_data__(None, None, None, None) + return rv + def __str__(self): - output_for_value = "mean=%%(%s)s, min=%%(%s)s, max=%%(%s)s, num=%%(%s)s" % (self.KEY_MEAN_VALUE, self.KEY_MIN_VALUE, self.KEY_MAX_VALUE, self.KEY_NUM_VALUES) - if self.__multivalue__: - rv = "" - for key in self: - rv += key + ': ' + str(self[key]) + "\n" + return "mean=%(mean)s, min=%(min_val)s, max=%(max_val)s, quantifier=%(quantifier)s" % self + + +class continues_statistic_multivalue(dict): + def __init__(self, **kwargs): + dict.__init__(self) + for key in kwargs: + if type(kwargs[key]) is continues_statistic: + self[key] = kwargs[key] + elif type(kwargs[key]) is dict: + self[key] = continues_statistic(**kwargs[key]) + else: + self[key] = continues_statistic(kwargs[key]) + + def expand(self, **kwargs): + for key in kwargs: + if key in self: + self[key].expand(kwargs[key]) + else: + self[key] = continues_statistic(kwargs[key]) + + def pop(self, key=None): + if key is None: + rv = continues_statistic_multivalue(**self) + self.clear() return rv else: - return output_for_value % self[self.KEY_SINGLE_VALUE] + return self[key].pop() + + def __str__(self): + return '\n'.join([key + ': ' + str(self[key]) for key in self]) class ringbuffer(list): @@ -89,72 +108,3 @@ class ringbuffer(list): rv = list.append(self, *args, **kwargs) self.__reduce_list__() return rv - - -class data_collector(object): - def __init__(self, bufferlength, current_max_age=300.): - self.__current_max_age__ = current_max_age - self.__current_data_rinbuffer__ = ringbuffer(length=3) - # - self.__last_add_data__ = 0 - self.__continues_statistic__ = None - self.__yesterdays_statistic__ = None - self.__measurement_day__ = None - # - self.__ringbuffer__ = ringbuffer(length=bufferlength) - - def __process_continues_statistic__(self, **kwargs): - # rotate continues_statistic day by day - today = time.strftime('%Y' + os.path.sep + '%m' + os.path.sep + '%d') - if self.__measurement_day__ is None: - self.__measurement_day__ = today - if self.__measurement_day__ != today: - self.__yesterdays_statistic__ = self.__continues_statistic__ - self.__continues_statistic__ = None - self.__measurement_day__ = None - # Set or update continues statistic - if self.__continues_statistic__ is None: - self.__continues_statistic__ = continues_statistic(**kwargs) - else: - self.__continues_statistic__ += continues_statistic(**kwargs) - - - def add_data(self, **kwargs): - logger.debug('Got data %s', repr(kwargs)) - self.__last_add_data__ = time.time() - self.__current_data_rinbuffer__.append(kwargs) - self.__process_continues_statistic__(**kwargs) - self.__ringbuffer__.append(kwargs) - - @property - def current(self): - if len(self.__current_data_rinbuffer__) == 0: - logger.info('No data available, yet.') - return None - elif time.time() > self.__last_add_data__ + self.__current_max_age__: - logger.info('Data max age reached. No current data available.') - return None - elif len(self.__current_data_rinbuffer__) < 3: - return self.__current_data_rinbuffer__[-1] - else: - rv = {} - for key in self.__current_data_rinbuffer__[0]: - data = [] - for entry in self.__current_data_rinbuffer__: - data.append(entry[key]) - else: - data.sort() - rv[key] = data[1] - return rv - - @property - def todays_statistic(self): - return self.__continues_statistic__ - - @property - def yesterdays_statistic(self): - return self.__yesterdays_statistic__ - - @property - def databuffer(self): - return self.__ringbuffer__[:]