Fix jumpavg: No negative variance from rounding
[csit.git] / PyPI / jumpavg / jumpavg / AvgStdevMetadataFactory.py
1 # Copyright (c) 2018 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Module holding AvgStdevMetadataFactory class."""
15
16 import math
17
18 from AvgStdevMetadata import AvgStdevMetadata
19
20
21 class AvgStdevMetadataFactory(object):
22     """Class factory which creates avg,stdev metadata from data."""
23
24     @staticmethod
25     def from_data(values):
26         """Return new metadata object fitting the values.
27
28         :param values: Run values to be processed.
29         :type values: Iterable of float or of AvgStdevMetadata
30         :returns: The metadata matching the values.
31         :rtype: AvgStdevMetadata
32         """
33         # Using Welford method to be more resistant to rounding errors.
34         # Adapted from code for sample standard deviation at:
35         # https://www.johndcook.com/blog/standard_deviation/
36         # The logic of plus operator is taken from
37         # https://www.johndcook.com/blog/skewness_kurtosis/
38         size = 0
39         avg = 0.0
40         moment_2 = 0.0
41         for value in values:
42             if not isinstance(value, AvgStdevMetadata):
43                 value = AvgStdevMetadata(size=1, avg=value)
44             old_size = size
45             delta = value.avg - avg
46             size += value.size
47             avg += delta * value.size / size
48             moment_2 += value.stdev * value.stdev * value.size
49             moment_2 += delta * delta * old_size * value.size / size
50         if size < 1:
51             return AvgStdevMetadata()
52         stdev = math.sqrt(moment_2 / size)
53         ret_obj = AvgStdevMetadata(size=size, avg=avg, stdev=stdev)
54         return ret_obj