1# Copyright 2013 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import numbers 6import math 7 8from telemetry import value as value_module 9from telemetry.value import none_values 10from telemetry.value import summarizable 11 12 13def Variance(sample): 14 """ Compute the population variance. 15 16 Args: 17 sample: a list of numbers. 18 """ 19 k = len(sample) - 1 # Bessel correction 20 if k <= 0: 21 return 0.0 22 m = _Mean(sample) 23 return sum((x - m)**2 for x in sample)/k 24 25 26def StandardDeviation(sample): 27 """ Compute standard deviation for a list of numbers. 28 29 Args: 30 sample: a list of numbers. 31 """ 32 return math.sqrt(Variance(sample)) 33 34 35def PooledStandardDeviation(list_of_samples, list_of_variances=None): 36 """ Compute standard deviation for a list of samples. 37 38 See: https://en.wikipedia.org/wiki/Pooled_variance for the formula. 39 40 Args: 41 list_of_samples: a list of lists, each is a list of numbers. 42 list_of_variances: a list of numbers, the i-th element is the variance of 43 the i-th sample in list_of_samples. If this is None, we use 44 Variance(sample) to get the variance of the i-th sample. 45 """ 46 pooled_variance = 0.0 47 total_degrees_of_freedom = 0 48 for i in xrange(len(list_of_samples)): 49 l = list_of_samples[i] 50 k = len(l) - 1 # Bessel correction 51 if k <= 0: 52 continue 53 variance = list_of_variances[i] if list_of_variances else Variance(l) 54 pooled_variance += k * variance 55 total_degrees_of_freedom += k 56 if total_degrees_of_freedom: 57 return (pooled_variance / total_degrees_of_freedom) ** 0.5 58 else: 59 return 0.0 60 61 62def _Mean(values): 63 return float(sum(values)) / len(values) if len(values) > 0 else 0.0 64 65 66class ListOfScalarValues(summarizable.SummarizableValue): 67 """ ListOfScalarValues represents a list of numbers. 68 69 By default, std is the standard deviation of all numbers in the list. Std can 70 also be specified in the constructor if the numbers are not from the same 71 population. 72 """ 73 def __init__(self, page, name, units, values, 74 important=True, description=None, 75 tir_label=None, none_value_reason=None, 76 std=None, improvement_direction=None, grouping_keys=None): 77 super(ListOfScalarValues, self).__init__(page, name, units, important, 78 description, tir_label, 79 improvement_direction, 80 grouping_keys) 81 if values is not None: 82 assert isinstance(values, list) 83 assert len(values) > 0 84 assert all(isinstance(v, numbers.Number) for v in values) 85 assert std is None or isinstance(std, numbers.Number) 86 else: 87 assert std is None 88 none_values.ValidateNoneValueReason(values, none_value_reason) 89 self.values = values 90 self.none_value_reason = none_value_reason 91 if values is not None and std is None: 92 std = StandardDeviation(values) 93 assert std is None or std >= 0, ( 94 'standard deviation cannot be negative: %s' % std) 95 self._std = std 96 97 @property 98 def std(self): 99 return self._std 100 101 @property 102 def variance(self): 103 return self._std ** 2 104 105 def __repr__(self): 106 if self.page: 107 page_name = self.page.display_name 108 else: 109 page_name = 'None' 110 return ('ListOfScalarValues(%s, %s, %s, %s, ' 111 'important=%s, description=%s, tir_label=%s, std=%s, ' 112 'improvement_direction=%s, grouping_keys=%s)') % ( 113 page_name, 114 self.name, 115 self.units, 116 repr(self.values), 117 self.important, 118 self.description, 119 self.tir_label, 120 self.std, 121 self.improvement_direction, 122 self.grouping_keys) 123 124 def GetBuildbotDataType(self, output_context): 125 if self._IsImportantGivenOutputIntent(output_context): 126 return 'default' 127 return 'unimportant' 128 129 def GetBuildbotValue(self): 130 return self.values 131 132 def GetRepresentativeNumber(self): 133 return _Mean(self.values) 134 135 def GetRepresentativeString(self): 136 return repr(self.values) 137 138 @staticmethod 139 def GetJSONTypeName(): 140 return 'list_of_scalar_values' 141 142 def AsDict(self): 143 d = super(ListOfScalarValues, self).AsDict() 144 d['values'] = self.values 145 d['std'] = self.std 146 147 if self.none_value_reason is not None: 148 d['none_value_reason'] = self.none_value_reason 149 150 return d 151 152 @staticmethod 153 def FromDict(value_dict, page_dict): 154 kwargs = value_module.Value.GetConstructorKwArgs(value_dict, page_dict) 155 kwargs['values'] = value_dict['values'] 156 kwargs['std'] = value_dict['std'] 157 158 if 'improvement_direction' in value_dict: 159 kwargs['improvement_direction'] = value_dict['improvement_direction'] 160 if 'none_value_reason' in value_dict: 161 kwargs['none_value_reason'] = value_dict['none_value_reason'] 162 163 return ListOfScalarValues(**kwargs) 164 165 @classmethod 166 def MergeLikeValuesFromSamePage(cls, values): 167 assert len(values) > 0 168 v0 = values[0] 169 170 return cls._MergeLikeValues(values, v0.page, v0.name, v0.grouping_keys) 171 172 @classmethod 173 def MergeLikeValuesFromDifferentPages(cls, values): 174 assert len(values) > 0 175 v0 = values[0] 176 return cls._MergeLikeValues(values, None, v0.name, v0.grouping_keys) 177 178 @classmethod 179 def _MergeLikeValues(cls, values, page, name, grouping_keys): 180 v0 = values[0] 181 merged_values = [] 182 list_of_samples = [] 183 none_value_reason = None 184 pooled_std = None 185 for v in values: 186 if v.values is None: 187 merged_values = None 188 merged_none_values = [v for v in values if v.values is None] 189 none_value_reason = (none_values.MERGE_FAILURE_REASON + 190 ' None values: %s' % repr(merged_none_values)) 191 break 192 merged_values.extend(v.values) 193 list_of_samples.append(v.values) 194 if merged_values and page is None: 195 # Pooled standard deviation is only used when merging values comming from 196 # different pages. Otherwise, fall back to the default computation done 197 # in the cosntructor of ListOfScalarValues. 198 pooled_std = PooledStandardDeviation( 199 list_of_samples, list_of_variances=[v.variance for v in values]) 200 return ListOfScalarValues( 201 page, name, v0.units, 202 merged_values, 203 important=v0.important, 204 description=v0.description, 205 tir_label=value_module.MergedTirLabel(values), 206 std=pooled_std, 207 none_value_reason=none_value_reason, 208 improvement_direction=v0.improvement_direction, 209 grouping_keys=grouping_keys) 210