(function (global, factory) { if (typeof define === "function" && define.amd) { define(['module'], factory); } else if (typeof exports !== "undefined") { factory(module); } else { var mod = { exports: {} }; factory(mod); global.regression = mod.exports; } })(this, function (module) { 'use strict'; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } var DEFAULT_OPTIONS = { order: 2, precision: 2, period: null }; /** * Determine the coefficient of determination (r^2) of a fit from the observations * and predictions. * * @param {Array>} data - Pairs of observed x-y values * @param {Array>} results - Pairs of observed predicted x-y values * * @return {number} - The r^2 value, or NaN if one cannot be calculated. */ function determinationCoefficient(data, results) { var predictions = []; var observations = []; data.forEach(function (d, i) { if (d[1] !== null) { observations.push(d); predictions.push(results[i]); } }); var sum = observations.reduce(function (a, observation) { return a + observation[1]; }, 0); var mean = sum / observations.length; var ssyy = observations.reduce(function (a, observation) { var difference = observation[1] - mean; return a + difference * difference; }, 0); var sse = observations.reduce(function (accum, observation, index) { var prediction = predictions[index]; var residual = observation[1] - prediction[1]; return accum + residual * residual; }, 0); return 1 - sse / ssyy; } /** * Determine the solution of a system of linear equations A * x = b using * Gaussian elimination. * * @param {Array>} input - A 2-d matrix of data in row-major form [ A | b ] * @param {number} order - How many degrees to solve for * * @return {Array} - Vector of normalized solution coefficients matrix (x) */ function gaussianElimination(input, order) { var matrix = input; var n = input.length - 1; var coefficients = [order]; for (var i = 0; i < n; i++) { var maxrow = i; for (var j = i + 1; j < n; j++) { if (Math.abs(matrix[i][j]) > Math.abs(matrix[i][maxrow])) { maxrow = j; } } for (var k = i; k < n + 1; k++) { var tmp = matrix[k][i]; matrix[k][i] = matrix[k][maxrow]; matrix[k][maxrow] = tmp; } for (var _j = i + 1; _j < n; _j++) { for (var _k = n; _k >= i; _k--) { matrix[_k][_j] -= matrix[_k][i] * matrix[i][_j] / matrix[i][i]; } } } for (var _j2 = n - 1; _j2 >= 0; _j2--) { var total = 0; for (var _k2 = _j2 + 1; _k2 < n; _k2++) { total += matrix[_k2][_j2] * coefficients[_k2]; } coefficients[_j2] = (matrix[n][_j2] - total) / matrix[_j2][_j2]; } return coefficients; } /** * Round a number to a precision, specificed in number of decimal places * * @param {number} number - The number to round * @param {number} precision - The number of decimal places to round to: * > 0 means decimals, < 0 means powers of 10 * * * @return {numbr} - The number, rounded */ function round(number, precision) { var factor = Math.pow(10, precision); return Math.round(number * factor) / factor; } /** * The set of all fitting methods * * @namespace */ var methods = { linear: function linear(data, options) { var sum = [0, 0, 0, 0, 0]; var len = 0; for (var n = 0; n < data.length; n++) { if (data[n][1] !== null) { len++; sum[0] += data[n][0]; sum[1] += data[n][1]; sum[2] += data[n][0] * data[n][0]; sum[3] += data[n][0] * data[n][1]; sum[4] += data[n][1] * data[n][1]; } } var run = len * sum[2] - sum[0] * sum[0]; var rise = len * sum[3] - sum[0] * sum[1]; var gradient = run === 0 ? 0 : round(rise / run, options.precision); var intercept = round(sum[1] / len - gradient * sum[0] / len, options.precision); var predict = function predict(x) { return [round(x, options.precision), round(gradient * x + intercept, options.precision)]; }; var points = data.map(function (point) { return predict(point[0]); }); return { points: points, predict: predict, equation: [gradient, intercept], r2: round(determinationCoefficient(data, points), options.precision), string: intercept === 0 ? 'y = ' + gradient + 'x' : 'y = ' + gradient + 'x + ' + intercept }; }, exponential: function exponential(data, options) { var sum = [0, 0, 0, 0, 0, 0]; for (var n = 0; n < data.length; n++) { if (data[n][1] !== null) { sum[0] += data[n][0]; sum[1] += data[n][1]; sum[2] += data[n][0] * data[n][0] * data[n][1]; sum[3] += data[n][1] * Math.log(data[n][1]); sum[4] += data[n][0] * data[n][1] * Math.log(data[n][1]); sum[5] += data[n][0] * data[n][1]; } } var denominator = sum[1] * sum[2] - sum[5] * sum[5]; var a = Math.exp((sum[2] * sum[3] - sum[5] * sum[4]) / denominator); var b = (sum[1] * sum[4] - sum[5] * sum[3]) / denominator; var coeffA = round(a, options.precision); var coeffB = round(b, options.precision); var predict = function predict(x) { return [round(x, options.precision), round(coeffA * Math.exp(coeffB * x), options.precision)]; }; var points = data.map(function (point) { return predict(point[0]); }); return { points: points, predict: predict, equation: [coeffA, coeffB], string: 'y = ' + coeffA + 'e^(' + coeffB + 'x)', r2: round(determinationCoefficient(data, points), options.precision) }; }, logarithmic: function logarithmic(data, options) { var sum = [0, 0, 0, 0]; var len = data.length; for (var n = 0; n < len; n++) { if (data[n][1] !== null) { sum[0] += Math.log(data[n][0]); sum[1] += data[n][1] * Math.log(data[n][0]); sum[2] += data[n][1]; sum[3] += Math.pow(Math.log(data[n][0]), 2); } } var a = (len * sum[1] - sum[2] * sum[0]) / (len * sum[3] - sum[0] * sum[0]); var coeffB = round(a, options.precision); var coeffA = round((sum[2] - coeffB * sum[0]) / len, options.precision); var predict = function predict(x) { return [round(x, options.precision), round(round(coeffA + coeffB * Math.log(x), options.precision), options.precision)]; }; var points = data.map(function (point) { return predict(point[0]); }); return { points: points, predict: predict, equation: [coeffA, coeffB], string: 'y = ' + coeffA + ' + ' + coeffB + ' ln(x)', r2: round(determinationCoefficient(data, points), options.precision) }; }, power: function power(data, options) { var sum = [0, 0, 0, 0, 0]; var len = data.length; for (var n = 0; n < len; n++) { if (data[n][1] !== null) { sum[0] += Math.log(data[n][0]); sum[1] += Math.log(data[n][1]) * Math.log(data[n][0]); sum[2] += Math.log(data[n][1]); sum[3] += Math.pow(Math.log(data[n][0]), 2); } } var b = (len * sum[1] - sum[0] * sum[2]) / (len * sum[3] - Math.pow(sum[0], 2)); var a = (sum[2] - b * sum[0]) / len; var coeffA = round(Math.exp(a), options.precision); var coeffB = round(b, options.precision); var predict = function predict(x) { return [round(x, options.precision), round(round(coeffA * Math.pow(x, coeffB), options.precision), options.precision)]; }; var points = data.map(function (point) { return predict(point[0]); }); return { points: points, predict: predict, equation: [coeffA, coeffB], string: 'y = ' + coeffA + 'x^' + coeffB, r2: round(determinationCoefficient(data, points), options.precision) }; }, polynomial: function polynomial(data, options) { var lhs = []; var rhs = []; var a = 0; var b = 0; var len = data.length; var k = options.order + 1; for (var i = 0; i < k; i++) { for (var l = 0; l < len; l++) { if (data[l][1] !== null) { a += Math.pow(data[l][0], i) * data[l][1]; } } lhs.push(a); a = 0; var c = []; for (var j = 0; j < k; j++) { for (var _l = 0; _l < len; _l++) { if (data[_l][1] !== null) { b += Math.pow(data[_l][0], i + j); } } c.push(b); b = 0; } rhs.push(c); } rhs.push(lhs); var coefficients = gaussianElimination(rhs, k).map(function (v) { return round(v, options.precision); }); var predict = function predict(x) { return [round(x, options.precision), round(coefficients.reduce(function (sum, coeff, power) { return sum + coeff * Math.pow(x, power); }, 0), options.precision)]; }; var points = data.map(function (point) { return predict(point[0]); }); var string = 'y = '; for (var _i = coefficients.length - 1; _i >= 0; _i--) { if (_i > 1) { string += coefficients[_i] + 'x^' + _i + ' + '; } else if (_i === 1) { string += coefficients[_i] + 'x + '; } else { string += coefficients[_i]; } } return { string: string, points: points, predict: predict, equation: [].concat(_toConsumableArray(coefficients)).reverse(), r2: round(determinationCoefficient(data, points), options.precision) }; } }; function createWrapper() { var reduce = function reduce(accumulator, name) { return _extends({ _round: round }, accumulator, _defineProperty({}, name, function (data, supplied) { return methods[name](data, _extends({}, DEFAULT_OPTIONS, supplied)); })); }; return Object.keys(methods).reduce(reduce, {}); } module.exports = createWrapper(); });