185 lines
4.7 KiB
JavaScript
185 lines
4.7 KiB
JavaScript
|
/**
|
|||
|
* @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;
|