Repository: josdejong/mathjs
Pull request #763 (2016-12-10 11:19:45)

Add median absolute deviation
New PR to merge with develop branch :)


Patch:
diff --git a/HISTORY.md b/HISTORY.md
index 0bb062b61..dd9d5049a 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,12 +1,6 @@
 # History
 
 
-## not yet released, version 3.8.1
-
-- Fixed #762: expression parser failing to invoke a function returned
-  by a function.
-
-
 ## 2016-11-18, version 3.8.0
 
 - Functions `add` and `multiply` now accept more than two arguments. See #739.
diff --git a/lib/expression/parse.js b/lib/expression/parse.js
index 1df3e396d..2f48f952e 100644
--- a/lib/expression/parse.js
+++ b/lib/expression/parse.js
@@ -1166,7 +1166,7 @@ function factory (type, config, load, typed) {
       params = [];
 
       if (token == '(') {
-        if (node.isSymbolNode || node.isAccessorNode || node.isFunctionNode) {
+        if (node.isSymbolNode || node.isAccessorNode) {
           // function invocation like fn(2, 3)
           openParams();
           getToken();
diff --git a/lib/function/statistics/index.js b/lib/function/statistics/index.js
index d3fc2e7f1..1e8197647 100644
--- a/lib/function/statistics/index.js
+++ b/lib/function/statistics/index.js
@@ -1,4 +1,5 @@
 module.exports = [
+  require('./mad'),
   require('./max'),
   require('./mean'),
   require('./median'),
@@ -9,4 +10,4 @@ module.exports = [
   require('./std'),
   require('./sum'),
   require('./var')
-];
\ No newline at end of file
+];
diff --git a/lib/function/statistics/mad.js b/lib/function/statistics/mad.js
new file mode 100644
index 000000000..96e4ef0ba
--- /dev/null
+++ b/lib/function/statistics/mad.js
@@ -0,0 +1,64 @@
+'use strict';
+
+var flatten = require('../../utils/array').flatten;
+
+function factory (type, config, load, typed) {
+  var abs      = load(require('../arithmetic/abs'));
+  var map      = load(require('../matrix/map'));
+  var median   = load(require('../statistics/median'));
+  var subtract = load(require('../arithmetic/subtract'));
+
+  /**
+   * Compute the median absolute deviation of a matrix or a list with values.
+   * The median absolute deviation is defined as the median of the absolute
+   * deviations from the median.
+   *
+   * Syntax:
+   *
+   *     math.mad(a, b, c, ...)
+   *     math.mad(A)
+   *
+   * Examples:
+   *
+   *     math.mad(10, 20, 30);             // returns 10
+   *     math.mad([1, 2, 3]);              // returns 1
+   *     math.mad([[1, 2, 3], [4, 5, 6]]); // returns 1.5
+   *
+   * See also:
+   *
+   *     median, abs, map, subtract
+   *
+   * @param {Array | Matrix} array
+   *                        A single matrix or multiple scalar values.
+   * @return {*} The median absolute deviation.
+   */
+  var mad = typed('mad', {
+    // mad([a, b, c, d, ...])
+    'Array | Matrix': _mad,
+
+    // mad(a, b, c, d, ...)
+    '...': function (args) {
+      return _mad(args);
+    }
+  });
+
+  mad.toTex = undefined; // use default template
+
+  return mad;
+
+  function _mad(array) {
+    array = flatten(array.valueOf());
+
+    if (array.length === 0) {
+      throw new Error('Cannot calculate median absolute deviation of an empty array');
+    }
+
+    var med = median(array);
+    return median(map(array, function (value) {
+      return abs(subtract(value, med));
+    }));
+  }
+}
+
+exports.name = 'mad';
+exports.factory = factory;


Test patch:
diff --git a/test/expression/parse.test.js b/test/expression/parse.test.js
index c13691a4a..872ea0d5f 100644
--- a/test/expression/parse.test.js
+++ b/test/expression/parse.test.js
@@ -769,23 +769,6 @@ describe('parse', function() {
       assert.deepEqual(parseAndEval('obj["fn"](2)', scope), 4);
     });
 
-    it('should invoke a function returned by a function', function () {
-      var scope = {
-        theAnswer: function () {
-          return function () {
-            return 42;
-          };
-        },
-        partialAdd: function (a) {
-          return function (b) {
-            return a + b;
-          };
-        }
-      };
-      assert.deepEqual(parseAndEval('theAnswer()()', scope), 42);
-      assert.deepEqual(parseAndEval('partialAdd(2)(3)', scope), 5);
-    });
-
     it('should invoke a function on an object with the right context', function () {
       approx.equal(parseAndEval('(2.54 cm).toNumeric("inch")'), 1);
       assert.deepEqual(parseAndEval('bignumber(2).plus(3)'), math.bignumber(5));
diff --git a/test/function/statistics/mad.test.js b/test/function/statistics/mad.test.js
new file mode 100644
index 000000000..2f37599ea
--- /dev/null
+++ b/test/function/statistics/mad.test.js
@@ -0,0 +1,60 @@
+var assert = require('assert');
+var approx = require('../../../tools/approx');
+var math = require('../../../index');
+var BigNumber = math.type.BigNumber;
+var DenseMatrix = math.type.DenseMatrix;
+var mad = math.mad;
+
+describe('mad', function() {
+
+  it('should return the median absolute deviation of numbers', function() {
+    assert.equal(mad(10), 0);
+    assert.equal(mad(4,6,8), 2);
+    assert.equal(mad(1, 10, 20, 30), 9.5);
+  });
+
+  it('should return the median absolute deviation of big numbers', function() {
+    assert.deepEqual(mad(new BigNumber(4),new BigNumber(6),new BigNumber(8)),
+      new BigNumber(2));
+  });
+
+  it('should return the median absolute deviation from an array', function() {
+    assert.equal(mad([10]), 0);
+    assert.equal(mad([4,6,8]), 2);
+    assert.equal(mad([1, 10, 20, 30]), 9.5);
+  });
+
+  it('should return the median absolute deviation from an 1d matrix', function() {
+    assert.equal(mad(new DenseMatrix([10])), 0);
+    assert.equal(mad(new DenseMatrix([4,6,8])), 2);
+    assert.equal(mad(new DenseMatrix([1, 10, 20, 30])), 9.5);
+  });
+
+  it('should return the median absolute deviation element from a 2d array', function() {
+    assert.deepEqual(mad([
+      [2,4,6],
+      [1,3,5]
+    ]), 1.5);
+  });
+
+  it('should return the median absolute deviation element from a 2d matrix', function() {
+    assert.deepEqual(mad(new DenseMatrix([
+      [2,4,6],
+      [1,3,5]
+    ])), 1.5);
+  });
+
+  it('should throw an error if called with invalid number of arguments', function() {
+    assert.throws(function() {mad()});
+  });
+
+  it('should throw an error if called with an empty array', function() {
+    assert.throws(function() {mad([])});
+  });
+
+  it('should LaTeX mad', function () {
+    var expression = math.parse('mad(1,2,3)');
+    assert.equal(expression.toTex(), '\\mathrm{mad}\\left(1,2,3\\right)');
+  });
+
+});