こんにちは。次のサンプルデータの時間別平均を計算したいと思います。
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
データは数日間持続し(100,000を超えるレコード)、データ列はさまざまであり、時には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;
これらの両方が繰り返されます。索引@データ配列(0..$#data
)。 forループは@sumsの要素を直接作成/修正し、map
新しいsum配列を返し、それを@sums配列に割り当てます。
$i
この関数はイテレータ変数を使用しませんが、map
(ローカル化された)スカラー変数を自動的に生成して使用します$_
。 $_
Perlのどこでも使用され、引数が指定されていないときのほとんどの関数に対する暗黙的な(つまり、基本的な)引数。たとえば、print
実際にはパラメータがなく、実際にはprint $_
です。これはis reallyなどのパターンマッチング演算子にも暗黙的に適用されます。split /,/
split /,/, $_
s/foo/bar
$_ =~ s/foo/bar/
同様にwhile (<>)
、実際にも似ていますwhile (defined($_ = <>))
(つまり、入力ファイルまたは標準入力から1行を読み取り、読み取る内容がある場合は$ _に割り当て、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;
これはforeachループと同様に@sums配列を直接変更し、すべての戻り値は削除されます(つまり、変数に割り当てられていません)。
もちろん、2番目のforeach
ループは次のように書くこともできます。
map { $avg[$_] = $sums[$_] / $count } 0..$#sums;