From bf773eb39a5900aaaf11987c74a2b49f7a264083 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 27 Apr 2021 10:09:13 +0100 Subject: [PATCH] Add "average" filter operator for arithmetic mean (#5612) --- core/modules/filters/math.js | 80 +++++++++++++++++-- .../tiddlers/tests/test-prefixes-filter.js | 22 +++++ .../filters/Mathematics Operators.tid | 3 +- editions/tw5.com/tiddlers/filters/average.tid | 13 +++ .../examples/average Operator (Examples).tid | 10 +++ .../examples/median Operator (Examples).tid | 10 +++ ...standard-deviation Operator (Examples).tid | 10 +++ .../examples/variance Operator (Examples).tid | 10 +++ editions/tw5.com/tiddlers/filters/median.tid | 13 +++ .../filters/standard-deviation Operator.tid | 15 ++++ .../tiddlers/filters/variance Operator.tid | 15 ++++ 11 files changed, 193 insertions(+), 8 deletions(-) create mode 100644 editions/tw5.com/tiddlers/filters/average.tid create mode 100644 editions/tw5.com/tiddlers/filters/examples/average Operator (Examples).tid create mode 100644 editions/tw5.com/tiddlers/filters/examples/median Operator (Examples).tid create mode 100644 editions/tw5.com/tiddlers/filters/examples/standard-deviation Operator (Examples).tid create mode 100644 editions/tw5.com/tiddlers/filters/examples/variance Operator (Examples).tid create mode 100644 editions/tw5.com/tiddlers/filters/median.tid create mode 100644 editions/tw5.com/tiddlers/filters/standard-deviation Operator.tid create mode 100644 editions/tw5.com/tiddlers/filters/variance Operator.tid diff --git a/core/modules/filters/math.js b/core/modules/filters/math.js index f52a8c678..9e767c362 100644 --- a/core/modules/filters/math.js +++ b/core/modules/filters/math.js @@ -125,6 +125,54 @@ exports.minall = makeNumericReducingOperator( Infinity // Initial value ); +exports.median = makeNumericArrayOperator( + function(values) { + var len = values.length, median; + values.sort(); + if(len % 2) { + // Odd, return the middle number + median = values[(len - 1) / 2]; + } else { + // Even, return average of two middle numbers + median = (values[len / 2 - 1] + values[len / 2]) / 2; + } + return [median]; + } +); + +exports.average = makeNumericReducingOperator( + function(accumulator,value) {return accumulator + value}, + 0, // Initial value + function(finalValue,numberOfValues) { + return finalValue/numberOfValues; + } +); + +exports.variance = makeNumericReducingOperator( + function(accumulator,value) {return accumulator + value}, + 0, + function(finalValue,numberOfValues,originalValues) { + return getVarianceFromArray(originalValues,finalValue/numberOfValues); + } +); + +exports["standard-deviation"] = makeNumericReducingOperator( + function(accumulator,value) {return accumulator + value}, + 0, + function(finalValue,numberOfValues,originalValues) { + var variance = getVarianceFromArray(originalValues,finalValue/numberOfValues); + return Math.sqrt(variance); + } +); + +//Calculate the variance of a population of numbers in an array given its mean +function getVarianceFromArray(values,mean) { + var deviationTotal = values.reduce(function(accumulator,value) { + return accumulator + Math.pow(value - mean, 2); + },0); + return deviationTotal/values.length; +}; + function makeNumericBinaryOperator(fnCalc) { return function(source,operator,options) { var result = [], @@ -134,19 +182,37 @@ function makeNumericBinaryOperator(fnCalc) { }); return result; }; -} +}; -function makeNumericReducingOperator(fnCalc,initialValue) { +function makeNumericReducingOperator(fnCalc,initialValue,fnFinal) { initialValue = initialValue || 0; return function(source,operator,options) { var result = []; source(function(tiddler,title) { - result.push(title); + result.push($tw.utils.parseNumber(title)); }); - return [$tw.utils.stringifyNumber(result.reduce(function(accumulator,currentValue) { - return fnCalc(accumulator,$tw.utils.parseNumber(currentValue)); - },initialValue))]; + var value = result.reduce(function(accumulator,currentValue) { + return fnCalc(accumulator,currentValue); + },initialValue); + if(fnFinal) { + value = fnFinal(value,result.length,result); + } + return [$tw.utils.stringifyNumber(value)]; }; -} +}; + +function makeNumericArrayOperator(fnCalc) { + return function(source,operator,options) { + var results = []; + source(function(tiddler,title) { + results.push($tw.utils.parseNumber(title)); + }); + results = fnCalc(results); + $tw.utils.each(results,function(value,index) { + results[index] = $tw.utils.stringifyNumber(value); + }); + return results; + }; +}; })(); diff --git a/editions/test/tiddlers/tests/test-prefixes-filter.js b/editions/test/tiddlers/tests/test-prefixes-filter.js index c089734b5..fb8979a83 100644 --- a/editions/test/tiddlers/tests/test-prefixes-filter.js +++ b/editions/test/tiddlers/tests/test-prefixes-filter.js @@ -298,6 +298,28 @@ describe("'reduce' and 'intersection' filter prefix tests", function() { expect(wiki.filterTiddlers("[tag[non-existent]reduceelse[0]]",anchorWidget).join(",")).toBe("0"); }); + it("should handle the average operator", function() { + expect(wiki.filterTiddlers("[tag[shopping]get[price]average[]]").join(",")).toBe("2.3575"); + expect(parseFloat(wiki.filterTiddlers("[tag[food]get[price]average[]]").join(","))).toBeCloseTo(3.155); + }); + + it("should handle the median operator", function() { + expect(parseFloat(wiki.filterTiddlers("[tag[shopping]get[price]median[]]").join(","))).toBeCloseTo(1.99); + expect(parseFloat(wiki.filterTiddlers("[tag[food]get[price]median[]]").join(","))).toBeCloseTo(3.155); + }); + + it("should handle the variance operator", function() { + expect(parseFloat(wiki.filterTiddlers("[tag[shopping]get[price]variance[]]").join(","))).toBeCloseTo(2.92); + expect(parseFloat(wiki.filterTiddlers("[tag[food]get[price]variance[]]").join(","))).toBeCloseTo(3.367); + expect(wiki.filterTiddlers(" +[variance[]]").toString()).toBe("NaN"); + }); + + it("should handle the standard-deviation operator", function() { + expect(parseFloat(wiki.filterTiddlers("[tag[shopping]get[price]standard-deviation[]]").join(","))).toBeCloseTo(1.71); + expect(parseFloat(wiki.filterTiddlers("[tag[food]get[price]standard-deviation[]]").join(","))).toBeCloseTo(1.835); + expect(wiki.filterTiddlers(" +[standard-deviation[]]").toString()).toBe("NaN"); + }); + it("should handle the :intersection prefix", function() { expect(wiki.filterTiddlers("[[Sparkling water]tags[]] :intersection[[Red wine]tags[]]").join(",")).toBe("drinks,textexample"); expect(wiki.filterTiddlers("[[Brownies]tags[]] :intersection[[Chocolate Cake]tags[]]").join(",")).toBe("food"); diff --git a/editions/tw5.com/tiddlers/filters/Mathematics Operators.tid b/editions/tw5.com/tiddlers/filters/Mathematics Operators.tid index 2040e0293..a0651ea8c 100644 --- a/editions/tw5.com/tiddlers/filters/Mathematics Operators.tid +++ b/editions/tw5.com/tiddlers/filters/Mathematics Operators.tid @@ -1,5 +1,5 @@ created: 20190206140446821 -modified: 20190611155838557 +modified: 20210417090408263 tags: Filters title: Mathematics Operators type: text/vnd.tiddlywiki @@ -26,6 +26,7 @@ The mathematics operators take three different forms: * ''Reducing operators'' apply an operation to all of the numbers in the input list, returning a single result (e.g. sum, product) ** <<.inline-operator-example "=1 =2 =3 =4 +[sum[]]">> ** <<.inline-operator-example "=1 =2 =3 =4 +[product[]]">> +** <<.inline-operator-example "=1 =2 =3 =4 +[average[]]">> Operators can be combined: diff --git a/editions/tw5.com/tiddlers/filters/average.tid b/editions/tw5.com/tiddlers/filters/average.tid new file mode 100644 index 000000000..36f39be1d --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/average.tid @@ -0,0 +1,13 @@ +caption: average +created: 20210417090137714 +modified: 20210426131553482 +op-input: a [[selection of titles|Title Selection]] +op-output: the arithmetic mean of the input as numbers +op-purpose: treating each input title as a number, compute their arithmetic mean +tags: [[Reducing Mathematics Operators]] [[Filter Operators]] [[Mathematics Operators]] +title: average Operator +type: text/vnd.tiddlywiki + +<<.from-version "5.1.24">> See [[Mathematics Operators]] for an overview. + +<<.operator-examples "average">> diff --git a/editions/tw5.com/tiddlers/filters/examples/average Operator (Examples).tid b/editions/tw5.com/tiddlers/filters/examples/average Operator (Examples).tid new file mode 100644 index 000000000..68fb03694 --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/examples/average Operator (Examples).tid @@ -0,0 +1,10 @@ +created: 20210426130837644 +modified: 20210426131553546 +tags: [[Operator Examples]] [[average Operator]] +title: average Operator (Examples) +type: text/vnd.tiddlywiki + +<<.operator-example 1 "=1 =3 =4 =5 +[average[]]">> + +Note that if there is no input the operator returns `NaN` +<<.operator-example 2 "[tag[NotATiddler]get[price]] +[average[]]">> diff --git a/editions/tw5.com/tiddlers/filters/examples/median Operator (Examples).tid b/editions/tw5.com/tiddlers/filters/examples/median Operator (Examples).tid new file mode 100644 index 000000000..ed76964b3 --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/examples/median Operator (Examples).tid @@ -0,0 +1,10 @@ +created: 20210426131042769 +modified: 20210426131553560 +tags: [[Operator Examples]] [[median Operator]] +title: median Operator (Examples) +type: text/vnd.tiddlywiki + +<<.operator-example 1 "=1 =3 =4 =5 +[median[]]">> + +Note that if there is no input the operator returns `NaN` +<<.operator-example 2 "[title[NotATiddler]get[price]] +[median[]]">> diff --git a/editions/tw5.com/tiddlers/filters/examples/standard-deviation Operator (Examples).tid b/editions/tw5.com/tiddlers/filters/examples/standard-deviation Operator (Examples).tid new file mode 100644 index 000000000..4290287d8 --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/examples/standard-deviation Operator (Examples).tid @@ -0,0 +1,10 @@ +created: 20210426130306824 +modified: 20210426131553553 +tags: [[Operator Examples]] [[standard-deviation Operator]] +title: standard-deviation Operator (Examples) +type: text/vnd.tiddlywiki + +<<.operator-example 1 "=1 =3 =4 =5 +[standard-deviation[]]">> + +Note that if there is no input the operator returns `NaN` +<<.operator-example 2 "[title[NotATiddler]get[price]] +[standard-deviation[]]">> diff --git a/editions/tw5.com/tiddlers/filters/examples/variance Operator (Examples).tid b/editions/tw5.com/tiddlers/filters/examples/variance Operator (Examples).tid new file mode 100644 index 000000000..fd0ad07a8 --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/examples/variance Operator (Examples).tid @@ -0,0 +1,10 @@ +created: 20210426130620777 +modified: 20210426131553522 +tags: [[Operator Examples]] [[variance Operator]] +title: variance Operator (Examples) +type: text/vnd.tiddlywiki + +<<.operator-example 1 "1 3 4 5 +[variance[]]">> + +Note that if there is no input the operator returns `NaN` +<<.operator-example 2 "[title[NotATiddler]is[tiddler]get[price]] +[variance[]]">> diff --git a/editions/tw5.com/tiddlers/filters/median.tid b/editions/tw5.com/tiddlers/filters/median.tid new file mode 100644 index 000000000..da8b6acc4 --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/median.tid @@ -0,0 +1,13 @@ +caption: median +created: 20210417090137714 +modified: 20210426131553507 +op-input: a [[selection of titles|Title Selection]] +op-output: the median of the input numbers +op-purpose: treating each input title as a number, compute their median value +tags: [[Filter Operators]] [[Mathematics Operators]] +title: median Operator +type: text/vnd.tiddlywiki + +<<.from-version "5.1.24">> See [[Mathematics Operators]] for an overview. + +<<.operator-examples "median">> diff --git a/editions/tw5.com/tiddlers/filters/standard-deviation Operator.tid b/editions/tw5.com/tiddlers/filters/standard-deviation Operator.tid new file mode 100644 index 000000000..125c043b1 --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/standard-deviation Operator.tid @@ -0,0 +1,15 @@ +caption: standard-deviation +created: 20210426130150358 +modified: 20210426131553530 +op-input: a [[selection of titles|Title Selection]] +op-output: the standard-deviation of the input as numbers +op-purpose: treating each input title as a number, compute their standard-deviation +tags: [[Reducing Mathematics Operators]] [[Filter Operators]] [[Mathematics Operators]] +title: standard-deviation Operator +type: text/vnd.tiddlywiki + +<<.from-version "5.1.24">> See [[Mathematics Operators]] for an overview. + +<<.tip """ The `standard-deviation` operator treats the input as a complete population and not a sample""">> + +<<.operator-examples "standard-deviation">> diff --git a/editions/tw5.com/tiddlers/filters/variance Operator.tid b/editions/tw5.com/tiddlers/filters/variance Operator.tid new file mode 100644 index 000000000..913eb2943 --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/variance Operator.tid @@ -0,0 +1,15 @@ +caption: variance +created: 20210426130029500 +modified: 20210426131553539 +op-input: a [[selection of titles|Title Selection]] +op-output: the variance of the input as numbers +op-purpose: treating each input title as a number, compute their variance +tags: [[Reducing Mathematics Operators]] [[Filter Operators]] [[Mathematics Operators]] +title: variance Operator +type: text/vnd.tiddlywiki + +<<.from-version "5.1.24">> See [[Mathematics Operators]] for an overview. + +<<.tip """ The `standard-deviation` operator treats the input as a complete population and not a sample""">> + +<<.operator-examples "variance">>