вычислить среднее значение небольших частей столбца, сгруппированных по ключу в 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. Я хочу напечатать это среднее значение рядом со строкой (в столбце 8), которая содержит 3 в столбце 6 (столбец 6). Обратите внимание, что значение в столбце 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);

person Nuttieke    schedule 26.04.2012    source источник
comment
Кроме того, вы хотите просто игнорировать столбцы с 1 по 4?   -  person thb    schedule 26.04.2012
comment
$ tmp_line была ошибкой, исправьте ее сейчас. Мне не нужны столбцы 1–4 для вычисления среднего, но я хочу их распечатать в конце, исправлю в своем вопросе!   -  person Nuttieke    schedule 26.04.2012
comment
Справедливо. Возможно ли, что существуют две конфликтующие строки данных, например 1408,3,345 и 1408,3,999? Если да, что вы хотите сделать в этом случае?   -  person thb    schedule 26.04.2012
comment
нет, невозможно иметь две такие строки   -  person Nuttieke    schedule 26.04.2012
comment
Хорошо. Ответ @Tuxuday ниже интересен, и я не буду его комментировать дальше, но вы можете попробовать. А пока позвольте мне немного поработать над этим и посмотреть, что я могу придумать.   -  person thb    schedule 26.04.2012
comment
Спасибо за уделенное время!   -  person Nuttieke    schedule 26.04.2012


Ответы (3)


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

#!/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>, если вы предпочитаете читать из файла данных, а не из стандартного ввода.

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

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

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

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

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

person thb    schedule 26.04.2012
comment
Спасибо. Я пробовал, но получаю пустой файл, но я думаю, что это идет в правильном направлении. Я думал, что мне нужно использовать хеши, но я действительно не знал, как это сделать .. - person Nuttieke; 26.04.2012
comment
Хорошо. Что касается пустого файла, мой код читает из стандартного ввода, а не из вашего файла. Чтобы заставить его читать из вашего файла, вы захотите заменить <> на <FILE> (и, конечно, вам нужно сначала открыть FILE, как ваш исходный код уже делает). - person thb; 26.04.2012
comment
Да, я это изменил. Я попробую снова! - person Nuttieke; 26.04.2012
comment
Оно работает! Сначала выводится среднее значение, а затем остальные. Это из-за чуши? - person Nuttieke; 26.04.2012
comment
Ой, работает, но не для всех строк. В некоторых строках он дает среднее значение, col5, col6, col7, другие строки показывают: среднее значение, col3, col4, col5, col6, col7, и все строки кажутся начинающимися с запятой - person Nuttieke; 26.04.2012
comment
Ты прав. Мой код - это упрощенный пример, который должен подтолкнуть вас в правильном направлении. Он выполняет основную часть работы, но не обрабатывает строку заголовка или (насколько я знаю, поскольку я не пробовал) N строк. Мой код теряет обучающую ценность, если я загромождаю его обработкой в ​​особых случаях! Что касается сорта, то это ничего. Это просто для вывода в отсортированном порядке столбца 5. Если вы предпочитаете другой порядок, пожалуйста, сообщите, поскольку мы можем сделать любой порядок, который вы хотите, с небольшим дополнительным кодированием. - person thb; 26.04.2012
comment
Большое спасибо, я хотел попробовать с хешами, вы мне понятнее! - person Nuttieke; 27.04.2012

Одностороннее использование модуля 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
person Birei    schedule 26.04.2012
comment
Я тоже попробую это сделать, но Text :: CSV_XS не установлен. - person Nuttieke; 27.04.2012
comment
Я не понимаю полностью, получаю сообщение об ошибке: Ожидаемые поля должны быть ссылкой на массив в script.pl строка 57, ‹$ fh› строка 7. - person Nuttieke; 27.04.2012
comment
@Nuttieke: Я отредактировал скрипт, чтобы добавить специальную проверку переменной $row3. Я предполагаю, потому что не могу воспроизвести вашу ошибку. - person Birei; 27.04.2012

Это должно помочь. Замените 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
person tuxuday    schedule 26.04.2012
comment
Хм, я пробовал, но получаю некоторые ошибки: использование неинициализированного значения в дополнение (+) в строке 10 test.pl, ‹FILE› строке 1886. Это меня озадачило .. - person Nuttieke; 26.04.2012
comment
Да, это предупреждение не должно быть ошибкой как таковое. Его строка coz 10, $ Result {$ Cols [0]} + = $ Cols [2];, не инициализируется перед добавлением. Вы скопировали код в том виде, как он есть, и запустили, изменив только индекс @Cols? - person tuxuday; 26.04.2012
comment
Удалось ли вам вывести avg и выполнить другие требования с помощью приведенного выше кода? - person tuxuday; 26.04.2012
comment
Не знаю, как снова распечатать результаты в виде CSV-файла с помощью Dumper. - person Nuttieke; 27.04.2012
comment
Работает неплохо! Но теперь среднее значение печатается на новой строке. Я пробовал пережевывать $ line, но это не имеет значения. - person Nuttieke; 27.04.2012
comment
Что вы подразумеваете под новой строкой? Пост-образец ввода / вывода. - person tuxuday; 27.04.2012
comment
Я добавил в свой пост образец ввода / вывода. - person tuxuday; 27.04.2012
comment
В вашем примере вывода я получаю -1 и 400,5 в новой строке. Как будто между $ line и $ Sum {$ Cols [0]} / $ Count {$ Cols [0]} будет \ n, - person Nuttieke; 27.04.2012
comment
Назначьте вычисление Avg переменной, затем выполните print $ line, $ Avg \ n;. Какая ОС? Это для твоей работы или домашнего задания? - person tuxuday; 27.04.2012
comment
Я уже пробовал это, также ограничил десятичные числа до 3. Unix - person Nuttieke; 27.04.2012
comment
Невозможно, чтобы вы получили там новые строки. Вы работаете с моим или вашим исходным образцом? - person tuxuday; 27.04.2012
comment
Я починил это. Я пробовал это на своих данных, но, видимо, из-за того, что csv был создан в Windows, мне пришлось использовать регулярное выражение s / \ s + $ //. В очередной раз благодарим за помощь! - person Nuttieke; 27.04.2012