time-to-botec

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

main.js (8401B)


      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 hasOwnProp = require( '@stdlib/assert/has-own-property' );
     24 var isObject = require( '@stdlib/assert/is-plain-object' );
     25 var isPositiveInteger = require( '@stdlib/assert/is-positive-integer' ).isPrimitive;
     26 var isBoolean = require( '@stdlib/assert/is-boolean' ).isPrimitive;
     27 var incrminmax = require( './../../../incr/minmax' );
     28 var incrmeanstdev = require( './../../../incr/meanstdev' );
     29 var copy = require( '@stdlib/utils/copy' );
     30 var setReadOnly = require( '@stdlib/utils/define-read-only-property' );
     31 var setReadOnlyAccessor = require( '@stdlib/utils/define-read-only-accessor' );
     32 var max = require( '@stdlib/math/base/special/max' );
     33 var sqrt = require( '@stdlib/math/base/special/sqrt' );
     34 var roundn = require( '@stdlib/math/base/special/roundn' );
     35 var tQuantile = require( './../../../base/dists/t/quantile' );
     36 var validate = require( './validate.js' );
     37 var defaults = require( './defaults.json' );
     38 
     39 
     40 // MAIN //
     41 
     42 /**
     43 * Returns an accumulator function which incrementally performs Grubbs' test for detecting outliers.
     44 *
     45 * @param {Options} [options] - function options
     46 * @param {number} [options.alpha=0.05] - significance level
     47 * @param {string} [options.alternative='two-sided'] - alternative hypothesis ('two-sided', 'min', 'max')
     48 * @param {NonNegativeInteger} [options.init=100] - number of data points used to compute initial statistics
     49 * @throws {TypeError} options argument must be an object
     50 * @throws {TypeError} must provide valid options
     51 * @throws {RangeError} `alpha` option must be on the interval `[0,1]`
     52 * @returns {Function} accumulator function
     53 *
     54 * @example
     55 * var rnorm = require( '@stdlib/random/base/normal' );
     56 *
     57 * var accumulator;
     58 * var opts;
     59 * var res;
     60 * var i;
     61 *
     62 * opts = {
     63 *     'init': 100
     64 * };
     65 *
     66 * accumulator = incrgrubbs( opts );
     67 *
     68 * for ( i = 0; i < 200; i++ ) {
     69 *     res = accumulator( rnorm( 10.0, 5.0 ) );
     70 * }
     71 */
     72 function incrgrubbs() {
     73 	var meanstdev;
     74 	var results;
     75 	var minmax;
     76 	var opts;
     77 	var err;
     78 	var mm;
     79 	var ms;
     80 	var gc;
     81 	var df;
     82 	var N;
     83 	var G;
     84 
     85 	opts = copy( defaults );
     86 	if ( arguments.length ) {
     87 		err = validate( opts, arguments[ 0 ] );
     88 		if ( err ) {
     89 			throw err;
     90 		}
     91 	}
     92 	// Initialize the results object:
     93 	results = {};
     94 	setReadOnlyAccessor( results, 'rejected', getRejected );
     95 	setReadOnly( results, 'alpha', opts.alpha );
     96 	setReadOnlyAccessor( results, 'criticalValue', getCriticalValue );
     97 	setReadOnlyAccessor( results, 'statistic', getStatistic );
     98 	setReadOnlyAccessor( results, 'df', getDOF );
     99 	setReadOnlyAccessor( results, 'mean', getMean );
    100 	setReadOnlyAccessor( results, 'sd', getStDev );
    101 	setReadOnlyAccessor( results, 'min', getMin );
    102 	setReadOnlyAccessor( results, 'max', getMax );
    103 	setReadOnly( results, 'alt', opts.alternative );
    104 	setReadOnly( results, 'method', 'Grubbs\' Test' );
    105 	setReadOnly( results, 'print', print );
    106 
    107 	N = 0;
    108 	df = 0;
    109 	G = 0.0;
    110 	gc = 0.0;
    111 
    112 	// Initialize statistics accumulators:
    113 	mm = [ 0.0, 0.0 ];
    114 	minmax = incrminmax( mm );
    115 
    116 	ms = [ 0.0, 0.0 ];
    117 	meanstdev = incrmeanstdev( ms );
    118 
    119 	return accumulator;
    120 
    121 	/**
    122 	* If provided a value, the accumulator function returns updated Grubbs' test results. If not provided a value, the accumulator function returns the current Grubbs' test results.
    123 	*
    124 	* @private
    125 	* @param {number} [x] - new value
    126 	* @returns {(Object|null)} test results or null
    127 	*/
    128 	function accumulator( x ) {
    129 		var sig;
    130 		var md;
    131 		var tc;
    132 		if ( arguments.length === 0 ) {
    133 			if ( N < opts.init || df <= 0 ) {
    134 				return null;
    135 			}
    136 			return results;
    137 		}
    138 		N += 1;
    139 
    140 		// Update model statistics:
    141 		meanstdev( x );
    142 		minmax( x );
    143 
    144 		// Compute the degrees of freedom:
    145 		df = N - 2;
    146 
    147 		if ( N < opts.init || df <= 0 ) {
    148 			return null;
    149 		}
    150 		// Compute the test statistic and significance level...
    151 		if ( opts.alternative === 'min' ) {
    152 			G = ( ms[0]-mm[0] ) / ms[ 1 ];
    153 			sig = opts.alpha / N;
    154 		} else if ( opts.alternative === 'max' ) {
    155 			G = ( mm[1]-ms[0] ) / ms[ 1 ];
    156 			sig = opts.alpha / N;
    157 		} else { // two-sided
    158 			md = max( ms[0]-mm[0], mm[1]-ms[0] ); // maximum absolute deviation
    159 			G = md / ms[ 1 ];
    160 			sig = opts.alpha / (2*N);
    161 		}
    162 		// Compute the critical values:
    163 		tc = tQuantile( 1.0-sig, df );
    164 		gc = (N-1)*tc / sqrt( N*(df+(tc*tc)) );
    165 
    166 		return results;
    167 	}
    168 
    169 	/**
    170 	* Returns a `boolean` indicating whether the null hypothesis should be rejected.
    171 	*
    172 	* @private
    173 	* @returns {boolean} boolean indicating whether the null hypothesis should be rejected
    174 	*/
    175 	function getRejected() {
    176 		return ( G > gc );
    177 	}
    178 
    179 	/**
    180 	* Returns the critical value.
    181 	*
    182 	* @private
    183 	* @returns {number} critical value
    184 	*/
    185 	function getCriticalValue() {
    186 		return gc;
    187 	}
    188 
    189 	/**
    190 	* Returns the test statistic.
    191 	*
    192 	* @private
    193 	* @returns {number} test statistic
    194 	*/
    195 	function getStatistic() {
    196 		return G;
    197 	}
    198 
    199 	/**
    200 	* Returns the degrees of freedom (DOF).
    201 	*
    202 	* @private
    203 	* @returns {PositiveInteger} degrees of freedom
    204 	*/
    205 	function getDOF() {
    206 		return df;
    207 	}
    208 
    209 	/**
    210 	* Returns the sample mean.
    211 	*
    212 	* @private
    213 	* @returns {number} sample mean
    214 	*/
    215 	function getMean() {
    216 		return ms[ 0 ];
    217 	}
    218 
    219 	/**
    220 	* Returns the corrected sample standard deviation.
    221 	*
    222 	* @private
    223 	* @returns {number} corrected sample standard deviation
    224 	*/
    225 	function getStDev() {
    226 		return ms[ 1 ];
    227 	}
    228 
    229 	/**
    230 	* Returns the sample minimum.
    231 	*
    232 	* @private
    233 	* @returns {number} sample minimum
    234 	*/
    235 	function getMin() {
    236 		return mm[ 0 ];
    237 	}
    238 
    239 	/**
    240 	* Returns the sample maximum.
    241 	*
    242 	* @private
    243 	* @returns {number} sample maximum
    244 	*/
    245 	function getMax() {
    246 		return mm[ 1 ];
    247 	}
    248 
    249 	/**
    250 	* Pretty-print test results.
    251 	*
    252 	* @private
    253 	* @param {Object} [options] - options object
    254 	* @param {PositiveInteger} [options.digits=4] - number of digits after the decimal point
    255 	* @param {boolean} [options.decision=true] - boolean indicating whether to print the test decision
    256 	* @throws {TypeError} options argument must be an object
    257 	* @throws {TypeError} must provide valid options
    258 	* @returns {string} formatted output
    259 	*/
    260 	function print( options ) {
    261 		var decision;
    262 		var digits;
    263 		var str;
    264 
    265 		digits = opts.digits;
    266 		decision = opts.decision;
    267 		if ( arguments.length > 0 ) {
    268 			if ( !isObject( options ) ) {
    269 				throw new TypeError( 'invalid argument. Must provide an object. Value: `' + options + '`.' );
    270 			}
    271 			if ( hasOwnProp( options, 'digits' ) ) {
    272 				if ( !isPositiveInteger( options.digits ) ) {
    273 					throw new TypeError( 'invalid option. `digits` option must be a positive integer. Option: `' + options.digits + '`.' );
    274 				}
    275 				digits = options.digits;
    276 			}
    277 			if ( hasOwnProp( options, 'decision' ) ) {
    278 				if ( !isBoolean( options.decision ) ) {
    279 					throw new TypeError( 'invalid option. `decision` option must be boolean. Option: `' + options.decision + '`.' );
    280 				}
    281 				decision = options.decision;
    282 			}
    283 		}
    284 		str = '';
    285 		str += results.method;
    286 		str += '\n\n';
    287 		str += 'Alternative hypothesis: ';
    288 		if ( opts.alternative === 'max' ) {
    289 			str += 'The maximum value (' + mm[ 1 ] + ') is an outlier';
    290 		} else if ( opts.alternative === 'min' ) {
    291 			str += 'The minimum value (' + mm[ 0 ] + ') is an outlier';
    292 		} else { // two-sided
    293 			str += 'The ';
    294 			if ( ms[0]-mm[0] > mm[1]-ms[0] ) {
    295 				str += 'minimum value (' + mm[ 0 ] + ')';
    296 			} else {
    297 				str += 'maximum value (' + mm[ 1 ] + ')';
    298 			}
    299 			str += ' is an outlier';
    300 		}
    301 		str += '\n\n';
    302 		str += '    criticalValue: ' + roundn( gc, -digits ) + '\n';
    303 		str += '    statistic: ' + roundn( G, -digits ) + '\n';
    304 		str += '    df: ' + df + '\n';
    305 		str += '\n';
    306 		if ( decision ) {
    307 			str += 'Test Decision: ';
    308 			if ( G > gc ) {
    309 				str += 'Reject null in favor of alternative at ' + (opts.alpha*100.0) + '% significance level';
    310 			} else {
    311 				str += 'Fail to reject null in favor of alternative at ' + (opts.alpha*100.0) + '% significance level';
    312 			}
    313 			str += '\n';
    314 		}
    315 		return str;
    316 	}
    317 }
    318 
    319 
    320 // EXPORTS //
    321 
    322 module.exports = incrgrubbs;