Foto: Cadu Silva / Diário de Pernambuco  ·  diariodepernambuco.com.br

Acidentes de Trânsito em Recife 2024

Padrões Ocultos em 5.315 Registros

Alana Lins
Professor: Ermeson Andrade
Entrega: 10 de junho de 2026
CAPD

1. Introdução

Contexto

No período registrado pela CTTU em 2024, foram documentados 5.315 acidentes — dos quais 4.353 resultaram em vítimas. Isso representa, em média, quase 15 acidentes com vítimas por dia no intervalo de horas coberto pelo dataset. 33 desses registros resultaram em morte e 4.320 registraram ao menos uma vítima com lesão. Esses números traduzem uma violência silenciosa e cotidiana que raramente ocupa manchetes, mas que impõe custos humanos, sociais e econômicos imensuráveis às famílias recifenses e ao sistema público de saúde.

Dados e Metodologia

Os dados analisados neste relatório foram obtidos no Portal de Dados Abertos do Recife, mantido pela CTTU, e cobrem todos os boletins de ocorrência lavrados por agentes de trânsito durante o ano de 2024. A metodologia adotada segue um pipeline estruturado de três etapas: importação e diagnóstico dos dados brutos; limpeza e transformação, com remoção de colunas sem dados e criação de variáveis derivadas; e análise exploratória por meio de múltiplas dimensões — temporal, espacial e de perfil de vítimas.

Do ponto de vista técnico, toda a análise foi conduzida em R, com o ecossistema tidyverse para manipulação e visualização, lubridate para tratamento de datas e horas, e pacotes de visualização interativa como plotly, leaflet, DT e wordcloud2. Variáveis derivadas foram criadas ao longo do pipeline — hora_int, dia_semana, mes_num, total_veiculos, tipo_dia e taxa_letalidade — para enriquecer as análises além do que as colunas originais permitiriam. A técnica de pivot_longer foi empregada para converter dados de formato largo em longo, permitindo análises de proporção e séries temporais comparativas.

Relevância

Os resultados desta análise têm aplicabilidade direta para três grupos de stakeholders. Para a CTTU, os padrões identificados oferecem subsídio para alocar agentes de fiscalização nos horários e locais de maior risco. Para a Secretaria de Mobilidade Urbana, os dados sobre atropelamentos e bairros críticos orientam intervenções de infraestrutura — travessias elevadas, iluminação e redução de velocidade. Para pesquisadores de segurança viária, o dataset e o código aberto permitem replicação, extensão temporal e comparação com os demais anos disponíveis no mesmo portal.

2. Pacotes Requeridos

Instalação e Carregamento

Os pacotes abaixo são necessários para reproduzir toda a análise desenvolvida neste relatório. Cada um cumpre uma função específica no pipeline, desde a manipulação dos dados brutos até a geração dos mapas e gráficos interativos.

Pacote Função no relatório
tidyverse Conjunto de pacotes base da análise: dplyr para filtros e agrupamentos, ggplot2 para gráficos estáticos, tidyr para pivotagem de colunas, readr para importação de CSV e stringr para manipulação de texto.
lubridate Facilita operações com datas e horas — extrai hora do dia, dia da semana e mês a partir do campo datetime, essenciais para todas as análises temporais.
DT Gera tabelas HTML interativas com busca, ordenação e paginação, usadas para exibir o dataset limpo e os rankings de bairros.
kableExtra Formata tabelas estáticas com estilo (listras, hover, bordas coloridas), utilizado na tabela de variáveis de interesse.
plotly Converte gráficos em visuais interativos com tooltip e zoom, permitindo ao leitor explorar valores exatos ao passar o cursor.
leaflet Cria mapas interativos com marcadores clicáveis e camadas de fundo (tiles), usado para visualizar a distribuição geográfica dos acidentes por bairro.
scales Formata os eixos dos gráficos com percentuais, notação de milhar e outros padrões numéricos do ggplot2.
ggthemes Oferece temas visuais adicionais para o ggplot2, ampliando as opções de estilo além do theme_minimal padrão.
rmdformats Fornece o tema visual readthedown utilizado neste documento, com sumário lateral e destaque de código.
wordcloud2 Gera nuvens de palavras interativas em HTML, usada para visualizar os logradouros com maior frequência de acidentes.
# tidyverse: conjunto de pacotes para manipulação e visualização de dados.
# Inclui dplyr (filtros e agrupamentos), ggplot2 (gráficos), tidyr (pivotagem),
# readr (importação de CSV), stringr (manipulação de texto) e forcats (fatores).
library(tidyverse)

# lubridate: facilita operações com datas e horas, como extrair hora do dia,
# dia da semana e mês a partir de um campo datetime.
library(lubridate)

# DT: gera tabelas HTML interativas com busca, ordenação e paginação,
# tornando a exploração dos dados mais dinâmica no relatório.
library(DT)

# kableExtra: formata tabelas estáticas com estilo (listras, hover, bordas),
# ideal para tabelas de resumo e descrição de variáveis.
library(kableExtra)

# plotly: converte gráficos em visuais interativos com hover e zoom,
# permitindo que o leitor explore os dados diretamente no HTML.
library(plotly)

# leaflet: cria mapas interativos com marcadores clicáveis e camadas de tiles,
# usado para visualizar a distribuição geográfica dos acidentes por bairro.
library(leaflet)

# scales: oferece funções para formatar eixos de gráficos com percentuais,
# notação de milhar e outros padrões de apresentação numérica.
library(scales)

# ggthemes: temas adicionais para ggplot2, ampliando as opções visuais
# além do theme_minimal e theme_classic padrão.
library(ggthemes)

# rmdformats: fornece temas visuais para relatórios RMarkdown, como o tema
# readthedown utilizado neste documento, com sumário lateral e destaque de código.
library(rmdformats)

# wordcloud2: gera nuvens de palavras interativas em HTML, usada para
# visualizar os logradouros com maior frequência de acidentes (G13 — bônus).
library(wordcloud2)

3. Preparação dos Dados

Fonte dos Dados

Os dados utilizados nesta análise são públicos e estão disponíveis em:

🔗 dados.recife.pe.gov.br — Acidentes de Trânsito com e sem Vítimas

O portal é mantido pela Prefeitura do Recife. O registro e a coleta das ocorrências são de responsabilidade da CTTU — Autarquia de Trânsito e Transporte Urbano do Recife, órgão responsável pelo monitoramento e gestão do trânsito na cidade.

Descrição do Dataset

Propósito original: O dataset foi concebido para registrar todas as ocorrências de sinistros de trânsito — com e sem vítimas — verificadas na malha viária de Recife. Os dados são originados diretamente dos boletins de ocorrência lavrados pelos agentes da CTTU no local do acidente, e não de relatórios estatísticos anuais consolidados. O próprio portal adverte que os números podem divergir dos divulgados em relatórios oficiais, justamente por essa diferença de metodologia de registro.

Período de coleta: O conjunto de dados cobre o período de junho de 2015 a 2024, distribuído em nove arquivos CSV anuais. Para esta análise foi utilizado exclusivamente o arquivo referente ao ano de 2024, com dados do intervalo de janeiro a dezembro de 2024.

Dimensões originais: O arquivo de 2024 contém 5.315 registros e 37 variáveis, cobrindo desde a identificação do acidente (protocolo, data, hora e localização) até a caracterização dos veículos envolvidos, o perfil das vítimas e condições do ambiente viário.

Variáveis e sua organização: As 37 colunas se dividem em quatro grupos principais: (1) identificação e localização — protocolo, data, hora, bairro, endereço e coordenadas de cruzamento; (2) caracterização do acidente — natureza (COM VÍTIMA, SEM VÍTIMA, VÍTIMA FATAL), tipo (colisão, atropelamento, choque etc.) e situação do registro; (3) contagem de veículos e vítimas — automóveis, motos, ônibus, caminhões, ciclistas, pedestres, vítimas e vítimas fatais; (4) condições do ambiente — variáveis como sinalização, condição da via, situação do semáforo e velocidade máxima permitida.

Peculiaridades dos dados de origem:

  • Vírgula como separador decimal: todas as colunas de contagem numérica (veículos e vítimas) foram registradas no padrão brasileiro, usando vírgula como decimal — por exemplo, "1,0" em vez de 1. O R interpreta esses valores como texto, exigindo conversão antes de qualquer operação aritmética.

  • Colunas completamente vazias: 11 das 37 variáveis — justamente as que descrevem as condições do ambiente viário (situacao_semaforo, sinalizacao, condicao_via, conservacao_via, velocidade_max_via, entre outras) — não possuem nenhum valor preenchido em toda a base. Isso indica que essas informações não eram coletadas sistematicamente pelos agentes no campo durante o ano de 2024.

  • Valores ausentes no campo numero: o endereço do acidente inclui um campo de número predial que apresenta aproximadamente 55% de valores ausentes (NA). Isso ocorre porque muitos acidentes acontecem em cruzamentos, viadutos ou trechos de via sem numeração definida. Esses registros foram mantidos na base com NA, pois a ausência do número não invalida o restante da informação de localização.

  • Campo Protocolo com decimal: o identificador único de cada ocorrência foi armazenado no formato "292972,0", com uma casa decimal desnecessária, provavelmente por um artefato de exportação do sistema da CTTU.

  • ⚠️ Limitação crítica — cobertura horária parcial: a coluna hora cobre apenas o período de 01:00 a 12:59. Não existe nenhum registro com hora 00:xx nem de 13h a 23h — verificado pelos 718 valores únicos de hora, todos entre "01:00:00" e "12:59:00". Todas as análises temporais por hora deste relatório representam apenas o período matutino e de madrugada, sem cobertura dos acidentes vespertinos e noturnos. As hipóteses mais prováveis são: recorte do sistema de registro da CTTU, turno específico de operação ou período de exportação dos dados. Esta limitação é registrada explicitamente em cada análise temporal ao longo do relatório.

  • Licença: os dados são disponibilizados sob a licença Open Database License (ODbL), que permite uso, redistribuição e produção de obras derivadas, desde que a fonte seja atribuída e obras derivadas sejam compartilhadas sob a mesma licença.

  • Atualização: o portal realiza atualizações semestrais, com a última atualização registrada em março de 2026.

Importação

O arquivo disponibilizado no portal é um CSV no padrão brasileiro: usa ponto-e-vírgula (;) como separador de colunas e vírgula (,) como separador decimal. Por isso, utilizamos read_csv2() em vez de read_csv() — a função já espera esse padrão e evita que números como "1,0" sejam lidos como texto de forma inesperada.

A coluna hora recebeu tratamento especial: foi forçada a ser lida como texto (col_character()). Sem isso, o R tentaria convertê-la automaticamente para um formato de hora e perderia os zeros à esquerda, quebrando a montagem do campo datetime na etapa seguinte.

Após a importação, dim() e glimpse() são usados para confirmar as dimensões do arquivo e inspecionar os tipos de dados atribuídos automaticamente — passo importante para identificar colunas que precisarão de correção antes da análise.

# read_csv2(): separador ";" e decimal "," — padrão dos arquivos brasileiros
# hora lida como texto para preservar o formato e permitir montagem do datetime
dados_brutos <- read_csv2("base de dados.csv",
                          col_types = cols(hora = col_character()))

# Verificar dimensões e tipos de dados do dataset original
dim(dados_brutos)
## [1] 5315   37
glimpse(dados_brutos)
## Rows: 5,315
## Columns: 37
## $ Protocolo                 <dbl> 292972, 292973, 292974, 292980, 292985, 2930…
## $ data                      <date> 2024-01-01, 2024-01-01, 2024-01-01, 2024-01…
## $ hora                      <chr> "06:05:00", "09:14:00", "10:31:00", "04:11:0…
## $ natureza                  <chr> "COM VÍTIMA", "COM VÍTIMA", "COM VÍTIMA", "C…
## $ situacao                  <chr> "FINALIZADA", "FINALIZADA", "FINALIZADA", "F…
## $ bairro                    <chr> "VASCO DA GAMA", "VASCO DA GAMA", "VASCO DA …
## $ endereco                  <chr> "AV NORTE MIGUEL ARRAES DE ALENCAR", "RUA VA…
## $ numero                    <chr> NA, "1353", "290", NA, "1672", NA, "3850", "…
## $ detalhe_endereco_acidente <chr> NA, NA, NA, "AV NORTE MIGUEL ARRAES DE ALENC…
## $ complemento               <chr> "E/F A IGREJA QUADRANGULAR SENTIDO MACAXEIRA…
## $ bairro_cruzamento         <chr> "VASCO DA GAMA", "VASCO DA GAMA", "VASCO DA …
## $ num_semaforo              <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ sentido_via               <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ tipo                      <chr> "COLISÃO LATERAL", "COLISÃO", "COLISÃO", "CO…
## $ auto                      <dbl> 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1,…
## $ moto                      <dbl> 0, 1, 3, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 1,…
## $ ciclom                    <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ ciclista                  <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ pedestre                  <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,…
## $ onibus                    <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ caminhao                  <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,…
## $ viatura                   <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,…
## $ outros                    <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ vitimas                   <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 0, 1,…
## $ vitimasfatais             <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ acidente_verificado       <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ tempo_clima               <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ situacao_semaforo         <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ sinalizacao               <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ condicao_via              <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ conservacao_via           <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ ponto_controle            <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ situacao_placa            <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ velocidade_max_via        <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ mao_direcao               <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ divisao_via1              <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ divisao_via2              <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …

Limpeza

Antes de qualquer análise, o dataset original passou por um pipeline de limpeza estruturado em nove etapas. A ordem das etapas é deliberada: primeiro eliminamos o que não serve (colunas vazias), depois construímos o que vamos precisar (datas, variáveis derivadas), corrigimos os tipos de dados (decimais, fatores), e por último filtramos os registros inválidos — pois só faz sentido filtrar após os dados estarem no tipo correto.


Etapa 1 — Remoção das colunas 100% nulas

Ao inspecionar o dataset com glimpse(), identificamos que 11 das 37 variáveis não possuem nenhum valor preenchido em nenhum dos 5.315 registros. São as colunas que descrevem condições do ambiente viário (situacao_semaforo, sinalizacao, condicao_via, conservacao_via, velocidade_max_via, entre outras). Como estão completamente vazias, não oferecem informação alguma — mantê-las apenas aumentaria o tamanho do objeto em memória e poluiria os outputs de glimpse() e summary(). Por isso, foram removidas logo no início do pipeline.

# essas 11 colunas estão 100% vazias no arquivo de 2024 — não tem o que aproveitar
dados <- dados_brutos %>%
  select(-acidente_verificado, -tempo_clima, -situacao_semaforo, -sinalizacao,
         -condicao_via, -conservacao_via, -ponto_controle, -situacao_placa,
         -velocidade_max_via, -mao_direcao, -divisao_via1, -divisao_via2)

Etapa 2 — Unificação de data e hora e criação de variáveis temporais

O dataset armazena data e hora em colunas separadas, o que impede qualquer operação temporal direta. As duas foram concatenadas em um único campo datetime (tipo POSIXct) usando paste() e ymd_hms(). A partir desse campo unificado, derivamos três variáveis que serão a base das análises temporais: hora_int (inteiro de 1 a 12, para os gráficos por hora do dia), dia_semana (fator com nome por extenso em português, para os gráficos semanais) e mes/mes_num (mês por extenso e como inteiro, para a evolução mensal). Sem essa etapa, não seria possível responder “em qual horário ocorrem mais acidentes?” ou “qual dia da semana é mais crítico?”.

# data e hora vêm em colunas separadas no CSV — junta tudo antes de qualquer cálculo temporal
dados <- dados %>%
  mutate(
    datetime   = ymd_hms(paste(data, hora)),
    hora_int   = hour(datetime),               # só o número inteiro da hora (1, 2 ... 12)
    dia_semana = NOMES_DIA[wday(datetime, week_start = 1)],
    mes        = NOMES_MES[month(datetime)],
    mes_num    = month(datetime)               # versão numérica do mês, pra ordenar eixos
  )

Etapa 3 — Correção do campo Protocolo

O identificador único de cada ocorrência foi exportado pelo sistema da CTTU no formato "292972,0" — com uma casa decimal completamente desnecessária, provavelmente um artefato do sistema de origem. Como o protocolo é um código identificador (não um valor numérico a ser somado ou comparado), a parte decimal foi descartada com gsub() e o campo convertido para inteiro. Isso evita que o R armazene um campo de ID como texto ou como número de ponto flutuante.

# o sistema da CTTU exporta o protocolo como "292972,0" — descarta tudo depois da vírgula
dados <- dados %>%
  mutate(Protocolo = as.integer(gsub(",.*", "", Protocolo)))

Etapa 4 — Conversão dos campos de contagem de veículos e vítimas

Todas as colunas de quantidade — automóveis, motos, pedestres, vítimas etc. — foram registradas no padrão brasileiro com vírgula decimal ("1,0" em vez de 1). O R, ao importar esses valores, os trata como texto (character), o que impede qualquer operação aritmética: somar "1,0" + "2,0" resulta em erro. A correção consiste em substituir a vírgula por ponto e converter para numérico com as.numeric(). Esta etapa precisa ocorrer antes da Etapa 5, pois lá vamos somar essas colunas para criar total_veiculos.

# COLS_NUMERICAS é um vetor de caracteres definido no chunk constantes
dados <- dados %>%
  mutate(across(all_of(COLS_NUMERICAS), ~ as.numeric(gsub(",", ".", .))))

Etapa 5 — Criação de variáveis derivadas

Com os campos numéricos corrigidos, criamos duas variáveis que serão usadas repetidamente nas análises. total_veiculos soma todos os tipos de veículo por acidente, permitindo medir a magnitude do evento independentemente do tipo de veículo. tipo_dia classifica cada registro em “Dia útil” ou “Final de semana”, o que possibilita comparar esses dois perfis de circulação sem precisar repetir a lógica de agrupamento em cada gráfico.

# DIAS_FDS é um vetor definido no chunk constantes: c("sábado", "domingo")
dados <- dados %>%
  mutate(
    total_veiculos = auto + moto + ciclom + ciclista +
                     onibus + caminhao + viatura + outros,
    tipo_dia       = ifelse(dia_semana %in% DIAS_FDS, "Final de semana", "Dia útil")
  )

Etapa 6 — Padronização de campos de texto

Os campos natureza, tipo, bairro e endereco apresentavam inconsistências típicas de digitação manual: letras minúsculas misturadas com maiúsculas e espaços extras no início ou no final do valor. Por exemplo, "colisão", "Colisão" e " COLISÃO" seriam tratados como categorias distintas em um group_by(). A padronização com str_to_upper() e str_trim() garante que todos os valores estejam no mesmo formato, evitando duplicatas artificiais nos agrupamentos e nos gráficos.

# across(all_of(COLS_TEXTO), ...) aplica a mesma transformação às 4 colunas de uma vez
# COLS_TEXTO = c("natureza","tipo","bairro","endereco") — definido no chunk constantes
dados <- dados %>%
  mutate(across(all_of(COLS_TEXTO), ~ str_trim(str_to_upper(.))))

Etapa 7 — Conversão para factor com ordem lógica

Após a padronização do texto, os campos categóricos foram convertidos para factor. Isso é essencial por dois motivos: (1) o ggplot2 respeita a ordem dos níveis do factor ao ordenar eixos e legendas — sem isso, natureza apareceria em ordem alfabética (COM VÍTIMA, SEM VÍTIMA, VÍTIMA FATAL) em vez de por gravidade crescente; (2) dia_semana sem ordem de factor seria plotado alfabeticamente (domingo primeiro), não na ordem correta da semana. A conversão precisa acontecer após a padronização de texto (Etapa 6), pois os níveis definidos aqui precisam bater exatamente com os valores já padronizados.

# NIVEIS_NATUREZA e NIVEIS_SEMANA definidos no chunk constantes — fonte única de verdade
dados <- dados %>%
  mutate(
    natureza   = factor(natureza,   levels = NIVEIS_NATUREZA),
    tipo       = factor(tipo),
    dia_semana = factor(dia_semana, levels = NIVEIS_SEMANA)
  )

Etapa 8 — Remoção de registros inválidos por situação

O dataset contém registros com situacao igual a "CANCELADA" (204 casos) e "DUPLICIDADE" (181 casos). Esses registros não representam acidentes reais: os cancelados foram abertos por engano ou descartados pelo agente, e os duplicados são entradas repetidas do mesmo evento. Mantê-los inflaria as contagens e distorceria qualquer análise de volume ou gravidade. Optamos por filtrar após a conversão de tipos (Etapas 3–7) para garantir que a comparação de strings seja feita sobre valores padronizados.

# SITUACOES_VALIDAS = c("FINALIZADA","EM ATENDIMENTO") — definido no chunk constantes
dados <- dados %>%
  filter(situacao %in% SITUACOES_VALIDAS)

Etapa 9 — Remoção de registros sem tipo de acidente

Cerca de 30 registros (0,6% dos dados) não possuem valor na coluna tipo. Sem saber se o evento foi uma colisão, um atropelamento ou um choque, não é possível classificá-lo em nenhuma das análises por tipo de acidente. Como representam uma fração mínima da base e não há como inferir o tipo retroativamente, foram removidos. Ao final, verificamos com cat() o total de registros e colunas resultantes para confirmar que o pipeline produziu o resultado esperado.

dados <- dados %>%
  filter(!is.na(tipo))  # ~30 registros sem tipo — sem essa info não dá pra classificar

# conferindo o que sobrou depois do pipeline inteiro
cat("Registros após limpeza:", nrow(dados), "\n")
## Registros após limpeza: 4904
cat("Colunas após limpeza:", ncol(dados), "\n")
## Colunas após limpeza: 32

Dataset Limpo

Para conferir o resultado da limpeza, são exibidas abaixo as dez primeiras observações com as colunas mais relevantes para a análise. A tabela é interativa: é possível buscar, ordenar e navegar pelas páginas.

# Mostrar as primeiras 10 linhas válidas do dataset limpo de forma interativa
# na.omit() garante que não apareçam NAs no campo datetime na tabela
dados %>%
  select(datetime, natureza, tipo, bairro, vitimas,
         vitimasfatais, total_veiculos, hora_int, dia_semana) %>%
  filter(!is.na(datetime)) %>%
  head(10) %>%
  datatable(
    caption  = "Dataset limpo — primeiras 10 linhas (de ~4.930 após limpeza)",
    options  = list(pageLength = 10, scrollX = TRUE),
    rownames = FALSE
  )

Variáveis de Interesse

A tabela a seguir descreve as principais variáveis utilizadas nas análises deste relatório, indicando o tipo de dado, uma descrição do conteúdo, o percentual de valores ausentes após a limpeza e a origem da variável — original do dataset ou criada durante o pipeline de transformação. Linhas em azul claro indicam variáveis derivadas.

# Definir o tibble separadamente para poder referenciar a coluna Origem no row_spec
variaveis_df <- tibble(
  `Variável`  = c("datetime", "natureza", "tipo", "bairro", "hora_int",
                  "dia_semana", "mes", "mes_num", "vitimas", "vitimasfatais",
                  "total_veiculos", "tipo_dia", "auto", "moto", "pedestre", "ciclista"),
  `Tipo`      = c("POSIXct", "Factor (3 níveis)", "Factor (13 níveis)", "Character",
                  "Integer (1–12)", "Factor (7 níveis)", "Factor (12 níveis)",
                  "Integer (1–12)", "Numeric", "Numeric", "Numeric", "Character",
                  "Numeric", "Numeric", "Numeric", "Numeric"),
  `Descrição` = c(
    "Data e hora unificadas do acidente",
    "Gravidade: SEM VÍTIMA / COM VÍTIMA / VÍTIMA FATAL",
    "Tipo: COLISÃO, ATROPELAMENTO, CHOQUE etc.",
    "Bairro da ocorrência em Recife",
    "Hora do dia — cobertura parcial: 1h a 12h59 (13h–23h ausentes no dataset)",
    "Dia da semana por extenso, ordenado de segunda-feira a domingo",
    "Mês do ano por extenso",
    "Mês do ano como inteiro (1 = janeiro, 12 = dezembro)",
    "Número de vítimas com lesão",
    "Número de vítimas fatais",
    "Soma de todos os veículos envolvidos (criada)",
    "Dia útil ou Final de semana (criada)",
    "Automóveis envolvidos",
    "Motocicletas envolvidas",
    "Pedestres envolvidos",
    "Ciclistas envolvidos"
  ),
  `Nulos (%)` = c("0%", "0%", "0%", "0,1%", "0%", "0%", "0%", "0%",
                  "0%", "0%", "0%", "0%", "0%", "0%", "0%", "0%"),
  `Origem`    = c("Criada", "Original", "Original", "Original", "Criada",
                  "Criada", "Criada", "Criada", "Original", "Original",
                  "Criada", "Criada", "Original", "Original", "Original", "Original")
)

variaveis_df %>%
  kable(caption = "Variáveis de interesse no dataset limpo", escape = FALSE) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE) %>%
  row_spec(which(variaveis_df$Origem == "Criada"),
           background = "#1a2a3a", color = "#FFD700")
Variáveis de interesse no dataset limpo
Variável Tipo Descrição Nulos (%) Origem
datetime POSIXct Data e hora unificadas do acidente 0% Criada
natureza Factor (3 níveis) Gravidade: SEM VÍTIMA / COM VÍTIMA / VÍTIMA FATAL 0% Original
tipo Factor (13 níveis) Tipo: COLISÃO, ATROPELAMENTO, CHOQUE etc. 0% Original
bairro Character Bairro da ocorrência em Recife 0,1% Original
hora_int Integer (1–12) Hora do dia — cobertura parcial: 1h a 12h59 (13h–23h ausentes no dataset) 0% Criada
dia_semana Factor (7 níveis) Dia da semana por extenso, ordenado de segunda-feira a domingo 0% Criada
mes Factor (12 níveis) Mês do ano por extenso 0% Criada
mes_num Integer (1–12) Mês do ano como inteiro (1 = janeiro, 12 = dezembro) 0% Criada
vitimas Numeric Número de vítimas com lesão 0% Original
vitimasfatais Numeric Número de vítimas fatais 0% Original
total_veiculos Numeric Soma de todos os veículos envolvidos (criada) 0% Criada
tipo_dia Character Dia útil ou Final de semana (criada) 0% Criada
auto Numeric Automóveis envolvidos 0% Original
moto Numeric Motocicletas envolvidas 0% Original
pedestre Numeric Pedestres envolvidos 0% Original
ciclista Numeric Ciclistas envolvidos 0% Original

4. Análise Exploratória

Esta seção reúne 13 análises organizadas em sub-abas temáticas. Cada uma parte dos dados brutos da CTTU e aplica alguma transformação — agrupamento, criação de variável, pivotagem ou filtragem — para revelar um padrão específico dos acidentes de trânsito em Recife em 2024. A primeira sub-aba (Além dos Dados Brutos) descreve as técnicas utilizadas; as demais apresentam os resultados. Todas as análises cobrem o intervalo de 1h a 12h, que é o período disponível no dataset — uma limitação registrada em cada sub-aba onde é relevante.

Além dos Dados Brutos

Um dos objetivos centrais desta análise é descobrir informações que não são auto-evidentes — ou seja, ir além de simplesmente plotar as colunas como estão. Para isso, três estratégias foram adotadas ao longo das sub-abas a seguir:


1. Criação de variáveis derivadas

Nenhuma das variáveis abaixo existia no dataset original. Todas foram construídas a partir de transformações sobre os dados brutos:

Variável Como foi criada Informação nova que gera
datetime paste(data, hora) + ymd_hms() Campo unificado que viabiliza toda análise temporal
hora_int hour(datetime) Hora como inteiro — permite agrupar por faixa horária
dia_semana wday(..., locale="pt_BR") Dia por extenso com ordem cronológica correta
tipo_dia ifelse(dia_semana %in% DIAS_FDS, ...) Dimensão “Dia útil vs. Final de semana” inexistente no original
total_veiculos Soma de 8 colunas de veículos Magnitude total do acidente em um único campo
taxa_letalidade fatais / total * 100 por tipo Revela que atropelamentos são ~10× mais letais que colisões
indice_perigo (fatais×3 + com_vítima×1) / total * 100 Ranking ponderado que inverte a ordem dos bairros mais perigosos
pct_com_vitima com_vitima / total * 100 por bairro Compara bairros de volumes diferentes em escala comum

2. Reestruturação com pivot_longer

Utilizada em três sub-abas distintas para transformar colunas em linhas e permitir comparações que os dados originais não viabilizavam:

  • Perfil de Vítimas — pedestres, ciclistas e motos passam de 3 colunas para 1 coluna + 1 chave, gerando um único gráfico comparativo
  • Motos vs. Automóveis — duas séries mensais em colunas separadas comparadas no mesmo gráfico de linhas
  • Gravidade por Veículo — barras 100% empilhadas que mostram proporção de desfechos graves por categoria

3. Comparação de subconjuntos com bind_rows

Na sub-aba Acidentes Fatais, os dados foram divididos em dois grupos — todos os acidentes e apenas os fatais — e reunidos com bind_rows para comparar suas distribuições horárias num mesmo gráfico. O resultado é contra-intuitivo: os fatais se concentram na madrugada (1h–5h), enquanto o volume geral atinge pico às 7h–8h.

Por Hora do Dia

A distribuição dos acidentes ao longo das 24 horas do dia revela padrões importantes sobre os momentos de maior risco no trânsito de Recife. Para tornar a análise mais rica, cada barra foi dividida pela natureza do acidente, permitindo visualizar simultaneamente o volume total e o perfil de gravidade em cada horário.

por_hora <- dados %>%
  count(hora_int, natureza) %>%
  group_by(hora_int) %>%
  mutate(total_hora = sum(n))  # total por hora pra ter o contexto de volume, mesmo que o gráfico use n

ggplot(por_hora, aes(x = hora_int, y = n, fill = natureza)) +
  geom_col() +
  scale_fill_manual(values = COR_NATUREZA, name = "Natureza") +
  escala_hora +
  labs(
    title   = "Acidentes por hora do dia em Recife (cobertura: 1h–12h)",
    x       = "Hora do dia",
    y       = "Número de acidentes",
    caption = FONTE
  ) +
  tema_legenda

Uma limitação importante desta análise deve ser registrada: o dataset disponibilizado no portal contém registros de horário apenas entre 1h e 12h, sugerindo que os dados do período vespertino e noturno (13h–23h) não foram incluídos nesta versão do arquivo — o que representa uma lacuna relevante e deve ser considerada na interpretação dos resultados.

Dentro do período disponível, o volume é mais baixo em 2h, seguido de um crescimento gradual e consistente até o pico às 7h–8h — correspondendo ao horário de entrada no trabalho e nas escolas, o momento de maior fluxo veicular da manhã. A partir das 9h, o volume recua progressivamente ao longo do restante da manhã, refletindo o fluxo mais distribuído após o rush. Os acidentes classificados como VÍTIMA FATAL, embora minoritários em volume absoluto, aparecem distribuídos ao longo de todo o período registrado, com presença relevante nas madrugadas (1h–4h), quando as vias tendem a estar mais vazias e os motoristas mais sonolentos ou sob efeito de álcool.

Por Dia da Semana

Para entender se o comportamento dos acidentes muda conforme o dia da semana, foi criada a variável tipo_dia, que agrupa os dias em Dia útil e Final de semana. O gráfico compara o volume e o perfil de gravidade dos acidentes em cada dia, permitindo identificar padrões distintos entre os períodos.

# tipo_dia criada na Etapa 5 do pipeline de limpeza
por_dia <- dados %>%
  count(dia_semana, natureza, tipo_dia)

ggplot(por_dia, aes(x = dia_semana, y = n, fill = natureza)) +
  geom_col(position = "dodge") +
  scale_fill_manual(values = COR_NATUREZA) +
  scale_x_discrete(labels = c("Seg.","Ter.","Qua.","Qui.","Sex.","Sáb.","Dom.")) +
  labs(
    title   = "Acidentes por dia da semana",
    x       = "Dia da semana",
    y       = "Número de acidentes",
    fill    = "Natureza",
    caption = FONTE
  ) +
  tema_legenda

Em dias úteis, especialmente de segunda a quinta-feira, o volume de acidentes é consideravelmente maior do que nos fins de semana, o que reflete diretamente o maior fluxo veicular nos dias de trabalho e escola. A segunda-feira e a terça-feira concentram os maiores volumes no período matutino coberto pelo dataset — o que pode estar associado ao retorno das atividades após o fim de semana e ao intenso fluxo de deslocamentos para o trabalho e escolas nas primeiras horas da manhã. Nos fins de semana, o volume total cai consideravelmente, mas o perfil de gravidade se altera: a proporção de acidentes classificados como VÍTIMA FATAL é relativamente mais elevada no sábado e domingo, sugerindo que, embora haja menos veículos nas vias, a velocidade mais alta e o possível consumo de álcool contribuem para acidentes de maior impacto. Essa inversão entre volume e gravidade é um indicativo importante para políticas de fiscalização diferenciada por período da semana.

Uma ressalva fundamental deve ser registrada sobre esta análise: como o dataset cobre apenas o período de 1h a 12h, o período vespertino e noturno (13h a 23h) está completamente ausente. Isso é particularmente relevante para a análise de fins de semana, pois as horas de maior risco associadas ao lazer noturno — tipicamente entre 22h e 4h de sábado para domingo — estão sub-representadas. O padrão identificado reflete apenas o comportamento matutino e da madrugada, e os resultados de sábado e domingo devem ser interpretados com essa limitação em vista.

Top 20 Bairros

O volume de acidentes não se distribui de forma uniforme pelo território de Recife. Para identificar os bairros mais críticos, foi construído um ranking com as 20 localidades de maior incidência, combinando o total de acidentes, o número de ocorrências com vítimas, o número de vítimas fatais e o tipo de acidente mais frequente em cada bairro. A variável pct_com_vitima foi criada para expressar a proporção de acidentes que resultaram em vítima, permitindo comparar bairros de volumes distintos em uma escala comum.

# Calcular métricas por bairro e selecionar os 20 com mais acidentes
ranking_bairros <- dados %>%
  group_by(bairro) %>%
  summarise(
    total          = n(),
    com_vitima     = sum(natureza == "COM VÍTIMA"),
    fatais         = sum(vitimasfatais, na.rm = TRUE),
    tipo_principal = names(sort(table(tipo), decreasing = TRUE))[1],
    pct_com_vitima = round(com_vitima / total * 100, 1),
    .groups = "drop"
  ) %>%
  arrange(desc(total)) %>%
  head(20)

# Tabela DT interativa com formatStyle condicional em duas colunas
ranking_bairros %>%
  datatable(
    caption  = "Top 20 bairros com mais acidentes — Recife 2024",
    colnames = c("Bairro", "Total", "Com vítima", "Fatais",
                 "Tipo principal", "% com vítima"),
    options  = list(pageLength = 10, scrollX = TRUE),
    rownames = FALSE
  ) %>%
  formatStyle(
    "fatais",
    background = styleColorBar(range(ranking_bairros$fatais), "#8B0000")
  ) %>%
  formatStyle(
    "pct_com_vitima",
    background = styleColorBar(c(0, 100), "#7B6000")
  )

Boa Viagem lidera o ranking absoluto com folga, reflexo de sua extensão territorial, alta densidade populacional e intensa circulação de veículos em vias como a Avenida Boa Viagem e a Avenida Engenheiro Domingos Ferreira. Imbiribeira e Santo Amaro figuram logo em seguida, ambos cortados por corredores viários de grande fluxo. A coluna de vítimas fatais — representada pela barra vermelha — revela que bairros com alto volume nem sempre concentram o maior número de mortes: alguns bairros menores apresentam fatalidades desproporcionais ao seu volume total, o que motivará a criação de um índice ponderado na análise G14. É importante lembrar que, por cobrir apenas o período 1h–12h, o ranking subestima o volume real de acidentes em bairros com forte movimento vespertino e noturno.

Perfil de Vítimas por Tipo

Diferentes tipos de acidente envolvem perfis distintos de vítimas. Para compreender essa relação, foi calculado o total de pedestres, ciclistas e motociclistas envolvidos em cada tipo de acidente, utilizando pivot_longer para converter as três colunas de contagem em um formato longo adequado para visualização comparativa. A análise foi restrita aos oito tipos com maior volume de motociclistas envolvidos, que representam os cenários mais críticos para vítimas vulneráveis.

# Agrupar por tipo de acidente e somar cada categoria de vítima/veículo
perfil <- dados %>%
  group_by(tipo) %>%
  summarise(
    Pedestres = sum(pedestre,  na.rm = TRUE),
    Ciclistas = sum(ciclista,  na.rm = TRUE),
    Motos     = sum(moto,      na.rm = TRUE),
    .groups   = "drop"
  ) %>%
  arrange(desc(Motos)) %>%
  head(8) %>%
  # pivot_longer converte as três colunas de contagem em formato longo:
  # cada linha passa a ter um tipo de vítima e seu total
  pivot_longer(c(Pedestres, Ciclistas, Motos),
               names_to  = "tipo_vitima",
               values_to = "total")

# Gráfico interativo com plotly — permite hover com valores exatos
plot_ly(perfil,
        x      = ~tipo,
        y      = ~total,
        color  = ~tipo_vitima,
        type   = "bar",
        colors = c("#F5A623", "#74C2E1", "#D0021B")) %>%
  layout(
    barmode    = "group",
    title      = list(text = "Perfil de vítimas por tipo de acidente",
                      font = list(color = "#F0F0F0")),
    paper_bgcolor = "#1c1c1c",
    plot_bgcolor  = "#1c1c1c",
    xaxis = list(title      = "",
                 tickangle  = -30,
                 tickfont   = list(color = "#C0C0C0"),
                 gridcolor  = "#2e2e2e",
                 linecolor  = "#444"),
    yaxis = list(title      = "Total envolvidos",
                 titlefont  = list(color = "#C0C0C0"),
                 tickfont   = list(color = "#C0C0C0"),
                 gridcolor  = "#2e2e2e",
                 linecolor  = "#444"),
    legend = list(title      = list(text = "Tipo de vítima",
                                    font = list(color = "#F0F0F0")),
                  font       = list(color = "#F0F0F0"),
                  bgcolor    = "#2a2a2a",
                  bordercolor = "#444")
  )

O gráfico evidencia que as colisões dominam em volume absoluto de motociclistas envolvidos, o que é esperado dado que colisões são o tipo mais frequente no dataset. O atropelamento, embora de menor volume total, concentra praticamente todos os pedestres registrados — confirmando que esse tipo de acidente é o mais diretamente associado a vítimas a pé. A presença de ciclistas é relativamente baixa em todos os tipos, mas chama atenção nos atropelamentos e nas quedas, sugerindo vulnerabilidade estrutural desse grupo em situações de perda de controle ou conflito com veículos motorizados. O caráter interativo do gráfico permite inspecionar os valores exatos de cada combinação ao passar o cursor.

Mapa Interativo

A dimensão espacial dos acidentes é explorada por meio de um mapa interativo que posiciona os 20 bairros com coordenadas disponíveis, representando cada um por um círculo cujo raio é proporcional ao total de acidentes e cuja cor indica o número de vítimas fatais. O clique em cada marcador abre um popup com os dados detalhados do bairro. As coordenadas foram atribuídas manualmente com base nas posições geográficas centrais de cada bairro em Recife.

# Função auxiliar: remove acentos para join robusto entre datasets
norm_bairro <- function(x) iconv(x, from = "UTF-8", to = "ASCII//TRANSLIT") |> toupper() |> trimws()

# Coordenadas geográficas (centróides) dos 20 bairros mais críticos
# bairro_key: versão sem acento usada apenas para o join
coords_bairros <- tibble(
  bairro_key = c("BOA VIAGEM", "IMBIRIBEIRA", "SANTO AMARO", "IBURA", "AFOGADOS",
                 "SAO JOSE", "MADALENA", "CASA AMARELA", "PINA", "IPUTINGA",
                 "BOA VISTA", "CAXANGA", "CAMPO GRANDE", "CORDEIRO", "AREIAS",
                 "ENCRUZILHADA", "IPSEP", "AGUA FRIA", "NOVA DESCOBERTA", "DOIS IRMAOS"),
  lat = c(-8.1141, -8.1027, -8.0625, -8.1295, -8.0782,
          -8.0569, -8.0563, -8.0250, -8.1058, -8.0587,
          -8.0580, -8.0620, -8.0650, -8.0651, -8.0984,
          -8.0303, -8.1205, -7.9892, -8.0030, -8.0120),
  lon = c(-34.8955, -34.9077, -34.8949, -34.9393, -34.9214,
          -34.8735, -34.9058, -34.9338, -34.8975, -34.9291,
          -34.8850, -34.9135, -34.9220, -34.9195, -34.9328,
          -34.8950, -34.9268, -34.9103, -34.9150, -34.9180)
)

# Consolidar acidentes por bairro, normalizar acentos e fazer join
mapa_dados <- dados %>%
  mutate(bairro_key = norm_bairro(bairro)) %>%
  count(bairro, bairro_key, natureza) %>%
  pivot_wider(names_from = natureza, values_from = n, values_fill = 0) %>%
  rename(
    sem_vitima   = `SEM VÍTIMA`,
    com_vitima   = `COM VÍTIMA`,
    vitima_fatal = `VÍTIMA FATAL`
  ) %>%
  mutate(total = sem_vitima + com_vitima + vitima_fatal) %>%
  inner_join(coords_bairros, by = "bairro_key")

# Paleta de cores proporcional ao número de vítimas fatais
pal <- colorNumeric(c("#F5A623", "#D0021B"), domain = mapa_dados$vitima_fatal)

leaflet(mapa_dados) %>%
  addTiles() %>%
  addCircleMarkers(
    lng         = ~lon,
    lat         = ~lat,
    radius      = ~sqrt(total) * 1.5,   # raio proporcional ao total de acidentes
    color       = ~pal(vitima_fatal),    # cor proporcional às vítimas fatais
    fillOpacity = 0.75,
    popup = ~paste0(
      "<b>", bairro, "</b><br>",
      "Total de acidentes: ", total, "<br>",
      "Com vítima: ", com_vitima, "<br>",
      "Vítimas fatais: ", vitima_fatal
    )
  ) %>%
  addLegend("bottomright",
            pal    = pal,
            values = ~vitima_fatal,
            title  = "Vítimas fatais")

O mapa revela a concentração geográfica dos acidentes na zona sul de Recife, especialmente em Boa Viagem e Imbiribeira, que formam o maior cluster visual pela combinação de raio elevado e tonalidade vermelha intensa. A zona norte, representada por Casa Amarela, Água Fria e Nova Descoberta, apresenta marcadores menores em volume, mas com tonalidades que indicam fatalidades — sugerindo que, proporcionalmente, essas áreas são mais letais. O mapa serve como ponto de partida para decisões de alocação de recursos: bairros grandes e vermelhos demandam mais fiscalização em volume; bairros pequenos e vermelhos demandam intervenção cirúrgica em trechos específicos.

Heatmap Hora × Dia

O heatmap cruza as duas dimensões temporais simultaneamente — hora do dia e dia da semana — em uma grade de 12 × 7 células. A intensidade da cor em cada célula representa o número de acidentes registrados naquela combinação específica, com gradiente de branco (baixo volume) a vermelho escuro (alto volume). Essa visualização permite identificar de forma imediata os cruzamentos de horário e dia mais críticos, sem a necessidade de percorrer múltiplos gráficos separados.

dados %>%
  count(hora_int, dia_semana) %>%
  ggplot(aes(x = hora_int, y = dia_semana, fill = n)) +
  geom_tile(color = "white", linewidth = 0.3) +
  scale_fill_gradient(low = "#FFF5E6", high = "#D0021B", name = "Acidentes") +
  escala_hora +
  labs(
    title   = "Concentração de acidentes por hora e dia da semana (1h–12h)",
    x       = "Hora do dia",
    y       = NULL,
    caption = FONTE
  ) +
  tema_base +
  theme(panel.grid = element_blank())  # grade some — no heatmap ela compete visualmente com as células

O heatmap evidencia que sexta-feira às 7h é o cruzamento de maior concentração de acidentes em todo o período analisado, seguida por quinta-feira e quarta-feira no mesmo horário. O padrão de rush matinal (7h–8h) é consistente em todos os dias úteis, confirmando o gráfico G1. Nos fins de semana, a grade é visivelmente mais fria — volumes menores distribuídos de forma mais uniforme ao longo da manhã —, o que reforça a diferença estrutural entre dias úteis e fins de semana. É necessário lembrar que o período 13h–23h está completamente ausente, de modo que o heatmap não captura os padrões noturnos, que provavelmente seriam mais intensos nos fins de semana.

Evolução Mensal

A análise temporal ao longo dos doze meses de 2024 permite identificar sazonalidade e tendências no volume de acidentes. O gráfico apresenta três linhas — uma para cada nível de gravidade —, permitindo observar se os acidentes mais graves seguem o mesmo ritmo dos acidentes em geral ou se apresentam padrão independente. A variável mes_num foi utilizada para ordenar o eixo X corretamente, com os nomes dos meses por extenso.

por_mes <- dados %>%
  count(mes_num, mes, natureza)

ggplot(por_mes, aes(x = mes_num, y = n, color = natureza, group = natureza)) +
  geom_line(linewidth = 1.1) +
  geom_point(size = 2.5) +
  scale_color_manual(values = COR_NATUREZA, name = "Natureza") +
  escala_mes +
  labs(
    title   = "Evolução mensal de acidentes de trânsito",
    x       = NULL,
    y       = "Número de acidentes",
    caption = FONTE
  ) +
  tema_legenda

O mês com maior volume de acidentes foi setembro, enquanto fevereiro registrou o menor número de ocorrências — padrão que pode estar associado ao recesso de janeiro e ao aumento do fluxo de entregas e deslocamentos no segundo semestre. A linha de COM VÍTIMA acompanha de perto o volume geral, confirmando que a maioria dos acidentes envolve ao menos uma pessoa lesionada. A linha de VÍTIMA FATAL permanece praticamente plana ao longo do ano — com oscilações mês a mês, mas sem pico sazonal claro —, indicando que as mortes no trânsito não se concentram em nenhum período específico, mas se distribuem de forma relativamente constante ao longo de 2024. Por cobrir apenas 1h–12h, a série mensal pode subestimar meses com perfil predominantemente vespertino ou noturno.

Taxa de Letalidade

Volume de acidentes e risco de morte são dimensões distintas. Um tipo de acidente pode ser muito frequente e relativamente seguro, enquanto outro, menos comum, pode ser sistematicamente letal. Para capturar essa diferença, foi criada a variável taxa_letalidade — percentual de acidentes de cada tipo que resultaram em morte — e construído um ranking horizontal que inverte a narrativa do volume: aqui, o que importa é a proporção de desfechos fatais, não o número absoluto de ocorrências. Tipos com menos de 10 registros foram excluídos para evitar distorções estatísticas causadas por amostras muito pequenas.

# taxa_letalidade: subproblema isolado — % de fatais por tipo de acidente
letalidade <- dados %>%
  group_by(tipo) %>%
  summarise(
    total           = n(),
    fatais          = sum(natureza == "VÍTIMA FATAL"),
    taxa_letalidade = round(fatais / total * 100, 2),
    .groups = "drop"
  ) %>%
  filter(total >= 10) %>%   # excluir tipos com n < 10 (amostras não representativas)
  arrange(desc(taxa_letalidade))

ggplot(letalidade,
       aes(x = reorder(tipo, taxa_letalidade),
           y = taxa_letalidade,
           fill = taxa_letalidade)) +
  geom_col() +
  geom_text(aes(label = paste0(taxa_letalidade, "%")), hjust = -0.1, size = 3.5, color = "#E0E0E0") +
  coord_flip() +
  scale_fill_gradient(low = "#F5A623", high = "#D0021B", guide = "none") +
  scale_y_continuous(limits = c(0, 8), labels = function(x) paste0(x, "%")) +
  labs(
    title    = "Taxa de letalidade por tipo de acidente",
    subtitle = "% de acidentes desse tipo que resultaram em morte",
    x        = NULL,
    y        = "Taxa de letalidade (%)",
    caption  = FONTE
  ) +
  tema_base

O resultado é contra-intuitivo: atropelamento lidera com folga em letalidade proporcional, mesmo sendo um dos tipos menos frequentes no dataset. Com 8 mortes em 170 casos, a taxa supera a de colisão — o tipo mais frequente — em mais de dez vezes. Colisões registram alto volume absoluto, mas proporcionalmente poucas mortes, pois a maioria envolve veículos com proteção passiva (airbags, cinto, carroceria). Pedestres, ao contrário, não têm qualquer proteção mecânica no momento do impacto. Esse achado tem implicação direta de política pública: recursos de segurança viária devem priorizar a proteção do pedestre — travessias elevadas, iluminação e redução de velocidade nos bairros com mais atropelamentos —, não apenas o controle do fluxo veicular geral.

Acidentes Fatais

Os 33 acidentes com vítima fatal registrados no período coberto pelo dataset merecem análise isolada. Por serem raros em termos absolutos, mas extremamente relevantes do ponto de vista humano e de política pública, são examinados sob duas perspectivas: quando ocorrem (comparativo percentual com todos os acidentes) e onde ocorrem (bairros com maior concentração). O comparativo percentual é construído com bind_rows, permitindo sobrepor as duas distribuições em um único gráfico de linhas.

# Subproblema 1: isolar fatais
fatais_df <- dados %>% filter(natureza == "VÍTIMA FATAL")

# bind_rows combina dois subconjuntos distintos para comparação direta
comparativo_hora <- bind_rows(
  pct_hora(dados,      "Todos os acidentes"),
  pct_hora(fatais_df,  "Acidentes fatais")
)

# Gráfico 1: comparativo horário — padrão contra-intuitivo dos fatais
ggplot(comparativo_hora, aes(x = hora_int, y = n_pct, color = grupo, group = grupo)) +
  geom_line(linewidth = 1.1) +
  geom_point(size = 2) +
  scale_color_manual(
    values = c("Todos os acidentes" = unname(COR_NATUREZA["SEM VÍTIMA"]),
               "Acidentes fatais"   = unname(COR_NATUREZA["VÍTIMA FATAL"])),
    name = NULL
  ) +
  escala_hora +
  scale_y_continuous(labels = function(x) paste0(round(x, 1), "%")) +
  labs(
    title    = "Quando os acidentes fatais acontecem vs. todos os acidentes",
    subtitle = "Distribuição percentual por hora do dia (período coberto: 1h–12h)",
    x        = "Hora do dia",
    y        = "% dos acidentes do grupo",
    caption  = FONTE
  ) +
  tema_legenda

# Gráfico 2: top 10 bairros com mais fatais
fatais_df %>%
  count(bairro, sort = TRUE) %>%
  head(10) %>%
  ggplot(aes(x = reorder(bairro, n), y = n)) +
  geom_col(fill = COR_NATUREZA["VÍTIMA FATAL"]) +
  geom_text(aes(label = n), hjust = -0.2, size = 4, color = "#E0E0E0") +
  coord_flip() +
  labs(
    title   = "Bairros com mais vítimas fatais",
    x       = NULL,
    y       = "Número de vítimas fatais",
    caption = FONTE
  ) +
  tema_base

O comparativo hora a hora revela um padrão contra-intuitivo: enquanto os acidentes em geral se concentram no rush das 7h–8h, os acidentes fatais apresentam proporcionalmente maior peso nas horas da madrugada — 11 dos 33 fatais ocorreram entre 1h e 5h, período em que as vias estão mais vazias e os motoristas mais sonolentos ou possivelmente sob efeito de álcool. A velocidade excessiva nas vias desocupadas é a hipótese mais plausível para explicar a gravidade desproporcional da madrugada. No mapa de bairros, Boa Viagem aparece com o maior número absoluto de fatais, mas bairros como Nova Descoberta e Bomba do Hemeterio têm proporcionalmente mais mortes para seu volume de acidentes — padrão que será explorado no índice ponderado G14. É necessário ressalvar que o período 13h–23h, ausente neste dataset, provavelmente também concentra acidentes fatais noturnos que não estão sendo capturados aqui.

Motos vs. Automóveis

Motocicletas e automóveis são as categorias de veículos mais presentes nos acidentes de Recife, mas evoluem de forma distinta ao longo do ano. Para comparar as duas séries temporais, os totais mensais de cada tipo foram calculados e então convertidos para formato longo com pivot_longer — transformação que permite plotar duas linhas no mesmo gráfico a partir de colunas distintas do dataset original.

# pivot_longer transforma 2 colunas (Automóveis, Motocicletas) em formato longo
# permitindo plotar duas séries no mesmo gráfico de linhas
evolucao_frota <- dados %>%
  group_by(mes_num, mes) %>%
  summarise(
    Automóveis   = sum(auto, na.rm = TRUE),
    Motocicletas = sum(moto, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  pivot_longer(c(Automóveis, Motocicletas),
               names_to  = "tipo_veiculo",
               values_to = "total")

ggplot(evolucao_frota, aes(x = mes_num, y = total, color = tipo_veiculo, group = tipo_veiculo)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 3) +
  scale_color_manual(values = c("Automóveis" = "#185FA5", "Motocicletas" = "#D85A30"), name = NULL) +
  escala_mes +
  labs(
    title    = "Automóveis vs. motocicletas envolvidos em acidentes",
    subtitle = "Evolução mensal do total de veículos por categoria",
    x        = NULL,
    y        = "Total de veículos envolvidos",
    caption  = FONTE
  ) +
  tema_legenda

O gráfico evidencia que as motocicletas crescem consistentemente no segundo semestre, atingindo os maiores valores em agosto e setembro. Uma hipótese plausível é o aumento das entregas por aplicativo (iFood, Rappi, Uber Eats) nesse período, que expande a frota de motoboys em circulação. Em nenhum mês do período analisado as motos ficam abaixo de 200 envolvimentos mensais, o que confirma que esse é um problema estrutural e permanente — não sazonal. Os automóveis, por sua vez, apresentam volume maior em termos absolutos na maior parte do ano, mas com variação mensal mais estável. A implicação para políticas públicas é direta: qualquer estratégia de redução de acidentes em Recife deve contemplar especificamente a segurança dos motociclistas, com atenção redobrada no segundo semestre.

Atropelamentos

Os atropelamentos merecem análise isolada por dois motivos: são o tipo de acidente mais letal proporcionalmente (conforme demonstrado no G8) e envolvem, por definição, a vítima mais vulnerável possível — o pedestre, sem qualquer proteção mecânica. O filtro tipo == "ATROPELAMENTO DE PESSOA" isola esse subconjunto para examinar seus padrões horários e espaciais. Uma curva geom_smooth é adicionada ao gráfico de horas para revelar a tendência subjacente além da variação aleatória entre horas.

# Subproblema isolado: filtrar apenas o tipo de maior letalidade proporcional
atropelamentos <- dados %>% filter(tipo == "ATROPELAMENTO DE PESSOA")

# Gráfico 1: distribuição horária com curva de tendência loess
atropelamentos %>%
  count(hora_int) %>%
  ggplot(aes(x = hora_int, y = n)) +
  geom_col(fill = "#993C1D", alpha = 0.8) +
  geom_smooth(method = "loess", se = FALSE, color = "#D85A30", linewidth = 1.2) +
  escala_hora +
  labs(
    title   = "Atropelamentos por hora do dia (cobertura: 1h–12h)",
    x       = "Hora do dia",
    y       = "Número de atropelamentos",
    caption = FONTE
  ) +
  tema_base

# Gráfico 2: top 10 bairros com mais atropelamentos
atropelamentos %>%
  count(bairro, sort = TRUE) %>%
  head(10) %>%
  ggplot(aes(x = reorder(bairro, n), y = n)) +
  geom_col(fill = "#993C1D") +
  geom_text(aes(label = n), hjust = -0.2, size = 4, color = "#E0E0E0") +
  coord_flip() +
  labs(
    title   = "Top 10 bairros com mais atropelamentos",
    x       = NULL,
    y       = "Número de atropelamentos",
    caption = FONTE
  ) +
  tema_base

A curva de tendência dos atropelamentos revela crescimento consistente ao longo da manhã, com pico entre 7h e 9h — o horário de deslocamento a pé para o trabalho e para escolas. Ao contrário do que se poderia imaginar, os atropelamentos de madrugada (1h–4h) não são desprezíveis: sua ocorrência nesse período sugere trabalhadores a pé em turnos noturnos ou pedestres em situação de vulnerabilidade nas vias. No mapa de bairros, Boa Viagem, Santo Amaro e São José concentram os maiores volumes, reflexo de suas calçadas estreitas, faixas de pedestres escassas e alto fluxo de veículos em avenidas de pista dupla. A recomendação técnica é objetiva: travessias elevadas e iluminação reforçada nesses bairros teriam impacto direto na redução das vítimas mais vulneráveis do trânsito recifense. Mais uma vez, cabe ressaltar que os atropelamentos do período 13h–23h — incluindo os noturnos em vias de lazer — não estão presentes neste dataset.

Gravidade por Veículo

Para comparar a proporção de gravidade entre diferentes categorias de veículos e vítimas, os dados foram convertidos de formato largo para longo com pivot_longer: cada linha do dataset original, que tinha colunas separadas para auto, moto, pedestre etc., foi expandida para que cada tipo de veículo ficasse em uma linha própria com sua quantidade e a natureza do acidente correspondente. Registros com quantidade zero foram descartados, pois representam veículos não envolvidos. O gráfico de barras 100% (position = "fill") permite comparar proporções independentemente do volume de cada categoria.

# COLS_VEICULOS definido no chunk constantes — fonte única para a lista de colunas
# pivot_longer transforma formato largo → longo para comparação entre categorias
dados_long <- dados %>%
  select(Protocolo, natureza, all_of(COLS_VEICULOS)) %>%
  pivot_longer(
    cols      = all_of(COLS_VEICULOS),
    names_to  = "tipo_veiculo",
    values_to = "quantidade"
  ) %>%
  filter(quantidade > 0) %>%   # remover veículos não presentes no acidente
  mutate(
    tipo_veiculo = factor(tipo_veiculo,
      levels = c("pedestre","ciclista","ciclom","moto","auto","onibus","caminhao"),
      labels = c("Pedestre","Ciclista","Ciclomotor","Motocicleta","Automóvel","Ônibus","Caminhão"))
  )

dados_long %>%
  count(tipo_veiculo, natureza) %>%
  group_by(tipo_veiculo) %>%
  mutate(pct = n / sum(n)) %>%
  ggplot(aes(x = tipo_veiculo, y = pct, fill = natureza)) +
  geom_col(position = "fill") +
  coord_flip() +
  scale_fill_manual(values = COR_NATUREZA, name = "Natureza") +
  scale_y_continuous(labels = percent) +
  labs(
    title    = "Proporção de gravidade por tipo de veículo e vítima",
    subtitle = "Pedestres e ciclistas são os mais vulneráveis proporcionalmente",
    x        = NULL,
    y        = "Proporção dos acidentes",
    caption  = FONTE
  ) +
  tema_legenda

O gráfico de barras 100% revela de forma imediata a hierarquia de vulnerabilidade: pedestres e ciclistas apresentam a maior proporção de desfechos graves — tanto COM VÍTIMA quanto VÍTIMA FATAL —, enquanto automóveis e caminhões têm parcela maior de acidentes SEM VÍTIMA. Isso ocorre porque veículos motorizados com carroceria protegem seus ocupantes em colisões de baixa e média velocidade, enquanto pedestres e ciclistas absorvem o impacto diretamente. Motocicletas ficam em posição intermediária: volume altíssimo de envolvimentos, mas com proporção de fatalidades menor do que pedestres. Ônibus e caminhões, apesar de pesados, raramente figuram como “vítimas” — seus condutores e passageiros têm mais proteção passiva. O resultado reforça a prioridade de investimento em infraestrutura para não-motorizados em Recife.

Nuvem de Logradouros

A coluna endereco, padronizada na etapa de limpeza, contém o nome do logradouro onde cada acidente ocorreu. A extração dos nomes de vias com str_extract e uma expressão regular permite identificar quais ruas e avenidas concentram o maior número de registros, complementando o mapa de bairros com informação mais granular sobre os trechos específicos mais críticos. A nuvem de palavras é gerada com wordcloud2, e o tamanho de cada termo é proporcional à frequência de acidentes naquela via.

# Extrair o nome da via do campo endereco com expressão regular
logradouros <- dados %>%
  filter(!is.na(endereco)) %>%
  mutate(
    # Regex: captura o prefixo (RUA, AV, AVENIDA etc.) + palavras em maiúsculas
    via = str_extract(endereco,
                      "^(AVENIDA|AV|RUA|ESTRADA|RODOVIA|ROD|ALAMEDA|AL|PRACA|PC)?\\s*[A-Z\\s]+") %>%
          str_trim()
  ) %>%
  count(via, sort = TRUE) %>%
  filter(!is.na(via), nchar(via) > 4, n >= 5) %>%   # excluir vias com menos de 5 ocorrências
  head(60)

# Nuvem de palavras interativa — tamanho proporcional à frequência
wordcloud2(logradouros,
           size            = 0.6,
           color           = rep(c("#E8001D", "#FFD700", "#74C2E1"),
                                 length.out = nrow(logradouros)),
           backgroundColor = "#1c1c1c")

A nuvem evidencia as vias com maior concentração de acidentes em Recife, com destaque para as grandes avenidas da zona sul e do centro. O uso de str_extract com expressão regular foi essencial para isolar o nome da via e descartar números de imóvel, pontos de referência e complementos que poluiriam os termos. Vias com menos de cinco ocorrências foram excluídas para evitar que termos raros e pouco representativos dominassem visualmente o wordcloud. O resultado complementa o mapa G5 — que mostra bairros —, adicionando a dimensão do trecho específico: não basta saber que Boa Viagem é crítica, é necessário saber em qual avenida de Boa Viagem o risco se concentra.

Índice de Perigo

O ranking simples por volume de acidentes pode ser enganoso: um bairro com muitos acidentes leves é menos perigoso do que um com poucos acidentes, mas com alta proporção de mortes. Para capturar essa diferença, foi criada a variável indice_perigo, calculada pela fórmula:

Índice = (fatais × 3 + com_vítima × 1) ÷ total × 100

O peso 3 para vítimas fatais reflete a irreversibilidade da morte em relação a uma lesão: uma fatalidade representa um impacto humano, social e econômico muito maior do que um acidente com ferido. O resultado é normalizado pelo total de acidentes do bairro, convertendo o valor para uma escala de 0 a 100 que pode ser comparada entre bairros de volumes muito diferentes. Por exemplo, um bairro com 50 acidentes e 2 fatais tem índice (2×3 + 0) / 50 × 100 = 12, enquanto um bairro com 200 acidentes e 4 fatais tem índice (4×3 + 0) / 200 × 100 = 6 — apesar de ter mais mortes absolutas, é proporcionalmente menos letal. Bairros com menos de 20 acidentes foram excluídos para evitar que amostras muito pequenas gerassem índices estatisticamente instáveis.

# Criar índice ponderado: fatais valem 3x, com_vítima vale 1x; normalizar pelo total
indice_perigo <- dados %>%
  group_by(bairro) %>%
  summarise(
    total      = n(),
    com_vitima = sum(natureza == "COM VÍTIMA"),
    fatais     = sum(natureza == "VÍTIMA FATAL"),
    # Fórmula: (fatais × 3 + com_vítima × 1) / total × 100
    indice_perigo = round((fatais * 3 + com_vitima * 1) / total * 100, 1),
    .groups = "drop"
  ) %>%
  filter(total >= 20) %>%   # mínimo de 20 acidentes para representatividade estatística
  arrange(desc(indice_perigo)) %>%
  head(20)

# DT interativo com formatStyle em duas colunas
indice_perigo %>%
  datatable(
    caption  = "Índice de perigo ponderado por bairro — Recife 2024",
    colnames = c("Bairro", "Total acidentes", "Com vítima", "Fatais",
                 "Índice de perigo"),
    options  = list(pageLength = 10, scrollX = TRUE,
                    order = list(list(4, "desc"))),
    rownames = FALSE
  ) %>%
  formatStyle(
    "indice_perigo",
    background = styleColorBar(range(indice_perigo$indice_perigo), "#FFCCCC"),
    fontWeight = "bold"
  ) %>%
  formatStyle(
    "fatais",
    color = styleInterval(0, c("black", "#D0021B"))
  )

O índice ponderado altera significativamente o ranking em relação ao volume bruto. Bairros como Nova Descoberta e Bomba do Hemeterio — que não aparecem entre os primeiros no ranking de volume — sobem consideravelmente nesta lista por concentrarem proporcionalmente mais vítimas fatais em relação ao total de acidentes. Boa Viagem, que lidera em volume, pode aparecer em posição mais modesta aqui caso sua proporção de acidentes graves seja menor do que a de bairros menores e mais letais. Essa mudança de perspectiva tem implicação direta para gestores de segurança viária: alocar recursos apenas onde há mais acidentes ignora os pontos onde cada acidente tem maior probabilidade de matar.

5. Conclusões

Principais Achados

A pergunta-problema central deste trabalho — quais são os padrões espaciais, temporais e de perfil de vítimas nos acidentes de trânsito de Recife em 2024? — foi respondida de forma progressiva ao longo das treze visualizações. No período coberto pelo dataset (1h–12h), os acidentes em geral concentram-se no rush matinal das 7h e 8h, com maior volume registrado nas segundas e terças-feiras. Contudo, o padrão dos acidentes fatais é distinto e contra-intuitivo: 11 dos 33 fatais registrados ocorrem entre 1h e 5h da madrugada, não no rush. Espacialmente, os bairros mais perigosos por volume (Boa Viagem, Imbiribeira) diferem dos mais perigosos pelo índice ponderado (Nova Descoberta, Bomba do Hemeterio). É necessário ressaltar que o período de 13h a 23h está completamente ausente do dataset, o que impede qualquer conclusão sobre padrões vespertinos e noturnos — e possivelmente subestima tanto o volume total quanto o número de fatalidades anuais.

Do ponto de vista metodológico, o pipeline de análise percorreu as etapas de importação com read_csv2, limpeza em nove passos documentados, criação de variáveis derivadas (hora_int, dia_semana, mes_num, total_veiculos, tipo_dia, taxa_letalidade, indice_perigo) e transformação com pivot_longer para análises de proporção e séries temporais. Foram produzidas treze visualizações: sete gráficos estáticos com ggplot2, um gráfico interativo com plotly, um mapa com leaflet, duas tabelas interativas com DT, uma nuvem de palavras com wordcloud2 e uma tabela estática com kableExtra.

Três achados se destacam pela sua relevância prática. Primeiro, acidentes fatais concentram-se na madrugada, não no horário de pico — sugerindo que a fiscalização de velocidade e álcool nas horas de menor fluxo pode ter impacto maior na redução de mortes do que o controle do rush matinal. Segundo, atropelamentos são mais de dez vezes mais letais proporcionalmente do que colisões — o tipo mais frequente —, o que coloca a proteção do pedestre no centro da agenda de segurança viária. Terceiro, motocicletas crescem consistentemente no segundo semestre, possivelmente associadas ao aumento das entregas por aplicativo, configurando um grupo de risco permanente que demanda política específica.

Implicações Práticas

As implicações para os gestores públicos são objetivas. A CTTU pode redistribuir recursos de fiscalização eletrônica e de agentes para os horários de madrugada e para os bairros com maior índice ponderado de perigo, em vez de concentrá-los apenas nos corredores de maior volume. A Secretaria de Mobilidade Urbana dispõe de evidência para priorizar travessias elevadas e iluminação em Boa Viagem, Santo Amaro e São José — os bairros com mais atropelamentos —, além de implementar redutores de velocidade nas vias onde os fatais de madrugada se concentram. Para os motociclistas de entrega, programas de capacitação e equipamento de proteção em parceria com as plataformas de aplicativo podem reduzir o risco estrutural identificado no segundo semestre.

Limitações

Cinco limitações devem ser registradas com transparência. A mais crítica é a cobertura horária parcial: o dataset cobre apenas 01:00–12:59, tornando impossível analisar acidentes do período vespertino e noturno (13h–23h), que provavelmente concentram padrões distintos, especialmente nos fins de semana. A segunda limitação são as onze colunas 100% nulas — sinalização, condição da via, situação do semáforo —, que inviabilizam qualquer análise causal sobre o ambiente físico dos acidentes. A terceira é a geocodificação aproximada por bairro, sem coordenadas exatas de cada acidente, o que reduz a precisão das análises espaciais. A quarta é a possível subnotificação de acidentes sem vítimas, pois muitos condutores não acionam a CTTU em ocorrências de menor gravidade. A quinta é a ausência de dados de velocidade real e de alcoolemia, que impediu a verificação direta das hipóteses comportamentais levantadas. Como trabalho futuro, recomenda-se solicitar à CTTU o dataset completo de 24 horas, cruzar os registros com dados de iluminação pública e boletins de ocorrência da Polícia Civil, e incorporar dados de fluxo veicular por hora para calcular taxas de acidente ajustadas pela exposição ao risco.