Algorithms for calculating variance: Difference between revisions

From Wikipedia
Jump to navigation Jump to search
I believe there was a mistake in the computation of sample_reliability_variance. (see analogous case in online_weighted_covariance algorithm below)
 
imported>LionmerterTHE
m clean up, typo(s) fixed: ically- → ically
 
Line 6: Line 6:
A formula for calculating the variance of an entire [[statistical population|population]] of size ''N'' is:
A formula for calculating the variance of an entire [[statistical population|population]] of size ''N'' is:


:<math>\sigma^2 = \overline{(x^2)} - \bar x^2 = \frac{\sum_{i=1}^N x_i^2}{N} - \left(\frac{\sum_{i=1}^N x_i}{N}\right)^2</math>
:<math>\sigma^2 = \overline{(x-\bar x)^2}  = \overline{(x^2)} - \bar x^2 = \frac{\sum_{i=1}^N x_i^2}{N} - \left(\frac{\sum_{i=1}^N x_i}{N}\right)^2</math>


Using [[Bessel's correction]] to calculate an [[estimator bias|unbiased]] estimate of the population variance from a finite [[statistical sample|sample]] of ''n'' observations, the formula is:
Using [[Bessel's correction]] to calculate an [[estimator bias|unbiased]] estimate of the population variance from a finite [[statistical sample|sample]] of ''n'' observations, the formula is:
Line 14: Line 14:
Therefore, a naïve algorithm to calculate the estimated variance is given by the following:
Therefore, a naïve algorithm to calculate the estimated variance is given by the following:


<div style="margin-left: 35px; width: 600px">
{{framebox|blue|margin-left=35px|width=400px}}
{{framebox|blue}}
* Let {{math|''n'' ← 0, Sum ← 0, SumSq ← 0}}
* Let {{math|''n'' ← 0, Sum ← 0, SumSq ← 0}}
* For each datum {{mvar|x}}:
* For each datum {{mvar|x}}:
Line 23: Line 22:
* {{math|Var {{=}} (SumSq − (Sum × Sum) / n) / (n − 1)}}
* {{math|Var {{=}} (SumSq − (Sum × Sum) / n) / (n − 1)}}
{{frame-footer}}
{{frame-footer}}
</div>


This algorithm can easily be adapted to compute the variance of a finite population: simply divide by ''n'' instead of ''n''&nbsp;−&nbsp;1 on the last line.
This algorithm can easily be adapted to compute the variance of a finite population: simply divide by ''n'' instead of ''n''&nbsp;−&nbsp;1 on the last line.


Because {{math|SumSq}} and {{math|(Sum×Sum)/''n''}} can be very similar numbers, [[Catastrophic cancellation|cancellation]] can lead to the [[precision (arithmetic)|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,<ref name="Einarsson2005">{{cite book|first=Bo|last=Einarsson|title=Accuracy and Reliability in Scientific Computing|url=https://books.google.com/books?id=8hrDV5EbrEsC|year=2005|publisher=SIAM|isbn=978-0-89871-584-2|page=47}}</ref><ref name="Chan1983">{{cite journal|url=http://cpsc.yale.edu/sites/default/files/files/tr222.pdf |archive-url=https://ghostarchive.org/archive/20221009/http://cpsc.yale.edu/sites/default/files/files/tr222.pdf |archive-date=2022-10-09 |url-status=live|first1=Tony F.|last1=Chan|author1-link=Tony F. Chan|first2=Gene H.|last2=Golub|author2-link=Gene H. Golub|first3=Randall J.|last3=LeVeque|title=Algorithms for computing the sample variance: Analysis and recommendations|journal=The American Statistician|volume=37|issue=3|pages=242–247|year=1983|jstor=2683386|doi=10.1080/00031305.1983.10483115}}</ref> and several alternate, numerically stable, algorithms have been proposed.<ref name=":1">{{Cite book|last1=Schubert|first1=Erich|last2=Gertz|first2=Michael|date=2018-07-09|title=Numerically stable parallel computation of (co-)variance|url=http://dl.acm.org/citation.cfm?id=3221269.3223036|publisher=ACM|pages=10|doi=10.1145/3221269.3223036|isbn=9781450365055|s2cid=49665540}}</ref> This is particularly bad if the standard deviation is small relative to the mean.
Because {{math|SumSq}} and {{math|(Sum×Sum)/''n''}} can be very similar numbers, [[Catastrophic cancellation|cancellation can lead to]] the [[precision (arithmetic)|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,<ref name="Einarsson2005">{{cite book|first=Bo|last=Einarsson|title=Accuracy and Reliability in Scientific Computing|url=https://books.google.com/books?id=8hrDV5EbrEsC|year=2005|publisher=SIAM|isbn=978-0-89871-584-2|page=47}}</ref><ref name="Chan1983">{{cite journal|url=http://cpsc.yale.edu/sites/default/files/files/tr222.pdf |archive-url=https://ghostarchive.org/archive/20221009/http://cpsc.yale.edu/sites/default/files/files/tr222.pdf |archive-date=2022-10-09 |url-status=live|first1=Tony F.|last1=Chan|author1-link=Tony F. Chan|first2=Gene H.|last2=Golub|author2-link=Gene H. Golub|first3=Randall J.|last3=LeVeque|title=Algorithms for computing the sample variance: Analysis and recommendations|journal=The American Statistician|volume=37|issue=3|pages=242–247|year=1983|jstor=2683386|doi=10.1080/00031305.1983.10483115}}</ref> and several alternate, numerically stable, algorithms have been proposed.<ref name=":1">{{Cite book|last1=Schubert|first1=Erich|last2=Gertz|first2=Michael|date=2018-07-09|title=Numerically stable parallel computation of (co-)variance|url=http://dl.acm.org/citation.cfm?id=3221269.3223036|publisher=ACM|pages=10|doi=10.1145/3221269.3223036|isbn=9781450365055|s2cid=49665540}}</ref> This is particularly bad if the standard deviation is small relative to the mean.


===Computing shifted data===
==Computing shifted data==


The variance is [[Invariant (mathematics)|invariant]] with respect to changes in a [[location parameter]], a property which can be used to avoid the catastrophic cancellation in this formula.
The variance is [[Invariant (mathematics)|invariant]] with respect to changes in a [[location parameter]], a property which can be used to avoid the catastrophic cancellation in this formula.
Line 43: Line 41:


If just the first sample is taken as <math>K</math> the algorithm can be written in [[Python (programming language)|Python programming language]] as
If just the first sample is taken as <math>K</math> the algorithm can be written in [[Python (programming language)|Python programming language]] as
<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
def shifted_data_variance(data):
def shifted_data_variance(data):
Line 60: Line 57:
</syntaxhighlight>
</syntaxhighlight>


This formula also facilitates the incremental computation that can be expressed as
===Two-pass algorithm===
<syntaxhighlight lang="python">
K = Ex = Ex2 = 0.0
n = 0
 
 
def add_variable(x):
    global K, n, Ex, Ex2
    if n == 0:
        K = x
    n += 1
    Ex += x - K
    Ex2 += (x - K) ** 2
 
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)
</syntaxhighlight>
 
==Two-pass algorithm==
An alternative approach, using a different formula for the variance, first computes the sample mean,
An alternative approach, using a different formula for the variance, first computes the sample mean,
:<math>\bar x = \frac {\sum_{j=1}^n x_j} n,</math>
:<math>\bar x = \frac {\sum_{j=1}^n x_j} n,</math>
Line 104: Line 72:
</syntaxhighlight>
</syntaxhighlight>


This algorithm is numerically stable if ''n'' is small.<ref name="Einarsson2005"/><ref>{{cite book |last=Higham |first=Nicholas J. |url=https://epubs.siam.org/doi/book/10.1137/1.9780898718027 |title=Accuracy and Stability of Numerical Algorithms |publisher=Society for Industrial and Applied Mathematics |year=2002 |edition=2nd |publication-place=Philadelphia, PA |chapter=Problem 1.10 |doi= 10.1137/1.9780898718027|isbn=978-0-898715-21-7 |id=e{{ISBN|978-0-89871-802-7}}, 2002075848 }} Metadata also listed at [https://dl.acm.org/doi/10.5555/579525 ACM Digital Library].</ref> 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.
This piece of code, if run on [[CPython]] 3.12 and newer, is always numerically stable. This is because these versions use Neumaier's [[compensated summation]] scheme for the <code>sum()</code> function, making it resistant to repeated roundoff error.<ref>{{Multiref2|[https://docs.python.org/3.12/whatsnew/3.12.html#other-language-changes What's New in Python 3.12]|{{cite web |last1=Hettinger |first1=R. |title=Improve accuracy of builtin sum() for float inputs · Issue #100425 · python/cpython |url=https://github.com/python/cpython/issues/100425 |website=GitHub - CPython v3.12 Added Features |access-date=7 October 2023 |language=en}}}}</ref> Many numerically oriented languages provide similar constructs.
 
This algorithm, if implemented with a naive addition <code>sum = 0; for x in data: sum += x; mean = sum / n</code>, would only be numerically stable if ''n'' is small because of the accumulation of roundoff error.<ref name="Einarsson2005"/><ref>{{cite book |last=Higham |first=Nicholas J. |url=https://epubs.siam.org/doi/book/10.1137/1.9780898718027 |title=Accuracy and Stability of Numerical Algorithms |publisher=Society for Industrial and Applied Mathematics |year=2002 |edition=2nd |publication-place=Philadelphia, PA |chapter=Problem 1.10 |doi= 10.1137/1.9780898718027|isbn=978-0-898715-21-7 |id=e{{ISBN|978-0-89871-802-7}}, 2002075848 }} Metadata also listed at [https://dl.acm.org/doi/10.5555/579525 ACM Digital Library].</ref>


==Welford's online algorithm==
== Incremental algorithms ==
It is often useful to be able to compute the variance in a [[One-pass algorithm|single pass]], inspecting each value <math>x_i</math> only once; for example, when the data is being collected without enough storage to keep all the values, or when costs of memory access dominate those of computation.  For such an [[online algorithm]], a [[recurrence relation]] is required between quantities from which the required statistics can be calculated in a numerically stable fashion.
It is often useful to be able to compute the variance in a [[One-pass algorithm|single pass]], inspecting each value <math>x_i</math> only once; for example, when the data is being collected without enough storage to keep all the values, or when costs of memory access dominate those of computation.  For such an [[online algorithm]], a [[recurrence relation]] is required between quantities from which the required statistics can be calculated in a numerically stable fashion.


The following formulas can be used to update the [[mean]] and (estimated) variance of the sequence, for an additional element ''x''<sub>''n''</sub>. Here, <math display="inline">\overline{x}_n = \frac{1}{n} \sum_{i=1}^n x_i </math> denotes the sample mean of the first ''n'' samples <math>(x_1,\dots,x_n)</math>, <math display="inline">\sigma^2_n = \frac{1}{n} \sum_{i=1}^n \left(x_i - \overline{x}_n \right)^2</math> their [[Variance#Biased_sample_variance|biased sample variance]], and <math display="inline">s^2_n = \frac{1}{n - 1} \sum_{i=1}^n \left(x_i - \overline{x}_n \right)^2</math> their [[Variance#Unbiased_sample_variance|unbiased sample variance]].
=== Incremental shifted data ===
Our shifted-data algorithm from before includes the simple updating of data in a loop, which is naturally incremental. It can be expressed as:<ref name="Chan1983" />
 
<syntaxhighlight lang="python">
class ShiftDataVariance:
    def __init__(self):
        self.K = 0.0
        self.n = 0
        self.Ex = 0.0
        self.Ex2 = 0.0
 
    def add_variable(self, x: float):
        if self.n == 0:
            self.K = x
        self.n += 1
        self.Ex += x - self.K
        self.Ex2 += (x - self.K) ** 2
   
    def remove_variable(self, x: float):
        self.n -= 1
        self.Ex -= x - self.K
        self.Ex2 -= (x - self.K) ** 2
 
    def add_variables(self, xs: list[float]):
        # Python uses Neumaier's algorithm for the builtin sum() function,
        # which is more accurate than a simple loop.
        if self.n == 0 and xs:
            self.K = xs[0]
        self.n += len(xs)
        self.Ex += sum(x - self.K for x in xs)
        self.Ex2 += sum((x - self.K) ** 2 for x in xs)
   
    def get_mean(self) -> float:
        return self.K + self.Ex / self.n
   
    def get_variance(self) -> float:
        return (self.Ex2 - self.Ex**2 / self.n) / (self.n - 1)
</syntaxhighlight>
 
===Welford's online algorithm===
Analogous to the two-pass algorithm above, it remains desirable to use the actual [[mean]] of the data instead of just picking the first element. The following formulas can be used to update the [[mean]] and (estimated) variance of the sequence, for an additional element ''x''<sub>''n''</sub>. Here, <math display="inline">\overline{x}_n = \frac{1}{n} \sum_{i=1}^n x_i </math> denotes the sample mean of the first ''n'' samples <math>(x_1,\dots,x_n)</math>, <math display="inline">\sigma^2_n = \frac{1}{n} \sum_{i=1}^n \left(x_i - \overline{x}_n \right)^2</math> their [[Variance#Biased sample variance|biased sample variance]], and <math display="inline">s^2_n = \frac{1}{n - 1} \sum_{i=1}^n \left(x_i - \overline{x}_n \right)^2</math> their [[Variance#Unbiased sample variance|unbiased sample variance]].


:<math>\bar x_n = \frac{(n-1) \, \bar x_{n-1} + x_n}{n} = \bar x_{n-1} + \frac{x_n - \bar x_{n-1}}{n} </math>
:<math>\bar x_n = \frac{(n-1) \, \bar x_{n-1} + x_n}{n} = \bar x_{n-1} + \frac{x_n - \bar x_{n-1}}{n} </math>
Line 117: Line 127:
:<math>s^2_n = \frac{n-2}{n-1} \, s^2_{n-1} + \frac{(x_n - \bar x_{n-1})^2}{n} = s^2_{n-1} + \frac{(x_n - \bar x_{n-1})^2}{n} - \frac{s^2_{n-1}}{n-1}, \quad n>1 </math>
:<math>s^2_n = \frac{n-2}{n-1} \, s^2_{n-1} + \frac{(x_n - \bar x_{n-1})^2}{n} = s^2_{n-1} + \frac{(x_n - \bar x_{n-1})^2}{n} - \frac{s^2_{n-1}}{n-1}, \quad n>1 </math>


These formulas suffer from numerical instability {{cn|date=March 2023}}, 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, <math display="inline">\sum_{i=1}^n (x_i - \bar x_n)^2</math>, here denoted <math>M_{2,n}</math>:
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, <math display="inline">\sum_{i=1}^n (x_i - \bar x_n)^2</math>, here denoted <math>M_{2,n}</math>:


: <math>\begin{align}
: <math>\begin{align}
Line 125: Line 135:
\end{align}</math>
\end{align}</math>


This algorithm was found by Welford,<ref>{{cite journal |first=B. P. |last=Welford |year=1962 |title=Note on a method for calculating corrected sums of squares and products |journal=[[Technometrics]] |volume=4 |issue=3 |pages=419–420 |jstor=1266577 |doi=10.2307/1266577}}</ref><ref>[[Donald E. Knuth]] (1998). ''[[The Art of Computer Programming]]'', volume 2: ''Seminumerical Algorithms'', 3rd edn., p.&nbsp;232. Boston: Addison-Wesley.</ref> and it has been thoroughly analyzed.<ref name="Chan1983" /><ref>{{cite journal |last=Ling |first=Robert F. |year=1974 |title=Comparison of Several Algorithms for Computing Sample Means and Variances |journal=Journal of the American Statistical Association |volume=69 |issue=348 |pages=859–866 |doi=10.2307/2286154|jstor=2286154 }}</ref> It is also common to denote <math>M_k = \bar x_k</math> and <math>S_k = M_{2,k}</math>.<ref>{{cite web |last=Cook |first=John D. |date=30 September 2022 |orig-date=1 November 2014 |title=Accurately computing sample variance |url=http://www.johndcook.com/standard_deviation.html |website=John D. Cook Consulting: Expert consulting in applied mathematics & data privacy}}</ref>
The following algorithm was found by Welford,<ref>{{cite journal |first=B. P. |last=Welford |year=1962 |title=Note on a method for calculating corrected sums of squares and products |journal=[[Technometrics]] |volume=4 |issue=3 |pages=419–420 |jstor=1266577 |doi=10.2307/1266577}}</ref><ref>[[Donald E. Knuth]] (1998). ''[[The Art of Computer Programming]]'', volume 2: ''Seminumerical Algorithms'', 3rd edn., p.&nbsp;232. Boston: Addison-Wesley.</ref> and it has been thoroughly analyzed.<ref name="Chan1983" /><ref>{{cite journal |last=Ling |first=Robert F. |year=1974 |title=Comparison of Several Algorithms for Computing Sample Means and Variances |journal=Journal of the American Statistical Association |volume=69 |issue=348 |pages=859–866 |doi=10.2307/2286154|jstor=2286154 }}</ref> It is also common to denote <math>M_k = \bar x_k</math> and <math>S_k = M_{2,k}</math>.<ref>{{cite web |last=Cook |first=John D. |date=30 September 2022 |orig-date=1 November 2014 |title=Accurately computing sample variance |url=http://www.johndcook.com/standard_deviation.html |website=John D. Cook Consulting: Expert consulting in applied mathematics & data privacy}}</ref> An example Python implementation for Welford's algorithm is given below, using the same framework as the above "shifted data" algorithm:
 
<syntaxhighlight lang="python">
class WelfordVariance:
    def __init__(self):  # Comparison to ShiftDataVariance:
        self.mean = 0.0  # = K + Ex / n
        self.count = 0    # = n
        self.M2 = 0.0    # = Ex2 - (Ex)^2 / n


An example Python implementation for Welford's algorithm is given below.
    def add_variable(self, x: float):
        self.count += 1
        old_mean = self.mean
        self.mean += (x - self.mean) / self.count
        self.M2 += (x - old_mean) * (x - self.mean)


<syntaxhighlight lang="python">
    def remove_variable(self, x: float):
# For a new value new_value, compute the new count, new mean, the new M2.
        self.count -= 1
# mean accumulates the mean of the entire dataset
        new_mean = self.mean
# M2 aggregates the squared distance from the mean
        self.mean -= (x - self.mean) / self.count
# count aggregates the number of samples seen so far
        self.M2 -= (x - new_mean) * (x - self.mean)
def update(existing_aggregate, new_value):
       
    (count, mean, M2) = existing_aggregate
     def get_mean(self) -> float:
    count += 1
        return self.mean
    delta = new_value - mean
    mean += delta / count
    delta2 = new_value - mean
     M2 += delta * delta2
    return (count, mean, M2)


# Retrieve the mean, variance and sample variance from an aggregate
    def get_variance(self) -> float:
def finalize(existing_aggregate):
        return self.M2 / self.count
    (count, mean, M2) = existing_aggregate
      
     if count < 2:
    def get_sample_variance(self) -> float:
        return float("nan")
         return self.M2 / (self.count - 1)
    else:
         (mean, variance, sample_variance) = (mean, M2 / count, M2 / (count - 1))
        return (mean, variance, sample_variance)
</syntaxhighlight>
</syntaxhighlight>


Line 157: Line 170:
The [[#Parallel algorithm|parallel algorithm]] below illustrates how to merge multiple sets of statistics calculated online.
The [[#Parallel algorithm|parallel algorithm]] below illustrates how to merge multiple sets of statistics calculated online.


==Weighted incremental algorithm==
===Weighted incremental algorithm===
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)<ref>{{cite journal |last=West |first=D. H. D. |year=1979 |title=Updating Mean and Variance Estimates: An Improved Method |journal=[[Communications of the ACM]] |volume=22 |issue=9 |pages=532–535 |doi=10.1145/359146.359153 |s2cid=30671293 |doi-access=free}}</ref> suggests this [[incremental computing|incremental algorithm]]:
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)<ref>{{cite journal |last=West |first=D. H. D. |year=1979 |title=Updating Mean and Variance Estimates: An Improved Method |journal=[[Communications of the ACM]] |volume=22 |issue=9 |pages=532–535 |doi=10.1145/359146.359153 |s2cid=30671293 |doi-access=free}}</ref> suggests this [[incremental computing|incremental algorithm]]:


<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
from collections import namedtuple
WeightedVariances = namedtuple("WeightedVariances", "pop freq reli")
def weighted_incremental_variance(data_weight_pairs):
def weighted_incremental_variance(data_weight_pairs):
     w_sum = w_sum2 = mean = S = 0
     w_sum = w_sum2 = mean = S = 0
Line 175: Line 190:
     # Frequency weights
     # Frequency weights
     sample_frequency_variance = S / (w_sum - 1)
     sample_frequency_variance = S / (w_sum - 1)
     # Reliability weights
     # Reliability weights
     sample_reliability_variance = S / (w_sum - w_sum2 / w_sum)
     sample_reliability_variance = S / (w_sum - w_sum2 / w_sum)
    return WeightedVariances(population_variance, sample_frequency_variance, sample_reliability_variance)
</syntaxhighlight>
</syntaxhighlight>


Line 192: Line 208:
This may be useful when, for example, multiple processing units may be assigned to discrete parts of the input.
This may be useful when, for example, multiple processing units may be assigned to discrete parts of the input.


Chan's method for estimating the mean is numerically unstable when <math>n_A \approx n_B</math> and both are large, because the numerical error in <math>\delta = \bar x_B - \bar x_A</math> is not scaled down in the way that it is in the <math>n_B = 1</math> case. In such cases, prefer <math display="inline">\bar x_{AB} = \frac{n_A \bar x_A + n_B \bar x_B}{n_{AB}}</math>.
Chan's method for estimating the mean is numerically unstable when <math>n_A \approx n_B</math> and both are large, because the [[numerical error]] in <math>\delta = \bar x_B - \bar x_A</math> is not scaled down in the way that it is in the <math>n_B = 1</math> case. In such cases, prefer <math display="inline">\bar x_{AB} = \frac{n_A \bar x_A + n_B \bar x_B}{n_{AB}}</math>. Reusing the <code>WelfordVariance</code> class from above, we have:
 
<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
def parallel_variance(n_a, avg_a, M2_a, n_b, avg_b, M2_b):
def merge(a: WelfordVariance, b: WelfordVariance) -> WelfordVariance:
     n = n_a + n_b
     ab = WelfordVariance()
     delta = avg_b - avg_a
    ab.count = a.count + b.count
     M2 = M2_a + M2_b + delta**2 * n_a * n_b / n
     delta = b.mean - a.mean
     var_ab = M2 / (n - 1)
     ab.mean = (a.count * a.mean + b.count * b.mean) / ab.count
    return var_ab
    ab.M2 = a.M2 + b.M2 + delta**2 * a.count * b.count / ab.count
    return ab
      
# example:
ab = merge(a, b)
print(ab.get_sample_variance())
</syntaxhighlight>
</syntaxhighlight>
This can be generalized to allow parallelization with [[Advanced Vector Extensions|AVX]], with [[Graphics processing unit|GPUs]], and [[Computer cluster|computer clusters]], and to covariance.<ref name=":1" />
 
This algorithm allows one to divide a dataset into several pieces, run them in parallel, and then merge the results together. This enables parallelization in any way, including [[Advanced Vector Extensions|AVX]], with [[Graphics processing unit|GPUs]], and [[computer cluster]]s. The algorithm can also be adapted for higher-order statistics as well as covariance.<ref name=":1" /><ref name=Terriberry/>


==Example==
==Example==
Assume that all floating point operations use standard [[IEEE 754#Double-precision 64 bit|IEEE 754 double-precision]]{{Broken anchor|date=2025-06-11|bot=User:Cewbot/log/20201008/configuration|target_link=IEEE 754#Double-precision 64 bit|reason= }} 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.
Assume that all floating point operations use standard [[Double-precision floating-point format|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 ({{nowrap|10<sup>8</sup>&nbsp;+&nbsp;4}}, {{nowrap|10<sup>8</sup>&nbsp;+&nbsp;7}}, {{nowrap|10<sup>8</sup>&nbsp;+&nbsp;13}}, {{nowrap|10<sup>8</sup>&nbsp;+&nbsp;16}}), 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.
Next consider the sample ({{nowrap|10<sup>8</sup>&nbsp;+&nbsp;4}}, {{nowrap|10<sup>8</sup>&nbsp;+&nbsp;7}}, {{nowrap|10<sup>8</sup>&nbsp;+&nbsp;13}}, {{nowrap|10<sup>8</sup>&nbsp;+&nbsp;16}}), 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.
Line 211: Line 234:


==Higher-order statistics==
==Higher-order statistics==
Terriberry<ref>{{Cite web |last=Terriberry |first=Timothy B. |date=15 October 2008 |orig-date=9 December 2007 |title=Computing Higher-Order Moments Online |url=http://people.xiph.org/~tterribe/notes/homs.html |url-status=dead |archive-url=https://web.archive.org/web/20140423031833/http://people.xiph.org/~tterribe/notes/homs.html |archive-date=23 April 2014 |access-date=5 May 2008}}</ref> extends Chan's formulae to calculating the third and fourth [[central moment]]s, needed for example when estimating [[skewness]] and [[kurtosis]]:
Terriberry<ref name=Terriberry>{{Cite web |last=Terriberry |first=Timothy B. |date=15 October 2008 |orig-date=9 December 2007 |title=Computing Higher-Order Moments Online |url=http://people.xiph.org/~tterribe/notes/homs.html |url-status=dead |archive-url=https://web.archive.org/web/20140423031833/http://people.xiph.org/~tterribe/notes/homs.html |archive-date=23 April 2014 |access-date=5 May 2008}}</ref> extends Chan's formulae to calculating the third and fourth [[central moment]]s, needed for example when estimating [[skewness]] and [[kurtosis]]:
:<math>
:<math>\begin{align}
\begin{align}
M_{3,X} = M_{3,A} + M_{3,B} & {} + \delta^3\frac{n_A n_B (n_A - n_B)}{n_X^2} + 3\delta\frac{n_AM_{2,B} - n_BM_{2,A}}{n_X} \\[6pt]
M_{3,X} = M_{3,A} + M_{3,B} & {} + \delta^3\frac{n_A n_B (n_A - n_B)}{n_X^2} + 3\delta\frac{n_AM_{2,B} - n_BM_{2,A}}{n_X} \\[6pt]
M_{4,X} = M_{4,A} + M_{4,B} & {} + \delta^4\frac{n_A n_B \left(n_A^2 - n_A n_B + n_B^2\right)}{n_X^3} \\[6pt]
M_{4,X} = M_{4,A} + M_{4,B} & {} + \delta^4\frac{n_A n_B \left(n_A^2 - n_A n_B + n_B^2\right)}{n_X^3} \\[6pt]
Line 238: Line 260:
</math>
</math>


By preserving the value <math>\delta / n</math>, only one division operation is needed and the higher-order statistics can thus be calculated for little incremental cost.
By preserving the value <math>\delta / n</math>, only one division operation is needed and the [[higher-order statistics]] can thus be calculated for little incremental cost.


An example of the online algorithm for kurtosis implemented as described is:
An example of the online algorithm for kurtosis implemented as described is:
Line 300: Line 322:
  |doi=10.1177/1475921709341014
  |doi=10.1177/1475921709341014
}}</ref>
}}</ref>
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:
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:


: <math> H(x_k)=\frac{h(x_k)}{A}</math>
: <math> H(x_k)=\frac{h(x_k)}{A}</math>
Line 310: Line 332:
             = \frac{1}{A} \sum_{k=1}^K x_k^n h(x_k) \, \Delta x_k
             = \frac{1}{A} \sum_{k=1}^K x_k^n h(x_k) \, \Delta x_k
</math>
</math>
: <math>
: <math>
  \theta_n^{(h)}= \sum_{k=1}^{K} \Big(x_k-m_1^{(h)}\Big)^n \, H(x_k) \, \Delta x_k
  \theta_n^{(h)}= \sum_{k=1}^{K} \Big(x_k-m_1^{(h)}\Big)^n \, H(x_k) \, \Delta x_k
Line 362: Line 383:


==Covariance==
==Covariance==
Very similar algorithms can be used to compute the [[covariance]].
Very similar algorithms can be used to compute the [[covariance]].


===Naïve algorithm===
===Naïve algorithm===
Line 385: Line 406:
:<math>\operatorname{Cov}(X,Y) = \operatorname{Cov}(X-k_x,Y-k_y) = \dfrac {\sum_{i=1}^n (x_i-k_x) (y_i-k_y) - (\sum_{i=1}^n (x_i-k_x))(\sum_{i=1}^n (y_i-k_y))/n}{n}. </math>
:<math>\operatorname{Cov}(X,Y) = \operatorname{Cov}(X-k_x,Y-k_y) = \dfrac {\sum_{i=1}^n (x_i-k_x) (y_i-k_y) - (\sum_{i=1}^n (x_i-k_x))(\sum_{i=1}^n (y_i-k_y))/n}{n}. </math>


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:
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:


<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
Line 395: Line 416:
     ky = data_y[0]
     ky = data_y[0]
     Ex = Ey = Exy = 0
     Ex = Ey = Exy = 0
     for ix, iy in zip(data_x, data_y):
     for ix, iy in zip(data_x, data_y):  # for higher precision, use sum():
         Ex += ix - kx
         Ex += ix - kx                   # Ex = sum(ix - kx for ix in data_x)  # or sum(ix) - kx * n
         Ey += iy - ky
         Ey += iy - ky                   # Ey = sum(iy - ky for iy in data_y)  # or sum(iy) - ky * n
         Exy += (ix - kx) * (iy - ky)
         Exy += (ix - kx) * (iy - ky)    # Exy = sum((ix - kx) * (iy - ky) for ix, iy in zip(data_x, data_y))  
     return (Exy - Ex * Ey / n) / n
     return (Exy - Ex * Ey / n) / n
</syntaxhighlight>
</syntaxhighlight>
Line 416: Line 437:


     covariance = 0
     covariance = 0
     for i1, i2 in zip(data1, data2):
     for i1, i2 in zip(data1, data2):    # for higher precision, use sum():
         a = i1 - mean1
         a = i1 - mean1                 # covariance = sum((i1 - mean1) * (i2 - mean2) for i1, i2 in zip(data1, data2))
         b = i2 - mean2
         b = i2 - mean2
         covariance += a * b / n
         covariance += a * b / n
     return covariance
     return covariance
</syntaxhighlight>
</syntaxhighlight>
A slightly more accurate compensated version performs the full naive algorithm on the residuals.  The final sums <math display="inline">\sum_i x_i</math> and <math display="inline">\sum_i y_i</math> ''should'' be zero, but the second pass compensates for any small error.


===Online===
===Online===
Line 454: Line 473:
         C += dx * (y - meany)
         C += dx * (y - meany)


    population_covar = C / n
     # covariance and Bessel's correction for sample
     # Bessel's correction for sample variance
     return (C / n, C / (n - 1))
     sample_covar = C / (n - 1)
</syntaxhighlight>
</syntaxhighlight>


Line 500: Line 518:


:<math>\operatorname{Cov}_N(X,Y) = \frac{C_N}{\sum_{i=1}^{N} w_i}</math>
:<math>\operatorname{Cov}_N(X,Y) = \frac{C_N}{\sum_{i=1}^{N} w_i}</math>
(This can be used with Python's <code>sum</code> for better accuracy. Block updating is also related to the parallel/merge algorithm.)


==See also==
==See also==

Latest revision as of 19:28, 25 March 2026

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.

Naïve algorithm

A formula for calculating the variance of an entire population of size N is:

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \sigma^2 = \overline{(x-\bar x)^2} = \overline{(x^2)} - \bar x^2 = \frac{\sum_{i=1}^N x_i^2}{N} - \left(\frac{\sum_{i=1}^N x_i}{N}\right)^2}

Using Bessel's correction to calculate an unbiased estimate of the population variance from a finite sample of n observations, the formula is:

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle s^2 = \left(\frac {\sum_{i=1}^n x_i^2} n - \left( \frac {\sum_{i=1}^n x_i} n \right)^2\right) \cdot \frac {n}{n-1}. }

Therefore, a naïve algorithm to calculate the estimated variance is given by the following:

Template:Framebox

  • Let n ← 0, Sum ← 0, SumSq ← 0
  • For each datum x:
    • nn + 1
    • Sum ← Sum + x
    • SumSq ← SumSq + x × x
  • Var = (SumSq − (Sum × Sum) / n) / (n − 1)

Template:Frame-footer

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 SumSq and (Sum×Sum)/n 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.

Computing shifted data

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.

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \operatorname{Var}(X-K)=\operatorname{Var}(X).}

with Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle K} any constant, which leads to the new formula

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \sigma^2 = \frac {\sum_{i=1}^n (x_i-K)^2 - (\sum_{i=1}^n (x_i-K))^2/n}{n-1}. }

the closer Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle K} is to the mean value the more accurate the result will be, but just choosing a value inside the samples range will guarantee the desired stability. If the values Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle (x_i - K)} are small then there are no problems with the sum of its squares, on the contrary, if they are large it necessarily means that the variance is large as well. In any case the second term in the formula is always smaller than the first one therefore no cancellation may occur.[2]

If just the first sample is taken as Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle K} the algorithm can be written in Python programming language as

def shifted_data_variance(data):
    if len(data) < 2:
        return 0.0
    K = data[0]
    n = Ex = Ex2 = 0.0
    for x in data:
        n += 1
        Ex += x - K
        Ex2 += (x - K) ** 2
    variance = (Ex2 - Ex**2 / n) / (n - 1)
    # use n instead of (n-1) if want to compute the exact variance of the given data
    # use (n-1) if data are samples of a larger population
    return variance

Two-pass algorithm

An alternative approach, using a different formula for the variance, first computes the sample mean,

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \bar x = \frac {\sum_{j=1}^n x_j} n,}

and then computes the sum of the squares of the differences from the mean,

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \text{sample variance} = s^2 = \dfrac {\sum_{i=1}^n (x_i - \bar x)^2}{n-1}, }

where s is the standard deviation. This is given by the following code:

def two_pass_variance(data):
    n = len(data)
    mean = sum(data) / n
    variance = sum((x - mean) ** 2 for x in data) / (n - 1)
    return variance

This piece of code, if run on CPython 3.12 and newer, is always numerically stable. This is because these versions use Neumaier's compensated summation scheme for the sum() function, making it resistant to repeated roundoff error.[4] Many numerically oriented languages provide similar constructs.

This algorithm, if implemented with a naive addition sum = 0; for x in data: sum += x; mean = sum / n, would only be numerically stable if n is small because of the accumulation of roundoff error.[1][5]

Incremental algorithms

It is often useful to be able to compute the variance in a single pass, inspecting each value Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle x_i} only once; for example, when the data is being collected without enough storage to keep all the values, or when costs of memory access dominate those of computation. For such an online algorithm, a recurrence relation is required between quantities from which the required statistics can be calculated in a numerically stable fashion.

Incremental shifted data

Our shifted-data algorithm from before includes the simple updating of data in a loop, which is naturally incremental. It can be expressed as:[2]

class ShiftDataVariance:
    def __init__(self):
        self.K = 0.0
        self.n = 0
        self.Ex = 0.0
        self.Ex2 = 0.0

    def add_variable(self, x: float):
        if self.n == 0:
            self.K = x
        self.n += 1
        self.Ex += x - self.K
        self.Ex2 += (x - self.K) ** 2
    
    def remove_variable(self, x: float):
        self.n -= 1
        self.Ex -= x - self.K
        self.Ex2 -= (x - self.K) ** 2

    def add_variables(self, xs: list[float]):
        # Python uses Neumaier's algorithm for the builtin sum() function,
        # which is more accurate than a simple loop.
        if self.n == 0 and xs:
            self.K = xs[0]
        self.n += len(xs)
        self.Ex += sum(x - self.K for x in xs)
        self.Ex2 += sum((x - self.K) ** 2 for x in xs)
    
    def get_mean(self) -> float:
        return self.K + self.Ex / self.n
    
    def get_variance(self) -> float:
        return (self.Ex2 - self.Ex**2 / self.n) / (self.n - 1)

Welford's online algorithm

Analogous to the two-pass algorithm above, it remains desirable to use the actual mean of the data instead of just picking the first element. The following formulas can be used to update the mean and (estimated) variance of the sequence, for an additional element xn. Here, Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\textstyle \overline{x}_n = \frac{1}{n} \sum_{i=1}^n x_i } denotes the sample mean of the first n samples Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle (x_1,\dots,x_n)} , Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\textstyle \sigma^2_n = \frac{1}{n} \sum_{i=1}^n \left(x_i - \overline{x}_n \right)^2} their biased sample variance, and Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\textstyle s^2_n = \frac{1}{n - 1} \sum_{i=1}^n \left(x_i - \overline{x}_n \right)^2} their unbiased sample variance.

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \bar x_n = \frac{(n-1) \, \bar x_{n-1} + x_n}{n} = \bar x_{n-1} + \frac{x_n - \bar x_{n-1}}{n} }
Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \sigma^2_n = \frac{(n-1) \, \sigma^2_{n-1} + (x_n - \bar x_{n-1})(x_n - \bar x_n)}{n} = \sigma^2_{n-1} + \frac{(x_n - \bar x_{n-1})(x_n - \bar x_n) - \sigma^2_{n-1}}{n}.}
Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle s^2_n = \frac{n-2}{n-1} \, s^2_{n-1} + \frac{(x_n - \bar x_{n-1})^2}{n} = s^2_{n-1} + \frac{(x_n - \bar x_{n-1})^2}{n} - \frac{s^2_{n-1}}{n-1}, \quad 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, Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\textstyle \sum_{i=1}^n (x_i - \bar x_n)^2} , here denoted Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle M_{2,n}} :

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \begin{align} M_{2,n} & = M_{2,n-1} + (x_n - \bar x_{n-1})(x_n - \bar x_n) \\[4pt] \sigma^2_n & = \frac{M_{2,n}}{n} \\[4pt] s^2_n & = \frac{M_{2,n}}{n-1} \end{align}}

The following algorithm was found by Welford,[6][7] and it has been thoroughly analyzed.[2][8] It is also common to denote Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle M_k = \bar x_k} and Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle S_k = M_{2,k}} .[9] An example Python implementation for Welford's algorithm is given below, using the same framework as the above "shifted data" algorithm:

class WelfordVariance:
    def __init__(self):   # Comparison to ShiftDataVariance:
        self.mean = 0.0   # = K + Ex / n
        self.count = 0    # = n
        self.M2 = 0.0     # = Ex2 - (Ex)^2 / n

    def add_variable(self, x: float):
        self.count += 1
        old_mean = self.mean
        self.mean += (x - self.mean) / self.count
        self.M2 += (x - old_mean) * (x - self.mean)

    def remove_variable(self, x: float):
        self.count -= 1
        new_mean = self.mean
        self.mean -= (x - self.mean) / self.count
        self.M2 -= (x - new_mean) * (x - self.mean)
        
    def get_mean(self) -> float:
        return self.mean

    def get_variance(self) -> float:
        return self.M2 / self.count
    
    def get_sample_variance(self) -> float:
        return self.M2 / (self.count - 1)

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.

Weighted incremental algorithm

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)[10] suggests this incremental algorithm:

from collections import namedtuple
WeightedVariances = namedtuple("WeightedVariances", "pop freq reli")
def weighted_incremental_variance(data_weight_pairs):
    w_sum = w_sum2 = mean = S = 0

    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)
    return WeightedVariances(population_variance, sample_frequency_variance, sample_reliability_variance)

Parallel algorithm

Chan et al.[11] note that Welford's online algorithm detailed above is a special case of an algorithm that works for combining arbitrary sets Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle A} and Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle B} :

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \begin{align} n_{AB} & = n_A + n_B \\ \delta & = \bar x_B - \bar x_A \\ \bar x_{AB} & = \bar x_A + \delta\cdot\frac{n_B}{n_{AB}} \\ M_{2,AB} & = M_{2,A} + M_{2,B} + \delta^2\cdot\frac{n_A n_B}{n_{AB}} \\ \end{align}} .

This may be useful when, for example, multiple processing units may be assigned to discrete parts of the input.

Chan's method for estimating the mean is numerically unstable when Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle n_A \approx n_B} and both are large, because the numerical error in Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \delta = \bar x_B - \bar x_A} is not scaled down in the way that it is in the Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle n_B = 1} case. In such cases, prefer Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\textstyle \bar x_{AB} = \frac{n_A \bar x_A + n_B \bar x_B}{n_{AB}}} . Reusing the WelfordVariance class from above, we have:

def merge(a: WelfordVariance, b: WelfordVariance) -> WelfordVariance:
    ab = WelfordVariance()
    ab.count = a.count + b.count
    delta = b.mean - a.mean
    ab.mean = (a.count * a.mean + b.count * b.mean) / ab.count
    ab.M2 = a.M2 + b.M2 + delta**2 * a.count * b.count / ab.count
    return ab
    
# example:
ab = merge(a, b)
print(ab.get_sample_variance())

This algorithm allows one to divide a dataset into several pieces, run them in parallel, and then merge the results together. This enables parallelization in any way, including AVX, with GPUs, and computer clusters. The algorithm can also be adapted for higher-order statistics as well as covariance.[3][12]

Example

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 (108 + 4, 108 + 7, 108 + 13, 108 + 16), 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 (109 + 4, 109 + 7, 109 + 13, 109 + 16). 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.

Higher-order statistics

Terriberry[12] extends Chan's formulae to calculating the third and fourth central moments, needed for example when estimating skewness and kurtosis:

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \begin{align} M_{3,X} = M_{3,A} + M_{3,B} & {} + \delta^3\frac{n_A n_B (n_A - n_B)}{n_X^2} + 3\delta\frac{n_AM_{2,B} - n_BM_{2,A}}{n_X} \\[6pt] M_{4,X} = M_{4,A} + M_{4,B} & {} + \delta^4\frac{n_A n_B \left(n_A^2 - n_A n_B + n_B^2\right)}{n_X^3} \\[6pt] & {} + 6\delta^2\frac{n_A^2 M_{2,B} + n_B^2 M_{2,A}}{n_X^2} + 4\delta\frac{n_AM_{3,B} - n_BM_{3,A}}{n_X} \end{align}}

Here the Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle M_k} are again the sums of powers of differences from the mean Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\textstyle \sum(x - \overline{x})^k} , giving

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \begin{align} & \text{skewness} = g_1 = \frac{\sqrt{n} M_3}{M_2^{3/2}}, \\[4pt] & \text{kurtosis} = g_2 = \frac{n M_4}{M_2^2}-3. \end{align} }

For the incremental case (i.e., Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle B = \{x\}} ), this simplifies to:

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \begin{align} \delta & = x - m \\[5pt] m' & = m + \frac{\delta}{n} \\[5pt] M_2' & = M_2 + \delta^2 \frac{n-1}{n} \\[5pt] M_3' & = M_3 + \delta^3 \frac{ (n - 1) (n - 2)}{n^2} - \frac{3\delta M_2}{n} \\[5pt] M_4' & = M_4 + \frac{\delta^4 (n - 1) (n^2 - 3n + 3)}{n^3} + \frac{6\delta^2 M_2}{n^2} - \frac{4\delta M_3}{n} \end{align} }

By preserving the value Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \delta / n} , only one division operation is needed and the higher-order statistics can thus be calculated for little incremental cost.

An example of the online algorithm for kurtosis implemented as described is:

def online_kurtosis(data):
    n = mean = M2 = M3 = M4 = 0

    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ÿ[13] further extends these results to arbitrary-order central moments, for the incremental and the pairwise cases, and subsequently Pébaÿ et al.[14] for weighted and compound moments. One can also find there similar formulas for covariance.

Choi and Sweetman[15] 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:

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle H(x_k)=\frac{h(x_k)}{A}}

where Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle h(x_k)} and Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle H(x_k)} represent the frequency and the relative frequency at bin Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle x_k} and Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\textstyle A= \sum_{k=1}^K h(x_k) \,\Delta x_k} is the total area of the histogram. After this normalization, the Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle n} raw moments and central moments of Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle x(t)} can be calculated from the relative histogram:

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle m_n^{(h)} = \sum_{k=1}^{K} x_k^n H(x_k) \, \Delta x_k = \frac{1}{A} \sum_{k=1}^K x_k^n h(x_k) \, \Delta x_k }
Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \theta_n^{(h)}= \sum_{k=1}^{K} \Big(x_k-m_1^{(h)}\Big)^n \, H(x_k) \, \Delta x_k = \frac{1}{A} \sum_{k=1}^{K} \Big(x_k-m_1^{(h)}\Big)^n h(x_k) \, \Delta x_k }

where the superscript Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle ^{(h)}} indicates the moments are calculated from the histogram. For constant bin width Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \Delta x_k=\Delta x} these two expressions can be simplified using Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle I= A/\Delta x} :

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle m_n^{(h)}= \frac{1}{I} \sum_{k=1}^K x_k^n \, h(x_k) }
Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \theta_n^{(h)}= \frac{1}{I} \sum_{k=1}^K \Big(x_k-m_1^{(h)}\Big)^n h(x_k) }

The second approach from Choi and Sweetman[15] 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 Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle Q} sets of statistical moments are known: Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle (\gamma_{0,q},\mu_{q},\sigma^2_{q},\alpha_{3,q},\alpha_{4,q}) \quad } for Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle q=1,2,\ldots,Q } , then each Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \gamma_n} can be expressed in terms of the equivalent Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle n} raw moments:

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \gamma_{n,q}= m_{n,q} \gamma_{0,q} \qquad \quad \textrm{for} \quad n=1,2,3,4 \quad \text{ and } \quad q = 1,2, \dots ,Q }

where Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \gamma_{0,q}} is generally taken to be the duration of the Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle q^{th}} time-history, or the number of points if Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \Delta t} is constant.

The benefit of expressing the statistical moments in terms of Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \gamma} is that the Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle Q} sets can be combined by addition, and there is no upper limit on the value of Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle Q} .

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \gamma_{n,c}= \sum_{q=1}^Q \gamma_{n,q} \quad \quad \text{for } n=0,1,2,3,4 }

where the subscript Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle _c} represents the concatenated time-history or combined Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \gamma} . These combined values of Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \gamma} can then be inversely transformed into raw moments representing the complete concatenated time-history

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle m_{n,c}=\frac{\gamma_{n,c}}{\gamma_{0,c}} \quad \text{for } n=1,2,3,4 }

Known relationships between the raw moments (Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle m_n} ) and the central moments (Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \theta_n = \operatorname E[(x-\mu)^n])} ) are then used to compute the central moments of the concatenated time-history. Finally, the statistical moments of the concatenated history are computed from the central moments:

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \mu_c=m_{1,c} \qquad \sigma^2_c=\theta_{2,c} \qquad \alpha_{3,c}=\frac{\theta_{3,c}}{\sigma_c^3} \qquad \alpha_{4,c}={\frac{\theta_{4,c}}{\sigma_c^4}}-3 }

Covariance

Very similar algorithms can be used to compute the covariance.

Naïve algorithm

The naïve algorithm is

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \operatorname{Cov}(X,Y) = \frac {\sum_{i=1}^n x_i y_i - (\sum_{i=1}^n x_i)(\sum_{i=1}^n y_i)/n}{n}. }

For the algorithm above, one could use the following Python code:

def naive_covariance(data1, data2):
    n = len(data1)
    sum1 = sum(data1)
    sum2 = sum(data2)
    sum12 = sum([i1 * i2 for i1, i2 in zip(data1, data2)])

    covariance = (sum12 - sum1 * sum2 / n) / n
    return covariance

With estimate of the mean

As for the variance, the covariance of two random variables is also shift-invariant, so given any two constant values Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle k_x} and Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle k_y,} it can be written:

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \operatorname{Cov}(X,Y) = \operatorname{Cov}(X-k_x,Y-k_y) = \dfrac {\sum_{i=1}^n (x_i-k_x) (y_i-k_y) - (\sum_{i=1}^n (x_i-k_x))(\sum_{i=1}^n (y_i-k_y))/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:

def shifted_data_covariance(data_x, data_y):
    n = len(data_x)
    if n < 2:
        return 0
    kx = data_x[0]
    ky = data_y[0]
    Ex = Ey = Exy = 0
    for ix, iy in zip(data_x, data_y):  # for higher precision, use sum():
        Ex += ix - kx                   # Ex = sum(ix - kx for ix in data_x)  # or sum(ix) - kx * n
        Ey += iy - ky                   # Ey = sum(iy - ky for iy in data_y)  # or sum(iy) - ky * n
        Exy += (ix - kx) * (iy - ky)    # Exy = sum((ix - kx) * (iy - ky) for ix, iy in zip(data_x, data_y)) 
    return (Exy - Ex * Ey / n) / n

Two-pass

The two-pass algorithm first computes the sample means, and then the covariance:

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \bar x = \sum_{i=1}^n x_i/n}
Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \bar y = \sum_{i=1}^n y_i/n}
Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \operatorname{Cov}(X,Y) = \frac {\sum_{i=1}^n (x_i - \bar x)(y_i - \bar y)}{n}. }

The two-pass algorithm may be written as:

def two_pass_covariance(data1, data2):
    n = len(data1)
    mean1 = sum(data1) / n
    mean2 = sum(data2) / n

    covariance = 0
    for i1, i2 in zip(data1, data2):    # for higher precision, use sum():
        a = i1 - mean1                  # covariance = sum((i1 - mean1) * (i2 - mean2) for i1, i2 in zip(data1, data2))
        b = i2 - mean2
        covariance += a * b / n
    return covariance

Online

A stable one-pass algorithm exists, similar to the online algorithm for computing the variance, that computes co-moment Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\textstyle C_n = \sum_{i=1}^n (x_i - \bar x_n)(y_i - \bar y_n)} :

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \begin{alignat}{2} \bar x_n &= \bar x_{n-1} &\,+\,& \frac{x_n - \bar x_{n-1}}{n} \\[5pt] \bar y_n &= \bar y_{n-1} &\,+\,& \frac{y_n - \bar y_{n-1}}{n} \\[5pt] C_n &= C_{n-1} &\,+\,& (x_n - \bar x_n)(y_n - \bar y_{n-1}) \\[5pt] &= C_{n-1} &\,+\,& (x_n - \bar x_{n-1})(y_n - \bar y_n) \end{alignat}}

The apparent asymmetry in that last equation is due to the fact that Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\textstyle (x_n - \bar x_n) = \frac{n-1}{n}(x_n - \bar x_{n-1})} , so both update terms are equal to Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\textstyle \frac{n-1}{n}(x_n - \bar x_{n-1})(y_n - \bar y_{n-1})} . Even greater accuracy can be achieved by first computing the means, then using the stable one-pass algorithm on the residuals.

Thus the covariance can be computed as

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \begin{align} \operatorname{Cov}_N(X,Y) = \frac{C_N}{N} &= \frac{\operatorname{Cov}_{N-1}(X,Y)\cdot(N-1) + (x_n - \bar x_n)(y_n - \bar y_{n-1})}{N}\\ &= \frac{\operatorname{Cov}_{N-1}(X,Y)\cdot(N-1) + (x_n - \bar x_{n-1})(y_n - \bar y_n)}{N}\\ &= \frac{\operatorname{Cov}_{N-1}(X,Y)\cdot(N-1) + \frac{N-1}{N}(x_n - \bar x_{n-1})(y_n - \bar y_{n-1})}{N}\\ &= \frac{\operatorname{Cov}_{N-1}(X,Y)\cdot(N-1) + \frac{N}{N-1}(x_n - \bar x_{n})(y_n - \bar y_{n})}{N}. \end{align}}
def online_covariance(data1, data2):
    meanx = meany = C = n = 0
    for x, y in zip(data1, data2):
        n += 1
        dx = x - meanx
        meanx += dx / n
        meany += (y - meany) / n
        C += dx * (y - meany)

    # covariance and Bessel's correction for sample
    return (C / n, C / (n - 1))

A small modification can also be made to compute the weighted covariance:

def online_weighted_covariance(data1, data2, data3):
    meanx = meany = 0
    wsum = wsum2 = 0
    C = 0
    for x, y, w in zip(data1, data2, data3):
        wsum += w
        wsum2 += w * w
        dx = x - meanx
        meanx += (w / wsum) * dx
        meany += (w / wsum) * (y - meany)
        C += w * dx * (y - meany)

    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:[3]

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle C_X = C_A + C_B + (\bar x_A - \bar x_B)(\bar y_A - \bar y_B)\cdot\frac{n_A n_B}{n_X}. }

Weighted batched version

A version of the weighted online algorithm that does batched updated also exists: let Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle w_1, \dots w_N} denote the weights, and write

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \begin{alignat}{2} \bar x_{n+k} &= \bar x_n &\,+\,& \frac{\sum_{i=n+1}^{n+k} w_i (x_i - \bar x_n)}{\sum_{i=1}^{n+k} w_i} \\ \bar y_{n+k} &= \bar y_n &\,+\,& \frac{\sum_{i=n+1}^{n+k} w_i (y_i - \bar y_n)}{\sum_{i=1}^{n+k} w_i} \\ C_{n+k} &= C_n &\,+\,& \sum_{i=n+1}^{n+k} w_i (x_i - \bar x_{n+k})(y_i - \bar y_n) \\ &= C_n &\,+\,& \sum_{i=n+1}^{n+k} w_i (x_i - \bar x_n)(y_i - \bar y_{n+k}) \\ \end{alignat}}

The covariance can then be computed as

Failed to parse (SVG (MathML can be enabled via browser plugin): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle \operatorname{Cov}_N(X,Y) = \frac{C_N}{\sum_{i=1}^{N} w_i}}

(This can be used with Python's sum for better accuracy. Block updating is also related to the parallel/merge algorithm.)

See also

References

  1. 1.0 1.1 Einarsson, Bo (2005). Accuracy and Reliability in Scientific Computing. SIAM. p. 47. ISBN 978-0-89871-584-2.
  2. 2.0 2.1 2.2 2.3 Chan, Tony F.; Golub, Gene H.; LeVeque, Randall J. (1983). "Algorithms for computing the sample variance: Analysis and recommendations" (PDF). The American Statistician. 37 (3): 242–247. doi:10.1080/00031305.1983.10483115. JSTOR 2683386. Archived (PDF) from the original on 9 October 2022.
  3. 3.0 3.1 3.2 Schubert, Erich; Gertz, Michael (9 July 2018). Numerically stable parallel computation of (co-)variance. ACM. p. 10. doi:10.1145/3221269.3223036. ISBN 9781450365055. S2CID 49665540.
  4. Template:Multiref2
  5. Higham, Nicholas J. (2002). "Problem 1.10". Accuracy and Stability of Numerical Algorithms (2nd ed.). Philadelphia, PA: Society for Industrial and Applied Mathematics. doi:10.1137/1.9780898718027. ISBN 978-0-898715-21-7. eISBN 978-0-89871-802-7, 2002075848. Metadata also listed at ACM Digital Library.
  6. Welford, B. P. (1962). "Note on a method for calculating corrected sums of squares and products". Technometrics. 4 (3): 419–420. doi:10.2307/1266577. JSTOR 1266577.
  7. Donald E. Knuth (1998). The Art of Computer Programming, volume 2: Seminumerical Algorithms, 3rd edn., p. 232. Boston: Addison-Wesley.
  8. Ling, Robert F. (1974). "Comparison of Several Algorithms for Computing Sample Means and Variances". Journal of the American Statistical Association. 69 (348): 859–866. doi:10.2307/2286154. JSTOR 2286154.
  9. Cook, John D. (30 September 2022) [1 November 2014]. "Accurately computing sample variance". John D. Cook Consulting: Expert consulting in applied mathematics & data privacy.
  10. West, D. H. D. (1979). "Updating Mean and Variance Estimates: An Improved Method". Communications of the ACM. 22 (9): 532–535. doi:10.1145/359146.359153. S2CID 30671293.
  11. Chan, Tony F.; Golub, Gene H.; LeVeque, Randall J. (November 1979). "Updating Formulae and a Pairwise Algorithm for Computing Sample Variances" (PDF). Department of Computer Science, Stanford University. Technical Report STAN-CS-79-773, supported in part by Army contract No. DAAGEI-‘E-G-013.
  12. 12.0 12.1 Terriberry, Timothy B. (15 October 2008) [9 December 2007]. "Computing Higher-Order Moments Online". Archived from the original on 23 April 2014. Retrieved 5 May 2008.
  13. Pébay, Philippe Pierre (September 2008). "Formulas for Robust, One-Pass Parallel Computation of Covariances and Arbitrary-Order Statistical Moments". Sponsoring Org.: USDOE. Albuquerque, NM, and Livermore, CA (United States): Sandia National Laboratories (SNL). doi:10.2172/1028931. OSTI 1028931. Technical Report SAND2008-6212, TRN: US201201%%57, DOE Contract Number: AC04-94AL85000 – via UNT Digital Library.
  14. Pébaÿ, Philippe; Terriberry, Timothy; Kolla, Hemanth; Bennett, Janine (2016). "Numerically Stable, Scalable Formulas for Parallel and Online Computation of Higher-Order Multivariate Central Moments with Arbitrary Weights". Computational Statistics. Springer. 31 (4): 1305–1325. doi:10.1007/s00180-015-0637-z. S2CID 124570169.
  15. 15.0 15.1 Choi, Myoungkeun; Sweetman, Bert (2010). "Efficient Calculation of Statistical Moments for Structural Health Monitoring". Journal of Structural Health Monitoring. 9 (1): 13–24. doi:10.1177/1475921709341014. S2CID 17534100.