여러 데이터 열의 시간별 평균 계산

여러 데이터 열의 시간별 평균 계산

안녕하세요. 다음 샘플 데이터에 대한 시간별 평균을 계산하고 싶습니다.

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

mapPerl, 루프 및 반복자 에 대한 추가 흥미로운 내용(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@sums 배열에 할당된 새로운 합계 배열을 반환합니다.

$i반복자 변수로 사용하는 대신 map함수는 이라는 (지역화된) 스칼라 변수를 자동으로 생성하고 사용합니다 $_. $_Perl의 모든 곳에서 사용되며 인수가 제공되지 않을 때 대부분의 함수에 대한 암시적(즉, 기본) 인수입니다. 예를 들어 print인수가 없으면 is는 실제로 print $_이고 split /,/is는 실제로입니다 split /,/, $_. 이는 패턴 일치 연산자에도 암시적으로 적용됩니다(예: s/foo/baris 실제로 ) $_ =~ 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

관련 정보