Foto: Cadu Silva / Diário de Pernambuco · diariodepernambuco.com.br
Padrões Ocultos em 5.315 Registros
Alana Lins
Professor: Ermeson Andrade
Entrega: 10 de junho de 2026
CAPD
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.
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.
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.
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)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.
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.
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
## 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, …
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
## Colunas após limpeza: 32
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
)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á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 |
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.
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:
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.
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_legendaUma 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.
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_legendaEm 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.
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.
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.
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.
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élulasO 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.
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_legendaO 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.
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_baseO 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.
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_baseO 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.
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_legendaO 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.
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_baseA 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.
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_legendaO 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.
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.
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.
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.
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.
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.