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;