Есть ли пакет статистики Perl, который не заставляет меня загружать весь набор данных одновременно? - PullRequest
9 голосов
/ 10 сентября 2009

Я ищу статистический пакет для Perl (CPAN в порядке), который позволяет мне добавлять данные постепенно, вместо того, чтобы передавать весь массив данных.

Необходимы только среднее, медиана, стандартное отклонение, максимальное и минимальное, ничего сложного.

Причина этого в том, что мой набор данных слишком велик, чтобы поместиться в память. Источник данных находится в базе данных MySQL, поэтому сейчас я просто запрашиваю подмножество данных и вычисляю для них статистику, а затем объединяю все управляемые подмножества.

Если у вас есть другие идеи о том, как преодолеть эту проблему, я был бы очень признателен!

Ответы [ 7 ]

5 голосов
/ 10 сентября 2009

Вы не можете указать точное значение 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.
4 голосов
4 голосов
/ 10 сентября 2009

Почему бы вам просто не спросить базу данных о значениях, которые вы пытаетесь вычислить?

Среди прочего, MySQL поддерживает GROUP BY (Aggregate) функции . Для недостающих функций все, что вам нужно, это немного SQL .

4 голосов
/ 10 сентября 2009

Статистика :: Описательный :: Дискретный позволяет сделать это способом, аналогичным Статистике :: Описательный, но оптимизирован для использования с большими наборами данных. (Документация сообщает об улучшении использования памяти, например, на два порядка (100x)).

1 голос
/ 11 октября 2010

@ DVK: однопроходные алгоритмы для вычисления среднего и стандартного отклонения здесь http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#On-line_algorithm не являются приближениями и являются более числовыми, чем пример, который вы приводите. См. Ссылки на этой странице.

0 голосов
/ 29 июня 2013

Я понимаю, что прошло 4 года, но на тот случай, если кому-то будет интересно, теперь есть модуль для эффективного использования памяти приблизительный статистический анализ выборки. Его интерфейс обычно соответствует интерфейсу Statistics :: Descriptive и co.

Делит выборку на логарифмические интервалы и сохраняет только количество попаданий. Таким образом, вводится фиксированная относительная ошибка (точность может быть скорректирована в new ()), однако большие объемы данных могут обрабатываться без использования большого количества памяти.

0 голосов
/ 12 сентября 2009

Это в основном не проверено, поэтому используйте с осторожностью. Поскольку у меня плохая память, я проверил алгоритм по Википедии . Мне не известен алгоритм вычисления медианы из потока чисел, но это не значит, что его нет.

#!/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";
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...