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 Seasonal Naive, que serve como baseline (referência base) para o sistema de previsão de dengue. Este modelo pressupõe que “o futuro repetirá o passado recente seguindo a sazonalidade”, utilizando o valor observado na mesma semana do ano anterior como previsão.
1.2 Metodologia
Modelo: Seasonal Naive (SNaive)
Estratégia: Rolling Replication (Janela deslizante)
Horizontes de Previsão: 4, 6 e 8 semanas
Métricas: RMSE, MAE, MAPE, RMSLE, MASE
Interpretabilidade: O modelo captura puramente a sazonalidade anual (lag 52).
2. Dados e Preparação
Code
# 1. Carregar dados brutosdf_raw <-load_raw_data()
#> [2026-01-11 14:46:09] INFO: Carregando dados de: /Users/caiosainvallio/ses-sp/forecast_dengue/data/raw/dengue.RData
#> [2026-01-11 14:46:09] INFO: Dados carregados: 248865 linhas, 23 colunas
Code
# 2. Agregar por estado df_state <-aggregate_state(df_raw)
#> [2026-01-11 14:46:09] INFO: Agregando dados por estado...
#> [2026-01-11 14:46:09] INFO: Dados agregados: 626 semanas
#> [2026-01-11 14:46:09] INFO: ========== INICIO: Preprocessamento ==========
#> [2026-01-11 14:46:09] INFO: Removidas 3 linhas da semana 53
#> [2026-01-11 14:46:09] WARN: Semanas faltantes detectadas: 2
#> [2026-01-11 14:46:09] WARN: ATENCAO: Alvo nao sera imputado (sera mantido como NA)
#> [2026-01-11 14:46:09] INFO: Imputados 8 NAs em mean_temp (fill)
#> [2026-01-11 14:46:09] INFO: Imputados 8 NAs em mean_max_temp (fill)
#> [2026-01-11 14:46:09] INFO: Imputados 8 NAs em mean_min_temp (fill)
#> [2026-01-11 14:46:09] INFO: Imputados 8 NAs em mean_precip (fill)
#> [2026-01-11 14:46:09] INFO: Dados preprocessados: 625 linhas
#> [2026-01-11 14:46:09] INFO: ========== FIM: Preprocessamento ==========
Code
# NOTA: Seasonal Naive NÃO precisa de features externas# Usa apenas o valor defasado de 52 semanas (sazonalidade)# Por isso, NÃO chamamos make_features() aquidf_features <- df # Usar dados preprocessados diretamente# Visualizar ultimas semanastail(df_features |>select(data_iniSE, est_inc100k), 5) |>kable(caption ="Dados mais recentes da série")
Dados mais recentes da série
data_iniSE
est_inc100k
621
2025-11-23
25.519079
622
2025-11-30
25.899642
623
2025-12-07
23.605036
624
2025-12-14
20.172107
625
2025-12-21
6.802992
3. Definição do Modelo
Code
# Carregar definicao do modelo do registromodel_name <-"seasonal_naive"model <-get_model(model_name)cat(sprintf("Modelo: %s\nFamília: %s\nDescrição: %s", model$name, model$family, model$description))
#> Modelo: seasonal_naive
#> Família: baseline
#> Descrição: Previsao usando valor da mesma semana do ano anterior
4. Backtesting (Validação Histórica)
Realizamos um backtesting robusto utilizando a abordagem de origem deslizante (rolling origin). O modelo é reavaliado a cada passo de tempo para garantir que as métricas reflitam o desempenho esperado em produção.
Code
# Configuracao do Backtest# Vamos definir horizontes explicitos para este relatoriobacktest_config <-list(backtest =list(horizons =c(4, 6, 8),initial_window =52*5, # 5 anos de treino inicialstep =4# Avancar a cada 4 semanas ),data =list(date_col ="data_iniSE",target ="est_inc100k" ))# Executar Backtest# Nota: O SNaive eh muito rapido, nao precisa de cache complexobt_result <-run_backtest(model_name = model_name,data = df_features,config = backtest_config,verbose =FALSE)# Resumo da execucaoprint(bt_result)
#>
#> === Resultado de Backtest ===
#>
#> Modelo: seasonal_naive
#> Origens: 90
#> Previsoes: 270
#> Duracao: 0.1 s
#>
#> Metricas por Horizonte:
#> h mae rmse mape smape mase rmsle n
#> 4 42.74004 96.31014 66.14327 68.11956 1.261964 0.9683374 90
#> 6 41.76718 89.45208 66.88110 67.60992 1.233238 0.9715382 90
#> 8 42.75303 96.31281 66.41829 67.49840 1.262347 0.9609409 90
5. Avaliação de Desempenho
5.1 Métricas Gerais
Abaixo apresentamos as métricas de erro agregadas por horizonte de previsão.
RMSLE (Root Mean Squared Logarithmic Error): Aproximação do erro percentual, penaliza menos erros em valores altos e mais em valores baixos. Importante para dados epidemiológicos com crescimento exponencial.
MASE (Mean Absolute Scaled Error): Comparativo com o SNaive in-sample. Valores próximos a 1 são esperados para o próprio SNaive no out-of-sample.
5.2 Análise Visual das Previsões
Comparação entre valores previstos e observados para o horizonte de 4 semanas.
Code
# Extrair previsoes de multiplos horizontespreds_multi <-bind_rows(extract_predictions(bt_result, horizon =4) |>mutate(horizon ="4 semanas"),extract_predictions(bt_result, horizon =6) |>mutate(horizon ="6 semanas"),extract_predictions(bt_result, horizon =8) |>mutate(horizon ="8 semanas"))# Plot estatico para ser convertido em interativop_backtest <-ggplot() +geom_line(data = preds_multi, aes(x = target_date, y = actual), color ="black", size =0.5) +geom_line(data = preds_multi, aes(x = target_date, y = predicted, color = horizon), size =0.5, alpha =0.8) +scale_color_manual(values =c("4 semanas"="#E74C3C", "6 semanas"="#3498DB", "8 semanas"="#2ECC71")) +labs(title ="Backtest: Previsões Multi-Horizonte",x ="Data", y ="Incidência") +theme_minimal()ggplotly(p_backtest) |>layout(legend =list(orientation ="h", x =0.5, xanchor ="center", y =-0.2))
5.3 Erros por Regime Epidemiológico
É importante avaliar se o modelo erra mais em picos epidêmicos ou em períodos de baixa transmissão.
Code
# Adicionar classificacao de regime aos resultadospreds_all <- bt_result$predictionspreds_all$regime <-classify_regime(preds_all$actual)# Calcular metricas por regimemetrics_regime <-compute_metrics_by_regime(preds_all)metrics_regime |>mutate(across(where(is.numeric), \(x) round(x, 3))) |>datatable(options =list(dom ='t'))
6. Previsão Futura
Gerando previsões para as próximas semanas com o modelo treinado em todos os dados disponíveis até 2 semanas atrás, simulando o atraso real de notificação do SINAN.
Code
# Definir atraso de notificação (semanas a ignorar)delay_weeks <-2last_reliable_date <-max(df_features$data_iniSE) - (delay_weeks *7)# Filtrar dados para treino final (removendo ultimas semanas incompletas)df_final <- df_features |>filter(data_iniSE <= last_reliable_date)cat(sprintf("Ignorando últimas %d semanas devido ao atraso de notificação.\n", delay_weeks))
#> Ignorando últimas 2 semanas devido ao atraso de notificação.
Code
cat(sprintf("Treinando modelo com dados até: %s\n", last_reliable_date))
#> Treinando modelo com dados até: 2025-12-07
Code
# Treinar com dados confiaveismodel_fit <- model$fit(df_final, list(season =52)) # Sazonalidade anual# 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+8last_date <-max(df_features$data_iniSE)future_dates <-seq(from = last_date +7,by ="week",length.out =8)# Prever 8 semanas (2 nowcast + 6 futuro)forecast_h8 <- model$predict(model_fit, h =8)# Intervalos de confiancaintervals <- model$predict_interval(model_fit, h =8, level =0.95)# Tabela de Previsãoforecast_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 Seasonal Naive - Próximas 8 Semanas") |>kable_styling(full_width =FALSE, bootstrap_options =c("striped", "hover"))
Previsão Seasonal Naive - Próximas 8 Semanas
Data
Semana_Epidem
Previsao
Inferior_95
Superior_95
2025-12-28
53
40.53
-123.44
204.49
2026-01-04
1
43.58
-120.38
207.54
2026-01-11
2
75.91
-88.05
239.88
2026-01-18
3
99.27
-64.69
263.24
2026-01-25
4
114.16
-49.81
278.12
2026-02-01
5
135.88
-28.08
299.84
2026-02-08
6
147.50
-16.47
311.46
2026-02-15
7
153.39
-10.58
317.35
Code
# Dados históricos usados no treino (até T-2)history_train <- df_features |>filter(data_iniSE <= last_reliable_date) |>tail(104) |>select(Date = data_iniSE, Value = est_inc100k) |>mutate(Type ="Histórico", Lower =NA, Upper =NA)# Dados recentes ignorados no treino (T-2 a T)history_ignored <- df_features |>filter(data_iniSE > last_reliable_date) |>select(Date = data_iniSE, Value = est_inc100k) |>mutate(Type ="Provisório (Ignorado)", Lower =NA, Upper =NA)# Previsão do modelo (T-2 a T+6)future_viz <-data.frame(Date = future_dates,Value = forecast_h8,Lower = intervals$lower,Upper = intervals$upper,Type ="Previsão (SNaive)")# Combinar para plotviz_df <-bind_rows(history_train, history_ignored, future_viz)p_future <-ggplot(viz_df, aes(x = Date, y = Value, color = Type)) +geom_line(aes(linetype = Type)) +geom_ribbon(aes(ymin = Lower, ymax = Upper, fill = Type), alpha =0.2, color =NA) +geom_point(data =filter(viz_df, Type =="Provisório (Ignorado)"), size =2) +scale_color_manual(values =c("Histórico"="black", "Previsão (SNaive)"="#E74C3C","Provisório (Ignorado)"="gray" )) +scale_fill_manual(values =c("Histórico"=NA, "Previsão (SNaive)"="#E74C3C","Provisório (Ignorado)"=NA )) +scale_linetype_manual(values =c("Histórico"="solid","Previsão (SNaive)"="solid","Provisório (Ignorado)"="dashed" )) +labs(title ="Previsão SNaive - Próximas 8 Semanas",subtitle ="Previsão gerada ignorando as últimas 2 semanas (simulação)",y ="Incidência / 100k hab") +theme_minimal() +theme(legend.position ="bottom")ggplotly(p_future) |>layout(legend =list(orientation ="h", x =0.5, xanchor ="center"))
7. Salvamento e Registro
7.1 Salvar Modelo
Salvando o objeto do modelo ajustado para uso posterior.
Atualizando a tabela mestre de métricas para comparação entre modelos.
Code
# Caminho da tabela de metricasmetrics_path <-file.path(PROJECT_ROOT, "data", "model_metrics.RData")# Preparar nova linha de registronew_entry <-data.frame(model_name ="seasonal_naive",execution_date =Sys.time(),horizon_4w_rmse = bt_result$metrics[bt_result$metrics$h ==4, "rmse"],horizon_4w_mae = bt_result$metrics[bt_result$metrics$h ==4, "mae"],horizon_4w_mape = bt_result$metrics[bt_result$metrics$h ==4, "mape"],horizon_4w_rmsle = bt_result$metrics[bt_result$metrics$h ==4, "rmsle"],horizon_8w_rmse = bt_result$metrics[bt_result$metrics$h ==8, "rmse"],horizon_8w_mae = bt_result$metrics[bt_result$metrics$h ==8, "mae"],horizon_8w_mape = bt_result$metrics[bt_result$metrics$h ==8, "mape"],horizon_8w_rmsle = bt_result$metrics[bt_result$metrics$h ==8, "rmsle"],avg_rmse =mean(bt_result$metrics$rmse),avg_mape =mean(bt_result$metrics$mape))# Carregar ou criar tabelaif (file.exists(metrics_path)) {load(metrics_path)# Se ja existe entrada para este modelo, remover antiga (ou manter historico?)# Vamos manter historico por data, mas para dashboard costuma-se pegar a mais recente model_metrics <-rbind(model_metrics, new_entry)} else { model_metrics <- new_entry}# Salvarsave(model_metrics, file = metrics_path)cat("Tabela de métricas atualizada com sucesso.\n")
#> Tabela de métricas atualizada com sucesso.
Code
model_metrics |>tail(5) |>kable()
model_name
execution_date
horizon_4w_rmse
horizon_4w_mae
horizon_4w_mape
horizon_4w_rmsle
horizon_8w_rmse
horizon_8w_mae
horizon_8w_mape
horizon_8w_rmsle
avg_rmse
avg_mape
seasonal_naive
2026-01-11 14:46:10
96.31014
42.74004
66.14327
0.9683374
96.31281
42.75303
66.41829
0.9609409
94.02501
66.48089
8. Conclusão
O modelo Seasonal Naive estabelece o baseline de performance. Qualquer modelo preditivo mais complexo (ARIMA, Machine Learning, Redes Neurais) deve obrigatoriamente superar as métricas apresentadas neste relatório para justificar sua complexidade.