time-to-botec

Benchmark sampling in different programming languages
Log | Files | Refs | README

lowess.js (4863B)


      1 /**
      2 * @license Apache-2.0
      3 *
      4 * Copyright (c) 2018 The Stdlib Authors.
      5 *
      6 * Licensed under the Apache License, Version 2.0 (the "License");
      7 * you may not use this file except in compliance with the License.
      8 * You may obtain a copy of the License at
      9 *
     10 *    http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing, software
     13 * distributed under the License is distributed on an "AS IS" BASIS,
     14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15 * See the License for the specific language governing permissions and
     16 * limitations under the License.
     17 */
     18 
     19 'use strict';
     20 
     21 // MODULES //
     22 
     23 var floor = require( '@stdlib/math/base/special/floor' );
     24 var abs = require( '@stdlib/math/base/special/abs' );
     25 var max = require( '@stdlib/math/base/special/max' );
     26 var min = require( '@stdlib/math/base/special/min' );
     27 var pow = require( '@stdlib/math/base/special/pow' );
     28 var lowest = require( './lowest.js' );
     29 
     30 
     31 // FUNCTIONS //
     32 
     33 /**
     34 * Comparator function used to sort values in ascending order.
     35 *
     36 * @private
     37 * @param {number} a - first value
     38 * @param {number} b - second value
     39 * @returns {number} difference between `a` and `b`
     40 */
     41 function ascending( a, b ) {
     42 	return a - b;
     43 }
     44 
     45 
     46 // MAIN //
     47 
     48 /**
     49 * Locally-weighted polynomial regression via the LOWESS algorithm.
     50 *
     51 * ## Method
     52 *
     53 * -   Calculates fitted values using a nearest neighbor function and robust locally weighted regression of degree one with the tricube weight function.
     54 *
     55 * ## References
     56 *
     57 * -   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).
     58 * -   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).
     59 *
     60 * @private
     61 * @param {NumericArray} x - ordered x-axis values (abscissa values)
     62 * @param {NumericArray} y - corresponding y-axis values (ordinate values)
     63 * @param {PositiveInteger} n - number of observations
     64 * @param {PositiveNumber} f - smoother span (proportion of points which influence smoothing at each value)
     65 * @param {NonNegativeInteger} nsteps - number of iterations in the robust fit
     66 * @param {PositiveNumber} delta - nonnegative parameter which may be used to reduce the number of computations
     67 * @returns {Object} sorted x-values and fitted values
     68 */
     69 function lowess( x, y, n, f, nsteps, delta ) {
     70 	var nright;
     71 	var denom;
     72 	var nleft;
     73 	var alpha;
     74 	var cmad;
     75 	var iter;
     76 	var last;
     77 	var cut;
     78 	var res;
     79 	var m1;
     80 	var m2;
     81 	var ns;
     82 	var c1;
     83 	var c9;
     84 	var d1;
     85 	var d2;
     86 	var rw;
     87 	var ys;
     88 	var i;
     89 	var j;
     90 	var r;
     91 
     92 	if ( n < 2 ) {
     93 		return y;
     94 	}
     95 	ys = new Array( n );
     96 	res = new Array( n );
     97 	rw = new Array( n );
     98 
     99 	// Use at least two and at most n points:
    100 	ns = max( min( floor( f * n ), n ), 2 );
    101 
    102 	// Robustness iterations:
    103 	for ( iter = 1; iter <= nsteps + 1; iter++ ) {
    104 		nleft = 0;
    105 		nright = ns - 1;
    106 		last = -1; // index of previously estimated point
    107 		i = 0; // index of current point
    108 		do {
    109 			while ( nright < n - 1 ) {
    110 				// Move nleft, nright to the right if radius decreases:
    111 				d1 = x[ i ] - x[ nleft ];
    112 				d2 = x[ nright + 1 ] - x[ i ];
    113 
    114 				// If d1 <= d2 with x[nright+1] == x[nright], lowest fixes:
    115 				if ( d1 <= d2 ) {
    116 					break;
    117 				}
    118 				// Radius will not decrease by a move to the right...
    119 				nleft += 1;
    120 				nright += 1;
    121 			}
    122 			// Fitted value at x[ i ]:
    123 			ys[ i ] = lowest( x, y, n, i, nleft, nright, res, (iter > 1), rw );
    124 
    125 			if ( last < i - 1 ) {
    126 				denom = x[ i ] - x[ last ];
    127 				for ( j = last + 1; j < i; j++ ) {
    128 					alpha = ( x[ j ] - x[ last ] ) / denom;
    129 					ys[ j ] = ( alpha*ys[ i ] ) + ( (1.0-alpha) * ys[ last ] );
    130 				}
    131 			}
    132 			last = i;
    133 			cut = x[ last ] + delta;
    134 			for ( i = last + 1; i < n; i++ ) {
    135 				if ( x[ i ] > cut ) {
    136 					break;
    137 				}
    138 				if ( x[ i ] === x[ last ] ) {
    139 					ys[ i ] = ys[ last ];
    140 					last = i;
    141 				}
    142 			}
    143 			i = max( last + 1, i - 1 );
    144 		} while ( last < n - 1 );
    145 
    146 		// Calculate Residuals:
    147 		for ( i = 0; i < n; i++ ) {
    148 			res[ i ] = y[ i ] - ys[ i ];
    149 		}
    150 		if ( iter > nsteps ) {
    151 			break; // Compute robustness weights except last time...
    152 		}
    153 		for ( i = 0; i < n; i++ ) {
    154 			rw[i] = abs( res[i] );
    155 		}
    156 		rw.sort( ascending );
    157 		m1 = floor( n / 2.0 );
    158 		m2 = n - m1 - 1.0;
    159 		cmad = 3.0 * ( rw[m1] + rw[m2] );
    160 		c9 = 0.999 * cmad;
    161 		c1 = 0.001 * cmad;
    162 		for ( i = 0; i < n; i++ ) {
    163 			r = abs( res[i] );
    164 			if ( r <= c1 ) {
    165 				rw[ i ] = 1.0; // near 0, avoid underflow
    166 			}
    167 			else if ( r > c9 ) {
    168 				rw[ i ] = 0.0;  // near 1, avoid underflow
    169 			}
    170 			else {
    171 				rw[ i ] = pow( 1.0 - pow( r / cmad, 2.0 ), 2.0 );
    172 			}
    173 		}
    174 	}
    175 	return {
    176 		'x': x,
    177 		'y': ys
    178 	};
    179 }
    180 
    181 
    182 // EXPORTS //
    183 
    184 module.exports = lowess;