OLD | NEW |
1 <!DOCTYPE html> | 1 <!DOCTYPE html> |
2 <!-- | 2 <!-- |
3 Copyright 2014 The Chromium Authors. All rights reserved. | 3 Copyright 2014 The Chromium Authors. All rights reserved. |
4 Use of this source code is governed by a BSD-style license that can be | 4 Use of this source code is governed by a BSD-style license that can be |
5 found in the LICENSE file. | 5 found in the LICENSE file. |
6 --> | 6 --> |
7 | 7 |
8 <link rel="import" href="/tracing/base/math/math.html"> | 8 <link rel="import" href="/tracing/base/math/math.html"> |
9 <link rel="import" href="/tracing/base/math/range.html"> | 9 <link rel="import" href="/tracing/base/math/range.html"> |
10 <script src="/mannwhitneyu/mannwhitneyu.js"></script> | 10 <script src="/mannwhitneyu/mannwhitneyu.js"></script> |
(...skipping 24 matching lines...) Expand all Loading... |
35 // TODO(charliea): Remove: | 35 // TODO(charliea): Remove: |
36 /* eslint-disable catapult-camelcase */ | 36 /* eslint-disable catapult-camelcase */ |
37 | 37 |
38 tr.exportTo('tr.b.math', function() { | 38 tr.exportTo('tr.b.math', function() { |
39 var identity = x => x; | 39 var identity = x => x; |
40 | 40 |
41 var Statistics = {}; | 41 var Statistics = {}; |
42 | 42 |
43 /* Returns the quotient, or zero if the denominator is zero.*/ | 43 /* Returns the quotient, or zero if the denominator is zero.*/ |
44 Statistics.divideIfPossibleOrZero = function(numerator, denominator) { | 44 Statistics.divideIfPossibleOrZero = function(numerator, denominator) { |
45 if (denominator === 0) | 45 if (denominator === 0) return 0; |
46 return 0; | |
47 return numerator / denominator; | 46 return numerator / denominator; |
48 }; | 47 }; |
49 | 48 |
50 Statistics.sum = function(ary, opt_func, opt_this) { | 49 Statistics.sum = function(ary, opt_func, opt_this) { |
51 var func = opt_func || identity; | 50 var func = opt_func || identity; |
52 var ret = 0; | 51 var ret = 0; |
53 var i = 0; | 52 var i = 0; |
54 for (var elt of ary) | 53 for (var elt of ary) { |
55 ret += func.call(opt_this, elt, i++); | 54 ret += func.call(opt_this, elt, i++); |
| 55 } |
56 return ret; | 56 return ret; |
57 }; | 57 }; |
58 | 58 |
59 Statistics.mean = function(ary, opt_func, opt_this) { | 59 Statistics.mean = function(ary, opt_func, opt_this) { |
60 var func = opt_func || identity; | 60 var func = opt_func || identity; |
61 var sum = 0; | 61 var sum = 0; |
62 var i = 0; | 62 var i = 0; |
63 | 63 |
64 for (var elt of ary) | 64 for (var elt of ary) { |
65 sum += func.call(opt_this, elt, i++); | 65 sum += func.call(opt_this, elt, i++); |
| 66 } |
66 | 67 |
67 if (i === 0) | 68 if (i === 0) return undefined; |
68 return undefined; | |
69 | 69 |
70 return sum / i; | 70 return sum / i; |
71 }; | 71 }; |
72 | 72 |
73 Statistics.geometricMean = function(ary, opt_func, opt_this) { | 73 Statistics.geometricMean = function(ary, opt_func, opt_this) { |
74 var func = opt_func || identity; | 74 var func = opt_func || identity; |
75 var i = 0; | 75 var i = 0; |
76 var logsum = 0; | 76 var logsum = 0; |
77 | 77 |
78 // The geometric mean is expressed as the arithmetic mean of logarithms | 78 // The geometric mean is expressed as the arithmetic mean of logarithms |
79 // in order to prevent overflow. | 79 // in order to prevent overflow. |
80 for (var elt of ary) { | 80 for (var elt of ary) { |
81 var x = func.call(opt_this, elt, i++); | 81 var x = func.call(opt_this, elt, i++); |
82 if (x <= 0) | 82 if (x <= 0) return 0; |
83 return 0; | |
84 logsum += Math.log(Math.abs(x)); | 83 logsum += Math.log(Math.abs(x)); |
85 } | 84 } |
86 | 85 |
87 if (i === 0) | 86 if (i === 0) return 1; |
88 return 1; | |
89 | 87 |
90 return Math.exp(logsum / i); | 88 return Math.exp(logsum / i); |
91 }; | 89 }; |
92 | 90 |
93 // Returns undefined if the sum of the weights is zero. | 91 // Returns undefined if the sum of the weights is zero. |
94 Statistics.weightedMean = function( | 92 Statistics.weightedMean = function( |
95 ary, weightCallback, opt_valueCallback, opt_this) { | 93 ary, weightCallback, opt_valueCallback, opt_this) { |
96 var valueCallback = opt_valueCallback || identity; | 94 var valueCallback = opt_valueCallback || identity; |
97 var numerator = 0; | 95 var numerator = 0; |
98 var denominator = 0; | 96 var denominator = 0; |
99 var i = -1; | 97 var i = -1; |
100 | 98 |
101 for (var elt of ary) { | 99 for (var elt of ary) { |
102 i++; | 100 i++; |
103 var value = valueCallback.call(opt_this, elt, i); | 101 var value = valueCallback.call(opt_this, elt, i); |
104 if (value === undefined) | 102 if (value === undefined) continue; |
105 continue; | |
106 var weight = weightCallback.call(opt_this, elt, i, value); | 103 var weight = weightCallback.call(opt_this, elt, i, value); |
107 numerator += weight * value; | 104 numerator += weight * value; |
108 denominator += weight; | 105 denominator += weight; |
109 } | 106 } |
110 | 107 |
111 if (denominator === 0) | 108 if (denominator === 0) return undefined; |
112 return undefined; | |
113 | 109 |
114 return numerator / denominator; | 110 return numerator / denominator; |
115 }; | 111 }; |
116 | 112 |
117 Statistics.variance = function(ary, opt_func, opt_this) { | 113 Statistics.variance = function(ary, opt_func, opt_this) { |
118 if (ary.length === 0) | 114 if (ary.length === 0) return undefined; |
119 return undefined; | 115 if (ary.length === 1) return 0; |
120 if (ary.length === 1) | |
121 return 0; | |
122 var func = opt_func || identity; | 116 var func = opt_func || identity; |
123 var mean = Statistics.mean(ary, func, opt_this); | 117 var mean = Statistics.mean(ary, func, opt_this); |
124 var sumOfSquaredDistances = Statistics.sum( | 118 var sumOfSquaredDistances = Statistics.sum( |
125 ary, | 119 ary, |
126 function(d, i) { | 120 function(d, i) { |
127 var v = func.call(this, d, i) - mean; | 121 var v = func.call(this, d, i) - mean; |
128 return v * v; | 122 return v * v; |
129 }, | 123 }, |
130 opt_this); | 124 opt_this); |
131 return sumOfSquaredDistances / (ary.length - 1); | 125 return sumOfSquaredDistances / (ary.length - 1); |
132 }; | 126 }; |
133 | 127 |
134 Statistics.stddev = function(ary, opt_func, opt_this) { | 128 Statistics.stddev = function(ary, opt_func, opt_this) { |
135 if (ary.length === 0) | 129 if (ary.length === 0) return undefined; |
136 return undefined; | |
137 return Math.sqrt( | 130 return Math.sqrt( |
138 Statistics.variance(ary, opt_func, opt_this)); | 131 Statistics.variance(ary, opt_func, opt_this)); |
139 }; | 132 }; |
140 | 133 |
141 Statistics.max = function(ary, opt_func, opt_this) { | 134 Statistics.max = function(ary, opt_func, opt_this) { |
142 var func = opt_func || identity; | 135 var func = opt_func || identity; |
143 var ret = -Infinity; | 136 var ret = -Infinity; |
144 var i = 0; | 137 var i = 0; |
145 for (var elt of ary) | 138 for (var elt of ary) { |
146 ret = Math.max(ret, func.call(opt_this, elt, i++)); | 139 ret = Math.max(ret, func.call(opt_this, elt, i++)); |
| 140 } |
147 return ret; | 141 return ret; |
148 }; | 142 }; |
149 | 143 |
150 Statistics.min = function(ary, opt_func, opt_this) { | 144 Statistics.min = function(ary, opt_func, opt_this) { |
151 var func = opt_func || identity; | 145 var func = opt_func || identity; |
152 var ret = Infinity; | 146 var ret = Infinity; |
153 var i = 0; | 147 var i = 0; |
154 for (var elt of ary) | 148 for (var elt of ary) { |
155 ret = Math.min(ret, func.call(opt_this, elt, i++)); | 149 ret = Math.min(ret, func.call(opt_this, elt, i++)); |
| 150 } |
156 return ret; | 151 return ret; |
157 }; | 152 }; |
158 | 153 |
159 Statistics.range = function(ary, opt_func, opt_this) { | 154 Statistics.range = function(ary, opt_func, opt_this) { |
160 var func = opt_func || identity; | 155 var func = opt_func || identity; |
161 var ret = new tr.b.math.Range(); | 156 var ret = new tr.b.math.Range(); |
162 var i = 0; | 157 var i = 0; |
163 for (var elt of ary) | 158 for (var elt of ary) { |
164 ret.addValue(func.call(opt_this, elt, i++)); | 159 ret.addValue(func.call(opt_this, elt, i++)); |
| 160 } |
165 return ret; | 161 return ret; |
166 }; | 162 }; |
167 | 163 |
168 Statistics.percentile = function(ary, percent, opt_func, opt_this) { | 164 Statistics.percentile = function(ary, percent, opt_func, opt_this) { |
169 if (!(percent >= 0 && percent <= 1)) | 165 if (!(percent >= 0 && percent <= 1)) { |
170 throw new Error('percent must be [0,1]'); | 166 throw new Error('percent must be [0,1]'); |
| 167 } |
171 | 168 |
172 var func = opt_func || identity; | 169 var func = opt_func || identity; |
173 var tmp = new Array(ary.length); | 170 var tmp = new Array(ary.length); |
174 var i = 0; | 171 var i = 0; |
175 for (var elt of ary) | 172 for (var elt of ary) { |
176 tmp[i] = func.call(opt_this, elt, i++); | 173 tmp[i] = func.call(opt_this, elt, i++); |
| 174 } |
177 tmp.sort((a, b) => a - b); | 175 tmp.sort((a, b) => a - b); |
178 var idx = Math.floor((ary.length - 1) * percent); | 176 var idx = Math.floor((ary.length - 1) * percent); |
179 return tmp[idx]; | 177 return tmp[idx]; |
180 }; | 178 }; |
181 | 179 |
182 /** | 180 /** |
183 * Sorts the samples, and map them linearly to the range [0,1]. | 181 * Sorts the samples, and map them linearly to the range [0,1]. |
184 * | 182 * |
185 * They're mapped such that for the N samples, the first sample is 0.5/N and | 183 * They're mapped such that for the N samples, the first sample is 0.5/N and |
186 * the last sample is (N-0.5)/N. | 184 * the last sample is (N-0.5)/N. |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
230 /** | 228 /** |
231 * Computes the discrepancy of a set of 1D samples from the interval [0,1]. | 229 * Computes the discrepancy of a set of 1D samples from the interval [0,1]. |
232 * | 230 * |
233 * The samples must be sorted. We define the discrepancy of an empty set | 231 * The samples must be sorted. We define the discrepancy of an empty set |
234 * of samples to be zero. | 232 * of samples to be zero. |
235 * | 233 * |
236 * http://en.wikipedia.org/wiki/Low-discrepancy_sequence | 234 * http://en.wikipedia.org/wiki/Low-discrepancy_sequence |
237 * http://mathworld.wolfram.com/Discrepancy.html | 235 * http://mathworld.wolfram.com/Discrepancy.html |
238 */ | 236 */ |
239 Statistics.discrepancy = function(samples, opt_locationCount) { | 237 Statistics.discrepancy = function(samples, opt_locationCount) { |
240 if (samples.length === 0) | 238 if (samples.length === 0) return 0.0; |
241 return 0.0; | |
242 | 239 |
243 var maxLocalDiscrepancy = 0; | 240 var maxLocalDiscrepancy = 0; |
244 var invSampleCount = 1.0 / samples.length; | 241 var invSampleCount = 1.0 / samples.length; |
245 var locations = []; | 242 var locations = []; |
246 // For each location, stores the number of samples less than that location. | 243 // For each location, stores the number of samples less than that location. |
247 var countLess = []; | 244 var countLess = []; |
248 // For each location, stores the number of samples less than or equal to | 245 // For each location, stores the number of samples less than or equal to |
249 // that location. | 246 // that location. |
250 var countLessEqual = []; | 247 var countLessEqual = []; |
251 | 248 |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
353 * | 350 * |
354 * The time stamp series C = [0,2,3,4] and D = [0,2,3,4,5] have the same | 351 * The time stamp series C = [0,2,3,4] and D = [0,2,3,4,5] have the same |
355 * absolute discrepancy, but D has lower relative discrepancy than C. | 352 * absolute discrepancy, but D has lower relative discrepancy than C. |
356 * | 353 * |
357 * |timestamps| may be a list of lists S = [S_1, S_2, ..., S_N], where each | 354 * |timestamps| may be a list of lists S = [S_1, S_2, ..., S_N], where each |
358 * S_i is a time stamp series. In that case, the discrepancy D(S) is: | 355 * S_i is a time stamp series. In that case, the discrepancy D(S) is: |
359 * D(S) = max(D(S_1), D(S_2), ..., D(S_N)) | 356 * D(S) = max(D(S_1), D(S_2), ..., D(S_N)) |
360 **/ | 357 **/ |
361 Statistics.timestampsDiscrepancy = function(timestamps, opt_absolute, | 358 Statistics.timestampsDiscrepancy = function(timestamps, opt_absolute, |
362 opt_locationCount) { | 359 opt_locationCount) { |
363 if (timestamps.length === 0) | 360 if (timestamps.length === 0) return 0.0; |
364 return 0.0; | |
365 | 361 |
366 if (opt_absolute === undefined) | 362 if (opt_absolute === undefined) opt_absolute = true; |
367 opt_absolute = true; | |
368 | 363 |
369 if (Array.isArray(timestamps[0])) { | 364 if (Array.isArray(timestamps[0])) { |
370 var rangeDiscrepancies = timestamps.map(function(r) { | 365 var rangeDiscrepancies = timestamps.map(function(r) { |
371 return Statistics.timestampsDiscrepancy(r); | 366 return Statistics.timestampsDiscrepancy(r); |
372 }); | 367 }); |
373 return Math.max.apply(null, rangeDiscrepancies); | 368 return Math.max.apply(null, rangeDiscrepancies); |
374 } | 369 } |
375 | 370 |
376 var s = Statistics.normalizeSamples(timestamps); | 371 var s = Statistics.normalizeSamples(timestamps); |
377 var samples = s.normalized_samples; | 372 var samples = s.normalized_samples; |
(...skipping 23 matching lines...) Expand all Loading... |
401 * Because timestamp discrepancy is defined in terms of timestamps, we first | 396 * Because timestamp discrepancy is defined in terms of timestamps, we first |
402 * convert the list of durations to monotonically increasing timestamps. | 397 * convert the list of durations to monotonically increasing timestamps. |
403 * | 398 * |
404 * Args: | 399 * Args: |
405 * durations: List of interval lengths in milliseconds. | 400 * durations: List of interval lengths in milliseconds. |
406 * absolute: See TimestampsDiscrepancy. | 401 * absolute: See TimestampsDiscrepancy. |
407 * opt_locationCount: See TimestampsDiscrepancy. | 402 * opt_locationCount: See TimestampsDiscrepancy. |
408 **/ | 403 **/ |
409 Statistics.durationsDiscrepancy = function( | 404 Statistics.durationsDiscrepancy = function( |
410 durations, opt_absolute, opt_locationCount) { | 405 durations, opt_absolute, opt_locationCount) { |
411 if (durations.length === 0) | 406 if (durations.length === 0) return 0.0; |
412 return 0.0; | |
413 | 407 |
414 var timestamps = durations.reduce(function(prev, curr, index, array) { | 408 var timestamps = durations.reduce(function(prev, curr, index, array) { |
415 prev.push(prev[prev.length - 1] + curr); | 409 prev.push(prev[prev.length - 1] + curr); |
416 return prev; | 410 return prev; |
417 }, [0]); | 411 }, [0]); |
418 return Statistics.timestampsDiscrepancy( | 412 return Statistics.timestampsDiscrepancy( |
419 timestamps, opt_absolute, opt_locationCount); | 413 timestamps, opt_absolute, opt_locationCount); |
420 }; | 414 }; |
421 | 415 |
422 /** | 416 /** |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
455 * | 449 * |
456 * Args: | 450 * Args: |
457 * samples: Array of elements that have already been selected. Start with []. | 451 * samples: Array of elements that have already been selected. Start with []. |
458 * streamLength: The current length of the stream, up to |newElement|. | 452 * streamLength: The current length of the stream, up to |newElement|. |
459 * newElement: The element that was just extracted from the stream. | 453 * newElement: The element that was just extracted from the stream. |
460 * numSamples: The total number of samples desired. | 454 * numSamples: The total number of samples desired. |
461 **/ | 455 **/ |
462 Statistics.uniformlySampleStream = function(samples, streamLength, newElement, | 456 Statistics.uniformlySampleStream = function(samples, streamLength, newElement, |
463 numSamples) { | 457 numSamples) { |
464 if (streamLength <= numSamples) { | 458 if (streamLength <= numSamples) { |
465 if (samples.length >= streamLength) | 459 if (samples.length >= streamLength) { |
466 samples[streamLength - 1] = newElement; | 460 samples[streamLength - 1] = newElement; |
467 else | 461 } else { |
468 samples.push(newElement); | 462 samples.push(newElement); |
| 463 } |
469 return; | 464 return; |
470 } | 465 } |
471 | 466 |
472 var probToKeep = numSamples / streamLength; | 467 var probToKeep = numSamples / streamLength; |
473 if (Math.random() > probToKeep) | 468 if (Math.random() > probToKeep) return; // New sample was rejected. |
474 return; // New sample was rejected. | |
475 | 469 |
476 // Keeping it, replace an alement randomly. | 470 // Keeping it, replace an alement randomly. |
477 var index = Math.floor(Math.random() * numSamples); | 471 var index = Math.floor(Math.random() * numSamples); |
478 samples[index] = newElement; | 472 samples[index] = newElement; |
479 }; | 473 }; |
480 | 474 |
481 /** | 475 /** |
482 * A mechanism to merge two arrays of uniformly sampled elements in a way that | 476 * A mechanism to merge two arrays of uniformly sampled elements in a way that |
483 * ensures elements in the final array are still sampled uniformly. | 477 * ensures elements in the final array are still sampled uniformly. |
484 * | 478 * |
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
626 /* An alternative measure of how spread out the distribution is, | 620 /* An alternative measure of how spread out the distribution is, |
627 * the variance is the square of the standard deviation. | 621 * the variance is the square of the standard deviation. |
628 * @return {number} variance. | 622 * @return {number} variance. |
629 */ | 623 */ |
630 get variance() { | 624 get variance() { |
631 throw Error('Not implemented'); | 625 throw Error('Not implemented'); |
632 } | 626 } |
633 }; | 627 }; |
634 | 628 |
635 Statistics.UniformDistribution = function(opt_range) { | 629 Statistics.UniformDistribution = function(opt_range) { |
636 if (!opt_range) | 630 if (!opt_range) opt_range = tr.b.math.Range.fromExplicitRange(0, 1); |
637 opt_range = tr.b.math.Range.fromExplicitRange(0, 1); | |
638 this.range = opt_range; | 631 this.range = opt_range; |
639 }; | 632 }; |
640 | 633 |
641 Statistics.UniformDistribution.prototype = { | 634 Statistics.UniformDistribution.prototype = { |
642 __proto__: Distribution.prototype, | 635 __proto__: Distribution.prototype, |
643 | 636 |
644 computeDensity: function(x) { | 637 computeDensity: function(x) { |
645 return 1 / this.range.range; | 638 return 1 / this.range.range; |
646 }, | 639 }, |
647 | 640 |
(...skipping 191 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
839 result.significance = Statistics.Significance.INSIGNIFICANT; | 832 result.significance = Statistics.Significance.INSIGNIFICANT; |
840 } | 833 } |
841 return result; | 834 return result; |
842 }; | 835 }; |
843 | 836 |
844 return { | 837 return { |
845 Statistics, | 838 Statistics, |
846 }; | 839 }; |
847 }); | 840 }); |
848 </script> | 841 </script> |
OLD | NEW |