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 Mean (Média Sazonal), que serve como uma alternativa de baseline para o sistema de previsão. Diferente do Seasonal Naive (que usa apenas o último ano), o Seasonal Mean utiliza a média de todos os anos anteriores para a mesma semana epidemiológica.
1.2 Metodologia
Modelo: Seasonal Mean
Estratégia: Rolling Replication (Janela deslizante)
Horizontes de Previsão: 4, 6 e 8 semanas
Comparativo: Comparação direta com o modelo Seasonal Naive
Métricas: RMSE, MAE, MAPE, RMSLE, MASE
2. Dados e Preparação
Code
# 1. Carregar dados brutosdf_raw <-load_raw_data()
#> [2026-01-11 14:46:36] INFO: Carregando dados de: /Users/caiosainvallio/ses-sp/forecast_dengue/data/raw/dengue.RData
#> [2026-01-11 14:46:36] INFO: Dados carregados: 248865 linhas, 23 colunas
Code
# 2. Agregar por estadodf_state <-aggregate_state(df_raw)
#> [2026-01-11 14:46:36] INFO: Agregando dados por estado...
#> [2026-01-11 14:46:37] INFO: Dados agregados: 626 semanas
#> [2026-01-11 14:46:37] INFO: ========== INICIO: Preprocessamento ==========
#> [2026-01-11 14:46:37] INFO: Removidas 3 linhas da semana 53
#> [2026-01-11 14:46:37] WARN: Semanas faltantes detectadas: 2
#> [2026-01-11 14:46:37] WARN: ATENCAO: Alvo nao sera imputado (sera mantido como NA)
#> [2026-01-11 14:46:37] INFO: Imputados 8 NAs em mean_temp (fill)
#> [2026-01-11 14:46:37] INFO: Imputados 8 NAs em mean_max_temp (fill)
#> [2026-01-11 14:46:37] INFO: Imputados 8 NAs em mean_min_temp (fill)
#> [2026-01-11 14:46:37] INFO: Imputados 8 NAs em mean_precip (fill)
#> [2026-01-11 14:46:37] INFO: Dados preprocessados: 625 linhas
#> [2026-01-11 14:46:37] INFO: ========== FIM: Preprocessamento ==========
Code
# NOTA: Seasonal Mean NÃO precisa de features externas# Usa apenas a média histórica por semana epidemiológica# Por isso, NÃO chamamos make_features() aquidf_features <- df # Usar dados preprocessados diretamente# Verificar coluna semanaif (!"semana"%in%names(df_features)) {stop("Erro: Coluna 'semana' não encontrada no dataframe.")}# Visualizar estruturaglimpse(df_features |>select(data_iniSE, est_inc100k, semana, ano))
Comparativo Direto: SNaive vs Seasonal Mean (Horizonte 4 semanas)
Métrica
SNaive
Seasonal_Mean
Diferença
Melhor
% Melhoria
RMSE (4 sem)
96.3101
91.2846
-5.0256
Seasonal Mean
5.2181
MAE (4 sem)
42.7400
39.0308
-3.7093
Seasonal Mean
8.6786
MAPE (4 sem)
66.1433
65.0013
-1.1420
Seasonal Mean
1.7266
RMSLE (4 sem)
0.9683
1.0641
0.0958
SNaive
-9.8944
5.3 Análise Visual (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(week_col ="semana"))# 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)# Previsaoforecast_h8 <- model$predict(model_fit, h =8, new_data = df_final)intervals <- model$predict_interval(model_fit, h =8, level =0.95, new_data = df_final)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 Seasonal Mean - Próximas 8 Semanas") |>kable_styling(full_width =FALSE, bootstrap_options =c("striped", "hover"))
Previsão Seasonal Mean - Próximas 8 Semanas
Data
Semana_Epidem
Previsao
Inferior_95
Superior_95
2025-12-28
53
11.79
5.62
17.95
2026-01-04
1
12.89
6.08
19.69
2026-01-11
2
20.46
8.72
32.20
2026-01-18
3
24.54
9.64
39.44
2026-01-25
4
30.69
12.99
48.38
2026-02-01
5
37.79
15.82
59.76
2026-02-08
6
45.65
19.43
71.87
2026-02-15
7
53.87
23.28
84.46
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 (Seasonal Mean)")# 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 (Seasonal Mean)"="blue","Provisório (Ignorado)"="gray" )) +scale_fill_manual(values =c("Histórico"=NA, "Previsão (Seasonal Mean)"="blue","Provisório (Ignorado)"=NA )) +scale_linetype_manual(values =c("Histórico"="solid","Previsão (Seasonal Mean)"="solid","Provisório (Ignorado)"="dashed" )) +labs(title ="Previsão Seasonal Mean - 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"))
Leaderboard de Modelos (Melhores Métricas Recentes)
Modelo
RMSE_4w
MAPE_4w
RMSLE_4w
seasonal_mean
91.28455
65.00128
1.0641488
seasonal_naive
96.31014
66.14327
0.9683374
8. Conclusão
A comparação entre Seasonal Mean e Seasonal Naive revela qual estratégia é mais adequada para a série histórica: - Se SNaive for melhor, indica que o passado imediato (ano anterior) é o melhor preditor (série com mudança de regime forte ou tendência evolutiva). - Se Seasonal Mean for melhor, indica que o padrão sazonal é estável ao longo dos anos e a média suaviza ruídos de anos atípicos.
Note
Modelos baseados em média tendem a ser mais conservadores e roubustos a outliers, mas podem falhar em capturar mudanças abruptas de nível (como grandes epidemias recentes que fogem da média histórica).