美好的一天,我想计算以下示例数据的每小时平均值:
Timestamp,data1,data2
2018 07 16 13:00:00,23,451
2018 07 16 13:10:00,26,452
2018 07 16 13:20:00,24,453
2018 07 16 13:30:00,23,454
2018 07 16 13:50:00,28,455
2018 07 16 14:20:00,20,456
2018 07 16 14:40:00,12,457
2018 07 16 14:50:00,22,458
2018 07 16 15:10:00,234,459
2018 07 16 17:50:00,23,845
2018 07 16 18:10:00,239,453
2018 07 17 10:10:00,29,452
2018 07 18 13:20:00,49,451
2018 07 19 13:30:00,28,456
期望的输出:
Date,Hour,Ave_data1,Ave_data2
2018 07 16,13,24.8,453
2018 07 16,14,18,457
2018 07 16,15,234,459
2018 07 16,17,23,845
2018 07 16,18,239,453
2018 07 17,10,29,452
2018 07 18,13,49,451
2018 07 19,13,28,456
请注意,数据会持续数天(100000+ 条记录),并且数据列会有所不同,有时会有超过 2 列(即 data1、data2、...、dataX)。所以我希望即使有更多列,脚本也能够进行计算。我们将非常感谢您的帮助。
PS:在发布此内容之前,我检查了旧帖子,它们并没有真正解决我的问题。
答案1
#!/usr/bin/perl
use strict;
my $prev = '';
my (@sums,@avg) = ();
my $count = 0;
while(<>) {
chomp;
if (m/^Timestamp/) {
my @headers = split /,/;
# insert "Ave_" at start of each header
@headers = map { "Ave_" . $_ } @headers;
# replace Timestamp header with Date,Hour headers.
splice @headers,0,1,qw(Date Hour);
print join(",",@headers), "\n";
next;
};
my (@data) = split /,/;
# extract and remove date and hour from first element of @data
(my $current = shift @data) =~ s/^(.*) (\d\d):.*$/$1,$2/;
if ($count == 0 || $current eq $prev) {
# add each field in @data to the same field in @sums
foreach my $i (0..$#data) { $sums[$i] += $data[$i] };
$prev = $current;
$count++;
next unless eof;
};
# calculate and print the averages for the previous hour
foreach my $i (0..$#sums) { $avg[$i] = $sums[$i] / $count };
print join(",", $prev, @avg), "\n";
# special case handling for when there's a new date/hour on the
# last line of file (otherwise it wouldn't get printed)
if (eof && $prev ne $current) {
print join(",", $current, @data), "\n";
};
@sums = @data;
@avg = ();
$prev = $current;
$count = 1;
};
这应该适用于任意数量的数据字段。
另存为,例如,average.pl
使其可执行chmod +x average.pl
并运行如下:
$ ./average.pl input.csv
Date,Hour,Ave_data1,Ave_data2
2018 07 16,13,24.8,453
2018 07 16,14,18,457
2018 07 16,15,234,459
2018 07 16,17,23,845
2018 07 16,18,239,453
2018 07 17,10,29,452
2018 07 18,13,49,451
2018 07 19,13,28,456
map
关于 perl 以及循环和迭代器的额外有趣的(IMO)内容:
仅供参考,foreach my $i ...
可以重写循环以使用 perl 的map
函数(请参阅perldoc -f map
,但简而言之:map
迭代列表,对每个元素执行操作,并返回新生成的列表或该生成列表中元素的计数) 。这是更惯用的 Perl 语言,但对于新的 Perl 程序员来说可能更难理解。例如
foreach my $i (0..$#data) { $sums[$i] += $data[$i] };
could be written as:
@sums = map { $sums[$_] + $data[$_] } 0..$#data;
这两个都迭代指数@data 数组 ( 0..$#data
) 的。 for 循环直接创建/修改 @sums 的元素,而map
返回一个新的 sum 数组,然后将其分配给 @sums 数组。
该函数不使用$i
迭代器变量,而是map
自动创建并使用一个名为 的(本地化)标量变量$_
。 $_
在 perl 中随处使用,并且在未提供参数时是大多数函数的隐式(即默认)参数。例如,print
没有参数实际上是print $_
,并且split /,/
实际上是split /,/, $_
。它对于模式匹配运算符也是隐式的,例如s/foo/bar
is 实际上$_ =~ s/foo/bar/
。
类似地,while (<>)
实际上是类似的while (defined($_ = <>))
(即从输入文件或标准输入中读取一行,如果有任何内容要读取,则将其分配给 $_ 并评估为 true。否则评估为 false 并结束循环while
)。
$_
通常被非正式地称为“当前事物”或“事物”。查看man perlvar
并搜索\$_
更多详细信息。还有一个等效的数组@_
,用于传递给子例程的参数。
foreach my $i (0..$#sums) { $avg[$i] = $sums[$i] / $count };
could be written as:
@avg = map { $_ / $count } @sums;
在这里,foreach
循环迭代指数@sums ( 0..$#sums
),而map
迭代价值观数组的@sums
。同样,foreach
循环直接修改数组的每个元素@avg
,同时map
返回一个分配给 的新数组@avg
。
两种形式在此脚本中产生相同的输出,并且两种形式都很有用,但是 Perl 程序员倾向于map
随着时间的推移使用它,因为它是迭代任何类型列表的通用工具。与执行相同操作的 for/foreach 循环相比,键入时间更短。因为,过了一段时间,用列表、数组和哈希来思考数据就会变得很自然。
它通常用于将数组转换为哈希(或将哈希的值或键转换为数组)。
顺便说一句,map
不必返回数组,其中的代码块{ ... }
可以执行 perl 代码可以执行的任何操作,并且返回值可以被丢弃或(如果分配给标量变量)返回任何生成列表的计数。
例如,第一个 foreach 循环也可以写为:
map { $sums[$_] += $data[$_] } 0..$#data;
这会直接修改@sums数组(就像foreach循环一样),并且任何返回值都会被丢弃(即不分配给任何变量)。
当然,第二个foreach
循环也可以写成:
map { $avg[$_] = $sums[$_] / $count } 0..$#sums;
答案2
离开GNU awk
:
#!/usr/bin/awk -f
BEGIN {
FS=OFS=","
}
NR == 1 {
# Build the header here
for (i = 2; i <= NF; i++) oh = oh OFS "Ave_" $i
print "Date", "Hour" oh
next
}
{
# Split date and time and build a timestamp with it.
# Set MM and SS to 0 to aggregate data from the same hour
split($1, a, " ")
sub(/:.*/, "", a[4])
ct = mktime(a[1] " " a[2] " " a[3] " " a[4] " 00 00")
# If the 'current time' differ from the 'old time' then
# do the average and print the line
if (ct != ot && ot) {
for (i in avg){
avg_h = avg_h OFS (avg[i] / cnt[i])
delete avg[i]
delete cnt[i]
}
sub(/^,/, "", avg_h)
print cd, ch, avg_h
avg_h = ""
saved = 0
}
j = 0
for (i = 2; i <= NF; i++) {
avg[j] += $i
cnt[j++] += 1
}
# Do the assignment if and only something has changed
if (!saved) {
saved = 1
ot = ct
cd = a[1] " " a[2] " " a[3]
ch = a[4]
}
}
END {
# There are something else? Print it
for (i in avg)
avg_h = avg_h OFS (avg[i] / cnt[i])
sub(/^,/, "", avg_h)
print cd, ch, avg_h
}
运行为:./script.awk data