Calcular promedios horarios para múltiples columnas de datos

Calcular promedios horarios para múltiples columnas de datos

Buen día, me gustaría calcular promedios horarios para los siguientes datos de muestra:

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

salida deseada:

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

Tenga en cuenta que los datos duran días (más de 100 000 registros) y las columnas de datos varían, a veces hay más de 2 columnas (es decir, datos1, datos2,..., datosX). Entonces me gustaría que el script pudiera hacer cálculos incluso cuando haya más columnas. Tu ayuda será altamente apreciada.

PD: Antes de publicar esto, revisé publicaciones antiguas y realmente no solucionan mi problema.

Respuesta1

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

Esto debería funcionar con cualquier número de campos de datos.

Guárdelo como, por ejemplo, average.pl, hágalo ejecutable chmod +x average.ply ejecútelo 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

Cosas extra interesantes (IMO) sobre perl y mapbucles e iteradores:

Para su información, los foreach my $i ...bucles podrían reescribirse para usar mapla función de Perl en su lugar (ver perldoc -f map, pero en resumen: mapitera sobre una lista, hace cosas con cada elemento y devuelve una nueva lista generada o un recuento de los elementos en esa lista generada) . Esto es más idiomáticamente que Perl, pero probablemente sea más difícil de entender para los nuevos programadores de Perl. p.ej

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

could be written as:

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

Ambos iteran sobre elíndicesde la matriz @data ( 0..$#data). El bucle for crea/modifica los elementos de @sums directamente, mientras que mapdevuelve una nueva matriz de sumas que luego se asigna a la matriz @sums.

En lugar de utilizarla $icomo variable iteradora, la mapfunción crea y utiliza automáticamente una variable escalar (localizada) llamada $_. $_se usa en todas partes en Perl y es el argumento implícito (es decir, predeterminado) para la mayoría de las funciones cuando no se proporciona un argumento. por ejemplo, printsin argumento es en realidad print $_y split /,/es en realidad split /,/, $_. También está implícito para los operadores de coincidencia de patrones, por ejemplo, s/foo/bares en realidad $_ =~ s/foo/bar/.

De manera similar, while (<>)en realidad es algo así como while (defined($_ = <>))(es decir, leer una línea del archivo de entrada o stdin, y si había algo para leer, asignarlo a $_ y evaluarlo como verdadero. De lo contrario, evaluarlo como falso y finalizar el whileciclo).

$_A menudo se le llama informalmente "lo actual" o "cosita". Ver man perlvary buscar \$_para más detalles. También hay un equivalente de matriz @_, que se utiliza para los parámetros pasados ​​a una subrutina.

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

could be written as:

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

Aquí, el foreachbucle itera sobre elíndicesde @sums ( 0..$#sums), mientras mapitera sobre elvaloresde la @sumsmatriz. Nuevamente, el foreachbucle modifica cada elemento de la @avgmatriz directamente, mientras que mapdevuelve una nueva matriz que se asigna a @avg.

Ambas formas producen resultados idénticos en este script, y ambas formas son útiles, pero los programadores de Perl tienden a usarlas mapcon el tiempo porque es una herramienta genéricamente útil para iterar sobre cualquier tipo de lista. Y más corto de escribir que un bucle for/foreach que hace lo mismo. Y porque, después de un tiempo, resulta natural pensar en los datos en términos de listas, matrices y hashes.

A menudo se usa para transformar una matriz en un hash (o los valores o claves de un hash en una matriz).

Por cierto, mapno tiene que devolver una matriz, el bloque de código en { ... }puede hacer cualquier cosa que el código Perl pueda hacer, y el valor de retorno podría simplemente descartarse o (si se asigna a una variable escalar) devolver un recuento de cualquier lista generada.

por ejemplo, el primer bucle foreach también podría escribirse como:

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

Esto modifica la matriz @sums directamente (tal como lo hace el bucle foreach) y cualquier valor de retorno se descarta (es decir, no se asigna a ninguna variable).

Y, por supuesto, el segundo foreachbucle también podría escribirse como:

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

Respuesta2

Lejos 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

información relacionada