如何计算和替换特定列表格数据中的值?

时间:2015-05-14 21:06:53

标签: string perl data-structures awk substitution

给出以下输入:

MCCC processed: unknown event at: Tue, 14 Oct 2014 12:02:26 CST 
station, mccc delay,    std,    cc coeff,  cc std,   pol   , t0_times  , delay_times
 ZJ.uno1     -0.7964    0.0051    0.9690    0.0139    0  GRAW.BHZ   301.1263    -1.8041
 ZJ.dose     -0.7065    0.0072    0.9760    0.0133    0  KNYN.BHZ   301.3372    -1.9249
 ZJ.tres      0.9675    0.0072    0.9548    0.0292    0  LEON.BHZ   301.2611    -0.1749
Phase: P        
PDE    2013  7 15 14  6 58.00   -60.867   -25.143   31.0  0.0  7.3 

我想从每个delay_times中删除第9列(delay_times)的平均值,这需要将第9列值相加,除以这些值的数量,然后从每个值中减去均值( - 1.8041,-1.9249,-0.1749)。

我很困惑从哪里开始这项努力。我在下面提供了一个起始脚本:

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

open my $file '<', "file.txt" or die $!;

while (<$file>) {
    my ($name, $time) = (split /\s+/, $file)[1,9];
 # Calculate the mean of the 9th column for every row that begins with ZJ,
 # and subtract the mean from each value (time) in the 9th column.
}

# Output the new file with the mean removed from each "time" in the 9th column

在awk或perl中这样做会更容易吗?谢谢。

2 个答案:

答案 0 :(得分:1)

使用awk:

$ awk '/Phase/{f=0} FNR==NR && f{s+=$9;n++;} /station/{f=1} FNR==NR{next;} FNR==1{ave=s/n} f{$9=$9-ave} 1' file file
MCCC processed: unknown event at: Tue, 14 Oct 2014 12:02:26 CST 
station, mccc delay, std, cc coeff, cc std, 1.3013 , t0_times , delay_times
ZJ.uno1 -0.7964 0.0051 0.9690 0.0139 0 GRAW.BHZ 301.1263 -0.5028
ZJ.dose -0.7065 0.0072 0.9760 0.0133 0 KNYN.BHZ 301.3372 -0.6236
ZJ.tres 0.9675 0.0072 0.9548 0.0292 0 LEON.BHZ 301.2611 1.1264
Phase: P        
PDE    2013  7 15 14  6 58.00   -60.867   -25.143   31.0  0.0  7.3 

如何运作

由于文件名在命令行中出现两次,因此该程序将读取该文件两次。第一次,它存储s中第9列数字和n中第9列数字的总和。因此平均值为s/n。在第二次通过时,它从第9列的值中减去平均值并打印行。

在我解释问题时,感兴趣的第9列值似乎是以station开头的行之后和以Phase开头的行之后的值。我们保留并更新标记f,以便在我们处于感兴趣的行范围内时发出信号。

  • /Phase/{f=0}

    当我们到达Phase的行时,将标记f设置为false,表示我们已到达行范围的末尾。

  • FNR==NR && f{s+=$9;n++;}

    首次阅读文件并且标记f为真,然后更新总和s并计算n

    在awk中,FNR是到目前为止从当前文件读取的行数,NR是读取的总行数。因此,如果FNR==NR,我们仍在阅读第一个文件。

  • /station/{f=1}

    如果我们与station一致,则将标记f设置为true以表示感兴趣的行的开头。

  • FNR==NR{next;}

    如果我们是第一次阅读文件,请跳过其余命令并跳转到next行。

  • FNR==1{ave=s/n}

    如果我们到了这里,我们现在正在第二次阅读该文件。当我们到达第二次阅读的第一行(FNR==1)时,计算平均值ave

  • f{$9=$9-ave}

    如果f为真,则从第9列ave中减去平均值$9

  • 1

    这是awk用于打印线的神秘短手。

答案 1 :(得分:0)

您尝试过的perl解决方案非常适合您 - 您只是没有完成它: - )

“扩展单线”可以如下:

perl -anE 'push @f,[@F] }{ for (@f){ $s += $_->[8] and $n++ if $_->[0] =~ /ZJ/ } 
           say $_->[0] =~ /ZJ/ ? ( "@{$_}[0..7] ", $_->[8]-($s/$n) ) : "@$_" 
           for @f'  data.txt

使用-an略微缩短(见perlrun),但不是非常高尔夫球。与@ John1024的awk解决方案一样,我们读取了两次文件 - 在这种情况下有两个for循环。我们使用三元运算符(<cond> ? :)打印 - 或say - 每行,按原样(@$_)或字段替换。

<强>输出

MCCC processed: unknown event at: Tue, 14 Oct 2014 12:02:26 CST
station, mccc delay, std, cc coeff, cc std, pol , t0_times , delay_times
ZJ.uno1 -0.7964 0.0051 0.9690 0.0139 0 GRAW.BHZ 301.1263 -0.5028
ZJ.dose -0.7065 0.0072 0.9760 0.0133 0 KNYN.BHZ 301.3372 -0.6236
ZJ.tres 0.9675 0.0072 0.9548 0.0292 0 LEON.BHZ 301.2611 1.1264
Phase: P
PDE 2013 7 15 14 6 58.00 -60.867 -25.143 31.0 0.0 7.3

作为可能如下所示的脚本:

use v5.16;

my (@timedata, $rec, $sum, $n) ;

while (<DATA>) {
    push @timedata, [ split(" ") ] ;
}

foreach my $rec (@timedata){ 
  $sum += $rec->[8] and $n++ if $rec->[0] =~ /ZJ/ ;
}    

foreach $rec (@timedata) {
 say $rec->[0] =~ /ZJ/  ?  ( "@{$rec}[0..7] ", $rec->[8]-($sum/$n) ) 
                        :    "@$rec" ;
}

__DATA__
MCCC processed: unknown event at: Tue, 14 Oct 2014 12:02:26 CST 
station, mccc delay,  std,   cc coeff,  cc std,   pol   , t0_times  , delay_times
 ZJ.uno1  -0.7964    0.0051    0.9690    0.0139    0  GRAW.BHZ   301.1263 -1.8041
 ZJ.dose  -0.7065    0.0072    0.9760    0.0133    0  KNYN.BHZ   301.3372 -1.9249
 ZJ.tres   0.9675    0.0072    0.9548    0.0292    0  LEON.BHZ   301.2611 -0.1749
Phase: P        
PDE    2013  7 15 14  6 58.00   -60.867   -25.143   31.0  0.0  7.3 

可能有一种方法可以避免这两个循环(whilemapfor合并但实际上并不算数),但是在一次传递中创建总和和平均值而在另一个代替,使脚本清晰简单。