안녕하세요. 다음 샘플 데이터의 시간별 평균을 계산하고 싶습니다.
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)이 있을 수도 있습니다. 그래서 더 많은 열이 있어도 스크립트가 계산할 수 있도록 하고 싶습니다. 귀하의 도움에 크게 감사하겠습니다.
추신: 이 글을 게시하기 전에 이전 게시물을 확인했는데 내 문제가 실제로 해결되지 않았습니다.
답변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($_ = <>))
(즉, 입력 파일이나 표준 입력에서 한 줄을 읽고, 읽을 내용이 있으면 $_에 할당하고 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 배열을 직접 수정하며 모든 반환 값은 삭제됩니다(즉, 변수에 할당되지 않음).
물론 두 번째 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