Skip to content

Commit 83168ea

Browse files
committed
Refactor to better handle NaN values
1 parent 9f10e37 commit 83168ea

File tree

2 files changed

+75
-21
lines changed

2 files changed

+75
-21
lines changed

lib/node_modules/@stdlib/stats/incr/mvariance/docs/repl.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@
1010
unbiased sample variance. If not provided a value, the accumulator function
1111
returns the current moving unbiased sample variance.
1212

13-
If provided `NaN` or a value which, when used in computations, results in
14-
`NaN`, the accumulated value is `NaN` for all future invocations.
15-
1613
The first `W-1` returned unbiased sample variance values will have less
1714
statistical support than subsequent unbiased sample variance values, as `W`
1815
values are needed to fill the window buffer. Until the window is full, the

lib/node_modules/@stdlib/stats/incr/mvariance/lib/main.js

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
var isPositiveInteger = require( '@stdlib/assert/is-positive-integer' ).isPrimitive;
2424
var isNumber = require( '@stdlib/assert/is-number' ).isPrimitive;
25+
var isnan = require( '@stdlib/math/base/assert/is-nan' );
2526

2627

2728
// MAIN //
@@ -164,6 +165,8 @@ function incrmvariance( W, mean ) {
164165
* // returns 28.0
165166
*/
166167
function accumulator1( x ) {
168+
var k;
169+
var v;
167170
if ( arguments.length === 0 ) {
168171
if ( N === 0 ) {
169172
return null;
@@ -179,9 +182,14 @@ function incrmvariance( W, mean ) {
179182
// Update the index for managing the circular buffer:
180183
i = (i+1) % W;
181184

182-
// Determine if we should update the initial window...
183-
if ( N < W ) {
184-
buf[ i ] = x;
185+
// Case: incoming value is NaN, the sliding second moment is automatically NaN...
186+
if ( isnan( x ) ) {
187+
N = W; // explicitly set to avoid `N < W` branch
188+
M2 = NaN;
189+
}
190+
// Case: initial window...
191+
else if ( N < W ) {
192+
buf[ i ] = x; // update buffer
185193
N += 1;
186194
delta = x - mu;
187195
mu += delta / N;
@@ -191,18 +199,42 @@ function incrmvariance( W, mean ) {
191199
}
192200
return M2 / (N-1);
193201
}
194-
// N = W = 1
195-
if ( N === 1 ) {
202+
// Case: N = W = 1
203+
else if ( N === 1 ) {
196204
return 0.0;
197205
}
198-
// Update the existing window...
199-
tmp = buf[ i ];
206+
// Case: outgoing value is NaN, and, thus, we need to compute the accumulated values...
207+
else if ( isnan( buf[ i ] ) ) {
208+
N = 1;
209+
mu = x;
210+
M2 = 0.0;
211+
for ( k = 0; k < W; k++ ) {
212+
if ( k !== i ) {
213+
v = buf[ k ];
214+
if ( isnan( v ) ) {
215+
N = W; // explicitly set to avoid `N < W` branch
216+
M2 = NaN;
217+
break; // second moment is automatically NaN, so no need to continue
218+
}
219+
N += 1;
220+
delta = v - mu;
221+
mu += delta / N;
222+
M2 += delta * (v - mu);
223+
}
224+
}
225+
}
226+
// Case: neither the current second moment nor the incoming value are NaN, so we need to update the accumulated values...
227+
else if ( isnan( M2 ) === false ) {
228+
tmp = buf[ i ];
229+
delta = x - tmp;
230+
d1 = tmp - mu;
231+
mu += delta / W;
232+
d2 = x - mu;
233+
M2 += delta * (d1 + d2);
234+
}
235+
// Case: the current second moment is NaN, so nothing to do until the buffer no longer contains NaN values...
236+
200237
buf[ i ] = x;
201-
delta = x - tmp;
202-
d1 = tmp - mu;
203-
mu += delta / W;
204-
d2 = x - mu;
205-
M2 += delta * (d1 + d2);
206238
return M2 / n;
207239
}
208240

@@ -214,6 +246,7 @@ function incrmvariance( W, mean ) {
214246
* @returns {(number|null)} unbiased sample variance or null
215247
*/
216248
function accumulator2( x ) {
249+
var k;
217250
if ( arguments.length === 0 ) {
218251
if ( N === 0 ) {
219252
return null;
@@ -226,18 +259,42 @@ function incrmvariance( W, mean ) {
226259
// Update the index for managing the circular buffer:
227260
i = (i+1) % W;
228261

229-
// Determine if we should update the initial window...
230-
if ( N < W ) {
231-
buf[ i ] = x;
262+
// Case: incoming value is NaN, the sliding second moment is automatically NaN...
263+
if ( isnan( x ) ) {
264+
N = W; // explicitly set to avoid `N < W` branch
265+
M2 = NaN;
266+
}
267+
// Case: initial window...
268+
else if ( N < W ) {
269+
buf[ i ] = x; // update buffer
232270
N += 1;
233271
delta = x - mu;
234272
M2 += delta * delta;
235273
return M2 / N;
236274
}
237-
// Update the existing window...
238-
tmp = buf[ i ];
275+
// Case: outgoing value is NaN, and, thus, we need to compute the accumulated values...
276+
else if ( isnan( buf[ i ] ) ) {
277+
M2 = 0.0;
278+
for ( k = 0; k < W; k++ ) {
279+
if ( k !== i ) {
280+
if ( isnan( buf[ k ] ) ) {
281+
N = W; // explicitly set to avoid `N < W` branch
282+
M2 = NaN;
283+
break; // second moment is automatically NaN, so no need to continue
284+
}
285+
delta = buf[ k ] - mu;
286+
M2 += delta * delta;
287+
}
288+
}
289+
}
290+
// Case: neither the current second moment nor the incoming value are NaN, so we need to update the accumulated values...
291+
else if ( isnan( M2 ) === false ) {
292+
tmp = buf[ i ];
293+
M2 += ( x-tmp ) * ( x-mu + tmp-mu );
294+
}
295+
// Case: the current second moment is NaN, so nothing to do until the buffer no longer contains NaN values...
296+
239297
buf[ i ] = x;
240-
M2 += ( x-tmp ) * ( x-mu + tmp-mu );
241298
return M2 / W;
242299
}
243300
}

0 commit comments

Comments
 (0)