/** * @license Apache-2.0 * * Copyright (c) 2018 The Stdlib Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; // MODULES // var floor = require( '@stdlib/math/base/special/floor' ); var abs = require( '@stdlib/math/base/special/abs' ); var max = require( '@stdlib/math/base/special/max' ); var min = require( '@stdlib/math/base/special/min' ); var pow = require( '@stdlib/math/base/special/pow' ); var lowest = require( './lowest.js' ); // FUNCTIONS // /** * Comparator function used to sort values in ascending order. * * @private * @param {number} a - first value * @param {number} b - second value * @returns {number} difference between `a` and `b` */ function ascending( a, b ) { return a - b; } // MAIN // /** * Locally-weighted polynomial regression via the LOWESS algorithm. * * ## Method * * - Calculates fitted values using a nearest neighbor function and robust locally weighted regression of degree one with the tricube weight function. * * ## References * * - Cleveland, William S. 1979. "Robust Locally and Smoothing Weighted Regression Scatterplots." _Journal of the American Statistical Association_ 74 (368): 829–36. doi:[10.1080/01621459.1979.10481038](https://doi.org/10.1080/01621459.1979.10481038). * - Cleveland, William S. 1981. "Lowess: A program for smoothing scatterplots by robust locally weighted regression." _American Statistician_ 35 (1): 54–55. doi:[10.2307/2683591](https://doi.org/10.2307/2683591). * * @private * @param {NumericArray} x - ordered x-axis values (abscissa values) * @param {NumericArray} y - corresponding y-axis values (ordinate values) * @param {PositiveInteger} n - number of observations * @param {PositiveNumber} f - smoother span (proportion of points which influence smoothing at each value) * @param {NonNegativeInteger} nsteps - number of iterations in the robust fit * @param {PositiveNumber} delta - nonnegative parameter which may be used to reduce the number of computations * @returns {Object} sorted x-values and fitted values */ function lowess( x, y, n, f, nsteps, delta ) { var nright; var denom; var nleft; var alpha; var cmad; var iter; var last; var cut; var res; var m1; var m2; var ns; var c1; var c9; var d1; var d2; var rw; var ys; var i; var j; var r; if ( n < 2 ) { return y; } ys = new Array( n ); res = new Array( n ); rw = new Array( n ); // Use at least two and at most n points: ns = max( min( floor( f * n ), n ), 2 ); // Robustness iterations: for ( iter = 1; iter <= nsteps + 1; iter++ ) { nleft = 0; nright = ns - 1; last = -1; // index of previously estimated point i = 0; // index of current point do { while ( nright < n - 1 ) { // Move nleft, nright to the right if radius decreases: d1 = x[ i ] - x[ nleft ]; d2 = x[ nright + 1 ] - x[ i ]; // If d1 <= d2 with x[nright+1] == x[nright], lowest fixes: if ( d1 <= d2 ) { break; } // Radius will not decrease by a move to the right... nleft += 1; nright += 1; } // Fitted value at x[ i ]: ys[ i ] = lowest( x, y, n, i, nleft, nright, res, (iter > 1), rw ); if ( last < i - 1 ) { denom = x[ i ] - x[ last ]; for ( j = last + 1; j < i; j++ ) { alpha = ( x[ j ] - x[ last ] ) / denom; ys[ j ] = ( alpha*ys[ i ] ) + ( (1.0-alpha) * ys[ last ] ); } } last = i; cut = x[ last ] + delta; for ( i = last + 1; i < n; i++ ) { if ( x[ i ] > cut ) { break; } if ( x[ i ] === x[ last ] ) { ys[ i ] = ys[ last ]; last = i; } } i = max( last + 1, i - 1 ); } while ( last < n - 1 ); // Calculate Residuals: for ( i = 0; i < n; i++ ) { res[ i ] = y[ i ] - ys[ i ]; } if ( iter > nsteps ) { break; // Compute robustness weights except last time... } for ( i = 0; i < n; i++ ) { rw[i] = abs( res[i] ); } rw.sort( ascending ); m1 = floor( n / 2.0 ); m2 = n - m1 - 1.0; cmad = 3.0 * ( rw[m1] + rw[m2] ); c9 = 0.999 * cmad; c1 = 0.001 * cmad; for ( i = 0; i < n; i++ ) { r = abs( res[i] ); if ( r <= c1 ) { rw[ i ] = 1.0; // near 0, avoid underflow } else if ( r > c9 ) { rw[ i ] = 0.0; // near 1, avoid underflow } else { rw[ i ] = pow( 1.0 - pow( r / cmad, 2.0 ), 2.0 ); } } } return { 'x': x, 'y': ys }; } // EXPORTS // module.exports = lowess;