如何计算和替换表格数据特定列中的值?
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
可能有一种方法可以避免这两个循环(尽管将 while
或 map
与 for
结合起来并不算数),但是在一次通过并替换为另一次使脚本清晰简单。
给定以下输入:
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
可能有一种方法可以避免这两个循环(尽管将 while
或 map
与 for
结合起来并不算数),但是在一次通过并替换为另一次使脚本清晰简单。