Feito por: João Eduardo
---
title: "VEÍCULOS ROUBADOS E FURTADOS EM MG"
output:
flexdashboard::flex_dashboard:
orientation: rows
vertical_layout: scroll
theme: cosmo
self_contained: true
source_code: embed
logo: logo8rpm_2.png
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, message = FALSE, warning = FALSE)
library(tidyverse)
library(DT)
library(janitor)
library(sf)
library(geobr)
library(viridis)
library(htmltools)
library(stringi)
# Verifica se os arquivos SEJUSP estão na mesma pasta do .rmd
arq_roubos <- "VRoubados.csv"
arq_furtos <- "VFurtados.csv"
if (!file.exists(arq_roubos) || !file.exists(arq_furtos)) {
stop("ERRO: Coloque os arquivos 'VRoubados.csv' e 'VFurtados.csv' na mesma pasta do arquivo .Rmd.")
}
#Checa em qual encoding está o arquivo csv
check_encoding <- function(arquivo) {
enc <- readr::guess_encoding(arquivo, n_max = 10000)
# Prioridade de escolha
prioridade <- c(
"UTF-8",
"UTF-8-BOM",
"windows-1252",
"ISO-8859-1",
"Latin1"
)
escolhido <- enc %>%
filter(encoding %in% prioridade) %>%
slice_min(order_by = match(encoding, prioridade), n = 1) %>%
pull(encoding)
if (length(escolhido) == 0) {
escolhido <- enc$encoding[1]
}
message("Encoding detectado: ", escolhido)
return(escolhido)
}
#Lê os arquivos CSV
ler_csv_pv <- function(arquivo) {
encoding_detectado <- check_encoding(arquivo)
readr::read_delim(
file = arquivo,
delim = ";",
locale = locale(encoding = encoding_detectado),
show_col_types = FALSE,
trim_ws = TRUE
)
}
#Guarda os dados em memória
roubos_raw <- ler_csv_pv(arq_roubos)
furtos_raw <- ler_csv_pv(arq_furtos)
#Normaliza os textos dos arquivos removendo acentos
normalizar_texto <- function(x) {
x %>%
as.character() %>%
stringi::stri_trans_general("Latin-ASCII") %>%
toupper() %>%
stringr::str_squish()
}
#Padroniza as colunas
padronizar <- function(df, natureza) {
df %>%
janitor::clean_names() %>%
transmute(
registros = suppressWarnings(as.numeric(.[[1]])),
municipio = normalizar_texto(.[[4]]),
cod_ibge = stringr::str_pad(
stringr::str_replace_all(as.character(.[[5]]), "[^0-9]", ""),
7, "left", "0"
),
mes = suppressWarnings(as.integer(.[[6]])),
ano_fato = suppressWarnings(as.integer(.[[7]])),
risp = normalizar_texto(.[[8]]),
rmbh = normalizar_texto(.[[9]]),
natureza = toupper(natureza)
)
}
#Cria base unificada de dados
dados <- bind_rows(
padronizar(roubos_raw, "ROUBO"),
padronizar(furtos_raw, "FURTO")
)
# Filtra período
dados_recentes <- dados %>%
filter(ano_fato >= 2023) %>%
mutate(data_base = as.Date(paste(ano_fato, mes, "01", sep = "-")))
# Soma os crimes de roubos
total_roubo <- dados_recentes %>%
filter(natureza == "ROUBO") %>%
summarise(t = sum(registros, na.rm = TRUE)) %>% pull(t)
# Soma os crimes de furtos
total_furto <- dados_recentes %>%
filter(natureza == "FURTO") %>%
summarise(t = sum(registros, na.rm = TRUE)) %>% pull(t)
# DataTables PT-BR
dt_lang_ptbr <- list(
sEmptyTable = "Nenhum dado disponível",
sInfo = "Mostrando _START_ até _END_ de _TOTAL_ registros",
sInfoEmpty = "Mostrando 0 até 0 de 0 registros",
sInfoFiltered = "(filtrado de _MAX_ registros)",
sLengthMenu = "Mostrar _MENU_ registros",
sSearch = "Pesquisar:",
sZeroRecords = "Nenhum registro encontrado",
oPaginate = list(sNext = "Próximo", sPrevious = "Anterior")
)
# Mapa de MG carregado localmente (sem internet)
if (!file.exists("mapa_municipios_mg_2020.rds")) {
stop("Arquivo de mapa não encontrado. Gere o RDS antes de rodar o painel e coloque na mesma pasta do arquivo .Rmd.")
}
mapa_mg <- readRDS("mapa_municipios_mg_2020.rds") %>%
filter(code_state == 31) %>%
mutate(code_muni = stringr::str_pad(as.character(code_muni), 7, "left", "0"))
# Cria chave de município no MAPA
lookup_municipios <- mapa_mg %>%
st_drop_geometry() %>%
transmute(
code_muni,
chave_muni = normalizar_texto(name_muni)
) %>%
distinct(chave_muni, .keep_all = TRUE)
# Cria chave de municipio nos DADOS
dados_recentes <- dados_recentes %>%
mutate(chave_muni = normalizar_texto(municipio))
# Agrega dados para o mapa HISTÓRICO
dados_mapa <- dados_recentes %>%
left_join(lookup_municipios, by = "chave_muni") %>%
group_by(code_muni) %>%
summarise(total = sum(registros, na.rm = TRUE), .groups = "drop")
# Junta no mapa de série histórica
mapa_historico <- mapa_mg %>%
left_join(dados_mapa, by = "code_muni")
# Agrega dados para o mapa RISP
dados_batalhao <- dados_recentes %>%
left_join(lookup_municipios, by = "chave_muni") %>%
group_by(code_muni, risp) %>%
summarise(total = sum(registros, na.rm = TRUE), .groups = "drop")
#Ordenar RISP
ordenar_risp <- function(x) {
risp_unicas <- unique(x)
num_risp <- stringr::str_extract(risp_unicas, "\\d+") |> as.integer()
factor(
x,
levels = risp_unicas[order(num_risp)]
)
}
# Junta no mapa da RISP
mapa_risp <- mapa_mg %>%
left_join(dados_batalhao, by = "code_muni")
```
# VISÃO GERAL {data-icon="fa-book-atlas"}
## Row {data-height="130"}
### **TOTAL DE VEÍCULOS ROUBADOS**
```{r}
flexdashboard::valueBox(format(total_roubo, big.mark="."), icon="fa-car-burst", color="#E74C3C")
```
### **TOTAL DE VEÍCULOS FURTADOS**
```{r}
flexdashboard::valueBox(format(total_furto, big.mark="."), icon="fa-car-on", color="#F39C12")
```
## Row {data-height="540"}
### **TABELA INTERATIVA - SÉRIE HISTÓRICA DOS ANOS DE 2023 A 2025**
```{r}
DT::datatable(
dados_recentes %>%
select(municipio, ano_fato, mes, natureza, registros, risp, rmbh) %>%
arrange(desc(ano_fato), desc(mes)),
colnames = c("Municipio", "Ano", "Mês", "Natureza", "Registros", "RISP", "RMBH"),
options = list(pageLength = 10, scrollX = TRUE, language = dt_lang_ptbr)
)
```
## Row {data-height="20"}
**Feito por:** João Eduardo
# GRÁFICOS ESTATÍSTICOS {data-icon="fa-chart-gantt"}
## Row {data-height="690"}
### **EVOLUÇÃO MENSAL DE FURTOS E ROUBOS DE VEÍCULOS**
```{r}
dados_recentes %>%
group_by(data_base, natureza) %>%
summarise(total = sum(registros, na.rm=TRUE), .groups="drop") %>%
ggplot(aes(data_base, total, color = natureza)) +
geom_line(linewidth=1.1) +
geom_point(size = 2) +
scale_color_manual(
values = c(
"ROUBO" = "#E74C3C", # vermelho
"FURTO" = "#F39C12" # laranja
)
) +
labs(
x = "Período",
y = "Total de Ocorrências",
color = "Natureza") +
theme_minimal()
```
## Row {data-height="690"}
### **COMPARATIVO ANUAL DE ROUBO E FURTO DE VEÍCULOS**
```{r}
dados_recentes %>%
group_by(ano_fato, natureza) %>%
summarise(total = sum(registros, na.rm = TRUE), .groups = "drop") %>%
ggplot(
aes(
x = factor(ano_fato),
y = total,
fill = natureza
)
) +
geom_col(
position = position_dodge(width = 0.8),
width = 0.7
)+
geom_text(
aes(label = scales::comma(total, big.mark = ".")),
position = position_dodge(width = 0.8),
vjust = -0.4,
size = 2.5
) +
scale_fill_manual(
values = c(
"ROUBO" = "#E74C3C", # vermelho
"FURTO" = "#F39C12" # laranja
)
) +
labs(
x = "Ano",
y = "Total de Ocorrências",
fill = "Natureza"
) +
theme_minimal()
```
## Row {data-height="690"}
### **TOP 10 MUNICÍPIOS – COMPOSIÇÃO POR NATUREZA - VALOR EXIBIDO É ABSOLUTO (ROUBO + FURTO)**
```{r}
dados_top10 <- dados_recentes %>%
group_by(municipio) %>%
summarise(total_municipio = sum(registros, na.rm = TRUE), .groups = "drop") %>%
slice_max(total_municipio, n = 10)
dados_top10_comp <- dados_recentes %>%
semi_join(dados_top10, by = "municipio") %>%
group_by(municipio, natureza) %>%
summarise(total = sum(registros, na.rm = TRUE), .groups = "drop") %>%
left_join(dados_top10, by = "municipio")
ggplot(
dados_top10_comp,
aes(
x = reorder(municipio, total_municipio),
y = total,
fill = natureza
)
) +
geom_col(width = 0.7) +
#RÓTULO DENTRO DA BARRA PARA BELO HORIZONTE
geom_text(
data = dados_top10 %>% filter(municipio == "BELO HORIZONTE"),
aes(
x = municipio,
y = total_municipio * 0.98,
label = scales::comma(total_municipio, big.mark = ".")
),
inherit.aes = FALSE,
hjust = 1,
size = 2.5,
) +
#RÓTULO FORA DA BARRA – DEMAIS MUNICÍPIOS
geom_text(
data = dados_top10 %>% filter(municipio != "BELO HORIZONTE"),
aes(
x = reorder(municipio, total_municipio),
y = total_municipio,
label = scales::comma(total_municipio, big.mark = ".")
),
inherit.aes = FALSE,
hjust = -0.15,
size = 2.5
) +
coord_flip() +
scale_fill_manual(
values = c(
"ROUBO" = "#E74C3C",
"FURTO" = "#F39C12"
)
) +
labs(
x = "Municípios",
y = "Total de Ocorrências",
fill = "Natureza"
) +
theme_minimal() +
theme(
legend.position = "right",
plot.title = element_text(face = "bold")
)
```
## Row {data-height="690"}
### **SAZONALIDADE MENSAL – ROUBO X FURTO**
```{r}
meses_pt <- c(
"1" = "JAN", "2" = "FEV", "3" = "MAR",
"4" = "ABR", "5" = "MAI", "6" = "JUN",
"7" = "JUL", "8" = "AGO", "9" = "SET",
"10" = "OUT", "11" = "NOV", "12" = "DEZ"
)
dados_recentes %>%
group_by(mes, natureza) %>%
summarise(total = sum(registros, na.rm = TRUE), .groups = "drop") %>%
ggplot(aes(
x = factor(mes),
y = total,
fill = natureza
)) +
geom_col(
position = position_dodge(width = 0.8),
width = 0.7
) +
geom_text(
aes(label = scales::comma(total, big.mark = ".")),
position = position_dodge(width = 0.8),
vjust = -0.3,
size = 2.5
) +
scale_x_discrete(labels = meses_pt) +
scale_fill_manual(
values = c(
"ROUBO" = "#E74C3C",
"FURTO" = "#F39C12"
)
) +
labs(
x = "Mês",
y = "Total de Ocorrências",
fill = "Natureza"
) +
theme_minimal() +
theme(
legend.position = "top",
plot.title = element_text(face = "bold")
)
```
# MAPA TEMÁTICO {data-icon="fa-map-location-dot"}
## Row {data-height="690"}
### **MAPA DE MINAS GERAIS POR MUNICÍPIOS - SÉRIE HISTÓRICA DOS ANOS DE 2023 A 2025**
```{r}
ggplot(mapa_historico) +
geom_sf(aes(fill = total), color = NA) +
labs(
fill = "Total"
) +
scale_fill_viridis_c(option="magma", direction=-1, na.value="grey90") +
theme_void()
```
## Row {data-height="690"}
### **MAPA DE MINAS GERAIS POR RISP - SÉRIE HISTÓRICA DOS ANOS DE 2023 A 2025**
```{r}
ggplot(mapa_risp) +
geom_sf(aes(fill = ordenar_risp(risp)), color = NA) +
labs(
fill = "RISP"
) +
theme_void() +
theme(
legend.position = "right",
legend.title = element_text(size = 9),
legend.text = element_text(size = 8),
legend.key.size = unit(0.5, "cm"),
plot.title = element_text(face = "bold", size = 14)
)
```