1 Bingo Arima

Código
#' Nosso objetivo é descobrir qual a ordem do ARIMA usando análise descritiva
#p=2, d=0, q=0, 
#modelo ARIMA(2,0,0)
# gerando os dados --------------------------------------------------------

ar <- sample(0:2, 1)
ma <- sample(0:2, 1)
dif <- sample(0:1, 1)

ar_parm <- runif(ar, min = .3, max = .4)
ma_parm <- runif(ma, min = .3, max = .4)

dados <- data.frame(
  unique_id = "Bingo Arima",
  ds = 1:(300 + dif),
  y = arima.sim(
    list(
      order = c(ar, dif, ma),
      ma = ma_parm,
      ar = ar_parm
    ),
    n = 300
  )
)

ARIMA(p,d,q) ARIMA(1,0,1)

p = AR = 1 (auto regressivo) d = D = 0 (diferenciação) q = MA = 1 (moving average)

\[ y_t = \alpha + \phi_1 y_{t-1} + \epsilon_t + \theta_1 \epsilon_{t-1} \]

No nosso caso, ARIMA(2,0,0)

\[ y_t = \alpha + \phi_1 y_{t-1} + \phi_2 y_{t-2} + \epsilon_t \]

1.1 Descritiva em R

Código
#library(fpp3)

dados_tsibble <- dados |>
  dplyr::mutate(
    ds = as.Date("1995-05-01") + months(ds),
    ds = tsibble::yearmonth(ds)
  ) |>
  tsibble::as_tsibble(index = ds)

# PASSO 1
dados_tsibble |>
  feasts::gg_tsdisplay(y, plot_type = "partial")

\[ y_t = \alpha + \beta_1 y_{t-1} + \beta_2 y_{t-2} + \beta_3 y_{t-3} \]

1.2 Descritiva em Python

Código
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib

matplotlib.use("module://positron.matplotlib_backend")
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from utilsforecast.plotting import plot_series

dados = r.dados


def plotar_serie(y):
    fig = plt.figure(figsize=(12, 8))
    gs = fig.add_gridspec(2, 2)
    ax1 = fig.add_subplot(gs[0, :])
    ax2 = fig.add_subplot(gs[1, 0])
    ax3 = fig.add_subplot(gs[1, 1])
    ax1.plot(y, marker="o")
    ax1.set_ylabel("y")
    ax1.set_xlabel("Month")
    plot_acf(y, ax2, zero=False, lags=24, bartlett_confint=False, auto_ylims=True)
    plot_pacf(y, ax3, zero=False, lags=24, auto_ylims=True)
    plt.savefig("acf_pacf_plot.png")
    plt.show()
    return


plotar_serie(dados.y)

1.3 Testes em R

Código
tseries::adf.test(dados_tsibble$y)

dados_tsibble |>
  fabletools::features(
    dados_tsibble$y,
    list(
      feasts::unitroot_kpss,
      feasts::unitroot_ndiffs,
      feasts::unitroot_nsdiffs
    )
  )

1.4 Testes em Python

Código
from statsmodels.tsa.stattools import kpss
from statsforecast.arima import ndiffs, nsdiffs

kpss_stat, kpss_pvalue, _, _ = kpss(dados.y, nlags=5)
print(f"kpss_stat: {kpss_stat:.3f}, kpss_pvalue: {kpss_pvalue:.2f}")

ndiffs(dados.y)

1.5 Diferenças em R

Código
dados_tsibble |>
  dplyr::mutate(dif = tsibble::difference(y)) |>
  feasts::gg_tsdisplay(dif, plot_type = "partial", lag_max = 30)

1.6 Diferenças em Python

Código
dif = dados.y.diff()[1:]
plotar_serie(dif)

1.7 Modelagem em R

Código
fit <- dados_tsibble |>
  fabletools::model(
    arima_manual1 = fable::ARIMA(y ~ 1 + pdq(1, 1, 1) + PDQ(0, 0, 0)),
    arima_manual2 = fable::ARIMA(y ~ 1 + pdq(2, 1, 0) + PDQ(0, 0, 0)),
    stepwise = fable::ARIMA(y ~ 1 + PDQ(0, 0, 0)),
    search = fable::ARIMA(y ~ 1 + PDQ(0, 0, 0), stepwise = FALSE)
  )

dplyr::glimpse(fit)

fit |>
  broom::glance() |>
  dplyr::select(.model, AICc) |>
  dplyr::arrange(AICc)

fit |>
  broom::augment() |>
  dplyr::filter(.model == "stepwise") |>
  feasts::gg_tsdisplay(.resid, plot_type = "partial")

1.8 Modelagem em Python

Código
from statsforecast import StatsForecast
from statsforecast.models import AutoARIMA, ARIMA
from statsforecast.arima import ARIMASummary

dados = r.dados

models = [
    ARIMA(order=(2, 0, 0), alias="arima200"),
    AutoARIMA(alias="stepwise"),
    AutoARIMA(stepwise=False, alias="search"),
]

sf = StatsForecast(models=models, freq=1, n_jobs=-1)

sf.fit(df=dados)

print(ARIMASummary(sf.fitted_[0, 0].model_))
print(ARIMASummary(sf.fitted_[0, 1].model_))
print(ARIMASummary(sf.fitted_[0, 2].model_))
Código
forecasts = sf.forecast(
    df=dados[["ds", "y", "unique_id"]], h=0, fitted=True, level=[80, 95]
)
fitted_values = sf.forecast_fitted_values()
insample_forecasts = fitted_values["stepwise"]
residuals = fitted_values["y"] - insample_forecasts

plotar_serie(residuals)

1.9 GABARITO

Código
print(c(ar, dif, ma))
Código
r.ar, r.dif, r.ma
Código
print(ar_parm)
print(ma_parm)

2 Continuando…

Código
library(fpp3)

link <- "https://www.tesourotransparente.gov.br/ckan/dataset/f85b6632-1c9c-4beb-9e60-72e91156c984/resource/f52c016b-1773-459b-a28f-6ddc4966a702/download/Transferencias---Dados-Consolidados.xlsx"


g <- httr::GET(link, httr::write_disk(tmp <- fs::file_temp(ext = ".xlsx")))

dados_raw <- tmp |>
  readxl::read_excel(
    1,
    "C38:OQ38",
    col_names = FALSE,
    .name_repair = "minimal"
  ) |>
  janitor::clean_names() |>
  tidyr::pivot_longer(dplyr::everything()) |>
  dplyr::mutate(
    date = seq(as.Date("1991-01-01"), as.Date("2024-09-01"), "1 month"),
    value = value / 1e9
  ) |>
  dplyr::select(-name) |>
  tidyr::fill(value) |>
  dplyr::filter(lubridate::year(date) >= 1998)

3 Entendimento da série

  1. Plote os gráficos da série e das ACF/PACF e discorra sobre o aspecto da série. Faça o teste de raíz unitária e explique as conclusões. Existe tendência estocástica?
Código
dados_raw |>
  ggplot(aes(date, value)) +
  geom_line()
Código
dados_raw |>
  ggplot(aes(date, log(value))) +
  geom_line()

Parece que faz sentido deflacionar a base nesse caso.

Código
dados <- dados_raw |>
  mutate(
    value = deflateBR::deflate(
      value,
      date,
      "09/2024",
      index = "ipca"
    )
  )

\[ y_t = \alpha + \phi_1 y_{t-1} + \Phi y_{t-12} + \epsilon_t \]

\[ \Delta^{12} y_t = y_t - y_{t-12} \]

Código
# série deflacionada
dados |>
  ggplot(aes(date, value)) +
  geom_line()
Código
tdata <- dados |>
  mutate(date = yearmonth(date)) |>
  as_tsibble(index = date)
Código
tdata |>
  gg_season(value)
Código
tdata |>
  gg_tsdisplay(value, plot_type = "partial")
Código
tdata |>
  mutate(value = difference(value, 12)) |>
  gg_tsdisplay(value, plot_type = "partial")

3.1 Teste de hipótese

SARIMA(p,d=0,q)(P,D=1,Q)

Código
tdata |>
  features(value, list(unitroot_ndiffs, unitroot_nsdiffs))
Código
tdata |>
  mutate(value = difference(value, 12)) |>
  features(value, unitroot_ndiffs)
Código
tdata |>
  mutate(value = difference(value, 1)) |>
  features(value, unitroot_nsdiffs)

3.2 Decomposição

  1. Use um método de decomposição estudado e explique os componentes, tendo em vista o aspecto da série. Existe sazonalidade? Qual periodicidade?
Código
tdata |>
  fabletools::model(
    stl = feasts::STL(value)
  ) |>
  fabletools::components() |>
  autoplot()
Código
tdata |>
  fabletools::model(
    stl = feasts::classical_decomposition(value)
  ) |>
  fabletools::components() |>
  autoplot()

4 Modelo SARIMA

  1. Identifique um modelo SARIMA apropriado. Justifique o modelo escolhido através de critérios de informação e/ou usando a etapa de identificação da abordagem Box-Jenkins. Nesta etapa é possível utilizar um método automático de seleção das ordens do modelo.

ARIMA(1,0,0)(2,1,1)[12]

Código
fit <- tdata |>
  model(
    arima_manual = ARIMA(value ~ 1 + pdq(1, 0, 0) + PDQ(2, 1, 1)),
    stepwise = ARIMA(value),
    search = ARIMA(value, stepwise = FALSE)
  )

fit |>
  pivot_longer(
    everything()
  )
Código
glance(fit)

4.1 Diagnóstico

  1. Faça a verificação de diagnóstico residual de seu modelo ARIMA. Os resíduos são ruído branco?
Código
fit |>
  select(stepwise) |>
  gg_tsresiduals(lag = 36)
Código
augment(fit) |>
  filter(.model == "stepwise") |>
  features(.innov, ljung_box, lag = 36, dof = 4)

4.2 Previsão

  1. Use o modelo SARIMA de sua escolha para fazer previsões h (livre escolha) passos à frente.
Código
forecast(fit, h = 24) |>
  filter(.model == "stepwise") |>
  autoplot(tdata)

4.3 [Extra] Comparação ARIMA e ETS

Código
train <- tdata |>
  filter_index(. ~ "2019 dec")

acuracia <- train |>
  model(
    ETS(value),
    ARIMA(value)
  ) |>
  forecast(h = 24) |>
  accuracy(tdata)

acuracia

5 Validação cruzada usando modeltime

Código
dados |>
  timetk::plot_time_series(date, value)

Fizemos um primeiro split para ajustar o modelo. Vamos testar sua performance depois via backtesting.

Código
split_forecast <- timetk::time_series_split(
  dados,
  date_var = date,
  initial = "11 years",
  assess = "24 months"
)

Vamos ajustar o modelo na parte azul e testar na parte vermelha!

Código
split_forecast |>
  timetk::tk_time_series_cv_plan() |>
  timetk::plot_time_series_cv_plan(date, value)

Além disso, aqui montamos um esquema de backtesting com vários recortes

Código
splits <- timetk::time_series_cv(
  dados,
  date_var = date,
  initial = "60 months",
  assess = "24 months",
  skip = 12,
  slice_limit = 10,
  cumulative = FALSE
)

splits
Código
splits |>
  timetk::tk_time_series_cv_plan() |>
  timetk::plot_time_series_cv_plan(date, value)

5.1 Modelos SARIMA, ETS e Prophet

  1. Repita os passos acima ajustando um modelo que use o método do Prophet. Faça a previsão para o mesmo período e compare com o modelo SARIMA.
Código
library(modeltime)
library(tidymodels)

model_arima <- arima_reg() |>
  set_engine("auto_arima") |>
  fit(value ~ date, training(split_forecast))

# [E]xponen[T]ial   [S]moothing
# [E]rror, [T]rend, [S]easonal

model_ets <- exp_smoothing() |>
  set_engine("ets") |>
  fit(value ~ date, training(split_forecast))

model_prophet <- prophet_reg(seasonality_yearly = TRUE) |>
  set_engine("prophet") |>
  fit(value ~ date, training(split_forecast))

É possível tunar hiperparâmetros (por exemplo, os hiperparâmetros do Prophet). Uma referência para isso está aqui.

Código
mtable <- modeltime_table(
  model_arima,
  model_ets,
  model_prophet
)

5.2 Comparação das métricas no teste (12 meses)

Código
dados_para_teste <- modeltime_calibrate(
  mtable,
  testing(split_forecast)
)
Código
dados_para_teste |>
  modeltime_accuracy() |>
  table_modeltime_accuracy(.interactive = FALSE)

Comparando os forecasts dos modelos

Código
dados_para_teste |>
  modeltime_forecast(
    new_data = testing(split_forecast),
    actual_data = dados
  ) |>
  plot_modeltime_forecast()

Fazendo a previsão de um modelo específico

Código
fcast <- dados_para_teste |>
  filter(.model_id == 3) |>
  modeltime_refit(dados) |>
  modeltime_forecast(
    h = "24 months",
    actual_data = dados,
    conf_interval = .8
  )

fcast |>
  plot_modeltime_forecast()

5.3 Backtesting

  1. Faça um backtest e verifique qual modelo tem melhor poder preditivo (calcule medidas de performance que sejam comparáveis entre os dois modelos usando diferentes janelas de tempo). Nesta etapa você pode usar apenas um split treino/teste temporal ou vários. Justifique qual dos modelos você usaria.
Código
resamples <- mtable |>
  modeltime.resample::modeltime_fit_resamples(
    resamples = splits
  )
Código
resamples |>
  modeltime.resample::plot_modeltime_resamples(
    .point_size = 3,
    .point_alpha = 0.8,
    .interactive = FALSE
  )
Código
resamples |>
  modeltime.resample::modeltime_resample_accuracy(summary_fns = mean) |>
  table_modeltime_accuracy(.interactive = FALSE)
De volta ao topo