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.pl
e 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 map
loops e iteradores:
Para sua informação, os foreach my $i ...
loops podem ser reescritos para usar map
a função do Perl (veja perldoc -f map
, mas resumindo: map
itera 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 map
retorna um novo array de somas que é então atribuído ao array @sums.
Em vez de usar $i
como variável iteradora, a map
funçã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, print
sem 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/bar
is 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 while
loop).
$_
é frequentemente chamado informalmente de "a coisa atual" ou "coisa". Veja man perlvar
e 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 foreach
loop itera sobre oíndicesde @sums ( 0..$#sums
), enquanto map
itera sobre ovaloresda @sums
matriz. Novamente, o foreach
loop modifica cada elemento do @avg
array diretamente, enquanto map
retorna 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 map
com 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, map
nã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 foreach
loop 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