2012-04-26 1 views
2

Этот вопрос очень похож на этот How can I get the average and standard deviations grouped by key?, но мне не удается его изменить, чтобы соответствовать моей проблеме.вычислить среднее значение небольших частей столбца, сгруппированное по ключу в perl?

У меня есть много файлов (.csv) с 7 столбцов, последние три колонки выглядеть следующим образом:

col5,col6,col7 
1408,1,123 
1408,2,234 
1408,3,345 
1408,4,456 
1408,5,567 
1408,6,678 
1409,0,123 
1409,1,234 
1409,2,345 
1409,3,456 
1409,4,567 
1409,5,678 
1409,6,789 
... 
N,0,123 
N,1,234 
N,2,345 
N,3,456 
N,4,567 
N,5,678 
N,6,789 

То, что я хочу сделать, это вычислить среднее значение последнего столбца (col7) для всех значений, которые имеют одинаковое значение в столбце 5 (col5), поэтому 1408, 1409, 1410, ... до тех пор, пока N и я не знаю N. Я хочу напечатать это среднее значение рядом с линией (в col8), который содержит 3 в столбце 6 (col6). Обратите внимание, что значение в колонке 6 (col6) от 0 до 6, а первый номер файла не всегда 0. Так что я хочу:

col1,col2,col3,col4,col5,col6,col7,col8 
bla,bla,bla,bla,1408,3,345,400.5 
bla,bla,bla,bla,1409,3,456,456 
... 
bla,bla,bla,bla,N,3,456,456 

У меня есть сценарий, который я могу использовать для вычислить среднее значение, но я должен уметь вставлять свои значения в массив для этого. Ниже я попытался сделать это, но это не сработает. Кроме того, я просто пытаюсь изучить Perl самостоятельно, поэтому, если это похоже на дерьмо, я просто пытаюсь!

open (FILE, "<", $dir.$file) or die; 
    my @lines = <FILE>; 
    foreach my $line(@lines) { 
     my ($col1,$col2,$col3,$col4,$col5,$col6,$col7) = split(/\,/, $line); 
     push @arrays5, $col5; 
    } 

    foreach my $array5(@arrays5) {    
     foreach my $line(@lines) { 
      my ($col1,$col2,$col3,$col4,$col5,$col6,$col7) = split(/\,/, $line); 
      if ($array5 == $col5) { 
       push @arrays7, $col7; 
      } 
     } 
    } 
close(FILE); 
+0

Что такое 'tmp_line'? – thb

+0

Кроме того, вы имеете в виду просто игнорировать столбцы с 1 по 4? – thb

+0

Ошибка $ tmp_line была исправлена. Мне не нужны столбцы 1 - 4, чтобы вычислить среднее значение, но я также хочу напечатать их в конце, я исправлю его в своем вопросе! – Nuttieke

ответ

0

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

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

my $target = 3; 

my %summary; 

while(<>) { 
    chomp; 
    my ($col1,$col2,$col3,$col4,$col5,$col6,$col7) = split /\,/; 
    $summary{$col5}{total} += $col7; 
    ++$summary{$col5}{count}; 
    $summary{$col5}{line} = $_ if $col6 == $target; 
} 

$summary{$_}{average} = $summary{$_}{total}/$summary{$_}{count} 
    for keys %summary; 

print "${summary{$_}{line}},${summary{$_}{average}}\n" 
    for sort keys %summary; 

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

Обратите внимание, что вы можете заменить <> на <FILE>, если вы предпочитаете читать из своего файла данных, а не из стандартного ввода.

РЕАЛИЗАЦИЯ ПРИМЕЧАНИЯ

код полагается на autovivification функции Perl. Обратите внимание, например, на строку ++$summary{$col5}{count};, которая, по-видимому, первоначально увеличивала несуществующий счетчик. Однако на самом деле это стандартная идиома Perl. Если вы пытаетесь сделать что-то арифметическое (например, инкремент) объекту, которого не существует, Perl неявно создает объект, инициализирует его до нуля, а затем делает то, что вам нужно (например, инкремент).

Возможно, было бы неразумно, если бы более трезвый язык программирования, такой как C++, был автовивифицирован, но многолетний опыт предполагает, что автовивитация поражает правильный баланс между порядком и удобством на чуть менее трезвом языке, таком как Perl.

На более элементарном уровне код, вероятно, имеет смысл только для тех, которые используются для хэшей Perl. Однако, если вы раньше не использовали хеши Perl, это был бы такой же хороший шанс, как и любой другой. Хэш является центральным элементом языка, и приведенное выше является довольно типичным примером его использования.

В этом случае у нас есть хэш хешей, что опять-таки довольно типично.

+0

Спасибо. Я пробовал это, но я получаю пустой файл, но я думаю, что это идет в правильном направлении. Я думал, что мне нужно использовать хэши, но я действительно не знал, как это сделать. – Nuttieke

+0

Хорошо. Что касается пустого файла, мой код читается со стандартного ввода, а не из вашего файла. Чтобы получить его для чтения из вашего файла, вы захотите заменить '<>' на '' (и, конечно, вы должны сначала открыть 'ФАЙЛ', как это делает ваш исходный код). – thb

+0

Да, я это изменил. Я попробую снова! – Nuttieke

0

Это должно сделать трюк. Заменить Cols [index] соответственно.

use Data::Dumper ; 
    open (FILE, "<", '/tmp/myfile') or die; 
    my @lines ; 
    my (%Sum,%Count); 

    chomp(@lines = <FILE>); 
    foreach my $line(@lines) { 
     next if $line =~ /col/; 
     my @Cols = split /,/, $line; 
     $Sum{$Cols[0]} += $Cols[2] ; 
     $Count{$Cols[0]}++; 
    } 

    foreach my $line(@lines) { 
     if($line=~/col/) { 
      print "$line,colX\n" ; 
      next; 
     } 

     my @Cols = split /,/, $line; 
     if($Cols[1]==3) { 
      print "$line,",$Sum{$Cols[0]}/$Count{$Cols[0]},"\n" ; 
     } else { 
      print "$line,-1\n"; 
     } 
    } 

ввода пробы/TMP/MyFile

col5,col6,col7 
1408,1,123 
1408,2,234 
1408,3,345 
1408,4,456 
1408,5,567 
1408,6,678 
1409,0,123 
1409,1,234 

Пример вывода

col5,col6,col7,colX 
1408,1,123,-1 
1408,2,234,-1 
1408,3,345,400.5 
1408,4,456,-1 
1408,5,567,-1 
1408,6,678,-1 
1409,0,123,-1 
1409,1,234,-1 
+0

Хм, я попробовал, но у меня есть некоторые ошибки: использование неинициализированного значения дополнительно (+) в строке test.pl 10, строка 1886. Это меня озадачило .. – Nuttieke

+0

Да, это ** предупреждение ** не должно быть ** ошибкой ** как таковой. Его линия 10, ** $ Результат {$ Cols [0]} + = $ Cols [2]; **, не инициализируется перед добавлением. Вы скопировали код, как есть, и выполнили - изменение только ** индекса @ Cols **? – tuxuday

+0

Ага, сейчас работает! Он принимает сумму этих значений. – Nuttieke

2

В одну сторону: Text::CSV_XS модуль. Он не является встроенным, поэтому он должен быть установлен с CPAN или аналогичным инструментом.

Содержание script.pl:

use warnings; 
use strict; 
use Text::CSV_XS; 

my ($offset, $col_total, $row3, $rows_processed); 

## Check arguments to the script. 
die qq[Usage: perl $0 <input-file>\n] unless @ARGV == 1; 

## Open input file. 
open my $fh, q[<], shift or die qq[Open error: $!\n]; 

## Create the CSV object. 
my $csv = Text::CSV_XS->new or 
     die qq[ERROR: ] . Text::CSV_XS->error_diag(); 

## Read file content seven lines each time. 
while (my $rows = $csv->getline_all($fh, $offset, 7)) { 

     ## End when there is no more rows. 
     last unless @$rows; 

     ## For each row in the group of seven... 
     for my $row (0 .. $#{$rows}) { 

       ## Get value of last column. 
       my $last_col_value = $rows->[ $row ][ $#{$rows->[$row]} ]; 

       ## If last column is not a number it is the header, so print it 
       ## appending the eigth column and read next one. 
       unless ($last_col_value =~ m/\A\d+\Z/) { 
         $csv->print(\*STDOUT, $rows->[ $row ]); 
         printf qq[,%s\n], q[col8]; 
         next; 
       } 

       ## Acumulate total amount for last column. 
       $col_total += $last_col_value; 

       ## Get third row. The output will be this row with the 
       ## average appended. 
       if ($rows->[ $row ][-2] == 3) { 
         $row3 = [ @{ $rows->[ $row ] } ]; 
       } 

       ## Count processed rows. 
       ++$rows_processed; 
     } 

     ## Print row with its average. 
     if ($rows_processed > 0 && ref $row3) { 
       $csv->print(\*STDOUT, $row3); 
       printf qq[,%g\n], $col_total/$rows_processed; 
     } 

     ## Initialize variables. 
     $col_total = $rows_processed = 0; 
     undef $row3; 
} 

Содержание infile:

col1,col2,col3,col4,col5,col6,col7 
bla,bla,bla,bla,1408,1,123 
bla,bla,bla,bla,1408,2,234 
bla,bla,bla,bla,1408,3,345 
bla,bla,bla,bla,1408,4,456 
bla,bla,bla,bla,1408,5,567 
bla,bla,bla,bla,1408,6,678 
bla,bla,bla,bla,1409,0,123 
bla,bla,bla,bla,1409,1,234 
bla,bla,bla,bla,1409,2,345 
bla,bla,bla,bla,1409,3,456 
bla,bla,bla,bla,1409,4,567 
bla,bla,bla,bla,1409,5,678 
bla,bla,bla,bla,1409,6,789 

Выполнить это нравится:

perl script.pl infile 

С следующий вывод:

col1,col2,col3,col4,col5,col6,col7,col8 
bla,bla,bla,bla,1408,3,345,400.5 
bla,bla,bla,bla,1409,3,456,456 
+0

Я тоже попробую это, но текст :: CSV_XS не установлен. – Nuttieke

+0

Я не получаю это полностью, получаю сообщение об ошибке: Ожидаемые поля должны быть массивом ref в строке script.pl, строка 57, <$fh> строка 7. – Nuttieke

+0

@Nuttieke: я отредактировал скрипт, чтобы добавить специальную проверку над '$ row3 'переменная. Я предполагаю, потому что не могу воспроизвести вашу ошибку. – Birei