Algorithms for calculating variance play a major role in computational statistics. A key difficulty in the design of good algorithms for this problem is that formulas for the variance may involve sums of squares, which can lead to numerical instability as well as to arithmetic overflow when dealing with large values.
A formula for calculating the variance of an entire population of size N is:
\sigma2=\overline{(x2)}-\barx2=
| ||||||||||||||||||||||||||||
N |
.
Using Bessel's correction to calculate an unbiased estimate of the population variance from a finite sample of n observations, the formula is:
s2=\left(
| ||||||||||||||||
n |
-\left(
| ||||||||||
n |
\right)2\right) ⋅
n | |
n-1 |
.
Therefore, a naïve algorithm to calculate the estimated variance is given by the following:
This algorithm can easily be adapted to compute the variance of a finite population: simply divide by n instead of n − 1 on the last line.
Because and can be very similar numbers, cancellation can lead to the precision of the result to be much less than the inherent precision of the floating-point arithmetic used to perform the computation. Thus this algorithm should not be used in practice,[1] [2] and several alternate, numerically stable, algorithms have been proposed.[3] This is particularly bad if the standard deviation is small relative to the mean.
The variance is invariant with respect to changes in a location parameter, a property which can be used to avoid the catastrophic cancellation in this formula.
\operatorname{Var}(X-K)=\operatorname{Var}(X).
with
K
\sigma2=
| ||||||||||||||||||||||||||||
n-1 |
.
the closer
K
(xi-K)
If just the first sample is taken as
K
This formula also facilitates the incremental computation that can be expressed as
def add_variable(x): global K, n, Ex, Ex2 if n
def remove_variable(x): global K, n, Ex, Ex2 n -= 1 Ex -= x - K Ex2 -= (x - K) ** 2
def get_mean: global K, n, Ex return K + Ex / n
def get_variance: global n, Ex, Ex2 return (Ex2 - Ex**2 / n) / (n - 1)
An alternative approach, using a different formula for the variance, first computes the sample mean,
\barx=
| ||||||||||
n, |
samplevariance=s2=\dfrac
n | |
{\sum | |
i=1 |
(xi-\barx)2}{n-1},
This algorithm is numerically stable if n is small.[1] [4] However, the results of both of these simple algorithms ("naïve" and "two-pass") can depend inordinately on the ordering of the data and can give poor results for very large data sets due to repeated roundoff error in the accumulation of the sums. Techniques such as compensated summation can be used to combat this error to a degree.
It is often useful to be able to compute the variance in a single pass, inspecting each value
xi
The following formulas can be used to update the mean and (estimated) variance of the sequence, for an additional element xn. Here, denotes the sample mean of the first n samples
(x1,...,xn)
\barxn=
(n-1)\barxn-1+xn | |
n |
=\barxn-1+
xn-\barxn-1 | |
n |
2 | |
\sigma | |
n |
=
| |||||||||
n |
=
2 | |
\sigma | |
n-1 |
+
| |||||||||||||
n |
.
2 | |
s | |
n |
=
n-2 | |
n-1 |
2 | |
s | |
n-1 |
+
(xn-\barxn-1)2 | |
n |
=
2 | |
s | |
n-1 |
+
(xn-\barxn-1)2 | |
n |
-
| |||||||
n-1 |
, n>1
These formulas suffer from numerical instability, as they repeatedly subtract a small number from a big number which scales with n. A better quantity for updating is the sum of squares of differences from the current mean, , here denoted
M2,n
\begin{align} M2,n&=M2,n-1+(xn-\barxn-1)(xn-\barxn)
2 | |
\\[4pt] \sigma | |
n |
&=
M2,n | |
n |
2 | |
\\[4pt] s | |
n |
&=
M2,n | |
n-1 |
\end{align}
This algorithm was found by Welford,[5] [6] and it has been thoroughly analyzed.[7] It is also common to denote
Mk=\barxk
Sk=M2,k
An example Python implementation for Welford's algorithm is given below.
def update(existing_aggregate, new_value): (count, mean, M2) = existing_aggregate count += 1 delta = new_value - mean mean += delta / count delta2 = new_value - mean M2 += delta * delta2 return (count, mean, M2)
def finalize(existing_aggregate): (count, mean, M2) = existing_aggregate if count < 2: return float("nan") else: (mean, variance, sample_variance) = (mean, M2 / count, M2 / (count - 1)) return (mean, variance, sample_variance)
This algorithm is much less prone to loss of precision due to catastrophic cancellation, but might not be as efficient because of the division operation inside the loop. For a particularly robust two-pass algorithm for computing the variance, one can first compute and subtract an estimate of the mean, and then use this algorithm on the residuals.
The parallel algorithm below illustrates how to merge multiple sets of statistics calculated online.
The algorithm can be extended to handle unequal sample weights, replacing the simple counter n with the sum of weights seen so far. West (1979)[9] suggests this incremental algorithm:
for x, w in data_weight_pairs: w_sum = w_sum + w w_sum2 = w_sum2 + w**2 mean_old = mean mean = mean_old + (w / w_sum) * (x - mean_old) S = S + w * (x - mean_old) * (x - mean)
population_variance = S / w_sum # Bessel's correction for weighted samples # Frequency weights sample_frequency_variance = S / (w_sum - 1) # Reliability weights sample_reliability_variance = S / (w_sum - w_sum2 / w_sum)
Chan et al.[10] note that Welford's online algorithm detailed above is a special case of an algorithm that works for combining arbitrary sets
A
B
\begin{align} nAB&=nA+nB\\ \delta&=\barxB-\barxA\\ \barxAB&=\barxA+\delta ⋅
nB | |
nAB |
\\ M2,AB&=M2,A+M2,B+
| ||||
\delta |
\\ \end{align}
Chan's method for estimating the mean is numerically unstable when
nA ≈ nB
\delta=\barxB-\barxA
nB=1
Assume that all floating point operations use standard IEEE 754 double-precision arithmetic. Consider the sample (4, 7, 13, 16) from an infinite population. Based on this sample, the estimated population mean is 10, and the unbiased estimate of population variance is 30. Both the naïve algorithm and two-pass algorithm compute these values correctly.
Next consider the sample, which gives rise to the same estimated variance as the first sample. The two-pass algorithm computes this variance estimate correctly, but the naïve algorithm returns 29.333333333333332 instead of 30.
While this loss of precision may be tolerable and viewed as a minor flaw of the naïve algorithm, further increasing the offset makes the error catastrophic. Consider the sample . Again the estimated population variance of 30 is computed correctly by the two-pass algorithm, but the naïve algorithm now computes it as −170.66666666666666. This is a serious problem with naïve algorithm and is due to catastrophic cancellation in the subtraction of two similar numbers at the final stage of the algorithm.
Terriberry[11] extends Chan's formulae to calculating the third and fourth central moments, needed for example when estimating skewness and kurtosis:
\begin{align} M3,X=M3,A+M3,B&{}+
| ||||||||||
\delta |
+3\delta
nAM2,B-nBM2,A | |
nX |
\\[6pt] M4,X=M4,A+M4,B&{}+
| ||||||||||||||||||||||||||||
\delta |
\\[6pt] &{}+
| |||||||||||||||||||||||||
6\delta |
+4\delta
nAM3,B-nBM3,A | |
nX |
\end{align}
Here the
Mk
\begin{align} &skewness=g1=
\sqrt{n | |
M |
3}{M
3/2 | |
2 |
For the incremental case (i.e.,
B=\{x\}
\begin{align} \delta&=x-m\\[5pt] m'&=m+
\delta | |
n |
\\[5pt] M2'&=M2+\delta2
n-1 | |
n |
\\[5pt] M3'&=M3+\delta3
(n-1)(n-2) | |
n2 |
-
3\deltaM2 | |
n |
\\[5pt] M4'&=M4+
\delta4(n-1)(n2-3n+3) | |
n3 |
+
6\delta2M2 | |
n2 |
-
4\deltaM3 | |
n |
\end{align}
By preserving the value
\delta/n
An example of the online algorithm for kurtosis implemented as described is:
for x in data: n1 = n n = n + 1 delta = x - mean delta_n = delta / n delta_n2 = delta_n**2 term1 = delta * delta_n * n1 mean = mean + delta_n M4 = M4 + term1 * delta_n2 * (n**2 - 3*n + 3) + 6 * delta_n2 * M2 - 4 * delta_n * M3 M3 = M3 + term1 * delta_n * (n - 2) - 3 * delta_n * M2 M2 = M2 + term1
# Note, you may also calculate variance using M2, and skewness using M3 # Caution: If all the inputs are the same, M2 will be 0, resulting in a division by 0. kurtosis = (n * M4) / (M2**2) - 3 return kurtosis
Pébaÿ[12] further extends these results to arbitrary-order central moments, for the incremental and the pairwise cases, and subsequently Pébaÿ et al.[13] for weighted and compound moments. One can also find there similar formulas for covariance.
Choi and Sweetman[14] offer two alternative methods to compute the skewness and kurtosis, each of which can save substantial computer memory requirements and CPU time in certain applications. The first approach is to compute the statistical moments by separating the data into bins and then computing the moments from the geometry of the resulting histogram, which effectively becomes a one-pass algorithm for higher moments. One benefit is that the statistical moment calculations can be carried out to arbitrary accuracy such that the computations can be tuned to the precision of, e.g., the data storage format or the original measurement hardware. A relative histogram of a random variable can be constructed in the conventional way: the range of potential values is divided into bins and the number of occurrences within each bin are counted and plotted such that the area of each rectangle equals the portion of the sample values within that bin:
H(x | ||||
|
where
h(xk)
H(xk)
xk
n
x(t)
(h) | |
m | |
n |
=
K | |
\sum | |
k=1 |
n | |
x | |
k |
H(xk)\Deltaxk =
1 | |
A |
K | |
\sum | |
k=1 |
n | |
x | |
k |
h(xk)\Deltaxk
(h) | |
\theta | |
n |
=
K | |
\sum | |
k=1 |
(xk-m
(h) | |
1 |
)nH(xk)\Deltaxk =
1 | |
A |
K | |
\sum | |
k=1 |
(xk-m
(h) | |
1 |
)nh(xk)\Deltaxk
where the superscript
(h)
\Deltaxk=\Deltax
I=A/\Deltax
(h) | |
m | |
n |
=
1 | |
I |
K | |
\sum | |
k=1 |
n | |
x | |
k |
h(xk)
(h) | |
\theta | |
n |
=
1 | |
I |
K | |
\sum | |
k=1 |
(xk-m
(h) | |
1 |
)nh(xk)
The second approach from Choi and Sweetman is an analytical methodology to combine statistical moments from individual segments of a time-history such that the resulting overall moments are those of the complete time-history. This methodology could be used for parallel computation of statistical moments with subsequent combination of those moments, or for combination of statistical moments computed at sequential times.
If
Q
(\gamma0,q,\muq
2 | |
,\sigma | |
q |
,\alpha3,q,\alpha4,q)
q=1,2,\ldots,Q
\gamman
n
\gamman,q=mn,q\gamma0,q rm{for} n=1,2,3,4 and q=1,2,...,Q
where
\gamma0,q
qth
\Deltat
The benefit of expressing the statistical moments in terms of
\gamma
Q
Q
\gamman,c=
Q | |
\sum | |
q=1 |
\gamman,q forn=0,1,2,3,4
where the subscript
c
\gamma
\gamma
mn,c=
\gamman,c | |
\gamma0,c |
forn=1,2,3,4
Known relationships between the raw moments (
mn
\thetan=\operatornameE[(x-\mu)n])
\muc=m1,c
2 | |
\sigma | |
c=\theta |
2,c \alpha3,c=
\theta3,c | ||||||
|
\alpha4,c={
\theta4,c | ||||||
|
Very similar algorithms can be used to compute the covariance.
The naïve algorithm is
\operatorname{Cov}(X,Y)=
| ||||||||||||||||||||||
n |
.
For the algorithm above, one could use the following Python code:
covariance = (sum12 - sum1 * sum2 / n) / n return covariance
As for the variance, the covariance of two random variables is also shift-invariant, so given any two constant values
kx
ky,
\operatorname{Cov}(X,Y)=\operatorname{Cov}(X-kx,Y-ky)=\dfrac
n | |
{\sum | |
i=1 |
(xi-kx)(yi-ky)-
n | |
(\sum | |
i=1 |
(xi-kx))(\sum
n | |
i=1 |
(yi-ky))/n}{n}.
and again choosing a value inside the range of values will stabilize the formula against catastrophic cancellation as well as make it more robust against big sums. Taking the first value of each data set, the algorithm can be written as:
The two-pass algorithm first computes the sample means, and then the covariance:
\barx=
n | |
\sum | |
i=1 |
xi/n
\bary=
n | |
\sum | |
i=1 |
yi/n
\operatorname{Cov}(X,Y)=
| ||||||||||
n |
.
The two-pass algorithm may be written as:
covariance = 0 for i1, i2 in zip(data1, data2): a = i1 - mean1 b = i2 - mean2 covariance += a * b / n return covariance
A slightly more accurate compensated version performs the full naive algorithm on the residuals. The final sums and should be zero, but the second pass compensates for any small error.
A stable one-pass algorithm exists, similar to the online algorithm for computing the variance, that computes co-moment :
\begin{alignat}{2} \barxn&=\barxn-1&+&
xn-\barxn-1 | |
n |
\\[5pt] \baryn&=\baryn-1&+&
yn-\baryn-1 | |
n |
\\[5pt] Cn&=Cn-1&+&(xn-\barxn)(yn-\baryn-1)\\[5pt] &=Cn-1&+&(xn-\barxn-1)(yn-\baryn) \end{alignat}
Thus the covariance can be computed as
\begin{align} \operatorname{Cov}N(X,Y)=
CN | |
N |
&=
\operatorname{Cov | |
N-1 |
(X,Y) ⋅ (N-1)+(xn-\barxn)(yn-\baryn-1)}{N}\\ &=
\operatorname{Cov | |
N-1 |
(X,Y) ⋅ (N-1)+(xn-\barxn-1)(yn-\baryn)}{N}\\ &=
\operatorname{Cov | |
N-1 |
(X,Y) ⋅ (N-1)+
N-1 | |
N |
(xn-\barxn-1)(yn-\baryn-1)}{N}\\ &=
\operatorname{Cov | |
N-1 |
(X,Y) ⋅ (N-1)+
N | |
N-1 |
(xn-\barxn)(yn-\baryn)}{N}. \end{align}
population_covar = C / n # Bessel's correction for sample variance sample_covar = C / (n - 1)
A small modification can also be made to compute the weighted covariance:
population_covar = C / wsum # Bessel's correction for sample variance # Frequency weights sample_frequency_covar = C / (wsum - 1) # Reliability weights sample_reliability_covar = C / (wsum - wsum2 / wsum)
Likewise, there is a formula for combining the covariances of two sets that can be used to parallelize the computation:
CX=CA+CB+(\barxA-\barxB)(\baryA-\bar
y | ||||
|
.
A version of the weighted online algorithm that does batched updated also exists: let
w1,...wN
\begin{alignat}{2} \barxn+k&=\barxn&+&
| ||||||||||
|
\\ \baryn+k&=\baryn&+&
| ||||||||||
|
\\ Cn+k&=Cn&+&
n+k | |
\sum | |
i=n+1 |
wi(xi-\barxn+k)(yi-\baryn)\\ &=Cn&+&
n+k | |
\sum | |
i=n+1 |
wi(xi-\barxn)(yi-\baryn+k)\\ \end{alignat}
The covariance can then be computed as
\operatorname{Cov}N(X,Y)=
CN | |||||||||
|