{r setup, include=FALSE} # Carregar bibliotecas necessárias library(flexdashboard) library(tidyverse) library(DT) library(plotly) library(summarytools) library(knitr) library(scales) library(zoo)
knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE)
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”)
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”) }
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”)
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_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)
dados_combinados <- bind_rows( veiculos_furtados_24m, veiculos_roubados_24m )
{r} total_furtos <- sum(veiculos_furtados_24m$Registros, na.rm = TRUE) valueBox(format(total_furtos, big.mark = “.”, decimal.mark = “,”), icon = “fa-car”, color = “warning”)
{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”)
{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”)
{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”)
{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”))
{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”))
{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)
{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)
{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)
{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”))
{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)
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))
{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)))
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))
{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)
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))
{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))
{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
{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
{r setup-mapas, include=FALSE} # Carregar bibliotecas para mapas library(sf) library(geobr) library(leaflet)
muni_mg <- tryCatch({ geobr::read_municipality(code_muni = “MG”, year = 2020, showProgress = FALSE) }, error = function(e) { NULL })
{r} valueBox(“Mapas Interativos”, caption = “Visualização geográfica por RPM e Município”, icon = “fa-map-marked-alt”, color = “success”)
{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,” <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>")
}
{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, “ <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>")
}