library(tidyverse)   # dplyr, ggplot2, readr, tidyr
library(sf)          # mapas (shapefiles)
library(leaflet)     # mapa interactivo
library(scales)      # formato de números
library(knitr)       # tablas
library(kableExtra)  # tablas con formato
library(ggrepel)     # etiquetas que no se pisan
library(patchwork)   # combinar gráficos

# Paleta y tema del curso
morado   <- "#351A4F"
magenta  <- "#D9006C"
turquesa <- "#168C86"
gris     <- "#D9D6DD"

tema_clase <- theme_minimal(base_size = 12) +
  theme(
    plot.title.position = "plot",
    plot.title    = element_text(face = "bold", size = 14, hjust = 0),
    plot.subtitle = element_text(size = 10.5, color = "grey35", hjust = 0),
    panel.grid.minor = element_blank(),
    plot.caption  = element_text(hjust = 0, color = "grey45", size = 8.5),
    plot.margin   = margin(10, 20, 10, 10)
  )
theme_set(tema_clase)

num  <- function(x, d = 0) format(round(x, d), big.mark = ".", decimal.mark = ",", nsmall = d)
pct  <- function(x, d = 0) paste0(num(x, d), " %")
pesos <- function(x) paste0("$", num(x / 1e6, 1), " M")

1 Introducción y preguntas

Bogotá se divide en 112 UPZ urbanas. Para cada una sabemos cuántos delitos se denunciaron entre enero y julio de 2023, quién vive allí, cuánto gana, cómo es su entorno y qué tan insegura se siente. La pregunta de fondo del taller es:

¿Qué nos dicen los datos sobre la inseguridad en Bogotá y su relación con el territorio y las condiciones de vida de cada UPZ?

Partimos de un EDA preliminar (notebook en Python) centrado en el hurto y su relación con pobreza, entorno y CAI. Al revisarlo con cuidado descubrimos que varias de sus conclusiones eran artefactos de cómo se calcula la tasa (sección 2.3). Corregirlo nos llevó a la historia de este informe: en Bogotá no hay una inseguridad, sino dos, que viven en lugares distintos.

1.1 Preguntas

# Pregunta
P1 ¿Las UPZ con más denuncias son las más inseguras, o solo las más pobladas? ¿Qué hacemos con las UPZ casi deshabitadas?
P2 ¿Dónde se concentra el hurto a personas y tiene que ver con la pobreza?
P3 ¿Cambia el tipo de delito predominante según el nivel de ingreso de la UPZ?
P4 ¿La cercanía a ollas, bares, lotes oscuros y el alumbrado se relacionan con ciertos delitos?
P5 ¿La percepción de inseguridad sigue a las denuncias?
P6 ¿Qué tan lejos queda la policía (CAI) en las UPZ con tasas más altas?

1.2 Variables elegidas (17, de 4 bloques)

vars_elegidas <- tribble(
  ~bloque,                     ~variable,                       ~por_que,
  "Demografía",                "poblacion",                     "Denominador de toda tasa: hay que mirarla antes de confiar en cualquier tasa (P1).",
  "Demografía",                "densidad_hab_km2",              "Distingue zonas residenciales densas de zonas de paso (P1, P2).",
  "Vivienda/infraestructura",  "luminarias_km2",                "Alumbrado por área: proxy de calles iluminadas (P4).",
  "Vivienda/infraestructura",  "pct_luminarias_led",            "Calidad del alumbrado (P4).",
  "Delitos",                   "casos_total",                   "Volumen de denuncias: lo que se ve en un conteo simple (P1).",
  "Delitos",                   "tasa_total",                    "Riesgo por habitante de los 10 delitos (P1, P6).",
  "Delitos",                   "tasa_hurto_personas",           "Delito más frecuente (más de la mitad de las denuncias) (P2).",
  "Delitos",                   "tasa_homicidios",               "Delito violento más grave (P3, P5).",
  "Delitos",                   "tasa_violencia_intrafamiliar",  "Violencia en el hogar, otra lógica distinta al hurto (P3, P5).",
  "Delitos",                   "tasa_lesiones",                 "Violencia interpersonal en la calle (P3, P4).",
  "Encuesta 2021",             "salario_promedio",              "Nivel de ingreso; base de los grupos socioeconómicos (P3).",
  "Encuesta 2021",             "pct_pobreza",                   "Pobreza, la hipótesis clásica del delito (P2).",
  "Encuesta 2021",             "pct_percibe_inseguridad",       "Percepción, no conteo: ¿coincide con las denuncias? (P5).",
  "Encuesta 2021",             "pct_cerca_ollas",               "Entorno: expendios de droga (P4).",
  "Encuesta 2021",             "pct_cerca_bares",               "Entorno: bares y prostíbulos, zonas de rumba (P4).",
  "Encuesta 2021",             "pct_cerca_lotes_oscuros",       "Entorno: lotes baldíos o sitios oscuros (P4).",
  "Encuesta 2021",             "min_cai_policia",               "Minutos caminando al CAI: presencia policial (P6)."
)
kable(vars_elegidas, col.names = c("Bloque", "Variable", "Por qué la elegimos")) %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = TRUE, font_size = 13) %>%
  column_spec(2, monospace = TRUE)
Bloque Variable Por qué la elegimos
Demografía poblacion Denominador de toda tasa: hay que mirarla antes de confiar en cualquier tasa (P1).
Demografía densidad_hab_km2 Distingue zonas residenciales densas de zonas de paso (P1, P2).
Vivienda/infraestructura luminarias_km2 Alumbrado por área: proxy de calles iluminadas (P4).
Vivienda/infraestructura pct_luminarias_led Calidad del alumbrado (P4).
Delitos casos_total Volumen de denuncias: lo que se ve en un conteo simple (P1).
Delitos tasa_total Riesgo por habitante de los 10 delitos (P1, P6).
Delitos tasa_hurto_personas Delito más frecuente (más de la mitad de las denuncias) (P2).
Delitos tasa_homicidios Delito violento más grave (P3, P5).
Delitos tasa_violencia_intrafamiliar Violencia en el hogar, otra lógica distinta al hurto (P3, P5).
Delitos tasa_lesiones Violencia interpersonal en la calle (P3, P4).
Encuesta 2021 salario_promedio Nivel de ingreso; base de los grupos socioeconómicos (P3).
Encuesta 2021 pct_pobreza Pobreza, la hipótesis clásica del delito (P2).
Encuesta 2021 pct_percibe_inseguridad Percepción, no conteo: ¿coincide con las denuncias? (P5).
Encuesta 2021 pct_cerca_ollas Entorno: expendios de droga (P4).
Encuesta 2021 pct_cerca_bares Entorno: bares y prostíbulos, zonas de rumba (P4).
Encuesta 2021 pct_cerca_lotes_oscuros Entorno: lotes baldíos o sitios oscuros (P4).
Encuesta 2021 min_cai_policia Minutos caminando al CAI: presencia policial (P6).

Dejamos por fuera, a propósito, los grupos de edad, las viviendas, el estrato detallado y los demás tiempos de desplazamiento: están muy correlacionados con el salario (estrato y educación cuentan casi la misma historia) o no se conectan con ninguna de nuestras preguntas. Preferimos 17 variables bien contadas a 67 a medias.

2 Los datos y su exploración

2.1 Carga y unión con el mapa

# Rutas relativas: el .Rmd está en la carpeta del taller, junto a input/
upz     <- read_csv("input/upz_variables.csv", show_col_types = FALSE,
                    locale = locale(encoding = "UTF-8"))
upz_shp <- st_read("input/shp/upz_bogota.shp", quiet = TRUE)
loc_shp <- st_read("input/shp/localidades_bogota.shp", quiet = TRUE)

dim(upz)
## [1] 112  67

La tabla tiene 112 UPZ y 67 columnas. Todas las columnas de interés son numéricas, salvo los nombres (upz_nombre, localidad) y la marca lógica em_agrupada.

tibble(tipo = map_chr(upz, ~ class(.x)[1])) %>% count(tipo, name = "columnas")
# Faltantes en toda la tabla
sum(is.na(upz))
## [1] 0

No hay valores faltantes (0 NA). Que no falte nada no significa que todo sea confiable: los problemas de esta tabla no son vacíos, sino denominadores pequeños y valores repetidos.

mapa_upz <- upz_shp %>% left_join(upz %>% select(-upz_nombre), by = "upz_cod")
sum(is.na(mapa_upz$poblacion))  # 0 = todas las UPZ del mapa tienen datos
## [1] 0

2.2 La población por UPZ: el primer filtro

Antes de calcular cualquier tasa miramos el denominador.

umbral <- 10000
n_bajas <- sum(upz$poblacion < umbral)

ggplot(upz, aes(x = poblacion)) +
  geom_histogram(aes(fill = poblacion < umbral), bins = 30, color = "white",
                 boundary = 0) +
  geom_vline(xintercept = umbral, linetype = "dashed", color = magenta) +
  annotate("text", x = umbral + 4000, y = 13, hjust = 0, color = magenta, size = 3.6,
           label = paste0(n_bajas, " UPZ con menos de 10.000 hab:\naeropuerto, parques, zonas industriales")) +
  scale_fill_manual(values = c(`TRUE` = magenta, `FALSE` = gris), guide = "none") +
  scale_x_continuous(labels = label_comma(big.mark = ".")) +
  labs(title = paste0(n_bajas, " UPZ tienen tan poca gente que cualquier tasa por habitante se dispara"),
       subtitle = "Distribución de la población por UPZ (DANE 2023)",
       x = "Habitantes", y = "Número de UPZ",
       caption = "Fuente: proyecciones DANE 2023.")

upz %>%
  filter(poblacion < umbral) %>%
  arrange(poblacion) %>%
  transmute(UPZ = upz_nombre, Localidad = localidad,
            Habitantes = num(poblacion), Denuncias = casos_total,
            `Tasa total /10.000 hab` = num(tasa_total, 0)) %>%
  kable(align = "llrrr") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE, font_size = 13)
UPZ Localidad Habitantes Denuncias Tasa total /10.000 hab
El Mochuelo Ciudad Bolívar 30 34 11.333
Aeropuerto El Dorado Fontibón 883 545 6.172
Parque Entrenubes Usme 1.169 16 137
Parque Salitre Barrios Unidos 1.442 233 1.616
Paseo De Los Libertadores Usaquén 2.690 74 275
Guaymaral Suba 2.692 45 167
Jardin Botanico Engativá 2.900 266 917
Parque Simon Bolivar - Can Teusaquillo 3.323 958 2.883
La Academia Suba 5.260 123 234
Monte Blanco Ciudad Bolívar 6.575 80 122
Zona Industrial Puente Aranda 6.666 1573 2.360

El caso extremo es El Mochuelo: 30 habitantes y 34 denuncias producen una tasa de 11.333 por 10.000 hab, ¡más de una denuncia por habitante en siete meses! Lo mismo pasa en el Aeropuerto El Dorado o el Parque Simón Bolívar: son lugares donde mucha gente pasa pero casi nadie vive. Su tasa mide visitantes, no residentes.

Decisión: para todo análisis de tasas y correlaciones trabajamos con las 101 UPZ con 10.000 habitantes o más. Las 11 restantes se mantienen en el mapa (en gris) y en el análisis de casos absolutos. El umbral es pequeño frente a la mediana (54.102 hab) y deja fuera justo las UPZ no residenciales.

upz_f <- upz %>% filter(poblacion >= umbral)

2.3 Lo que el EDA preliminar no vio

Nuestro primer EDA promediaba tasas de hurto de UPZ por localidad, sin filtrar, y concluía que Ciudad Bolívar y Fontibón eran las localidades con más hurto a personas. Al repetir el cálculo correctamente (sumando casos y población de las UPZ residenciales de cada localidad) la conclusión se invierte:

loc_ingenuo <- upz %>% group_by(localidad) %>%
  summarise(tasa_promedio_sin_filtro = mean(tasa_hurto_personas))
loc_correcto <- upz_f %>% group_by(localidad) %>%
  summarise(tasa_corregida = sum(casos_hurto_personas) / sum(poblacion) * 10000)

comparacion_loc <- loc_ingenuo %>% left_join(loc_correcto, by = "localidad") %>%
  mutate(puesto_antes = min_rank(desc(tasa_promedio_sin_filtro)),
         puesto_despues = min_rank(desc(tasa_corregida))) %>%
  arrange(puesto_antes)

comparacion_loc %>%
  filter(localidad %in% c("Ciudad Bolívar", "Fontibón", "Santa Fe", "Teusaquillo",
                          "Los Mártires", "Chapinero", "Usme", "Bosa")) %>%
  transmute(Localidad = localidad,
            `Promedio simple, sin filtro` = num(tasa_promedio_sin_filtro),
            `Puesto antes` = puesto_antes,
            `Tasa corregida` = num(tasa_corregida),
            `Puesto después` = puesto_despues) %>%
  kable(align = "lrrrr", caption = "Hurto a personas por 10.000 hab: antes y después de corregir") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE, font_size = 13) %>%
  row_spec(which(comparacion_loc %>%
                   filter(localidad %in% c("Ciudad Bolívar", "Fontibón", "Santa Fe", "Teusaquillo",
                                           "Los Mártires", "Chapinero", "Usme", "Bosa")) %>%
                   pull(localidad) %in% c("Ciudad Bolívar", "Fontibón")),
           bold = TRUE, color = magenta)
Hurto a personas por 10.000 hab: antes y después de corregir
Localidad Promedio simple, sin filtro Puesto antes Tasa corregida Puesto después
Ciudad Bolívar 837 1 54 16
Fontibón 697 2 126 9
Santa Fe 582 3 434 3
Teusaquillo 572 4 344 5
Los Mártires 503 5 481 1
Chapinero 356 9 346 4
Bosa 43 18 44 18
Usme 42 19 43 19

Ciudad Bolívar pasa del puesto 1 al 16 de 19. Una sola UPZ de 30 habitantes había fabricado un “hallazgo”. El notebook también reportaba las tasas “por 100.000 hab”, cuando la fuente las da por 10.000, y atribuía a la población y la densidad el 80 % de la “importancia” en un Random Forest: era el denominador de la tasa reapareciendo como predictor. Lo contamos porque también es un hallazgo.

2.4 Distribuciones de las variables elegidas

etiquetas <- c(
  poblacion = "Población", densidad_hab_km2 = "Densidad (hab/km²)",
  luminarias_km2 = "Luminarias por km²", pct_luminarias_led = "% luminarias LED",
  casos_total = "Denuncias (casos)", tasa_total = "Tasa total /10.000",
  tasa_hurto_personas = "Tasa hurto personas", tasa_homicidios = "Tasa homicidios",
  tasa_violencia_intrafamiliar = "Tasa violencia intrafamiliar", tasa_lesiones = "Tasa lesiones",
  salario_promedio = "Salario promedio ($)", pct_pobreza = "% pobreza",
  pct_percibe_inseguridad = "% percibe inseguridad", pct_cerca_ollas = "% cerca de ollas",
  pct_cerca_bares = "% cerca de bares", pct_cerca_lotes_oscuros = "% cerca lotes oscuros",
  min_cai_policia = "Minutos al CAI"
)
vars <- names(etiquetas)

upz_f %>%
  select(all_of(vars)) %>%
  pivot_longer(everything()) %>%
  mutate(name = factor(etiquetas[name], levels = etiquetas)) %>%
  ggplot(aes(x = value)) +
  geom_histogram(bins = 20, fill = morado, color = "white") +
  facet_wrap(~ name, scales = "free", ncol = 4) +
  scale_x_continuous(labels = label_number(big.mark = ".", decimal.mark = ",", scale_cut = cut_short_scale())) +
  labs(title = "Casi todas las tasas de delito tienen una cola larga a la derecha",
       subtitle = "Distribución de las 17 variables elegidas · 101 UPZ con 10.000 hab o más",
       x = NULL, y = "UPZ") +
  theme(strip.text = element_text(face = "bold", size = 9),
        axis.text = element_text(size = 7.5))

Las tasas de delito (y el salario) son muy asimétricas: unas pocas UPZ concentran valores muy altos. Por eso, en lo que sigue usamos medianas además de medias y correlaciones de Spearman (por rangos), que no se dejan arrastrar por los extremos como la de Pearson.

2.5 Otra advertencia: los valores repetidos de la encuesta

En 32 UPZ la Encuesta Multipropósito no da un valor propio sino el de su grupo (em_agrupada = TRUE): varias UPZ vecinas comparten exactamente el mismo salario, la misma pobreza y la misma percepción. Esto reduce la variación real y puede inflar o atenuar correlaciones. Para comprobarlo, en la P4 repetimos las correlaciones solo con las UPZ no agrupadas.

upz_f %>% count(em_agrupada, name = "UPZ")

3 Análisis

3.1 Tabla resumen

resumen <- upz_f %>%
  select(all_of(vars)) %>%
  pivot_longer(everything(), names_to = "variable") %>%
  group_by(variable) %>%
  summarise(Media = mean(value), Mediana = median(value), `Desv. est.` = sd(value),
            Q1 = quantile(value, .25), Q3 = quantile(value, .75),
            Mín = min(value), Máx = max(value)) %>%
  mutate(variable = factor(variable, levels = vars)) %>%
  arrange(variable) %>%
  mutate(Variable = etiquetas[as.character(variable)], .before = 1) %>%
  select(-variable)

resumen %>%
  mutate(across(where(is.numeric), ~ ifelse(abs(.x) >= 100, num(.x, 0), num(.x, 2)))) %>%
  kable(align = "lrrrrrrr",
        caption = "Estadística descriptiva de las variables elegidas (101 UPZ con ≥ 10.000 hab)") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = TRUE, font_size = 13) %>%
  pack_rows("Demografía", 1, 2) %>%
  pack_rows("Infraestructura", 3, 4) %>%
  pack_rows("Delitos ene–jul 2023 (casos y tasas por 10.000 hab)", 5, 10) %>%
  pack_rows("Encuesta Multipropósito 2021", 11, 17)
Estadística descriptiva de las variables elegidas (101 UPZ con ≥ 10.000 hab)
Variable Media Mediana Desv. est. Q1 Q3 Mín Máx
Demografía
Población 78.247 62.139 56.791 38.856 103.350 11.247 305.907
Densidad (hab/km²) 21.117 21.229 9.404 14.209 27.471 3.042 46.157
Infraestructura
Luminarias por km² 982 1.047 346 727 1.192 116 1.751
% luminarias LED 43,93 50,42 32,13 9,92 74,44 0,00 95,77
Delitos ene–jul 2023 (casos y tasas por 10.000 hab)
Denuncias (casos) 1.573 1.285 996 888 2.031 115 5.441
Tasa total /10.000 299 187 354 126 273 64,47 2.344
Tasa hurto personas 168 96,03 224 54,33 160 15,70 1.488
Tasa homicidios 0,90 0,56 1,13 0,24 1,14 0,00 6,25
Tasa violencia intrafamiliar 21,72 17,65 24,08 12,46 23,33 5,36 232
Tasa lesiones 15,34 11,76 14,49 8,25 15,92 4,15 98,54
Encuesta Multipropósito 2021
Salario promedio ($) 2.553.007 1.826.624 1.768.638 1.229.038 3.574.567 845.688 7.977.532
% pobreza 3,99 2,70 3,83 0,99 5,79 0,07 16,56
% percibe inseguridad 61,29 65,97 18,67 48,52 74,30 17,45 92,11
% cerca de ollas 21,45 20,92 15,37 10,14 30,54 0,31 75,60
% cerca de bares 14,54 13,00 10,71 5,82 19,48 0,64 53,91
% cerca lotes oscuros 19,25 17,89 11,96 11,28 24,86 0,64 59,76
Minutos al CAI 18,09 17,49 4,40 15,06 20,54 10,06 35,11

Lectura rápida: la UPZ típica (mediana) registra 187 denuncias por 10.000 hab en siete meses, pero la media es 299: la diferencia la ponen unas pocas UPZ del centro. El salario medio por UPZ va de $0,8 M a $8,0 M, casi 10 veces más. Y en la UPZ mediana 66 % de los hogares dice tener problemas de inseguridad en su entorno.

3.2 P1. ¿Más denuncias = más inseguridad?

rho_casos_pob <- cor(upz$casos_total, upz$poblacion, method = "spearman")
rho_tasa_pob  <- cor(upz_f$tasa_total, upz_f$poblacion, method = "spearman")

top_casos <- upz %>% slice_max(casos_total, n = 6)
top_tasa  <- upz_f %>% slice_max(tasa_total, n = 6)

ggplot(upz, aes(x = poblacion, y = casos_total)) +
  geom_point(aes(color = poblacion < umbral), size = 2.3, alpha = 0.8) +
  geom_text_repel(data = bind_rows(top_casos, top_tasa, upz %>% filter(poblacion < 1000)) %>% distinct(upz_cod, .keep_all = TRUE),
                  aes(label = upz_nombre), size = 3, color = "grey20", max.overlaps = 20,
                  min.segment.length = 0) +
  scale_color_manual(values = c(`TRUE` = magenta, `FALSE` = morado),
                     labels = c(`TRUE` = "< 10.000 hab", `FALSE` = "≥ 10.000 hab"), name = NULL) +
  scale_x_log10(labels = label_comma(big.mark = ".")) +
  scale_y_log10(labels = label_comma(big.mark = ".")) +
  labs(title = "Las denuncias crecen con la población, pero el centro se sale de la tendencia",
       subtitle = paste0("Denuncias (ene–jul 2023) vs. población · escala log · Spearman = ",
                         num(rho_casos_pob, 2),
                         "\nLas UPZ que se salen hacia arriba son centros de comercio y paso."),
       x = "Población (log)", y = "Denuncias de los 10 delitos (log)",
       caption = "Fuente: SDSCJ (delitos) y DANE (población).") +
  theme(legend.position = "top", legend.justification = "left")

En general, el volumen de denuncias crece con la población (Spearman 0,56): UPZ enormes como El Rincón (295.322 hab) o Bosa Central acumulan muchos casos porque allí vive muchísima gente. Pero las tres UPZ con más denuncias absolutas de toda la ciudad (La Sabana, Chico Lago, Las Nieves) tienen menos de 40.000 habitantes: están muy por encima de la tendencia. Cuando se divide por habitantes, la relación se invierte: la tasa total se relaciona negativamente con la población (Spearman -0,55). Las UPZ con más riesgo por habitante son chicas y céntricas, con mucha población flotante que trabaja, compra o se divierte allí pero no vive allí. Ni el conteo ni la tasa son “la verdad”: el conteo mide carga para la policía; la tasa por residente sobrestima el riesgo en zonas de paso.

3.3 P2. ¿Dónde se concentra el hurto y tiene que ver con la pobreza?

# Quintiles calculados solo con UPZ residenciales; las de baja población quedan en gris
quintil <- function(x, ref) {
  cortes <- quantile(ref, probs = seq(0, 1, 0.2))
  cut(x, breaks = cortes, include.lowest = TRUE,
      labels = c("Muy baja", "Baja", "Media", "Alta", "Muy alta"))
}
mapa_upz <- mapa_upz %>%
  mutate(
    q_hurto = if_else(poblacion >= umbral,
                      as.character(quintil(tasa_hurto_personas, upz_f$tasa_hurto_personas)), NA_character_),
    tasa_violenta = tasa_homicidios + tasa_lesiones + tasa_violencia_intrafamiliar,
    q_violencia = if_else(poblacion >= umbral,
                          as.character(quintil(tasa_violenta,
                                               with(upz_f, tasa_homicidios + tasa_lesiones + tasa_violencia_intrafamiliar))),
                          NA_character_)
  )
niveles <- c("Muy baja", "Baja", "Media", "Alta", "Muy alta")
mapa_largo <- mapa_upz %>%
  select(upz_cod, q_hurto, q_violencia, geometry) %>%
  pivot_longer(c(q_hurto, q_violencia), names_to = "indicador", values_to = "quintil") %>%
  mutate(indicador = recode(indicador,
                            q_hurto = "Hurto a personas",
                            q_violencia = "Violencia (homicidios + lesiones + v. intrafamiliar)"),
         quintil = factor(quintil, levels = niveles))

ggplot(mapa_largo) +
  geom_sf(aes(fill = quintil), color = "white", linewidth = 0.15) +
  geom_sf(data = loc_shp, fill = NA, color = "grey25", linewidth = 0.3) +
  facet_wrap(~ indicador) +
  scale_fill_manual(values = c("#F1EAF4", "#D4BEDE", "#A57DB8", "#6E4290", morado),
                    na.value = "grey85", na.translate = TRUE,
                    labels = c(niveles, "< 10.000 hab (excluida)"),
                    name = "Tasa por 10.000 hab\n(quintiles)", drop = FALSE) +
  labs(title = "El hurto se concentra en el centro expandido; la violencia, en el centro y el sur",
       subtitle = "Quintiles de la tasa por 10.000 hab (ene–jul 2023) · 101 UPZ con 10.000 hab o más",
       caption = "Fuente: SDSCJ, DANE. Bordes oscuros = localidades. Gris = UPZ con menos de 10.000 habitantes.") +
  theme_void(base_size = 12) +
  theme(plot.title = element_text(face = "bold", size = 14),
        plot.subtitle = element_text(color = "grey35"),
        plot.caption = element_text(hjust = 0, color = "grey45", size = 8.5),
        strip.text = element_text(face = "bold", size = 11, margin = margin(b = 6)),
        legend.position = "right")

rho_pob_hurto <- cor(upz_f$pct_pobreza, upz_f$tasa_hurto_personas, method = "spearman")
rho_sal_hurto <- cor(upz_f$salario_promedio, upz_f$tasa_hurto_personas, method = "spearman")
pearson_crudo <- cor(upz$pct_pobreza, upz$tasa_hurto_personas)

ggplot(upz_f, aes(x = salario_promedio, y = tasa_hurto_personas)) +
  geom_smooth(method = "loess", se = FALSE, color = gris, linewidth = 1.2) +
  geom_point(aes(color = pct_pobreza), size = 2.6, alpha = 0.9) +
  geom_text_repel(data = upz_f %>% slice_max(tasa_hurto_personas, n = 5),
                  aes(label = upz_nombre), size = 3, color = "grey20") +
  scale_x_log10(labels = label_number(scale = 1e-6, suffix = " M", prefix = "$", decimal.mark = ",")) +
  scale_y_log10(labels = label_comma(big.mark = ".")) +
  scale_color_gradient(low = "#F4C7DC", high = magenta, name = "% pobreza") +
  labs(title = "Donde se gana más, se denuncian más hurtos a personas por habitante",
       subtitle = paste0("Tasa de hurto a personas vs. salario promedio (escalas log) · Spearman salario = ",
                         num(rho_sal_hurto, 2), "; pobreza = ", num(rho_pob_hurto, 2)),
       x = "Salario mensual promedio de la UPZ", y = "Hurto a personas por 10.000 hab",
       caption = "Fuente: SDSCJ 2023, Encuesta Multipropósito 2021.")

El hurto a personas no es un fenómeno de la pobreza. Es al revés: la correlación con la pobreza es -0,62 y con el salario 0,71. El EDA preliminar encontraba una correlación casi nula (Pearson 0,02) porque mezclaba las UPZ deshabitadas y usaba una medida sensible a extremos. La curva sube con fuerza hasta unos $3 M de salario promedio y luego se aplana: entre las UPZ de mayor ingreso, el hurto ya no crece, y los extremos (Las Nieves, La Sabana, Puente Aranda) son zonas de comercio, no de residencia rica. El hurto ocurre donde hay cosas que robar y gente que pasa: centro, Chapinero, Teusaquillo, el corredor nororiental. Ojo con un sesgo probable: en zonas de mayor ingreso también se denuncia más (más acceso, más bienes asegurados).

3.4 P3. ¿Cambia el tipo de delito según el ingreso?

upz_f <- upz_f %>%
  mutate(grupo_ingreso = ntile(salario_promedio, 4),
         grupo_ingreso = factor(grupo_ingreso, 1:4,
                                labels = c("Q1 · ingreso más bajo", "Q2", "Q3", "Q4 · ingreso más alto")))

nombres_delito <- c(
  hurto_personas = "Hurto a personas", hurto_celulares = "Hurto de celulares",
  violencia_intrafamiliar = "Violencia intrafamiliar", lesiones = "Lesiones",
  hurto_comercio = "Hurto a comercio", hurto_bicicletas = "Hurto de bicicletas",
  hurto_residencias = "Hurto a residencias", hurto_motos = "Hurto de motos",
  hurto_automotor = "Hurto de automotores", homicidios = "Homicidios"
)

# Tasa agregada del grupo = suma de casos / suma de población (no promedio de tasas)
tasas_grupo <- upz_f %>%
  select(grupo_ingreso, poblacion, starts_with("casos_"), -casos_total) %>%
  pivot_longer(starts_with("casos_"), names_to = "delito", names_prefix = "casos_", values_to = "casos") %>%
  group_by(grupo_ingreso, delito) %>%
  summarise(casos = sum(casos), poblacion = sum(poblacion), .groups = "drop") %>%
  group_by(delito) %>%
  mutate(tasa = casos / poblacion * 10000,
         indice = tasa / tasa[grupo_ingreso == "Q1 · ingreso más bajo"] * 100) %>%
  ungroup() %>%
  mutate(delito_nom = nombres_delito[delito])

rangos_salario <- upz_f %>% group_by(grupo_ingreso) %>%
  summarise(n = n(), sal_min = min(salario_promedio), sal_max = max(salario_promedio))
violentos <- c("homicidios", "violencia_intrafamiliar", "lesiones")

orden <- tasas_grupo %>% filter(grupo_ingreso == "Q4 · ingreso más alto") %>%
  arrange(indice) %>% pull(delito_nom)

tasas_grupo %>%
  mutate(delito_nom = factor(delito_nom, levels = orden),
         tipo = if_else(delito %in% violentos, "Violencia contra las personas", "Hurtos")) %>%
  ggplot(aes(x = tasa, y = delito_nom)) +
  geom_line(aes(group = delito_nom), color = gris, linewidth = 1.6) +
  geom_point(aes(fill = grupo_ingreso), shape = 21, size = 3.4, color = "white", stroke = 0.6) +
  facet_wrap(~ tipo, scales = "free", ncol = 2) +
  scale_x_log10(labels = label_number(decimal.mark = ",", big.mark = ".", accuracy = 0.1)) +
  scale_fill_manual(values = c("#E9A5C7", magenta, "#7A5A96", morado), name = "Grupo de ingreso") +
  labs(title = "En las UPZ más pobres pesa la violencia; en las más ricas, el hurto",
       subtitle = "Tasa agregada por 10.000 hab según cuartil de salario promedio de la UPZ (escala log)",
       x = "Denuncias por 10.000 hab (ene–jul 2023)", y = NULL,
       caption = "Tasa de cada grupo = suma de casos / suma de población de sus UPZ. Fuente: SDSCJ, EM 2021.") +
  theme(legend.position = "top", legend.justification = "left",
        strip.text = element_text(face = "bold", size = 11),
        panel.grid.major.y = element_blank())

tasas_grupo %>%
  filter(delito %in% c("homicidios", "violencia_intrafamiliar", "lesiones", "hurto_personas", "hurto_celulares")) %>%
  select(delito_nom, grupo_ingreso, tasa) %>%
  pivot_wider(names_from = grupo_ingreso, values_from = tasa) %>%
  mutate(`Razón Q4/Q1` = .[[5]] / .[[2]]) %>%
  mutate(across(where(is.numeric), ~ num(.x, 2))) %>%
  rename(Delito = delito_nom) %>%
  kable(align = "lrrrrr",
        caption = "Tasa por 10.000 hab según cuartil de ingreso (Q1 = más bajo)") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE, font_size = 13)
Tasa por 10.000 hab según cuartil de ingreso (Q1 = más bajo)
Delito Q1 · ingreso más bajo Q2 Q3 Q4 · ingreso más alto Razón Q4/Q1
Homicidios 1,22 0,68 0,56 0,29 0,24
Hurto de celulares 17,35 25,51 55,75 75,52 4,35
Hurto a personas 50,04 74,00 153,82 197,62 3,95
Lesiones 12,86 12,26 13,39 9,29 0,72
Violencia intrafamiliar 27,18 19,47 16,58 12,66 0,47
h_q1 <- tasas_grupo %>% filter(delito == "homicidios", grupo_ingreso == "Q1 · ingreso más bajo") %>% pull(tasa)
h_q4 <- tasas_grupo %>% filter(delito == "homicidios", grupo_ingreso == "Q4 · ingreso más alto") %>% pull(tasa)
v_q1 <- tasas_grupo %>% filter(delito == "violencia_intrafamiliar", grupo_ingreso == "Q1 · ingreso más bajo") %>% pull(tasa)
v_q4 <- tasas_grupo %>% filter(delito == "violencia_intrafamiliar", grupo_ingreso == "Q4 · ingreso más alto") %>% pull(tasa)
hp_q1 <- tasas_grupo %>% filter(delito == "hurto_personas", grupo_ingreso == "Q1 · ingreso más bajo") %>% pull(tasa)
hp_q4 <- tasas_grupo %>% filter(delito == "hurto_personas", grupo_ingreso == "Q4 · ingreso más alto") %>% pull(tasa)

El hurto a personas es el delito más denunciado en casi todas las UPZ, pero la mezcla cambia con el ingreso. En el cuartil de menor salario la tasa de homicidios es 4,2 veces la del cuartil más rico y la de violencia intrafamiliar 2,1 veces; el hurto a personas, en cambio, es 3,9 veces mayor en el cuartil más rico, y lo mismo ocurre con el hurto a comercio y de celulares. Las lesiones y los hurtos de motos y automotores se mantienen parejos en los tres primeros cuartiles y caen en el más rico. Aquí aparecen las dos inseguridades: una contra el patrimonio, en zonas de comercio y alto ingreso; otra contra la vida y el cuerpo, dentro y alrededor del hogar, en zonas pobres.

3.5 P4. Entorno: ollas, bares, lotes oscuros y alumbrado

delitos_p4 <- c(tasa_hurto_personas = "Hurto a personas", tasa_lesiones = "Lesiones",
                tasa_homicidios = "Homicidios", tasa_violencia_intrafamiliar = "Violencia intraf.")
entorno_p4 <- c(pct_cerca_ollas = "% cerca de ollas", pct_cerca_bares = "% cerca de bares",
                pct_cerca_lotes_oscuros = "% lotes oscuros", luminarias_km2 = "Luminarias por km²",
                pct_luminarias_led = "% luminarias LED", pct_pobreza = "% pobreza (referencia)")

corr_spearman <- function(datos) {
  cor(datos[names(entorno_p4)], datos[names(delitos_p4)], method = "spearman") %>%
    as_tibble(rownames = "entorno") %>%
    pivot_longer(-entorno, names_to = "delito", values_to = "rho")
}

corr_todas <- corr_spearman(upz_f)
corr_no_agr <- corr_spearman(filter(upz_f, !em_agrupada)) %>% rename(rho_no_agrupadas = rho)

corr_todas %>%
  mutate(entorno = factor(entorno_p4[entorno], levels = rev(entorno_p4)),
         delito = factor(delitos_p4[delito], levels = delitos_p4)) %>%
  ggplot(aes(x = delito, y = entorno, fill = rho)) +
  geom_tile(color = "white", linewidth = 1) +
  geom_text(aes(label = num(rho, 2), color = abs(rho) > 0.45), size = 3.8) +
  scale_fill_gradient2(low = turquesa, mid = "white", high = magenta, midpoint = 0,
                       limits = c(-1, 1), name = "Spearman") +
  scale_color_manual(values = c(`TRUE` = "white", `FALSE` = "grey20"), guide = "none") +
  scale_x_discrete(position = "top") +
  labs(title = "Las ollas acompañan a la violencia, no al hurto",
       subtitle = "Correlación de Spearman entre entorno y tasas de delito · 101 UPZ con 10.000 hab o más",
       x = NULL, y = NULL,
       caption = "Magenta = se mueven juntas; turquesa = en sentido opuesto. Fuente: EM 2021, SDSCJ 2023, Datos Abiertos (luminarias).") +
  theme(panel.grid = element_blank(), axis.text.x = element_text(face = "bold"))

corr_todas %>% left_join(corr_no_agr, by = c("entorno", "delito")) %>%
  filter(entorno %in% c("pct_cerca_ollas", "pct_cerca_lotes_oscuros", "pct_cerca_bares")) %>%
  mutate(Entorno = entorno_p4[entorno], Delito = delitos_p4[delito]) %>%
  transmute(Entorno, Delito, `Todas (101)` = num(rho, 2),
            `Solo no agrupadas` = num(rho_no_agrupadas, 2)) %>%
  kable(align = "llrr", caption = "Robustez: correlaciones con y sin las UPZ de encuesta agrupada") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE, font_size = 13)
Robustez: correlaciones con y sin las UPZ de encuesta agrupada
Entorno Delito Todas (101) Solo no agrupadas
% cerca de ollas Hurto a personas -0,41 -0,40
% cerca de ollas Lesiones 0,47 0,40
% cerca de ollas Homicidios 0,64 0,59
% cerca de ollas Violencia intraf. 0,66 0,59
% cerca de bares Hurto a personas 0,10 0,03
% cerca de bares Lesiones 0,47 0,41
% cerca de bares Homicidios 0,35 0,34
% cerca de bares Violencia intraf. 0,44 0,43
% lotes oscuros Hurto a personas -0,47 -0,52
% lotes oscuros Lesiones 0,15 0,05
% lotes oscuros Homicidios 0,38 0,34
% lotes oscuros Violencia intraf. 0,36 0,28
rho <- function(e, d) corr_todas %>% filter(entorno == e, delito == d) %>% pull(rho)
  • Vivir cerca de ollas va de la mano con más homicidios (ρ = 0,64) y más violencia intrafamiliar (ρ = 0,66), pero con menos hurto a personas (ρ = -0,41). El patrón es casi idéntico al de la pobreza (última fila): ollas, lotes oscuros y pobreza conviven en los mismos barrios, y con estos datos no podemos separar su efecto.
  • Los bares, a diferencia de las ollas, no se relacionan con el hurto (ρ = 0,10) pero sí con las lesiones (ρ = 0,47): riñas en zonas de rumba.
  • El alumbrado no cuenta la historia esperada: más luminarias por km² no va con menos delitos, sino con más, sobre todo lesiones (ρ = 0,46): las zonas centrales y comerciales están más iluminadas y tienen más movimiento nocturno. El % de LED es prácticamente irrelevante. Ojo: el inventario es de 2021 y mide luminarias, no calles bien iluminadas.
  • Las conclusiones se sostienen al quitar las UPZ con encuesta agrupada (tabla de robustez).

3.6 P5. ¿La percepción sigue a las denuncias?

rho_perc_total <- cor(upz_f$pct_percibe_inseguridad, upz_f$tasa_total, method = "spearman")
rho_perc_vif   <- cor(upz_f$pct_percibe_inseguridad, upz_f$tasa_violencia_intrafamiliar, method = "spearman")
rho_perc_hom   <- cor(upz_f$pct_percibe_inseguridad, upz_f$tasa_homicidios, method = "spearman")

panel <- function(var_y, titulo, rho_val, color_pts) {
  ggplot(upz_f, aes(x = pct_percibe_inseguridad, y = .data[[var_y]])) +
    geom_point(color = color_pts, alpha = 0.75, size = 2.2) +
    geom_smooth(method = "lm", se = FALSE, color = "grey30", linewidth = 0.8) +
    scale_y_log10(labels = label_comma(big.mark = ".", decimal.mark = ",")) +
    labs(subtitle = paste0(titulo, " · Spearman = ", num(rho_val, 2)),
         x = "% de hogares que perciben inseguridad", y = "Tasa por 10.000 hab (log)")
}

(panel("tasa_total", "Total de denuncias", rho_perc_total, gris) +
   panel("tasa_violencia_intrafamiliar", "Violencia intrafamiliar", rho_perc_vif, magenta)) +
  plot_annotation(
    title = "La gente se siente insegura donde hay violencia, no donde hay más denuncias",
    caption = "Percepción: EM 2021. Denuncias: SDSCJ ene–jul 2023. 101 UPZ con 10.000 hab o más.",
    theme = tema_clase)

La percepción de inseguridad no sigue al total de denuncias (ρ = -0,31, incluso negativa): las UPZ con más hurto por habitante, como Chicó Lago, tienen de las percepciones más bajas de la ciudad. En cambio, la percepción sí acompaña a la violencia intrafamiliar (ρ = 0,67) y a los homicidios (ρ = 0,58). Dos lecturas posibles, que los datos no permiten distinguir: (a) lo que la gente siente responde a la violencia y al deterioro del entorno, no al robo; (b) en las zonas centrales los hurtos los sufre la población flotante, que no es la que respondió la encuesta en su vivienda.

3.7 P6. ¿Qué tan lejos queda la policía?

upz_f <- upz_f %>%
  mutate(nivel_tasa = ntile(tasa_total, 4),
         nivel_tasa = factor(nivel_tasa, 1:4,
                             labels = c("Q1\ntasa más baja", "Q2", "Q3", "Q4\ntasa más alta")))

med_cai <- upz_f %>% group_by(nivel_tasa) %>% summarise(mediana = median(min_cai_policia))
rho_cai <- cor(upz_f$min_cai_policia, upz_f$tasa_total, method = "spearman")

ggplot(upz_f, aes(x = nivel_tasa, y = min_cai_policia)) +
  geom_boxplot(aes(fill = nivel_tasa == "Q4\ntasa más alta"), width = 0.55,
               outlier.shape = NA, alpha = 0.9, color = "grey30") +
  geom_jitter(width = 0.12, alpha = 0.5, size = 1.6, color = "grey25") +
  geom_text(data = med_cai, aes(y = mediana, label = paste0(num(mediana, 1), " min")),
            nudge_x = 0.42, size = 3.4, fontface = "bold", color = morado) +
  scale_fill_manual(values = c(`TRUE` = magenta, `FALSE` = gris), guide = "none") +
  scale_y_continuous(limits = c(0, NA)) +
  labs(title = "Las UPZ con más denuncias por habitante tienen el CAI más cerca, no más lejos",
       subtitle = paste0("Minutos caminando al CAI según cuartil de la tasa total · Spearman = ", num(rho_cai, 2)),
       x = "Cuartil de la tasa total de denuncias", y = "Minutos caminando al CAI",
       caption = "Fuente: EM 2021 (minutos), SDSCJ 2023 (tasas). 101 UPZ con 10.000 hab o más.")

En el cuartil con tasas más altas el CAI queda a una mediana de 15,6 minutos, contra 18,9 en el de tasas más bajas (ρ = -0,43). La lectura ingenua (“más CAI = más delito”) es absurda: lo más probable es la causalidad inversa (se ponen CAI donde hay más delito) y que en zonas centrales, compactas y con más servicios, todo queda más cerca. También es posible que tener la policía a mano facilite denunciar. Una vez más: correlación no es causalidad.

3.8 Ranking: las 10 UPZ con más denuncias por habitante

ranking <- upz_f %>%
  slice_max(tasa_total, n = 10) %>%
  mutate(posicion = row_number(),
         tipo = if_else(tasa_homicidios > quantile(upz_f$tasa_homicidios, 0.75),
                        "También en el 25 % con más homicidios", "Predomina el hurto"))
n_violentas <- sum(ranking$tipo != "Predomina el hurto")
palabras <- c("una", "dos", "tres", "cuatro", "cinco", "seis", "siete", "ocho", "nueve", "diez")

ggplot(ranking, aes(x = tasa_total, y = fct_reorder(upz_nombre, tasa_total))) +
  geom_col(aes(fill = tipo), width = 0.7) +
  geom_text(aes(label = num(tasa_total)), hjust = -0.15, size = 3.4) +
  geom_vline(xintercept = median(upz_f$tasa_total), linetype = "dashed", color = "grey40") +
  scale_fill_manual(values = c("Predomina el hurto" = gris, "También en el 25 % con más homicidios" = magenta),
                    name = NULL) +
  scale_x_continuous(expand = expansion(mult = c(0, 0.12)), labels = label_comma(big.mark = ".")) +
  labs(title = paste0("El top 10 es el centro expandido de Bogotá; ", palabras[n_violentas], " UPZ suman hurto y homicidios"),
       subtitle = paste0("Denuncias de los 10 delitos por 10.000 hab (ene–jul 2023) · línea punteada = mediana de Bogotá (",
                         num(median(upz_f$tasa_total)), ")"),
       x = "Denuncias por 10.000 hab", y = NULL,
       caption = "Magenta: además están en el cuartil superior de la tasa de homicidios (UPZ con 10.000 hab o más).") +
  theme(legend.position = "top", legend.justification = "left", panel.grid.major.y = element_blank())

ranking %>%
  transmute(`#` = posicion, UPZ = upz_nombre, Localidad = localidad,
            Habitantes = num(poblacion),
            `Tasa total` = num(tasa_total),
            `Hurto personas` = num(tasa_hurto_personas),
            Homicidios = num(tasa_homicidios, 2),
            `Violencia intraf.` = num(tasa_violencia_intrafamiliar, 1),
            `% percibe inseguridad` = pct(pct_percibe_inseguridad),
            `Min. al CAI` = num(min_cai_policia, 1)) %>%
  kable(align = "rllrrrrrrr", caption = "Top 10 UPZ por tasa total (por 10.000 hab, ene–jul 2023)") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = TRUE, font_size = 13) %>%
  row_spec(which(ranking$tipo == "También en el 25 % con más homicidios"), bold = TRUE, color = magenta)
Top 10 UPZ por tasa total (por 10.000 hab, ene–jul 2023)
# UPZ Localidad Habitantes Tasa total Hurto personas Homicidios Violencia intraf. % percibe inseguridad Min. al CAI
1 Las Nieves Santa Fe 17.203 2.344 1.488 5,23 32,5 60 % 10,1
2 Sagrado Corazon Santa Fe 11.247 1.518 851 1,78 232,1 60 % 10,1
3 La Sabana Los Mártires 38.856 1.400 852 6,18 84,4 83 % 16,0
4 Chico Lago Chapinero 38.608 1.291 842 0,26 15,3 21 % 13,1
5 Puente Aranda Puente Aranda 12.482 1.251 723 0,80 40,9 66 % 17,2
6 Chapinero Chapinero 26.491 1.095 700 0,75 18,1 50 % 15,6
7 Teusaquillo Teusaquillo 28.881 1.030 648 0,69 22,2 63 % 10,4
8 Galerias Teusaquillo 38.425 798 497 1,04 13,5 49 % 12,8
9 Los Alcazares Barrios Unidos 44.678 782 467 2,91 18,8 70 % 12,8
10 La Candelaria Candelaria 18.409 745 448 1,09 33,7 65 % 12,7

El ranking confirma la geografía de P1 y P2: las diez UPZ están en Santa Fe, Los Mártires, Chapinero, Teusaquillo, Puente Aranda, Barrios Unidos y La Candelaria, el centro expandido. Pero no son iguales: Las Nieves, Sagrado Corazon, La Sabana, Los Alcazares combinan hurto altísimo con homicidios entre los más altos de la ciudad (el 25 % superior), mientras Chicó Lago tiene muchísimo hurto y casi nada de violencia, y la percepción de inseguridad más baja del top.

3.9 Mapa interactivo para explorar

pal <- colorFactor(palette = c("#F1EAF4", "#D4BEDE", "#A57DB8", "#6E4290", morado),
                   levels = niveles, na.color = "#CCCCCC")

leaflet(mapa_upz) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addPolygons(
    fillColor = ~pal(q_violencia), fillOpacity = 0.85, color = "white", weight = 0.6,
    group = "Violencia",
    label = ~paste0(upz_nombre, " · ", num(poblacion), " hab · violencia: ",
                    num(tasa_violenta, 1), " /10.000 · hurto: ", num(tasa_hurto_personas), " /10.000"),
    highlightOptions = highlightOptions(weight = 2, color = magenta, bringToFront = TRUE)) %>%
  addPolygons(
    fillColor = ~pal(q_hurto), fillOpacity = 0.85, color = "white", weight = 0.6,
    group = "Hurto a personas",
    label = ~paste0(upz_nombre, " · ", num(poblacion), " hab · hurto: ",
                    num(tasa_hurto_personas), " /10.000 · violencia: ", num(tasa_violenta, 1), " /10.000"),
    highlightOptions = highlightOptions(weight = 2, color = magenta, bringToFront = TRUE)) %>%
  addLayersControl(baseGroups = c("Hurto a personas", "Violencia"),
                   options = layersControlOptions(collapsed = FALSE)) %>%
  addLegend(pal = pal, values = niveles, position = "bottomright",
            title = "Quintil de la tasa<br>(gris: < 10.000 hab)")

4 Hallazgos e historia

1. Primero, el denominador (P1). Contar denuncias es contar gente: las UPZ más pobladas del sur y occidente tienen más casos simplemente porque allí vive más gente. Al dividir por habitantes aparece otra ciudad, y aparece también una trampa: 11 UPZ casi deshabitadas tienen tasas absurdas. Nuestro propio EDA preliminar cayó en ella y “descubrió” que Ciudad Bolívar era la localidad con más hurto. Filtrar esas UPZ no es un detalle técnico: cambia las conclusiones.

2. El hurto vive donde está la riqueza y el movimiento (P2). Una vez corregido, el hurto a personas por habitante se concentra en el centro expandido y el norte, y crece con el salario de la UPZ (ρ = 0,71). Es un delito de oportunidad: donde hay comercio, oficinas, celulares y gente de paso.

3. La violencia vive en otra parte (P3). Homicidios, lesiones y violencia intrafamiliar dibujan otro mapa: el centro deteriorado (Las Nieves, La Sabana, Las Cruces) y el sur. En el cuartil más pobre la tasa de homicidios es 4,2 veces la del más rico. Son dos inseguridades, con víctimas, lugares y probablemente causas distintas.

4. El entorno acompaña a la violencia, no al robo (P4). Ollas y lotes oscuros se asocian con homicidios y violencia intrafamiliar, no con el hurto. Pero esas condiciones van juntas con la pobreza, así que no podemos decir cuál pesa. El alumbrado, contra lo esperado, no muestra relación protectora.

5. La gente siente la segunda inseguridad (P5). La percepción no sigue al total de denuncias (que domina el hurto), sino a la violencia. Los hogares del sur se sienten inseguros aunque se denuncien menos robos por habitante; los del norte, con más hurto, se sienten más seguros.

6. La policía está más cerca de donde más se denuncia (P6). Las UPZ con tasas más altas tienen el CAI más cerca. Más que un fracaso de la policía, sugiere que los CAI se ubican donde hay más delito y que la cercanía facilita denunciar.

La historia en una frase: en Bogotá el robo se concentra donde hay riqueza y movimiento, y la violencia donde hay pobreza y deterioro; la gente teme la segunda, y la cifra total de denuncias, dominada por el hurto, la esconde. Para la política pública esto importa: un solo indicador (“delitos por habitante”) pondría el foco en Chapinero y Teusaquillo y dejaría en segundo plano los barrios donde hay más homicidios y violencia en el hogar.

5 Limitaciones y conclusión

Lo que los datos muestran: patrones descriptivos claros y consistentes entre fuentes independientes (denuncias, encuesta y censo): la separación hurto/violencia aparece en el mapa, en los grupos de ingreso, en el entorno y en la percepción.

Lo que no muestran:

  • Denuncias no son delitos. El subregistro probablemente es mayor en zonas pobres y para violencia intrafamiliar; parte de la diferencia norte–sur en hurto puede ser diferencia en la propensión a denunciar.
  • Tasas por residente, no por exposición. En el centro el denominador correcto sería la población flotante, que no tenemos. Las tasas del centro están infladas.
  • Siete meses (ene–jul 2023), sin delitos sexuales (no publicados). No se pueden comparar con cifras anuales de otras fuentes.
  • Fuentes de años distintos: encuesta y luminarias de 2021, delitos y población de 2023.
  • Encuesta muestral y agrupada: en 32 UPZ los valores se comparten con UPZ vecinas. Las correlaciones se sostienen sin ellas, pero son estimaciones con error.
  • Correlación no es causalidad. Ollas, pobreza, lotes oscuros y violencia se mueven juntas; los datos no permiten separar sus efectos. Un análisis multivariado (por ejemplo el PCA del próximo taller) ayudará a ver si todas cargan sobre un mismo eje de “vulnerabilidad”.
  • Umbral de 10.000 habitantes: es una decisión nuestra. Con otro umbral cambian algunos puestos del ranking, pero no los patrones generales.

Conclusión. Preguntar “¿dónde es más inseguro Bogotá?” no tiene una sola respuesta. Depende de si contamos casos o tasas, robos o violencia, denuncias o percepción. La lección del EDA es tan metodológica como sustantiva: mirar el denominador antes de confiar en una tasa, y separar los tipos de delito antes de sumar, cambia la historia que cuentan los datos.


Datos: Secretaría Distrital de Seguridad, Convivencia y Justicia (delitos ene–jul 2023); DANE (proyecciones de población 2023 y Encuesta Multipropósito 2021); Datos Abiertos Bogotá (luminarias 2021). Informe reproducible: tejer con rmarkdown::render("Taller_Insights_UPZ.Rmd") desde la carpeta del taller.