2

这个问题与这个问题非常相似如何获得按键分组的平均值和标准偏差?但我没有设法修改它以适应我的问题。

我有很多包含 7 列的文件 (.csv),最后三列如下所示:

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

我想要做的是计算在第 5 列 (col5) 中具有相同值的所有值的最后一列 (col7) 的平均值,所以 1408、1409、1410,... 直到 N 并且我没有知道 N。我想在第 6 列(col6)中包含 3 的行(在 col8 中)旁边打印这个平均值。请注意,第 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);
4

3 回答 3

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
于 2012-04-26T14:07:05.377 回答
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>

实施说明

该代码依赖于 Perl 的自动生存功能。观察例如 line ++$summary{$col5}{count};,它最初似乎增加了一个不存在的计数器。然而,这实际上是标准的 Perl 习惯用法。如果你试图对一个不存在的对象做一些算术运算(比如递增),Perl 会隐式创建这个对象,将它初始化为零,然后对它做你想做的事情(比如递增)。

像 C++ 这样更清醒的编程语言自动激活可能是不明智的,但多年的经验表明,自动激活在像 Perl 这样稍微不那么清醒的语言中在顺序和便利之间取得了适当的平衡。

在更基本的层面上,代码可能只对那些习惯于 Perl 散列的人有意义。但是,如果您以前没有使用过 Perl 的哈希,那么这将是一个学习它们的好机会。哈希是该语言的核心支柱,上面给出了一个相当典型的使用示例。

在这种情况下,我们有一个哈希值,这也是相当典型的。

于 2012-04-26T12:46:26.310 回答
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
于 2012-04-26T12:20:57.823 回答