All files / lowess/lib main.js

51.87% Statements 69/133
100% Branches 1/1
0% Functions 0/2
51.87% Lines 69/133

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 1341x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x       1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                                                                                                                           1x 1x 1x 1x 1x  
/**
* @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 isNumberArray = require( '@stdlib/assert/is-number-array' ).primitives;
var isTypedArrayLike = require( '@stdlib/assert/is-typed-array-like' );
var range = require( '@stdlib/stats/strided/range' );
var format = require( '@stdlib/string/format' );
var lowess = require( './lowess.js' );
var validate = require( './validate.js' );
 
 
// FUNCTIONS //
 
/**
* Comparator function used to sort (x,y)-pairs in ascending order by the first coordinate.
*
* @private
* @param {Array} a - first pair
* @param {Array} b - second pair
* @returns {number} difference between `a` and `b`
*/
function ascending( a, b ) {
	return a[ 0 ] - b[ 0 ];
}
 
 
// MAIN //
 
/**
* Locally-weighted polynomial regression via the LOWESS algorithm.
*
* ## 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).
*
* @param {NumericArray} x - ordered x-axis values (abscissa values)
* @param {NumericArray} y - corresponding y-axis values (ordinate values)
* @param {Options} options - function options
* @param {PositiveNumber} [options.f=2/3] - smoother span (proportion of points which influence smoothing at each value)
* @param {integer} [options.nsteps=3] - number of iterations in the robust fit (fewer iterations translates to faster function execution)
* @param {NonNegativeNumber} [options.delta] - nonnegative parameter which may be used to reduce the number of computations
* @param {boolean} [options.sorted=false] - boolean indicating if the input array `x` is already in sorted order
* @throws {TypeError} first argument must be a numeric array
* @throws {TypeError} second argument must be a numeric array
* @throws {RangeError} first and second arguments must have the same length
* @returns {Object} ordered x-values and fitted values
*/
function main( x, y, options ) {
	var nsteps;
	var delta;
	var opts;
	var err;
	var xy;
	var f;
	var i;
	var n;
	var r;

	if ( !isTypedArrayLike( x ) && !isNumberArray( x ) ) {
		throw new TypeError( format( 'invalid argument. First argument must be a numeric array. Value: `%s`.', x ) );
	}
	if ( !isTypedArrayLike( y ) && !isNumberArray( y ) ) {
		throw new TypeError( format( 'invalid argument. Second argument must be a numeric array. Value: `%s`.', y ) );
	}
	n = x.length;
	if ( y.length !== n ) {
		throw new RangeError( 'invalid arguments. First and second arguments must have the same length.' );
	}
	opts = {};
	if ( arguments.length > 2 ) {
		err = validate( opts, options );
		if ( err ) {
			throw err;
		}
	}
	// Input data has to be sorted:
	if ( opts.sorted !== true ) {
		// Copy to prevent mutation and sort by x:
		xy = new Array( n ); // eslint-disable-line stdlib/no-new-array
		for ( i = 0; i < n; i++ ) {
			xy[ i ] = [ x[ i ], y[ i ] ];
		}
		xy.sort( ascending ); // TODO: Revisit once we have function for sorting multiple arrays by the elements of one of the arrays
		x = new Array( n ); // eslint-disable-line stdlib/no-new-array
		y = new Array( n ); // eslint-disable-line stdlib/no-new-array
		for ( i = 0; i < n; i++ ) {
			x[ i ] = xy[ i ][ 0 ];
			y[ i ] = xy[ i ][ 1 ];
		}
	}
	if ( opts.nsteps === void 0 ) {
		nsteps = 3;
	} else {
		nsteps = opts.nsteps;
	}
	if ( opts.f === void 0 ) {
		f = 2.0/3.0;
	} else {
		f = opts.f;
	}
	if ( opts.delta === void 0 ) {
		r = range( n, x, 1 );
		delta = 0.01 * r;
	} else {
		delta = opts.delta;
	}
	return lowess( x, y, n, f, nsteps, delta );
}
 
 
// EXPORTS //
 
module.exports = main;