UNPKG

loess

Version:

JavaScript implementation of the Locally-Weighted Regression package originally written in C by Cleveland, Grosse and Shyu (1992)

161 lines (125 loc) 6.1 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _mathjs = require('mathjs'); var _mathjs2 = _interopRequireDefault(_mathjs); var _lodash = require('lodash.sortby'); var _lodash2 = _interopRequireDefault(_lodash); var _gaussian = require('gaussian'); var _gaussian2 = _interopRequireDefault(_gaussian); var _inputsValidation = require('./inputsValidation'); var _helpers = require('./helpers'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } // import data from '../data/gas.json' var Loess = function () { function Loess(data) { var _this = this; var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; _classCallCheck(this, Loess); Object.assign(this, (0, _inputsValidation.validateModel)(data, options)); if (this.options.normalize) this.normalization = this.x.map(_helpers.normalize); this.expandedX = (0, _helpers.polynomialExpansion)(this.x, this.options.degree); var normalized = this.normalization ? this.x.map(function (x, idx) { return _this.normalization[idx](x); }) : this.x; this.transposedX = (0, _helpers.transpose)(normalized); } _createClass(Loess, [{ key: 'predict', value: function predict(data) { var _this2 = this; var _validatePredict$bind = _inputsValidation.validatePredict.bind(this)(data); var x_new = _validatePredict$bind.x_new; var n = _validatePredict$bind.n; var expandedX = (0, _helpers.polynomialExpansion)(x_new, this.options.degree); var normalized = this.normalization ? x_new.map(function (x, idx) { return _this2.normalization[idx](x); }) : x_new; var distM = (0, _helpers.distMatrix)((0, _helpers.transpose)(normalized), this.transposedX); var weightM = (0, _helpers.weightMatrix)(distM, this.w, this.bandwidth); var fitted = void 0, residuals = void 0, weights = void 0, betas = void 0; function iterate(wt) { var _this3 = this; fitted = []; residuals = []; betas = []; weights = _mathjs2.default.dotMultiply(wt, weightM); (0, _helpers.transpose)(expandedX).forEach(function (point, idx) { var fit = (0, _helpers.weightedLeastSquare)(_this3.expandedX, _this3.y, weights[idx]); if (fit.error) { var mle = _mathjs2.default.multiply(_this3.y, weights[idx]) / _mathjs2.default.sum(weights[idx]); fit.beta = _mathjs2.default.zeros(_this3.expandedX.length).set([0], mle); fit.residual = _mathjs2.default.subtract(_this3.y, mle); } fitted.push(_mathjs2.default.squeeze(_mathjs2.default.multiply(point, fit.beta))); residuals.push(fit.residual); betas.push(fit.beta.toArray()); var median = _mathjs2.default.median(_mathjs2.default.abs(fit.residual)); wt[idx] = fit.residual.map(function (r) { return (0, _helpers.weightFunc)(r, 6 * median, 2); }); }); } var robustWeights = Array(n).fill(_mathjs2.default.ones(this.n)); for (var iter = 0; iter < this.options.iterations; iter++) { iterate.bind(this)(robustWeights); }var output = { fitted: fitted, betas: betas, weights: weights }; if (this.options.band) { (function () { var z = (0, _gaussian2.default)(0, 1).ppf(1 - (1 - _this2.options.band) / 2); var halfwidth = weights.map(function (weight, idx) { var V1 = _mathjs2.default.sum(weight); var V2 = _mathjs2.default.multiply(weight, weight); var intervalEstimate = Math.sqrt(_mathjs2.default.multiply(_mathjs2.default.square(residuals[idx]), weight) / (V1 - V2 / V1)); return intervalEstimate * z; }); Object.assign(output, { halfwidth: halfwidth }); })(); } return output; } }, { key: 'grid', value: function grid(cuts) { var _this4 = this; _inputsValidation.validateGrid.bind(this)(cuts); var x_new = []; var x_cuts = []; this.x.forEach(function (x, idx) { var x_sorted = (0, _lodash2.default)(x); var x_min = x_sorted[0]; var x_max = x_sorted[_this4.n - 1]; var width = (x_max - x_min) / (cuts[idx] - 1); x_cuts.push([]); for (var i = 0; i < cuts[idx]; i++) { x_cuts[idx].push(x_min + i * width); }var repeats = 1; var copies = 1; for (var _i = idx - 1; _i >= 0; _i--) { repeats *= cuts[_i]; }for (var _i2 = idx + 1; _i2 < _this4.d; _i2++) { copies *= cuts[_i2]; }x_new.push([]); for (var _i3 = 0; _i3 < repeats; _i3++) { x_new[idx] = x_new[idx].concat(x_cuts[idx].reduce(function (acc, cut) { return acc.concat(Array(copies).fill(cut)); }, [])); } }); var data = { x: x_new[0], x_cut: x_cuts[0] }; if (this.d > 1) Object.assign(data, { x2: x_new[1], x_cut2: x_cuts[1] }); return data; } }]); return Loess; }(); // const w = data.NOx.map(() => Math.random() * 10) // const fit = new Loess({y: data.NOx, x: data.E, w}, {span: 0.8, band: 0.8, degree: 'quadratic'}) // console.log(JSON.stringify(fit.predict(fit.grid([30])))) exports.default = Loess;