Skip to content

Commit 7be5fd2

Browse files
committed
Refactor to better handle NaN values
1 parent 1c89960 commit 7be5fd2

File tree

2 files changed

+70
-20
lines changed

2 files changed

+70
-20
lines changed

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@
1111
accumulator function returns the current moving corrected sample standard
1212
deviation.
1313

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

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

Lines changed: 70 additions & 17 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
var sqrt = require( '@stdlib/math/base/special/sqrt' );
2627

2728

@@ -104,6 +105,8 @@ function incrmstdev( W, mean ) {
104105
* // returns ~5.29
105106
*/
106107
function accumulator1( x ) {
108+
var k;
109+
var v;
107110
if ( arguments.length === 0 ) {
108111
if ( N === 0 ) {
109112
return null;
@@ -119,9 +122,13 @@ function incrmstdev( W, mean ) {
119122
// Update the index for managing the circular buffer:
120123
i = (i+1) % W;
121124

122-
// Determine if we should update the initial window...
123-
if ( N < W ) {
124-
buf[ i ] = x;
125+
// Case: incoming value is NaN, the sliding second moment is automatically NaN...
126+
if ( isnan( x ) ) {
127+
M2 = NaN;
128+
}
129+
// Case: initial window...
130+
else if ( N < W ) {
131+
buf[ i ] = x; // update buffer
125132
N += 1;
126133
delta = x - mu;
127134
mu += delta / N;
@@ -131,18 +138,41 @@ function incrmstdev( W, mean ) {
131138
}
132139
return sqrt( M2 / (N-1) );
133140
}
134-
// N = W = 1
141+
// Case: N = W = 1
135142
if ( N === 1 ) {
136143
return 0.0;
137144
}
138-
// Update the existing window...
139-
tmp = buf[ i ];
145+
// Case: outgoing value is NaN, and, thus, we need to compute the accumulated values...
146+
if ( isnan( buf[ i ] ) ) {
147+
N = 1;
148+
mu = x;
149+
M2 = 0.0;
150+
for ( k = 0; k < W; k++ ) {
151+
if ( k !== i ) {
152+
v = buf[ k ];
153+
if ( isnan( v ) ) {
154+
M2 = NaN;
155+
break; // second moment is automatically NaN, so no need to continue
156+
}
157+
N += 1;
158+
delta = v - mu;
159+
mu += delta / N;
160+
M2 += delta * (v - mu);
161+
}
162+
}
163+
}
164+
// Case: neither the current second moment nor the incoming value are NaN, so we need to update the accumulated values...
165+
else if ( isnan( M2 ) === false ) {
166+
tmp = buf[ i ];
167+
delta = x - tmp;
168+
d1 = tmp - mu;
169+
mu += delta / W;
170+
d2 = x - mu;
171+
M2 += delta * (d1 + d2);
172+
}
173+
// Case: the current second moment is NaN, so nothing to do until the buffer no longer contains NaN values...
174+
140175
buf[ i ] = x;
141-
delta = x - tmp;
142-
d1 = tmp - mu;
143-
mu += delta / W;
144-
d2 = x - mu;
145-
M2 += delta * (d1 + d2);
146176
return sqrt( M2 / n );
147177
}
148178

@@ -154,6 +184,7 @@ function incrmstdev( W, mean ) {
154184
* @returns {(number|null)} corrected sample standard deviation or null
155185
*/
156186
function accumulator2( x ) {
187+
var k;
157188
if ( arguments.length === 0 ) {
158189
if ( N === 0 ) {
159190
return null;
@@ -166,18 +197,40 @@ function incrmstdev( W, mean ) {
166197
// Update the index for managing the circular buffer:
167198
i = (i+1) % W;
168199

169-
// Determine if we should update the initial window...
170-
if ( N < W ) {
171-
buf[ i ] = x;
200+
// Case: incoming value is NaN, the sliding second moment is automatically NaN...
201+
if ( isnan( x ) ) {
202+
M2 = NaN;
203+
}
204+
// Case: initial window...
205+
else if ( N < W ) {
206+
buf[ i ] = x; // update buffer
172207
N += 1;
173208
delta = x - mu;
174209
M2 += delta * delta;
175210
return sqrt( M2 / N );
176211
}
177-
// Update the existing window...
178-
tmp = buf[ i ];
212+
// Case: outgoing value is NaN, and, thus, we need to compute the accumulated values...
213+
if ( isnan( buf[ i ] ) ) {
214+
M2 = 0.0;
215+
for ( k = 0; k < W; k++ ) {
216+
if ( k !== i ) {
217+
if ( isnan( buf[ k ] ) ) {
218+
M2 = NaN;
219+
break; // second moment is automatically NaN, so no need to continue
220+
}
221+
delta = buf[ k ] - mu;
222+
M2 += delta * delta;
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+
M2 += ( x-tmp ) * ( x-mu + tmp-mu );
230+
}
231+
// Case: the current second moment is NaN, so nothing to do until the buffer no longer contains NaN values...
232+
179233
buf[ i ] = x;
180-
M2 += ( x-tmp ) * ( x-mu + tmp-mu );
181234
return sqrt( M2 / W );
182235
}
183236
}

0 commit comments

Comments
 (0)