VISÃO GERAL

Row

TOTAL DE VEÍCULOS ROUBADOS

13.606

TOTAL DE VEÍCULOS FURTADOS

73.387

Row

TABELA INTERATIVA - SÉRIE HISTÓRICA DOS ANOS DE 2023 A 2025

Row

Feito por: João Eduardo

GRÁFICOS ESTATÍSTICOS

Row

EVOLUÇÃO MENSAL DE FURTOS E ROUBOS DE VEÍCULOS

Row

COMPARATIVO ANUAL DE ROUBO E FURTO DE VEÍCULOS

Row

TOP 10 MUNICÍPIOS – COMPOSIÇÃO POR NATUREZA - VALOR EXIBIDO É ABSOLUTO (ROUBO + FURTO)

Row

SAZONALIDADE MENSAL – ROUBO X FURTO

MAPA TEMÁTICO

Row

MAPA DE MINAS GERAIS POR MUNICÍPIOS - SÉRIE HISTÓRICA DOS ANOS DE 2023 A 2025

Row

MAPA DE MINAS GERAIS POR RISP - SÉRIE HISTÓRICA DOS ANOS DE 2023 A 2025

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