Вычисление среднечасовых значений для нескольких столбцов данных

Никакой альтернативы не требуется, я просто:

  • создал пустой файл,
  • отформатировал как ext4,
  • установил его как петлевое устройство,
  • переместил Dropbox домой.

В качестве примера:

  1. Добавьте вам /etc/fstabподобную строку для монтирования при загрузке файловой системы dropbox.

    /home/user/.dropbox-store   /home/user/Dropbox   ext4    defaults,loop    0 0
    
  2. Выполнить

    dropbox stop
    dd if=/dev/zero bs=1k count=1 seek=2097152 of=~/.dropbox-store
    /sbin/mkfs.ext4 -L DropboxHome ~/.dropbox-store 
    mv ~/Dropbox ~/Dropbox_
    mkdir ~/Dropbox
    # emulate boot mount to check fstab syntax 
    sudo mount -a
    sudo chown user.users ~/Dropbox
    rsync -av ~/Dropbox_/. ~/Dropbox/.
    dropbox start
    

Это грубое решение. Это просто дорожная карта, которую вы должны адаптировать к своим потребностям. Например. решение fstabприемлемо только на однопользовательском ноутбуке, как у меня.

-1
31.05.2021, 15:07
2 ответа
#!/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

Очень интересно (IMO )материал о perl и map, циклах и итераторах:

К вашему сведению, циклы 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без аргумента на самом деле print $_, а split /,/на самом деле split /,/, $_. Это также неявно для операторов сопоставления с образцом, например. s/foo/barна самом деле $_ =~ s/foo/bar/.

Аналогично, while (<>)на самом деле что-то вродеwhile (defined($_ = <>))(т.е. прочитать строку из входного файла или stdin, и если там было что читать, присвоить $_и оценить как истину. В противном случае оцените как 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;

Это напрямую изменяет массив @sums (так же, как это делает цикл foreach ), и любое возвращаемое значение отбрасывается (, т. е. не присваивается какой-либо переменной ).

И, конечно же, второй цикл foreachможно было бы записать и как:

map { $avg[$_] = $sums[$_] / $count } 0..$#sums;
2
28.07.2021, 11:27

А 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

1
28.07.2021, 11:27

Теги

Похожие вопросы