main.js (4604B)
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 isArrayLike = require( '@stdlib/assert/is-array-like-object' ); 24 var isnan = require( '@stdlib/math/base/assert/is-nan' ); 25 var sqrt = require( '@stdlib/math/base/special/sqrt' ); 26 27 28 // MAIN // 29 30 /** 31 * Returns an accumulator function which incrementally computes an arithmetic mean and corrected sample standard deviation. 32 * 33 * ## Method 34 * 35 36 * 37 * - This implementation uses Welford's algorithm for efficient computation, which can be derived as follows. Let 38 * 39 * ```tex 40 * \begin{align*} 41 * S_n &= n \sigma_n^2 \\ 42 * &= \sum_{i=1}^{n} (x_i - \mu_n)^2 \\ 43 * &= \biggl(\sum_{i=1}^{n} x_i^2 \biggr) - n\mu_n^2 44 * \end{align*} 45 * ``` 46 * 47 * Accordingly, 48 * 49 * ```tex 50 * \begin{align*} 51 * S_n - S_{n-1} &= \sum_{i=1}^{n} x_i^2 - n\mu_n^2 - \sum_{i=1}^{n-1} x_i^2 + (n-1)\mu_{n-1}^2 \\ 52 * &= x_n^2 - n\mu_n^2 + (n-1)\mu_{n-1}^2 \\ 53 * &= x_n^2 - \mu_{n-1}^2 + n(\mu_{n-1}^2 - \mu_n^2) \\ 54 * &= x_n^2 - \mu_{n-1}^2 + n(\mu_{n-1} - \mu_n)(\mu_{n-1} + \mu_n) \\ 55 * &= x_n^2 - \mu_{n-1}^2 + (\mu_{n-1} - x_n)(\mu_{n-1} + \mu_n) \\ 56 * &= x_n^2 - \mu_{n-1}^2 + \mu_{n-1}^2 - x_n\mu_n - x_n\mu_{n-1} + \mu_n\mu_{n-1} \\ 57 * &= x_n^2 - x_n\mu_n - x_n\mu_{n-1} + \mu_n\mu_{n-1} \\ 58 * &= (x_n - \mu_{n-1})(x_n - \mu_n) \\ 59 * &= S_{n-1} + (x_n - \mu_{n-1})(x_n - \mu_n) 60 * \end{align*} 61 * ``` 62 * 63 * where we use the identity 64 * 65 * ```tex 66 * x_n - \mu_{n-1} = n (\mu_n - \mu_{n-1}) 67 * ``` 68 * 69 * ## References 70 * 71 * - Welford, B. P. 1962. "Note on a Method for Calculating Corrected Sums of Squares and Products." _Technometrics_ 4 (3). Taylor & Francis: 419–20. doi:[10.1080/00401706.1962.10490022](https://doi.org/10.1080/00401706.1962.10490022). 72 * - van Reeken, A. J. 1968. "Letters to the Editor: Dealing with Neely's Algorithms." _Communications of the ACM_ 11 (3): 149–50. doi:[10.1145/362929.362961](https://doi.org/10.1145/362929.362961). 73 * 74 * @param {Collection} [out] - output array 75 * @throws {TypeError} output argument must be array-like 76 * @returns {Function} accumulator function 77 * 78 * @example 79 * var accumulator = incrmeanstdev(); 80 * 81 * var ms = accumulator(); 82 * // returns null 83 * 84 * ms = accumulator( 2.0 ); 85 * // returns [ 2.0, 0.0 ] 86 * 87 * ms = accumulator( -5.0 ); 88 * // returns [ -1.5, ~4.95 ] 89 * 90 * ms = accumulator( 3.0 ); 91 * // returns [ 0.0, ~4.36 ] 92 * 93 * ms = accumulator( 5.0 ); 94 * // returns [ 1.25, ~4.35 ] 95 * 96 * ms = accumulator(); 97 * // returns [ 1.25, ~4.35 ] 98 */ 99 function incrmeanstdev( out ) { 100 var meanstdev; 101 var delta; 102 var mu; 103 var M2; 104 var N; 105 if ( arguments.length === 0 ) { 106 meanstdev = [ 0.0, 0.0 ]; 107 } else { 108 if ( !isArrayLike( out ) ) { 109 throw new TypeError( 'invalid argument. Output argument must be an array-like object. Value: `' + out + '`.' ); 110 } 111 meanstdev = out; 112 } 113 M2 = 0.0; 114 mu = 0.0; 115 N = 0; 116 return accumulator; 117 118 /** 119 * If provided a value, the accumulator function returns updated results. If not provided a value, the accumulator function returns the current results. 120 * 121 * @private 122 * @param {number} [x] - input value 123 * @returns {(ArrayLikeObject|null)} output array or null 124 */ 125 function accumulator( x ) { 126 if ( arguments.length === 0 ) { 127 if ( N === 0 ) { 128 return null; 129 } 130 meanstdev[ 0 ] = mu; // Why? Because we cannot guarantee someone hasn't mutated the output array 131 if ( N === 1 ) { 132 if ( isnan( M2 ) ) { 133 meanstdev[ 1 ] = NaN; 134 } else { 135 meanstdev[ 1 ] = 0.0; 136 } 137 return meanstdev; 138 } 139 meanstdev[ 1 ] = sqrt( M2/(N-1) ); 140 return meanstdev; 141 } 142 N += 1; 143 delta = x - mu; 144 mu += delta / N; 145 M2 += delta * ( x - mu ); 146 147 meanstdev[ 0 ] = mu; 148 if ( N < 2 ) { 149 if ( isnan( M2 ) ) { 150 meanstdev[ 1 ] = NaN; 151 } else { 152 meanstdev[ 1 ] = 0.0; 153 } 154 return meanstdev; 155 } 156 meanstdev[ 1 ] = sqrt( M2/(N-1) ); 157 return meanstdev; 158 } 159 } 160 161 162 // EXPORTS // 163 164 module.exports = incrmeanstdev;