main.js (4256B)
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 isNumberArray = require( '@stdlib/assert/is-number-array' ).primitives; 24 var isTypedArrayLike = require( '@stdlib/assert/is-typed-array-like' ); 25 var range = require( './../../base/range' ); 26 var lowess = require( './lowess.js' ); 27 var validate = require( './validate.js' ); 28 29 30 // FUNCTIONS // 31 32 /** 33 * Comparator function used to sort (x,y)-pairs in ascending order by the first coordinate. 34 * 35 * @private 36 * @param {Array} a - first pair 37 * @param {Array} b - second pair 38 * @returns {number} difference between `a` and `b` 39 */ 40 function ascending( a, b ) { 41 return a[ 0 ] - b[ 0 ]; 42 } 43 44 45 // MAIN // 46 47 /** 48 * Locally-weighted polynomial regression via the LOWESS algorithm. 49 * 50 * ## References 51 * 52 * - 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). 53 * - 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). 54 * 55 * @param {NumericArray} x - ordered x-axis values (abscissa values) 56 * @param {NumericArray} y - corresponding y-axis values (ordinate values) 57 * @param {Options} options - function options 58 * @param {PositiveNumber} [options.f=2/3] - smoother span (proportion of points which influence smoothing at each value) 59 * @param {integer} [options.nsteps=3] - number of iterations in the robust fit (fewer iterations translates to faster function execution) 60 * @param {NonNegativeNumber} [options.delta] - nonnegative parameter which may be used to reduce the number of computations 61 * @param {boolean} [options.sorted=false] - boolean indicating if the input array `x` is already in sorted order 62 * @throws {TypeError} first argument must be a numeric array 63 * @throws {TypeError} second argument must be a numeric array 64 * @throws {Error} arguments `x` and `y` must have the same length 65 * @returns {Object} ordered x-values and fitted values 66 */ 67 function main( x, y, options ) { 68 var nsteps; 69 var delta; 70 var opts; 71 var err; 72 var xy; 73 var f; 74 var i; 75 var n; 76 var r; 77 78 if ( !isTypedArrayLike( x ) && !isNumberArray( x ) ) { 79 throw new TypeError( 'invalid argument. First argument `x` must be a numeric array. Value: `' + x + '`.' ); 80 } 81 if ( !isTypedArrayLike( y ) && !isNumberArray( y ) ) { 82 throw new TypeError( 'invalid argument. Second argument `y` must be a numeric array. Value: `' + y + '`.' ); 83 } 84 n = x.length; 85 if ( y.length !== n ) { 86 throw new Error( 'invalid arguments. Arguments `x` and `y` must have the same length.' ); 87 } 88 opts = {}; 89 if ( arguments.length > 2 ) { 90 err = validate( opts, options ); 91 if ( err ) { 92 throw err; 93 } 94 } 95 // Input data has to be sorted: 96 if ( opts.sorted !== true ) { 97 // Copy to prevent mutation and sort by x: 98 xy = new Array( n ); 99 for ( i = 0; i < n; i++ ) { 100 xy[ i ] = [ x[ i ], y[ i ] ]; 101 } 102 xy.sort( ascending ); // TODO: Revisit once we have function for sorting multiple arrays by the elements of one of the arrays 103 x = new Array( n ); 104 y = new Array( n ); 105 for ( i = 0; i < n; i++ ) { 106 x[ i ] = xy[ i ][ 0 ]; 107 y[ i ] = xy[ i ][ 1 ]; 108 } 109 } 110 if ( opts.nsteps === void 0 ) { 111 nsteps = 3; 112 } else { 113 nsteps = opts.nsteps; 114 } 115 if ( opts.f === void 0 ) { 116 f = 2.0/3.0; 117 } else { 118 f = opts.f; 119 } 120 if ( opts.delta === void 0 ) { 121 r = range( n, x, 1 ); 122 delta = 0.01 * r; 123 } else { 124 delta = opts.delta; 125 } 126 return lowess( x, y, n, f, nsteps, delta ); 127 } 128 129 130 // EXPORTS // 131 132 module.exports = main;