Pular para conteúdo

Lista 16 · Séries temporais

Acompanha o capítulo 16. Média móvel, perfil de um ciclo, suavização exponencial e o erro de uma previsão — medido sempre em dados que o método não viu.

Abrir no Colab

Baixar o caderno

Como esta lista funciona

Abra o caderno no Colab, escreva a sua solução no lugar do # TODO e rode a célula de teste logo abaixo. Ela mesma diz o que passou e o que falhou — você não precisa esperar correção de ninguém.

O código de cada exercício também está aqui embaixo, caso você prefira colar num editor local. Os testes comparam números com tolerância.

Exercícios marcados com ✏️ são à mão, no papel, com 4 casas decimais — como na parte em papel das avaliações.

Exercício 01

✏️ À mão. Uma série tem os valores 10, 12, 11, 15, 14, 16.

a) Calcule a média móvel com janela de 3 (cada valor é a média de 3 seguidos, a partir do primeiro).

b) Aplique a suavização exponencial com \(\alpha = 0{,}5\), começando com a previsão igual ao primeiro valor: \(\hat y_{t+1} = 0{,}5\,y_t + 0{,}5\,\hat y_t\). Qual a previsão para o sétimo valor?

c) Se o sétimo valor for 17, qual o erro absoluto de cada previsão: a da suavização e a "ingênua" (repetir o último)?

Exercício 02

Escreva media_movel(serie, janela), que devolve um array com a média de cada grupo de janela valores seguidos: o primeiro elemento é a média de serie[0:janela], o segundo, de serie[1:janela + 1], e assim por diante.

media_movel([10.0, 12.0, 11.0, 15.0, 14.0, 16.0], 3)    # -> [12.  12.67 13.33 15.  ]
import numpy as np


def media_movel(serie, janela):
    """Array com a média de cada janela de valores seguidos (sem as pontas incompletas)."""
    n = len(serie) - janela + 1
    medias = np.zeros(n)
    # TODO: para cada i, a média da fatia serie[i:i + janela]
    return medias


# --- testes (não altere) ---
m = media_movel([10.0, 12.0, 11.0, 15.0, 14.0, 16.0], 3)
esperado = [11.0, 38 / 3, 40 / 3, 15.0]
assert len(m) == 4, "6 valores com janela 3: 4 médias"
for i in range(4):
    assert abs(m[i] - esperado[i]) < 1e-12
assert len(media_movel([1.0, 2.0, 3.0], 3)) == 1
print("Exercício 02: todos os testes passaram!")

Exercício 03

Escreva mae(previsto, real), o erro absoluto médio: a média de \(|\hat y_i - y_i|\). Converta as duas entradas com np.array (podem chegar como listas).

import numpy as np


def mae(previsto, real):
    """Erro absoluto médio."""
    # TODO: np.array nas duas, abs da diferença e np.mean
    pass


# --- testes (não altere) ---
assert abs(mae([10, 12, 11], [11, 12, 14]) - 4 / 3) < 1e-12
assert mae([5, 5], [5, 5]) == 0
print("Exercício 03: todos os testes passaram!")

Exercício 04

Escreva rmse(previsto, real), a raiz do erro quadrático médio: \(\sqrt{\frac{1}{n}\sum (\hat y_i - y_i)^2}\).

Para os mesmos erros, por que o RMSE é sempre maior ou igual ao MAE? Quando são iguais?

import numpy as np


def rmse(previsto, real):
    """Raiz do erro quadrático médio."""
    # TODO: a diferença, o quadrado, a média e a raiz
    pass


# --- testes (não altere) ---
assert abs(rmse([10, 12, 11], [11, 12, 14]) - (10 / 3) ** 0.5) < 1e-12
assert abs(rmse([0, 0], [3, 3]) - 3) < 1e-12, "todos os erros iguais: RMSE = MAE"
print("Exercício 04: todos os testes passaram!")

Exercício 05

O ciclo típico. Escreva perfil_ciclo(serie, periodo), que devolve um array com periodo valores: o valor da posição \(k\) é a média de serie[k], serie[k + periodo], serie[k + 2*periodo]... — a fatia serie[k::periodo].

perfil_ciclo(np.array([1, 5, 2, 3, 7, 4]), 3)    # -> [2. 6. 3.]

Com periodo = 24 numa série horária, é o "dia típico".

import numpy as np


def perfil_ciclo(serie, periodo):
    """Média de cada posição do ciclo: perfil[k] = média de serie[k], serie[k + periodo], ..."""
    perfil = np.zeros(periodo)
    # TODO: para cada posição k, a média da fatia serie[k::periodo]
    return perfil


# --- testes (não altere) ---
import numpy as np


p = perfil_ciclo(np.array([1.0, 5.0, 2.0, 3.0, 7.0, 4.0]), 3)
for i in range(3):
    assert abs(p[i] - [2, 6, 3][i]) < 1e-12, f"veio {p}"
print("Exercício 05: todos os testes passaram!")

Exercício 06

Escreva suavizacao(serie, alpha), que começa com a previsão igual ao primeiro valor e, para cada valor da série (inclusive o primeiro), atualiza \(\hat y \leftarrow \alpha\,y + (1 - \alpha)\,\hat y\). Devolva a previsão final — a do próximo valor.

suavizacao([10.0, 12.0, 11.0, 15.0, 14.0, 16.0], 0.5)    # -> 14.75  (o ex01)
def suavizacao(serie, alpha):
    """Previsão para o próximo valor pela suavização exponencial simples."""
    previsao = serie[0]
    # TODO: um laço pela série, atualizando a previsão
    return previsao


# --- testes (não altere) ---
assert abs(suavizacao([10.0, 12.0, 11.0, 15.0, 14.0, 16.0], 0.5) - 14.75) < 1e-12
assert abs(suavizacao([10.0, 12.0, 11.0, 15.0, 14.0, 16.0], 1.0) - 16.0) < 1e-12, "alpha = 1: o último valor"
assert abs(suavizacao([10.0, 12.0, 11.0, 15.0, 14.0, 16.0], 0.0) - 10.0) < 1e-12, "alpha = 0: nunca sai do primeiro"
print("Exercício 06: todos os testes passaram!")

Exercício 07

Telecom — prever o tráfego. O tráfego de uma rede se repete a cada dia. Escreva previsao_sazonal(serie, periodo, passos), que prevê os próximos passos valores copiando, para cada um, o valor de um período atrás. Quando passos for maior que periodo, a previsão passa a copiar os próprios valores previstos: por isso, vá acrescentando cada previsão ao histórico.

previsao_sazonal([5, 9, 7, 6, 10, 8], 3, 4)    # -> [ 6 10  8  6]
import numpy as np


def previsao_sazonal(serie, periodo, passos):
    """Os próximos `passos` valores, copiando o valor de um período atrás."""
    historico = list(serie)
    previsao = []
    # TODO: para cada passo, o valor de um período atrás no histórico; guarde na
    # previsão E no histórico
    return np.array(previsao)


# --- testes (não altere) ---
p = previsao_sazonal([5, 9, 7, 6, 10, 8], 3, 4)
esperado = [6, 10, 8, 6]
assert len(p) == 4
for i in range(4):
    assert p[i] == esperado[i], f"veio {p}"
print("Exercício 07: todos os testes passaram!")