我想计算perl中每行数字的方差。我写了这个子程序:
################################################################
# variance
#
#
# A subroutine to compute the variance of an array
# division by n-1 i s used
#
sub var{
my ($data) = @_;
if (@$data ==1) {
return 0;
}
my $mean = mean ($data);
my $sqtotal = 0;
foreach (@$data) {
$sqtotal += ($_ - $mean) ** 2
}
my $var = $sqtotal / (scalar @$data - 1);
return $var;
}
如果我用58个相同数字的元素给它这个数组
[0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98, 0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98, 0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98,0.98]
计算给了我1.25421964097639e-30。
我还尝试使用Statistics :: Descriptive模块(http://metacpan.org/pod/Statistics::Descriptive),它给了我2.11916254524942e-15。
我也试过这个网站(http://www.alcula.com/calculators/statistics/variance/),结果是2.2438191655582E-15。
为什么结果不一样......
我本来可以使用该模块,但对于我的文件来说,它的内存密集程度非常高,基本上由数百万行58个数字组成。我不知道为什么它耗尽了这么多内存。
有人可以告诉我为什么我的计算给出了与模块不同的数字,以及如何使模块以更少的内存工作?内存密集的东西只是该模块的固有缺点。几个帖子似乎暗示了这一点。
谢谢!
答案 0 :(得分:5)
常数序列的方差为零,因此您的计算或多或少都是正确的,并且或多或少都相同。
您得到的结果略微不同于零,因为您使用有限精度浮点数执行许多操作。我们来看看这段代码:
$z = 0;
$z += 0.98 for 1..58;
$mean = $z / 58;
printf "%.20f", $mean;
使用此代码,我们取数字0.98的58个实例的总和,然后将总和除以58.这个代码打印出0.98000000000000000000
是有道理的,对吧?不,我实际得到的是
0.97999999999999887201
(您的结果可能会有所不同)。
规范What Every Programmer Should Know About Floating-Point Arithmetic可以向你解释血淋淋的细节。
答案 1 :(得分:1)
您的数据使用0.01(1/100)精度吗?你提供我的建议的例子。
YES =>您可以使用定点算术而不是浮点算法来减少舍入误差的累积。使用1 /(100 ** 2)= 1 / 10_000比例因子。
https://en.wikipedia.org/wiki/Fixed-point_arithmetic
sub var4{
my ($data) = @_;
if (@$data ==1) {
return 0;
}
my $totalD2 = 0;
foreach (@$data) {
$totalD2 += $_*100
}
my $meanD2 = $totalD2 / (scalar @$data);
my $sqtotalD4 = 0;
foreach (@$data) {
$sqtotalD4 += ($_*100 - $meanD2) ** 2;
}
my $varD4 = $sqtotalD4 / (scalar @$data - 1);
return $varD4/10_000; # convert from fixed point to floating point
}
答案 2 :(得分:1)
我正在等待有人发表关于浮点运算(ty mob)的帖子,因为这最终是答案。
但是,您链接到的Statistics :: Descriptive模块为version 2.6,而当前为version 3.0607。
如果查看2.6的源代码,它会使用一些奇怪的数学来计算均值和方差:
sub add_data {
....
##Calculate new mean, pseudo-variance, min and max;
foreach ( @{ $aref } ) {
$oldmean = $self->{mean};
$self->{sum} += $_;
$self->{count}++;
....
$self->{mean} += ($_ - $oldmean) / $self->{count};
$self->{pseudo_variance} += ($_ - $oldmean) * ($_ - $self->{mean});
}
现在,这种计算运行平均值的方法是数学上准确的,与Sum(A1..An)/n
相同。但是,由于浮点运算,你会看到一个区别,而不是只是从和/计数中得到平均值。另外,我怀疑这种运行方差可能在数学上是相同的(不打算做纸张证明),但是你也会看到由浮点运算引起的细微差别。
该模块的最新版本确实使用了更简单的计算均值和方差的方法,并且通过利用List :: Util等模块也提高了效率。因此,如果您链接到2.6版本只是一个过分,那么我建议您升级到最新版本。