{r setup, include=FALSE} # Carregar bibliotecas necessárias library(flexdashboard) library(tidyverse) library(DT) library(plotly) library(summarytools) library(knitr) library(scales) library(zoo)

Configurações gerais

knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE)

Carregar dados com encoding UTF-8

veiculos_furtados <- read.csv2(“Banco Veculos Furtados - Atualizado Outubro 2025.csv”, fileEncoding = “UTF-8”) veiculos_roubados <- read.csv2(“Banco Veculos Roubados - Atualizado Outubro 2025.csv”, fileEncoding = “UTF-8”)

Se UTF-8 falhar, tentar latin1

if(any(grepl(“\?|<U\+”, veiculos_furtados$Municipio[1:5]))) { veiculos_furtados <- read.csv2(“Banco Veculos Furtados - Atualizado Outubro 2025.csv”, fileEncoding = “latin1”) veiculos_roubados <- read.csv2(“Banco Veculos Roubados - Atualizado Outubro 2025.csv”, fileEncoding = “latin1”) }

Tratamento de dados

Renomear colunas para facilitar

colnames(veiculos_furtados) <- c(“Registros”, “Natureza”, “Alvos”, “Municipio”, “Cod_IBGE”, “Mes”, “Ano”, “RPM”, “RMBH”) colnames(veiculos_roubados) <- c(“Registros”, “Natureza”, “Alvos”, “Municipio”, “Cod_IBGE”, “Mes”, “Ano”, “RPM”, “RMBH”)

Converter tipos e substituir RISP por RPM nos valores

veiculos_furtados <- veiculos_furtados %>% mutate( Registros = as.numeric(Registros), Mes = as.numeric(Mes), Ano = as.numeric(Ano), Cod_IBGE = as.numeric(Cod_IBGE), RPM = gsub(“RISP”, “RPM”, RPM) ) %>% filter(!is.na(Ano))

veiculos_roubados <- veiculos_roubados %>% mutate( Registros = as.numeric(Registros), Mes = as.numeric(Mes), Ano = as.numeric(Ano), Cod_IBGE = as.numeric(Cod_IBGE), RPM = gsub(“RISP”, “RPM”, RPM) ) %>% filter(!is.na(Ano))

Filtrar últimos 24 meses (outubro 2023 a outubro 2025)

filtrar_24_meses <- function(df) { df %>% filter( (Ano == 2025) | (Ano == 2024) | (Ano == 2023 & Mes >= 11) ) }

veiculos_furtados_24m <- filtrar_24_meses(veiculos_furtados) veiculos_roubados_24m <- filtrar_24_meses(veiculos_roubados)

Unir os dados para análise conjunta

dados_combinados <- bind_rows( veiculos_furtados_24m, veiculos_roubados_24m )

Tabelas e Estatísticas

Row

Total de Furtos (24 meses)

{r} total_furtos <- sum(veiculos_furtados_24m$Registros, na.rm = TRUE) valueBox(format(total_furtos, big.mark = “.”, decimal.mark = “,”), icon = “fa-car”, color = “warning”)

Total de Roubos (24 meses)

{r} total_roubos <- sum(veiculos_roubados_24m$Registros, na.rm = TRUE) valueBox(format(total_roubos, big.mark = “.”, decimal.mark = “,”), icon = “fa-car-crash”, color = “danger”)

Média Mensal de Furtos

{r} media_furtos <- veiculos_furtados_24m %>% group_by(Ano, Mes) %>% summarise(Total = sum(Registros, na.rm = TRUE), .groups = “drop”) %>% summarise(Media = mean(Total, na.rm = TRUE)) %>% pull(Media) valueBox(format(round(media_furtos, 0), big.mark = “.”, decimal.mark = “,”), icon = “fa-chart-line”, color = “info”)

Média Mensal de Roubos

{r} media_roubos <- veiculos_roubados_24m %>% group_by(Ano, Mes) %>% summarise(Total = sum(Registros, na.rm = TRUE), .groups = “drop”) %>% summarise(Media = mean(Total, na.rm = TRUE)) %>% pull(Media) valueBox(format(round(media_roubos, 0), big.mark = “.”, decimal.mark = “,”), icon = “fa-chart-bar”, color = “primary”)

Row

Tabela Interativa - Veículos Furtados por Município (Top 50)

{r} tabela_furtos <- veiculos_furtados_24m %>% group_by(Municipio, RPM) %>% summarise( Total_Registros = sum(Registros, na.rm = TRUE), Media_Mensal = round(mean(Registros, na.rm = TRUE), 2), .groups = “drop” ) %>% arrange(desc(Total_Registros)) %>% head(50)

datatable(tabela_furtos, caption = “Top 50 Municípios - Veículos Furtados (Últimos 24 meses)”, options = list( pageLength = 10, scrollX = TRUE, dom = ‘Bfrtip’, language = list( search = “Buscar:”, lengthMenu = “Mostrar MENU registros”, info = “Mostrando START a END de TOTAL registros”, paginate = list(previous = “Anterior”, next = “Próximo”) ) ), filter = “top”, rownames = FALSE, colnames = c(“Município”, “RPM”, “Total de Registros”, “Média Mensal”))

Tabela Interativa - Veículos Roubados por Município (Top 50)

{r} tabela_roubos <- veiculos_roubados_24m %>% group_by(Municipio, RPM) %>% summarise( Total_Registros = sum(Registros, na.rm = TRUE), Media_Mensal = round(mean(Registros, na.rm = TRUE), 2), .groups = “drop” ) %>% arrange(desc(Total_Registros)) %>% head(50)

datatable(tabela_roubos, caption = “Top 50 Municípios - Veículos Roubados (Últimos 24 meses)”, options = list( pageLength = 10, scrollX = TRUE, dom = ‘Bfrtip’, language = list( search = “Buscar:”, lengthMenu = “Mostrar MENU registros”, info = “Mostrando START a END de TOTAL registros”, paginate = list(previous = “Anterior”, next = “Próximo”) ) ), filter = “top”, rownames = FALSE, colnames = c(“Município”, “RPM”, “Total de Registros”, “Média Mensal”))

Row

Sumarização Estatística Interativa - Furtos por RPM

{r} # Estatísticas descritivas por RPM - Furtos stats_furtos_risp <- veiculos_furtados_24m %>% group_by(RPM) %>% summarise( N = n(), Total = sum(Registros, na.rm = TRUE), Media = round(mean(Registros, na.rm = TRUE), 2), Mediana = median(Registros, na.rm = TRUE), Desvio_Padrao = round(sd(Registros, na.rm = TRUE), 2), Minimo = min(Registros, na.rm = TRUE), Maximo = max(Registros, na.rm = TRUE), Q1 = quantile(Registros, 0.25, na.rm = TRUE), Q3 = quantile(Registros, 0.75, na.rm = TRUE), .groups = “drop” ) %>% arrange(desc(Total))

datatable(stats_furtos_risp, caption = “Estatísticas Descritivas de Furtos por RPM”, options = list( pageLength = 15, scrollX = TRUE, dom = ‘Bfrtip’, language = list( search = “Buscar:”, info = “Mostrando START a END de TOTAL registros” ) ), filter = “top”, rownames = FALSE, colnames = c(“RPM”, “N”, “Total”, “Média”, “Mediana”, “Desvio Padrão”, “Mínimo”, “Máximo”, “Q1”, “Q3”)) %>% formatRound(columns = c(“Media”, “Desvio_Padrao”), digits = 2)

Sumarização Estatística Interativa - Roubos por RPM

{r} # Estatísticas descritivas por RPM - Roubos stats_roubos_risp <- veiculos_roubados_24m %>% group_by(RPM) %>% summarise( N = n(), Total = sum(Registros, na.rm = TRUE), Media = round(mean(Registros, na.rm = TRUE), 2), Mediana = median(Registros, na.rm = TRUE), Desvio_Padrao = round(sd(Registros, na.rm = TRUE), 2), Minimo = min(Registros, na.rm = TRUE), Maximo = max(Registros, na.rm = TRUE), Q1 = quantile(Registros, 0.25, na.rm = TRUE), Q3 = quantile(Registros, 0.75, na.rm = TRUE), .groups = “drop” ) %>% arrange(desc(Total))

datatable(stats_roubos_risp, caption = “Estatísticas Descritivas de Roubos por RPM”, options = list( pageLength = 15, scrollX = TRUE, dom = ‘Bfrtip’, language = list( search = “Buscar:”, info = “Mostrando START a END de TOTAL registros” ) ), filter = “top”, rownames = FALSE, colnames = c(“RPM”, “N”, “Total”, “Média”, “Mediana”, “Desvio Padrão”, “Mínimo”, “Máximo”, “Q1”, “Q3”)) %>% formatRound(columns = c(“Media”, “Desvio_Padrao”), digits = 2)

Row

Comparativo Mensal - Evolução Temporal

{r} # Série temporal mensal serie_mensal <- dados_combinados %>% group_by(Ano, Mes, Natureza) %>% summarise(Total = sum(Registros, na.rm = TRUE), .groups = “drop”) %>% mutate( Data = paste0(Ano, “-”, sprintf(“%02d”, Mes)), Natureza = ifelse(Natureza == “FURTO”, “Furto”, “Roubo”) ) %>% arrange(Data)

datatable(serie_mensal %>% select(Data, Natureza, Total) %>% pivot_wider(names_from = Natureza, values_from = Total), caption = “Série Temporal Mensal - Furtos e Roubos”, options = list( pageLength = 12, scrollX = TRUE, dom = ‘Bfrtip’, language = list( search = “Buscar:”, info = “Mostrando START a END de TOTAL registros” ) ), rownames = FALSE, colnames = c(“Ano-Mês”, “Furto”, “Roubo”)) %>% formatRound(columns = c(“Furto”, “Roubo”), digits = 0)

Resumo Estatístico Geral

{r} # Resumo geral resumo_geral <- dados_combinados %>% group_by(Natureza) %>% summarise( Total_Ocorrencias = sum(Registros, na.rm = TRUE), Media_por_Municipio = round(mean(Registros, na.rm = TRUE), 2), Desvio_Padrao = round(sd(Registros, na.rm = TRUE), 2), Mediana = median(Registros, na.rm = TRUE), Coef_Variacao = round((sd(Registros, na.rm = TRUE) / mean(Registros, na.rm = TRUE)) * 100, 2), Municipios_Afetados = n_distinct(Municipio[Registros > 0]), .groups = “drop” )

datatable(resumo_geral, caption = “Resumo Estatístico Geral por Tipo de Crime”, options = list( pageLength = 5, scrollX = TRUE, dom = ‘t’ ), rownames = FALSE, colnames = c(“Natureza”, “Total”, “Média/Município”, “Desvio Padrão”, “Mediana”, “Coef. Variação (%)”, “Municípios Afetados”))

Gráficos

Row

Gráfico 1: Evolução Temporal - Furtos e Roubos de Veículos

{r} # Preparar dados para série temporal serie_temporal <- dados_combinados %>% group_by(Ano, Mes, Natureza) %>% summarise(Total = sum(Registros, na.rm = TRUE), .groups = “drop”) %>% mutate( Data = as.Date(paste0(Ano, “-”, sprintf(“%02d”, Mes), “-01”)), Natureza = ifelse(Natureza == “FURTO”, “Furto”, “Roubo”) ) %>% arrange(Data)

Gráfico de linha interativo

p1 <- ggplot(serie_temporal, aes(x = Data, y = Total, color = Natureza, group = Natureza)) + geom_line(linewidth = 1.2) + geom_point(size = 2) + scale_color_manual(values = c(“Furto” = “#FFA500”, “Roubo” = “#DC3545”)) + scale_x_date(date_labels = “%b/%Y”, date_breaks = “3 months”) + scale_y_continuous(labels = scales::comma_format(big.mark = “.”, decimal.mark = “,”)) + labs( title = “Evolução Mensal de Furtos e Roubos de Veículos”, subtitle = “Minas Gerais - Últimos 24 meses”, x = “Período”, y = “Total de Ocorrências”, color = “Tipo” ) + theme_minimal() + theme( axis.text.x = element_text(angle = 45, hjust = 1), legend.position = “bottom” )

ggplotly(p1) %>% layout(legend = list(orientation = “h”, x = 0.3, y = -0.2))

Gráfico 2: Top 10 Municípios com Mais Ocorrências

{r} # Top 10 municípios - dados combinados top_municipios <- dados_combinados %>% group_by(Municipio, Natureza) %>% summarise(Total = sum(Registros, na.rm = TRUE), .groups = “drop”) %>% mutate(Natureza = ifelse(Natureza == “FURTO”, “Furto”, “Roubo”)) %>% group_by(Municipio) %>% mutate(Total_Geral = sum(Total)) %>% ungroup() %>% filter(Municipio %in% (dados_combinados %>% group_by(Municipio) %>% summarise(T = sum(Registros, na.rm = TRUE)) %>% arrange(desc(T)) %>% head(10) %>% pull(Municipio)))

Ordenar municípios pelo total

ordem_municipios <- top_municipios %>% group_by(Municipio) %>% summarise(Total = sum(Total)) %>% arrange(Total) %>% pull(Municipio)

top_municipios\(Municipio <- factor(top_municipios\)Municipio, levels = ordem_municipios)

p2 <- ggplot(top_municipios, aes(x = Municipio, y = Total, fill = Natureza)) + geom_bar(stat = “identity”, position = “stack”) + scale_fill_manual(values = c(“Furto” = “#FFA500”, “Roubo” = “#DC3545”)) + scale_y_continuous(labels = scales::comma_format(big.mark = “.”, decimal.mark = “,”)) + coord_flip() + labs( title = “Top 10 Municípios - Furtos e Roubos de Veículos”, subtitle = “Últimos 24 meses”, x = ““, y =”Total de Ocorrências”, fill = “Tipo” ) + theme_minimal() + theme(legend.position = “bottom”)

ggplotly(p2) %>% layout(legend = list(orientation = “h”, x = 0.3, y = -0.15))

Row

Gráfico 3: Distribuição por RPM

{r} # Total por RPM risp_dados <- dados_combinados %>% group_by(RPM, Natureza) %>% summarise(Total = sum(Registros, na.rm = TRUE), .groups = “drop”) %>% mutate( Natureza = ifelse(Natureza == “FURTO”, “Furto”, “Roubo”), RPM_Num = as.numeric(gsub(“RPM ([0-9]+).*“,”\1”, RPM)) ) %>% arrange(RPM_Num)

Ordenar por número da RPM

risp_dados\(RPM <- factor(risp_dados\)RPM, levels = unique(risp_dados\(RPM[order(risp_dados\)RPM_Num)]))

p3 <- ggplot(risp_dados, aes(x = RPM, y = Total, fill = Natureza)) + geom_bar(stat = “identity”, position = “dodge”) + scale_fill_manual(values = c(“Furto” = “#FFA500”, “Roubo” = “#DC3545”)) + scale_y_continuous(labels = scales::comma_format(big.mark = “.”, decimal.mark = “,”)) + labs( title = “Distribuição de Ocorrências por RPM”, subtitle = “Comparativo Furtos vs Roubos - Últimos 24 meses”, x = “Região Integrada de Segurança Pública”, y = “Total de Ocorrências”, fill = “Tipo” ) + theme_minimal() + theme( axis.text.x = element_text(angle = 45, hjust = 1, size = 7), legend.position = “bottom” )

ggplotly(p3) %>% layout(legend = list(orientation = “h”, x = 0.3, y = -0.25))

Gráfico 4: Comparativo Anual - Boxplot

{r} # Boxplot por ano dados_boxplot <- dados_combinados %>% mutate( Natureza = ifelse(Natureza == “FURTO”, “Furto”, “Roubo”), Ano = factor(Ano) ) %>% group_by(Ano, Mes, Natureza) %>% summarise(Total = sum(Registros, na.rm = TRUE), .groups = “drop”)

p4 <- ggplot(dados_boxplot, aes(x = Ano, y = Total, fill = Natureza)) + geom_boxplot(alpha = 0.7) + scale_fill_manual(values = c(“Furto” = “#FFA500”, “Roubo” = “#DC3545”)) + scale_y_continuous(labels = scales::comma_format(big.mark = “.”, decimal.mark = “,”)) + labs( title = “Distribuição Mensal por Ano”, subtitle = “Boxplot comparativo de Furtos e Roubos”, x = “Ano”, y = “Total Mensal de Ocorrências”, fill = “Tipo” ) + theme_minimal() + theme(legend.position = “bottom”)

ggplotly(p4) %>% layout(legend = list(orientation = “h”, x = 0.3, y = -0.15))

Row

Gráfico 5: Proporção RMBH vs Interior

{r} # Proporção RMBH prop_rmbh <- dados_combinados %>% mutate( Natureza = ifelse(Natureza == “FURTO”, “Furto”, “Roubo”), Regiao = ifelse(RMBH == “SIM”, “RMBH”, “Interior”) ) %>% group_by(Natureza, Regiao) %>% summarise(Total = sum(Registros, na.rm = TRUE), .groups = “drop”)

p5 <- plot_ly(prop_rmbh, x = ~Natureza, y = ~Total, color = ~Regiao, type = “bar”, colors = c(“RMBH” = “#17A2B8”, “Interior” = “#6C757D”)) %>% layout( title = list(text = “Comparativo: RMBH vs Interior
Últimos 24 meses”), xaxis = list(title = “Tipo de Ocorrência”), yaxis = list(title = “Total de Ocorrências”, tickformat = “,.0f”), barmode = “group”, legend = list(orientation = “h”, x = 0.3, y = -0.15) )

p5

Gráfico 6: Tendência com Média Móvel

{r} # Média móvel de 3 meses serie_mm <- dados_combinados %>% group_by(Ano, Mes, Natureza) %>% summarise(Total = sum(Registros, na.rm = TRUE), .groups = “drop”) %>% mutate( Data = as.Date(paste0(Ano, “-”, sprintf(“%02d”, Mes), “-01”)), Natureza = ifelse(Natureza == “FURTO”, “Furto”, “Roubo”) ) %>% arrange(Natureza, Data) %>% group_by(Natureza) %>% mutate( Media_Movel_3m = zoo::rollmean(Total, k = 3, fill = NA, align = “right”) ) %>% ungroup()

p6 <- plot_ly(serie_mm, x = ~Data, y = ~Total, color = ~Natureza, type = “scatter”, mode = “lines+markers”, colors = c(“Furto” = “#FFA500”, “Roubo” = “#DC3545”), name = ~paste(Natureza, “- Mensal”), line = list(dash = “dot”, width = 1), marker = list(size = 5)) %>% add_trace(y = ~Media_Movel_3m, mode = “lines”, name = ~paste(Natureza, “- MM 3m”), line = list(width = 2.5)) %>% layout( title = list(text = “Tendência com Média Móvel (3 meses)
Furtos e Roubos de Veículos”), xaxis = list(title = “Período”, tickformat = “%b/%Y”), yaxis = list(title = “Ocorrências”, tickformat = “,.0f”), legend = list(orientation = “h”, x = 0, y = -0.2), hovermode = “x unified” )

p6

Mapas Temáticos

{r setup-mapas, include=FALSE} # Carregar bibliotecas para mapas library(sf) library(geobr) library(leaflet)

Baixar dados geográficos de Minas Gerais (municípios)

Usar cache para evitar downloads repetidos

muni_mg <- tryCatch({ geobr::read_municipality(code_muni = “MG”, year = 2020, showProgress = FALSE) }, error = function(e) { NULL })

Row

Sobre os Mapas

{r} valueBox(“Mapas Interativos”, caption = “Visualização geográfica por RPM e Município”, icon = “fa-map-marked-alt”, color = “success”)

Row

Mapa 1: Total de Ocorrências por Município (Top 30)

{r} if (!is.null(muni_mg)) { # Agregar dados por município dados_muni <- dados_combinados %>% group_by(Cod_IBGE, Municipio) %>% summarise( Total_Ocorrencias = sum(Registros, na.rm = TRUE), Total_Furtos = sum(Registros[Natureza == “FURTO”], na.rm = TRUE), Total_Roubos = sum(Registros[Natureza == “ROUBO”], na.rm = TRUE), .groups = “drop” ) %>% arrange(desc(Total_Ocorrencias)) %>% head(30)

# Criar código IBGE de 6 dígitos no geobr (remover dígito verificador) muni_mg_ajust <- muni_mg %>% mutate(code_muni_6 = as.numeric(substr(as.character(code_muni), 1, 6)))

# Juntar com dados geográficos muni_dados <- muni_mg_ajust %>% inner_join(dados_muni, by = c(“code_muni_6” = “Cod_IBGE”))

# Criar paleta de cores pal <- colorNumeric( palette = “YlOrRd”, domain = muni_dados$Total_Ocorrencias )

# Criar mapa leaflet(muni_dados) %>% addProviderTiles(providers$CartoDB.Positron) %>% addPolygons( fillColor = ~pal(Total_Ocorrencias), weight = 1, opacity = 1, color = “white”, dashArray = “3”, fillOpacity = 0.7, highlightOptions = highlightOptions( weight = 3, color = “#666”, dashArray = ““, fillOpacity = 0.9, bringToFront = TRUE ), label = ~paste0(”“, Municipio,”
“,”Total: “, format(Total_Ocorrencias, big.mark =”.”), “
”, “Furtos:”, format(Total_Furtos, big.mark = “.”), “
”, “Roubos:”, format(Total_Roubos, big.mark = “.”) ) %>% lapply(htmltools::HTML), labelOptions = labelOptions( style = list(“font-weight” = “normal”, padding = “3px 8px”), textsize = “13px”, direction = “auto” ) ) %>% addLegend( pal = pal, values = ~Total_Ocorrencias, title = “Total de
Ocorrências”, position = “bottomright” ) %>% addScaleBar( position = “bottomleft”, options = scaleBarOptions( maxWidth = 150, metric = TRUE, imperial = FALSE ) ) %>% addControl( html = ’
    <svg width="40" height="40" viewBox="0 0 100 100">
      <circle cx="50" cy="50" r="48" fill="white" stroke="#333" stroke-width="1.5"/>
      <polygon points="50,5 55,45 50,35 45,45" fill="#c00" stroke="#333" stroke-width="0.8"/>
      <polygon points="50,95 55,55 50,65 45,55" fill="#333" stroke="#333" stroke-width="0.8"/>
      <polygon points="5,50 45,45 35,50 45,55" fill="#333" stroke="#333" stroke-width="0.8"/>
      <polygon points="95,50 55,45 65,50 55,55" fill="#333" stroke="#333" stroke-width="0.8"/>
      <text x="50" y="18" text-anchor="middle" font-size="10" font-weight="bold" fill="#c00">N</text>
      <text x="50" y="95" text-anchor="middle" font-size="8" font-weight="bold" fill="#333">S</text>
      <text x="8" y="54" text-anchor="middle" font-size="8" font-weight="bold" fill="#333">O</text>
      <text x="92" y="54" text-anchor="middle" font-size="8" font-weight="bold" fill="#333">L</text>
      <circle cx="50" cy="50" r="3" fill="#333"/>
    </svg>
  </div>',
  position = "topright"
) %>%
fitBounds(
  lng1 = -51.5, lat1 = -23.0,
  lng2 = -39.5, lat2 = -14.0
) %>%
setMaxBounds(
  lng1 = -53, lat1 = -24,
  lng2 = -38, lat2 = -13
)
} else { HTML(“
    <h4>Mapa indisponível</h4>
    <p>Não foi possível carregar os dados geográficos.</p>
   </div>")

}

Mapa 2: Distribuição por RPM

{r} if (!is.null(muni_mg)) { # Agregar dados por RPM dados_risp <- dados_combinados %>% group_by(RPM) %>% summarise( Total_Ocorrencias = sum(Registros, na.rm = TRUE), Total_Furtos = sum(Registros[Natureza == “FURTO”], na.rm = TRUE), Total_Roubos = sum(Registros[Natureza == “ROUBO”], na.rm = TRUE), n_municipios = n_distinct(Municipio), .groups = “drop” ) %>% filter(!is.na(RPM) & RPM != ““)

# Agregar municípios por RPM para criar geometrias muni_risp <- dados_combinados %>% select(Cod_IBGE, RPM) %>% distinct()

# Criar código IBGE de 6 dígitos no geobr muni_mg_ajust <- muni_mg %>% mutate(code_muni_6 = as.numeric(substr(as.character(code_muni), 1, 6)))

# Juntar dados geográficos com RPM muni_com_risp <- muni_mg_ajust %>% inner_join(muni_risp, by = c(“code_muni_6” = “Cod_IBGE”)) %>% filter(!is.na(RPM) & RPM != ““)

# Dissolver por RPM para criar polígonos únicos risp_geo <- muni_com_risp %>% group_by(RPM) %>% summarise(geometry = st_union(geom), .groups = “drop”) %>% left_join(dados_risp, by = “RPM”)

# Criar paleta de cores pal_risp <- colorNumeric( palette = “YlOrRd”, domain = risp_geo$Total_Ocorrencias )

# Criar mapa leaflet(risp_geo) %>% addProviderTiles(providers$CartoDB.Positron) %>% addPolygons( fillColor = ~pal_risp(Total_Ocorrencias), weight = 2, opacity = 1, color = “white”, fillOpacity = 0.7, highlightOptions = highlightOptions( weight = 4, color = “#333”, fillOpacity = 0.9, bringToFront = TRUE ), label = ~paste0( “RPM:”, RPM, “
”, “Total:”, format(Total_Ocorrencias, big.mark = “.”), “
”, “Furtos:”, format(Total_Furtos, big.mark = “.”), “
”, “Roubos:”, format(Total_Roubos, big.mark = “.”), “
”, “Municípios:”, n_municipios ) %>% lapply(htmltools::HTML), labelOptions = labelOptions( style = list(“font-weight” = “normal”, padding = “3px 8px”), textsize = “13px”, direction = “auto” ) ) %>% addLegend( pal = pal_risp, values = ~Total_Ocorrencias, title = “Total por
RPM”, position = “bottomright” ) %>% addScaleBar( position = “bottomleft”, options = scaleBarOptions( maxWidth = 150, metric = TRUE, imperial = FALSE ) ) %>% addControl( html = ’
    <svg width="40" height="40" viewBox="0 0 100 100">
      <circle cx="50" cy="50" r="48" fill="white" stroke="#333" stroke-width="1.5"/>
      <polygon points="50,5 55,45 50,35 45,45" fill="#c00" stroke="#333" stroke-width="0.8"/>
      <polygon points="50,95 55,55 50,65 45,55" fill="#333" stroke="#333" stroke-width="0.8"/>
      <polygon points="5,50 45,45 35,50 45,55" fill="#333" stroke="#333" stroke-width="0.8"/>
      <polygon points="95,50 55,45 65,50 55,55" fill="#333" stroke="#333" stroke-width="0.8"/>
      <text x="50" y="18" text-anchor="middle" font-size="10" font-weight="bold" fill="#c00">N</text>
      <text x="50" y="95" text-anchor="middle" font-size="8" font-weight="bold" fill="#333">S</text>
      <text x="8" y="54" text-anchor="middle" font-size="8" font-weight="bold" fill="#333">O</text>
      <text x="92" y="54" text-anchor="middle" font-size="8" font-weight="bold" fill="#333">L</text>
      <circle cx="50" cy="50" r="3" fill="#333"/>
    </svg>
  </div>',
  position = "topright"
) %>%
fitBounds(
  lng1 = -51.5, lat1 = -23.0,
  lng2 = -39.5, lat2 = -14.0
) %>%
setMaxBounds(
  lng1 = -53, lat1 = -24,
  lng2 = -38, lat2 = -13
)
} else { HTML(“
    <h4>Mapa indisponível</h4>
    <p>Não foi possível carregar os dados geográficos.</p>
   </div>")

}

---
title: "Análise de Veículos Roubados e Furtados em MG"
author: "Anderson Veloso dos Santos"
output: 
  flexdashboard::flex_dashboard:
    orientation: rows
    vertical_layout: scroll
    theme: cosmo
    source_code: embed
    logo: www/ESCUDO_pequeno.png
---

<style>
/* Destacar botão Source Code */
#flexdashboard-source-code-btn,
.navbar a[data-toggle="modal"][data-target*="source"],
.navbar-nav > li > a[data-toggle="modal"],
button[data-toggle="modal"][data-target*="source"],
.source-code-btn,
#source-code-btn {
  background-color: #28a745 !important;
  color: white !important;
  font-weight: bold !important;
  border-radius: 5px !important;
  padding: 8px 15px !important;
  margin: 5px 10px !important;
  animation: pulse 2s infinite;
  box-shadow: 0 2px 5px rgba(0,0,0,0.2);
  border: 2px solid #1e7e34 !important;
}

#flexdashboard-source-code-btn:hover,
.navbar a[data-toggle="modal"][data-target*="source"]:hover,
.navbar-nav > li > a[data-toggle="modal"]:hover,
button[data-toggle="modal"][data-target*="source"]:hover,
.source-code-btn:hover,
#source-code-btn:hover {
  background-color: #218838 !important;
  transform: scale(1.05);
}

/* Seletor específico para ícone de código */
.navbar a .fa-code,
.navbar button .fa-code {
  color: white !important;
}

.navbar a:has(.fa-code),
.navbar li:has(a[data-toggle="modal"]) a {
  background-color: #28a745 !important;
  color: white !important;
  font-weight: bold !important;
  border-radius: 5px !important;
  animation: pulse 2s infinite;
}

@keyframes pulse {
  0% { box-shadow: 0 0 0 0 rgba(40, 167, 69, 0.7); }
  70% { box-shadow: 0 0 0 10px rgba(40, 167, 69, 0); }
  100% { box-shadow: 0 0 0 0 rgba(40, 167, 69, 0); }
}

/* Centralizar dados das colunas (exceto a primeira) */
.dataTables_wrapper table.dataTable tbody td:not(:first-child),
.dataTables_wrapper table.dataTable thead th:not(:first-child) {
  text-align: center !important;
}
</style>

{r setup, include=FALSE}
# Carregar bibliotecas necessárias
library(flexdashboard)
library(tidyverse)
library(DT)
library(plotly)
library(summarytools)
library(knitr)
library(scales)
library(zoo)

# Configurações gerais
knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE)

# Carregar dados com encoding UTF-8
veiculos_furtados <- read.csv2("Banco Veculos Furtados - Atualizado Outubro 2025.csv", 
                                fileEncoding = "UTF-8")
veiculos_roubados <- read.csv2("Banco Veculos Roubados - Atualizado Outubro 2025.csv", 
                                fileEncoding = "UTF-8")

# Se UTF-8 falhar, tentar latin1
if(any(grepl("\\?|<U\\+", veiculos_furtados$Municipio[1:5]))) {
  veiculos_furtados <- read.csv2("Banco Veculos Furtados - Atualizado Outubro 2025.csv", 
                                  fileEncoding = "latin1")
  veiculos_roubados <- read.csv2("Banco Veculos Roubados - Atualizado Outubro 2025.csv", 
                                  fileEncoding = "latin1")
}

# Tratamento de dados
# Renomear colunas para facilitar
colnames(veiculos_furtados) <- c("Registros", "Natureza", "Alvos", "Municipio", 
                                  "Cod_IBGE", "Mes", "Ano", "RPM", "RMBH")
colnames(veiculos_roubados) <- c("Registros", "Natureza", "Alvos", "Municipio", 
                                  "Cod_IBGE", "Mes", "Ano", "RPM", "RMBH")

# Converter tipos e substituir RISP por RPM nos valores
veiculos_furtados <- veiculos_furtados %>%
  mutate(
    Registros = as.numeric(Registros),
    Mes = as.numeric(Mes),
    Ano = as.numeric(Ano),
    Cod_IBGE = as.numeric(Cod_IBGE),
    RPM = gsub("RISP", "RPM", RPM)
  ) %>%
  filter(!is.na(Ano))

veiculos_roubados <- veiculos_roubados %>%
  mutate(
    Registros = as.numeric(Registros),
    Mes = as.numeric(Mes),
    Ano = as.numeric(Ano),
    Cod_IBGE = as.numeric(Cod_IBGE),
    RPM = gsub("RISP", "RPM", RPM)
  ) %>%
  filter(!is.na(Ano))

# Filtrar últimos 24 meses (outubro 2023 a outubro 2025)
filtrar_24_meses <- function(df) {
  df %>%
    filter(
      (Ano == 2025) | 
      (Ano == 2024) | 
      (Ano == 2023 & Mes >= 11)
    )
}

veiculos_furtados_24m <- filtrar_24_meses(veiculos_furtados)
veiculos_roubados_24m <- filtrar_24_meses(veiculos_roubados)

# Unir os dados para análise conjunta
dados_combinados <- bind_rows(
  veiculos_furtados_24m,
  veiculos_roubados_24m
)


# Tabelas e Estatísticas {data-icon="fa-table"}

## Row {data-height=100}

### Total de Furtos (24 meses)
{r}
total_furtos <- sum(veiculos_furtados_24m$Registros, na.rm = TRUE)
valueBox(format(total_furtos, big.mark = ".", decimal.mark = ","), 
         icon = "fa-car", color = "warning")


### Total de Roubos (24 meses)
{r}
total_roubos <- sum(veiculos_roubados_24m$Registros, na.rm = TRUE)
valueBox(format(total_roubos, big.mark = ".", decimal.mark = ","), 
         icon = "fa-car-crash", color = "danger")


### Média Mensal de Furtos
{r}
media_furtos <- veiculos_furtados_24m %>%
  group_by(Ano, Mes) %>%
  summarise(Total = sum(Registros, na.rm = TRUE), .groups = "drop") %>%
  summarise(Media = mean(Total, na.rm = TRUE)) %>%
  pull(Media)
valueBox(format(round(media_furtos, 0), big.mark = ".", decimal.mark = ","), 
         icon = "fa-chart-line", color = "info")


### Média Mensal de Roubos
{r}
media_roubos <- veiculos_roubados_24m %>%
  group_by(Ano, Mes) %>%
  summarise(Total = sum(Registros, na.rm = TRUE), .groups = "drop") %>%
  summarise(Media = mean(Total, na.rm = TRUE)) %>%
  pull(Media)
valueBox(format(round(media_roubos, 0), big.mark = ".", decimal.mark = ","), 
         icon = "fa-chart-bar", color = "primary")


## Row {data-height=450}

### Tabela Interativa - Veículos Furtados por Município (Top 50)
{r}
tabela_furtos <- veiculos_furtados_24m %>%
  group_by(Municipio, RPM) %>%
  summarise(
    Total_Registros = sum(Registros, na.rm = TRUE),
    Media_Mensal = round(mean(Registros, na.rm = TRUE), 2),
    .groups = "drop"
  ) %>%
  arrange(desc(Total_Registros)) %>%
  head(50)

datatable(tabela_furtos,
          caption = "Top 50 Municípios - Veículos Furtados (Últimos 24 meses)",
          options = list(
            pageLength = 10,
            scrollX = TRUE,
            dom = 'Bfrtip',
            language = list(
              search = "Buscar:",
              lengthMenu = "Mostrar _MENU_ registros",
              info = "Mostrando _START_ a _END_ de _TOTAL_ registros",
              paginate = list(previous = "Anterior", `next` = "Próximo")
            )
          ),
          filter = "top",
          rownames = FALSE,
          colnames = c("Município", "RPM", "Total de Registros", "Média Mensal"))


### Tabela Interativa - Veículos Roubados por Município (Top 50)
{r}
tabela_roubos <- veiculos_roubados_24m %>%
  group_by(Municipio, RPM) %>%
  summarise(
    Total_Registros = sum(Registros, na.rm = TRUE),
    Media_Mensal = round(mean(Registros, na.rm = TRUE), 2),
    .groups = "drop"
  ) %>%
  arrange(desc(Total_Registros)) %>%
  head(50)

datatable(tabela_roubos,
          caption = "Top 50 Municípios - Veículos Roubados (Últimos 24 meses)",
          options = list(
            pageLength = 10,
            scrollX = TRUE,
            dom = 'Bfrtip',
            language = list(
              search = "Buscar:",
              lengthMenu = "Mostrar _MENU_ registros",
              info = "Mostrando _START_ a _END_ de _TOTAL_ registros",
              paginate = list(previous = "Anterior", `next` = "Próximo")
            )
          ),
          filter = "top",
          rownames = FALSE,
          colnames = c("Município", "RPM", "Total de Registros", "Média Mensal"))


## Row {data-height=500}

### Sumarização Estatística Interativa - Furtos por RPM
{r}
# Estatísticas descritivas por RPM - Furtos
stats_furtos_risp <- veiculos_furtados_24m %>%
  group_by(RPM) %>%
  summarise(
    N = n(),
    Total = sum(Registros, na.rm = TRUE),
    Media = round(mean(Registros, na.rm = TRUE), 2),
    Mediana = median(Registros, na.rm = TRUE),
    Desvio_Padrao = round(sd(Registros, na.rm = TRUE), 2),
    Minimo = min(Registros, na.rm = TRUE),
    Maximo = max(Registros, na.rm = TRUE),
    Q1 = quantile(Registros, 0.25, na.rm = TRUE),
    Q3 = quantile(Registros, 0.75, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  arrange(desc(Total))

datatable(stats_furtos_risp,
          caption = "Estatísticas Descritivas de Furtos por RPM",
          options = list(
            pageLength = 15,
            scrollX = TRUE,
            dom = 'Bfrtip',
            language = list(
              search = "Buscar:",
              info = "Mostrando _START_ a _END_ de _TOTAL_ registros"
            )
          ),
          filter = "top",
          rownames = FALSE,
          colnames = c("RPM", "N", "Total", "Média", "Mediana", 
                       "Desvio Padrão", "Mínimo", "Máximo", "Q1", "Q3")) %>%
  formatRound(columns = c("Media", "Desvio_Padrao"), digits = 2)


### Sumarização Estatística Interativa - Roubos por RPM
{r}
# Estatísticas descritivas por RPM - Roubos
stats_roubos_risp <- veiculos_roubados_24m %>%
  group_by(RPM) %>%
  summarise(
    N = n(),
    Total = sum(Registros, na.rm = TRUE),
    Media = round(mean(Registros, na.rm = TRUE), 2),
    Mediana = median(Registros, na.rm = TRUE),
    Desvio_Padrao = round(sd(Registros, na.rm = TRUE), 2),
    Minimo = min(Registros, na.rm = TRUE),
    Maximo = max(Registros, na.rm = TRUE),
    Q1 = quantile(Registros, 0.25, na.rm = TRUE),
    Q3 = quantile(Registros, 0.75, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  arrange(desc(Total))

datatable(stats_roubos_risp,
          caption = "Estatísticas Descritivas de Roubos por RPM",
          options = list(
            pageLength = 15,
            scrollX = TRUE,
            dom = 'Bfrtip',
            language = list(
              search = "Buscar:",
              info = "Mostrando _START_ a _END_ de _TOTAL_ registros"
            )
          ),
          filter = "top",
          rownames = FALSE,
          colnames = c("RPM", "N", "Total", "Média", "Mediana", 
                       "Desvio Padrão", "Mínimo", "Máximo", "Q1", "Q3")) %>%
  formatRound(columns = c("Media", "Desvio_Padrao"), digits = 2)


## Row {data-height=400}

### Comparativo Mensal - Evolução Temporal
{r}
# Série temporal mensal
serie_mensal <- dados_combinados %>%
  group_by(Ano, Mes, Natureza) %>%
  summarise(Total = sum(Registros, na.rm = TRUE), .groups = "drop") %>%
  mutate(
    Data = paste0(Ano, "-", sprintf("%02d", Mes)),
    Natureza = ifelse(Natureza == "FURTO", "Furto", "Roubo")
  ) %>%
  arrange(Data)

datatable(serie_mensal %>% select(Data, Natureza, Total) %>% 
            pivot_wider(names_from = Natureza, values_from = Total),
          caption = "Série Temporal Mensal - Furtos e Roubos",
          options = list(
            pageLength = 12,
            scrollX = TRUE,
            dom = 'Bfrtip',
            language = list(
              search = "Buscar:",
              info = "Mostrando _START_ a _END_ de _TOTAL_ registros"
            )
          ),
          rownames = FALSE,
          colnames = c("Ano-Mês", "Furto", "Roubo")) %>%
  formatRound(columns = c("Furto", "Roubo"), digits = 0)


### Resumo Estatístico Geral
{r}
# Resumo geral
resumo_geral <- dados_combinados %>%
  group_by(Natureza) %>%
  summarise(
    Total_Ocorrencias = sum(Registros, na.rm = TRUE),
    Media_por_Municipio = round(mean(Registros, na.rm = TRUE), 2),
    Desvio_Padrao = round(sd(Registros, na.rm = TRUE), 2),
    Mediana = median(Registros, na.rm = TRUE),
    Coef_Variacao = round((sd(Registros, na.rm = TRUE) / mean(Registros, na.rm = TRUE)) * 100, 2),
    Municipios_Afetados = n_distinct(Municipio[Registros > 0]),
    .groups = "drop"
  )

datatable(resumo_geral,
          caption = "Resumo Estatístico Geral por Tipo de Crime",
          options = list(
            pageLength = 5,
            scrollX = TRUE,
            dom = 't'
          ),
          rownames = FALSE,
          colnames = c("Natureza", "Total", "Média/Município", "Desvio Padrão", 
                       "Mediana", "Coef. Variação (%)", "Municípios Afetados"))


# Gráficos {data-icon="fa-chart-bar"}

## Row {data-height=500}

### Gráfico 1: Evolução Temporal - Furtos e Roubos de Veículos
{r}
# Preparar dados para série temporal
serie_temporal <- dados_combinados %>%
  group_by(Ano, Mes, Natureza) %>%
  summarise(Total = sum(Registros, na.rm = TRUE), .groups = "drop") %>%
  mutate(
    Data = as.Date(paste0(Ano, "-", sprintf("%02d", Mes), "-01")),
    Natureza = ifelse(Natureza == "FURTO", "Furto", "Roubo")
  ) %>%
  arrange(Data)

# Gráfico de linha interativo
p1 <- ggplot(serie_temporal, aes(x = Data, y = Total, color = Natureza, group = Natureza)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 2) +
  scale_color_manual(values = c("Furto" = "#FFA500", "Roubo" = "#DC3545")) +
  scale_x_date(date_labels = "%b/%Y", date_breaks = "3 months") +
  scale_y_continuous(labels = scales::comma_format(big.mark = ".", decimal.mark = ",")) +
  labs(
    title = "Evolução Mensal de Furtos e Roubos de Veículos",
    subtitle = "Minas Gerais - Últimos 24 meses",
    x = "Período",
    y = "Total de Ocorrências",
    color = "Tipo"
  ) +
  theme_minimal() +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1),
    legend.position = "bottom"
  )

ggplotly(p1) %>%
  layout(legend = list(orientation = "h", x = 0.3, y = -0.2))


### Gráfico 2: Top 10 Municípios com Mais Ocorrências
{r}
# Top 10 municípios - dados combinados
top_municipios <- dados_combinados %>%
  group_by(Municipio, Natureza) %>%
  summarise(Total = sum(Registros, na.rm = TRUE), .groups = "drop") %>%
  mutate(Natureza = ifelse(Natureza == "FURTO", "Furto", "Roubo")) %>%
  group_by(Municipio) %>%
  mutate(Total_Geral = sum(Total)) %>%
  ungroup() %>%
  filter(Municipio %in% (dados_combinados %>%
                           group_by(Municipio) %>%
                           summarise(T = sum(Registros, na.rm = TRUE)) %>%
                           arrange(desc(T)) %>%
                           head(10) %>%
                           pull(Municipio)))

# Ordenar municípios pelo total
ordem_municipios <- top_municipios %>%
  group_by(Municipio) %>%
  summarise(Total = sum(Total)) %>%
  arrange(Total) %>%
  pull(Municipio)

top_municipios$Municipio <- factor(top_municipios$Municipio, levels = ordem_municipios)

p2 <- ggplot(top_municipios, aes(x = Municipio, y = Total, fill = Natureza)) +
  geom_bar(stat = "identity", position = "stack") +
  scale_fill_manual(values = c("Furto" = "#FFA500", "Roubo" = "#DC3545")) +
  scale_y_continuous(labels = scales::comma_format(big.mark = ".", decimal.mark = ",")) +
  coord_flip() +
  labs(
    title = "Top 10 Municípios - Furtos e Roubos de Veículos",
    subtitle = "Últimos 24 meses",
    x = "",
    y = "Total de Ocorrências",
    fill = "Tipo"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")

ggplotly(p2) %>%
  layout(legend = list(orientation = "h", x = 0.3, y = -0.15))


## Row {data-height=500}

### Gráfico 3: Distribuição por RPM
{r}
# Total por RPM
risp_dados <- dados_combinados %>%
  group_by(RPM, Natureza) %>%
  summarise(Total = sum(Registros, na.rm = TRUE), .groups = "drop") %>%
  mutate(
    Natureza = ifelse(Natureza == "FURTO", "Furto", "Roubo"),
    RPM_Num = as.numeric(gsub("RPM ([0-9]+).*", "\\1", RPM))
  ) %>%
  arrange(RPM_Num)

# Ordenar por número da RPM
risp_dados$RPM <- factor(risp_dados$RPM, levels = unique(risp_dados$RPM[order(risp_dados$RPM_Num)]))

p3 <- ggplot(risp_dados, aes(x = RPM, y = Total, fill = Natureza)) +
  geom_bar(stat = "identity", position = "dodge") +
  scale_fill_manual(values = c("Furto" = "#FFA500", "Roubo" = "#DC3545")) +
  scale_y_continuous(labels = scales::comma_format(big.mark = ".", decimal.mark = ",")) +
  labs(
    title = "Distribuição de Ocorrências por RPM",
    subtitle = "Comparativo Furtos vs Roubos - Últimos 24 meses",
    x = "Região Integrada de Segurança Pública",
    y = "Total de Ocorrências",
    fill = "Tipo"
  ) +
  theme_minimal() +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1, size = 7),
    legend.position = "bottom"
  )

ggplotly(p3) %>%
  layout(legend = list(orientation = "h", x = 0.3, y = -0.25))


### Gráfico 4: Comparativo Anual - Boxplot
{r}
# Boxplot por ano
dados_boxplot <- dados_combinados %>%
  mutate(
    Natureza = ifelse(Natureza == "FURTO", "Furto", "Roubo"),
    Ano = factor(Ano)
  ) %>%
  group_by(Ano, Mes, Natureza) %>%
  summarise(Total = sum(Registros, na.rm = TRUE), .groups = "drop")

p4 <- ggplot(dados_boxplot, aes(x = Ano, y = Total, fill = Natureza)) +
  geom_boxplot(alpha = 0.7) +
  scale_fill_manual(values = c("Furto" = "#FFA500", "Roubo" = "#DC3545")) +
  scale_y_continuous(labels = scales::comma_format(big.mark = ".", decimal.mark = ",")) +
  labs(
    title = "Distribuição Mensal por Ano",
    subtitle = "Boxplot comparativo de Furtos e Roubos",
    x = "Ano",
    y = "Total Mensal de Ocorrências",
    fill = "Tipo"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")

ggplotly(p4) %>%
  layout(legend = list(orientation = "h", x = 0.3, y = -0.15))


## Row {data-height=500}

### Gráfico 5: Proporção RMBH vs Interior
{r}
# Proporção RMBH
prop_rmbh <- dados_combinados %>%
  mutate(
    Natureza = ifelse(Natureza == "FURTO", "Furto", "Roubo"),
    Regiao = ifelse(RMBH == "SIM", "RMBH", "Interior")
  ) %>%
  group_by(Natureza, Regiao) %>%
  summarise(Total = sum(Registros, na.rm = TRUE), .groups = "drop")

p5 <- plot_ly(prop_rmbh, x = ~Natureza, y = ~Total, color = ~Regiao, type = "bar",
              colors = c("RMBH" = "#17A2B8", "Interior" = "#6C757D")) %>%
  layout(
    title = list(text = "Comparativo: RMBH vs Interior<br><sup>Últimos 24 meses</sup>"),
    xaxis = list(title = "Tipo de Ocorrência"),
    yaxis = list(title = "Total de Ocorrências", tickformat = ",.0f"),
    barmode = "group",
    legend = list(orientation = "h", x = 0.3, y = -0.15)
  )

p5


### Gráfico 6: Tendência com Média Móvel
{r}
# Média móvel de 3 meses
serie_mm <- dados_combinados %>%
  group_by(Ano, Mes, Natureza) %>%
  summarise(Total = sum(Registros, na.rm = TRUE), .groups = "drop") %>%
  mutate(
    Data = as.Date(paste0(Ano, "-", sprintf("%02d", Mes), "-01")),
    Natureza = ifelse(Natureza == "FURTO", "Furto", "Roubo")
  ) %>%
  arrange(Natureza, Data) %>%
  group_by(Natureza) %>%
  mutate(
    Media_Movel_3m = zoo::rollmean(Total, k = 3, fill = NA, align = "right")
  ) %>%
  ungroup()

p6 <- plot_ly(serie_mm, x = ~Data, y = ~Total, color = ~Natureza, 
              type = "scatter", mode = "lines+markers",
              colors = c("Furto" = "#FFA500", "Roubo" = "#DC3545"),
              name = ~paste(Natureza, "- Mensal"),
              line = list(dash = "dot", width = 1),
              marker = list(size = 5)) %>%
  add_trace(y = ~Media_Movel_3m, mode = "lines",
            name = ~paste(Natureza, "- MM 3m"),
            line = list(width = 2.5)) %>%
  layout(
    title = list(text = "Tendência com Média Móvel (3 meses)<br><sup>Furtos e Roubos de Veículos</sup>"),
    xaxis = list(title = "Período", tickformat = "%b/%Y"),
    yaxis = list(title = "Ocorrências", tickformat = ",.0f"),
    legend = list(orientation = "h", x = 0, y = -0.2),
    hovermode = "x unified"
  )

p6


# Mapas Temáticos {data-icon="fa-map"}

{r setup-mapas, include=FALSE}
# Carregar bibliotecas para mapas
library(sf)
library(geobr)
library(leaflet)

# Baixar dados geográficos de Minas Gerais (municípios)
# Usar cache para evitar downloads repetidos
muni_mg <- tryCatch({
  geobr::read_municipality(code_muni = "MG", year = 2020, showProgress = FALSE)
}, error = function(e) {
  NULL
})


## Row {data-height=100}

### Sobre os Mapas
{r}
valueBox("Mapas Interativos", 
         caption = "Visualização geográfica por RPM e Município", 
         icon = "fa-map-marked-alt", 
         color = "success")


## Row {data-height=600}

### Mapa 1: Total de Ocorrências por Município (Top 30)
{r}
if (!is.null(muni_mg)) {
  # Agregar dados por município
  dados_muni <- dados_combinados %>%
    group_by(Cod_IBGE, Municipio) %>%
    summarise(
      Total_Ocorrencias = sum(Registros, na.rm = TRUE),
      Total_Furtos = sum(Registros[Natureza == "FURTO"], na.rm = TRUE),
      Total_Roubos = sum(Registros[Natureza == "ROUBO"], na.rm = TRUE),
      .groups = "drop"
    ) %>%
    arrange(desc(Total_Ocorrencias)) %>%
    head(30)
  
  # Criar código IBGE de 6 dígitos no geobr (remover dígito verificador)
  muni_mg_ajust <- muni_mg %>%
    mutate(code_muni_6 = as.numeric(substr(as.character(code_muni), 1, 6)))
  
  # Juntar com dados geográficos
  muni_dados <- muni_mg_ajust %>%
    inner_join(dados_muni, by = c("code_muni_6" = "Cod_IBGE"))
  
  # Criar paleta de cores
  pal <- colorNumeric(
    palette = "YlOrRd",
    domain = muni_dados$Total_Ocorrencias
  )
  
  # Criar mapa
  leaflet(muni_dados) %>%
    addProviderTiles(providers$CartoDB.Positron) %>%
    addPolygons(
      fillColor = ~pal(Total_Ocorrencias),
      weight = 1,
      opacity = 1,
      color = "white",
      dashArray = "3",
      fillOpacity = 0.7,
      highlightOptions = highlightOptions(
        weight = 3,
        color = "#666",
        dashArray = "",
        fillOpacity = 0.9,
        bringToFront = TRUE
      ),
      label = ~paste0(
        "<strong>", Municipio, "</strong><br>",
        "Total: ", format(Total_Ocorrencias, big.mark = "."), "<br>",
        "Furtos: ", format(Total_Furtos, big.mark = "."), "<br>",
        "Roubos: ", format(Total_Roubos, big.mark = ".")
      ) %>% lapply(htmltools::HTML),
      labelOptions = labelOptions(
        style = list("font-weight" = "normal", padding = "3px 8px"),
        textsize = "13px",
        direction = "auto"
      )
    ) %>%
    addLegend(
      pal = pal,
      values = ~Total_Ocorrencias,
      title = "Total de<br>Ocorrências",
      position = "bottomright"
    ) %>%
    addScaleBar(
      position = "bottomleft",
      options = scaleBarOptions(
        maxWidth = 150,
        metric = TRUE,
        imperial = FALSE
      )
    ) %>%
    addControl(
      html = '<div style="background: white; padding: 4px; border-radius: 4px; border: 1px solid rgba(0,0,0,0.3);">
        <svg width="40" height="40" viewBox="0 0 100 100">
          <circle cx="50" cy="50" r="48" fill="white" stroke="#333" stroke-width="1.5"/>
          <polygon points="50,5 55,45 50,35 45,45" fill="#c00" stroke="#333" stroke-width="0.8"/>
          <polygon points="50,95 55,55 50,65 45,55" fill="#333" stroke="#333" stroke-width="0.8"/>
          <polygon points="5,50 45,45 35,50 45,55" fill="#333" stroke="#333" stroke-width="0.8"/>
          <polygon points="95,50 55,45 65,50 55,55" fill="#333" stroke="#333" stroke-width="0.8"/>
          <text x="50" y="18" text-anchor="middle" font-size="10" font-weight="bold" fill="#c00">N</text>
          <text x="50" y="95" text-anchor="middle" font-size="8" font-weight="bold" fill="#333">S</text>
          <text x="8" y="54" text-anchor="middle" font-size="8" font-weight="bold" fill="#333">O</text>
          <text x="92" y="54" text-anchor="middle" font-size="8" font-weight="bold" fill="#333">L</text>
          <circle cx="50" cy="50" r="3" fill="#333"/>
        </svg>
      </div>',
      position = "topright"
    ) %>%
    fitBounds(
      lng1 = -51.5, lat1 = -23.0,
      lng2 = -39.5, lat2 = -14.0
    ) %>%
    setMaxBounds(
      lng1 = -53, lat1 = -24,
      lng2 = -38, lat2 = -13
    )
} else {
  HTML("<div style='text-align:center; padding:50px;'>
        <h4>Mapa indisponível</h4>
        <p>Não foi possível carregar os dados geográficos.</p>
       </div>")
}


### Mapa 2: Distribuição por RPM
{r}
if (!is.null(muni_mg)) {
  # Agregar dados por RPM
  dados_risp <- dados_combinados %>%
    group_by(RPM) %>%
    summarise(
      Total_Ocorrencias = sum(Registros, na.rm = TRUE),
      Total_Furtos = sum(Registros[Natureza == "FURTO"], na.rm = TRUE),
      Total_Roubos = sum(Registros[Natureza == "ROUBO"], na.rm = TRUE),
      n_municipios = n_distinct(Municipio),
      .groups = "drop"
    ) %>%
    filter(!is.na(RPM) & RPM != "")
  
  # Agregar municípios por RPM para criar geometrias
  muni_risp <- dados_combinados %>%
    select(Cod_IBGE, RPM) %>%
    distinct()
  
  # Criar código IBGE de 6 dígitos no geobr
  muni_mg_ajust <- muni_mg %>%
    mutate(code_muni_6 = as.numeric(substr(as.character(code_muni), 1, 6)))
  
  # Juntar dados geográficos com RPM
  muni_com_risp <- muni_mg_ajust %>%
    inner_join(muni_risp, by = c("code_muni_6" = "Cod_IBGE")) %>%
    filter(!is.na(RPM) & RPM != "")
  
  # Dissolver por RPM para criar polígonos únicos
  risp_geo <- muni_com_risp %>%
    group_by(RPM) %>%
    summarise(geometry = st_union(geom), .groups = "drop") %>%
    left_join(dados_risp, by = "RPM")
  
  # Criar paleta de cores
  pal_risp <- colorNumeric(
    palette = "YlOrRd",
    domain = risp_geo$Total_Ocorrencias
  )
  
  # Criar mapa
  leaflet(risp_geo) %>%
    addProviderTiles(providers$CartoDB.Positron) %>%
    addPolygons(
      fillColor = ~pal_risp(Total_Ocorrencias),
      weight = 2,
      opacity = 1,
      color = "white",
      fillOpacity = 0.7,
      highlightOptions = highlightOptions(
        weight = 4,
        color = "#333",
        fillOpacity = 0.9,
        bringToFront = TRUE
      ),
      label = ~paste0(
        "<strong>RPM: ", RPM, "</strong><br>",
        "Total: ", format(Total_Ocorrencias, big.mark = "."), "<br>",
        "Furtos: ", format(Total_Furtos, big.mark = "."), "<br>",
        "Roubos: ", format(Total_Roubos, big.mark = "."), "<br>",
        "Municípios: ", n_municipios
      ) %>% lapply(htmltools::HTML),
      labelOptions = labelOptions(
        style = list("font-weight" = "normal", padding = "3px 8px"),
        textsize = "13px",
        direction = "auto"
      )
    ) %>%
    addLegend(
      pal = pal_risp,
      values = ~Total_Ocorrencias,
      title = "Total por<br>RPM",
      position = "bottomright"
    ) %>%
    addScaleBar(
      position = "bottomleft",
      options = scaleBarOptions(
        maxWidth = 150,
        metric = TRUE,
        imperial = FALSE
      )
    ) %>%
    addControl(
      html = '<div style="background: white; padding: 4px; border-radius: 4px; border: 1px solid rgba(0,0,0,0.3);">
        <svg width="40" height="40" viewBox="0 0 100 100">
          <circle cx="50" cy="50" r="48" fill="white" stroke="#333" stroke-width="1.5"/>
          <polygon points="50,5 55,45 50,35 45,45" fill="#c00" stroke="#333" stroke-width="0.8"/>
          <polygon points="50,95 55,55 50,65 45,55" fill="#333" stroke="#333" stroke-width="0.8"/>
          <polygon points="5,50 45,45 35,50 45,55" fill="#333" stroke="#333" stroke-width="0.8"/>
          <polygon points="95,50 55,45 65,50 55,55" fill="#333" stroke="#333" stroke-width="0.8"/>
          <text x="50" y="18" text-anchor="middle" font-size="10" font-weight="bold" fill="#c00">N</text>
          <text x="50" y="95" text-anchor="middle" font-size="8" font-weight="bold" fill="#333">S</text>
          <text x="8" y="54" text-anchor="middle" font-size="8" font-weight="bold" fill="#333">O</text>
          <text x="92" y="54" text-anchor="middle" font-size="8" font-weight="bold" fill="#333">L</text>
          <circle cx="50" cy="50" r="3" fill="#333"/>
        </svg>
      </div>',
      position = "topright"
    ) %>%
    fitBounds(
      lng1 = -51.5, lat1 = -23.0,
      lng2 = -39.5, lat2 = -14.0
    ) %>%
    setMaxBounds(
      lng1 = -53, lat1 = -24,
      lng2 = -38, lat2 = -13
    )
} else {
  HTML("<div style='text-align:center; padding:50px;'>
        <h4>Mapa indisponível</h4>
        <p>Não foi possível carregar os dados geográficos.</p>
       </div>")
}