是否有一个 Perl 统计包不能让我一次加载整个数据集?

Posted

技术标签:

【中文标题】是否有一个 Perl 统计包不能让我一次加载整个数据集?【英文标题】:Is there a Perl statistics package that doesn't make me load the entire dataset at once? 【发布时间】:2010-11-27 05:01:30 【问题描述】:

我正在寻找 Perl 的统计数据包(CPAN 很好),它允许我以增量方式添加数据,而不必传入整个数据数组。

只需要平均值、中位数、标准差、最大值和最小值,没有什么太复杂的。

这是因为我的数据集太大而无法放入内存。数据源位于 mysql 数据库中,所以现在我只是查询数据的一个子集并为它们计算统计信息,然后再组合所有可管理的子集。

如果您对如何克服这个问题有其他想法,我将非常感激!

【问题讨论】:

【参考方案1】:

Statistics::Descriptive::Discrete 允许您以类似于 Statistics::Descriptive 的方式执行此操作,但已针对大型数据集进行了优化。 (例如,文档报告内存使用量提高了两个数量级 (100x))。

【讨论】:

小心 - 这个模块(根据 POD)针对离散化数据进行了优化,例如来自具有一组离散可能值的 A/D 转换的数据。例如。如果您的数据是由 8 位 A/D 生成的,那么您的数据集中只有 256 个可能的值。即使您可能拥有一百万个数据点,但在这百万个点中您只有 256 个不同的值。【参考方案2】:

除非您将整个内容保存在内存中两次遍历数据,否则您无法计算出精确的标准差和中位数。

更新虽然您不能一次性完成精确的 stddev,但有一个近似的一次性算法,链接在此答案的评论中。

其余的完全是微不足道的(不需要模块)在 3-5 行 Perl 中完成。 STDDEV/Median 也可以相当简单地在 2 次通过中完成(我刚刚推出了一个完全符合您描述的脚本,但出于 IP 原因,我很确定我不允许将其作为示例发布给您,抱歉)

示例代码:

my ($min, $max)
my $sum = 0;
my $count = 0;
while (<>) 
    chomp;
    my $current_value = $_; #assume input is 1 value/line for simplicity sake
    $sum += $current_value;
    $count++;
    $min = $current_value if (!defined $min || $min > $current_value);
    $max = $current_value if (!defined $max || $max < $current_value);

my $mean = $sum * 1.0 / $count;
my $sum_mean_diffs_2 = 0;

while (<>)  # Second pass to compute stddev (use for median too)
    chomp;
    my $current_value = $_; 
    $sum_mean_diffs += ($current_value - $mean) * ($current_value - $mean);

my $std_dev = sqrt($sum_mean_diffs / $count);
# Median is left as excercise for the reader.

【讨论】:

re $sum * 1.0 / $count,Perl 不像 C 那样进行类型化操作; *1.0 是不必要的。 这不是真的。您可以计算流式平均值和标准差。开发。算法细节可以在***上找到:en.wikipedia.org/wiki/… +1 最后评论。尽管该 Wiki 中的公式只是近似值,但还是很酷!【参考方案3】:

您为什么不简单地向数据库询问您要计算的值?

其中,MySQL 具有 GROUP BY (Aggregate) functions。对于缺少的功能,您只需要a little SQL。

【讨论】:

虽然这对他的项目来说可能没问题,但通常强烈建议将计算统计信息卸载到应用程序代码中,因为扩展应用服务器资源比扩展数据库服务器要容易得多。 @DVK:我不确定我是否认同这个反对意见。导出应用服务器自己统计所需的数据所涉及的资源肯定远远大于允许数据库服务器自己计算它们吗?【参考方案4】:

PDL 可能会提供一个可能的解决方案:

看看这个以前的 SO 答案,它显示了如何获得 means, std dev, etc。

这里是重复的代码的相关部分:

use strict;
use warnings;
use PDL;

my $figs = pdl [
    [0.01, 0.01, 0.02, 0.04, 0.03],
    [0.00, 0.02, 0.02, 0.03, 0.02],
    [0.01, 0.02, 0.02, 0.03, 0.02],
    [0.01, 0.00, 0.01, 0.05, 0.03],
];

my ( $mean, $prms, $median, $min, $max, $adev, $rms ) = statsover( $figs );

【讨论】:

【参考方案5】:

这在很大程度上未经测试,因此请谨慎使用。因为我记性不好,我对照Wikipedia检查了算法。我不知道有一种算法可以从数字流中计算中位数,但这并不意味着没有。

#!/usr/bin/perl
use strict;
use warnings;

use MooseX::Declare;

class SimpleStats 
  has 'min'       => (is => 'rw', isa => 'Num', default => 9**9**9);
  has 'max'       => (is => 'rw', isa => 'Num', default => -9**9**9);
  has 'A'         => (is => 'rw', isa => 'Num', default => 0);
  has 'Q'         => (is => 'rw', isa => 'Num', default => 0);
  has 'n'         => (is => 'rw', isa => 'Int', default => 0);
  has 'n_nonzero' => (is => 'rw', isa => 'Int', default => 0);
  has 'sum_w'     => (is => 'rw', isa => 'Int', default => 0);

  method add (Num $x, Num $w = 1) 
    $self->min($x) if $x < $self->min;
    $self->max($x) if $x > $self->max;
    my $n = $self->n;
    if ($n == 0) 
      $self->A($x);
      $self->sum_w($w);
    
    else 
      my $A = $self->A;
      my $Q = $self->Q;
      my $sum_w_before = $self->sum_w;
      $self->sum_w($sum_w_before+$w);
      $self->A($A + ($x-$A) * $w/$self->sum_w);
      $self->Q($Q + $w*($x-$A)*($x-$self->A));
    
    $self->n($n+1);
    $self->n_nonzero($self->n_nonzero+1) if $w != 0;
    return();
  

  method mean ()  $self->A 

  method sample_variance () 
    $self->Q * $self->n_nonzero() /
    ( ($self->n_nonzero-1) * $self->sum_w )
  

  method std_variance ()  $self->Q / $self->sum_w 
  method std_dev      ()  sqrt($self->std_variance) 

  # slightly evil. Just don't reuse objects
  method reset ()  %$self = %__PACKAGE__->new() 


package main;

my $stats = SimpleStats->new;

while (<STDIN>) 
  s/^\s+//;
  s/\s+$//;
  my ($x, $w) = split /\s+/, $_;
  if (defined $w) 
    $stats->add($x, $w);
   else 
    $stats->add($x);
  


print "Mean:        ", $stats->mean, "\n";
print "Sample var:  ", $stats->sample_variance, "\n";
print "Std var:     ", $stats->std_variance, "\n";
print "Std dev:     ", $stats->std_dev, "\n";
print "Entries:     ", $stats->n, "\n";
print "Min:         ", $stats->min, "\n";
print "Max:         ", $stats->max, "\n";

【讨论】:

【参考方案6】:

@DVK:此处用于计算平均值和标准差的一次性算法http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#On-line_algorithm 不是近似值,并且比您给出的示例在数值上更稳健。请参阅该页面上的参考资料。

【讨论】:

【参考方案7】:

我知道还有 4 年的时间,但如果有人感兴趣,现在有一个 module 用于内存效率近似统计样本分析。它的界面一般遵循Statistics::Descriptive and co的界面。

它将样本划分为对数间隔,并且只保留命中计数。因此引入了一个固定的相对误差(可以在 new() 中调整精度),但是可以在不使用太多内存的情况下处理大量数据。

【讨论】:

以上是关于是否有一个 Perl 统计包不能让我一次加载整个数据集?的主要内容,如果未能解决你的问题,请参考以下文章

一次获取多个 JID XMPP

长谈:关于 View Measure 测量机制,让我一次把话说完

请假过来面试,没有被录用,总不能让我一点收获都没有吧

Perl:同一个文件中的两个包不能导入同一个包?

光棍节的表白句子说说,光棍节的由来

请问怎么用R语言正则表达式统计文章的单词数和中文字数,不能用程序包?