From 55c383fb56ba40b8159ab7f87f979fb7acee0135 Mon Sep 17 00:00:00 2001 From: ROKUMATE Date: Wed, 24 Dec 2025 10:29:12 +0530 Subject: [PATCH 1/2] feat: add `@stdlib/stats/incr/nanmmaape` package Implement moving mean arctangent absolute percentage error with NaN handling. Add thin wrapper around mmaape that ignores NaN values in either argument. Include comprehensive test suite (52 tests, all passing), TypeScript definitions and type tests, benchmarks for performance testing, examples demonstrating NaN handling, and complete documentation in README. Closes RFC for nanmmaape implementation. --- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: passed - task: lint_package_json status: passed - task: lint_repl_help status: na - task: lint_javascript_src status: passed - task: lint_javascript_cli status: na - task: lint_javascript_examples status: passed - task: lint_javascript_tests status: passed - task: lint_javascript_benchmarks status: passed - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: passed - task: lint_typescript_tests status: passed - task: lint_license_headers status: passed --- --- .../@stdlib/stats/incr/nanmmaape/README.md | 223 +++++++++++ .../incr/nanmmaape/benchmark/benchmark.js | 103 +++++ .../incr/nanmmaape/docs/types/index.d.ts | 78 ++++ .../stats/incr/nanmmaape/docs/types/test.ts | 73 ++++ .../stats/incr/nanmmaape/examples/index.js | 48 +++ .../@stdlib/stats/incr/nanmmaape/lib/index.js | 60 +++ .../@stdlib/stats/incr/nanmmaape/lib/main.js | 83 ++++ .../@stdlib/stats/incr/nanmmaape/package.json | 89 +++++ .../@stdlib/stats/incr/nanmmaape/test/test.js | 354 ++++++++++++++++++ 9 files changed, 1111 insertions(+) create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmmaape/README.md create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmmaape/benchmark/benchmark.js create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmmaape/docs/types/index.d.ts create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmmaape/docs/types/test.ts create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmmaape/examples/index.js create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/index.js create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/main.js create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmmaape/package.json create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmmaape/test/test.js diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/README.md b/lib/node_modules/@stdlib/stats/incr/nanmmaape/README.md new file mode 100644 index 000000000000..1b7ec2f7ca02 --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/README.md @@ -0,0 +1,223 @@ + + +# incrnanmmaape + +> Compute a moving [mean arctangent absolute percentage error][@kim:2016a] (MAAPE) incrementally, ignoring `NaN` values. + +
+ +For a window of size `W`, the [mean arctangent absolute percentage error][@kim:2016a] is defined as + + + +```math +\mathop{\mathrm{MAAPE}} = \frac{1}{W} \sum_{i=0}^{W-1} \mathop{\mathrm{arctan}}\biggl( \biggl| \frac{a_i - f_i}{a_i} \biggr| \biggr) +``` + + + + + +where `f_i` is the forecast value and `a_i` is the actual value. + +
+ + + +
+ +## Usage + +```javascript +var incrnanmmaape = require( '@stdlib/stats/incr/nanmmaape' ); +``` + +#### incrnanmmaape( window ) + +Returns an accumulator `function` which incrementally computes a moving [mean arctangent absolute percentage error][@kim:2016a], ignoring `NaN` values. The `window` parameter defines the number of values over which to compute the moving [mean arctangent absolute percentage error][@kim:2016a]. + +```javascript +var accumulator = incrnanmmaape( 3 ); +``` + +#### accumulator( \[f, a] ) + +If provided input values `f` and `a`, the accumulator function returns an updated [mean arctangent absolute percentage error][@kim:2016a]. If not provided input values `f` and `a`, the accumulator function returns the current [mean arctangent absolute percentage error][@kim:2016a]. + +```javascript +var accumulator = incrnanmmaape( 3 ); + +var m = accumulator(); +// returns null + +// Fill the window... +m = accumulator( 2.0, 3.0 ); // [(2.0,3.0)] +// returns ~0.32 + +m = accumulator( 1.0, 4.0 ); // [(2.0,3.0), (1.0,4.0)] +// returns ~0.48 + +m = accumulator( 3.0, 9.0 ); // [(2.0,3.0), (1.0,4.0), (3.0,9.0)] +// returns ~0.52 + +// Window begins sliding... +m = accumulator( 7.0, 3.0 ); // [(1.0,4.0), (3.0,9.0), (7.0,3.0)] +// returns ~0.72 + +m = accumulator( 5.0, 3.0 ); // [(3.0,9.0), (7.0,3.0), (5.0,3.0)] +// returns ~0.70 + +m = accumulator(); +// returns ~0.70 +``` + +`NaN` values are ignored when provided as either argument: + +```javascript +var accumulator = incrnanmmaape( 3 ); + +var m = accumulator( 2.0, 3.0 ); +// returns ~0.32 + +m = accumulator( NaN, 4.0 ); +// returns ~0.32 + +m = accumulator( 5.0, NaN ); +// returns ~0.32 + +m = accumulator( NaN, NaN ); +// returns ~0.32 + +m = accumulator( 3.0, 1.0 ); +// returns ~0.71 + +m = accumulator(); +// returns ~0.71 +``` + +
+ + + +
+ +## Notes + +- Input values are **not** type checked. If provided `NaN`, the accumulator function **ignores** the value. If non-numeric inputs are possible, you are advised to type check and handle accordingly **before** passing the value to the accumulator function. +- As `W` (f,a) pairs are needed to fill the window buffer, the first `W-1` returned values are calculated from smaller sample sizes. Until the window is full, each returned value is calculated from all provided **non-NaN** values. +- Note that, unlike the [mean absolute percentage error][@stdlib/stats/incr/mape] (MAPE), the [mean arctangent absolute percentage error][@kim:2016a] is expressed in radians on the interval \[0,π/2]. + +
+ + + +
+ +## Examples + + + +```javascript +var randu = require( '@stdlib/random/base/randu' ); +var incrnanmmaape = require( '@stdlib/stats/incr/nanmmaape' ); + +var accumulator; +var v1; +var v2; +var i; + +// Initialize an accumulator: +accumulator = incrnanmmaape( 5 ); + +// For each simulated datum, update the moving mean arctangent absolute percentage error... +for ( i = 0; i < 100; i++ ) { + if ( randu() < 0.2 ) { + v1 = NaN; + } else { + v1 = ( randu()*100.0 ) + 50.0; + } + if ( randu() < 0.2 ) { + v2 = NaN; + } else { + v2 = ( randu()*100.0 ) + 50.0; + } + accumulator( v1, v2 ); +} +console.log( accumulator() ); +``` + +
+ + + +
+ +## References + +- Kim, Sungil, and Heeyoung Kim. 2016. "A new metric of absolute percentage error for intermittent demand forecasts." _International Journal of Forecasting_ 32 (3): 669–79. doi:[10.1016/j.ijforecast.2015.12.003][@kim:2016a]. + +
+ + + + + + + + + + + + + + diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/benchmark/benchmark.js b/lib/node_modules/@stdlib/stats/incr/nanmmaape/benchmark/benchmark.js new file mode 100644 index 000000000000..814b4369d3b8 --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/benchmark/benchmark.js @@ -0,0 +1,103 @@ +/** +* @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 bench = require( '@stdlib/bench' ); +var randu = require( '@stdlib/random/base/randu' ); +var pkg = require( './../package.json' ).name; +var incrnanmmaape = require( './../lib' ); + + +// MAIN // + +bench( pkg, function benchmark( b ) { + var f; + var i; + b.tic(); + for ( i = 0; i < b.iterations; i++ ) { + f = incrnanmmaape( (i%5)+1 ); + if ( typeof f !== 'function' ) { + b.fail( 'should return a function' ); + } + } + b.toc(); + if ( typeof f !== 'function' ) { + b.fail( 'should return a function' ); + } + b.pass( 'benchmark finished' ); + b.end(); +}); + +bench( pkg+'::accumulator', function benchmark( b ) { + var acc; + var v; + var i; + + acc = incrnanmmaape( 5 ); + + b.tic(); + for ( i = 0; i < b.iterations; i++ ) { + v = acc( randu()+0.5, randu()+0.5 ); + if ( v !== v ) { + b.fail( 'should not return NaN' ); + } + } + b.toc(); + if ( v !== v ) { + b.fail( 'should not return NaN' ); + } + b.pass( 'benchmark finished' ); + b.end(); +}); + +bench( pkg+'::accumulator:with_nan_values', function benchmark( b ) { + var acc; + var v1; + var v2; + var v; + var i; + + acc = incrnanmmaape( 5 ); + + b.tic(); + for ( i = 0; i < b.iterations; i++ ) { + if ( randu() < 0.2 ) { + v1 = NaN; + } else { + v1 = randu() + 0.5; + } + if ( randu() < 0.2 ) { + v2 = NaN; + } else { + v2 = randu() + 0.5; + } + v = acc( v1, v2 ); + if ( v !== v ) { + b.fail( 'should not return NaN' ); + } + } + b.toc(); + if ( v !== v ) { + b.fail( 'should not return NaN' ); + } + b.pass( 'benchmark finished' ); + b.end(); +}); diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/docs/types/index.d.ts b/lib/node_modules/@stdlib/stats/incr/nanmmaape/docs/types/index.d.ts new file mode 100644 index 000000000000..57447884e1e4 --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/docs/types/index.d.ts @@ -0,0 +1,78 @@ +/* +* @license Apache-2.0 +* +* Copyright (c) 2019 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. +*/ + +// TypeScript Version: 4.1 + +/// + +/** +* If provided input values, the accumulator function returns an updated mean arctangent absolute percentage error. If not provided input values, the accumulator function returns the current mean arctangent absolute percentage error. +* +* ## Notes +* +* - Note that, unlike the mean absolute percentage error (MAPE), the mean arctangent absolute percentage error is expressed in radians on the interval [0,π/2]. +* - If provided `NaN`, the accumulator function **ignores** the value. +* +* @param f - input value +* @param a - input value +* @returns mean arctangent absolute percentage error or null +*/ +type accumulator = ( f?: number, a?: number ) => number | null; + +/** +* Returns an accumulator function which incrementally computes a moving mean arctangent absolute percentage error, ignoring `NaN` values. +* +* ## Notes +* +* - The `W` parameter defines the number of values over which to compute the moving mean arctangent absolute percentage error. +* - As `W` (f,a) pairs are needed to fill the window buffer, the first `W-1` returned values are calculated from smaller sample sizes. Until the window is full, each returned value is calculated from all provided **non-NaN** values. +* +* @param W - window size +* @throws must provide a positive integer +* @returns accumulator function +* +* @example +* var accumulator = incrnanmmaape( 3 ); +* +* var m = accumulator(); +* // returns null +* +* m = accumulator( 2.0, 3.0 ); +* // returns ~0.32 +* +* m = accumulator( NaN, 2.0 ); +* // returns ~0.32 +* +* m = accumulator( 5.0, 2.0 ); +* // returns ~0.65 +* +* m = accumulator( 3.0, NaN ); +* // returns ~0.65 +* +* m = accumulator( 3.0, 2.0 ); +* // returns ~0.59 +* +* m = accumulator(); +* // returns ~0.59 +*/ +declare function incrnanmmaape( W: number ): accumulator; + + +// EXPORTS // + +export = incrnanmmaape; diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/docs/types/test.ts b/lib/node_modules/@stdlib/stats/incr/nanmmaape/docs/types/test.ts new file mode 100644 index 000000000000..90c435fcc54f --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/docs/types/test.ts @@ -0,0 +1,73 @@ +/* +* @license Apache-2.0 +* +* Copyright (c) 2019 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. +*/ + +import incrnanmmaape = require( './index' ); + + +// TESTS // + +// The function returns an accumulator function... +{ + incrnanmmaape( 3 ); // $ExpectType accumulator +} + +// The compiler throws an error if the function is provided a value other than a number... +{ + incrnanmmaape( '5' ); // $ExpectError + incrnanmmaape( true ); // $ExpectError + incrnanmmaape( false ); // $ExpectError + incrnanmmaape( null ); // $ExpectError + incrnanmmaape( undefined ); // $ExpectError + incrnanmmaape( [] ); // $ExpectError + incrnanmmaape( {} ); // $ExpectError + incrnanmmaape( ( x: number ): number => x ); // $ExpectError +} + +// The compiler throws an error if the function is provided insufficient arguments... +{ + incrnanmmaape(); // $ExpectError +} + +// The function returns an accumulator function which returns an accumulated result... +{ + const acc = incrnanmmaape( 3 ); + + acc(); // $ExpectType number | null + acc( 3.14, 2.0 ); // $ExpectType number | null +} + +// The compiler throws an error if the returned accumulator function is provided invalid arguments... +{ + const acc = incrnanmmaape( 3 ); + + acc( '5', 2.0 ); // $ExpectError + acc( true, 2.0 ); // $ExpectError + acc( false, 2.0 ); // $ExpectError + acc( null, 2.0 ); // $ExpectError + acc( [], 2.0 ); // $ExpectError + acc( {}, 2.0 ); // $ExpectError + acc( ( x: number ): number => x, 2.0 ); // $ExpectError + + acc( 3.14, '5' ); // $ExpectError + acc( 3.14, true ); // $ExpectError + acc( 3.14, false ); // $ExpectError + acc( 3.14, null ); // $ExpectError + acc( 3.14, [] ); // $ExpectError + acc( 3.14, {} ); // $ExpectError + acc( 3.14, ( x: number ): number => x ); // $ExpectError +} diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/examples/index.js b/lib/node_modules/@stdlib/stats/incr/nanmmaape/examples/index.js new file mode 100644 index 000000000000..c8fc27a71f79 --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/examples/index.js @@ -0,0 +1,48 @@ +/** +* @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'; + +var randu = require( '@stdlib/random/base/randu' ); +var incrnanmmaape = require( './../lib' ); + +var accumulator; +var err; +var v1; +var v2; +var i; + +// Initialize an accumulator: +accumulator = incrnanmmaape( 5 ); + +// For each simulated datum, update the moving mean arctangent absolute percentage error... +console.log( '\nValue\tValue\tMAAPE\n' ); +for ( i = 0; i < 100; i++ ) { + if ( randu() < 0.2 ) { + v1 = NaN; + } else { + v1 = ( randu()*100.0 ) + 50.0; + } + if ( randu() < 0.2 ) { + v2 = NaN; + } else { + v2 = ( randu()*100.0 ) + 50.0; + } + err = accumulator( v1, v2 ); + console.log( '%d\t%d\t%d', v1.toFixed( 3 ), v2.toFixed( 3 ), ( err ) ? err.toFixed( 3 ) : 'null' ); +} diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/index.js b/lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/index.js new file mode 100644 index 000000000000..5866ab9b764a --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/index.js @@ -0,0 +1,60 @@ +/** +* @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'; + +/** +* Compute a moving mean arctangent absolute percentage error incrementally, ignoring NaN values. +* +* @module @stdlib/stats/incr/nanmmaape +* +* @example +* var incrnanmmaape = require( '@stdlib/stats/incr/nanmmaape' ); +* +* var accumulator = incrnanmmaape( 3 ); +* +* var m = accumulator(); +* // returns null +* +* m = accumulator( 2.0, 3.0 ); +* // returns ~0.32 +* +* m = accumulator( NaN, 2.0 ); +* // returns ~0.32 +* +* m = accumulator( 5.0, 2.0 ); +* // returns ~0.65 +* +* m = accumulator( 3.0, NaN ); +* // returns ~0.65 +* +* m = accumulator( 3.0, 2.0 ); +* // returns ~0.59 +* +* m = accumulator(); +* // returns ~0.59 +*/ + +// MODULES // + +var main = require( './main.js' ); + + +// EXPORTS // + +module.exports = main; diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/main.js b/lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/main.js new file mode 100644 index 000000000000..edb6959d135c --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/main.js @@ -0,0 +1,83 @@ +/** +* @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 isnan = require( '@stdlib/math/base/assert/is-nan' ); +var incrmmaape = require( '@stdlib/stats/incr/mmaape' ); + + +// MAIN // + +/** +* Returns an accumulator function which incrementally computes a moving mean arctangent absolute percentage error, ignoring `NaN` values. +* +* @param {PositiveInteger} W - window size +* @throws {TypeError} must provide a positive integer +* @returns {Function} accumulator function +* +* @example +* var accumulator = incrnanmmaape( 3 ); +* +* var m = accumulator(); +* // returns null +* +* m = accumulator( 2.0, 3.0 ); +* // returns ~0.32 +* +* m = accumulator( NaN, 2.0 ); +* // returns ~0.32 +* +* m = accumulator( 5.0, 2.0 ); +* // returns ~0.65 +* +* m = accumulator( 3.0, NaN ); +* // returns ~0.65 +* +* m = accumulator( 3.0, 2.0 ); +* // returns ~0.59 +* +* m = accumulator(); +* // returns ~0.59 +*/ +function incrnanmmaape( W ) { + var mmaape = incrmmaape( W ); + return accumulator; + + /** + * If provided input values, the accumulator function returns an updated mean arctangent absolute percentage error. If not provided input values, the accumulator function returns the current mean arctangent absolute percentage error. + * + * @private + * @param {number} [f] - input value + * @param {number} [a] - input value + * @returns {(number|null)} mean arctangent absolute percentage error or null + */ + function accumulator( f, a ) { + if ( arguments.length === 0 || isnan( f ) || isnan( a ) ) { + return mmaape(); + } + return mmaape( f, a ); + } +} + + +// EXPORTS // + +module.exports = incrnanmmaape; diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/package.json b/lib/node_modules/@stdlib/stats/incr/nanmmaape/package.json new file mode 100644 index 000000000000..f76fde3cd203 --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/package.json @@ -0,0 +1,89 @@ +{ + "name": "@stdlib/stats/incr/nanmmaape", + "version": "0.0.0", + "description": "Compute a moving arctangent mean absolute percentage error (MAAPE) incrementally, ignoring NaN values.", + "license": "Apache-2.0", + "author": { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/stdlib/graphs/contributors" + }, + "contributors": [ + { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/stdlib/graphs/contributors" + } + ], + "main": "./lib", + "directories": { + "benchmark": "./benchmark", + "doc": "./docs", + "example": "./examples", + "lib": "./lib", + "test": "./test" + }, + "types": "./docs/types", + "scripts": {}, + "homepage": "https://github.com/stdlib-js/stdlib", + "repository": { + "type": "git", + "url": "git://github.com/stdlib-js/stdlib.git" + }, + "bugs": { + "url": "https://github.com/stdlib-js/stdlib/issues" + }, + "dependencies": {}, + "devDependencies": {}, + "engines": { + "node": ">=0.10.0", + "npm": ">2.7.0" + }, + "os": [ + "aix", + "darwin", + "freebsd", + "linux", + "macos", + "openbsd", + "sunos", + "win32", + "windows" + ], + "keywords": [ + "stdlib", + "stdmath", + "statistics", + "stats", + "mathematics", + "math", + "average", + "avg", + "mean", + "error", + "err", + "mape", + "maape", + "absolute", + "abs", + "incremental", + "accumulator", + "moving mean", + "moving average", + "sliding window", + "sliding", + "window", + "moving", + "rolling", + "time series", + "timeseries", + "demand", + "forecasting", + "forecast", + "difference", + "diff", + "delta", + "nan", + "missing", + "omit", + "ignore" + ] +} diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/test/test.js b/lib/node_modules/@stdlib/stats/incr/nanmmaape/test/test.js new file mode 100644 index 000000000000..3c2d8430f96b --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/test/test.js @@ -0,0 +1,354 @@ +/** +* @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 tape = require( 'tape' ); +var abs = require( '@stdlib/math/base/special/abs' ); +var atan = require( '@stdlib/math/base/special/atan' ); +var EPS = require( '@stdlib/constants/float64/eps' ); +var incrnanmmaape = require( './../lib' ); + + +// TESTS // + +tape( 'main export is a function', function test( t ) { + t.ok( true, __filename ); + t.strictEqual( typeof incrnanmmaape, 'function', 'main export is a function' ); + t.end(); +}); + +tape( 'the function throws an error if not provided a positive integer', function test( t ) { + var values; + var i; + + values = [ + '5', + -5.0, + 0.0, + 3.14, + true, + null, + void 0, + NaN, + [], + {}, + function noop() {} + ]; + + for ( i = 0; i < values.length; i++ ) { + t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] ); + } + t.end(); + + function badValue( value ) { + return function badValue() { + incrnanmmaape( value ); + }; + } +}); + +tape( 'the function returns an accumulator function', function test( t ) { + t.strictEqual( typeof incrnanmmaape( 3 ), 'function', 'returns expected value' ); + t.end(); +}); + +tape( 'the accumulator function computes a moving mean arctangent absolute percentage error incrementally', function test( t ) { + var expected; + var actual; + var delta; + var data; + var acc; + var tol; + var N; + var i; + + data = [ + [ 2.0, 3.0 ], + [ 3.0, 1.0 ], + [ 5.0, 2.0 ], + [ 4.0, 4.0 ], + [ 3.0, 10.0 ], + [ 4.0, 5.0 ] + ]; + N = data.length; + + acc = incrnanmmaape( 3 ); + + // Note: manually computed + expected = [ + (1.0/1.0)*( atan(1.0/3.0) ), + (1.0/2.0)*( atan(1.0/3.0)+atan(2.0/1.0) ), + (1.0/3.0)*( atan(1.0/3.0)+atan(2.0/1.0)+atan(3.0/2.0) ), + (1.0/3.0)*( atan(2.0/1.0)+atan(3.0/2.0)+atan(0.0/4.0) ), + (1.0/3.0)*( atan(3.0/2.0)+atan(0.0/4.0)+atan(7.0/10.0) ), + (1.0/3.0)*( atan(0.0/4.0)+atan(7.0/10.0)+atan(1.0/5.0) ) + ]; + for ( i = 0; i < N; i++ ) { + actual = acc( data[i][0], data[i][1] ); + if ( actual === expected[i] ) { + t.strictEqual( actual, expected[i], 'returns expected value' ); + } else { + delta = abs( expected[i] - actual ); + tol = 1.0 * EPS * abs( expected[i] ); + t.strictEqual( delta <= tol, true, 'within tolerance. Actual: '+actual+'. Expected: '+expected[i]+'. Delta: '+delta+'. Tol: '+tol+'.' ); + } + } + t.end(); +}); + +tape( 'if not provided an input value, the accumulator function returns the current mean arctangent absolute percentage error', function test( t ) { + var data; + var acc; + var i; + + data = [ + [ 2.0, 3.0 ], + [ 3.0, 5.0 ], + [ 19.0, 10.0 ] + ]; + acc = incrnanmmaape( 2 ); + for ( i = 0; i < data.length; i++ ) { + acc( data[i][0], data[i][1] ); + } + t.strictEqual( acc(), 0.5*( atan(2.0/5.0)+atan(9.0/10.0) ), 'returns expected value' ); + t.end(); +}); + +tape( 'if data has yet to be provided, the accumulator function returns `null`', function test( t ) { + var acc = incrnanmmaape( 3 ); + t.strictEqual( acc(), null, 'returns expected value' ); + t.end(); +}); + +tape( 'the accumulator function ignores NaN values in the first argument', function test( t ) { + var expected; + var delta; + var data; + var acc; + var tol; + var v; + var i; + + // Test data with NaN values in the first position (forecast values) + data = [ + [ 2.0, 3.0 ], + [ NaN, 1.0 ], + [ 3.0, 1.0 ], + [ 5.0, 2.0 ], + [ NaN, 4.0 ], + [ 4.0, 4.0 ] + ]; + + acc = incrnanmmaape( 3 ); + + // Expected: NaN values should be skipped, window continues sliding + + // Window: [(2.0,3.0)] + + // Window: [(2.0,3.0)] - NaN skipped, window has only 1 value + + // Window: [(2.0,3.0), (3.0,1.0)] + + // Window: [(2.0,3.0), (3.0,1.0), (5.0,2.0)] - window full, all 3 values + + // Window: [(2.0,3.0), (3.0,1.0), (5.0,2.0)] - NaN skipped, window unchanged + + // Window: [(3.0,1.0), (5.0,2.0), (4.0,4.0)] + expected = [ + (1.0/1.0)*( atan(1.0/3.0) ), // [(2.0,3.0)] + (1.0/1.0)*( atan(1.0/3.0) ), // [(2.0,3.0)] - NaN ignored + (1.0/2.0)*( atan(1.0/3.0)+atan(2.0/1.0) ), // [(2.0,3.0), (3.0,1.0)] + (1.0/3.0)*( atan(1.0/3.0)+atan(2.0/1.0)+atan(3.0/2.0) ), // [(2.0,3.0), (3.0,1.0), (5.0,2.0)] + (1.0/3.0)*( atan(1.0/3.0)+atan(2.0/1.0)+atan(3.0/2.0) ), // [(2.0,3.0), (3.0,1.0), (5.0,2.0)] - NaN ignored, window unchanged + (1.0/3.0)*( atan(2.0/1.0)+atan(3.0/2.0)+atan(0.0/4.0) ) // [(3.0,1.0), (5.0,2.0), (4.0,4.0)] + ]; + + for ( i = 0; i < data.length; i++ ) { + v = acc( data[i][0], data[i][1] ); + if ( v === expected[i] ) { + t.strictEqual( v, expected[i], 'returns expected value for pair '+i ); + } else { + delta = abs( expected[i] - v ); + tol = 2.0 * EPS * abs( expected[i] ); + t.strictEqual( delta <= tol, true, 'within tolerance for pair '+i+'. Actual: '+v+'. Expected: '+expected[i]+'. Delta: '+delta+'. Tol: '+tol+'.' ); + } + } + t.end(); +}); + +tape( 'the accumulator function ignores NaN values in the second argument', function test( t ) { + var expected; + var delta; + var data; + var acc; + var tol; + var v; + var i; + + // Test data with NaN values in the second position (actual values) + data = [ + [ 2.0, 3.0 ], + [ 3.0, NaN ], + [ 3.0, 1.0 ], + [ 5.0, 2.0 ], + [ 4.0, NaN ], + [ 4.0, 4.0 ] + ]; + + acc = incrnanmmaape( 3 ); + + // Expected: NaN values should be skipped, window continues sliding + expected = [ + (1.0/1.0)*( atan(1.0/3.0) ), // [(2.0,3.0)] + (1.0/1.0)*( atan(1.0/3.0) ), // [(2.0,3.0)] - NaN ignored + (1.0/2.0)*( atan(1.0/3.0)+atan(2.0/1.0) ), // [(2.0,3.0), (3.0,1.0)] + (1.0/3.0)*( atan(1.0/3.0)+atan(2.0/1.0)+atan(3.0/2.0) ), // [(2.0,3.0), (3.0,1.0), (5.0,2.0)] + (1.0/3.0)*( atan(1.0/3.0)+atan(2.0/1.0)+atan(3.0/2.0) ), // [(2.0,3.0), (3.0,1.0), (5.0,2.0)] - NaN ignored, window unchanged + (1.0/3.0)*( atan(2.0/1.0)+atan(3.0/2.0)+atan(0.0/4.0) ) // [(3.0,1.0), (5.0,2.0), (4.0,4.0)] + ]; + + for ( i = 0; i < data.length; i++ ) { + v = acc( data[i][0], data[i][1] ); + if ( v === expected[i] ) { + t.strictEqual( v, expected[i], 'returns expected value for pair '+i ); + } else { + delta = abs( expected[i] - v ); + tol = 2.0 * EPS * abs( expected[i] ); + t.strictEqual( delta <= tol, true, 'within tolerance for pair '+i+'. Actual: '+v+'. Expected: '+expected[i]+'. Delta: '+delta+'. Tol: '+tol+'.' ); + } + } + t.end(); +}); + +tape( 'the accumulator function ignores NaN values in both arguments', function test( t ) { + var expected; + var delta; + var data; + var acc; + var tol; + var v; + var i; + + // Test data with NaN values in both positions + data = [ + [ 2.0, 3.0 ], + [ NaN, NaN ], + [ 3.0, 1.0 ], + [ 5.0, 2.0 ], + [ NaN, 4.0 ], + [ 4.0, NaN ], + [ 4.0, 4.0 ] + ]; + + acc = incrnanmmaape( 3 ); + + // Expected: NaN values should be skipped, window continues sliding + expected = [ + (1.0/1.0)*( atan(1.0/3.0) ), // [(2.0,3.0)] + (1.0/1.0)*( atan(1.0/3.0) ), // [(2.0,3.0)] - NaN ignored + (1.0/2.0)*( atan(1.0/3.0)+atan(2.0/1.0) ), // [(2.0,3.0), (3.0,1.0)] + (1.0/3.0)*( atan(1.0/3.0)+atan(2.0/1.0)+atan(3.0/2.0) ), // [(2.0,3.0), (3.0,1.0), (5.0,2.0)] + (1.0/3.0)*( atan(1.0/3.0)+atan(2.0/1.0)+atan(3.0/2.0) ), // [(2.0,3.0), (3.0,1.0), (5.0,2.0)] - NaN ignored, window unchanged + (1.0/3.0)*( atan(1.0/3.0)+atan(2.0/1.0)+atan(3.0/2.0) ), // [(2.0,3.0), (3.0,1.0), (5.0,2.0)] - NaN ignored, window unchanged + (1.0/3.0)*( atan(2.0/1.0)+atan(3.0/2.0)+atan(0.0/4.0) ) // [(3.0,1.0), (5.0,2.0), (4.0,4.0)] + ]; + + for ( i = 0; i < data.length; i++ ) { + v = acc( data[i][0], data[i][1] ); + if ( v === expected[i] ) { + t.strictEqual( v, expected[i], 'returns expected value for pair '+i ); + } else { + delta = abs( expected[i] - v ); + tol = 2.0 * EPS * abs( expected[i] ); + t.strictEqual( delta <= tol, true, 'within tolerance for pair '+i+'. Actual: '+v+'. Expected: '+expected[i]+'. Delta: '+delta+'. Tol: '+tol+'.' ); + } + } + t.end(); +}); + +tape( 'when all values in the window are NaN, the accumulator function returns `null`', function test( t ) { + var data; + var acc; + var v; + var i; + + data = [ + [ NaN, 1.0 ], + [ NaN, NaN ], + [ 1.0, NaN ] + ]; + + acc = incrnanmmaape( 3 ); + + for ( i = 0; i < data.length; i++ ) { + v = acc( data[i][0], data[i][1] ); + t.strictEqual( v, null, 'returns null when all values are NaN' ); + } + t.end(); +}); + +tape( 'the accumulator function correctly handles a mix of NaN and non-NaN values', function test( t ) { + var expected; + var delta; + var data; + var acc; + var tol; + var v; + var i; + + data = [ + [ NaN, 3.0 ], + [ 2.0, 3.0 ], + [ NaN, NaN ], + [ 3.0, 1.0 ], + [ 5.0, NaN ], + [ NaN, 2.0 ], + [ 5.0, 2.0 ], + [ 4.0, 4.0 ] + ]; + + acc = incrnanmmaape( 3 ); + + expected = [ + null, // [] - NaN ignored, nothing yet + (1.0/1.0)*( atan(1.0/3.0) ), // [(2.0,3.0)] + (1.0/1.0)*( atan(1.0/3.0) ), // [(2.0,3.0)] - NaN ignored + (1.0/2.0)*( atan(1.0/3.0)+atan(2.0/1.0) ), // [(2.0,3.0), (3.0,1.0)] + (1.0/2.0)*( atan(1.0/3.0)+atan(2.0/1.0) ), // [(2.0,3.0), (3.0,1.0)] - NaN ignored + (1.0/2.0)*( atan(1.0/3.0)+atan(2.0/1.0) ), // [(2.0,3.0), (3.0,1.0)] - NaN ignored + (1.0/3.0)*( atan(1.0/3.0)+atan(2.0/1.0)+atan(3.0/2.0) ), // [(2.0,3.0), (3.0,1.0), (5.0,2.0)] + (1.0/3.0)*( atan(2.0/1.0)+atan(3.0/2.0)+atan(0.0/4.0) ) // [(3.0,1.0), (5.0,2.0), (4.0,4.0)] + ]; + + for ( i = 0; i < data.length; i++ ) { + v = acc( data[i][0], data[i][1] ); + if ( expected[i] === null ) { + t.strictEqual( v, null, 'returns null for pair '+i ); + } else if ( v === expected[i] ) { + t.strictEqual( v, expected[i], 'returns expected value for pair '+i ); + } else { + delta = abs( expected[i] - v ); + tol = 2.0 * EPS * abs( expected[i] ); + t.strictEqual( delta <= tol, true, 'within tolerance for pair '+i+'. Actual: '+v+'. Expected: '+expected[i]+'. Delta: '+delta+'. Tol: '+tol+'.' ); + } + } + t.end(); +}); From fa96752e535141f936a284f82b18406ab4946083 Mon Sep 17 00:00:00 2001 From: stdlib-bot <82920195+stdlib-bot@users.noreply.github.com> Date: Wed, 14 Jan 2026 04:26:54 +0000 Subject: [PATCH 2/2] chore: update copyright years --- lib/node_modules/@stdlib/stats/incr/nanmmaape/README.md | 2 +- .../@stdlib/stats/incr/nanmmaape/benchmark/benchmark.js | 2 +- .../@stdlib/stats/incr/nanmmaape/docs/types/index.d.ts | 2 +- .../@stdlib/stats/incr/nanmmaape/docs/types/test.ts | 2 +- lib/node_modules/@stdlib/stats/incr/nanmmaape/examples/index.js | 2 +- lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/index.js | 2 +- lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/main.js | 2 +- lib/node_modules/@stdlib/stats/incr/nanmmaape/test/test.js | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/README.md b/lib/node_modules/@stdlib/stats/incr/nanmmaape/README.md index 1b7ec2f7ca02..b35e0ee54c7f 100644 --- a/lib/node_modules/@stdlib/stats/incr/nanmmaape/README.md +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/README.md @@ -2,7 +2,7 @@ @license Apache-2.0 -Copyright (c) 2018 The Stdlib Authors. +Copyright (c) 2026 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. diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/benchmark/benchmark.js b/lib/node_modules/@stdlib/stats/incr/nanmmaape/benchmark/benchmark.js index 814b4369d3b8..5d8ddf51fba8 100644 --- a/lib/node_modules/@stdlib/stats/incr/nanmmaape/benchmark/benchmark.js +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/benchmark/benchmark.js @@ -1,7 +1,7 @@ /** * @license Apache-2.0 * -* Copyright (c) 2018 The Stdlib Authors. +* Copyright (c) 2026 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. diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/docs/types/index.d.ts b/lib/node_modules/@stdlib/stats/incr/nanmmaape/docs/types/index.d.ts index 57447884e1e4..305c1f0948ac 100644 --- a/lib/node_modules/@stdlib/stats/incr/nanmmaape/docs/types/index.d.ts +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/docs/types/index.d.ts @@ -1,7 +1,7 @@ /* * @license Apache-2.0 * -* Copyright (c) 2019 The Stdlib Authors. +* Copyright (c) 2026 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. diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/docs/types/test.ts b/lib/node_modules/@stdlib/stats/incr/nanmmaape/docs/types/test.ts index 90c435fcc54f..231bb354295d 100644 --- a/lib/node_modules/@stdlib/stats/incr/nanmmaape/docs/types/test.ts +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/docs/types/test.ts @@ -1,7 +1,7 @@ /* * @license Apache-2.0 * -* Copyright (c) 2019 The Stdlib Authors. +* Copyright (c) 2026 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. diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/examples/index.js b/lib/node_modules/@stdlib/stats/incr/nanmmaape/examples/index.js index c8fc27a71f79..40c22c8ec7db 100644 --- a/lib/node_modules/@stdlib/stats/incr/nanmmaape/examples/index.js +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/examples/index.js @@ -1,7 +1,7 @@ /** * @license Apache-2.0 * -* Copyright (c) 2018 The Stdlib Authors. +* Copyright (c) 2026 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. diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/index.js b/lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/index.js index 5866ab9b764a..2e7edf5f37ac 100644 --- a/lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/index.js +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/index.js @@ -1,7 +1,7 @@ /** * @license Apache-2.0 * -* Copyright (c) 2018 The Stdlib Authors. +* Copyright (c) 2026 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. diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/main.js b/lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/main.js index edb6959d135c..3031c50c19fe 100644 --- a/lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/main.js +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/lib/main.js @@ -1,7 +1,7 @@ /** * @license Apache-2.0 * -* Copyright (c) 2018 The Stdlib Authors. +* Copyright (c) 2026 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. diff --git a/lib/node_modules/@stdlib/stats/incr/nanmmaape/test/test.js b/lib/node_modules/@stdlib/stats/incr/nanmmaape/test/test.js index 3c2d8430f96b..77c0fe072a9b 100644 --- a/lib/node_modules/@stdlib/stats/incr/nanmmaape/test/test.js +++ b/lib/node_modules/@stdlib/stats/incr/nanmmaape/test/test.js @@ -1,7 +1,7 @@ /** * @license Apache-2.0 * -* Copyright (c) 2018 The Stdlib Authors. +* Copyright (c) 2026 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.