meanstdev.js (6370B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2018 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 isnan = require( '@stdlib/math/base/assert/is-nan' ); 24 var sqrt = require( '@stdlib/math/base/special/sqrt' ); 25 26 27 // MAIN // 28 29 /** 30 * Returns an accumulator function which incrementally computes a moving arithmetic mean and corrected sample standard deviation. 31 * 32 * ## Method 33 * 34 * - Let \\(W\\) be a window of \\(N\\) elements over which we want to compute a corrected sample standard deviation. 35 * 36 * - We first recognize that the corrected sample standard deviation is defined as the square root of the unbiased sample variance. Accordingly, in order to derive an update equation for the corrected sample standard deviation, deriving an update equation for the unbiased sample variance is sufficient. 37 * 38 * - The difference between the unbiased sample variance in a window \\(W_i\\) and the unbiased sample variance in a window \\(W_{i+1})\\) is given by 39 * 40 * ```tex 41 * \Delta s^2 = s_{i+1}^2 - s_{i}^2 42 * ``` 43 * 44 * - If we multiply both sides by \\(N-1\\), 45 * 46 * ```tex 47 * (N-1)(\Delta s^2) = (N-1)s_{i+1}^2 - (N-1)s_{i}^2 48 * ``` 49 * 50 * - If we substitute the definition of the unbiased sample variance having the form 51 * 52 * ```tex 53 * \begin{align*} 54 * s^2 &= \frac{1}{N-1} \biggl( \sum_{i=1}^{N} (x_i - \bar{x})^2 \biggr) \\ 55 * &= \frac{1}{N-1} \biggl( \sum_{i=1}^{N} (x_i^2 - 2\bar{x}x_i + \bar{x}^2) \biggr) \\ 56 * &= \frac{1}{N-1} \biggl( \sum_{i=1}^{N} x_i^2 - 2\bar{x} \sum_{i=1}^{N} x_i + \sum_{i=1}^{N} \bar{x}^2) \biggr) \\ 57 * &= \frac{1}{N-1} \biggl( \sum_{i=1}^{N} x_i^2 - \frac{2N\bar{x}\sum_{i=1}^{N} x_i}{N} + N\bar{x}^2 \biggr) \\ 58 * &= \frac{1}{N-1} \biggl( \sum_{i=1}^{N} x_i^2 - 2N\bar{x}^2 + N\bar{x}^2 \biggr) \\ 59 * &= \frac{1}{N-1} \biggl( \sum_{i=1}^{N} x_i^2 - N\bar{x}^2 \biggr) 60 * \end{align*} 61 * ``` 62 * 63 * we return 64 * 65 * ```tex 66 * (N-1)(\Delta s^2) = \biggl(\sum_{k=1}^N x_k^2 - N\bar{x}_{i+1}^2 \biggr) - \biggl(\sum_{k=0}^{N-1} x_k^2 - N\bar{x}_{i}^2 \biggr) 67 * ``` 68 * 69 * - This can be further simplified by recognizing that subtracting the sums reduces to \\(x_N^2 - x_0^2\\); in which case, 70 * 71 * ```tex 72 * \begin{align*} 73 * (N-1)(\Delta s^2) &= x_N^2 - x_0^2 - N\bar{x}_{i+1}^2 + N\bar{x}_{i}^2 \\ 74 * &= x_N^2 - x_0^2 - N(\bar{x}_{i+1}^2 - \bar{x}_{i}^2) \\ 75 * &= x_N^2 - x_0^2 - N(\bar{x}_{i+1} - \bar{x}_{i})(\bar{x}_{i+1} + \bar{x}_{i}) 76 * \end{align*} 77 * ``` 78 * 79 * - Recognizing that the difference of means can be expressed 80 * 81 * ```tex 82 * \bar{x}_{i+1} - \bar{x}_i = \frac{1}{N} \biggl( \sum_{k=1}^N x_k - \sum_{k=0}^{N-1} x_k \biggr) = \frac{x_N - x_0}{N} 83 * ``` 84 * 85 * and substituting into the equation above 86 * 87 * ```tex 88 * (N-1)(\Delta s^2) = x_N^2 - x_0^2 - (x_N - x_0)(\bar{x}_{i+1} + \bar{x}_{i}) 89 * ``` 90 * 91 * - Rearranging terms gives us the update equation 92 * 93 * ```tex 94 * \begin{align*} 95 * (N-1)(\Delta s^2) &= (x_N - x_0)(x_N + x_0) - (x_N - x_0)(\bar{x}_{i+1} + \bar{x}_{i}) 96 * &= (x_N - x_0)(x_N + x_0 - \bar{x}_{i+1} - \bar{x}_{i}) \\ 97 * &= (x_N - x_0)(x_N - \bar{x}_{i+1} + x_0 - \bar{x}_{i}) 98 * \end{align*} 99 * ``` 100 * 101 * @private 102 * @param {Collection} out - output array 103 * @param {PositiveInteger} W - window size 104 * @param {Collection} buf - data buffer 105 * @returns {Function} accumulator function 106 * 107 * @example 108 * var buf = [ 0.0, 0.0, 0.0 ]; 109 * var accumulator = incrmmeanstdev( [ 0.0, 0.0 ], 3, buf ); 110 * 111 * var v = accumulator( 2.0, 0 ); 112 * // returns [ 2.0, 0.0 ] 113 * 114 * buf[ 0 ] = 2.0; 115 * 116 * v = accumulator( -5.0, 1 ); 117 * // returns [ -1.5, ~4.95 ] 118 * 119 * buf[ 1 ] = -5.0; 120 * 121 * v = accumulator( 3.0, 2 ); 122 * // returns [ 0.0, ~4.36 ] 123 * 124 * buf[ 2 ] = 3.0; 125 * 126 * v = accumulator( 5.0, 0 ); 127 * // returns [ 1.0, ~5.29 ] 128 * 129 * buf[ 0 ] = 5.0; 130 */ 131 function incrmmeanstdev( out, W, buf ) { 132 var delta; 133 var tmp; 134 var M2; 135 var mu; 136 var d1; 137 var d2; 138 var N; 139 var n; 140 141 n = W - 1; 142 M2 = 0.0; 143 mu = 0.0; 144 N = 0; 145 146 return accumulator; 147 148 /** 149 * Updates accumulator state. 150 * 151 * @private 152 * @param {number} x - input value 153 * @param {NonNegativeInteger} i - buffer index 154 * @returns {ArrayLike} output array 155 */ 156 function accumulator( x, i ) { 157 var k; 158 var v; 159 160 // Case: incoming value is NaN, the sliding second moment is automatically NaN... 161 if ( isnan( x ) ) { 162 N = W; // explicitly set to avoid `N < W` branch 163 mu = NaN; 164 M2 = NaN; 165 } 166 // Case: initial window... 167 else if ( N < W ) { 168 N += 1; 169 delta = x - mu; 170 mu += delta / N; 171 M2 += delta * (x - mu); 172 173 out[ 0 ] = mu; 174 if ( N === 1 ) { 175 out[ 1 ] = 0.0; 176 } else { 177 out[ 1 ] = sqrt( M2/(N-1) ); 178 } 179 return out; 180 } 181 // Case: N = W = 1 182 else if ( N === 1 ) { 183 mu = x; 184 M2 = 0.0; 185 out[ 0 ] = x; 186 out[ 1 ] = 0.0; 187 return out; 188 } 189 // Case: outgoing value is NaN, and, thus, we need to compute the accumulated values... 190 else if ( isnan( buf[ i ] ) ) { 191 N = 1; 192 mu = x; 193 M2 = 0.0; 194 for ( k = 0; k < W; k++ ) { 195 if ( k !== i ) { 196 v = buf[ k ]; 197 if ( isnan( v ) ) { 198 N = W; // explicitly set to avoid `N < W` branch 199 mu = NaN; 200 M2 = NaN; 201 break; // second moment is automatically NaN, so no need to continue 202 } 203 N += 1; 204 delta = v - mu; 205 mu += delta / N; 206 M2 += delta * (v - mu); 207 } 208 } 209 } 210 // Case: neither the current second moment nor the incoming value are NaN, so we need to update the accumulated values... 211 else if ( isnan( M2 ) === false ) { 212 tmp = buf[ i ]; 213 delta = x - tmp; 214 d1 = tmp - mu; 215 mu += delta / W; 216 d2 = x - mu; 217 M2 += delta * (d1 + d2); 218 } 219 // Case: the current second moment is NaN, so nothing to do until the buffer no longer contains NaN values... 220 221 out[ 0 ] = mu; 222 out[ 1 ] = sqrt( M2/n ); 223 return out; 224 } 225 } 226 227 228 // EXPORTS // 229 230 module.exports = incrmmeanstdev;