Modelos_Previsao

Importando os dados:

library(tidyverse)
library(forecast)
library(tidyquant)
library(timetk)
library(modeltime)
library(tidymodels)
library(workflows)

df <- read_csv2("dados/DadosProduto.csv") %>% 
  mutate(DATA = as.Date(DATA, format = "%d/%m/%Y %H:%M")) %>% 
  mutate(QUANTIDADE = ifelse(QUANTIDADE < 0, 0, QUANTIDADE)) %>% 
  mutate(l_y = log(QUANTIDADE + 1)) # um teste com variavel em log

df %>% head()
## # A tibble: 6 x 3
##   DATA       QUANTIDADE   l_y
##   <date>          <dbl> <dbl>
## 1 2017-01-04        228  5.43
## 2 2017-01-05        228  5.43
## 3 2017-01-06        108  4.69
## 4 2017-01-07        276  5.62
## 5 2017-01-08          0  0   
## 6 2017-01-09          0  0

tk_get_frequency realiza uma análise espectral da série e retorna o período de frequência dominante da série. O resultado parece indicar uma série semanal. Alternativametne pode-se utilizar a função getfrequency().

df %>% 
  tk_index() %>% 
  tk_get_frequency(period = "auto")
## [1] 7

Converter os dados de diário para semanal usando tq_transmute:

# converter para semana
df_semanal <- df %>% tq_transmute(select = l_y,
                    mutate_fun = apply.weekly,
                    FUN = sum)
df_semanal %>% head()
## # A tibble: 6 x 2
##   DATA         l_y
##   <date>     <dbl>
## 1 2017-01-08  21.2
## 2 2017-01-15  20.3
## 3 2017-01-22  23.0
## 4 2017-01-29  31.2
## 5 2017-02-05  23.0
## 6 2017-02-12  28.0

Gráfico da série temporal

A série semanal parece ter alguns padrões de tendência e talvez de sazonalidade.

df_semanal %>% 
  plot_time_series(DATA, l_y)
# codigo alternativo
#df_semanal %>% 
#  ggplot(aes(x = DATA, y = QUANTIDADE)) + 
#  geom_line()

Para investigar vamos usar plot_stl_diagnostics. Alternativamente pode-se utilizar a função decompose.

df_semanal %>% 
  plot_stl_diagnostics(DATA, l_y, .frequency = "auto", .trend = "auto")

Modelos de Previsão

Particionando os dados

Particionando os dados entre testing set e training set. Vamos utilizar os últimos 6 meses como o testing set (assess = "6 months") e todos os períodos anteriores como training set (cumulative = TRUE). O ideal é utilizar um procedimento de cross-validation.

ts_split <- df_semanal %>% 
  time_series_split(assess = "6 months", # últimos 6 meses como testing set
                    cumulative = TRUE) # todos os períodos anteriores como training set

O resultado do split do banco de dados:

ts_split %>% 
  tk_time_series_cv_plan() %>% 
  plot_time_series_cv_plan(DATA, l_y)

Modelos

Temos dois tipos de modelos: (1) os modelos clássicos de séries temporais, como o ARIMA e o Prophet, que usarão os valores passados da série para produzir ajuste do modelo e (2) modelos de machine learning que incorporam outras variáveis explicativas, sobretudo derivadas da variável data.

Para facilizar o processo de rodar vários modelos vamos criar um workflow com três passos:

    1. Definir o modelo
    1. Pre-processar os dados, criando as variáveis
    1. Ajustar o modelo sob os dados

Os modelos de TS convencionais sempre usarão a mesma receita, de quantidade (y) em função dos seus valores passados (DATA).

recipe_auto <- recipe(l_y ~ DATA, data = training(ts_split))

Para os demais modelos, usaremos um conjunto de variáveis derivadas de DATA. Com step_timeseries_signature convertemos a variável DATA em várias variáveis derivadas. Com step_date(DATA, featurs = "month") criamos uma variável de mês, que captura sazonalidade mensal. Com step_mutate(date_num = as.numeric(DATA)) criamos uma variável numérica e crescente para cada período no tempo. Esta última variável captura tendências na série.

recipe_spec <- recipe(l_y ~ DATA, data = training(ts_split)) %>% 
  step_timeseries_signature(DATA) %>% 
  step_fourier(DATA, period = c(12, 24, 36, 48), K=2) %>% 
  step_rm(contains("iso"), contains("minute"), contains("hour"),
          contains("am.pm"), contains("xts")) %>% 
  step_date(DATA, features = "month", ordinal = FALSE) %>% 
  step_mutate(date_num = as.numeric(DATA)) %>% 
  step_normalize(date_num) %>% 
  step_normalize(contains("index.num")) %>% 
  step_dummy(contains("lbl"), one_hot = TRUE) %>% 
  step_dummy(all_nominal())

As variáveis criadas por curiosidade:

bake(prep(recipe_spec), new_data = training(ts_split))
## # A tibble: 184 x 67
##    DATA         l_y DATA_index.num DATA_year DATA_half DATA_quarter
##    <date>     <dbl>          <dbl>     <int>     <int>        <int>
##  1 2017-01-08  21.2          -1.72      2017         1            1
##  2 2017-01-15  20.3          -1.70      2017         1            1
##  3 2017-01-22  23.0          -1.68      2017         1            1
##  4 2017-01-29  31.2          -1.66      2017         1            1
##  5 2017-02-05  23.0          -1.64      2017         1            1
##  6 2017-02-12  28.0          -1.62      2017         1            1
##  7 2017-02-19  27.9          -1.61      2017         1            1
##  8 2017-02-26  28.5          -1.59      2017         1            1
##  9 2017-03-05  18.0          -1.57      2017         1            1
## 10 2017-03-12  21.5          -1.55      2017         1            1
## # ... with 174 more rows, and 61 more variables: DATA_month...7 <int>,
## #   DATA_day <int>, DATA_second <int>, DATA_wday <int>, DATA_mday <int>,
## #   DATA_qday <int>, DATA_yday <int>, DATA_mweek <int>, DATA_week <int>,
## #   DATA_week2 <int>, DATA_week3 <int>, DATA_week4 <int>, DATA_mday7 <int>,
## #   DATA_sin12_K1 <dbl>, DATA_cos12_K1 <dbl>, DATA_sin12_K2 <dbl>,
## #   DATA_cos12_K2 <dbl>, DATA_sin24_K1 <dbl>, DATA_cos24_K1 <dbl>,
## #   DATA_sin24_K2 <dbl>, DATA_cos24_K2 <dbl>, DATA_sin36_K1 <dbl>,
## #   DATA_cos36_K1 <dbl>, DATA_sin36_K2 <dbl>, DATA_cos36_K2 <dbl>,
## #   DATA_sin48_K1 <dbl>, DATA_cos48_K1 <dbl>, DATA_sin48_K2 <dbl>,
## #   DATA_cos48_K2 <dbl>, DATA_month...36 <fct>, date_num <dbl>,
## #   DATA_month.lbl_01 <dbl>, DATA_month.lbl_02 <dbl>, DATA_month.lbl_03 <dbl>,
## #   DATA_month.lbl_04 <dbl>, DATA_month.lbl_05 <dbl>, DATA_month.lbl_06 <dbl>,
## #   DATA_month.lbl_07 <dbl>, DATA_month.lbl_08 <dbl>, DATA_month.lbl_09 <dbl>,
## #   DATA_month.lbl_10 <dbl>, DATA_month.lbl_11 <dbl>, DATA_month.lbl_12 <dbl>,
## #   DATA_wday.lbl_1 <dbl>, DATA_wday.lbl_2 <dbl>, DATA_wday.lbl_3 <dbl>,
## #   DATA_wday.lbl_4 <dbl>, DATA_wday.lbl_5 <dbl>, DATA_wday.lbl_6 <dbl>,
## #   DATA_wday.lbl_7 <dbl>, DATA_month...37_fev <dbl>,
## #   DATA_month...37_mar <dbl>, DATA_month...37_abr <dbl>,
## #   DATA_month...37_mai <dbl>, DATA_month...37_jun <dbl>,
## #   DATA_month...37_jul <dbl>, DATA_month...37_ago <dbl>,
## #   DATA_month...37_set <dbl>, DATA_month...37_out <dbl>,
## #   DATA_month...37_nov <dbl>, DATA_month...37_dez <dbl>

Modelo 1: ARIMA

O primeiro modelo é um ARIMA simples, que usará o auto.arima() para definir os parâmetros ótimos do modelo. Assim, o workflow para o arima adiciona a receita recipe_auto, depois definimos o modelo e por fim, ajustamos o modelo aos dados.

modelo_fit_arima <- arima_reg() %>% 
  set_engine(engine = "auto_arima") 

workflow_fit_arima <- workflow() %>% 
  add_recipe(recipe_auto) %>% 
  add_model(modelo_fit_arima) %>% 
  fit(training(ts_split))

Modelo 2: ARIMA BOOSTED (XGBOOST)

É uma forma de utilizar o modelo XGBoost para modelar os resíduos de um modelo ARIMA. Aqui vamos adicionar adicionar novas variáveis derivadas da coluna utilizando a receita recipe_spec.

modelo_fit_arima_boosted <- arima_boost(
  min_n = 2,
  learn_rate = 0.015) %>%
  set_engine(engine = "auto_arima_xgboost") 


workflow_fit_arima_xgboost <- workflow() %>% 
  add_model(modelo_fit_arima_boosted) %>% 
  add_recipe(recipe_spec) %>% 
  fit(training(ts_split))

Modelo 3: EXPONENTIAL SMOOTHING

Criar um modelo de Estado espaço Exponential Smoothing do tipo Error-Trend-Season.

modelo_fit_ets <- exp_smoothing() %>% 
  set_engine(engine = "ets")

workflow_fit_ets <- workflow() %>%
  add_model(modelo_fit_ets) %>% 
  add_recipe(recipe_auto) %>% 
  fit(training(ts_split))

Modelo 4: PROPHET

Modelo Prophet desenvolvido pelo facebook.

modelo_fit_prophet <- prophet_reg(seasonality_yearly = TRUE) %>% 
  set_engine(engine = "prophet")

workflow_fit_prophet <- workflow() %>% 
  add_model(modelo_fit_prophet) %>% 
  add_recipe(recipe_auto) %>%
  fit(training(ts_split))

Modelo 5: PROPHET BOOSTED

modelo_fit_prophet_boost <- prophet_boost(learn_rate = 0.1) %>% 
  set_engine(engine = "prophet_xgboost")

workflow_fit_prophet_boost <- workflow() %>% 
  add_model(modelo_fit_prophet_boost) %>% 
  add_recipe(recipe_spec) %>%
  fit(training(ts_split))

Modelo 6: Modelo Linear TSLM

workflow_fit_lm <- linear_reg() %>%
  set_engine("lm") %>% 
  fit(l_y ~ as.numeric(DATA) + factor(month(DATA, label = TRUE), ordered = FALSE),
      data = training(ts_split))

Modelo 7: Modelo de Random Forest

modelo_fit_rf <- rand_forest(trees = 500, min_n = 50) %>% 
  set_engine(engine = "randomForest")

worflow_fit_rf <- workflow() %>% 
  add_model(modelo_fit_rf) %>% 
  add_recipe(recipe_spec) %>% 
  fit(training(ts_split))

Adicionar Modelos Ajustados

tabela_modelos <- modeltime_table(
  workflow_fit_arima,
  workflow_fit_arima_xgboost,
  workflow_fit_ets,
  workflow_fit_prophet,
  workflow_fit_prophet_boost,
  worflow_fit_rf
)
tabela_modelos
## # Modeltime Table
## # A tibble: 6 x 3
##   .model_id .model     .model_desc                              
##       <int> <list>     <chr>                                    
## 1         1 <workflow> ARIMA(2,1,3)(0,0,1)[13]                  
## 2         2 <workflow> ARIMA(4,1,1)(1,0,0)[13] W/ XGBOOST ERRORS
## 3         3 <workflow> ETS(A,N,A)                               
## 4         4 <workflow> PROPHET                                  
## 5         5 <workflow> PROPHET W/ XGBOOST ERRORS                
## 6         6 <workflow> RANDOMFOREST

Calibrando o Modelo

Aqui são calculados as métricas de performance do modelo a partir das previsões feitas para fora da amostra da base de treinamento. Assim, são quantificado os erros e estimados os intervalos de confiança.

tabela_calibracao <- tabela_modelos %>%
  modeltime_calibrate(new_data = testing(ts_split))
tabela_calibracao
## # Modeltime Table
## # A tibble: 6 x 5
##   .model_id .model     .model_desc                        .type .calibration_da~
##       <int> <list>     <chr>                              <chr> <list>          
## 1         1 <workflow> ARIMA(2,1,3)(0,0,1)[13]            Test  <tibble [25 x 4~
## 2         2 <workflow> ARIMA(4,1,1)(1,0,0)[13] W/ XGBOOS~ Test  <tibble [25 x 4~
## 3         3 <workflow> ETS(A,N,A)                         Test  <tibble [25 x 4~
## 4         4 <workflow> PROPHET                            Test  <tibble [25 x 4~
## 5         5 <workflow> PROPHET W/ XGBOOST ERRORS          Test  <tibble [25 x 4~
## 6         6 <workflow> RANDOMFOREST                       Test  <tibble [25 x 4~

Visualizar a Previsão

Com os dados calibrados, podemos visualziar as previsões sobre o período de teste.

tabela_calibracao %>% 
  modeltime_forecast(
    new_data = testing(ts_split),
    actual_data = df_semanal) %>% 
  plot_modeltime_forecast()

Medidas de Performance

O Modelo Prophet parece produzir o menor valor de Mean-Absolute-Error (MAE). Mas ainda muito alto.

tabela_calibracao %>% 
  modeltime_accuracy() %>% 
  table_modeltime_accuracy()

Reajustar o Modelo

Reajusta ro modelo para o banco de dados completo e realizar previsões para as datas futuras.

tabela_refit <- tabela_calibracao %>% 
  modeltime_refit(data = df_semanal)

tabela_refit %>% 
  modeltime_forecast(h = "3 months", actual_data = df_semanal) %>% 
  plot_modeltime_forecast()

Resample

Nosso resultado pode ser sensível ao uso de uma janela específica de treinamento e teste. Podemos avaliar se os resultados se modificam quando utilizamos janelas diferentes. Usamos a função time_serse_cv para produzir 4 splits test/training diferentes, com o treinamento sempre possuindo 12 meses de duração, e o período de teste, 6 meses.

resamples_tscv <- df_semanal %>% 
  time_series_cv(initial = "12 months", # training window
                assess = "6 months", # assessment window
                skip = "6 months", # shift between resample sets
                slice_limit = 4) # no. of resamples

As quatro janelas podem ser observadas abaixo:

resamples_tscv %>% 
  tk_time_series_cv_plan() %>% 
  plot_time_series_cv_plan(DATA, l_y)

Usando o tabela_modelos e o resample_tscv cada modelo é internamente refitado para cada novo training set.

library(modeltime.resample)

resamples_fitted <- tabela_modelos %>% 
  modeltime_fit_resamples(
    resamples = resamples_tscv,
    control = control_resamples(verbose = FALSE)
  )

Avaliando os resultados

O modelo linear parece muito sensível ao recorte temporal, produzindo um MAE muito alto no slice 3.

resamples_fitted %>%
    plot_modeltime_resamples(
      .point_size  = 3, 
      .point_alpha = 0.8,
      .interactive = FALSE
    )

Tabela de Precisão

A tabela abaixo mostra os resultados médios para os 4 recortes. De maneira geral, o modelo ETS tem o menor MAE médio e o menor RMSE médio.

resamples_fitted %>%
    modeltime_resample_accuracy(summary_fns = mean) %>%
    table_modeltime_accuracy()