From 697083136a77c006b7816dddccb1330c6c7cb06f Mon Sep 17 00:00:00 2001 From: Don Roy Chacko Date: Tue, 29 Apr 2025 20:54:49 +0530 Subject: [PATCH 1/2] feat(stats/incr/nanmcovariance): add nanmcovariance to the stats/incr/* namespace This commit adds the `nanmcovariance` module to the `stats/incr/*` namespace, providing a way to compute a moving unbiased sample covariance incrementally, while handling NaN values. This commit was made to address Issue stdlib-js#5567 and as suggested in the issue, it is based on a thin wrapper around wmean, similar to the relationship between nansum and sum, mainting API consistency and design. This commit includes appropriate documentation and tests for the new purpose of the package, styles of which are consistent to the stats/incr/* namespace. Fixes: stdlib-js#5567 [RFC] Private-ref: stdlib-js#5567 Authored-by: Don Chacko --- .../stats/incr/nanmcovariance/README.md | 186 ++++++++ .../nanmcovariance/benchmark/benchmark.js | 114 +++++ ...unbiased_sample_covariance_known_means.svg | 76 ++++ ...biased_sample_covariance_unknown_means.svg | 82 ++++ .../stats/incr/nanmcovariance/docs/repl.txt | 51 +++ .../incr/nanmcovariance/docs/types/index.d.ts | 89 ++++ .../incr/nanmcovariance/docs/types/test.ts | 123 ++++++ .../incr/nanmcovariance/examples/index.js | 40 ++ .../stats/incr/nanmcovariance/lib/index.js | 67 +++ .../stats/incr/nanmcovariance/lib/main.js | 224 ++++++++++ .../stats/incr/nanmcovariance/package.json | 77 ++++ .../stats/incr/nanmcovariance/test/test.js | 403 ++++++++++++++++++ 12 files changed, 1532 insertions(+) create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmcovariance/README.md create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmcovariance/benchmark/benchmark.js create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/img/equation_unbiased_sample_covariance_known_means.svg create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/img/equation_unbiased_sample_covariance_unknown_means.svg create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/repl.txt create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/types/index.d.ts create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/types/test.ts create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmcovariance/examples/index.js create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmcovariance/lib/index.js create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmcovariance/lib/main.js create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmcovariance/package.json create mode 100644 lib/node_modules/@stdlib/stats/incr/nanmcovariance/test/test.js diff --git a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/README.md b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/README.md new file mode 100644 index 000000000000..313df144d3ce --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/README.md @@ -0,0 +1,186 @@ + + +# incrnanmcovariance + +> Compute a moving [unbiased sample covariance][covariance] incrementally, while handling NaN values. + +
+ +For unknown population means, the [unbiased sample covariance][covariance] for a window `n` of size `W` is defined as + + + +```math +\mathop{\mathrm{cov_n}} = \frac{1}{n-1} \sum_{i=j}^{j+W-1} (x_i - \bar{x}_n)(y_i - \bar{y}_n) +``` + + + + + +where `j` specifies the index of the value at which the window begins. For example, for a trailing (i.e., non-centered) window using zero-based indexing and `j` greater than or equal to `W`, `j` is the `n-W`th value with `n` being the number of values thus analyzed. + +For known population means, the [unbiased sample covariance][covariance] for a window `n` of size `W` is defined as + + + +```math +\mathop{\mathrm{cov_n}} = \frac{1}{n} \sum_{i=j}^{j+W-1} (x_i - \mu_x)(y_i - \mu_y) +``` + + + + + +
+ + + +
+ +## Usage + +```javascript +var incrnanmcovariance = require( '@stdlib/stats/incr/nanmcovariance' ); +``` + +#### incrnanmcovariance( window\[, mx, my] ) + +Returns an accumulator `function` which incrementally computes a moving [unbiased sample covariance][covariance]. The `window` parameter defines the number of values over which to compute the moving [unbiased sample covariance][covariance]. + +```javascript +var accumulator = incrnanmcovariance( 3 ); +``` + +If means are already known, provide `mx` and `my` arguments. + +```javascript +var accumulator = incrnanmcovariance( 3, 5.0, -3.14 ); +``` + +#### accumulator( \[x, y] ) + +If provided input values `x` and `y`, the accumulator function returns an updated [unbiased sample covariance][covariance]. If not provided input values `x` and `y`, the accumulator function returns the current [unbiased sample covariance][covariance]. + +```javascript +var accumulator = incrnanmcovariance( 3 ); + +var v = accumulator(); +// returns null + +// Fill the window... +v = accumulator( 2.0, 1.0 ); // [(2.0, 1.0)] +// returns 0.0 + +v = accumulator( -5.0, 3.14 ); // [(2.0, 1.0), (-5.0, 3.14)] +// returns ~-7.49 + +v = accumulator( 3.0, -1.0 ); // [(2.0, 1.0), (-5.0, 3.14), (3.0, -1.0)] +// returns -8.35 + +// Window begins sliding... +v = accumulator( 5.0, -9.5 ); // [(-5.0, 3.14), (3.0, -1.0), (5.0, -9.5)] +// returns -29.42 + +v = accumulator( -5.0, 1.5 ); // [(3.0, -1.0), (5.0, -9.5), (-5.0, 1.5)] +// returns -24.5 + +v = accumulator(); +// returns -24.5 +``` + +
+ + + + +
+ +## Examples + + + +```javascript +var randu = require( '@stdlib/random/base/randu' ); +var incrnanmcovariance = require( '@stdlib/stats/incr/nanmcovariance' ); + +var accumulator; +var x; +var y; +var i; + +// Initialize an accumulator: +accumulator = incrnanmcovariance( 5 ); + +// For each simulated datum, update the moving unbiased sample covariance... +for ( i = 0; i < 100; i++ ) { + x = randu() * 100.0; + y = randu() * 100.0; + accumulator( x, y ); +} +console.log( accumulator() ); +``` + +
+ + + + + + + + + + + + + + diff --git a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/benchmark/benchmark.js b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/benchmark/benchmark.js new file mode 100644 index 000000000000..8a2deba4ba4c --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/benchmark/benchmark.js @@ -0,0 +1,114 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 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 isnan = require( '@stdlib/math/base/assert/is-nan' ); +var pkg = require( './../package.json' ).name; +var incrnanmcovariance = require( './../lib' ); + + +// MAIN // + +bench( pkg, function benchmark( b ) { + var f; + var i; + b.tic(); + for ( i = 0; i < b.iterations; i++ ) { + f = incrnanmcovariance( (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 = incrnanmcovariance( 5 ); + + b.tic(); + for ( i = 0; i < b.iterations; i++ ) { + v = acc( randu(), randu() ); + if ( isnan( v ) ) { + b.fail( 'should not return NaN' ); + } + } + b.toc(); + if ( isnan( v ) ) { + b.fail( 'should not return NaN' ); + } + b.pass( 'benchmark finished' ); + b.end(); +}); + +bench( pkg+'::accumulator,unknown_means', function benchmark( b ) { + var acc; + var v; + var i; + + acc = incrnanmcovariance( 5 ); + + b.tic(); + for ( i = 0; i < b.iterations; i++ ) { + v = acc( randu(), randu() ); + if ( isnan( v ) ) { + b.fail( 'should not return NaN' ); + } + } + b.toc(); + if ( isnan( v ) ) { + b.fail( 'should not return NaN' ); + } + b.pass( 'benchmark finished' ); + b.end(); +}); + +bench( pkg+'::accumulator,known_means', function benchmark( b ) { + var acc; + var v; + var i; + + acc = incrnanmcovariance( 5, 3.0, -1.0 ); + + b.tic(); + for ( i = 0; i < b.iterations; i++ ) { + v = acc( randu(), randu() ); + if ( isnan( v ) ) { + b.fail( 'should not return NaN' ); + } + } + b.toc(); + if ( isnan( v ) ) { + b.fail( 'should not return NaN' ); + } + b.pass( 'benchmark finished' ); + b.end(); +}); diff --git a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/img/equation_unbiased_sample_covariance_known_means.svg b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/img/equation_unbiased_sample_covariance_known_means.svg new file mode 100644 index 000000000000..b36fb46a6a8f --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/img/equation_unbiased_sample_covariance_known_means.svg @@ -0,0 +1,76 @@ + +normal c normal o normal v Subscript normal n Baseline equals StartFraction 1 Over n EndFraction sigma-summation Underscript i equals j Overscript j plus upper W minus 1 Endscripts left-parenthesis x Subscript i Baseline minus mu Subscript x Baseline right-parenthesis left-parenthesis y Subscript i Baseline minus mu Subscript y Baseline right-parenthesis + + + \ No newline at end of file diff --git a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/img/equation_unbiased_sample_covariance_unknown_means.svg b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/img/equation_unbiased_sample_covariance_unknown_means.svg new file mode 100644 index 000000000000..1d8750e5494a --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/img/equation_unbiased_sample_covariance_unknown_means.svg @@ -0,0 +1,82 @@ + +normal c normal o normal v Subscript normal n Baseline equals StartFraction 1 Over n minus 1 EndFraction sigma-summation Underscript i equals j Overscript j plus upper W minus 1 Endscripts left-parenthesis x Subscript i Baseline minus x overbar Subscript n Baseline right-parenthesis left-parenthesis y Subscript i Baseline minus y overbar Subscript n Baseline right-parenthesis + + + \ No newline at end of file diff --git a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/repl.txt b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/repl.txt new file mode 100644 index 000000000000..c16b59c5504b --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/repl.txt @@ -0,0 +1,51 @@ + +{{alias}}( W[, mx, my] ) + Returns an accumulator function which incrementally computes a moving + unbiased sample covariance, while ignoring NaN values. + + The `W` parameter defines the number of values over which to compute the + moving unbiased sample covariance. + + If provided values, the accumulator function returns an updated moving + unbiased sample covariance. If not provided values, the accumulator function + returns the current moving unbiased sample covariance. + + As `W` (x,y) 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 values. + + Parameters + ---------- + W: integer + Window size. + + mx: number (optional) + Known mean. + + my: number (optional) + Known mean. + + Returns + ------- + acc: Function + Accumulator function. + + Examples + -------- + > var accumulator = {{alias}}( 3 ); + > var v = accumulator() + null + > v = accumulator( 2.0, 1.0 ) + 0.0 + > v = accumulator( -5.0, 3.14 ) + ~-7.49 + > v = accumulator( 3.0, -1.0 ) + -8.35 + > v = accumulator( 5.0, -9.5 ) + -29.42 + > v = accumulator() + -29.42 + + See Also + -------- + diff --git a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/types/index.d.ts b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/types/index.d.ts new file mode 100644 index 000000000000..6fe482371731 --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/types/index.d.ts @@ -0,0 +1,89 @@ +/* +* @license Apache-2.0 +* +* Copyright (c) 2025 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 arguments, returns an updated moving unbiased sample covariance; otherwise, returns the current moving unbiased sample covariance. +* +* @param x - value +* @param y - value +* @returns moving unbiased sample covariance +*/ +type accumulator = ( x?: number, y?: number ) => number | null; + +/** +* Returns an accumulator function which incrementally computes a moving unbiased sample covariance. +* +* ## Notes +* +* - The `W` parameter defines the number of values over which to compute the moving unbiased sample covariance. +* - As `W` (x,y) 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 values. +* +* @param W - window size +* @param meanx - mean value +* @param meany - mean value +* @throws first argument must be a positive integer +* @returns accumulator function +* +* @example +* var accumulator = incrnanmcovariance( 3, -2.0, 10.0 ); +*/ +declare function incrnanmcovariance( W: number, meanx: number, meany: number ): accumulator; + +/** +* Returns an accumulator function which incrementally computes a moving unbiased sample covariance, while handling NaN values. +* +* ## Notes +* +* - The `W` parameter defines the number of values over which to compute the moving unbiased sample covariance. +* - As `W` (x,y) 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 values. +* +* @param W - window size +* @throws first argument must be a positive integer +* @returns accumulator function +* +* @example +* var accumulator = incrnanmcovariance( 3 ); +* +* var v = accumulator(); +* // returns null +* +* v = accumulator( 2.0, 1.0 ); +* // returns 0.0 +* +* v = accumulator( -5.0, 3.14 ); +* // returns ~-7.49 +* +* v = accumulator( 3.0, -1.0 ); +* // returns -8.35 +* +* v = accumulator( 5.0, -9.5 ); +* // returns -29.42 +* +* v = accumulator(); +* // returns -29.42 +*/ +declare function incrnanmcovariance( W: number ): accumulator; + + +// EXPORTS // + +export = incrnanmcovariance; diff --git a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/types/test.ts b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/types/test.ts new file mode 100644 index 000000000000..b2415724fa80 --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/docs/types/test.ts @@ -0,0 +1,123 @@ +/* +* @license Apache-2.0 +* +* Copyright (c) 2025 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 incrnanmcovariance = require( './index' ); + + +// TESTS // + +// The function returns an accumulator function... +{ + incrnanmcovariance( 3 ); // $ExpectType accumulator + incrnanmcovariance( 3, 2, 4 ); // $ExpectType accumulator +} + +// The compiler throws an error if the function is provided non-numeric arguments... +{ + incrnanmcovariance( 2, '5' ); // $ExpectError + incrnanmcovariance( 2, true ); // $ExpectError + incrnanmcovariance( 2, false ); // $ExpectError + incrnanmcovariance( 2, null ); // $ExpectError + incrnanmcovariance( 2, undefined ); // $ExpectError + incrnanmcovariance( 2, [] ); // $ExpectError + incrnanmcovariance( 2, {} ); // $ExpectError + incrnanmcovariance( 2, ( x: number ): number => x ); // $ExpectError + + incrnanmcovariance( '5', 4 ); // $ExpectError + incrnanmcovariance( true, 4 ); // $ExpectError + incrnanmcovariance( false, 4 ); // $ExpectError + incrnanmcovariance( null, 4 ); // $ExpectError + incrnanmcovariance( undefined, 4 ); // $ExpectError + incrnanmcovariance( [], 4 ); // $ExpectError + incrnanmcovariance( {}, 4 ); // $ExpectError + incrnanmcovariance( ( x: number ): number => x, 4 ); // $ExpectError + + incrnanmcovariance( '5', 2.5, 3.5 ); // $ExpectError + incrnanmcovariance( true, 2.5, 3.5 ); // $ExpectError + incrnanmcovariance( false, 2.5, 3.5 ); // $ExpectError + incrnanmcovariance( null, 2.5, 3.5 ); // $ExpectError + incrnanmcovariance( undefined, 2.5, 3.5 ); // $ExpectError + incrnanmcovariance( [], 2.5, 3.5 ); // $ExpectError + incrnanmcovariance( {}, 2.5, 3.5 ); // $ExpectError + incrnanmcovariance( ( x: number ): number => x, 2.5, 3.5 ); // $ExpectError +} + +// The compiler throws an error if the function is provided an invalid number of arguments... +{ + incrnanmcovariance(); // $ExpectError + incrnanmcovariance( 1, 2 ); // $ExpectError + incrnanmcovariance( 2, 2, 3, 4 ); // $ExpectError +} + +// The function returns an accumulator function which returns an accumulated result... +{ + const acc = incrnanmcovariance( 3 ); + + acc(); // $ExpectType number | null + acc( 3.14, 2.0 ); // $ExpectType number | null +} + +// The function returns an accumulator function which returns an accumulated result (known means)... +{ + const acc = incrnanmcovariance( 3, 2, -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 = incrnanmcovariance( 3 ); + + acc( '5', 1.0 ); // $ExpectError + acc( true, 1.0 ); // $ExpectError + acc( false, 1.0 ); // $ExpectError + acc( null, 1.0 ); // $ExpectError + acc( [], 1.0 ); // $ExpectError + acc( {}, 1.0 ); // $ExpectError + acc( ( x: number ): number => x, 1.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 +} + +// The compiler throws an error if the returned accumulator function is provided invalid arguments (known means)... +{ + const acc = incrnanmcovariance( 3, 2, -3 ); + + acc( '5', 1.0 ); // $ExpectError + acc( true, 1.0 ); // $ExpectError + acc( false, 1.0 ); // $ExpectError + acc( null, 1.0 ); // $ExpectError + acc( [], 1.0 ); // $ExpectError + acc( {}, 1.0 ); // $ExpectError + acc( ( x: number ): number => x, 1.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/nanmcovariance/examples/index.js b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/examples/index.js new file mode 100644 index 000000000000..72d50642b937 --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/examples/index.js @@ -0,0 +1,40 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 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 incrnanmcovariance = require( './../lib' ); + +var accumulator; +var cov; +var x; +var y; +var i; + +// Initialize an accumulator: +accumulator = incrnanmcovariance( 5 ); + +// For each simulated datum, update the moving unbiased sample covariance... +console.log( '\nx\ty\tSample Covariance\n' ); +for ( i = 0; i < 100; i++ ) { + x = randu() * 100.0; + y = randu() * 100.0; + cov = accumulator( x, y ); + console.log( '%d\t%d\t%d', x.toFixed( 4 ), y.toFixed( 4 ), cov.toFixed( 4 ) ); +} diff --git a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/lib/index.js b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/lib/index.js new file mode 100644 index 000000000000..30d511354eaa --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/lib/index.js @@ -0,0 +1,67 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 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 unbiased sample covariance incrementally, while handling NaN values. +* +* @module @stdlib/stats/incr/nanmcovariance +* +* @example +* var incrnanmcovariance = require( '@stdlib/stats/incr/nanmcovariance' ); +* +* var accumulator = incrnanmcovariance( 3 ); +* +* var v = accumulator(); +* // returns null +* +* v = accumulator( 2.0, 1.0 ); +* // returns 0.0 +* +* v = accumulator( NaN, 1.0 ); +* // returns 0.0 +* +* v = accumulator( -5.0, 3.14 ); +* // returns ~-7.49 +* +* v = accumulator( 3.0, -1.0 ); +* // returns -8.35 +* +* v = accumulator( 3.0, NaN ); +* // returns -8.35 +* +* v = accumulator( 5.0, -9.5 ); +* // returns -29.42 +* +* v = accumulator(); +* // returns -29.42 +* +* v = accumulator( NaN, NaN ); +* // returns -29.42 +*/ + + +// MODULES // + +var main = require( './main.js' ); + + +// EXPORTS // + +module.exports = main; diff --git a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/lib/main.js b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/lib/main.js new file mode 100644 index 000000000000..8a8622e5eb53 --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/lib/main.js @@ -0,0 +1,224 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 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 incrmcovariance = require( '@stdlib/stats/incr/mcovariance' ); + + +// MAIN // + +/** +* Returns an accumulator function which incrementally computes a moving unbiased sample covariance, while handling NaN values. +* +* ## Method +* +* - Let \\(W\\) be a window of \\(N\\) elements over which we want to compute an unbiased sample covariance. +* +* - We begin by defining the covariance \\( \operatorname{cov}_n(x,y) \\) for a window \\(n\\) as follows +* +* ```tex +* \operatorname{cov}_n(x,y) &= \frac{C_n}{n} +* ``` +* +* where \\(C_n\\) is the co-moment, which is defined as +* +* ```tex +* C_n = \sum_{i=1}^{N} ( x_i - \bar{x}_n ) ( y_i - \bar{y}_n ) +* ``` +* +* and where \\(\bar{x}_n\\) and \\(\bar{y}_n\\) are the sample means for \\(x\\) and \\(y\\), respectively, and \\(i=1\\) specifies the first element in a window. +* +* - The sample mean is computed using the canonical formula +* +* ```tex +* \bar{x}_n = \frac{1}{N} \sum_{i=1}^{N} x_i +* ``` +* +* which, taking into account a previous window, can be expressed +* +* ```tex +* \begin{align*} +* \bar{x}_n &= \frac{1}{N} \biggl( \sum_{i=0}^{N-1} x_i - x_0 + x_N \biggr) \\ +* &= \bar{x}_{n-1} + \frac{x_N - x_0}{N} +* \end{align*} +* ``` +* +* where \\(x_0\\) is the first value in the previous window. +* +* - We can substitute into the co-moment equation +* +* ```tex +* \begin{align*} +* C_n &= \sum_{i=1}^{N} ( x_i - \bar{x}_n ) ( y_i - \bar{y}_n ) \\ +* &= \sum_{i=1}^{N} \biggl( x_i - \bar{x}_{n-1} - \frac{x_N - x_0}{N} \biggr) \biggl( y_i - \bar{y}_{n-1} - \frac{y_N - y_0}{N} \biggr) \\ +* &= \sum_{i=1}^{N} \biggl( \Delta x_{i,n-1} - \frac{x_N - x_0}{N} \biggr) \biggl( \Delta y_{i,n-1} - \frac{y_N - y_0}{N} \biggr) +* \end{align*} +* ``` +* +* where +* +* ```tex +* \Delta x_{i,k} = x_i - \bar{x}_{k} +* ``` +* +* - We can subsequently expand terms and apply a summation identity +* +* ```tex +* \begin{align*} +* C_n &= \sum_{i=1}^{N} \Delta x_{i,n-1} \Delta y_{i,n-1} - \sum_{i=1}^{N} \Delta x_{i,n-1} \biggl( \frac{y_N - y_0}{N} \biggr) - \sum_{i=1}^{N} \Delta y_{i,n-1} \biggl( \frac{x_N - x_0}{N} \biggr) + \sum_{i=1}^{N} \biggl( \frac{x_N - x_0}{N} \biggr) \biggl( \frac{y_N - y_0}{N} \biggr) \\ +* &= \sum_{i=1}^{N} \Delta x_{i,n-1} \Delta y_{i,n-1} - \biggl( \frac{y_N - y_0}{N} \biggr) \sum_{i=1}^{N} \Delta x_{i,n-1} - \biggl( \frac{x_N - x_0}{N} \biggr) \sum_{i=1}^{N} \Delta y_{i,n-1} + \frac{(x_N - x_0)(y_N - y_0)}{N} +* \end{align*} +* ``` +* +* - Let us first consider the second term which we can reorganize as follows +* +* ```tex +* \begin{align*} +* \biggl( \frac{y_N - y_0}{N} \biggr) \sum_{i=1}^{N} \Delta x_{i,n-1} &= \biggl( \frac{y_N - y_0}{N} \biggr) \sum_{i=1}{N} ( x_i - \bar{x}_{n-1}) \\ +* &= \biggl( \frac{y_N - y_0}{N} \biggr) \sum_{i=1}^{N} x_i - \biggl( \frac{y_N - y_0}{N} \biggr) \sum_{i=1}^{N} \bar{x}_{n-1} \\ +* &= (y_N - y_0) \bar{x}_{n} - (y_N - y_0)\bar{x}_{n-1} \\ +* &= (y_N - y_0) (\bar{x}_{n} - \bar{x}_{n-1}) \\ +* &= \frac{(x_N - x_0)(y_N - y_0)}{N} +* \end{align*} +* ``` +* +* - The third term can be reorganized in a manner similar to the second term such that +* +* ```tex +* \biggl( \frac{x_N - x_0}{N} \biggr) \sum_{i=1}^{N} \Delta y_{i,n-1} = \frac{(x_N - x_0)(y_N - y_0)}{N} +* ``` +* +* - Substituting back into the equation for the co-moment +* +* ```tex +* \begin{align*} +* C_n &= \sum_{i=1}^{N} \Delta x_{i,n-1} \Delta y_{i,n-1} - \frac{(x_N - x_0)(y_N - y_0)}{N} - \frac{(x_N - x_0)(y_N - y_0)}{N} + \frac{(x_N - x_0)(y_N - y_0)}{N} \\ +* &= \sum_{i=1}^{N} \Delta x_{i,n-1} \Delta y_{i,n-1} - \frac{(x_N - x_0)(y_N - y_0)}{N} +* \end{align*} +* ``` +* +* - Let us now consider the first term which we can modify as follows +* +* ```tex +* \begin{align*} +* \sum_{i=1}^{N} \Delta x_{i,n-1} \Delta y_{i,n-1} &= \sum_{i=1}^{N} (x_i - \bar{x}_{n-1})(y_i - \bar{y}_{n-1}) \\ +* &= \sum_{i=1}^{N-1} (x_i - \bar{x}_{n-1})(y_i - \bar{y}_{n-1}) + (x_N - \bar{x}_{n-1})(y_N - \bar{y}_{n-1}) \\ +* &= \sum_{i=1}^{N-1} (x_i - \bar{x}_{n-1})(y_i - \bar{y}_{n-1}) + (x_N - \bar{x}_{n-1})(y_N - \bar{y}_{n-1}) + (x_0 - \bar{x}_{n-1})(y_0 - \bar{y}_{n-1}) - (x_0 - \bar{x}_{n-1})(y_0 - \bar{y}_{n-1}) \\ +* &= \sum_{i=0}^{N-1} (x_i - \bar{x}_{n-1})(y_i - \bar{y}_{n-1}) + (x_N - \bar{x}_{n-1})(y_N - \bar{y}_{n-1}) - (x_0 - \bar{x}_{n-1})(y_0 - \bar{y}_{n-1}) +* \end{align*} +* ``` +* +* where we recognize that the first term equals the co-moment for the previous window +* +* ```tex +* C_{n-1} = \sum_{i=0}^{N-1} (x_i - \bar{x}_{n-1})(y_i - \bar{y}_{n-1}) +* ``` +* +* In which case, +* +* ```tex +* \begin{align*} +* \sum_{i=1}^{N} \Delta x_{i,n-1} \Delta y_{i,n-1} &= C_{n-1} + (x_N - \bar{x}_{n-1})(y_N - \bar{y}_{n-1}) - (x_0 - \bar{x}_{n-1})(y_0 - \bar{y}_{n-1}) \\ +* &= C_{n-1} + \Delta x_{N,n-1} \Delta y_{N,n-1} - \Delta x_{0,n-1} \Delta y_{0,n-1} +* \end{align*} +* ``` +* +* - Substituting into the equation for the co-moment +* +* ```tex +* C_n = C_{n-1} + \Delta x_{N,n-1} \Delta y_{N,n-1} - \Delta x_{0,n-1} \Delta y_{0,n-1} - \frac{(x_N - x_0)(y_N - y_0)}{N} +* ``` +* +* - We can make one further modification to the last term +* +* ```tex +* \begin{align*} +* \frac{(x_N - x_0)(y_N - y_0)}{N} &= \frac{(x_N - \bar{x}_{n-1} - x_0 + \bar{x}_{n-1})(y_N - \bar{y}_{n-1} - y_0 + \bar{y}_{n-1})}{N} \\ +* &= \frac{(\Delta x_{N,n-1} - \Delta x_{0,n-1})(\Delta y_{N,n-1} - \Delta y_{0,n-1})}{N} +* \end{align*} +* ``` +* +* which, upon substitution into the equation for the co-moment, yields +* +* ```tex +* C_n = C_{n-1} + \Delta x_{N,n-1} \Delta y_{N,n-1} - \Delta x_{0,n-1} \Delta y_{0,n-1} - \frac{(\Delta x_{N,n-1} - \Delta x_{0,n-1})(\Delta y_{N,n-1} - \Delta y_{0,n-1})}{N} +* ``` +* +* @param {PositiveInteger} W - window size +* @param {number} [meanx] - mean value +* @param {number} [meany] - mean value +* @throws {TypeError} first argument must be a positive integer +* @throws {TypeError} second argument must be a number +* @throws {TypeError} third argument must be a number +* @returns {Function} accumulator function +* +* @example +* var accumulator = incrnanmcovariance( 3 ); +* +* var v = accumulator(); +* // returns null +* +* v = accumulator( 2.0, 1.0 ); +* // returns 0.0 +* +* v = accumulator( -5.0, 3.14 ); +* // returns ~-7.49 +* +* v = accumulator( 3.0, -1.0 ); +* // returns -8.35 +* +* v = accumulator( 5.0, -9.5 ); +* // returns -29.42 +* +* v = accumulator(); +* // returns -29.42 +* +* @example +* var accumulator = incrnanmcovariance( 3, -2.0, 10.0 ); +*/ +function incrnanmcovariance( W, meanx, meany ) { + var mcovariance = ( arguments.length > 1 ) ? + incrmcovariance( W, meanx, meany ) : + incrmcovariance( W ); + + return accumulator; + + /** + * If provided a value, the accumulator function returns an updated unbiased sample covariance, while handling NaN values. If not provided a value, the accumulator function returns the current unbiased sample covariance. + * + * @private + * @param {number} [x] - input value + * @param {number} [y] - input value + * @returns {(number|null)} unbiased sample covariance or null + */ + function accumulator( x, y ) { + if ( isnan( x ) || isnan( y )) { + return mcovariance(); + } + return mcovariance( x, y ); + } +} + + +// EXPORTS // + +module.exports = incrnanmcovariance; diff --git a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/package.json b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/package.json new file mode 100644 index 000000000000..4f0b86a3f1c6 --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/package.json @@ -0,0 +1,77 @@ +{ + "name": "@stdlib/stats/incr/nanmcovariance", + "version": "0.0.0", + "description": "Compute a moving unbiased sample covariance incrementally, while handling 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", + "covariance", + "sample covariance", + "variance", + "unbiased", + "var", + "deviation", + "dispersion", + "standard deviation", + "stdev", + "correlation", + "corr", + "incremental", + "accumulator", + "moving covariance", + "sliding window", + "sliding", + "window", + "moving" + ] +} diff --git a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/test/test.js b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/test/test.js new file mode 100644 index 000000000000..2e5664292558 --- /dev/null +++ b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/test/test.js @@ -0,0 +1,403 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 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 randu = require( '@stdlib/random/base/randu' ); +var abs = require( '@stdlib/math/base/special/abs' ); +var pow = require( '@stdlib/math/base/special/pow' ); +var EPS = require( '@stdlib/constants/float64/eps' ); +var incrnanmcovariance = require( './../lib' ); + + +// FUNCTIONS // + +/** +* Computes sample means using Welford's algorithm. +* +* @private +* @param {Array} out - output array +* @param {ArrayArray} arr - input array +* @returns {Array} output array +*/ +function mean( out, arr ) { + var delta; + var mx; + var my; + var N; + var i; + + mx = 0.0; + my = 0.0; + + N = 0; + for ( i = 0; i < arr.length; i++ ) { + N += 1; + delta = arr[i][0] - mx; + mx += delta / N; + delta = arr[i][1] - my; + my += delta / N; + } + out[ 0 ] = mx; + out[ 1 ] = my; + return out; +} + +/** +* Computes the covariance using textbook formula. +* +* @private +* @param {ArrayArray} arr - input array +* @param {number} mx - `x` mean +* @param {number} my - `y` mean +* @param {boolean} bool - boolean indicating whether to compute a biased covariance +* @returns {number} covariance +*/ +function covariance( arr, mx, my, bool ) { + var N; + var C; + var i; + + N = arr.length; + C = 0.0; + for ( i = 0; i < N; i++ ) { + C += ( arr[i][0]-mx ) * ( arr[i][1]-my ); + } + if ( bool ) { + return C / N; + } + if ( N === 1 ) { + return 0.0; + } + return C / (N-1); +} + +/** +* Generates a set of sample datasets. +* +* @private +* @param {PositiveInteger} N - number of datasets +* @param {PositiveInteger} M - dataset length +* @param {PositiveInteger} [seed] - PRNG seed +* @returns {ArrayArray} sample datasets +*/ +function datasets( N, M, seed ) { + var data; + var rand; + var tmp; + var i; + var j; + + rand = randu.factory({ + 'seed': seed || ( randu()*pow( 2.0, 31 ) )|0 + }); + + // Generate datasets consisting of (x,y) pairs of varying value ranges... + data = []; + for ( i = 0; i < N; i++ ) { + tmp = []; + for ( j = 0; j < M; j++ ) { + tmp.push([ + rand() * pow( 10.0, i ), + rand() * pow( 10.0, i ) + ]); + } + data.push( tmp ); + } + return data; +} + + +// TESTS // + +tape( 'main export is a function', function test( t ) { + t.ok( true, __filename ); + t.strictEqual( typeof incrnanmcovariance, 'function', 'main export is a function' ); + t.end(); +}); + +tape( 'the function throws an error if not provided a positive integer for the window size', 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() { + incrnanmcovariance( value ); + }; + } +}); + +tape( 'the function throws an error if not provided a positive integer for the window size (known means)', function test( t ) { + var values; + var i; + + values = [ + '5', + -5.0, + 0.0, + 3.14, + true, + null, + void 0, + [], + {}, + 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() { + incrnanmcovariance( value, 3.0, 3.14 ); + }; + } +}); + +tape( 'the function throws an error if not provided a number as the mean value', function test( t ) { + var values; + var i; + + values = [ + '5', + true, + false, + null, + void 0, + [], + {}, + 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() { + incrnanmcovariance( 3, value, 3.14 ); + }; + } +}); + +tape( 'the function throws an error if not provided a number as the mean value', function test( t ) { + var values; + var i; + + values = [ + '5', + true, + false, + null, + void 0, + [], + {}, + 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() { + incrnanmcovariance( 3, 3.14, value ); + }; + } +}); + +tape( 'the function returns an accumulator function', function test( t ) { + t.equal( typeof incrnanmcovariance( 3 ), 'function', 'returns a function' ); + t.end(); +}); + +tape( 'the function returns an accumulator function (known means)', function test( t ) { + t.equal( typeof incrnanmcovariance( 3, 3.0, 3.14 ), 'function', 'returns a function' ); + t.end(); +}); + +tape( 'the accumulator function computes a moving unbiased sample covariance incrementally', function test( t ) { + var expected; + var actual; + var delta; + var means; + var data; + var acc; + var arr; + var tol; + var d; + var N; + var M; + var W; + var i; + var j; + + N = 10; + M = 100; + data = datasets( N, M, randu.seed ); + + // Define the window size: + W = 10; + + // For each dataset, compute the actual and expected covariance... + for ( i = 0; i < N; i++ ) { + d = data[ i ]; + + acc = incrnanmcovariance( W ); + for ( j = 0; j < M; j++ ) { + actual = acc( d[j][0], d[j][1] ); + if ( j < W ) { + arr = d.slice( 0, j+1 ); + } else { + arr = d.slice( j-W+1, j+1 ); + } + means = mean( [ 0.0, 0.0 ], arr ); + expected = covariance( arr, means[ 0 ], means[ 1 ], false ); + if ( actual === expected ) { + t.equal( actual, expected, 'returns expected value. dataset: '+i+'. window: '+j+'.' ); + } else { + delta = abs( actual - expected ); + tol = 5.0e5 * EPS * abs( expected ); + t.equal( delta < tol, true, 'dataset: '+i+'. window: '+j+'. expected: '+expected+'. actual: '+actual+'. tol: '+tol+'. delta: '+delta+'.' ); + } + } + } + t.end(); +}); + +tape( 'the accumulator function handles unknown means', function test( t ) { + var acc; + var v; + + acc = incrnanmcovariance( 3 ); + v = acc( 2.0, 1.0 ); + t.equal( v, 0.0, 'returns 0 for first value with unknown means' ); + + t.end(); +}); + +tape( 'the accumulator function handles known means', function test( t ) { + var acc; + var v; + + acc = incrnanmcovariance( 3, 3.0, 3.14 ); + v = acc( 2.0, 1.0 ); + t.notEqual( v, 0.0, 'does not return 0' ); + + t.end(); +}); + +tape( 'the accumulator function computes a moving unbiased sample covariance incrementally (known means)', function test( t ) { + var expected; + var actual; + var means; + var delta; + var data; + var acc; + var arr; + var tol; + var d; + var N; + var M; + var W; + var i; + var j; + + N = 10; + M = 100; + data = datasets( N, M, randu.seed ); + + // Define the window size: + W = 10; + + // For each dataset, compute the actual and expected covariance... + for ( i = 0; i < N; i++ ) { + d = data[ i ]; + means = mean( [ 0.0, 0.0 ], d ); + acc = incrnanmcovariance( W, means[ 0 ], means[ 1 ] ); + for ( j = 0; j < M; j++ ) { + actual = acc( d[j][0], d[j][1] ); + if ( j < W ) { + arr = d.slice( 0, j+1 ); + } else { + arr = d.slice( j-W+1, j+1 ); + } + expected = covariance( arr, means[ 0 ], means[ 1 ], true ); + if ( actual === expected ) { + t.equal( actual, expected, 'returns expected value. dataset: '+i+'. window: '+j+'.' ); + } else { + delta = abs( actual - expected ); + tol = 5.0e5 * EPS * abs( expected ); + t.equal( delta < tol, true, 'dataset: '+i+'. window: '+j+'. expected: '+expected+'. actual: '+actual+'. tol: '+tol+'. delta: '+delta+'.' ); + } + } + } + t.end(); +}); + +tape( 'if the window size is `1` and the means are unknown, the accumulator function always returns `0`', function test( t ) { + var acc; + var cov; + var i; + + acc = incrnanmcovariance( 1 ); + for ( i = 0; i < 100; i++ ) { + cov = acc( randu()*100.0, randu()*100.0 ); + t.equal( cov, 0.0, 'returns 0' ); + } + t.end(); +}); + +tape( 'if the window size is `1` and the means are known, the accumulator function may not always return `0`', function test( t ) { + var acc; + var cov; + var i; + + acc = incrnanmcovariance( 1, 500.0, -500.0 ); // means are outside the range of simulated values so the covariance should never be zero + for ( i = 0; i < 100; i++ ) { + cov = acc( randu()*100.0, randu()*100.0 ); + t.notEqual( cov, 0.0, 'does not return 0' ); + } + t.end(); +}); From 43c026516ff6b3444b694f8cdabcf13b192b4c4c Mon Sep 17 00:00:00 2001 From: Don Roy Chacko Date: Wed, 30 Apr 2025 13:35:31 +0530 Subject: [PATCH 2/2] fixup! feat(stat/incr/nanmcovariance): add extra nanmcovariance test cases This commit fixes a coverage issue with the previous commit where the NaN branch of the main.js file of the nanmcovariance package, is not adequetly tested. It now ensures that the branch for known and unknown means are tested and benchmarked, for full coverage. Authored by: Don Chacko --- .../stats/incr/nanmcovariance/README.md | 8 +- .../stats/incr/nanmcovariance/lib/main.js | 10 +- .../stats/incr/nanmcovariance/test/test.js | 107 ++++++++++-------- 3 files changed, 73 insertions(+), 52 deletions(-) diff --git a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/README.md b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/README.md index 313df144d3ce..43bda794c902 100644 --- a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/README.md +++ b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/README.md @@ -102,6 +102,9 @@ v = accumulator( -5.0, 3.14 ); // [(2.0, 1.0), (-5.0, 3.14)] v = accumulator( 3.0, -1.0 ); // [(2.0, 1.0), (-5.0, 3.14), (3.0, -1.0)] // returns -8.35 +v = accumulator( NaN, NaN ); +// returns -8.35 + // Window begins sliding... v = accumulator( 5.0, -9.5 ); // [(-5.0, 3.14), (3.0, -1.0), (5.0, -9.5)] // returns -29.42 @@ -109,6 +112,9 @@ v = accumulator( 5.0, -9.5 ); // [(-5.0, 3.14), (3.0, -1.0), (5.0, -9.5)] v = accumulator( -5.0, 1.5 ); // [(3.0, -1.0), (5.0, -9.5), (-5.0, 1.5)] // returns -24.5 +v = accumulator( NaN, 1.5 ); +// returns -24.5 + v = accumulator(); // returns -24.5 ``` @@ -159,7 +165,7 @@ console.log( accumulator() ); - [`@stdlib/stats/incr/covariance`][@stdlib/stats/incr/covariance]: compute an unbiased sample covariance incrementally. - [`@stdlib/stats/incr/mpcorr`][@stdlib/stats/incr/mpcorr]: compute a moving sample Pearson product-moment correlation coefficient incrementally. -- [`@stdlib/stats/incr/mvariance`][@stdlib/stats/incr/mvariance]: compute a moving unbiased sample variance incrementally. +- [`@stdlib/stats/incr/mcovariance`][@stdlib/stats/incr/mcovariance]: compute a moving unbiased sample variance incrementally. diff --git a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/lib/main.js b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/lib/main.js index 8a8622e5eb53..af920075d067 100644 --- a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/lib/main.js +++ b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/lib/main.js @@ -189,6 +189,12 @@ var incrmcovariance = require( '@stdlib/stats/incr/mcovariance' ); * v = accumulator( 5.0, -9.5 ); * // returns -29.42 * +* v = accumulator( NaN, -9.5 ); +* // returns -29.42 +* +* v = accumulator( NaN, NaN ); +* // returns -29.42 +* * v = accumulator(); * // returns -29.42 * @@ -208,10 +214,10 @@ function incrnanmcovariance( W, meanx, meany ) { * @private * @param {number} [x] - input value * @param {number} [y] - input value - * @returns {(number|null)} unbiased sample covariance or null + * @returns {(number)} unbiased sample covariance */ function accumulator( x, y ) { - if ( isnan( x ) || isnan( y )) { + if ( arguments.length === 0 || isnan( x ) || isnan( y )) { return mcovariance(); } return mcovariance( x, y ); diff --git a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/test/test.js b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/test/test.js index 2e5664292558..d0a413bce7c5 100644 --- a/lib/node_modules/@stdlib/stats/incr/nanmcovariance/test/test.js +++ b/lib/node_modules/@stdlib/stats/incr/nanmcovariance/test/test.js @@ -220,6 +220,64 @@ tape( 'the function throws an error if not provided a number as the mean value', } }); +tape( 'the accumulator function handles NaN values correctly with unknown means', function test( t ) { + var acc; + var v; + + acc = incrnanmcovariance( 3 ); + + v = acc( 2.0, 1.0 ); + t.equal( v, 0.0, 'returns 0 for first value' ); + + v = acc( NaN, 3.0 ); + t.equal( v, 0.0, 'returns current value when x is NaN' ); + + v = acc( 4.0, NaN ); + t.equal( v, 0.0, 'returns current value when y is NaN' ); + + v = acc( NaN, NaN ); + t.equal( v, 0.0, 'returns current value when both x and y are NaN' ); + + t.end(); +}); + +tape( 'the accumulator function handles NaN values correctly with known means', function test( t ) { + var current; + var acc; + var v; + + acc = incrnanmcovariance( 3, 2.0, 1.0 ); + + v = acc( 5.0, 2.0 ); + t.notEqual( v, 0.0, 'returns non-zero for first value' ); + + // Store current value to check + current = v; + + v = acc( NaN, 3.0 ); + t.equal( v, current, 'returns current value when x is NaN' ); + + v = acc( 4.0, NaN ); + t.equal( v, current, 'returns current value when y is NaN' ); + + v = acc( NaN, NaN ); + t.equal( v, current, 'returns current value when both x and y are NaN' ); + + t.end(); +}); + +tape( 'if data has yet to be provided, the accumulator function returns `null`', function test( t ) { + var acc = incrnanmcovariance( 3 ); + t.equal( acc(), null, 'returns null' ); + t.end(); +}); + +tape( 'if data has yet to be provided, the accumulator function returns `null` (known means)', function test( t ) { + var acc = incrnanmcovariance( 3, 3.0, 3.14 ); + t.equal( acc(), null, 'returns null' ); + t.end(); +}); + tape( 'the function throws an error if not provided a number as the mean value', function test( t ) { var values; var i; @@ -257,55 +315,6 @@ tape( 'the function returns an accumulator function (known means)', function tes t.end(); }); -tape( 'the accumulator function computes a moving unbiased sample covariance incrementally', function test( t ) { - var expected; - var actual; - var delta; - var means; - var data; - var acc; - var arr; - var tol; - var d; - var N; - var M; - var W; - var i; - var j; - - N = 10; - M = 100; - data = datasets( N, M, randu.seed ); - - // Define the window size: - W = 10; - - // For each dataset, compute the actual and expected covariance... - for ( i = 0; i < N; i++ ) { - d = data[ i ]; - - acc = incrnanmcovariance( W ); - for ( j = 0; j < M; j++ ) { - actual = acc( d[j][0], d[j][1] ); - if ( j < W ) { - arr = d.slice( 0, j+1 ); - } else { - arr = d.slice( j-W+1, j+1 ); - } - means = mean( [ 0.0, 0.0 ], arr ); - expected = covariance( arr, means[ 0 ], means[ 1 ], false ); - if ( actual === expected ) { - t.equal( actual, expected, 'returns expected value. dataset: '+i+'. window: '+j+'.' ); - } else { - delta = abs( actual - expected ); - tol = 5.0e5 * EPS * abs( expected ); - t.equal( delta < tol, true, 'dataset: '+i+'. window: '+j+'. expected: '+expected+'. actual: '+actual+'. tol: '+tol+'. delta: '+delta+'.' ); - } - } - } - t.end(); -}); - tape( 'the accumulator function handles unknown means', function test( t ) { var acc; var v;