Ano bissexto - extrapolando valor

4

Eu tenho algumas tabelas (table.txt) da seguinte forma:

YEAR MONTH DAY RES
1971 1     1   1345
1971 1     2   1265
1971 1     3   1167

A duração de cada série temporal vai de 1.1.1971 até 31.12.2099 . Infelizmente, algumas séries temporais estão perdendo anos bissextos e seus valores (por exemplo, ano de 1972 é um ano bissexto, portanto o mês de fevereiro deve ter 29 dias, mas minha série temporal tem apenas 28 dias em fevereiro de 1972). Para exemplos nas minhas tabelas atuais, o final do mês de fevereiro de 1972 é apresentado da seguinte forma:

YEAR MONTH DAY RES
1972 2     27  100
1972 2     28  101
1972 3     1   102

Isso está errado, porque não é contabilizar nenhum ano bissexto. Em vez disso, gostaria de incluir em minhas séries temporais todos os dias que faltam (obviamente, 29 de fevereiro) de todos os anos bissextos da minha série temporal, extrapolando o valor com o dia anterior e seguinte, como segue:

YEAR MONTH DAY RES
1972 2     27  100
1972 2     28  101
1972 2     29  101.5
1972 3     1   102

Existe uma maneira de fazer isso usando o shell / bash?

    
por steve 20.10.2015 / 18:17

3 respostas

4

Talvez algo como:

awk '
  function isleap(y) {
    return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)
  }
  $2 == 3 && $3 == 1 && isleap($1) && last_day != 29 {
    print $1, 2, 29, (last_data + $4) / 2
  }
  {print; last_day = $3; last_data = $4}' file
    
por 20.10.2015 / 18:36
2

Eu só estava pensando sobre isso e, por causa da maneira como os anos bissextos alternam todos os anos pares, o seguinte é verdadeiro:

([13579][26]|[02468][048]) == leap year

Basicamente, os anos bissextos ocorrem nos anos 2 e 6 nas décadas ímpares, mas nos anos 4 e 8 nas décadas pares e na virada de todas as outras décadas.

E assim você pode fazer:

sed -e'  /[02468] * 2 * 28 /!b'\
    -e'h;/[13579][26] * 2 / G' \
    -e'  /[02468][048] * 2 /G' \
    -e'  /\n/s/ 28 / 29 /2'    \
    -eP\;D <in >out

... que encontraria, em dobro, depois modificaria todas as linhas de 28 de fevereiro na entrada apenas para anos bissextos, independentemente do ponto inicial de qualquer loop de alternância.

Esse foi meu primeiro instinto:

sed -e'/\([02648] * 2 * 2\)8 /!b' \
    -e:n -e'n;//!bn' -e'p;s// /' <in

... o que foi apenas uma ligeira adaptação à minha resposta à sua outra pergunta , mas que só vai trabalho para cada série em que o primeiro ano sequer encontrado não é um ano bissexto porque funciona alternadamente.

Eu testei os dois sed s em relação ao meu arquivo de teste da sua outra pergunta . O infil já teve anos bissextos, é claro, e o código que usei para gerá-lo também está na resposta, mas ambos funcionaram para uma série que começou em 1970, embora o primeiro não fosse quebrado de qualquer maneira:

1970  2   27  58
1970  2   28  59
1970  3   1   60
1972  2   27  58
1972  2   28  59
1972  2   29  59
1972  2   29  60
1972  3   1   61
1974  2   27  58
1974  2   28  59
1974  3   1   60
1976  2   27  58
1976  2   28  59
1976  2   29  59
1976  2   29  60
1976  3   1   61
1978  2   27  58
1978  2   28  59
1978  3   1   60
1980  2   27  58
1980  2   28  59
1980  2   29  59
1980  2   29  60
1980  3   1   61
    
por 21.10.2015 / 15:06
1

Solução de Perl:

#!/usr/bin/perl
use warnings;
use strict;

use Time::Piece;

print scalar <>; # Skip the header.

while (<>) {
    my ($year, $month, $day, $res) = split;
    my $t = 'Time::Piece'->strptime("$year $month $day", '%Y %m %d');
    if ($t->is_leap_year && 2 == $month && 28 == $day) {
        print;
        $_ = <>;
        my ($year2, $month2, $day2, $res2) = split;
        die "Expected March the 1st: $_"
            unless $year == $year2 && 3 == $month2 && 1 == $day2;
        print join("\t", $year, 2, 29, ($res + $res2) / 2), "\n";
    }
    print;
}

Salvar como fix_feb29.pl . Então corra

for file in *.txt ; do
    fix_feb29.pl -i~ "$file"
done
    
por 21.10.2015 / 15:21