Commit c0e84e2c authored by Jason Davies's avatar Jason Davies
Browse files

Optimise d3.mean.

Originally we were using Welford’s algorithm, but this is primarily
useful when computing the variance in a numerically stable manner, since
Welford’s approach requires an incremental mean.

I’ve removed a test for the mean of more than one instance of
Number.MAX_VALUE as this is unlikely to occur in practice; most likely
this was the reason I used Welford’s algorithm in the first place.

There’s a paper [1] comparing various algorithms for computing the mean,
and Welford’s is actually slightly less accurate than the naïve
approach.  There are some more accurate approaches but I think it’s
overkill for d3.mean.

[1] Youngs, Edward A., and Elliot M. Cramer. "Some results relevant to
choice of sum and sum-of-product algorithms." Technometrics 13.3 (1971):
657-665.

Related: #1842.
parent 624f21c9
......@@ -91,13 +91,13 @@
return x != null && !isNaN(x);
}
d3.mean = function(array, f) {
var n = array.length, a, m = 0, i = -1, j = 0;
var s = 0, n = array.length, a, i = -1, j = n;
if (arguments.length === 1) {
while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j;
while (++i < n) if (d3_number(a = array[i])) s += a; else --j;
} else {
while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j;
while (++i < n) if (d3_number(a = f.call(array, array[i], i))) s += a; else --j;
}
return j ? m : undefined;
return j ? s / j : undefined;
};
d3.quantile = function(values, p) {
var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h;
......
This diff is collapsed.
import "../math/number";
d3.mean = function(array, f) {
var n = array.length,
var s = 0,
n = array.length,
a,
m = 0,
i = -1,
j = 0;
j = n;
if (arguments.length === 1) {
while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j;
while (++i < n) if (d3_number(a = array[i])) s += a; else --j;
} else {
while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j;
while (++i < n) if (d3_number(a = f.call(array, array[i], i))) s += a; else --j;
}
return j ? m : undefined;
return j ? s / j : undefined;
};
......@@ -18,10 +18,6 @@ suite.addBatch({
assert.equal(mean([1, 2, 3, 4, 5, NaN]), 3);
assert.equal(mean([10, null, 3, undefined, 5, NaN]), 6);
},
"can handle large numbers without overflowing": function(mean) {
assert.equal(mean([Number.MAX_VALUE, Number.MAX_VALUE]), Number.MAX_VALUE);
assert.equal(mean([-Number.MAX_VALUE, -Number.MAX_VALUE]), -Number.MAX_VALUE);
},
"returns undefined for empty array": function(mean) {
assert.isUndefined(mean([]));
assert.isUndefined(mean([null]));
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment