Análisis y clasificación de la gravedad de los siniestros viales en CABA
Un pipeline reproducible — de la ingesta a la predicción — sobre 37.849 siniestros reales: qué los hace graves, cuántos habrá, y dónde.
Un pipeline reproducible — de la ingesta a la predicción — sobre 37.849 siniestros reales: qué los hace graves, cuántos habrá, y dónde.
Todo el trabajo sigue el proceso KDD / CRISP-DM: selección, limpieza, transformación, minería y evaluación de patrones.
Un dataset entra solo si comparte llave a granularidad útil, aporta señal medible contra el target, y cubre el período. Documentar lo que no aporta también es un resultado.
La base y lo que integramos
No aportaban señal · 4 de 6
Un dataset entra solo si comparte llave útil, aporta señal medible y cubre el período. Documentar el resultado negativo con números es parte del entregable.
Precipitación y temperatura por día → la feature llovió_ese_día, con variación real entre siniestros.
78 feriados 2021–24 → la feature es_feriado, que complementa al fin de semana.
Vehículos por peaje — el único conteo vehicular continuo de CABA → proxy de exposición y tránsito.
Lo que define si un dato sirve es la granularidad, no la fuente: el clima diario varía entre siniestros del mismo mes; los peajes son el único conteo vehicular continuo de la Ciudad. Todo entra como archivos estáticos versionados — sin APIs en runtime.
La hora pico de siniestros sigue la hora pico de tránsito. Tras la moto, el peatón; la contraparte más común es el auto.

Siniestros / 100.000 hab / año · Censo 2022.
Comuna 1: 458 — 2,3× la Comuna 6.
Crecimiento sostenido tras la pandemia: +35,2% entre 2021 y 2024.
La línea roja —el % que termina severo— se mantiene en una banda baja: crece la cantidad, no la gravedad.
Pero el número crudo esconde una pregunta: ¿empeoró la seguridad vial, o solo volvió el tránsito a la calle?
Proxy: peajes de autopistas (IDECBA), el único conteo vehicular continuo de la Ciudad. Sin normalizar por exposición, el número crudo engaña.
Avenidas · sensores de volumen 2024 (95 puntos)
Av. Rivadavia
Primera en crudo — y sigue primera normalizada (65/millón). Su volumen no la explica.
Av. Corrientes
Top-5 en crudo, cae al último normalizada (19/millón). Su ranking era puro tránsito.
Autopistas · peajes IDECBA, siniestros por millón de vehículos
La Illia tiene 5× menos siniestros por vehículo pero la mayor severidad: pocos choques, y a alta velocidad. El sensor mide un punto, no toda la arteria — índice comparativo.
Siniestros por día, según la lluvia
−8% con lluvia · −21% con lluvia fuerte. Dosis-dependiente.
Es una caída general de exposición — circula menos gente. La gravedad del siniestro individual no cambia: 5,7% vs 5,9% severo.
Confirmado por 3 fuentes independientes
De la zona peatonal a la autopista, los siniestros severos se multiplican por cuatro. El límite legal lo fija el tipo de vía.
La receta · 8 variables del siniestro
Imputación por mediana → StandardScaler (estandarizar evita que la edad domine por escala) → k-means (random_state=42, n_init=10).
k elegido por el método del codo (k=2…8)
Cómo se nombró cada cluster
| Perfil | Rasgo dominante | % severo |
|---|---|---|
| Persona mayor | edad 73 · sin moto | 12,9% |
| Peatón adulto | ~36 · peatón | 10,3% |
| Moto laboral | ~32 · 70% masc · semana | 7,0% |
| Finde joven | ~32 · 100% finde | 6,3% |
| Vehicular leve | auto vs auto · sin moto/peatón | 2,0% |
k-means devuelve grupos anónimos (0–4). Cada uno se etiquetó mirando la media de sus features por cluster —edad, % moto, % peatón, % finde— y su rasgo dominante.
Qué es y para qué lo usamos
PCA combina las 8 variables del perfil en componentes ortogonales, ordenados por cuánta varianza capturan. Le dimos dos usos:
1 · Visualizar — proyectar los 5 perfiles a 2D para ver que se separan (el scatter de la slide de perfiles).
2 · Decidir si reducir dimensiones antes de modelar — y la respuesta fue que no.
Varianza acumulada por componente
Hacen falta 7 de 8 componentes para el 80% de la varianza: no hay un eje dominante. Las features son casi ortogonales → PCA no sirve para comprimir, solo para proyectar y poder ver los grupos.
% de siniestros severos · graves + mortales
×2,2 más severos — sobre una base global de 5,7%
% de siniestros con víctimas menores de 30
Casi 1 de cada 2 siniestros de esa franja es de un menor de 30 (edad mediana 31 vs 35): la madrugada de finde es cuando los jóvenes están al volante.
Matiz importante: la severidad individual por edad la dominan los mayores de 65 (12,9% severo, fragilidad), no los jóvenes (7,2%). Exposición y vulnerabilidad son mecanismos distintos.
Primera tarea · predecir si un siniestro será grave
La accuracy miente
Un modelo que diga siempre "LEVE" —sin mirar ninguna variable— acierta el 94% de las veces. Con un target así de desbalanceado, el puntaje engaña: hay que medir cuántos severos realmente caza.
El target original, reagrupado
Solución: binario LEVE vs SEVERO (5,7%), class_weight=balanced, y evaluar con precision, recall, F1-Score y ROC-AUC —no accuracy— sobre validación cruzada estratificada de 5 particiones.
Del registro crudo a la matriz del modelo
Importancia en el RF · top 5 (Gini)
Categóricas → OneHotEncoder(handle_unknown='ignore'); numéricas → imputación por mediana + StandardScaler. Excluimos los flags *_DESCONOCIDO: la completitud del registro es consecuencia de la gravedad (leakage de proceso) → ver Limitaciones.
El pipeline · preproceso por tipo de columna
Todo el preproceso vive dentro del Pipeline de scikit-learn: se ajusta solo con el train de cada fold, nunca con validación — sin leakage.
Modelo final: Random Forest · n=300 · max_depth=10 · min_samples_leaf=5.
El protocolo de validación
Holdout 20% estratificado, intacto — toda la selección de modelos ocurre solo sobre el train.
Validación cruzada estratificada de 5 folds (random_state=42), mismo protocolo para los 5 modelos.
class_weight='balanced' — compensa que solo el 5,7% es severo.
Árboles con criterio Gini + poda (max_depth, min_samples_leaf) contra el overfitting.
Métricas de evaluación: precision · recall · F1 · ROC-AUC (no accuracy).
Qué probamos
Un segundo k-means (k=8) que agrupa las coordenadas (lat, lon) en zonas y le pasa esa etiqueta al Random Forest como una variable más.
(lat, lon) → k-means(8) → Z0…Z7 → one-hot → RF
Ajustado dentro de cada fold (KMeansZonaAdder) para no filtrar validación. El protocolo fue limpio — el resultado, igual de pobre.
Qué pasó
La sacamos del modelo final. La gravedad casi no depende del lugar: es un perfil, no una zona. El k-means vale acá como herramienta descriptiva (los 5 perfiles, slides 12-13), no como feature del clasificador.
Validación cruzada estratificada · 5 folds · sobre el train — el holdout se mide en la próxima slide
| Modelo | Accuracy | Precisión sev. | Recall sev. | F1 sev. ★ | ROC-AUC |
|---|---|---|---|---|---|
| Árbol de decisión · d=4 interpretable | 0,626 | 0,111 | 0,79 | 0,194 | 0,756 |
| Árbol de decisión · d=7 | 0,690 | 0,125 | 0,73 | 0,214 | 0,768 |
| Regresión logística | 0,715 | 0,137 | 0,75 | 0,231 | 0,815 |
| Random Forest · n=300, d=10 | 0,778 | 0,157 | 0,65 | 0,253 | 0,811 |
| + zona k-means ablación | 0,794 | 0,166 | 0,64 | 0,264 | 0,816 |
Ordenamos por F1, el balance de precisión y recall — la métrica correcta con clases desbalanceadas. Ahí RF gana.
Entre los más altos; la diferencia con la logística (0,815) está dentro del ruido (±0,011).
El d=4 llega a 0,79 de recall pero marcando de más (F1 0,194). RF no canjea precisión por recall.
La última fila es la ablación: sumar la zona del k-means da +0,005 ROC-AUC, dentro del ruido — nos quedamos con el modelo más simple. Criterio Gini, poda preventiva contra el sobreajuste.
Matriz de confusión · holdout (test 20%) · umbral 0,50
Fila superior: aciertos · inferior: errores. % sobre cada clase real (test 20%, umbral 0,50).
El recall se elige con el umbral · sobre el holdout
Es un triage de gravedad: al registrar el siniestro —quién, dónde, edad— estima su gravedad para priorizar la respuesta. Como perder un severo cuesta más que una falsa alarma, bajamos el umbral y el recall sube de 0,67 a 0,85 — sin cambiar de modelo, eligiendo su punto sobre la curva.
Identifica factores de gravedad — peatón, 65+, madrugada, tipo de vía — con los datos del parte.
Atrapa 79 de cada 100 severos, y sus reglas se leen una por una. Sacrifica precisión, pero como red de seguridad auditable es justo lo que se quiere.
No es una alternativa de más recall al RF: a la misma precisión, el RF calibrado da 0,85 vs 0,79. El árbol no discrimina mejor; su valor está en que es legible. Por eso es complemento, no el modelo final. Criterio Gini, profundidad 4, reglas con plot_tree.
Cómo decide, en factores
Antes de estos tres, el árbol abre separando el perfil de la víctima (% de varones — moto vs. peatón), que no es un "más/menos" sino un tipo de siniestro. Cada factor de acá ≈ duplica la tasa base de 5,7%.
La receta · una fila por día (1.461 días)
Validación temporal, no aleatoria: entrenamos con 2021–2023 y testeamos sobre 2024, un año que el modelo nunca vio.
Tres modelos · gana la lineal
R² = fracción de la variación diaria explicada. Los árboles solo predicen valores vistos en el train; la lineal proyecta la tendencia (recuperación post-pandemia). Por eso gana — por margen, no por goleada.
Coeficientes · efecto en siniestros por día
Por millón de vehículos extra · escala en siniestros/día
Acierta dentro de ±7 sobre ~26 por día. En conteos diarios el R² tiene techo bajo: lo que falta para 1,0 es ruido día-a-día irreducible, no error del modelo.
El feriado es el factor más fuerte — más que cualquier variable climática. Validado sobre 2024, un año nunca visto.
Los árboles no extrapolan la tendencia creciente; la lineal sí. Reemplazar la "tendencia" abstracta por el tránsito real del mes la explica por su mecanismo: la recuperación post-pandemia.
La receta
Cada siniestro es una canasta de ítems: franja horaria, tipo de calle, víctima, acusado, finde/semana — más su etiqueta LEVE / SEVERO.
El algoritmo Apriori busca combinaciones frecuentes y arma reglas; nos quedamos solo con las que terminan en SEVERO y las ordenamos por lift.
Las tres métricas
Qué tan común es la combinación X — su frecuencia sobre los 37.849 siniestros. Pedimos ≥ 1% para evitar reglas anecdóticas.
De los siniestros que cumplen X, qué proporción termina severa. Es la probabilidad condicional de gravedad dada la combinación.
Confianza dividida la base global (5,7%). Lift 2 = el doble de probable que el promedio. Filtramos lift ≥ 1,2.
La receta
Un punto es núcleo si tiene al menos min_samples vecinos dentro de un radio eps. Los núcleos conectados forman un hotspot; lo que queda aislado es ruido y se descarta.
Trabaja sobre coordenadas reales con distancia geográfica (haversine), no euclídea sobre grados.
Por qué DBSCAN y no k-means
A diferencia de k-means, no le decís cuántos grupos hay: DBSCAN descubre cuántos focos densos existen — encontró 29.
k-means asigna todo punto a una zona; DBSCAN deja fuera lo disperso y se queda solo con los focos densos — justo lo que es una "esquina peligrosa".
La señal de gravedad está en el quién y el cuándo — peatón, moto, mayores de 65, madrugada de finde, tipo de vía — no en el ambiente ni en el lugar exacto.
El crecimiento es ante todo exposición: dos tercios del +35% lo explica el tránsito (+22%); la tasa por vehículo subió solo +10%.
La lluvia es un factor de frecuencia (menos exposición → menos siniestros), no de gravedad. Confirmado por tres fuentes.
La velocidad de la vía es el factor estructural de severidad más claro: el gradiente 20 → 80 km/h multiplica por 4 los siniestros severos.
Proteger peatones en avenidas, controlar madrugadas de finde, atender a los mayores de 65 e intervenir los hotspots.
Evaluar la integración antes de integrar, reagrupar el target (la mejora más barata) y quedarse con el modelo más simple cuando la mejora no supera el ruido.
Integrantes