SARIMA com Variáveis Exógenas - Análise de Desempenho
Authors
UIVS - Unidade de Inteligência em Vigilância em Saúde
Caio Sain Vallio
Published
11/01/2026
1. Introdução
1.1 Objetivo
Este relatório analisa o desempenho do modelo SARIMAX (Seasonal AutoRegressive Integrated Moving Average with eXogenous variables), que estende o SARIMA tradicional ao incorporar variáveis exógenas (regressores externos) como preditoras adicionais.
1.2 Metodologia
Modelo: SARIMAX (Auto-ajustado)
Estratégia: Rolling Replication (Janela deslizante) com recálculo de features por fold
Horizontes de Previsão: 4, 6 e 8 semanas
Comparativo: Comparação com SNaive, SARIMA e modelos anteriores
Métricas: RMSE, MAE, MAPE, RMSLE, MASE
Paralelismo: Execução paralela via furrr para otimização de tempo
1.3 Variáveis Exógenas: Conceitos Estatísticos
O modelo SARIMAX incorpora variáveis exógenas (\(\mathbf{X}_t\)) à estrutura tradicional do SARIMA:
Climáticas Defasadas: Temperatura e precipitação com lags (4-8 semanas) para respeitar o ciclo biológico do vetor.
Tendência Climática: Médias móveis de temperatura para capturar persistência térmica.
Nota: Optamos por não incluir harmônicos de Fourier ou lags autoregressivos como regressores externos (xreg) nesta iteração, pois o componente SARIMA (Seasonal AR+MA) já modela a sazonalidade estocástica e a autocorrelação de forma eficiente. A inclusão duplicada poderia gerar colinearidade e instabilidade.
1.3.2 Importância do Lag Epidemiológico
O ciclo biológico do Aedes aegypti impõe um delay natural entre condições climáticas favoráveis e aumento de casos:
Temperatura: Afeta desenvolvimento larval (7-14 dias) + período de incubação extrínseco (8-12 dias)
Precipitação: Influência na disponibilidade de criadouros com atraso de 2-4 semanas
Por isso, utilizamos lags de 4-8 semanas para variáveis climáticas.
1.3.3 Prevenção de Data Leakage
Cuidado Crítico
Ao incluir variáveis derivadas do alvo (como lag_1, roll_mean_4), é essencial que estas sejam calculadas apenas com dados disponíveis até o momento da previsão. No backtesting, isso significa recalcular features a cada fold.
Para garantir isso, utilizamos a função run_backtest_parallel() que:
Recebe dados base (sem features calculadas)
Para cada origem de backtest, chama make_features_safe(data, config, cutoff_idx = origin)
Treina o modelo apenas com features calculadas até o ponto de corte
2. Dados e Preparação
Code
# 1. Carregar dados brutosdf_raw <-load_raw_data()
#> [2026-01-11 14:57:28] INFO: Carregando dados de: /Users/caiosainvallio/ses-sp/forecast_dengue/data/raw/dengue.RData
#> [2026-01-11 14:57:29] INFO: Dados carregados: 248865 linhas, 23 colunas
Code
# 2. Agregar por estadodf_state <-aggregate_state(df_raw)
#> [2026-01-11 14:57:29] INFO: Agregando dados por estado...
#> [2026-01-11 14:57:29] INFO: Dados agregados: 626 semanas
#> [2026-01-11 14:57:29] INFO: ========== INICIO: Preprocessamento ==========
#> [2026-01-11 14:57:29] INFO: Removidas 3 linhas da semana 53
#> [2026-01-11 14:57:29] WARN: Semanas faltantes detectadas: 2
#> [2026-01-11 14:57:29] WARN: ATENCAO: Alvo nao sera imputado (sera mantido como NA)
#> [2026-01-11 14:57:29] INFO: Imputados 8 NAs em mean_temp (fill)
#> [2026-01-11 14:57:29] INFO: Imputados 8 NAs em mean_max_temp (fill)
#> [2026-01-11 14:57:29] INFO: Imputados 8 NAs em mean_min_temp (fill)
#> [2026-01-11 14:57:29] INFO: Imputados 8 NAs em mean_precip (fill)
#> [2026-01-11 14:57:29] INFO: Dados preprocessados: 625 linhas
#> [2026-01-11 14:57:29] INFO: ========== FIM: Preprocessamento ==========
Code
# IMPORTANTE: Para SARIMAX, NÃO chamamos make_features() aqui!# O backtest paralelo vai calcular features por fold para evitar leakage# Dados BASE (sem features) para o backtestingdf_base <- df |>mutate(data_iniSE =as.Date(data_iniSE)) |>arrange(data_iniSE)# Carregar config de features para passar ao backtesterconfig_features <-tryCatch(load_config("features"), error =function(e) list())if (length(config_features) ==0) { config_features <-list(temporal =list(enabled =TRUE), seasonal =list(enabled =TRUE))}cat("Dados base carregados:", nrow(df_base), "linhas\n")
#> Dados base carregados: 625 linhas
Code
cat("Features serão calculadas por fold no backtesting (sem leakage)\n")
#> Features serão calculadas por fold no backtesting (sem leakage)
cat("- temp_roll_mean_4: Tendência de temperatura (média móvel 4 semanas)\n")
#> - temp_roll_mean_4: Tendência de temperatura (média móvel 4 semanas)
Code
cat("\n*Foram excluídos termos de Fourier e lags da variável alvo para evitar redundância com os componentes Sazonal e AR do modelo SARIMA.*\n")
#>
#> *Foram excluídos termos de Fourier e lags da variável alvo para evitar redundância com os componentes Sazonal e AR do modelo SARIMA.*
4. Backtesting
4.1 Prevenção de Data Leakage
Modo Seguro Ativado
O backtesting utiliza run_backtest_parallel() que recalcula todas as features a cada fold usando apenas dados disponíveis até aquele ponto. Isso garante que:
Lags são calculados apenas com observações passadas
Médias móveis não “vazam” informação do futuro
A avaliação reflete o desempenho real em produção
4.2 Execução com Paralelismo
Utilizamos a execução paralela para reduzir o tempo de processamento:
Code
# Seleção Racional de Features (Baseada na Decomposição)# A análise de decomposição mostrou forte sazonalidade e componente AR.# O modelo SARIMA puro já captura bem a sazonalidade estocástica e a autocorrelação.# Portanto, para o SARIMAX, devemos evitar redundância:# - Remover 'lags' da variável alvo (deixar o componente AR do ARIMA cuidar disso)# - Remover termos de Fourier (deixar o componente Sazonal do SARIMA cuidar disso)# - Manter apenas variáveis CLIMÁTICAS que trazem informação novaselected_xregs <-c("temperature_lag4", # Efeito biológico da temperatura"precipitation_lag4", # Efeito de criadouros"temp_roll_mean_4"# Tendência térmica recente)# Configuração do Backtestbacktest_config <-list(backtest =list(horizons =c(4, 6, 8),initial_window =52*5, # 5 anos de treino inicialstep =4# Avançar a cada 4 semanas ),data =list(date_col ="data_iniSE",target ="est_inc100k" ),# Config de features para recálculo por foldfeatures = config_features)# Configuração do modelo SARIMAX refinadomodel_config <-list(target ="est_inc100k",season =52,auto =TRUE,lambda =0, # Box-Cox (Log)stepwise =TRUE,approximation =TRUE,xreg_cols = selected_xregs, # Forçar uso apenas das climáticasseasonal =list(D =1) # Forçar diferenciação sazonal para evitar previsão "flat")cat("### Estratégia de Modelagem Refinada\n")
#> ### Estratégia de Modelagem Refinada
Code
cat("Selecionando apenas regressores climáticos para evitar colinearidade com estrutura SARIMA:\n")
#> Selecionando apenas regressores climáticos para evitar colinearidade com estrutura SARIMA:
cat(sprintf("Treinando modelo com dados até: %s\n", last_reliable_date))
#> Treinando modelo com dados até: 2025-12-07
Code
# Para DEPLOY: calcular features com dados filtrados# (diferente do backtesting que recalcula por fold)df_model <-make_features_for_prediction(df_final_base, config_features)
#> [2026-01-11 15:17:46] INFO: ========== INICIO: Feature Engineering ==========
#> [2026-01-11 15:17:46] INFO: Adicionando features temporais...
#> [2026-01-11 15:17:46] INFO: Adicionando features de sazonalidade...
#> [2026-01-11 15:17:46] INFO: Adicionando features climaticas...
#> [2026-01-11 15:17:46] INFO: Features criadas: 47 novas colunas
#> [2026-01-11 15:17:46] INFO: ========== FIM: Feature Engineering ==========
Code
# Treinar modelo final com todos os dadosmodel_fit <- model$fit(df_model, model_config)# Exibir parâmetros do modelo selecionadoparams <- model$get_params(model_fit)cat("### Especificação do Modelo Final\n")
# Datas para previsão (seguindo padrão SARIMA: iniciando após o último dado disponível, T+1)# O modelo foi treinado até T-2, mas projetamos 8 semanas à frente para cobrir # o período de nowcast (T-1, T) e futuro (T+1..T+6), rotulando como T+1..T+8# Usando df_base para garantir data máxima corretalast_date <-max(df_base$data_iniSE)future_dates <-seq(from = last_date +7, by ="week", length.out =8)# Previsãoforecast_h8 <- model$predict(model_fit, h =8, new_data = df_model)intervals <- model$predict_interval(model_fit, h =8, level =0.95, new_data = df_model)forecast_df <-data.frame(Data = future_dates,Semana_Epidem = lubridate::epiweek(future_dates),Previsao =round(forecast_h8, 2),Inferior_95 =round(intervals$lower, 2),Superior_95 =round(intervals$upper, 2))forecast_df |>kable(caption ="Previsão SARIMAX - Próximas 8 Semanas") |>kable_styling(full_width =FALSE, bootstrap_options =c("striped", "hover"))