1 Introducción

El presente informe tiene como objetivo describir y analizar la base de datos de comparendos de tránsito generados por un sistema de fotodetección (fotomultas) ubicado en la calle 72 de la ciudad de Barranquilla (departamento del Atlántico) entre el 12 de abril y el 27 de mayo de 2010. Mediante el lenguaje R se aplican funciones estadísticas básicas, se construyen representaciones gráficas y se resuelven seis consultas sobre la información, con el fin de identificar patrones de comportamiento de los infractores, de las infracciones y del valor económico asociado a los comparendos.

Con el botón Code de la esquina superior derecha se puede mostrar u ocultar todo el código del informe. Además, cada bloque de código tiene su propio botón para mostrarlo u ocultarlo, de modo que cada gráfico o tabla puede verse solo con su resultado.

La siguiente figura ubica la ciudad de Barranquilla y, de forma aproximada, el sector de la calle 72 donde operaba el sistema de fotodetección.

# MAPA INTERACTIVO con el paquete leaflet
library(leaflet)   # carga el paquete (debe estar instalado)

# Coordenadas aproximadas del sector de la calle 72 (se pueden ajustar:
# en Google Maps, clic derecho sobre el punto exacto y copiar las coordenadas)
lat_punto <- 10.987310    # latitud del punto (guardada en una variable)
lng_punto <- -74.809604   # longitud del punto

# El mapa se construye por capas, unidas con %>% ("y luego"):
leaflet() %>%                                              # 1) crea un mapa vacío
  addTiles() %>%                                           # 2) mapa base de OpenStreetMap
  setView(lng = -74.7889, lat = 10.9878, zoom = 12) %>%    # 3) centro y nivel de zoom (centro de Barranquilla)
  addCircles(lng = lng_punto, lat = lat_punto, radius = 400,   # 4) círculo de 400 m de radio...
             color = "#B3201F", fillColor = "#B3201F", fillOpacity = 0.25) %>%  # ...borde rojo, relleno rojo 25 % opaco
  addMarkers(lng = lng_punto, lat = lat_punto,             # 5) marcador (pin) en el punto...
             popup = "Sector aproximado de la fotodetección: calle 72, Barranquilla") %>%  # ...con texto al hacer clic
  addControl("<b>Barranquilla, Atlántico (Colombia)</b>", position = "topright")  # 6) rótulo fijo arriba a la derecha

Nota: el mapa necesita conexión a internet para cargar el mapa base. El punto señala una ubicación aproximada.

1.1 Descripción general

La base de datos corresponde a la hoja BD Comparendos OK y contiene 2.120 registros (un comparendo por fila) y 25 columnas. Sobre todos los comparendos se libró mandamiento de pago el 19/08/2010. En total hay 1.248 ejecutados distintos y 1.457 placas de vehículo distintas. Las columnas se organizan en tres grupos:

  • Variables originales: número y fecha del mandamiento de pago, ejecutado, tipo y número de identificación, código de infracción, número y fecha del comparendo y placa del vehículo.
  • Variables derivadas: género (Hombre, Mujer o Empresa para ejecutados con NIT), nombre de la infracción, año, mes, nombre del mes, día, nombre del día, tipo de vehículo (Auto o Moto) y Valor, que es el costo del comparendo en 2010, definido como un porcentaje del salario mínimo mensual legal vigente (SMMLV) de la época.
  • Variables auxiliares: las columnas con prefijo # valen siempre 1 y sirven como contadores en tablas dinámicas.

El catálogo de infracciones registradas es el siguiente (SMMLV de 2010: $515.000):

Código Infracción Valor (COP) Equivalencia
64 Velocidad superior a la máxima permitida 257.500 0,5 SMMLV
67 No respetar el paso de peatones 257.500 0,5 SMMLV
76 Transitar en sentido contrario 515.000 1 SMMLV
77 No detenerse ante una luz roja o amarilla 515.000 1 SMMLV

A continuación se cargan las librerías necesarias y se lee el archivo de Excel. Se descartan las filas vacías al final de la hoja y se crea la columna FECHA en formato de fecha.

# CARGA DE PAQUETES Y DE LA BASE DE DATOS

# Paquetes necesarios (instalar una sola vez si hacen falta):
# install.packages(c("dplyr", "tidyr", "readxl", "ggplot2", "scales", "knitr", "janitor", "leaflet"))
library(dplyr)     # manipular datos: filter, mutate, group_by, summarise, count...
library(tidyr)     # reorganizar tablas: pivot_wider, pivot_longer, complete
library(readxl)    # leer archivos de Excel (.xlsx)
library(ggplot2)   # hacer gráficos
library(scales)    # dar formato a números en ejes y etiquetas
library(knitr)     # kable(): dibuja tablas bonitas en el informe

# El archivo debe estar en la misma carpeta que este .Rmd
archivo <- "Daniel_Ramirez_Ramos_BD_Comparendos.xlsx"   # nombre del Excel guardado en una variable

# read_excel() lee la hoja indicada y crea el data frame "df" (la tabla de datos).
# Luego, con %>%, se le aplican transformaciones paso a paso.
df <- read_excel(archivo, sheet = "BD Comparendos OK") %>%

  # quitar filas vacias: se queda solo con las filas donde COMPARENDO NO es NA
  # (is.na() detecta vacíos; el signo ! lo invierte: "no es vacío")
  filter(!is.na(COMPARENDO)) %>%

  # mutate() crea o modifica columnas
  # fecha en formato date de r: as.Date() convierte el texto/fecha de Excel al tipo Date de R
  mutate(FECHA = as.Date(`FECHA DE COMPARENDO`),   # (las comillas invertidas ` ` se usan porque el nombre tiene espacios)

         # elimina los decimales excedentes: round() redondea Valor al entero más cercano
         Valor = round(Valor))

Para que todo el informe use el mismo formato numérico (punto para los miles y coma para los decimales) y los gráficos mantengan el texto en negrilla, se definen una sola vez unas funciones de formato y un tema gráfico. Los conteos se muestran como enteros, los promedios con un decimal, los porcentajes con un decimal y los valores en pesos como enteros.

# FUNCIONES DE FORMATO (paquete scales)
# label_number(), label_percent() y label_dollar() NO formatean directamente:
# devuelven una FUNCIÓN que luego se usa así: fmt_n(1234) -> "1.234"
# Argumentos comunes:
#   accuracy     = a qué decimal redondear (1 = enteros, 0.1 = un decimal, 0.01 = dos)
#   big.mark     = separador de miles (aquí punto)
#   decimal.mark = separador decimal (aquí coma)
#   suffix/prefix= texto que se pega al final / al inicio
#   scale        = multiplica el número antes de mostrarlo (1e-6 = pasa a millones)

# Funciones de formato (punto = miles, coma = decimales)
fmt_n     <- label_number(accuracy = 1,    big.mark = ".", decimal.mark = ",")                   # 1.234  (enteros)
fmt_d1    <- label_number(accuracy = 0.1,  big.mark = ".", decimal.mark = ",")                   # 1.234,5 (un decimal)
fmt_pct1  <- label_percent(accuracy = 0.1,  suffix = " %", big.mark = ".", decimal.mark = ",")   # 45,8 %  (porcentaje, 1 decimal)
fmt_pct2  <- label_percent(accuracy = 0.01, suffix = " %", big.mark = ".", decimal.mark = ",")   # 0,05 %  (porcentaje, 2 decimales)
fmt_cop   <- label_dollar(accuracy = 1, prefix = "$", big.mark = ".", decimal.mark = ",")        # $1.234  (pesos)
fmt_m0    <- label_number(accuracy = 1,   scale = 1e-6, suffix = " M", big.mark = ".", decimal.mark = ",")  # 12 M   (millones sin decimales)
fmt_m1    <- label_number(accuracy = 0.1, scale = 1e-6, suffix = " M", big.mark = ".", decimal.mark = ",")  # 12,3 M (millones con 1 decimal)

# Tema gráfico: todas las letras en negrilla, y etiquetas de datos en negrilla
# theme_set() fija el tema de TODOS los gráficos siguientes.
# theme_gray = tema gris por defecto de ggplot; base_size = tamaño base de letra;
# theme(text = element_text(face="bold")) pone todo el texto en negrilla.
theme_set(theme_gray(base_size = 12) + theme(text = element_text(face = "bold")))
# Cambia el valor por defecto de geom_text() para que las etiquetas de datos también sean negrilla
update_geom_defaults("text", list(fontface = "bold"))

1.2 Calidad de los datos

Antes de analizar se revisó la consistencia de la base. No hay valores vacíos en ninguna columna. Se detectó 1 fila completamente duplicada y 18 números de comparendo que aparecen más de una vez (en 36 registros). Estos casos se conservaron tal como vienen en la fuente, porque representan menos del 1 % de los registros y no alteran las conclusiones.

# REVISIÓN DE CALIDAD: cuenta problemas posibles en la base.
# paste() une texto con el resultado; print() lo muestra en pantalla.

# is.na(df) marca con TRUE cada celda vacía; sum() cuenta los TRUE
print(paste("Celdas vacías:", sum(is.na(df))))                                   # celdas vacias
## [1] "Celdas vacías: 0"
# duplicated(df) marca TRUE las filas que ya habían aparecido antes (idénticas en todo)
print(paste("Filas totalmente duplicadas:", sum(duplicated(df))))                # filas duplicadas
## [1] "Filas totalmente duplicadas: 1"
# duplicated(df$COMPARENDO) hace lo mismo pero solo mirando la columna del número de comparendo
print(paste("Números de comparendo repetidos:", sum(duplicated(df$COMPARENDO)))) # comparendos repetidos
## [1] "Números de comparendo repetidos: 18"

2 Aplicación de funciones

2.1 Función colnames()-

La función colnames() devuelve los nombres de las columnas, necesarios para referirse a cada una correctamente en el código.

colnames(df)  # vector con los nombres de las columnas (útil para escribirlos bien en el código)
##  [1] "No. MANDAMIENTO DE PAGO"   "FECHA MANDAMIENTO DE PAGO"
##  [3] "EJECUTADO"                 "TIPO DE IDENTIFICACION"   
##  [5] "#TIPO DE IDENTIFICACION"   "No. IDENTIFICACION"       
##  [7] "GENERO"                    "#GENERO"                  
##  [9] "COD. INFRACCION"           "#COD. INFRACCION"         
## [11] "NOM.INFRACCION"            "#NOM.INFRACCION"          
## [13] "COMPARENDO"                "FECHA DE COMPARENDO"      
## [15] "AÑO"                       "MES"                      
## [17] "NOM MES"                   "#NOM MES"                 
## [19] "DIA"                       "NOM DIA"                  
## [21] "#NOM DIA"                  "PLACA DE VEHICULO"        
## [23] "VEHICULO"                  "#VEHICULO"                
## [25] "Valor"                     "FECHA"

2.2 Función summary()

Se obtiene un resumen estadístico de la fecha del comparendo y del valor a pagar con la función summary(), presentado en una tabla con el formato numérico del informe. Los comparendos se concentran entre el 12/04/2010 y el 27/05/2010. El valor del comparendo solo toma dos valores, $257.500 y $515.000, por lo que la mediana coincide con el valor mínimo ($257.500) y el promedio es de $304.749.

# summary() calcula: mínimo, 1.er cuartil, mediana, media, 3.er cuartil y máximo.
# as.numeric() lo convierte en un vector de números simples para poder darle formato.
# (Las fechas, internamente, son números: días transcurridos desde el 1/1/1970.)
res_fecha <- as.numeric(summary(df$FECHA))   # minimo, q1, mediana, media, q3, maximo
res_valor <- as.numeric(summary(df$Valor))

# Se arma una tabla (data.frame) con tres columnas:
tabla_resumen <- data.frame(
  `Estadístico` = c("Mínimo", "1.er cuartil", "Mediana", "Media", "3.er cuartil", "Máximo"),  # nombres de las filas
  # floor() quita decimales (la media de fechas puede tener decimales);
  # as.Date(..., origin="1970-01-01") vuelve a convertir esos números en fechas;
  # format(..., "%d/%m/%Y") las muestra como día/mes/año
  Fecha = format(as.Date(floor(res_fecha), origin = "1970-01-01"), "%d/%m/%Y"),
  Valor = fmt_cop(res_valor),     # valores con formato de pesos ($257.500)
  check.names = FALSE             # permite nombres de columna con tildes/espacios sin cambiarlos
)
# kable() dibuja la tabla; align = "lrr" = columna 1 a la izquierda (l), columnas 2 y 3 a la derecha (r)
kable(tabla_resumen, align = "lrr")
Estadístico Fecha Valor
Mínimo 12/04/2010 $257.500
1.er cuartil 27/04/2010 $257.500
Mediana 06/05/2010 $257.500
Media 05/05/2010 $304.749
3.er cuartil 16/05/2010 $257.500
Máximo 27/05/2010 $515.000

3 Aplicación de medidas y análisis

3.1 Comparendos por día

Se cuenta la cantidad de comparendos registrados en cada fecha. La base cubre 46 días con registros, con un promedio de 46,1 comparendos diarios (mediana de 43). El máximo ocurrió el 1/05/2010 con 89 comparendos, seguido del 9/05 (86) y del 16/05 (84); el mínimo fue de 18 comparendos el 27/05/2010, último día de la base. La serie muestra un nivel bajo en la primera quincena de abril y un salto a partir del 24 de abril, que se mantiene durante mayo con picos recurrentes.

# GRÁFICO DE LÍNEAS: evolución diaria de los comparendos
# Grafico de lineas: comparendos por dia (con etiqueta de dato en cada dia)
df %>%
  count(FECHA) %>%        # cuenta filas por fecha -> tabla con columnas FECHA y n (cantidad de comparendos)
  ggplot(aes(x = FECHA, y = n)) +      # ggplot: x = fecha, y = cantidad. Las capas se suman con +
  geom_line(color = "#B3201F") +       # dibuja la línea que une los puntos
  geom_point(color = "#B3201F", size = 1.5) +   # dibuja un punto por cada día
  geom_text(aes(label = fmt_n(n)), vjust = -0.9, size = 3.2) +  # escribe el número encima de cada punto (vjust negativo = hacia arriba)
  scale_x_date(date_breaks = "1 day", date_labels = "%d/%m") +  # una marca en el eje X por día, formato día/mes
  # expansion(mult=c(0.05, 0.12)): deja 5 % de espacio abajo y 12 % arriba para que quepan las etiquetas
  scale_y_continuous(labels = fmt_n, expand = expansion(mult = c(0.05, 0.12))) +
  labs(title = "Comparendos por día", x = "Fecha", y = "Cantidad de comparendos") +  # título y nombres de ejes
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1))  # fechas del eje X giradas 90° para que no se encimen

Al promediar por día de la semana, sábados y domingos concentran el mayor número de comparendos por jornada (62,0 y 66,0) frente a unos 40 de los días entre semana. Esto sugiere un patrón ligado a los fines de semana, cuando la circulación y la velocidad pueden ser distintas. Con estos datos no se puede establecer la causa; se requeriría información adicional de tráfico.

# TABLA: promedio de comparendos por día de la semana
# Promedio de comparendos segun el dia de la semana
# (el nombre del objeto va entre comillas invertidas porque tiene espacios)
`Registros de comparendos por dia` <- df %>%
  group_by(`NOM DIA`) %>%                # agrupa las filas por nombre del día (lunes, martes, ...)
  summarise(                             # calcula un resumen por cada grupo:
    dias = n_distinct(FECHA),            #   cuántas fechas distintas hay de ese día de la semana
    comparendos = n(),                   #   cuántos comparendos hubo en total (n() cuenta filas)
    promedio = round(comparendos / dias, 1)) %>%   #   promedio = total / días, redondeado a 1 decimal
  arrange(desc(promedio))                # ordena de mayor a menor promedio

# transmute() = como mutate() pero conserva SOLO las columnas que se crean aquí
# (aquí se renombran y se les da formato de texto para presentarlas)
`Registros de comparendos por dia` %>%
  transmute(`Día de la semana` = `NOM DIA`,
            `Días en la base` = fmt_n(dias),
            Comparendos = fmt_n(comparendos),
            `Promedio por día` = fmt_d1(promedio)) %>%
  kable(align = "lrrr")                  # dibuja la tabla (1.ª columna izquierda, las otras 3 derecha)
Día de la semana Días en la base Comparendos Promedio por día
DOMINGO 6 396 66,0
SÁBADO 6 372 62,0
VIERNES 6 264 44,0
LUNES 7 295 42,1
MIÉRCOLES 7 273 39,0
MARTES 7 264 37,7
JUEVES 7 256 36,6

3.2 Comparendos por género

Se cuenta la cantidad de comparendos según el género del ejecutado; los ejecutados con NIT se clasifican como Empresa. Las mujeres acumulan 970 comparendos (45,8 %), los hombres 683 (32,2 %) y las empresas 467 (22,0 %). Las empresas, que son solo 130 ejecutados distintos, concentran más de una quinta parte de los comparendos, lo que indica un promedio por ejecutado mucho mayor que el de las personas naturales (3,6 comparendos por empresa frente a 1,5 por mujer y 1,5 por hombre). Esto es coherente con flotas de vehículos o vehículos en leasing registrados a nombre de una misma entidad.

# GRÁFICO DE BARRAS VERTICALES: comparendos por género
df %>%
  count(GENERO) %>%     # cuenta comparendos por género -> columnas GENERO y n
  # reorder(GENERO, -n) ordena las barras de mayor a menor (el signo menos invierte el orden)
  # fill = GENERO: cada barra se colorea según su género
  ggplot(aes(x = reorder(GENERO, -n), y = n, fill = GENERO)) +
  geom_col() +          # barras cuya altura es el valor de y (geom_bar contaría filas; geom_col usa el valor ya calculado)
  geom_text(aes(label = fmt_n(n)), vjust = -0.4) +   # número encima de cada barra
  scale_y_continuous(labels = fmt_n, expand = expansion(mult = c(0, 0.12))) +  # eje Y con formato; 0 % espacio abajo, 12 % arriba
  # scale_fill_manual asigna un color específico a cada categoría
  scale_fill_manual(values = c(MUJER = "#B3201F", HOMBRE = "#4B5563", EMPRESA = "#9CA3AF")) +
  labs(title = "Distribución de comparendos por género/empresa", x = "Género", y = "Cantidad") +
  theme(legend.position = "none")   # oculta la leyenda (es redundante: el eje X ya dice el género)

3.3 Valor total por tipo de infracción

Se suman los valores de los comparendos agrupados por infracción. Como la base registra el valor del comparendo (no el pago efectivo), se habla de valor total sancionado y no de recaudo. El valor total asciende a $646.067.500. La velocidad superior a la máxima permitida aporta $418.695.000 (64,8 %), seguida por no detenerse ante luz roja o amarilla con $198.275.000 (30,7 %). Entre las dos reúnen el 95,5 % del total. No respetar el paso de peatones suma $27.037.500 (4,2 %) y transitar en sentido contrario $2.060.000 (0,3 %). El peso de la velocidad se explica por su frecuencia (1.626 comparendos), y no por un valor unitario mayor: cada comparendo de velocidad vale 0,5 SMMLV, la mitad que el de luz roja.

# GRÁFICO DE BARRAS HORIZONTALES: valor total sancionado por infracción
df %>%
  group_by(NOM.INFRACCION) %>%                       # agrupa por tipo de infracción
  summarise(Total = sum(Valor, na.rm = TRUE)) %>%    # suma los valores de cada grupo (na.rm=TRUE ignora vacíos)
  arrange(desc(Total)) %>%                           # ordena de mayor a menor
  ggplot(aes(x = reorder(NOM.INFRACCION, Total), y = Total)) +   # reorder: ordena las barras por Total
  geom_col(fill = "#8B1A1A") +                       # barras de un solo color
  coord_flip() +                                     # gira el gráfico 90°: las barras quedan horizontales
  geom_text(aes(label = fmt_cop(Total)), hjust = -0.1) +   # valor en pesos al final de cada barra (hjust negativo = hacia la derecha)
  scale_x_discrete(labels = label_wrap(30)) +        # parte en varias líneas los nombres largos (30 caracteres)
  scale_y_continuous(labels = fmt_m0, expand = expansion(mult = c(0, 0.25))) +  # eje en millones; 25 % de espacio extra para las etiquetas
  labs(title = "Valor total de los comparendos por tipo de infracción",
       x = "Infracción", y = "Valor total (millones de COP)")

3.4 Comparendos por tipo de vehículo

Se cuenta la cantidad de comparendos por tipo de vehículo. Los automóviles concentran 1.938 comparendos (91,4 %) y las motos 182 (8,6 %). La diferencia es amplia y probablemente refleja la composición del parque automotor y del flujo vehicular en la vía; sin datos de aforo no es posible concluir que los conductores de automóvil infrinjan más, solo que son la mayoría de los vehículos sancionados.

# GRÁFICO CIRCULAR (de torta) con etiquetas de cantidad y porcentaje
# ggplot no tiene "geom_pie": se hace una barra apilada y se enrolla con coord_polar.
# Grafico circular con etiquetas de cantidad y porcentaje
df %>%
  count(VEHICULO) %>%                                   # cuenta comparendos por tipo de vehículo
  ggplot(aes(x = "", y = n, fill = VEHICULO)) +         # x vacío = una sola barra; fill = una "rebanada" por vehículo
  geom_col(width = 1, color = "white") +                # barra apilada de ancho 1, con borde blanco entre rebanadas
  geom_text(aes(label = paste0(fmt_n(n), "\n(", fmt_pct1(n / sum(n)), ")")),   # texto: cantidad, salto de línea (\n) y porcentaje
            position = position_stack(vjust = 0.5), color = "white", size = 4) +  # centra el texto dentro de cada rebanada
  coord_polar(theta = "y") +                            # convierte la barra en círculo (theta="y": el ángulo representa y)
  labs(title = "Comparendos por tipo de vehículo", fill = "Tipo de vehículo") +
  theme_void() +                                        # quita ejes, fondo y cuadrícula (se ve limpio)
  scale_fill_manual(values = c("AUTO" = "#8B1A1A", "MOTO" = "#4B5563")) +   # colores por categoría
  theme(legend.position = "right", text = element_text(face = "bold"))     # leyenda a la derecha y negrilla (theme_void la había quitado)

# TABLA complementaria del gráfico circular
# Tabla de datos
df %>%
  count(VEHICULO, name = "Cantidad") %>%      # cuenta por vehículo; name= le pone nombre a la columna del conteo
  mutate(Porcentaje = Cantidad / sum(Cantidad)) %>%   # porcentaje = parte / total (como fracción)
  transmute(Vehículo = VEHICULO,              # deja solo estas columnas, ya con formato de texto
            Cantidad = fmt_n(Cantidad),
            `Porcentaje (%)` = fmt_pct1(Porcentaje)) %>%
  kable(align = "lrr")                        # dibuja la tabla
Vehículo Cantidad Porcentaje (%)
AUTO 1.938 91,4 %
MOTO 182 8,6 %

3.5 Comparendos por mes

Se agrupan los comparendos por el mes en que ocurrieron. En mayo se registraron 1.408 comparendos y en abril 712, es decir 98 % más en mayo. Esta comparación debe leerse con cuidado: la base solo incluye 19 días de abril (desde el 12) frente a 27 de mayo. Al normalizar por el número de días, el promedio diario pasa de 37,5 comparendos en abril a 52,1 en mayo, un aumento de 39 %, que sigue siendo importante pero bastante menor que el que sugiere el total.

# GRÁFICO DE BARRAS: total de comparendos por mes
df %>%
  count(`NOM MES`) %>%                                     # cuenta comparendos por nombre del mes
  ggplot(aes(x = reorder(`NOM MES`, -n), y = n)) +         # barras ordenadas de mayor a menor
  geom_col(fill = "#B3201F") +
  geom_text(aes(label = fmt_n(n)), vjust = -0.4) +         # cantidad encima de cada barra
  scale_y_continuous(labels = fmt_n, expand = expansion(mult = c(0, 0.12))) +
  labs(title = "Comparendos por mes", x = "Mes", y = "Cantidad")

# TABLA: promedio diario por mes (corrige el sesgo de que abril tiene menos días en la base)
# Promedio diario por mes (los meses tienen distinta cantidad de dias en la base)
df %>%
  group_by(`NOM MES`) %>%                       # agrupa por mes
  summarise(
    comparendos = n(),                          # total de comparendos del mes
    dias = n_distinct(FECHA),                   # días distintos con registros en ese mes
    promedio_diario = round(comparendos / dias, 1)) %>%   # promedio por día
  transmute(Mes = `NOM MES`,                    # presenta las columnas con formato
            Comparendos = fmt_n(comparendos),
            `Días en la base` = fmt_n(dias),
            `Promedio diario` = fmt_d1(promedio_diario)) %>%
  kable(align = "lrrr")
Mes Comparendos Días en la base Promedio diario
ABRIL 712 19 37,5
MAYO 1.408 27 52,1

3.6 Top 10 de personas con más comparendos

Se identifican las diez personas con más comparendos, excluyendo empresas. Encabeza la lista Liliana Rozo Pinzon con 27 comparendos, casi tres veces más que la segunda persona (10). De las 1.118 personas naturales de la base, 290 (26 %) tienen dos o más comparendos, por lo que la reincidencia es un fenómeno relevante. A partir de la posición 9 hay empates en 6 comparendos entre varias personas; el listado se desempata por orden alfabético.

# GRÁFICO DE BARRAS HORIZONTALES: 10 personas con más comparendos
df %>%
  filter(GENERO != "EMPRESA") %>%      # excluye empresas (!= significa "distinto de")
  count(EJECUTADO) %>%                 # cuenta comparendos por persona
  arrange(desc(n), EJECUTADO) %>%      # desempate alfabetico: primero mayor n; si empatan, por nombre A-Z
  slice_head(n = 10) %>%               # se queda con las primeras 10 filas
  ggplot(aes(x = reorder(EJECUTADO, n), y = n)) +   # ordena las barras por cantidad
  geom_col(fill = "#B3201F") +
  geom_text(aes(label = fmt_n(n)), hjust = -0.2) +  # cantidad al final de cada barra
  coord_flip() +                       # barras horizontales (para leer bien los nombres)
  scale_y_continuous(labels = fmt_n, expand = expansion(mult = c(0, 0.12))) +
  labs(title = "Top 10 personas con más comparendos", x = "Nombre", y = "Cantidad")

4 Consultas

Esta sección resuelve en R las seis consultas construidas en el libro de Excel como tablas dinámicas (hojas CONSULTA_P1 a CONSULTA_P6), pero presentadas como gráficos. Las cifras coinciden con las de las tablas dinámicas.

4.1 Consulta 1: infracciones cometidas por tipo de vehículo

Cuántos comparendos de cada infracción corresponden a automóviles y a motos. La velocidad es la infracción más frecuente en ambos tipos de vehículo (1.467 en automóviles y 159 en motos). Las motos solo registran 182 comparendos y ninguno por no respetar el paso de peatones.

# Cuenta comparendos por cada combinación (infracción, vehículo).
consulta1 <- df %>%
  count(NOM.INFRACCION, VEHICULO) %>%
  # complete() agrega las combinaciones que no existen (ej.: moto + peatones) con n = 0,
  # para que aparezca la barra en cero y los dos vehículos se vean siempre
  complete(NOM.INFRACCION, VEHICULO, fill = list(n = 0))   # incluye combinaciones con 0

# GRÁFICO DE BARRAS AGRUPADAS (una barra por vehículo dentro de cada infracción)
# reorder(NOM.INFRACCION, n, sum): ordena las infracciones por la suma de n
ggplot(consulta1, aes(x = reorder(NOM.INFRACCION, n, sum), y = n, fill = VEHICULO)) +
  geom_col(position = position_dodge(width = 0.9)) +   # dodge = barras una al lado de la otra (no apiladas)
  geom_text(aes(label = fmt_n(n)),
            position = position_dodge(width = 0.9), hjust = -0.15, size = 3.8) +  # misma posición dodge para que cada número quede en su barra
  coord_flip() +                                       # barras horizontales
  scale_x_discrete(labels = label_wrap(28)) +          # nombres largos en varias líneas
  scale_y_continuous(labels = fmt_n, expand = expansion(mult = c(0, 0.15))) +
  scale_fill_manual(values = c(AUTO = "#B3201F", MOTO = "#4B5563")) +
  labs(title = "Infracciones cometidas por tipo de vehículo",
       x = "Infracción", y = "Cantidad de comparendos", fill = "Vehículo")

4.2 Consulta 2: infracciones por género

Cantidad de comparendos de cada infracción según el género del ejecutado. Las mujeres encabezan en velocidad (736), luz roja (181) y paso de peatones (52); en sentido contrario, con solo 4 casos, predominan los hombres. En el paso de peatones las empresas (31) superan a los hombres (22).

# Igual que la consulta 1, pero cruzando infracción con género (3 barras por infracción)
consulta2 <- df %>%
  count(NOM.INFRACCION, GENERO) %>%
  complete(NOM.INFRACCION, GENERO, fill = list(n = 0))   # rellena con 0 las combinaciones inexistentes

ggplot(consulta2, aes(x = reorder(NOM.INFRACCION, n, sum), y = n, fill = GENERO)) +
  geom_col(position = position_dodge(width = 0.9)) +   # barras agrupadas
  geom_text(aes(label = fmt_n(n)),
            position = position_dodge(width = 0.9), hjust = -0.15, size = 3.6) +
  coord_flip() +
  scale_x_discrete(labels = label_wrap(28)) +
  scale_y_continuous(labels = fmt_n, expand = expansion(mult = c(0, 0.15))) +
  scale_fill_manual(values = c(MUJER = "#B3201F", HOMBRE = "#4B5563", EMPRESA = "#9CA3AF")) +
  labs(title = "Infracciones por género",
       x = "Infracción", y = "Cantidad de comparendos", fill = "Género")

4.3 Consulta 3: comparendos por día

Número de comparendos de cada fecha, coloreado por mes (abril: 712, mayo: 1.408; total: 2.120). La línea punteada marca el promedio diario (46,1 comparendos). El día más alto fue el 1 de mayo, con 89.

# Cuenta comparendos por fecha y mes; name= nombra la columna del conteo "Comparendos"
consulta3 <- df %>%
  count(FECHA, `NOM MES`, name = "Comparendos")
promedio_dia <- mean(consulta3$Comparendos)   # promedio de comparendos por día (se usa para la línea punteada)

# GRÁFICO DE BARRAS POR DÍA, coloreado por mes
ggplot(consulta3, aes(x = FECHA, y = Comparendos, fill = `NOM MES`)) +
  geom_col() +                                              # una barra por día
  geom_text(aes(label = fmt_n(Comparendos)), vjust = -0.4, size = 3.2) +   # cantidad sobre cada barra
  # línea horizontal en el promedio; linetype="dashed" = punteada
  geom_hline(yintercept = promedio_dia, linetype = "dashed", color = "#1F2937") +
  # annotate() pone UN texto suelto (no depende de los datos): rótulo "Promedio: 46,1"
  annotate("text", x = min(consulta3$FECHA), y = promedio_dia,
           label = paste0("Promedio: ", fmt_d1(promedio_dia)),
           hjust = 0, vjust = -0.6, size = 3.8) +
  scale_x_date(date_breaks = "1 day", date_labels = "%d/%m") +
  scale_y_continuous(labels = fmt_n, expand = expansion(mult = c(0, 0.1))) +
  scale_fill_manual(values = c(ABRIL = "#9CA3AF", MAYO = "#B3201F")) +   # gris para abril, rojo para mayo
  labs(title = "Comparendos por día", x = "Fecha",
       y = "Cantidad de comparendos", fill = "Mes") +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1))   # fechas giradas

4.4 Consulta 4: relación de infracciones con el género (suma de Valor)

Valor total sancionado en cada infracción, según el género. Las mujeres aportan el mayor valor en todas las infracciones salvo transitar en sentido contrario, donde los hombres suman $1.545.000 de $2.060.000. Dentro de cada barra se muestra el valor de cada género (en millones de pesos, M) y al final de la barra el total de la infracción.

# Suma el Valor por (infracción, género)
consulta4 <- df %>%
  group_by(NOM.INFRACCION, GENERO) %>%
  summarise(Total = sum(Valor), .groups = "drop") %>%    # .groups="drop" quita la agrupación al terminar
  mutate(NOM.INFRACCION = reorder(NOM.INFRACCION, Total, sum))   # ordenar por total

# Tabla aparte con el total de cada infracción (suma de los tres géneros) para rotular el final de la barra
totales4 <- consulta4 %>%
  group_by(NOM.INFRACCION) %>%
  summarise(Total = sum(Total))

# GRÁFICO DE BARRAS APILADAS: cada barra se divide en colores por género
ggplot(consulta4, aes(x = NOM.INFRACCION, y = Total, fill = GENERO)) +
  geom_col() +    # sin position_dodge => las barras se apilan (comportamiento por defecto)
  # Etiqueta dentro de cada segmento; ifelse oculta las menores a 20 millones (2e7) para que no se amontonen
  geom_text(aes(label = ifelse(Total >= 2e7, fmt_m1(Total), "")),     # etiqueta de cada segmento
            position = position_stack(vjust = 0.5), color = "white", size = 3.6) +
  # Segunda capa de texto con OTROS datos (totales4): el total de cada infracción al final de la barra
  # inherit.aes = FALSE: no hereda las estéticas del ggplot() principal (porque aquí no hay GENERO)
  geom_text(data = totales4, aes(x = NOM.INFRACCION, y = Total, label = fmt_m1(Total)),
            inherit.aes = FALSE, hjust = -0.1, size = 3.8) +          # total de la infraccion
  coord_flip() +
  scale_x_discrete(labels = label_wrap(28)) +
  scale_y_continuous(labels = fmt_m0, expand = expansion(mult = c(0, 0.2))) +
  scale_fill_manual(values = c(MUJER = "#B3201F", HOMBRE = "#4B5563", EMPRESA = "#9CA3AF")) +
  labs(title = "Valor total sancionado por infracción y género",
       x = "Infracción", y = "Valor total (millones de COP)", fill = "Género")

4.5 Consulta 5: ejecutados con más comparendos por mes

Los diez ejecutados con más comparendos de cada tipo de identificación (con empates incluidos), discriminados por mes. Entre las empresas (NIT) lidera Leasing Bancolombia S.A. C.F. con 61 comparendos y se repiten varias entidades financieras y de leasing, lo que es consistente con vehículos financiados a nombre de la entidad. Entre las personas con cédula de ciudadanía destaca Liliana Rozo Pinzon con 27, de los cuales 23 fueron en mayo. Debajo del gráfico se incluye una tabla auxiliar con las cantidades, los porcentajes y el valor sancionado de cada ejecutado.

consulta5 <- df %>%
  count(`TIPO DE IDENTIFICACION`, EJECUTADO, `NOM MES`) %>%   # comparendos por tipo de ID, ejecutado y mes
  # pivot_wider: pasa los meses de filas a columnas (una columna ABRIL y otra MAYO);
  # values_fill = 0 pone 0 si el ejecutado no tuvo comparendos ese mes
  pivot_wider(names_from = `NOM MES`, values_from = n, values_fill = 0) %>%
  mutate(Total = ABRIL + MAYO) %>%                  # total del ejecutado = abril + mayo
  group_by(`TIPO DE IDENTIFICACION`) %>%            # agrupa por tipo de identificación (CC, NIT, ...)
  slice_max(Total, n = 10, with_ties = TRUE) %>%     # top 10 por tipo de identificacion (with_ties = TRUE conserva los empatados)
  ungroup()                                         # quita la agrupación

# pivot_longer: hace lo contrario, vuelve ABRIL y MAYO a filas (formato "largo" que necesita ggplot
# para poder colorear por mes)
consulta5_largo <- consulta5 %>%
  pivot_longer(cols = c(ABRIL, MAYO), names_to = "Mes", values_to = "Comparendos")

# GRÁFICO DE BARRAS AGRUPADAS POR MES Y DIVIDIDO EN PANELES (facetas) POR TIPO DE ID
ggplot(consulta5_largo, aes(x = Comparendos, y = reorder(EJECUTADO, Total), fill = Mes)) +
  geom_col(position = position_dodge(width = 0.9), orientation = "y") +   # orientation="y": barras horizontales sin usar coord_flip
  geom_text(aes(label = fmt_n(Comparendos)),
            position = position_dodge(width = 0.9), hjust = -0.2, size = 3.4) +
  # facet_grid: un panel por cada tipo de identificación, apilados verticalmente;
  # scales/space = "free_y": cada panel solo muestra sus propios ejecutados y ajusta su altura
  facet_grid(`TIPO DE IDENTIFICACION` ~ ., scales = "free_y", space = "free_y") +
  scale_x_continuous(labels = fmt_n, expand = expansion(mult = c(0, 0.12))) +
  scale_fill_manual(values = c(ABRIL = "#9CA3AF", MAYO = "#B3201F")) +
  labs(title = "Ejecutados con más comparendos por mes",
       x = "Cantidad de comparendos", y = "Ejecutado", fill = "Mes") +
  theme(strip.text.y = element_text(angle = 0))    # rótulo del panel (CC / NIT) en horizontal

4.6 Consulta 6: dinero recaudado por género y tipo de vehículo

Valor total sancionado por género, separado por tipo de vehículo. Las mujeres encabezan con $296.640.000 (45,9 % del total); en los tres grupos la mayor parte corresponde a automóviles, que suman $593.280.000 de los $646.067.500 del total.

# Suma el Valor por (género, vehículo)
consulta6 <- df %>%
  group_by(GENERO, VEHICULO) %>%
  summarise(Total = sum(Valor), .groups = "drop")

# GRÁFICO DE BARRAS AGRUPADAS: valor sancionado por género, una barra por vehículo
# reorder(GENERO, -Total, sum): ordena los géneros de mayor a menor suma de Total
ggplot(consulta6, aes(x = reorder(GENERO, -Total, sum), y = Total, fill = VEHICULO)) +
  geom_col(position = position_dodge(width = 0.9)) +
  geom_text(aes(label = fmt_m1(Total)),                    # valor en millones sobre cada barra
            position = position_dodge(width = 0.9), vjust = -0.4, size = 3.8) +
  scale_y_continuous(labels = fmt_m0, expand = expansion(mult = c(0, 0.12))) +
  scale_fill_manual(values = c(AUTO = "#B3201F", MOTO = "#4B5563")) +
  labs(title = "Valor sancionado por género y tipo de vehículo",
       x = "Género", y = "Valor total (millones de COP)", fill = "Vehículo")

5 Conclusión

El análisis de los 2.120 comparendos registrados entre el 12 de abril y el 27 de mayo de 2010 en la calle 72 de Barranquilla permite destacar lo siguiente:

  • Infracciones: la velocidad superior a la máxima permitida es la infracción dominante (1.626 comparendos, 76,7 %) y aporta el 64,8 % del valor total, que es de $646.067.500. Junto con no detenerse ante la luz roja o amarilla suman el 95,5 %.
  • Tiempo: los comparendos aumentan desde finales de abril y se concentran en fines de semana: sábados y domingos promedian 62 y 66 comparendos por día frente a unos 40 entre semana. El aumento de abril a mayo es de 39 % en promedio diario, no del 98 % que sugiere el total, porque la base trae menos días de abril.
  • Infractores: las mujeres registran el 45,8 % de los comparendos, los hombres el 32,2 % y las empresas el 22,0 %; las empresas, con muy pocos ejecutados, tienen el mayor promedio de comparendos por ejecutado. El 91,4 % de los comparendos corresponde a automóviles.
  • Reincidencia: 290 personas naturales tienen dos o más comparendos y una de ellas acumula 27, lo que sugiere que las sanciones por sí solas no cambian la conducta de algunos infractores.

En conjunto, los resultados indican que las intervenciones de seguridad vial en este punto deberían priorizar el control de velocidad, especialmente los fines de semana, y estrategias diferenciadas para propietarios institucionales, como entidades de leasing, y para los reincidentes. Limitaciones: el periodo es corto (46 días), no hay datos de flujo vehicular que permitan calcular tasas y el valor corresponde a la sanción impuesta, no al pago efectivo.

6 Referencias

  • Secretaría de Tránsito y Transporte de Barranquilla. Base de datos de comparendos con mandamiento de pago, 2010.
  • Ley 769 de 2002, Código Nacional de Tránsito Terrestre (Colombia).
  • Decreto 5053 de 2009, por el cual se fija el salario mínimo mensual legal vigente para 2010 en $515.000.
  • Wickham, H. et al. dplyr, tidyr, ggplot2 y readxl. The Comprehensive R Archive Network (CRAN).
  • Cheng, J. et al. leaflet: Create Interactive Web Maps with the JavaScript ‘Leaflet’ Library. CRAN. Mapa base © colaboradores de OpenStreetMap.