Skip to content

Commit 0620122

Browse files
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#5628 Authored-by: Don Chacko <[email protected]>
1 parent 6570841 commit 0620122

File tree

12 files changed

+1488
-0
lines changed

12 files changed

+1488
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
<!--
2+
3+
@license Apache-2.0
4+
5+
Copyright (c) 2025 The Stdlib Authors.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
19+
-->
20+
21+
# incrnanmcovariance
22+
23+
> Compute a moving [unbiased sample covariance][covariance] incrementally, while handling NaN values.
24+
25+
<section class="intro">
26+
27+
For unknown population means, the [unbiased sample covariance][covariance] for a window `n` of size `W` is defined as
28+
29+
<!-- <equation class="equation" label="eq:unbiased_sample_covariance_unknown_means" align="center" raw="\operatorname{cov_n} = \frac{1}{n-1} \sum_{i=j}^{j+W-1} (x_i - \bar{x}_n)(y_i - \bar{y}_n)" alt="Equation for the unbiased sample covariance for unknown population means."> -->
30+
31+
```math
32+
\mathop{\mathrm{cov_n}} = \frac{1}{n-1} \sum_{i=j}^{j+W-1} (x_i - \bar{x}_n)(y_i - \bar{y}_n)
33+
```
34+
35+
<!-- <div class="equation" align="center" data-raw-text="\operatorname{cov_n} = \frac{1}{n-1} \sum_{i=j}^{j+W-1} (x_i - \bar{x}_n)(y_i - \bar{y}_n)" data-equation="eq:unbiased_sample_covariance_unknown_means">
36+
<img src="https://cdn.jsdelivr.net/gh/stdlib-js/stdlib@49d8cabda84033d55d7b8069f19ee3dd8b8d1496/lib/node_modules/@stdlib/stats/incr/mcovariance/docs/img/equation_unbiased_sample_covariance_unknown_means.svg" alt="Equation for the unbiased sample covariance for unknown population means.">
37+
<br>
38+
</div> -->
39+
40+
<!-- </equation> -->
41+
42+
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.
43+
44+
For known population means, the [unbiased sample covariance][covariance] for a window `n` of size `W` is defined as
45+
46+
<!-- <equation class="equation" label="eq:unbiased_sample_covariance_known_means" align="center" raw="\operatorname{cov_n} = \frac{1}{n} \sum_{i=j}^{j+W-1} (x_i - \mu_x)(y_i - \mu_y)" alt="Equation for the unbiased sample covariance for known population means."> -->
47+
48+
```math
49+
\mathop{\mathrm{cov_n}} = \frac{1}{n} \sum_{i=j}^{j+W-1} (x_i - \mu_x)(y_i - \mu_y)
50+
```
51+
52+
<!-- <div class="equation" align="center" data-raw-text="\operatorname{cov_n} = \frac{1}{n} \sum_{i=j}^{j+W-1} (x_i - \mu_x)(y_i - \mu_y)" data-equation="eq:unbiased_sample_covariance_known_means">
53+
<img src="https://cdn.jsdelivr.net/gh/stdlib-js/stdlib@27e2a43c70db648bb5bbc3fd0cdee050c25adc0b/lib/node_modules/@stdlib/stats/incr/mcovariance/docs/img/equation_unbiased_sample_covariance_known_means.svg" alt="Equation for the unbiased sample covariance for known population means.">
54+
<br>
55+
</div> -->
56+
57+
<!-- </equation> -->
58+
59+
</section>
60+
61+
<!-- /.intro -->
62+
63+
<section class="usage">
64+
65+
## Usage
66+
67+
```javascript
68+
var incrnanmcovariance = require( '@stdlib/stats/incr/nanmcovariance' );
69+
```
70+
71+
#### incrnanmcovariance( window\[, mx, my] )
72+
73+
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].
74+
75+
```javascript
76+
var accumulator = incrnanmcovariance( 3 );
77+
```
78+
79+
If means are already known, provide `mx` and `my` arguments.
80+
81+
```javascript
82+
var accumulator = incrnanmcovariance( 3, 5.0, -3.14 );
83+
```
84+
85+
#### accumulator( \[x, y] )
86+
87+
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].
88+
89+
```javascript
90+
var accumulator = incrnanmcovariance( 3 );
91+
92+
var v = accumulator();
93+
// returns null
94+
95+
// Fill the window...
96+
v = accumulator( 2.0, 1.0 ); // [(2.0, 1.0)]
97+
// returns 0.0
98+
99+
v = accumulator( -5.0, 3.14 ); // [(2.0, 1.0), (-5.0, 3.14)]
100+
// returns ~-7.49
101+
102+
v = accumulator( 3.0, -1.0 ); // [(2.0, 1.0), (-5.0, 3.14), (3.0, -1.0)]
103+
// returns -8.35
104+
105+
// Window begins sliding...
106+
v = accumulator( 5.0, -9.5 ); // [(-5.0, 3.14), (3.0, -1.0), (5.0, -9.5)]
107+
// returns -29.42
108+
109+
v = accumulator( -5.0, 1.5 ); // [(3.0, -1.0), (5.0, -9.5), (-5.0, 1.5)]
110+
// returns -24.5
111+
112+
v = accumulator();
113+
// returns -24.5
114+
```
115+
116+
</section>
117+
118+
<!-- /.usage -->
119+
120+
121+
<section class="examples">
122+
123+
## Examples
124+
125+
<!-- eslint no-undef: "error" -->
126+
127+
```javascript
128+
var randu = require( '@stdlib/random/base/randu' );
129+
var incrnanmcovariance = require( '@stdlib/stats/incr/nanmcovariance' );
130+
131+
var accumulator;
132+
var x;
133+
var y;
134+
var i;
135+
136+
// Initialize an accumulator:
137+
accumulator = incrnanmcovariance( 5 );
138+
139+
// For each simulated datum, update the moving unbiased sample covariance...
140+
for ( i = 0; i < 100; i++ ) {
141+
x = randu() * 100.0;
142+
y = randu() * 100.0;
143+
accumulator( x, y );
144+
}
145+
console.log( accumulator() );
146+
```
147+
148+
</section>
149+
150+
<!-- /.examples -->
151+
152+
<!-- Section for related `stdlib` packages. Do not manually edit this section, as it is automatically populated. -->
153+
154+
<section class="related">
155+
156+
* * *
157+
158+
## See Also
159+
160+
- <span class="package-name">[`@stdlib/stats/incr/covariance`][@stdlib/stats/incr/covariance]</span><span class="delimiter">: </span><span class="description">compute an unbiased sample covariance incrementally.</span>
161+
- <span class="package-name">[`@stdlib/stats/incr/mpcorr`][@stdlib/stats/incr/mpcorr]</span><span class="delimiter">: </span><span class="description">compute a moving sample Pearson product-moment correlation coefficient incrementally.</span>
162+
- <span class="package-name">[`@stdlib/stats/incr/mvariance`][@stdlib/stats/incr/mvariance]</span><span class="delimiter">: </span><span class="description">compute a moving unbiased sample variance incrementally.</span>
163+
164+
</section>
165+
166+
<!-- /.related -->
167+
168+
<!-- Section for all links. Make sure to keep an empty line after the `section` element and another before the `/section` close. -->
169+
170+
<section class="links">
171+
172+
[covariance]: https://en.wikipedia.org/wiki/Covariance
173+
174+
<!-- <related-links> -->
175+
176+
[@stdlib/stats/incr/covariance]: https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/stats/incr/covariance
177+
178+
[@stdlib/stats/incr/mpcorr]: https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/stats/incr/mpcorr
179+
180+
[@stdlib/stats/incr/mcovariance]: https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/stats/incr/mcovariance
181+
182+
<!-- </related-links> -->
183+
184+
</section>
185+
186+
<!-- /.links -->
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 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 bench = require( '@stdlib/bench' );
24+
var randu = require( '@stdlib/random/base/randu' );
25+
var isnan = require( '@stdlib/math/base/assert/is-nan' );
26+
var pkg = require( './../package.json' ).name;
27+
var incrnanmcovariance = require( './../lib' );
28+
29+
30+
// MAIN //
31+
32+
bench( pkg, function benchmark( b ) {
33+
var f;
34+
var i;
35+
b.tic();
36+
for ( i = 0; i < b.iterations; i++ ) {
37+
f = incrnanmcovariance( (i%5)+1 );
38+
if ( typeof f !== 'function' ) {
39+
b.fail( 'should return a function' );
40+
}
41+
}
42+
b.toc();
43+
if ( typeof f !== 'function' ) {
44+
b.fail( 'should return a function' );
45+
}
46+
b.pass( 'benchmark finished' );
47+
b.end();
48+
});
49+
50+
bench( pkg+'::accumulator', function benchmark( b ) {
51+
var acc;
52+
var v;
53+
var i;
54+
55+
acc = incrnanmcovariance( 5 );
56+
57+
b.tic();
58+
for ( i = 0; i < b.iterations; i++ ) {
59+
v = acc( randu(), randu() );
60+
if ( isnan( v ) ) {
61+
b.fail( 'should not return NaN' );
62+
}
63+
}
64+
b.toc();
65+
if ( isnan( v ) ) {
66+
b.fail( 'should not return NaN' );
67+
}
68+
b.pass( 'benchmark finished' );
69+
b.end();
70+
});
71+
72+
bench( pkg+'::accumulator,known_means', function benchmark( b ) {
73+
var acc;
74+
var v;
75+
var i;
76+
77+
acc = incrnanmcovariance( 5, 3.0, -1.0 );
78+
79+
b.tic();
80+
for ( i = 0; i < b.iterations; i++ ) {
81+
v = acc( randu(), randu() );
82+
if ( isnan( v ) ) {
83+
b.fail( 'should not return NaN' );
84+
}
85+
}
86+
b.toc();
87+
if ( isnan( v ) ) {
88+
b.fail( 'should not return NaN' );
89+
}
90+
b.pass( 'benchmark finished' );
91+
b.end();
92+
});

0 commit comments

Comments
 (0)