介绍
该算法是最初由B.P.Welford于1962年提出的计算样本均值和样本方差的算法。
算法如下1:
初始化 M 1 = x 1 , S 1 = 0 M_1=x_1,S_1=0 M1=x1,S1=0
对于接下来的样本值 x x x,使用递推公式
M
k
=
M
k
−
1
+
x
k
−
M
k
−
1
k
M_k=M_{k-1}+\frac{x_k-M_{k-1}}{k}
Mk=Mk−1+kxk−Mk−1
S
k
=
S
k
−
1
+
(
x
k
−
M
k
−
1
)
(
x
k
−
M
k
)
S_k=S_{k-1}+(x_k-M_{k-1})(x_k-M_k)
Sk=Sk−1+(xk−Mk−1)(xk−Mk)
其中, 2 ⩽ k ⩽ n 2\leqslant{k}\leqslant{n} 2⩽k⩽n,第 k k k个样本方差估计为 s 2 = S k ( k − 1 ) s^2=\frac{S_k}{(k-1)} s2=(k−1)Sk
推导
样本均值2
通常计算均值的方法是
m
e
a
n
=
t
o
t
a
l
c
o
u
n
t
mean=\frac{total}{count}
mean=counttotal
用数学符号表达为:
x
ˉ
=
1
n
∑
i
=
1
n
x
i
\bar{x}=\frac{1}{n}\sum_{i=1}^{n}x_{i}
xˉ=n1i=1∑nxi
但是传统的计算方法有如下两个弊端:
- 当累积的总和很大,在使用浮点型数据类型时可能造成精度缺失和溢出问题
- 计算时必须保存所有数据
这两个问题可以使用一种增加式方法来解决,当有新值出现,再调整均值和方差。
均值可以表示为前n-1
个值的均值加上最后的新值除以n
:
x
ˉ
=
1
n
(
∑
i
=
1
n
−
1
x
i
+
x
n
)
\bar{x}=\frac{1}{n}(\sum_{i=1}^{n-1}x_{i}+x_{n})
xˉ=n1(i=1∑n−1xi+xn)
其中前n-1个值的均值可以表示为total/sum的形式:
x
ˉ
n
−
1
=
∑
i
=
1
n
−
1
x
i
n
−
1
\bar{x}_{n-1}=\frac{\sum_{i=1}^{n-1}x_{i}}{n-1}
xˉn−1=n−1∑i=1n−1xi
变换该公式:
∑
i
=
1
n
−
1
x
i
=
(
x
ˉ
n
−
1
)
(
n
−
1
)
\sum_{i=1}^{n-1}x_{i}=(\bar{x}_{n-1})(n-1)
i=1∑n−1xi=(xˉn−1)(n−1)
使用上式表示前n-1
个值的均值,带入均值计算公式:
x
ˉ
n
=
1
n
(
(
n
−
1
)
x
ˉ
n
−
1
+
x
n
)
\bar{x}_{n}=\frac{1}{n}((n-1)\bar{x}_{n-1}+x_{n})
xˉn=n1((n−1)xˉn−1+xn)
展开上式:
x
ˉ
n
=
n
x
ˉ
n
−
1
−
x
ˉ
n
−
1
+
x
n
n
\bar{x}_{n}=\frac{n\bar{x}_{n-1}-\bar{x}_{n-1}+x_n}{n}
xˉn=nnxˉn−1−xˉn−1+xn
变换一下:
x
ˉ
n
=
x
ˉ
n
−
1
+
x
n
−
x
ˉ
n
−
1
n
{\color{DarkRed}\bar{x}_{n}=\bar{x}_{n-1}+\frac{x_n-{\bar{x}}_{n-1}}{n}}
xˉn=xˉn−1+nxn−xˉn−1
所以当新增一个值时,新的均值等于旧的均值再加上
x
n
−
x
ˉ
n
−
1
n
\frac{x_n-{\bar{x}}_{n-1}}{n}
nxn−xˉn−1
在得到结论之前,我们可以再推导出一个将要用于方差计算中的恒等式。
我们当前的结论变换一下:
x
ˉ
n
−
x
ˉ
n
−
1
=
x
n
−
x
ˉ
n
−
1
n
\bar{x}_{n}-\bar{x}_{n-1}=\frac{x_n-{\bar{x}}_{n-1}}{n}
xˉn−xˉn−1=nxn−xˉn−1
两端同时乘以n:
n
(
x
ˉ
n
−
x
ˉ
n
−
1
)
=
x
n
−
x
ˉ
n
−
1
n(\bar{x}_{n}-\bar{x}_{n-1})=x_n-{\bar{x}}_{n-1}
n(xˉn−xˉn−1)=xn−xˉn−1
再同时乘以-1:
n
(
x
ˉ
n
−
1
−
x
ˉ
n
)
=
x
ˉ
n
−
1
−
x
n
{\color{DarkRed}n(\bar{x}_{n-1}-\bar{x}_{n})={\bar{x}}_{n-1}-x_n}
n(xˉn−1−xˉn)=xˉn−1−xn
样本方差计算3
这节将会介绍如何递增地计算样本方差和标准差。
同样地,这种方法可以不需要保存所有数据,并且是“数值稳定”的,即不会有精度和溢出问题。
样本方差算法的推导比样本均值多一些步骤,但同样容易理解。
通常来说,样本方差的数学公式如下:
s
2
=
∑
i
=
1
n
(
x
i
−
x
ˉ
n
)
2
(
n
−
1
)
s^2=\frac{\sum_{i=1}^n(x_i-\bar{x}_n )^2}{(n-1)}
s2=(n−1)∑i=1n(xi−xˉn)2
两遍同乘以
(
n
−
1
)
(n-1)
(n−1):
(
n
−
1
)
s
2
=
∑
i
=
1
n
(
x
i
−
x
ˉ
n
)
2
(n-1)s^2=\sum_{i=1}^n(x_i-\bar{x}_n )^2
(n−1)s2=i=1∑n(xi−xˉn)2
令
d
n
2
=
(
n
−
1
)
s
2
d_n^2=(n-1)s^2
dn2=(n−1)s2:
d
n
2
=
∑
i
=
1
n
(
x
i
−
x
ˉ
n
)
2
d_n^2=\sum_{i=1}^n(x_i-\bar{x}_n )^2
dn2=i=1∑n(xi−xˉn)2
展开等式右边:
d
n
2
=
∑
i
=
1
n
(
x
i
2
−
2
x
i
x
ˉ
n
+
x
ˉ
n
2
)
d_n^2=\sum_{i=1}^n(x_i^2-2x_i\bar{x}_n+\bar{x}_n^2 )
dn2=i=1∑n(xi2−2xixˉn+xˉn2)
进一步展开等式右边:
d
n
2
=
∑
i
=
1
n
x
i
2
−
∑
i
=
1
n
2
x
i
x
ˉ
n
+
∑
i
=
1
n
x
ˉ
n
2
d_n^2=\sum_{i=1}^nx_i^2-\sum_{i=1}^n2x_i\bar{x}_n+\sum_{i=1}^n\bar{x}_n^2
dn2=i=1∑nxi2−i=1∑n2xixˉn+i=1∑nxˉn2
提出常数:
d
n
2
=
∑
i
=
1
n
x
i
2
−
2
x
ˉ
n
∑
i
=
1
n
x
i
+
x
ˉ
n
2
∑
i
=
1
n
1
d_n^2=\sum_{i=1}^nx_i^2-2\bar{x}_n\sum_{i=1}^nx_i+\bar{x}_n^2 \sum_{i=1}^n1
dn2=i=1∑nxi2−2xˉni=1∑nxi+xˉn2i=1∑n1
又因为:
∑
i
=
1
n
x
i
=
n
x
ˉ
n
\sum_{i=1}^nx_i=n\bar{x}_n
i=1∑nxi=nxˉn
代入得:
d
n
2
=
∑
i
=
1
n
x
i
2
−
2
n
x
ˉ
n
2
+
n
x
ˉ
n
2
d_n^2=\sum_{i=1}^nx_i^2-2n\bar{x}_n^2+n\bar{x}_n^2
dn2=i=1∑nxi2−2nxˉn2+nxˉn2
整理得到:
d
n
2
=
∑
i
=
1
n
x
i
2
−
n
x
ˉ
n
2
d_n^2=\sum_{i=1}^nx_i^2-n\bar{x}_n^2
dn2=i=1∑nxi2−nxˉn2
同理可得到:
d
n
−
1
2
=
∑
i
=
1
n
−
1
x
i
2
−
(
n
−
1
)
x
ˉ
n
−
1
2
d_{n-1}^2=\sum_{i=1}^{n-1}x_i^2-(n-1)\bar{x}_{n-1}^2
dn−12=i=1∑n−1xi2−(n−1)xˉn−12
上述两式相减得:
d
n
2
−
d
n
−
1
2
=
∑
i
=
1
n
x
i
2
−
n
x
ˉ
n
2
−
(
∑
i
=
1
n
−
1
x
i
2
−
(
n
−
1
)
x
ˉ
n
−
1
2
)
d_n^2-d_{n-1}^2=\sum_{i=1}^nx_i^2-n\bar{x}_n^2-(\sum_{i=1}^{n-1}x_i^2-(n-1)\bar{x}_{n-1}^2)
dn2−dn−12=i=1∑nxi2−nxˉn2−(i=1∑n−1xi2−(n−1)xˉn−12)
整理得:
d
n
2
−
d
n
−
1
2
=
∑
i
=
1
n
x
i
2
−
n
x
ˉ
n
2
−
∑
i
=
1
n
−
1
x
i
2
+
(
n
−
1
)
x
ˉ
n
−
1
2
d_n^2-d_{n-1}^2=\sum_{i=1}^nx_i^2-n\bar{x}_n^2-\sum_{i=1}^{n-1}x_i^2+(n-1)\bar{x}_{n-1}^2
dn2−dn−12=i=1∑nxi2−nxˉn2−i=1∑n−1xi2+(n−1)xˉn−12
同类项相减:
d
n
2
−
d
n
−
1
2
=
x
n
2
−
n
x
ˉ
n
2
+
(
n
−
1
)
x
ˉ
n
−
1
2
d_n^2-d_{n-1}^2=x_n^2-n\bar{x}_n^2+(n-1)\bar{x}_{n-1}^2
dn2−dn−12=xn2−nxˉn2+(n−1)xˉn−12
展开最后两项:
d
n
2
−
d
n
−
1
2
=
x
n
2
−
n
x
ˉ
n
2
+
n
x
ˉ
n
−
1
2
−
x
ˉ
n
−
1
2
d_n^2-d_{n-1}^2=x_n^2-n\bar{x}_n^2+n\bar{x}_{n-1}^2-\bar{x}_{n-1}^2
dn2−dn−12=xn2−nxˉn2+nxˉn−12−xˉn−12
重排顺序:
d
n
2
−
d
n
−
1
2
=
x
n
2
−
x
ˉ
n
−
1
2
−
n
x
ˉ
n
2
+
n
x
ˉ
n
−
1
2
d_n^2-d_{n-1}^2=x_n^2-\bar{x}_{n-1}^2-n\bar{x}_n^2+n\bar{x}_{n-1}^2
dn2−dn−12=xn2−xˉn−12−nxˉn2+nxˉn−12
提取公因式:
d
n
2
−
d
n
−
1
2
=
x
n
2
−
x
ˉ
n
−
1
2
+
n
(
x
ˉ
n
−
1
2
−
x
ˉ
n
2
)
d_n^2-d_{n-1}^2=x_n^2-\bar{x}_{n-1}^2+n(\bar{x}_{n-1}^2-\bar{x}_n^2)
dn2−dn−12=xn2−xˉn−12+n(xˉn−12−xˉn2)
由于
a
2
−
b
2
=
(
a
−
b
)
(
a
+
b
)
a^2-b^2=(a-b)(a+b)
a2−b2=(a−b)(a+b)
将其运用在括号中的表达式:
d
n
2
−
d
n
−
1
2
=
x
n
2
−
x
ˉ
n
−
1
2
+
n
(
x
ˉ
n
−
1
−
x
ˉ
n
)
(
x
ˉ
n
−
1
+
x
ˉ
n
)
d_n^2-d_{n-1}^2=x_n^2-\bar{x}_{n-1}^2+n(\bar{x}_{n-1}-\bar{x}_n)(\bar{x}_{n-1}+\bar{x}_n)
dn2−dn−12=xn2−xˉn−12+n(xˉn−1−xˉn)(xˉn−1+xˉn)
上一小节我们得到:
n
(
x
ˉ
n
−
1
−
x
ˉ
n
)
=
x
ˉ
n
−
1
−
x
n
{\color{DarkRed}n(\bar{x}_{n-1}-\bar{x}_{n})={\bar{x}}_{n-1}-x_n}
n(xˉn−1−xˉn)=xˉn−1−xn
代入得:
d
n
2
−
d
n
−
1
2
=
x
n
2
−
x
ˉ
n
−
1
2
+
(
x
ˉ
n
−
1
−
x
n
)
(
x
ˉ
n
−
1
+
x
ˉ
n
)
d_n^2-d_{n-1}^2=x_n^2-\bar{x}_{n-1}^2+({\bar{x}}_{n-1}-x_n)(\bar{x}_{n-1}+\bar{x}_n)
dn2−dn−12=xn2−xˉn−12+(xˉn−1−xn)(xˉn−1+xˉn)
展开得:
d
n
2
−
d
n
−
1
2
=
x
n
2
−
x
ˉ
n
−
1
2
+
x
ˉ
n
−
1
2
+
x
ˉ
n
−
1
x
ˉ
n
−
x
n
x
ˉ
n
−
1
−
x
ˉ
n
x
n
d_n^2-d_{n-1}^2=x_n^2-\bar{x}_{n-1}^2+{\bar{x}}_{n-1}^2+{\bar{x}}_{n-1}\bar{x}_n-x_n\bar{x}_{n-1}-\bar{x}_nx_n
dn2−dn−12=xn2−xˉn−12+xˉn−12+xˉn−1xˉn−xnxˉn−1−xˉnxn
相互抵消后得:
d
n
2
−
d
n
−
1
2
=
x
n
2
+
x
ˉ
n
−
1
x
ˉ
n
−
x
n
x
ˉ
n
−
1
−
x
ˉ
n
x
n
d_n^2-d_{n-1}^2=x_n^2+{\bar{x}}_{n-1}\bar{x}_n-x_n\bar{x}_{n-1}-\bar{x}_nx_n
dn2−dn−12=xn2+xˉn−1xˉn−xnxˉn−1−xˉnxn
可知
(
x
−
a
)
(
x
−
b
)
=
x
2
−
b
x
−
a
x
+
a
b
(x-a)(x-b)=x^2-bx-ax+ab
(x−a)(x−b)=x2−bx−ax+ab
所以原式变换为:
d
n
2
−
d
n
−
1
2
=
(
x
n
−
x
ˉ
n
−
1
)
(
x
n
−
x
ˉ
n
)
d_n^2-d_{n-1}^2=(x_n-\bar{x}_{n-1})(x_n-\bar{x}_n)
dn2−dn−12=(xn−xˉn−1)(xn−xˉn)
所以
d
n
2
=
d
n
−
1
2
+
(
x
n
−
x
ˉ
n
−
1
)
(
x
n
−
x
ˉ
n
)
{\color{DarkRed}d_n^2=d_{n-1}^2+(x_n-\bar{x}_{n-1})(x_n-\bar{x}_n)}
dn2=dn−12+(xn−xˉn−1)(xn−xˉn)
过程有些繁琐,但是结果已经得到啦!
为了得到方差
s
2
s^2
s2,只需将
d
n
2
d_n^2
dn2除以
n
n
n或者
(
n
−
1
)
(n-1)
(n−1)即可。
s
n
2
=
d
n
2
n
−
1
s_n^2=\frac{d_n^2}{n-1}
sn2=n−1dn2
标准差
s
n
=
d
n
2
n
−
1
s_n=\sqrt{\frac{d_n^2}{n-1}}
sn=n−1dn2
程序实现
package chapter2;
import java.util.Scanner;
public class Welford {
private int num;
private double mu;
private double sum;
/**
* 添加新值
* @param newValue
*/
public void addNewValue(double newValue){
num++;
double delta = (newValue-mu);
mu+=delta/num;
sum+=delta*(newValue-mu);
}
public Welford() {
}
/**
* @return 均值
*/
public double mean() {
return mu;
}
/**
*
* @return 方差
*/
public double var() {
if (num <= 1) return Double.NaN;
return sum/(num-1);
}
/**
*
* @return 标准差
*/
public double stddev() {
return Math.sqrt(this.var());
}
/**
*
* @return 样本量
*/
public int count(){
return num;
}
@Override
public String toString() {
return
"count=" + num +
", mean=" + mu +
", stddev=" + stddev()
;
}
public static void main(String[] args) {
Welford w = new Welford();
Scanner sc = new Scanner(System.in);
while (sc.hasNext()){
w.addNewValue(Double.parseDouble(sc.next()));
}
System.out.printf("n = %d\n",w.count());
System.out.printf("mean = %.5f\n",w.mean());
System.out.printf("stddev = %.5f\n",w.stddev());
System.out.printf("var = %.5f\n",w.var());
System.out.print(w);
}
}