A mobilidade urbana representa um dos maiores desafios das cidades contemporâneas. Em Recife — cidade com malha viária complexa, alta densidade demográfica e frota veicular em crescimento contínuo —, compreender os padrões de circulação é fundamental para a tomada de decisões estratégicas. Este projeto analisa os dados de monitoramento eletrônico de velocidade das vias do município, coletados pela Autarquia de Trânsito e Transporte Urbano (CTTU) ao longo de todo o mês de janeiro de 2025.
A base utilizada, “Velocidade das Vias — Quantitativo por
Velocidade Média — Janeiro 2025”, é disponibilizada
publicamente pelo Portal de
Dados Abertos da Prefeitura do Recife. O dataset contém
283.999 registros e 18 colunas,
capturados por radares instalados nas principais vias da cidade. As
variáveis centrais para esta análise são o momento do registro
(data_hora), o ponto de monitoramento
(local_equipamento) e a velocidade média registrada
(velocidade_media).
A análise segue o pipeline clássico de Data Science:
Os insights gerados têm aplicação direta para a CTTU e gestores de mobilidade urbana, possibilitando:
Os pacotes abaixo foram selecionados para cobrir todas as etapas do pipeline: importação, manipulação, visualização e apresentação dos dados.
library(tidyverse) # Manipulação e visualização de dados (dplyr, ggplot2, readr, tidyr...)
library(lubridate) # Operações com datas e horas de forma simplificada
library(plotly) # Conversão de gráficos ggplot2 em visualizações HTML interativas
library(DT) # Tabelas HTML interativas com paginação, busca e rolagem
tidyverse: Ecossistema coeso de
pacotes que provê uma gramática unificada para leitura
(readr), manipulação (dplyr,
tidyr) e visualização (ggplot2) de dados,
seguindo os princípios de tidy data.lubridate: Simplifica operações com
objetos POSIXct/Date — essencial para extrair
componentes como hora, dia da semana e mês a partir de campos datetime
sem ambiguidade.plotly: Permite converter qualquer
gráfico ggplot2 em uma visualização HTML interativa com uma
única chamada a ggplotly(), adicionando automaticamente
tooltips, zoom e filtragem por legenda.DT: Gera tabelas HTML baseadas na
biblioteca JavaScript DataTables, com suporte a paginação,
busca em tempo real e rolagem horizontal — ideal para exibir grandes
datasets em relatórios RMarkdown publicados no RPubs.Os dados provêm do Portal de Dados Abertos da Prefeitura do
Recife (dados.recife.pe.gov.br). O arquivo
original, no formato .csv, reúne registros horários de
equipamentos de monitoramento eletrônico distribuídos pelas principais
vias do município durante janeiro de 2025.
O arquivo CSV utiliza ponto-e-vírgula como separador
(padrão de exportação do portal do Recife) e está estruturado no formato
largo: cada linha representa um intervalo de 15 minutos
em um equipamento/faixa, com 11 colunas de contagem por faixa de
velocidade (qtd_0a10km até
qtd_acimade100km). A partir dessas colunas derivamos duas
variáveis analíticas essenciais:
volume_veiculos: soma total de
veículos em todos os intervalos de velocidade;velocidade_media: média ponderada pelo
ponto médio de cada faixa (ex.: 5 km/h para 0–10 km/h, 15,5 km/h para
11–20 km/h, etc.).A coluna data_hora é reconstituída combinando os campos
data e hour do CSV original.
# Leitura com separador ";" (read_csv2) e todas as colunas como caractere
dados_brutos <- read_csv2(
"lombadas-2025-janeiro-quantitativo-das-vias-por-velocidade-media.csv",
locale = locale(encoding = "UTF-8"),
col_types = cols(.default = col_character())
) %>%
# Renomeia "hour" para evitar conflito com lubridate::hour()
rename(hora_csv = hour) %>%
# Converte colunas de contagem por faixa: vazio/"" → 0, depois inteiro
mutate(across(
starts_with("qtd_"),
~ replace_na(suppressWarnings(as.integer(.)), 0L)
)) %>%
mutate(
# ── Variáveis derivadas ───────────────────────────────────────────────────
# Volume total: soma das 11 faixas de velocidade
volume_veiculos = rowSums(across(starts_with("qtd_")), na.rm = TRUE),
# Velocidade média ponderada pelos pontos médios de cada faixa (km/h)
velocidade_media = round(
(qtd_0a10km * 5.0 + qtd_11a20km * 15.5 +
qtd_21a30km * 25.5 + qtd_31a40km * 35.5 +
qtd_41a50km * 45.5 + qtd_51a60km * 55.5 +
qtd_61a70km * 65.5 + qtd_71a80km * 75.5 +
qtd_81a90km * 85.5 + qtd_91a100km * 95.5 +
qtd_acimade100km * 105.0) /
if_else(volume_veiculos == 0L, NA_real_, as.double(volume_veiculos)),
1
),
# Datetime: combina "data" (YYYY-MM-DD) com "hora_csv" (0–23)
data_hora = ymd_h(paste(data, hora_csv), tz = "America/Recife"),
local_equipamento = equipamento
# ─────────────────────────────────────────────────────────────────────────
) %>%
# Remove intervalos sem veículos ou com velocidade indeterminada
filter(volume_veiculos > 0L, !is.na(velocidade_media))
cat("Dimensões após limpeza inicial:", nrow(dados_brutos), "linhas ×",
ncol(dados_brutos), "colunas\n")
## Dimensões após limpeza inicial: 283997 linhas × 22 colunas
Com os dados derivados no bloco anterior, criamos as variáveis temporais necessárias para as análises. As etapas incluem:
hora e semana_mes a partir do
data_hora já parseado;dia_semana recebe rótulos em português via fator ordenado,
independentemente do locale do sistema;tipo_dia
categoriza cada registro como “Dia Útil” (segunda a sexta) ou
“Final de Semana” (sábado e domingo), variável central para a
terceira análise.dados <- dados_brutos %>%
mutate(
# data_hora já é POSIXct (criado em preparacao); apenas extraímos componentes
hora = hour(data_hora),
# Rótulos em português independente do locale do sistema
dia_semana = factor(
wday(data_hora, week_start = 1),
levels = 1:7,
labels = c("Segunda", "Terça", "Quarta", "Quinta",
"Sexta", "Sábado", "Domingo")
),
mes = factor(
month(data_hora),
levels = 1:12,
labels = c("Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho",
"Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro")
),
tipo_dia = if_else(
wday(data_hora, week_start = 1) %in% 1:5,
"Dia Útil",
"Final de Semana"
),
semana_mes = paste0("Semana ", ceiling(day(data_hora) / 7))
)
glimpse(dados)
## Rows: 283,997
## Columns: 26
## $ ano <chr> "2025", "2025", "2025", "2025", "2025", "2025", "202…
## $ mes <fct> Janeiro, Janeiro, Janeiro, Janeiro, Janeiro, Janeiro…
## $ equipamento <chr> "CTTU-9111", "CTTU-9111", "CTTU-9111", "CTTU-9111", …
## $ faixa <chr> "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A…
## $ data <chr> "2025-01-11", "2025-01-11", "2025-01-11", "2025-01-1…
## $ hora_csv <chr> "12", "12", "12", "12", "13", "13", "13", "13", "14"…
## $ minutos_intervalo <chr> "12:00-12:15", "12:15-12:30", "12:30-12:45", "12:45-…
## $ qtd_0a10km <int> 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ qtd_11a20km <int> 7, 11, 9, 0, 6, 16, 6, 2, 1, 0, 1, 5, 0, 3, 5, 3, 4,…
## $ qtd_21a30km <int> 89, 90, 89, 69, 85, 81, 84, 78, 63, 67, 87, 81, 80, …
## $ qtd_31a40km <int> 32, 26, 56, 46, 53, 41, 54, 44, 48, 70, 52, 39, 39, …
## $ qtd_41a50km <int> 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1…
## $ qtd_51a60km <int> 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0…
## $ qtd_61a70km <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ qtd_71a80km <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ qtd_81a90km <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ qtd_91a100km <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ qtd_acimade100km <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ volume_veiculos <dbl> 130, 130, 154, 116, 145, 139, 144, 124, 113, 137, 14…
## $ velocidade_media <dbl> 27.8, 26.2, 28.6, 29.6, 28.9, 27.4, 28.8, 28.9, 29.8…
## $ data_hora <dttm> 2025-01-11 12:00:00, 2025-01-11 12:00:00, 2025-01-1…
## $ local_equipamento <chr> "CTTU-9111", "CTTU-9111", "CTTU-9111", "CTTU-9111", …
## $ hora <int> 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, …
## $ dia_semana <fct> Sábado, Sábado, Sábado, Sábado, Sábado, Sábado, Sába…
## $ tipo_dia <chr> "Final de Semana", "Final de Semana", "Final de Sema…
## $ semana_mes <chr> "Semana 2", "Semana 2", "Semana 2", "Semana 2", "Sem…
A tabela abaixo exibe os primeiros 100 registros do dataset, ordenados por data e hora. Utilize a barra de busca, a paginação e a rolagem horizontal para explorar os dados interativamente:
DT::datatable(
dados %>% arrange(data_hora) %>% head(100),
caption = "Tabela 1 — Amostra dos Dados Processados (100 primeiros registros)",
rownames = FALSE,
class = "stripe hover",
options = list(
pageLength = 5,
scrollX = TRUE,
language = list(
search = "Buscar:",
info = "Mostrando _START_ a _END_ de _TOTAL_ registros",
paginate = list(previous = "Anterior", `next` = "Próximo"),
lengthMenu = "Exibir _MENU_ registros por página"
)
)
)
A primeira análise investiga como o volume de veículos se distribui ao longo do dia, identificando horários de pico e calmaria.
resumo_hora <- dados %>%
group_by(hora) %>%
summarise(
volume_medio = mean(volume_veiculos),
volume_total = sum(volume_veiculos),
n_registros = n(),
.groups = "drop"
)
p1 <- resumo_hora %>%
ggplot(aes(
x = hora,
y = volume_medio,
text = paste0(
"<b>", sprintf("%02d:00", hora), "</b>",
"<br>Volume médio: ", round(volume_medio, 1), " veículos",
"<br>Registros: ", n_registros
)
)) +
geom_area(fill = "#2196F3", alpha = 0.18) +
geom_line(color = "#1565C0", linewidth = 1.3) +
geom_point(size = 2.8, shape = 21,
fill = "#42A5F5", color = "#0D47A1", stroke = 1.2) +
# Destaque visual dos horários de pico
annotate("rect", xmin = 6.5, xmax = 9.5, ymin = -Inf, ymax = Inf,
fill = "#FF7043", alpha = 0.10) +
annotate("rect", xmin = 16.5, xmax = 19.5, ymin = -Inf, ymax = Inf,
fill = "#FF7043", alpha = 0.10) +
scale_x_continuous(
breaks = 0:23,
labels = sprintf("%02d:00", 0:23),
expand = expansion(add = 0.5)
) +
scale_y_continuous(
labels = scales::comma,
expand = expansion(mult = c(0, 0.08))
) +
labs(
title = "Volume Médio de Veículos por Hora do Dia — Janeiro 2025",
subtitle = "Regiões sombreadas em laranja indicam os horários de pico (7h–9h e 17h–19h)",
x = "Hora do Dia",
y = "Volume Médio de Veículos",
caption = "Fonte: Portal de Dados Abertos da Prefeitura do Recife"
) +
theme_minimal(base_size = 11) +
theme(
plot.title = element_text(face = "bold", hjust = 0.5, size = 13),
plot.subtitle = element_text(hjust = 0.5, color = "gray40", size = 10),
plot.caption = element_text(color = "gray55", size = 8),
axis.text.x = element_text(angle = 45, hjust = 1, size = 8),
panel.grid.minor = element_blank()
)
ggplotly(p1, tooltip = "text") %>%
layout(
hovermode = "x unified",
xaxis = list(title = "Hora do Dia"),
yaxis = list(title = "Volume Médio de Veículos")
)
Insight: O gráfico revela a clássica curva bimodal do tráfego urbano. O pico matutino concentra-se entre 7h e 9h, período que coincide com o deslocamento casa–trabalho/escola, atingindo os maiores volumes do dia. O pico vespertino, ocorrendo entre 17h e 19h, reflete o retorno dos trabalhadores e apresenta intensidade ligeiramente superior. A madrugada (0h–5h) registra volumes mínimos — queda de mais de 90% em relação ao pico —, configurando a janela ideal para obras e manutenção viária. Para a CTTU, esses dados fundamentam a programação de ciclos semafóricos adaptativos e o escalonamento de agentes de tráfego nos momentos críticos.
A segunda análise identifica os pontos de monitoramento (radares) que registram o maior fluxo acumulado de veículos no mês de janeiro.
top10_locais <- dados %>%
group_by(local_equipamento) %>%
summarise(
volume_total = sum(volume_veiculos),
vel_media_local = round(mean(velocidade_media), 1),
n_registros = n(),
.groups = "drop"
) %>%
slice_max(order_by = volume_total, n = 10) %>%
mutate(
# Remove o sufixo "- Radar XX" para rótulos mais limpos no eixo
local_curto = str_remove(local_equipamento, " - Radar.*$")
)
p2 <- top10_locais %>%
ggplot(aes(
x = reorder(local_curto, volume_total),
y = volume_total,
fill = volume_total,
text = paste0(
"<b>", local_equipamento, "</b>",
"<br>Volume total: ", scales::comma(volume_total),
" veículos",
"<br>Velocidade média: ", vel_media_local, " km/h",
"<br>Registros no período: ", n_registros
)
)) +
geom_col(width = 0.72, show.legend = FALSE) +
scale_fill_gradient(low = "#81D4FA", high = "#01579B") +
scale_y_continuous(
labels = scales::comma,
expand = expansion(mult = c(0, 0.12))
) +
coord_flip() +
labs(
title = "Top 10 Pontos de Monitoramento por Volume Total de Tráfego",
subtitle = "Radares com maior registro acumulado de veículos — Janeiro de 2025",
x = NULL,
y = "Volume Total de Veículos",
caption = "Fonte: Portal de Dados Abertos da Prefeitura do Recife"
) +
theme_minimal(base_size = 11) +
theme(
plot.title = element_text(face = "bold", hjust = 0.5, size = 13),
plot.subtitle = element_text(hjust = 0.5, color = "gray40", size = 10),
plot.caption = element_text(color = "gray55", size = 8),
panel.grid.major.y = element_blank(),
panel.grid.minor = element_blank()
)
ggplotly(p2, tooltip = "text") %>%
layout(
xaxis = list(title = "Volume Total de Veículos"),
yaxis = list(title = "")
)
Insight: A análise revela uma concentração expressiva do tráfego em poucas vias estruturantes. As avenidas no topo do ranking são corredores de importância metropolitana, conectando bairros residenciais ao centro e ao sistema viário de maior fluxo. Essa concentração tem uma implicação estratégica clara: intervenções de engenharia de tráfego ou fiscalização eletrônica nesses pontos críticos têm efeito multiplicador sobre a mobilidade geral da cidade. Gestores podem, portanto, priorizar investimentos em monitoramento, sinalização inteligente e manutenção da via pública justamente nos locais identificados.
A terceira análise compara hora a hora o comportamento do fluxo de veículos entre dias úteis e finais de semana, explorando o efeito das férias escolares de janeiro na mobilidade urbana.
resumo_tipo_dia <- dados %>%
group_by(hora, tipo_dia) %>%
summarise(
volume_medio = mean(volume_veiculos),
n_registros = n(),
.groups = "drop"
)
p3 <- resumo_tipo_dia %>%
ggplot(aes(
x = hora,
y = volume_medio,
color = tipo_dia,
fill = tipo_dia,
group = tipo_dia,
text = paste0(
"<b>", tipo_dia, "</b>",
"<br>Hora: ", sprintf("%02d:00", hora),
"<br>Volume médio: ", round(volume_medio, 1), " veículos",
"<br>Registros: ", n_registros
)
)) +
geom_area(alpha = 0.13, position = "identity") +
geom_line(linewidth = 1.3) +
geom_point(size = 2.6, shape = 21, color = "white", stroke = 1.2) +
scale_color_manual(
values = c("Dia Útil" = "#1565C0", "Final de Semana" = "#C62828"),
name = "Tipo de Dia"
) +
scale_fill_manual(
values = c("Dia Útil" = "#1565C0", "Final de Semana" = "#C62828"),
name = "Tipo de Dia"
) +
scale_x_continuous(
breaks = 0:23,
labels = sprintf("%02d:00", 0:23),
expand = expansion(add = 0.5)
) +
scale_y_continuous(
labels = scales::comma,
expand = expansion(mult = c(0, 0.08))
) +
labs(
title = "Volume Médio de Veículos por Hora: Dias Úteis × Finais de Semana",
subtitle = "Comportamento do tráfego em janeiro de 2025 (período de férias escolares)",
x = "Hora do Dia",
y = "Volume Médio de Veículos",
caption = "Fonte: Portal de Dados Abertos da Prefeitura do Recife"
) +
theme_minimal(base_size = 11) +
theme(
plot.title = element_text(face = "bold", hjust = 0.5, size = 13),
plot.subtitle = element_text(hjust = 0.5, color = "gray40", size = 10),
plot.caption = element_text(color = "gray55", size = 8),
axis.text.x = element_text(angle = 45, hjust = 1, size = 8),
legend.position = "top",
legend.title = element_text(face = "bold"),
panel.grid.minor = element_blank()
)
ggplotly(p3, tooltip = "text") %>%
layout(
hovermode = "x unified",
legend = list(
orientation = "h", y = 1.12,
x = 0.5, xanchor = "center"
)
)
Insight: O contraste entre os dois perfis ilustra claramente o efeito das férias escolares sobre a mobilidade de Recife. Nos dias úteis, o padrão bimodal se mantém marcado, com picos matutino e vespertino bem definidos, evidenciando que o tráfego comercial e de serviços essenciais sustenta a circulação mesmo sem o fluxo escolar. Nos finais de semana, o volume é consideravelmente menor e mais distribuído ao longo do dia, com um único pico deslocado para o meio do dia, refletindo o caráter recreativo dos deslocamentos. Essa diferença reforça a necessidade de regimes operacionais distintos de fiscalização e sinalização para cada tipo de dia, mesmo em períodos atípicos como as férias de verão.
Este relatório investigou os padrões de mobilidade urbana em Recife a
partir dos registros de monitoramento eletrônico de velocidade coletados
em janeiro de 2025. A análise seguiu um pipeline estruturado: importação
e limpeza dos dados, engenharia de atributos temporais
(hora, dia_semana, tipo_dia) e
análise exploratória orientada por três perguntas de negócio concretas,
todas apresentadas por meio de visualizações interativas geradas com
ggplot2 e plotly.
| # | Análise | Principal Descoberta |
|---|---|---|
| 1 | Padrões Temporais | Dois picos de tráfego bem definidos: 7h–9h (matutino) e 17h–19h (vespertino), com queda superior a 90% na madrugada (0h–5h). |
| 2 | Índice por Região | O tráfego é altamente concentrado em poucas vias estruturantes; as 10 vias do ranking respondem pela maior parcela do volume total da cidade. |
| 3 | Dinâmica de Janeiro | Dias úteis mantêm o perfil bimodal mesmo em período de férias; finais de semana apresentam fluxo reduzido com pico único e tardio. |
set.seed(42); os resultados devem ser revalidados com a
base oficial após substituir o bloco mock pelo read_csv()
comentado;Para ampliar o valor analítico deste trabalho, recomenda-se: (i) estender a análise para os demais meses de 2025 e comparar sazonalidades; (ii) incorporar variáveis externas como condições meteorológicas e eventos especiais (Carnaval, feriados regionais); e (iii) explorar modelos preditivos de machine learning para antecipação de congestionamentos e apoio à tomada de decisão em tempo real.
Relatório produzido com R Markdown · Dados: Portal de Dados Abertos da Prefeitura do Recife · Autor: Luis Abreu · Publicado no RPubs