Uma análise exploratória sobre sazonalidade, picos epidêmicos, distribuição territorial, gravidade dos casos e relação com variáveis meteorológicas.
Foi escolhido o problema da dengue por se tratar de uma questão relevante de saúde pública. As arboviroses representam um desafio constante no Brasil e no mundo.
Este projeto analisa dados históricos de dengue no Brasil integrados a dados meteorológicos. O objetivo é identificar padrões temporais, regiões mais críticas, períodos de pico, indicadores de gravidade e possíveis associações entre clima e aumento de casos.
A análise foi pensada para apoiar governos, secretarias de saúde e equipes de vigilância epidemiológica. Com os resultados, é possível planejar melhor campanhas de prevenção, alocação de recursos, monitoramento de municípios críticos e ações antes dos períodos de maior risco.
A abordagem adotada seguiu quatro etapas principais:
Pergunta principal: como os casos de dengue se comportam no Brasil em termos de tempo, território, gravidade e clima?
| Pacote | Finalidade |
|---|---|
| tidyverse | Importação, transformação e visualização dos dados. |
| lubridate | Conversão e manipulação de datas. |
| scales | Formatação de números, percentuais e eixos. |
| knitr | Geração de tabelas no relatório. |
| geobr (opcional) | Mapa dos estados brasileiros. |
| sf (opcional) | Suporte a dados espaciais usados pelo mapa. |
Foram utilizadas duas fontes principais: a base de notificações de dengue do SUS e os registros meteorológicos do INMET. Como as duas bases possuem muitas colunas, a apresentação abaixo resume os campos mais relevantes para a análise, em vez de listar todas as variáveis brutas.
tp_not, id_agravo, dt_notific, sem_not, nu_ano, sg_uf_not, id_municip, id_regiona, id_unidade, dt_sin_pri, sem_pri, ano_nasc, nu_idade_n, cs_sexo, cs_gestant, cs_raca, cs_escol_n, sg_uf, id_mn_resi, id_rg_resi, id_pais, dt_invest, id_ocupa_n, febre, mialgia, cefaleia, exantema, vomito, nausea, dor_costas, conjuntvit, artrite, artralgia, petequia_n, leucopenia, laco, dor_retro, diabetes, hematolog, hepatopat, renal, hipertensa, acido_pept, auto_imune, dt_chik_s1, dt_chik_s2, dt_prnt, res_chiks1, res_chiks2, resul_prnt, dt_soro, resul_soro, dt_ns1, resul_ns1, dt_viral, resul_vi_n, dt_pcr, resul_pcr_, sorotipo, histopa_n, imunoh_n, hospitaliz, dt_interna, uf, municipio, tpautocto, coufinf, copaisinf, comuninf, classi_fin, criterio, doenca_tra, clinc_chik, evolucao, dt_obito, dt_encerra, alrm_hipot, alrm_plaq, alrm_vom, alrm_sang, alrm_hemat, alrm_abdom, alrm_letar, alrm_hepat, alrm_liq, dt_alrm, grav_pulso, grav_conv, grav_ench, grav_insuf, grav_taqui, grav_extre, grav_hipot, grav_hemat, grav_melen, grav_metro, grav_sang, grav_ast, grav_mioc, grav_consc, grav_orgao, dt_grav, mani_hemor, epistaxe, gengivo, metro, petequias, hematura, sangram, laco_n, plasmatico, evidencia, plaq_menor, con_fhd, complica, tp_sistema, nduplic_n, dt_digita, cs_flxret, flxrecebi, migrado_w
Data, Hora UTC, PRECIPITAÇÃO TOTAL, HORÁRIO (mm), PRESSÃO ATMOSFÉRICA AO NÍVEL DA ESTAÇÃO, HORÁRIA (mB), PRESSÃO ATMOSFÉRICA MÁX. NA HORA ANT. (AUT) (mB), PRESSÃO ATMOSFÉRICA MÍN. NA HORA ANT. (AUT) (mB), RADIAÇÃO GLOBAL (Kj/m²), TEMPERATURA DO AR - BULBO SECO, HORÁRIA (°C), TEMPERATURA DO PONTO DE ORVALHO (°C), TEMPERATURA MÁXIMA NA HORA ANT. (AUT) (°C), TEMPERATURA MÍNIMA NA HORA ANT. (AUT) (°C), TEMPERATURA ORVALHO MÁX. NA HORA ANT. (AUT) (°C), TEMPERATURA ORVALHO MÍN. NA HORA ANT. (AUT) (°C), UMIDADE REL. MÁX. NA HORA ANT. (AUT) (%), UMIDADE REL. MÍN. NA HORA ANT. (AUT) (%), UMIDADE RELATIVA DO AR, HORÁRIA (%), VENTO, DIREÇÃO HORÁRIA (gr) (° (gr)), VENTO, RAJADA MÁXIMA (m/s), VENTO, VELOCIDADE HORÁRIA (m/s)
A base de dengue oferece a dimensão epidemiológica: volume de casos, localização, período e indícios de gravidade. Já a base do INMET oferece a dimensão ambiental: chuva, umidade, temperatura, vento e radiação. A junção dessas duas fontes permite analisar a dengue como um fenômeno temporal, territorial e possivelmente influenciado por condições climáticas.
O processamento foi feito em dois momentos. Primeiro, as bases brutas foram limpas e padronizadas. Depois, foram criados subconjuntos agregados para facilitar a análise e evitar que o relatório ficasse pesado.
Na base de dengue, o foco foi transformar notificações individuais em indicadores por data, UF e município. A partir disso, foram calculados totais de casos, hospitalizações, sinais de alarme, casos graves, óbitos e algumas comorbidades.
Na base do INMET, o foco foi transformar registros horários em informações diárias. Isso exigiu padronizar datas, corrigir números com vírgula decimal, tratar valores ausentes e resumir as variáveis climáticas em medidas úteis para comparação com os casos.
Essa preparação foi importante porque as bases originais possuem níveis diferentes: a dengue vem em notificações epidemiológicas e o clima vem em medições horárias. A análise só se torna possível depois que as duas fontes passam a conversar em uma mesma escala de tempo e território.
O resultado do pré-processamento não foi apenas uma base única, mas também três subconjuntos mais leves: mensal por município, anual por município e semanal geral. Esses subconjuntos foram usados para construir as visualizações do relatório.
Este relatório reúne, em uma única publicação, as duas etapas do processamento dos dados utilizados na atividade.
A primeira etapa constrói uma base diária consolidada, unindo informações epidemiológicas de dengue e dados climáticos. A segunda etapa parte dessa base já processada para gerar tabelas menores e mais leves, voltadas para análises por mês, por ano e por semana.
A separação foi mantida de forma proposital. No desenvolvimento
original, o processo foi dividido em dois scripts para facilitar a
organização, a depuração e a reutilização da base principal. Nesta
versão unificada, os dois blocos aparecem no mesmo documento, mas a
lógica continua separada: primeiro a tabela grande é salva em arquivo
.csv; depois ela é recarregada para gerar os arquivos
derivados.
O fluxo utilizado foi:
dengue_clima_dataset.csv.Essa escolha evita misturar a etapa mais pesada de preparação dos dados com a etapa de visualização. Além disso, caso seja necessário alterar apenas os subconjuntos ou os gráficos, não é preciso revisar toda a lógica de construção da base original.
O código abaixo corresponde à primeira etapa do projeto. Ele lê os arquivos originais, trata problemas de nomes, datas e valores ausentes, junta as bases de dengue e clima e salva a tabela final processada.
pacotes <- c("tidyverse", "lubridate", "stringi", "zoo")
invisible(lapply(pacotes, function(pkg) {
if (!requireNamespace(pkg, quietly = TRUE)) install.packages(pkg)
library(pkg, character.only = TRUE)
}))
source_data_dir <- find_data_dir()
processed_dir <- file.path(source_data_dir, "processed")
dir.create(processed_dir, recursive = TRUE, showWarnings = FALSE)
output_file <- file.path(processed_dir, "dengue_clima_dataset.csv")
limite_percentual_ausente <- 60
# Funções auxiliares
normalize_column_name <- function(col) {
col %>% stringi::stri_trans_general("Latin-ASCII") %>% stringr::str_to_lower() %>%
stringr::str_replace_all("[^a-z0-9]+", "_") %>% stringr::str_replace_all("^_|_$", "")
}
normalize_text <- function(x) {
x %>% as.character() %>% stringi::stri_trans_general("Latin-ASCII") %>%
stringr::str_to_lower() %>% stringr::str_squish()
}
parse_data_flex <- function(x) {
x <- as.character(x)
d1 <- suppressWarnings(lubridate::ymd(x))
d2 <- suppressWarnings(lubridate::dmy(x))
dplyr::coalesce(d1, d2)
}
as_num <- function(x) {
y <- suppressWarnings(as.numeric(stringr::str_replace(as.character(x), ",", ".")))
y[y <= -9990] <- NA_real_
y
}
fill_rolling_mean <- function(x, width = 4, min_obs = 2) {
media_movel <- zoo::rollapplyr(
data = x,
width = width,
FUN = function(v) if (sum(!is.na(v)) >= min_obs) mean(v, na.rm = TRUE) else NA_real_,
partial = TRUE,
fill = NA_real_
)
ifelse(is.na(x), media_movel, x)
}
mean_na <- function(x) if (all(is.na(x))) NA_real_ else mean(x, na.rm = TRUE)
sum_na <- function(x) if (all(is.na(x))) NA_real_ else sum(x, na.rm = TRUE)
pick_col <- function(cols, pattern) {
achada <- cols[stringr::str_detect(cols, stringr::regex(pattern, ignore_case = TRUE))][1]
ifelse(is.na(achada), NA_character_, achada)
}
add_missing_cols <- function(df, cols) {
for (col in cols) if (!col %in% names(df)) df[[col]] <- NA
df
}
has_any_1 <- function(df, prefix) {
cols <- names(df)[stringr::str_detect(names(df), paste0("^", prefix))]
if (length(cols) == 0) return(rep(FALSE, nrow(df)))
rowSums(as.data.frame(lapply(df[cols], function(x) x == 1)), na.rm = TRUE) > 0
}
get_municip_map_df <- function(cod_municipios_unicos) {
municipios <- readr::read_csv(file.path(source_data_dir, "municipios.csv"), show_col_types = FALSE) %>%
select(codigo_ibge, nome, codigo_uf)
estados <- readr::read_csv(file.path(source_data_dir, "estados.csv"), show_col_types = FALSE) %>%
select(codigo_uf, uf)
municipios %>%
left_join(estados, by = "codigo_uf") %>%
mutate(codigo_municipio = codigo_ibge %/% 10) %>%
select(codigo_municipio, nome, uf) %>%
filter(codigo_municipio %in% cod_municipios_unicos)
}
# DENGBR
prep_dengbr <- function(df, df_cod_municip) {
df <- add_missing_cols(df, c("CLASSI_FIN", "EVOLUCAO", "DT_OBITO", "HOSPITALIZ", "DIABETES", "HIPERTENSA"))
df_base <- df %>%
left_join(df_cod_municip, by = c("ID_MUNICIP" = "codigo_municipio")) %>%
mutate(
data = parse_data_flex(DT_NOTIFIC),
uf = stringr::str_to_upper(stringr::str_squish(uf)),
municipio = normalize_text(nome),
caso_confirmado = CLASSI_FIN %in% c(10, 11, 12),
caso_hospitalizado = HOSPITALIZ == 1,
caso_obito = EVOLUCAO == 2 | !is.na(parse_data_flex(DT_OBITO)),
caso_diabetes = DIABETES == 1,
caso_hipertensao = HIPERTENSA == 1
) %>%
drop_na(data, uf, municipio)
df_base$tem_sinal_alarme <- has_any_1(df_base, "ALRM_")
df_base$tem_gravidade <- has_any_1(df_base, "GRAV_")
df_base %>%
mutate(caso_com_sinal_alarme = CLASSI_FIN == 11 | tem_sinal_alarme, caso_grave = CLASSI_FIN == 12 | tem_gravidade) %>%
group_by(data, uf, municipio) %>%
summarise(
casos_dengue = n(),
casos_confirmados = sum(caso_confirmado, na.rm = TRUE),
casos_hospitalizados = sum(caso_hospitalizado, na.rm = TRUE),
casos_obito = sum(caso_obito, na.rm = TRUE),
casos_com_sinais_alarme = sum(caso_com_sinal_alarme, na.rm = TRUE),
casos_graves = sum(caso_grave, na.rm = TRUE),
casos_com_diabetes = sum(caso_diabetes, na.rm = TRUE),
casos_com_hipertensao = sum(caso_hipertensao, na.rm = TRUE),
.groups = "drop"
)
}
# INMET
prep_inmet <- function(df, file_name) {
df <- df %>% select(!matches("^\\.\\.\\.|^X[0-9]+$|^$"))
names(df) <- make.unique(normalize_column_name(names(df)), sep = "_")
mapa <- c(
data = pick_col(names(df), "^data$"),
precipitacao_total = pick_col(names(df), "precipitacao_total"),
pressao_atmosferica = pick_col(names(df), "pressao_atmosferica_ao_nivel_da_estacao"),
radiacao_global = pick_col(names(df), "radiacao_global"),
temperatura_ar = pick_col(names(df), "temperatura_do_ar_bulbo_seco"),
temperatura_orvalho = pick_col(names(df), "temperatura_do_ponto_de_orvalho"),
umidade_relativa = pick_col(names(df), "umidade_relativa_do_ar"),
vento_velocidade = pick_col(names(df), "vento_velocidade_horaria")
)
if (is.na(mapa[["data"]])) stop(paste("Coluna de data não encontrada no arquivo", file_name))
df <- df %>% select(all_of(unique(na.omit(mapa))))
for (nome_final in names(mapa)) {
nome_original <- mapa[[nome_final]]
if (!is.na(nome_original) && nome_original %in% names(df)) df <- df %>% rename(!!nome_final := all_of(nome_original))
}
vars_clima <- c("precipitacao_total", "pressao_atmosferica", "radiacao_global", "temperatura_ar", "temperatura_orvalho", "umidade_relativa", "vento_velocidade")
df <- add_missing_cols(df, vars_clima) %>%
mutate(data = parse_data_flex(data), across(all_of(vars_clima), as_num)) %>%
arrange(data)
percentual_ausente <- df %>% summarise(across(all_of(vars_clima), ~ mean(is.na(.x)) * 100))
cols_preencher <- names(percentual_ausente)[unlist(percentual_ausente) <= limite_percentual_ausente]
df <- df %>% mutate(across(all_of(cols_preencher), fill_rolling_mean))
df_diario <- df %>%
group_by(data) %>%
summarise(
precipitacao_total = sum_na(precipitacao_total),
pressao_atmosferica = mean_na(pressao_atmosferica),
radiacao_global = mean_na(radiacao_global),
temperatura_ar = mean_na(temperatura_ar),
temperatura_orvalho = mean_na(temperatura_orvalho),
umidade_relativa = mean_na(umidade_relativa),
vento_velocidade = mean_na(vento_velocidade),
.groups = "drop"
) %>%
drop_na(data)
partes_nome <- stringr::str_split(file_name, "_", simplify = TRUE)
if (ncol(partes_nome) < 5) stop(paste("Nome do arquivo INMET fora do padrão esperado:", file_name))
uf <- partes_nome[1, 3]
municipio <- partes_nome[1, 5]
df_diario %>%
mutate(uf = stringr::str_to_upper(stringr::str_squish(uf)), municipio = normalize_text(municipio)) %>%
select(data, uf, municipio, everything())
}
cat("Lendo DENGBR...\n")
dengue_files <- file.path(source_data_dir, "DENGBR", c("DENGBR23.csv", "DENGBR24.csv", "DENGBR25.csv"))
cols_dengue <- c(
"DT_NOTIFIC", "ID_MUNICIP", "ID_AGRAVO", "CLASSI_FIN", "EVOLUCAO", "DT_OBITO", "HOSPITALIZ", "DIABETES", "HIPERTENSA",
"ALRM_HIPOT", "ALRM_PLAQ", "ALRM_VOM", "ALRM_SANG", "ALRM_HEMAT", "ALRM_ABDOM", "ALRM_LETAR", "ALRM_HEPAT", "ALRM_LIQ",
"GRAV_PULSO", "GRAV_CONV", "GRAV_ENCH", "GRAV_INSUF", "GRAV_TAQUI", "GRAV_EXTRE", "GRAV_HIPOT", "GRAV_HEMAT",
"GRAV_MELEN", "GRAV_METRO", "GRAV_SANG", "GRAV_AST", "GRAV_MIOC", "GRAV_CONSC", "GRAV_ORGAO"
)
dengue_raw <- purrr::map_dfr(
dengue_files,
~ readr::read_csv(.x, col_select = any_of(cols_dengue), show_col_types = FALSE)
)
cod_municipios_unicos <- unique(dengue_raw$ID_MUNICIP)
df_cod_municip <- get_municip_map_df(cod_municipios_unicos)
df_dengue <- prep_dengbr(dengue_raw, df_cod_municip)
cat("DENGBR processado:\n")
print(dplyr::glimpse(df_dengue))
cat("Lendo INMET...\n")
inmet_files <- list.files(
file.path(source_data_dir, "INMET"),
pattern = "\\.csv$",
recursive = TRUE,
full.names = TRUE,
ignore.case = TRUE
)
if (length(inmet_files) == 0) stop("Nenhum arquivo CSV do INMET foi encontrado em dados/INMET.")
df_inmet <- purrr::map_dfr(seq_along(inmet_files), function(i) {
path <- inmet_files[i]
arquivo <- tools::file_path_sans_ext(basename(path))
cat("Processando INMET", i, "de", length(inmet_files), "-", arquivo, "\n")
suppressMessages(
readr::read_delim(
path,
delim = ";",
skip = 8,
locale = readr::locale(encoding = "Latin1"),
show_col_types = FALSE,
trim_ws = TRUE,
name_repair = "unique_quiet",
progress = FALSE
)
) %>% prep_inmet(file_name = arquivo)
}) %>% arrange(uf, municipio, data)
cat("INMET processado:\n")
print(dplyr::glimpse(df_inmet))
cat("Criando tabela final...\n")
df_inmet_diario <- df_inmet %>%
group_by(uf, municipio, data) %>%
summarise(across(where(is.numeric), mean_na), .groups = "drop")
df_final <- df_inmet_diario %>%
left_join(df_dengue, by = c("data", "uf", "municipio")) %>%
mutate(
across(starts_with("casos_"), ~ tidyr::replace_na(.x, 0)),
ano = lubridate::year(data),
mes = lubridate::month(data),
dia = lubridate::day(data),
dia_da_semana = lubridate::wday(data, label = TRUE, abbr = FALSE)
) %>%
arrange(uf, municipio, data)
readr::write_csv(df_final, output_file)
cat("\nTabela final salva em:", output_file, "\n")
cat("Linhas:", nrow(df_final), "\n")
cat("Colunas:", ncol(df_final), "\n")
resumo_base <- tibble::tibble(
indicador = c("linhas", "colunas", "ufs", "municipios", "data_inicial", "data_final", "total_casos_dengue", "total_hospitalizados", "total_obitos", "total_casos_graves", "linhas_duplicadas_por_chave"),
valor = c(
nrow(df_final),
ncol(df_final),
n_distinct(df_final$uf),
n_distinct(paste(df_final$uf, df_final$municipio)),
as.character(min(df_final$data, na.rm = TRUE)),
as.character(max(df_final$data, na.rm = TRUE)),
sum(df_final$casos_dengue, na.rm = TRUE),
sum(df_final$casos_hospitalizados, na.rm = TRUE),
sum(df_final$casos_obito, na.rm = TRUE),
sum(df_final$casos_graves, na.rm = TRUE),
df_final %>% count(data, uf, municipio) %>% filter(n > 1) %>% nrow()
)
)
cat("\nResumo da base:\n")
print(resumo_base)
faltantes_final <- df_final %>%
summarise(across(everything(), ~ mean(is.na(.x)) * 100)) %>%
pivot_longer(everything(), names_to = "coluna", values_to = "percentual_faltante") %>%
arrange(desc(percentual_faltante))
cat("\nPercentual de faltantes por coluna:\n")
print(faltantes_final)
cat("\nProcessamento finalizado.\n")
A base consolidada foi salva em arquivo .csv para
funcionar como um ponto intermediário do processo.
Mesmo estando tudo em uma única publicação, essa leitura posterior foi mantida para deixar claro que, originalmente, a implementação foi organizada em duas etapas independentes. Assim, a segunda parte do código não depende diretamente dos objetos criados em memória na primeira parte; ela depende do arquivo processado que foi gerado.
O código abaixo lê a base diária
dengue_clima_dataset.csv e gera três tabelas derivadas:
dengue_mensal_municipio.csv: casos de dengue agregados
por município e mês.dengue_anual_municipio.csv: casos de dengue agregados
por município e ano.geral_semanal_completo.csv: visão semanal geral com
variáveis de dengue e clima.pacotes <- c("tidyverse", "lubridate")
invisible(lapply(pacotes, function(pkg) {
if (!requireNamespace(pkg, quietly = TRUE)) install.packages(pkg)
library(pkg, character.only = TRUE)
}))
# -----------------------------------------------------------------------------
# Script: create_dengue_subsets.R
# Objetivo:
# Ler a base diaria dengue_clima_dataset.csv e criar apenas 3 subsets leves:
# 1) dengue_mensal_municipio.csv -> dengue por municipio e mes
# 2) dengue_anual_municipio.csv -> dengue por municipio e ano
# 3) geral_semanal_completo.csv -> visao semanal geral com dengue + clima
# -----------------------------------------------------------------------------
# O diretório de dados é localizado automaticamente.
source_data_dir <- find_data_dir()
processed_dir <- file.path(source_data_dir, "processed")
if (!dir.exists(processed_dir)) {
stop("Nao encontrei a pasta processed. Execute a etapa anterior ou ajuste os diretorios.")
}
input_file <- file.path(processed_dir, "dengue_clima_dataset.csv")
subsets_dir <- file.path(processed_dir, "subsets")
dir.create(subsets_dir, recursive = TRUE, showWarnings = FALSE)
if (!file.exists(input_file)) {
stop("Arquivo dengue_clima_dataset.csv nao encontrado em dados/processed.")
}
# Funcoes auxiliares para evitar NaN quando uma coluna esta toda vazia.
safe_sum <- function(x) {
if (all(is.na(x))) NA_real_ else sum(x, na.rm = TRUE)
}
safe_mean <- function(x) {
if (all(is.na(x))) NA_real_ else mean(x, na.rm = TRUE)
}
safe_max <- function(x) {
if (all(is.na(x))) NA_real_ else max(x, na.rm = TRUE)
}
cat("Lendo dataset processado...\n")
## Lendo dataset processado...
df_base <- readr::read_csv(input_file, show_col_types = FALSE) %>%
mutate(
data = as.Date(data),
ano = lubridate::year(data),
mes = lubridate::month(data),
mes_inicio = lubridate::floor_date(data, unit = "month"),
semana_inicio = lubridate::floor_date(data, unit = "week", week_start = 1),
ano_semana = lubridate::isoyear(data),
semana = lubridate::isoweek(data),
casos_dengue = as.numeric(casos_dengue)
)
# Somente colunas de dengue.
# Pelo dataset atual, essas colunas seguem o padrao casos_*.
dengue_cols <- names(df_base)[stringr::str_detect(names(df_base), "^casos_")]
if (length(dengue_cols) == 0) {
stop("Nenhuma coluna de dengue encontrada. Esperava colunas iniciando com 'casos_'.")
}
# Colunas climaticas esperadas no dataset final.
clima_cols <- intersect(
c(
"precipitacao_total",
"pressao_atmosferica",
"radiacao_global",
"temperatura_ar",
"temperatura_orvalho",
"umidade_relativa",
"vento_velocidade"
),
names(df_base)
)
clima_media_cols <- setdiff(clima_cols, "precipitacao_total")
cat("Colunas de dengue encontradas:\n")
## Colunas de dengue encontradas:
print(dengue_cols)
## [1] "casos_dengue" "casos_confirmados"
## [3] "casos_hospitalizados" "casos_obito"
## [5] "casos_com_sinais_alarme" "casos_graves"
## [7] "casos_com_diabetes" "casos_com_hipertensao"
cat("Colunas climaticas encontradas:\n")
## Colunas climaticas encontradas:
print(clima_cols)
## [1] "precipitacao_total" "pressao_atmosferica" "radiacao_global"
## [4] "temperatura_ar" "temperatura_orvalho" "umidade_relativa"
## [7] "vento_velocidade"
# -----------------------------------------------------------------------------
# 1. Subset mensal orientado a municipio - somente informacoes de dengue
# -----------------------------------------------------------------------------
dengue_mensal_municipio <- df_base %>%
group_by(uf, municipio, ano, mes, mes_inicio) %>%
summarise(
dias_monitorados = n_distinct(data),
dias_com_casos = sum(casos_dengue > 0, na.rm = TRUE),
media_diaria_casos_dengue = safe_mean(casos_dengue),
pico_diario_casos_dengue = safe_max(casos_dengue),
across(all_of(dengue_cols), safe_sum),
.groups = "drop"
) %>%
arrange(uf, municipio, ano, mes)
readr::write_csv(
dengue_mensal_municipio,
file.path(subsets_dir, "dengue_mensal_municipio.csv")
)
# -----------------------------------------------------------------------------
# 2. Subset anual orientado a municipio - somente informacoes de dengue
# -----------------------------------------------------------------------------
dengue_anual_municipio <- df_base %>%
group_by(uf, municipio, ano) %>%
summarise(
dias_monitorados = n_distinct(data),
dias_com_casos = sum(casos_dengue > 0, na.rm = TRUE),
meses_monitorados = n_distinct(mes_inicio),
media_diaria_casos_dengue = safe_mean(casos_dengue),
pico_diario_casos_dengue = safe_max(casos_dengue),
across(all_of(dengue_cols), safe_sum),
.groups = "drop"
) %>%
arrange(uf, municipio, ano)
readr::write_csv(
dengue_anual_municipio,
file.path(subsets_dir, "dengue_anual_municipio.csv")
)
# -----------------------------------------------------------------------------
# 3. Subset semanal geral completo - dengue + clima
#
# Regra usada:
# - Colunas casos_*: soma semanal geral.
# - precipitacao_total: primeiro soma por municipio/semana, depois tira a media
# municipal no agregado geral, para evitar um total nacional artificial gigante.
# - Demais variaveis climaticas: media semanal geral.
# -----------------------------------------------------------------------------
base_semanal_municipio_temp <- df_base %>%
group_by(uf, municipio, semana_inicio, ano_semana, semana) %>%
summarise(
dias_monitorados = n_distinct(data),
across(all_of(dengue_cols), safe_sum),
across(any_of("precipitacao_total"), safe_sum),
across(all_of(clima_media_cols), safe_mean),
.groups = "drop"
)
geral_semanal_completo <- base_semanal_municipio_temp %>%
group_by(semana_inicio, ano_semana, semana) %>%
summarise(
ufs_monitoradas = n_distinct(uf),
municipios_monitorados = n_distinct(paste(uf, municipio)),
dias_municipio_monitorados = sum(dias_monitorados, na.rm = TRUE),
across(all_of(dengue_cols), safe_sum),
across(
any_of("precipitacao_total"),
safe_mean,
.names = "{.col}_media_municipal"
),
across(
all_of(clima_media_cols),
safe_mean,
.names = "{.col}_media"
),
.groups = "drop"
) %>%
arrange(semana_inicio)
readr::write_csv(
geral_semanal_completo,
file.path(subsets_dir, "geral_semanal_completo.csv")
)
cat("\nSubsets salvos em:", subsets_dir, "\n")
##
## Subsets salvos em: C:/Users/pedro/OneDrive/Documentos/R_files/dados/processed/subsets
cat("Arquivos gerados:\n")
## Arquivos gerados:
cat("- dengue_mensal_municipio.csv:", nrow(dengue_mensal_municipio), "linhas\n")
## - dengue_mensal_municipio.csv: 20473 linhas
cat("- dengue_anual_municipio.csv:", nrow(dengue_anual_municipio), "linhas\n")
## - dengue_anual_municipio.csv: 1726 linhas
cat("- geral_semanal_completo.csv:", nrow(geral_semanal_completo), "linhas\n")
## - geral_semanal_completo.csv: 158 linhas
cat("\nProcessamento finalizado.\n")
##
## Processamento finalizado.
A solução foi organizada dessa forma para manter o código mais fácil de entender e revisar. A primeira parte concentra a limpeza, padronização e integração das bases. A segunda parte utiliza a base já tratada para gerar tabelas menores, mais adequadas para visualizações e análises específicas.
find_processed_dir <- function() {
wd <- getwd()
candidates <- c(
file.path(wd, "R_files", "dados", "processed"),
file.path(wd, "dados", "processed"),
wd,
file.path(dirname(wd), "dados", "processed"),
file.path(dirname(wd), "R_files", "dados", "processed")
)
candidates <- unique(normalizePath(candidates, winslash = "/", mustWork = FALSE))
for (candidate in candidates) {
subset_candidate <- file.path(candidate, "subsets")
has_subsets <- all(file.exists(c(
file.path(subset_candidate, "dengue_mensal_municipio.csv"),
file.path(subset_candidate, "dengue_anual_municipio.csv"),
file.path(subset_candidate, "geral_semanal_completo.csv")
)))
if (has_subsets) return(candidate)
}
stop(
paste0(
"Não encontrei a pasta de dados processados.\n\n",
"Diretório atual: ", wd, "\n\n",
"Verifique se existe: R_files/dados/processed/subsets"
)
)
}
base_dir <- find_processed_dir()
subset_dir <- file.path(base_dir, "subsets")
path_diaria <- file.path(base_dir, "dengue_clima_dataset.csv")
path_mensal <- file.path(subset_dir, "dengue_mensal_municipio.csv")
path_anual <- file.path(subset_dir, "dengue_anual_municipio.csv")
path_semanal <- file.path(subset_dir, "geral_semanal_completo.csv")
df_mensal <- readr::read_csv(path_mensal, show_col_types = FALSE) %>%
mutate(mes_inicio = as.Date(mes_inicio))
df_anual <- readr::read_csv(path_anual, show_col_types = FALSE) %>%
mutate(ano = as.integer(ano))
df_semanal <- readr::read_csv(path_semanal, show_col_types = FALSE) %>%
mutate(semana_inicio = as.Date(semana_inicio))
if (file.exists(path_diaria)) {
df_diaria <- readr::read_csv(path_diaria, show_col_types = FALSE) %>%
mutate(data = as.Date(data))
} else {
df_diaria <- NULL
}
# Garante que as funções usadas no dashboard tenham o comportamento esperado.
safe_sum <- function(x) {
if (all(is.na(x))) 0 else sum(x, na.rm = TRUE)
}
safe_mean <- function(x) {
if (all(is.na(x))) NA_real_ else mean(x, na.rm = TRUE)
}
A estrutura final foi organizada em três subconjuntos principais. Cada um responde a um tipo de pergunta diferente.
O subconjunto mensal por município ajuda a observar sazonalidade, picos e evolução ao longo do tempo. O subconjunto anual por município facilita comparações territoriais e indicadores de gravidade. Já o subconjunto semanal geral foi usado principalmente na análise climática, porque permite comparar casos e variáveis meteorológicas em uma frequência mais próxima.
resumo_bases <- tibble::tribble(
~Base, ~Linhas, ~Colunas, ~Nível,
"Mensal por município", nrow(df_mensal), ncol(df_mensal), "UF, município, ano e mês",
"Anual por município", nrow(df_anual), ncol(df_anual), "UF, município e ano",
"Semanal geral", nrow(df_semanal), ncol(df_semanal), "Ano e semana"
)
knitr::kable(resumo_bases)
| Base | Linhas | Colunas | Nível |
|---|---|---|---|
| Mensal por município | 20473 | 17 | UF, município, ano e mês |
| Anual por município | 1726 | 16 | UF, município e ano |
| Semanal geral | 158 | 21 | Ano e semana |
As principais variáveis analisadas foram selecionadas de acordo com o problema de saúde pública abordado. A tabela abaixo descreve o papel de cada uma.
| Variável | Interpretação |
|---|---|
| casos_dengue | Total de notificações de dengue. |
| casos_hospitalizados | Casos que exigiram hospitalização. |
| casos_com_sinais_alarme | Casos com sinais clínicos de alerta. |
| casos_graves | Casos classificados como graves. |
| casos_obito | Casos que evoluíram para óbito. |
| precipitacao_total_media_municipal | Volume de chuva. |
| temperatura_ar_media | Temperatura média do ar. |
| umidade_relativa_media | Umidade relativa média. |
| vento_velocidade_media | Velocidade média do vento. |
A base final possui mais de dois milhões de casos agregados no período analisado. Esse volume dá força para observar padrões gerais, como sazonalidade, picos e concentração territorial. Ainda assim, quantidade de dados não elimina problemas de qualidade.
Os dados representam notificações registradas. Portanto, eles dependem da capacidade de cada município de identificar, registrar e atualizar os casos. Municípios com melhor estrutura de vigilância podem aparecer com mais casos não apenas porque têm mais dengue, mas também porque notificam melhor.
Também é necessário cuidado com os campos clínicos. Hospitalização, sinais de alarme, classificação grave e óbito podem ter preenchimento desigual. Por isso, as taxas de gravidade devem ser lidas como indicadores exploratórios, não como uma medida perfeita do risco real.
Interpretação: o relatório usa os dados para identificar tendências e levantar hipóteses. Os resultados ajudam a direcionar perguntas, mas não substituem uma investigação epidemiológica completa.
Antes de construir os gráficos, foram criadas algumas bases resumidas. Essas transformações geram novas informações a partir dos dados originais e são usadas nas análises seguintes.
# Evolução mensal geral
evolucao_mensal <- df_mensal %>%
group_by(ano, mes) %>%
summarise(
casos_dengue = safe_sum(casos_dengue),
casos_hospitalizados = safe_sum(casos_hospitalizados),
casos_com_sinais_alarme = safe_sum(casos_com_sinais_alarme),
casos_graves = safe_sum(casos_graves),
casos_obito = safe_sum(casos_obito),
.groups = "drop"
) %>%
mutate(
mes_nome = lubridate::month(mes, label = TRUE, abbr = TRUE),
periodo = paste0(stringr::str_pad(mes, 2, pad = "0"), "/", ano),
data_mes = as.Date(paste(ano, mes, "01", sep = "-"))
) %>%
arrange(data_mes)
media_mensal_casos <- mean(evolucao_mensal$casos_dengue, na.rm = TRUE)
# Sazonalidade média
sazonalidade_media <- evolucao_mensal %>%
group_by(mes, mes_nome) %>%
summarise(media_casos = safe_mean(casos_dengue), .groups = "drop")
# Métricas simples de força da sazonalidade
total_casos_periodo <- sum(evolucao_mensal$casos_dengue, na.rm = TRUE)
casos_fev_maio <- evolucao_mensal %>%
filter(mes %in% 2:5) %>%
summarise(total = safe_sum(casos_dengue)) %>%
pull(total)
mes_maior_media <- sazonalidade_media %>%
arrange(desc(media_casos)) %>%
slice_head(n = 1)
modelo_sazonal <- lm(log1p(casos_dengue) ~ factor(mes), data = evolucao_mensal)
metricas_sazonalidade <- tibble::tibble(
Métrica = c(
"Concentração entre fevereiro e maio",
"Mês com maior média",
"Maior média mensal / média geral",
"Coeficiente de variação mensal",
"R² do modelo usando mês"
),
Valor = c(
fmt_pct(casos_fev_maio / total_casos_periodo),
as.character(mes_maior_media$mes_nome),
round(max(sazonalidade_media$media_casos, na.rm = TRUE) / mean(sazonalidade_media$media_casos, na.rm = TRUE), 2),
round(sd(sazonalidade_media$media_casos, na.rm = TRUE) / mean(sazonalidade_media$media_casos, na.rm = TRUE), 2),
round(summary(modelo_sazonal)$r.squared, 3)
)
)
# Indicadores gerais
kpis <- df_anual %>%
summarise(
casos = safe_sum(casos_dengue),
hospitalizacoes = safe_sum(casos_hospitalizados),
sinais = safe_sum(casos_com_sinais_alarme),
graves = safe_sum(casos_graves),
obitos = safe_sum(casos_obito),
municipios = n_distinct(paste(uf, municipio)),
ufs = n_distinct(uf),
.groups = "drop"
) %>%
mutate(
taxa_hospitalizacao = hospitalizacoes / casos,
taxa_sinais = sinais / casos,
taxa_graves = graves / casos,
taxa_obitos = obitos / casos
)
# Maiores picos
picos_mensais <- evolucao_mensal %>%
mutate(
vezes_media = casos_dengue / media_mensal_casos,
taxa_obitos = ifelse(casos_dengue > 0, casos_obito / casos_dengue, NA_real_),
taxa_hospitalizacao = ifelse(casos_dengue > 0, casos_hospitalizados / casos_dengue, NA_real_)
) %>%
arrange(desc(casos_dengue))
maior_pico <- picos_mensais %>% slice_head(n = 1)
# Território
territorio_municipio <- df_anual %>%
group_by(uf, municipio) %>%
summarise(
casos_dengue = safe_sum(casos_dengue),
hospitalizacoes = safe_sum(casos_hospitalizados),
sinais = safe_sum(casos_com_sinais_alarme),
graves = safe_sum(casos_graves),
obitos = safe_sum(casos_obito),
.groups = "drop"
) %>%
mutate(participacao = casos_dengue / sum(casos_dengue, na.rm = TRUE)) %>%
arrange(desc(casos_dengue))
territorio_uf <- df_anual %>%
group_by(uf) %>%
summarise(
casos_dengue = safe_sum(casos_dengue),
hospitalizacoes = safe_sum(casos_hospitalizados),
sinais = safe_sum(casos_com_sinais_alarme),
graves = safe_sum(casos_graves),
obitos = safe_sum(casos_obito),
.groups = "drop"
) %>%
mutate(participacao = casos_dengue / sum(casos_dengue, na.rm = TRUE)) %>%
arrange(desc(casos_dengue))
top_municipio <- territorio_municipio %>% slice_head(n = 1)
top_uf <- territorio_uf %>% slice_head(n = 1)
# Gravidade por ano
gravidade_anual <- df_anual %>%
group_by(ano) %>%
summarise(
municipios = n_distinct(paste(uf, municipio)),
casos = safe_sum(casos_dengue),
hospitalizacoes = safe_sum(casos_hospitalizados),
sinais = safe_sum(casos_com_sinais_alarme),
graves = safe_sum(casos_graves),
obitos = safe_sum(casos_obito),
.groups = "drop"
) %>%
mutate(
taxa_hospitalizacao = hospitalizacoes / casos,
taxa_sinais = sinais / casos,
taxa_graves = graves / casos,
taxa_obitos = obitos / casos
)
# Correlação climática
clima_cols_presentes <- intersect(names(clima_labels), names(df_semanal))
if (length(clima_cols_presentes) > 0) {
correlacoes_clima <- purrr::map_dfr(clima_cols_presentes, function(col_clima) {
tibble::tibble(
variavel = col_clima,
nome = clima_labels[[col_clima]],
correlacao = safe_cor(df_semanal[[col_clima]], df_semanal$casos_dengue)
)
}) %>%
arrange(desc(abs(correlacao)))
} else {
correlacoes_clima <- tibble::tibble(
variavel = character(),
nome = character(),
correlacao = numeric()
)
}
# Comparação entre clima, tempo linear e sazonalidade mensal
df_semanal_tempo <- df_semanal %>%
arrange(semana_inicio) %>%
mutate(
tempo_linear = row_number(),
mes = lubridate::month(semana_inicio)
) %>%
left_join(
sazonalidade_media %>%
select(mes, sazonalidade_media_mes = media_casos),
by = "mes"
)
correlacoes_tempo_sazonalidade <- tibble::tibble(
variavel = c("tempo_linear", "sazonalidade_media_mes"),
nome = c("Tempo linear", "Sazonalidade média do mês"),
correlacao = c(
safe_cor(df_semanal_tempo$tempo_linear, df_semanal_tempo$casos_dengue),
safe_cor(df_semanal_tempo$sazonalidade_media_mes, df_semanal_tempo$casos_dengue)
)
)
correlacoes_comparativas <- bind_rows(
correlacoes_clima,
correlacoes_tempo_sazonalidade
) %>%
arrange(desc(abs(correlacao)))
A visão geral funciona como uma fotografia inicial do problema. Ela mostra o tamanho da base analisada e ajuda a dimensionar a diferença entre casos totais, hospitalizações, sinais de alarme, casos graves e óbitos.
A série mensal mostra que a dengue não se distribui de forma uniforme ao longo do tempo. Existem períodos de baixa ocorrência e períodos de aceleração intensa, formando picos bem definidos. Esse comportamento reforça que a análise temporal é essencial: mais do que saber o total de casos, é importante identificar quando o sistema de saúde pode ser mais pressionado.
A análise de sazonalidade mostra o comportamento médio dos casos ao longo dos meses. O padrão mais evidente é a concentração entre fevereiro e maio, período em que os casos sobem de forma consistente em relação ao restante do ano.
Ao olhar os maiores picos mensais, o destaque fica concentrado em 2024, principalmente entre fevereiro, março, abril e maio. Isso sugere que o ano de 2024 não foi apenas mais alto no total, mas teve um surto concentrado justamente dentro da janela sazonal mais favorável.
A tabela abaixo ajuda a contextualizar esses meses de pico.
| Período | Casos | Vezes a média | Hospitalizações | Sinais de alarme | Graves | Óbitos | Tx. hospitalização | Tx. óbitos |
|---|---|---|---|---|---|---|---|---|
| 03/2024 | 375.193 | 5.4 | 12.150 | 9.259 | 722 | 554 | 3,24% | 0,15% |
| 04/2024 | 340.520 | 4.9 | 11.758 | 7.911 | 840 | 493 | 3,45% | 0,14% |
| 02/2024 | 268.758 | 3.9 | 9.484 | 8.064 | 446 | 345 | 3,53% | 0,13% |
| 05/2024 | 217.439 | 3.1 | 8.902 | 5.627 | 499 | 375 | 4,09% | 0,17% |
| 04/2023 | 145.751 | 2.1 | 4.095 | 2.001 | 226 | 169 | 2,81% | 0,12% |
| 03/2023 | 129.459 | 1.9 | 4.044 | 1.965 | 164 | 134 | 3,12% | 0,10% |
| 05/2023 | 107.853 | 1.6 | 3.445 | 1.794 | 144 | 121 | 3,19% | 0,11% |
| 01/2024 | 105.966 | 1.5 | 4.702 | 2.921 | 163 | 144 | 4,44% | 0,14% |
| 03/2025 | 80.487 | 1.2 | 4.198 | 2.194 | 209 | 153 | 5,22% | 0,19% |
| 06/2024 | 76.028 | 1.1 | 3.533 | 2.052 | 142 | 120 | 4,65% | 0,16% |
A análise territorial mostra onde os casos se concentram. Essa etapa é importante porque a dengue não se espalha de maneira homogênea pelo país: alguns estados e municípios concentram uma parcela muito maior das notificações.
| UF | Município | Casos | Participação | Hospitalizações | Óbitos |
|---|---|---|---|---|---|
| DF | Brasilia | 331.968 | 13,30% | 15.760 | 556 |
| GO | Goiania | 116.953 | 4,69% | 5.651 | 208 |
| MG | Uberlandia | 82.358 | 3,30% | 4.609 | 95 |
| SP | Presidente Prudente | 71.996 | 2,88% | 1.120 | 76 |
| SC | Florianopolis | 65.258 | 2,61% | 1.297 | 55 |
| PR | Foz Do Iguacu | 60.842 | 2,44% | 2.488 | 52 |
| SC | Itajai | 49.689 | 1,99% | 338 | 52 |
| ES | Vitoria | 48.469 | 1,94% | 1.764 | 44 |
| SP | Bauru | 48.318 | 1,94% | 1.326 | 55 |
| BA | Vitoria Da Conquista | 40.424 | 1,62% | 2.054 | 65 |
A tabela mostra que poucos municípios concentram uma parcela importante dos casos. Brasília aparece com grande destaque, seguida por capitais e municípios de estados como Goiás, Minas Gerais, São Paulo, Santa Catarina e Paraná.
Esse resultado pode indicar regiões prioritárias para ações de controle, mas precisa ser interpretado com cautela. A concentração em Brasília, por exemplo, pode refletir tanto alta incidência quanto a forma como o Distrito Federal organiza e registra seus dados. Assim, o território aponta onde olhar primeiro, mas não explica sozinho por que esses locais aparecem no topo.
A tabela de gravidade resume a evolução anual dos principais indicadores clínicos.
| Ano | Municípios | Casos | Hospitalizações | Sinais de alarme | Graves | Óbitos | Tx. hospitalização | Tx. sinais | Tx. graves | Tx. óbitos |
|---|---|---|---|---|---|---|---|---|---|---|
| 2023 | 567 | 611.852 | 22.589 | 11.079 | 962 | 733 | 3,69% | 1,81% | 0,16% | 0,12% |
| 2024 | 565 | 1.487.980 | 56.466 | 38.470 | 3.053 | 2.210 | 3,79% | 2,59% | 0,21% | 0,15% |
| 2025 | 594 | 396.086 | 22.199 | 10.643 | 948 | 697 | 5,60% | 2,69% | 0,24% | 0,18% |
A tabela anual indica que o volume de casos e os indicadores de gravidade não crescem sempre na mesma proporção.
Essa diferença pode estar ligada a vários fatores: perfil da população, acesso ao atendimento, qualidade da notificação, presença de comorbidades, estrutura da rede de saúde ou diferenças regionais. A análise seguinte busca justamente separar essas duas leituras: onde há mais dengue e onde a dengue parece evoluir com maior frequência para situações graves.
Para tornar essa comparação mais justa, foram considerados apenas municípios com pelo menos 1.000 casos acumulados. Esse corte reduz distorções causadas por municípios com poucos registros, onde pequenas variações poderiam gerar taxas artificialmente altas.
Também foi criado um índice exploratório de agravamento, que resume esses indicadores em uma medida comparativa.
| UF | Município | Casos | Índice agrav. | Tx. hosp. | Tx. sinais | Tx. graves | Tx. óbitos |
|---|---|---|---|---|---|---|---|
| MS | Dourados | 2.543 | 33,82% | 22,41% | 9,59% | 0,98% | 0,83% |
| MA | Sao Luis | 5.486 | 31,22% | 23,53% | 6,18% | 1,06% | 0,46% |
| MT | Pontes E Lacerda | 3.299 | 30,71% | 23,89% | 5,85% | 0,76% | 0,21% |
| MA | Imperatriz | 1.089 | 28,65% | 17,45% | 10,56% | 0,46% | 0,18% |
| SE | Aracaju | 2.719 | 27,47% | 15,41% | 10,81% | 0,63% | 0,63% |
| GO | Itumbiara | 1.615 | 24,77% | 11,58% | 12,63% | 0,25% | 0,31% |
| PR | Icaraima | 1.296 | 24,31% | 13,35% | 10,96% | 0,00% | 0,00% |
| RJ | Niteroi | 3.008 | 23,94% | 16,16% | 6,85% | 0,70% | 0,23% |
| GO | Itapaci | 1.279 | 23,69% | 20,95% | 2,50% | 0,08% | 0,16% |
| MT | Sinop | 2.753 | 21,58% | 16,31% | 4,32% | 0,65% | 0,29% |
Esse resultado sugere que a vigilância não deve olhar apenas para os maiores volumes de notificações. Municípios com menor quantidade absoluta de casos podem indicar maior vulnerabilidade clínica, maior pressão sobre o sistema de saúde ou diferenças na forma como os casos graves são registrados. Por isso, esses locais podem ser relevantes para investigações mais específicas.
A análise climática compara a evolução semanal dos casos com variáveis meteorológicas agregadas. O objetivo é observar associações exploratórias, não estabelecer causalidade.
A leitura climática deve ser feita com cuidado. A correlação mostra associação no mesmo período, mas a dengue pode responder ao clima com atraso. Chuva, umidade e temperatura podem influenciar o ciclo do mosquito antes que o aumento apareça nas notificações.
Mesmo com essa limitação, a análise é útil para levantar hipóteses. Variáveis como umidade, precipitação, radiação global e velocidade do vento podem ajudar a explicar parte da variação observada, mas a evidência mais forte do relatório continua sendo a sazonalidade: os casos se concentram de forma clara em uma janela específica do ano.
Este projeto analisou a dengue como um problema de saúde pública que varia no tempo, no território e na gravidade dos casos. Para isso, foram integrados dados epidemiológicos do SUS com variáveis meteorológicas do INMET, permitindo observar não apenas o total de casos, mas também quando eles aumentam, onde se concentram e quais fatores podem ajudar a levantar hipóteses sobre esse comportamento.
O achado mais evidente é a sazonalidade. Os casos se concentram com mais força nos primeiros meses do ano, especialmente entre fevereiro e maio. Esse padrão sugere que ações preventivas não deveriam começar apenas quando os casos já estão altos. Campanhas de conscientização, eliminação de focos, organização da rede de atendimento e comunicação com a população poderiam ser planejadas alguns meses antes dessa janela crítica, para reduzir o impacto dos picos.
O maior surto observado ocorreu em 2024, principalmente entre março e abril. A análise mostra claramente o pico, mas ainda não explica por que aquele ano foi tão diferente dos demais. Uma investigação mais completa poderia verificar se houve condições climáticas mais favoráveis, maior circulação viral, mudanças na notificação, diferenças regionais ou algum fator social específico. Assim, o resultado não fecha uma resposta, mas aponta uma pergunta importante: o que tornou 2024 um ano tão mais intenso?
A distribuição territorial também trouxe um ponto de atenção. Os casos aparecem muito concentrados em poucos municípios, com destaque para Brasília. Esse resultado pode indicar maior incidência real, mas também pode refletir diferenças na forma de registrar e organizar os dados. Como Brasília representa o Distrito Federal de maneira concentrada, ela pode aparecer com um volume muito maior do que municípios de outros estados. Por isso, essa informação é útil para priorizar investigações, mas precisa ser lida com cautela antes de concluir que o risco real é necessariamente maior ali.
Na análise de gravidade, o ponto mais interessante é que volume de casos e agravamento proporcional não são a mesma coisa. Alguns municípios com menor número absoluto de casos apresentaram índice de agravamento acima de 20%, enquanto locais com muitos registros podem ter taxas proporcionais menores. Isso sugere que a vigilância deve olhar para dois recortes diferentes: onde há mais transmissão e onde os casos parecem evoluir com mais frequência para hospitalização, sinais de alarme, casos graves ou óbito. Essa diferença pode indicar vulnerabilidade da população, dificuldade de acesso ao atendimento, diferenças de registro ou maior pressão sobre a rede de saúde.
A parte climática foi útil como análise exploratória, mas não deve ser tratada como explicação definitiva. Variáveis como umidade relativa, precipitação, radiação global e velocidade do vento apresentaram algum nível de associação com os casos semanais, mas a relação não foi forte o suficiente para explicar sozinha o comportamento da dengue. Ainda assim, essas variáveis podem ser úteis em análises preditivas, principalmente se forem combinadas com a sazonalidade e testadas com defasagens, já que o efeito do clima pode aparecer nas notificações algumas semanas depois.
As principais limitações estão relacionadas à confiança e à completude dos dados. A base depende da qualidade da notificação nos municípios, e isso pode gerar subnotificação ou concentração artificial em locais com melhor estrutura de vigilância. Além disso, campos clínicos como hospitalização, sinais de alarme, casos graves e óbitos podem não ter o mesmo padrão de preenchimento em todo o país. A análise climática também foi limitada por usar correlação simples, sem modelar causalidade nem defasagens temporais.
Como próximos passos, a análise poderia buscar responder às perguntas que surgiram a partir dos próprios resultados. Seria importante investigar por que 2024 foi tão intenso, por que Brasília aparece com volume tão alto, quais características explicam municípios com alto agravamento relativo e se fatores socioeconômicos, saneamento, densidade populacional ou acesso à saúde ajudam a explicar essas diferenças. Também seria útil incluir população municipal para calcular incidência, e não apenas volume bruto de casos, além de testar modelos com clima observado duas, quatro ou seis semanas antes dos casos.
De forma geral, o trabalho mostra que a dengue não deve ser acompanhada apenas pelo total acumulado de notificações. A combinação entre sazonalidade, território, gravidade e clima ajuda a transformar os dados em uma ferramenta de vigilância. Mesmo com limitações, a análise permite levantar hipóteses relevantes e apoiar decisões mais direcionadas, como antecipar campanhas antes dos meses críticos, priorizar regiões com maior concentração de casos e investigar municípios com maior proporção de agravamentos.