From 699b995b63bd6b9b8c377c7332b173ad21c75a93 Mon Sep 17 00:00:00 2001 From: Ethan Jewett Date: Wed, 17 Sep 2014 15:20:10 -0500 Subject: [PATCH] sumOfSq and std (standard deviation) - adding files --- src/std.js | 37 +++++++++++++++ src/sum-of-squares.js | 25 ++++++++++ test/std.spec.js | 91 +++++++++++++++++++++++++++++++++++++ test/sum-of-squares.spec.js | 50 ++++++++++++++++++++ 4 files changed, 203 insertions(+) create mode 100644 src/std.js create mode 100644 src/sum-of-squares.js create mode 100644 test/std.spec.js create mode 100644 test/sum-of-squares.spec.js diff --git a/src/std.js b/src/std.js new file mode 100644 index 0000000..c6f1dc4 --- /dev/null +++ b/src/std.js @@ -0,0 +1,37 @@ +var reductio_std = { + add: function (prior) { + return function (p, v) { + if(prior) prior(p, v); + if(p.count > 0) { + p.std = 0.0; + var n = p.sumOfSq - p.sum*p.sum/p.count; + if (n>0.0) p.std = Math.sqrt(n/(p.count-1)); + } else { + p.std = 0.0; + } + return p; + }; + }, + remove: function (prior) { + return function (p, v) { + if(prior) prior(p, v); + if(p.count > 0) { + p.std = 0.0; + var n = p.sumOfSq - p.sum*p.sum/p.count; + if (n>0.0) p.std = Math.sqrt(n/(p.count-1)); + } else { + p.std = 0; + } + return p; + }; + }, + initial: function (prior) { + return function (p) { + p = prior(p); + p.std = 0; + return p; + }; + } +}; + +module.exports = reductio_std; \ No newline at end of file diff --git a/src/sum-of-squares.js b/src/sum-of-squares.js new file mode 100644 index 0000000..ab55046 --- /dev/null +++ b/src/sum-of-squares.js @@ -0,0 +1,25 @@ +var reductio_sum_of_sq = { + add: function (a, prior) { + return function (p, v) { + if(prior) prior(p, v); + p.sumOfSq = p.sumOfSq + a(v)*a(v); + return p; + }; + }, + remove: function (a, prior) { + return function (p, v) { + if(prior) prior(p, v); + p.sumOfSq = p.sumOfSq - a(v)*a(v); + return p; + }; + }, + initial: function (prior) { + return function (p) { + p = prior(p); + p.sumOfSq = 0; + return p; + }; + } +}; + +module.exports = reductio_sum_of_sq; \ No newline at end of file diff --git a/test/std.spec.js b/test/std.spec.js new file mode 100644 index 0000000..3932448 --- /dev/null +++ b/test/std.spec.js @@ -0,0 +1,91 @@ +// Counting tests +describe('Reductio std', function () { + + var std, noSumOfSq, accStd; + + beforeEach(function () { + var data = crossfilter([ + { foo: 'one', bar: 1 }, + { foo: 'two', bar: 2 }, + { foo: 'three', bar: 3 }, + { foo: 'one', bar: 4 }, + { foo: 'one', bar: 5 }, + { foo: 'two', bar: 6 }, + ]); + + var dim = data.dimension(function(d) { return d.foo; }); + var group = dim.group(); + var groupNoSumOfSq = dim.group(); + var groupAccStd = dim.group(); + + var reducer = reductio() + .std(true) + .count(true); + + // This doesn't work because no .sumOfSq(accessor) has been defined. + // The resulting group only tracks counts. + reducer(groupNoSumOfSq); + + reducer.count(false).sumOfSq(false); + reducer.std(function(d) { return d.bar; }); + + reducer(groupAccStd); + + reducer.sumOfSq(false); + reducer.std(false); + + reducer.sumOfSq(function(d) { return d.bar; }) + .std(true); + + // Now it should track count, sumOfSq, and std. + reducer(group); + + std = group; + noSumOfSq = groupNoSumOfSq; + accStd = groupAccStd; + }); + + it('has three groups', function (topic) { + expect(std.top(Infinity).length).toEqual(3); + }); + + it('grouping have the right averages', function (topic) { + var values = {}; + std.top(Infinity).forEach(function (d) { + values[d.key] = d.value; + }); + + expect(Math.round(values['one'].std)).toEqual(Math.round(2.08167)); + expect(Math.round(values['two'].std)).toEqual(Math.round(2.82843)); + expect(Math.round(values['three'].std)).toEqual(Math.round(0)); + }); + + it('grouping with .std() but no .sumOfSq() doesn\'t work', function (topic) { + var values = {}; + noSumOfSq.top(Infinity).forEach(function (d) { + values[d.key] = d.value; + }); + + // It has a count, as defined. + expect(values['one'].count).toEqual(3); + + // But no sumOfSq. + expect(values['one'].sumOfSq).toBeUndefined(); + + // And no std. + expect(values['one'].std).toBeUndefined(); + + // Also throws an error on the console, but that's more difficult to test. + }); + + it('grouping with .std(accessor) works', function (topic) { + var values = {}; + accStd.top(Infinity).forEach(function (d) { + values[d.key] = d.value; + }); + + expect(Math.round(values['one'].std)).toEqual(Math.round(2.08167)); + expect(Math.round(values['two'].std)).toEqual(Math.round(2.82843)); + expect(Math.round(values['three'].std)).toEqual(Math.round(0)); + }); +}); \ No newline at end of file diff --git a/test/sum-of-squares.spec.js b/test/sum-of-squares.spec.js new file mode 100644 index 0000000..6217425 --- /dev/null +++ b/test/sum-of-squares.spec.js @@ -0,0 +1,50 @@ +// Counting tests +describe('Reductio sumOfSq', function () { + var group; + + beforeEach(function () { + var data = crossfilter([ + { foo: 'one', bar: 1 }, + { foo: 'two', bar: 2 }, + { foo: 'three', bar: 3 }, + { foo: 'one', bar: 4 }, + { foo: 'one', bar: 5 }, + { foo: 'two', bar: 6 }, + ]); + + var dim = data.dimension(function(d) { return d.foo; }); + group = dim.group(); + + var reducer = reductio() + .count(true) + .sumOfSq(function(d) { return d.bar; }); + + reducer(group); + }); + + it('has three groups', function () { + expect(group.top(Infinity).length).toEqual(3); + }); + + it('grouping have the right sum of squares', function () { + var values = {}; + group.top(Infinity).forEach(function (d) { + values[d.key] = d.value; + }); + + expect(values['one'].sumOfSq).toEqual(42); + expect(values['two'].sumOfSq).toEqual(40); + expect(values['three'].sumOfSq).toEqual(9); + }); + + it('grouping plays nicely with count', function () { + var values = {}; + group.top(Infinity).forEach(function (d) { + values[d.key] = d.value; + }); + + expect(values['one'].count).toEqual(3); + expect(values['two'].count).toEqual(2); + expect(values['three'].count).toEqual(1); + }); +}); \ No newline at end of file