1 Introdução

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 para o RPubs, 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.

2 Organização do processo

O fluxo utilizado foi:

  1. Ler e tratar os dados brutos de dengue e clima.
  2. Padronizar nomes de colunas, datas, municípios e valores numéricos.
  3. Corrigir valores ausentes quando possível.
  4. Agregar os registros em uma base diária única.
  5. Salvar a base grande em dengue_clima_dataset.csv.
  6. Recarregar essa base processada.
  7. Criar os subconjuntos mensal, anual e semanal.

Essa escolha evita misturar a etapa mais pesada de preparação dos dados com a etapa de análise/agregação. Além disso, caso seja necessário alterar apenas os subconjuntos, não é preciso reconstruir toda a base original novamente.

3 Etapa 1: criação da base diária consolidada

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 <- file.path(getwd(), "dados")
if (!dir.exists(source_data_dir)) {
  stop("Não encontrei a pasta dados. Execute este script na raiz do projeto ou ajuste os diretórios.")
}

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")

4 Salvamento e recarregamento da base

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.

5 Etapa 2: criação dos subconjuntos de análise

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
# -----------------------------------------------------------------------------

# Esperado: executar na raiz do projeto, onde existe R_files/dados/processed.
source_data_dir <- file.path(getwd(), "R_files", "dados")
processed_dir <- file.path(source_data_dir, "processed")

# Fallback simples caso o script seja executado de dentro da pasta R_files.
if (!dir.exists(processed_dir)) {
  alt_processed_dir <- file.path(getwd(), "dados", "processed")

  if (dir.exists(alt_processed_dir)) {
    processed_dir <- alt_processed_dir
  } else {
    stop("Nao encontrei a pasta processed. Execute este script na raiz do projeto 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.

6 Considerações finais

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.

Essa divisão também facilita a manutenção: se houver alguma mudança na forma de agregar os dados, basta modificar a segunda etapa. Se houver mudança nos dados originais ou na estratégia de limpeza, a alteração fica concentrada na primeira etapa.