Cálculo de médias horárias para múltiplas colunas de dados

Cálculo de médias horárias para múltiplas colunas de dados

Bom dia, gostaria de calcular médias horárias para os seguintes dados de amostra:

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

saída desejada:

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

Observe que os dados duram dias (mais de 100.000 registros) e as colunas de dados variam, às vezes há mais de 2 colunas (ou seja, dados1, dados2,..., dadosX). Então, eu gostaria que o script pudesse fazer cálculos mesmo quando houvesse mais colunas. Sua ajuda será muito apreciada.

PS: Antes de postar isso, verifiquei postagens antigas e elas realmente não resolvem o meu problema.

Responder1

#!/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;
};

Isso deve funcionar com qualquer número de campos de dados.

Salve como, por exemplo, average.pl, torne-o executável chmod +x average.ple execute como:

$ ./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

Coisas extra interessantes (IMO) sobre perl e maploops e iteradores:

Para sua informação, os foreach my $i ...loops podem ser reescritos para usar mapa função do Perl (veja perldoc -f map, mas resumindo: mapitera sobre uma lista, fazendo coisas com cada elemento e retornando uma nova lista gerada ou uma contagem dos elementos nessa lista gerada) . Isso é mais idiomaticamente Perl, mas provavelmente é mais difícil de entender para novos programadores Perl. por exemplo

     foreach my $i (0..$#data) { $sums[$i] += $data[$i] };

could be written as:

     @sums = map { $sums[$_] + $data[$_] } 0..$#data;

Ambos iteram sobre oíndicesda matriz @data ( 0..$#data). O loop for cria/modifica os elementos de @sums diretamente, enquanto mapretorna um novo array de somas que é então atribuído ao array @sums.

Em vez de usar $icomo variável iteradora, a mapfunção cria e usa automaticamente uma variável escalar (localizada) chamada $_. $_é usado em todos os lugares em perl e é o argumento implícito (ou seja, padrão) para a maioria das funções quando um argumento não é fornecido. por exemplo, printsem argumento é na verdade print $_e split /,/é na verdade split /,/, $_. Também está implícito para operadores de correspondência de padrões, por exemplo, s/foo/baris Actually $_ =~ s/foo/bar/.

Da mesma forma, while (<>)é na verdade algo como while (defined($_ = <>))(ou seja, leia uma linha do arquivo de entrada ou stdin e, se houver algo para ler, atribua-o a $_ e avalie como verdadeiro. Caso contrário, avalie como falso e encerre o whileloop).

$_é frequentemente chamado informalmente de "a coisa atual" ou "coisa". Veja man perlvare pesquise \$_para mais detalhes. Há também um array equivalente @_, que é usado para os parâmetros passados ​​para uma sub-rotina.

  foreach my $i (0..$#sums) { $avg[$i] = $sums[$i] / $count };

could be written as:

  @avg = map { $_ / $count } @sums;

Aqui, o foreachloop itera sobre oíndicesde @sums ( 0..$#sums), enquanto mapitera sobre ovaloresda @sumsmatriz. Novamente, o foreachloop modifica cada elemento do @avgarray diretamente, enquanto mapretorna um novo array que é atribuído a @avg.

Ambos os formulários produzem saída idêntica neste script, e ambos os formulários são úteis, mas os programadores perl tendem a usá-los mapcom o tempo porque é uma ferramenta genericamente útil para iterar qualquer tipo de lista. E mais curto para digitar do que um loop for/foreach que faz a mesma coisa. E porque, depois de um tempo, torna-se natural pensar nos seus dados em termos de listas, arrays e hashes.

É frequentemente usado para transformar um array em um hash (ou os valores ou chaves de um hash em um array).

Aliás, mapnão é necessário retornar uma matriz, o bloco de código { ... }pode fazer qualquer coisa que o código Perl possa fazer, e o valor de retorno pode ser simplesmente descartado ou (se atribuído a uma variável escalar) retornar uma contagem de qualquer lista gerada.

por exemplo, o primeiro loop foreach também poderia ser escrito como:

map { $sums[$_] += $data[$_] } 0..$#data;

Isso modifica o array @sums diretamente (assim como faz o loop foreach) e qualquer valor de retorno é descartado (ou seja, não é atribuído a nenhuma variável).

E, claro, o segundo foreachloop também poderia ser escrito como:

map { $avg[$_] = $sums[$_] / $count } 0..$#sums;

Responder2

Ausente 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
}

Correr como :./script.awk data

informação relacionada