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

How to calculate and substitute values in a specific column of tabular 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 

我想从每个 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 中会更容易吗?谢谢。

使用 awk:

$ awk '/Phase/{f=0} FNR==NR && f{s+=;n++;} /station/{f=1} FNR==NR{next;} FNR==1{ave=s/n} f{=-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 

工作原理

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

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

  • /Phase/{f=0}

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

  • FNR==NR && f{s+=;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{=-ave}

    如果 f 为真,从第 9 列 </code> 中减去平均值 <code>ave

  • 1

    这是 awk 的隐式简写形式。

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

一个"extended one liner"可以如下:

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 结合起来并不算数),但是在一次通过并替换为另一次使脚本清晰简单。