计算列的小部分的平均值,按perl中的键分组?

时间:2012-04-26 12:08:21

标签: perl statistics average

这个问题与How can I get the average and standard deviations grouped by key?非常相似,但我无法修改它以适应我的问题。

我有很多带有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和I不知道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);

3 个答案:

答案 0 :(得分: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

答案 1 :(得分: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

答案 2 :(得分: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的 autovivification 功能。例如,观察行++$summary{$col5}{count};,它似乎最初会增加一个不存在的计数器。但是,这实际上是标准的Perl习语。如果您尝试对不存在的对象执行算术(如递增),Perl会隐式创建对象,将其初始化为零,然后执行您想要的操作(如递增)。

对于像C ++这样更加清醒的编程语言来说,自动生成可能是不明智的,但多年的经验表明,自动更新在订单和便利之间取得了正确的平衡,而在像Perl这样稍微不那么清醒的语言中。

在更基础的层面上,代码可能只对那些用于Perl哈希的人有意义。但是,如果您以前没有使用过Perl的哈希值,那么这对于学习它们来说是一个很好的机会。哈希是语言的核心支柱,上面是一个相当典型的例子。

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