Taller de Insights · EDA y storytelling · Visualización de Datos

Dos Bogotás inseguras:
el hurto sigue al dinero, la violencia se queda en casa

Lo que dicen las 112 UPZ de Bogotá sobre la inseguridad, su territorio y las condiciones de vida de quienes las habitan.
112
UPZ analizadas
(101 en el análisis de tasas)
7,9 M
habitantes
(proyección DANE 2023)
163 mil
denuncias de alto impacto
enero–julio 2023
84 %
de las denuncias
son hurtos
16 %
son violencia contra personas
(homicidio, lesiones, VIF)
Katerin Pinzón · María Fernanda Gutiérrez · Larry Santiago Chaves

1. Introducción y preguntas

Bogotá suele hablar de “inseguridad” como si fuera una sola cosa. Este informe parte de una sospecha: detrás de la palabra hay al menos dos fenómenos distintos, que ocurren en lugares distintos, a personas distintas y por razones distintas. Por un lado, el hurto, que es el delito que más se denuncia. Por otro, la violencia contra las personas (homicidios, lesiones y violencia intrafamiliar), que se denuncia mucho menos y se vive de otra manera.

Para comprobarlo, se cruzaron los delitos con el territorio, el nivel socioeconómico, el entorno (ollas, lotes oscuros, alumbrado), la percepción de inseguridad y la cercanía a la policía.

¿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?
1¿Dónde se concentran los hurtos y dónde la violencia intrafamiliar?Territorio · mapa
2¿Cambia el tipo de delito predominante según el salario o el estrato de la zona?Nivel socioeconómico
3¿Se relacionan el alumbrado, los lotes oscuros, las ollas y los bares con ciertos delitos?Entorno de riesgo
4¿La gente se siente insegura donde de verdad hay más denuncias?Percepción
5¿Qué tan lejos queda el CAI en las UPZ con tasas más altas?Policía
6¿Qué UPZ cargan con más violencia intrafamiliar y qué tienen en común?Ranking

Variables elegidas y por qué

No se usaron las 67 columnas. Se eligieron 18 variables de 4 bloques, una por cada pieza de la historia, con el fin de construir dos índices a partir de ellas.

(*) Variable construida a partir de las del bloque de delitos
Variable Bloque ¿Por qué esta y no otra?
poblacion Demografía Denominador de todas las tasas: hay que revisarla antes de confiar en cualquier tasa.
luminarias_km2 Vivienda e infraestructura Alumbrado por superficie: ¿más luz en la calle, menos delito?
luminarias_1000hab Vivienda e infraestructura Alumbrado por habitante: sirve para mostrar cómo engaña un denominador mal elegido.
pct_luminarias_led Vivienda e infraestructura Calidad del alumbrado: ¿las luminarias LED se asocian con menos delito? (P3).
tasa_hurto_personas Delitos El delito más denunciado de la ciudad.
tasa_hurto_residencias Delitos Un hurto que ocurre donde la gente vive, no donde transita.
tasa_homicidios Delitos La violencia más grave.
tasa_lesiones Delitos Violencia física en la calle.
tasa_violencia_intrafamiliar Delitos Violencia dentro del hogar: la menos distorsionada por la población flotante.
tasa_total Delitos Visión agregada; es la que se usa para hacer los cuartiles de la P5.
salario_promedio Encuesta Multipropósito Nivel socioeconómico (P2).
pct_estrato_1_2 Encuesta Multipropósito Nivel socioeconómico desde la vivienda (P2).
pct_pobreza Encuesta Multipropósito Privación (P2, P6).
pct_cerca_ollas Encuesta Multipropósito Entorno de riesgo: expendios de droga (P3).
pct_cerca_lotes_oscuros Encuesta Multipropósito Entorno de riesgo: lotes baldíos o sitios oscuros (P3).
pct_cerca_bares Encuesta Multipropósito Entorno de riesgo: bares y prostíbulos (P3).
pct_percibe_inseguridad Encuesta Multipropósito Percepción, no conteo: ¿coincide con las denuncias? (P4).
min_cai_policia Encuesta Multipropósito Minutos caminando hasta el CAI (P5).
tasa_hurtos * Delitos Índice construido: los 7 tipos de hurto por cada 10.000 habitantes.
tasa_violencia * Delitos Índice construido: homicidios + lesiones + VIF por cada 10.000 habitantes.

Se dejaron por fuera, a propósito, las variables de transporte y servicios (min_transmilenio, min_parque, etc.), las de edad y vivienda: son interesantes, pero se consideró que no son relevantes para poder responder las preguntas.

2. Los datos y su exploración

2.1 Estructura, tipos y faltantes

Este es el código que se utilizó para cargar los datos y construir las variables derivadas (se ejecuta al inicio del informe; aquí se muestra completo):

# Si falta alguna: install.packages(c("tidyverse","sf","leaflet","scales",
#                                     "patchwork","ggrepel","kableExtra","DT"))
library(tidyverse)   # dplyr + ggplot2 + readr + tidyr
library(sf)          # datos espaciales (.shp)
library(leaflet)     # mapa interactivo
library(scales)      # formato de números
library(patchwork)   # combinar gráficos
library(ggrepel)     # etiquetas que no se pisan
library(kableExtra)  # tablas bonitas
library(DT)          # tabla interactiva
# ---- 1. Cargar (rutas RELATIVAS: el .Rmd vive junto a la carpeta input/) ---
upz_raw <- read_csv("input/upz_variables.csv", show_col_types = FALSE)
upz_shp <- st_read("input/shp/upz_bogota.shp", quiet = TRUE)
loc_shp <- st_read("input/shp/localidades_bogota.shp", quiet = TRUE)

# ---- 2. Variables derivadas: dos familias de delito ------------------------
# Violencia contra las personas = homicidios + lesiones + violencia intrafamiliar
# Hurtos (patrimonio)           = los 7 tipos de hurto
umbral <- params$umbral_pob

upz <- upz_raw %>%
  mutate(
    casos_violencia = casos_homicidios + casos_lesiones + casos_violencia_intrafamiliar,
    casos_hurtos    = casos_total - casos_violencia,
    tasa_violencia  = casos_violencia / poblacion * 1e4,
    tasa_hurtos     = casos_hurtos    / poblacion * 1e4,
    pct_violencia   = casos_violencia / casos_total * 100,
    poblacion_baja  = poblacion < umbral
  )

# Base para TASAS: sin las UPZ de población muy baja
upz_t <- upz %>%
  filter(!poblacion_baja) %>%
  mutate(
    cuartil_salario = factor(ntile(salario_promedio, 4), 1:4,
                             c("Q1 · más bajo", "Q2", "Q3", "Q4 · más alto")),
    cuartil_total   = factor(ntile(tasa_total, 4), 1:4,
                             c("Q1 · tasa más baja", "Q2", "Q3", "Q4 · tasa más alta"))
  )

# ---- 3. Unir con el mapa ----------------------------------------------------
mapa <- upz_shp %>% left_join(upz %>% select(-upz_nombre), by = "upz_cod")

# ---- 4. Cifras clave que se usan en el texto -------------------------------
k <- list(
  n_upz      = nrow(upz),
  pob        = sum(upz$poblacion),
  casos      = sum(upz$casos_total),
  pct_hurtos = sum(upz$casos_hurtos) / sum(upz$casos_total) * 100,
  n_baja     = sum(upz$poblacion_baja),
  n_t        = nrow(upz_t),
  pob_baja   = sum(upz$poblacion[upz$poblacion_baja]),
  casos_baja = sum(upz$casos_total[upz$poblacion_baja])
)

Tamaño de la tabla

El primer número es la cantidad de filas (UPZ) y el segundo, la cantidad de columnas (variables).

dim(upz_raw)                                   # filas x columnas
## [1] 112  67

Tipos de dato

Cuántas columnas hay de cada tipo: character son textos (nombres de UPZ y localidad), logical es verdadero/falso (em_agrupada) y numeric son números.

table(sapply(upz_raw, class))                  # tipos de dato
## 
## character   logical   numeric 
##         2         1        64

Valores faltantes

Cantidad total de celdas vacías (NA) en la tabla. Un 0 significa que no falta ningún dato.

sum(is.na(upz_raw))                            # faltantes en toda la tabla
## [1] 0

¿Una fila por UPZ?

TRUE significa que ningún código de UPZ está repetido: cada fila es una UPZ distinta.

n_distinct(upz_raw$upz_cod) == nrow(upz_raw)   # ¿una fila por UPZ?
## [1] TRUE

¿Todas las UPZ cruzan con el mapa?

TRUE significa que todos los códigos de la tabla existen en el shapefile, así que ninguna UPZ se pierde al dibujar los mapas.

all(upz_raw$upz_cod %in% upz_shp$upz_cod)      # ¿todas cruzan con el mapa?
## [1] TRUE

La tabla tiene 112 filas y 67 columnas, una fila por UPZ, y ningún valor faltante. Todas las UPZ cruzan con el shapefile por upz_cod. Las columnas se agrupan en cinco bloques. Abajo aparecen pintadas por bloque, con las elegidas resaltadas y las demás atenuadas:

Territorio (5)  upz_codupz_nombrelocalidad_codlocalidadarea_km2
Demografía (6)  poblaciondensidad_hab_km2pct_mujerespct_0_14pct_15_29pct_60_mas
Vivienda e infraestructura (7)  viviendas_totalesviviendas_ocupadaspersonas_por_viviendaluminariasluminarias_km2luminarias_1000habpct_luminarias_led
Delitos (22)  casos_homicidioscasos_lesionescasos_hurto_personascasos_hurto_residenciascasos_hurto_automotorcasos_hurto_bicicletascasos_hurto_comerciocasos_hurto_celularescasos_hurto_motoscasos_violencia_intrafamiliarcasos_totaltasa_homicidiostasa_lesionestasa_hurto_personastasa_hurto_residenciastasa_hurto_automotortasa_hurto_bicicletastasa_hurto_comerciotasa_hurto_celularestasa_hurto_motostasa_violencia_intrafamiliartasa_total
Encuesta Multipropósito (27)  pct_viviendas_desocupadasem_agrupadapct_pobrezaedad_promediosalario_promediopct_estrato_1_2pct_estrato_3_4pct_estrato_5_6pct_vivienda_propiapct_arriendoanios_educacion_15maspct_padre_universitariopct_madre_universitariapct_percibe_inseguridadpct_cerca_ollaspct_cerca_barespct_cerca_lotes_oscurospct_cerca_basurerospct_cerca_terminalespct_cerca_fabricasmin_transmileniomin_sitpmin_parquemin_cai_policiamin_centro_medicomin_culturamin_ciclorruta

2.2 Antes de confiar en una tasa: la población

Cada tasa es casos / población × 10.000. Si la población es muy pequeña, unas pocas denuncias bastan para disparar la tasa. Además, muchas de esas denuncias son de gente que pasa por la UPZ y no de quien vive en ella.

upz %>% arrange(poblacion) %>%
  select(upz_nombre, localidad, poblacion, casos_total, tasa_total) %>%
  head(13) %>%
  kbl(format.args = list(big.mark = ".", decimal.mark = ","), digits = 0,
      col.names = c("UPZ", "Localidad", "Población", "Denuncias", "Tasa total /10.000"),
      caption = "Las 13 UPZ menos pobladas") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE) %>%
  row_spec(which(sort(upz$poblacion)[1:13] < umbral), color = vino, bold = TRUE)
Las 13 UPZ menos pobladas
UPZ Localidad Población Denuncias Tasa total /10.000
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 1.573 2.360
Sagrado Corazon Santa Fe 11.247 1.707 1.518
Alamos Engativá 12.077 642 532
p_pob1 <- ggplot(upz, aes(poblacion, fct_reorder(upz_nombre, poblacion))) +
  geom_vline(xintercept = umbral, colour = naranja, linetype = 2) +
  geom_point(aes(colour = poblacion_baja), size = 1.6) +
  scale_x_log10(labels = label_number(big.mark = ".", decimal.mark = ",")) +
  scale_colour_manual(values = c(`FALSE` = gris, `TRUE` = vino), guide = "none") +
  labs(title = "Población por UPZ", subtitle = "escala logarítmica · una UPZ por punto",
       x = "Habitantes (log)", y = NULL) +
  theme(axis.text.y = element_blank(), panel.grid.major.y = element_blank())

p_pob2 <- ggplot(upz, aes(poblacion, tasa_total)) +
  geom_vline(xintercept = umbral, colour = naranja, linetype = 2) +
  geom_point(aes(colour = poblacion_baja), size = 2.4, alpha = .85) +
  geom_text_repel(data = filter(upz, tasa_total > 1300 | poblacion < 1000),
                  aes(label = upz_nombre), size = 3.2, colour = tinta, seed = 1) +
  scale_x_log10(labels = label_number(big.mark = ".", decimal.mark = ",")) +
  scale_y_continuous(labels = label_number(big.mark = ".", decimal.mark = ",")) +
  scale_colour_manual(values = c(`FALSE` = petroleo, `TRUE` = vino), guide = "none") +
  labs(title = "Tasa total vs. población", subtitle = "a menor población, tasas absurdas",
       x = "Habitantes (log)", y = "Denuncias por 10.000 hab")

guardar(
  (p_pob1 | p_pob2) + plot_layout(widths = c(1, 1.6)) +
    plot_annotation(
      title = "Por debajo de 10.000 habitantes la tasa deja de medir riesgo",
      subtitle = paste0("En El Mochuelo (30 hab.) la tasa supera 11.000 por 10.000: más denuncias que habitantes.\n",
                        "La línea naranja marca el umbral de ", fmt(umbral), " hab."),
      caption = fuente, theme = tema_info),
  "01_poblacion_vs_tasa")

Se excluyeron de los análisis de tasas las 11 UPZ con menos de 10.000 habitantes (parques, el aeropuerto, zonas industriales y rurales). Se eligió ese umbral porque en los datos hay un salto natural: la UPZ más poblada por debajo tiene 6.666 habitantes, y la siguiente ya tiene 11.247. Esas UPZ suman apenas el 0,4 % de la población, pero el 2,4 % de las denuncias. No se borraron: siguen en el mapa (en gris) y en los conteos. El umbral es un parámetro (params$umbral_pob) y se puede cambiar para volver a tejer el informe.

Por encima del umbral persiste un problema más sutil: la población flotante. Las Nieves, Sagrado Corazón y La Candelaria (el centro) o Chicó Lago (Chapinero) reciben a diario a cientos de miles de personas que no viven allí. Su tasa por residente exagera el riesgo de quien las habita. Se conservan, pero se leen con cuidado. Por eso, para el ranking, se prefirió la violencia intrafamiliar, que ocurre en el hogar.

2.3 Distribuciones

vars_num <- c("tasa_hurto_personas", "tasa_hurto_residencias", "tasa_homicidios",
              "tasa_lesiones", "tasa_violencia_intrafamiliar", "salario_promedio",
              "pct_estrato_1_2", "pct_pobreza", "pct_percibe_inseguridad",
              "pct_cerca_ollas", "pct_cerca_lotes_oscuros", "pct_cerca_bares",
              "min_cai_policia", "luminarias_km2", "luminarias_1000hab", "pct_luminarias_led")

largo <- upz_t %>% select(all_of(vars_num)) %>%
  pivot_longer(everything(), names_to = "variable") %>%
  mutate(variable = factor(variable, vars_num),
         familia = case_when(str_detect(variable, "hurto") ~ "hurto",
                             str_detect(variable, "^tasa") ~ "violencia",
                             TRUE ~ "contexto"))
medianas <- largo %>% group_by(variable) %>% summarise(med = median(value))

guardar(
  ggplot(largo, aes(value, fill = familia)) +
    geom_histogram(bins = 22, colour = crema, linewidth = .2) +
    geom_vline(data = medianas, aes(xintercept = med), colour = naranja, linewidth = .8) +
    facet_wrap(~variable, scales = "free", ncol = 4) +
    scale_fill_manual(values = c(hurto = petroleo, violencia = vino, contexto = gris), guide = "none") +
    scale_x_continuous(labels = label_number(scale_cut = cut_short_scale(), decimal.mark = ",")) +
    labs(title = "Casi todas las tasas de delito tienen una cola larga a la derecha",
         subtitle = paste0("Distribución en las ", k$n_t, " UPZ analizadas · línea naranja = mediana · ",
                           "petroleo = hurtos, vino = violencia, gris = contexto"),
         x = NULL, y = "Nº de UPZ", caption = fuente) +
    theme(strip.text = element_text(size = 9.5), axis.text = element_text(size = 8)),
  "02_distribuciones", h = 8.5)

Las tasas de hurto, sobre todo, son muy asimétricas: unas pocas UPZ concentran valores muy altos. Por eso, a lo largo del informe:

  • se resume con la mediana y los cuartiles, no solo con la media;
  • la asociación se mide con la correlación de Spearman (ρ), que trabaja con rangos y no se deja arrastrar por los extremos.

2.4 Tabla resumen de las variables elegidas

resumen <- upz_t %>%
  select(all_of(vars_num), tasa_hurtos, tasa_violencia) %>%
  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),
            `CV %` = sd(value) / mean(value) * 100) %>%
  mutate(Variable = factor(Variable, c(vars_num, "tasa_hurtos", "tasa_violencia"))) %>%
  arrange(Variable)

write_csv(resumen, "output/tablas/tabla_resumen.csv")

resumen %>%
  # el salario se muestra en miles de pesos para que la tabla quepa (el CV no cambia)
  mutate(across(c(Media:`Máx`), ~ if_else(Variable == "salario_promedio", .x / 1000, .x)),
         Variable = if_else(Variable == "salario_promedio", "salario_promedio (miles $)", as.character(Variable))) %>%
  kbl(digits = 1, format.args = list(big.mark = ".", decimal.mark = ","),
      caption = paste0("Estadística descriptiva · ", k$n_t, " UPZ con ≥ ", fmt(umbral),
                       " habitantes · CV = coeficiente de variación")) %>%
  kable_styling(bootstrap_options = c("striped", "condensed", "hover"), full_width = TRUE, font_size = 13) %>%
  column_spec(1, monospace = TRUE) %>%
  column_spec(9, bold = TRUE, color = ifelse(resumen$`CV %` > 75, vino, tinta)) %>%
  scroll_box(width = "100%")
Estadística descriptiva · 101 UPZ con ≥ 10.000 habitantes · CV = coeficiente de variación
Variable Media Mediana Desv. est. Q1 Q3 Mín Máx CV %
tasa_hurto_personas 167,7 96,0 223,9 54,3 159,7 15,7 1.487,5 133,5
tasa_hurto_residencias 6,5 6,0 4,0 3,5 8,4 0,9 26,3 61,7
tasa_homicidios 0,9 0,6 1,1 0,2 1,1 0,0 6,2 126,2
tasa_lesiones 15,3 11,8 14,5 8,2 15,9 4,2 98,5 94,5
tasa_violencia_intrafamiliar 21,7 17,6 24,1 12,5 23,3 5,4 232,1 110,9
salario_promedio (miles $) 2.553,0 1.826,6 1.768,6 1.229,0 3.574,6 845,7 7.977,5 69,3
pct_estrato_1_2 41,6 23,7 42,9 0,6 96,8 0,0 100,0 102,9
pct_pobreza 4,0 2,7 3,8 1,0 5,8 0,1 16,6 96,0
pct_percibe_inseguridad 61,3 66,0 18,7 48,5 74,3 17,4 92,1 30,5
pct_cerca_ollas 21,5 20,9 15,4 10,1 30,5 0,3 75,6 71,7
pct_cerca_lotes_oscuros 19,3 17,9 12,0 11,3 24,9 0,6 59,8 62,1
pct_cerca_bares 14,5 13,0 10,7 5,8 19,5 0,6 53,9 73,7
min_cai_policia 18,1 17,5 4,4 15,1 20,5 10,1 35,1 24,3
luminarias_km2 982,4 1.046,7 346,0 727,2 1.192,0 116,3 1.751,4 35,2
luminarias_1000hab 54,6 46,6 32,7 36,8 59,4 14,1 182,6 59,8
pct_luminarias_led 43,9 50,4 32,1 9,9 74,4 0,0 95,8 73,1
tasa_hurtos 261,0 150,4 332,3 93,0 251,2 30,3 2.235,1 127,3
tasa_violencia 38,0 32,2 35,0 21,8 38,4 10,7 299,6 92,1

Cómo leer la tabla: las variables con un coeficiente de variación (CV) alto, en vino, son las que más diferencian a unas UPZ de otras. Las tasas de hurto y de homicidio varían muchísimo más que la percepción de inseguridad o los minutos al CAI. Por ejemplo, la mediana de hurto a personas es de 96 por cada 10.000 habitantes, mientras que la media sube a 168, arrastrada por unas pocas UPZ.

2.5 Correlaciones entre las variables elegidas

vars_cor <- c("tasa_hurtos", "tasa_hurto_personas", "tasa_hurto_residencias",
              "tasa_violencia", "tasa_violencia_intrafamiliar", "tasa_lesiones", "tasa_homicidios",
              "pct_percibe_inseguridad", "salario_promedio", "pct_estrato_1_2", "pct_pobreza",
              "pct_cerca_ollas", "pct_cerca_lotes_oscuros", "pct_cerca_bares",
              "luminarias_km2", "luminarias_1000hab", "min_cai_policia")

M <- cor(upz_t[vars_cor], method = "spearman")
write_csv(as_tibble(M, rownames = "variable"), "output/tablas/correlaciones_spearman.csv")

M_largo <- as_tibble(M, rownames = "x") %>%
  pivot_longer(-x, names_to = "y", values_to = "rho") %>%
  mutate(x = factor(x, vars_cor), y = factor(y, rev(vars_cor))) %>%
  filter(as.integer(x) < (length(vars_cor) + 1 - as.integer(y)))   # triángulo inferior

guardar(
  ggplot(M_largo, aes(x, y, fill = rho)) +
    geom_tile(colour = crema, linewidth = .6) +
    geom_text(aes(label = sub("^(-?)0\\.", "\\1.", sprintf("%.2f", rho)),
                  colour = abs(rho) > .5), size = 2.9) +
    scale_fill_gradient2(low = petroleo, mid = medio, high = vino, midpoint = 0,
                         limits = c(-1, 1), name = "ρ de Spearman") +
    scale_colour_manual(values = c(`TRUE` = "white", `FALSE` = tinta), guide = "none") +
    scale_x_discrete(position = "top") +
    labs(title = "Hurtos y violencia se mueven en direcciones opuestas",
         subtitle = "Vino = correlación positiva · azul = negativa (Spearman).\nLos hurtos suben con el salario; la violencia, la percepción y las ollas suben con la pobreza.",
         x = NULL, y = NULL, caption = fuente) +
    theme(plot.margin = margin(12, 70, 10, 12),
          axis.text.x = element_text(angle = 40, hjust = 0, size = 9),
          axis.text.y = element_text(size = 9), panel.grid = element_blank(),
          legend.key.width = unit(1.6, "cm")),
  "03_matriz_correlaciones", h = 8)

La matriz ya adelanta la historia. La tasa de hurtos y la de violencia casi no se parecen entre sí (ρ = 0,02), y cada una tiene “amigos” distintos. Los hurtos se asocian con el salario (ρ = 0,70). La violencia, con la pobreza (ρ = 0,63), las ollas (ρ = 0,61) y la percepción de inseguridad (ρ = 0,60).

2.6 ¿Pesa que la encuesta esté agrupada?

En 32 UPZ la Encuesta Multipropósito no tiene un valor propio: comparten el de su grupo de vecinas. Eso podría inflar o desinflar las correlaciones. Para comprobarlo, se repitieron las principales solo con las UPZ que tienen un valor propio.

sens <- expand_grid(encuesta = c("salario_promedio", "pct_pobreza", "pct_cerca_ollas",
                                 "pct_percibe_inseguridad", "min_cai_policia"),
                    delito = c("tasa_hurtos", "tasa_violencia")) %>%
  rowwise() %>%
  mutate(`ρ todas` = cor(upz_t[[encuesta]], upz_t[[delito]], method = "spearman"),
         `ρ sin agrupadas` = with(filter(upz_t, !em_agrupada),
                                  cor(get(encuesta), get(delito), method = "spearman"))) %>%
  ungroup() %>% mutate(Diferencia = `ρ sin agrupadas` - `ρ todas`)

sens %>% kbl(digits = 2, caption = paste0("Todas = ", k$n_t, " UPZ · sin agrupadas = ",
                                          sum(!upz_t$em_agrupada), " UPZ")) %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE)
Todas = 101 UPZ · sin agrupadas = 80 UPZ
encuesta delito ρ todas ρ sin agrupadas Diferencia
salario_promedio tasa_hurtos 0.70 0.69 -0.01
salario_promedio tasa_violencia -0.53 -0.54 -0.01
pct_pobreza tasa_hurtos -0.62 -0.58 0.03
pct_pobreza tasa_violencia 0.63 0.65 0.01
pct_cerca_ollas tasa_hurtos -0.41 -0.39 0.02
pct_cerca_ollas tasa_violencia 0.61 0.53 -0.08
pct_percibe_inseguridad tasa_hurtos -0.39 -0.33 0.06
pct_percibe_inseguridad tasa_violencia 0.60 0.62 0.01
min_cai_policia tasa_hurtos -0.43 -0.43 0.00
min_cai_policia tasa_violencia -0.24 -0.33 -0.09

Los signos no cambian y las magnitudes se mueven poco (máximo 0,09). Las conclusiones no dependen de las UPZ agrupadas, así que se mantienen.

3. Análisis por pregunta

P1 · ¿Dónde se concentran los hurtos y dónde la violencia intrafamiliar?

¿Dónde se concentran los hurtos y dónde la violencia intrafamiliar?
mapa_q <- mapa %>%
  mutate(q_hurtos = if_else(poblacion_baja, NA_integer_, ntile(if_else(poblacion_baja, NA_real_, tasa_hurtos), 5)),
         q_vif    = if_else(poblacion_baja, NA_integer_, ntile(if_else(poblacion_baja, NA_real_, tasa_violencia_intrafamiliar), 5)))

etiq_q <- c("1" = "Q1 · bajo", "2" = "Q2", "3" = "Q3", "4" = "Q4", "5" = "Q5 · alto")

un_mapa <- function(var, alto, titulo) {
  ggplot(mapa_q) +
    geom_sf(aes(fill = factor(.data[[var]])), colour = crema, linewidth = .12) +
    geom_sf(data = loc_shp, fill = NA, colour = tinta, linewidth = .3) +
    scale_fill_manual(values = colorRampPalette(c(claro, alto))(5), labels = etiq_q,
                      na.value = "#BDB6AC", na.translate = TRUE,
                      name = NULL, breaks = as.character(1:5)) +
    labs(title = titulo) +
    theme_void(base_size = 12) +
    theme(plot.title = element_text(family = "serif", face = "bold", size = 14, colour = alto),
          legend.position = "bottom", legend.text = element_text(size = 9),
          plot.background = element_rect(fill = crema, colour = NA)) +
    guides(fill = guide_legend(nrow = 1, label.position = "bottom", keywidth = unit(1.2, "cm")))
}

guardar(
  (un_mapa("q_hurtos", petroleo, "Hurtos por 10.000 hab") |
     un_mapa("q_vif", vino, "Violencia intrafamiliar por 10.000 hab")) +
    plot_annotation(
      title = "El hurto se concentra en el centro y el nororiente;\nla violencia en casa, en el sur y el suroccidente",
      subtitle = paste0("Quintiles de la tasa por UPZ · gris = UPZ con menos de ", fmt(umbral),
                        " hab. (excluidas) · líneas = localidades"),
      caption = fuente, theme = tema_info),
  "04_mapa_hurtos_vs_vif", h = 7.2)

En el mapa interactivo, al pasar el cursor sobre una UPZ se ven sus cifras, y en la esquina superior derecha se puede cambiar de capa.

# Se simplifican los polígonos (tolerancia 15 m) para que el HTML no pese demasiado
simple  <- function(x) st_simplify(x, preserveTopology = TRUE, dTolerance = 15)
m_ok    <- mapa %>% filter(!poblacion_baja) %>% simple()
m_baja  <- mapa %>% filter(poblacion_baja) %>% simple()
loc_lf  <- simple(loc_shp)
pal_h <- colorQuantile(c(claro, petroleo), m_ok$tasa_hurtos, n = 5)
pal_v <- colorQuantile(c(claro, vino), m_ok$tasa_violencia_intrafamiliar, n = 5)
pal_p <- colorNumeric(c(claro, naranja, coral), m_ok$pct_percibe_inseguridad)

etiqueta <- sprintf(
  "<b>%s</b> · %s<br>%s hab.<br>Hurtos: <b>%s</b> /10.000<br>Violencia intrafamiliar: <b>%s</b> /10.000<br>Percibe inseguridad: <b>%s %%</b><br>CAI a %s min caminando",
  m_ok$upz_nombre, m_ok$localidad, fmt(m_ok$poblacion), fmt(m_ok$tasa_hurtos),
  fmt(m_ok$tasa_violencia_intrafamiliar, 1), fmt(m_ok$pct_percibe_inseguridad),
  fmt(m_ok$min_cai_policia)) %>% lapply(htmltools::HTML)

hl <- highlightOptions(weight = 2.5, color = naranja, bringToFront = TRUE)

leaflet(m_ok, width = "100%", height = 620) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addPolygons(data = m_baja, fillColor = "#BDB6AC", fillOpacity = .6,
              color = "white", weight = .5, label = ~paste0(upz_nombre, " (excluida: ", fmt(poblacion), " hab.)")) %>%
  addPolygons(fillColor = ~pal_h(tasa_hurtos), fillOpacity = .85, color = "white", weight = .5,
              label = etiqueta, highlightOptions = hl) %>%
  addPolygons(fillColor = ~pal_v(tasa_violencia_intrafamiliar), fillOpacity = .85, color = "white",
              weight = .5, label = etiqueta, highlightOptions = hl, group = "Violencia intrafamiliar") %>%
  addPolygons(fillColor = ~pal_p(pct_percibe_inseguridad), fillOpacity = .85, color = "white",
              weight = .5, label = etiqueta, highlightOptions = hl, group = "Percepción de inseguridad") %>%
  addPolylines(data = loc_lf, color = tinta, weight = 1.2, opacity = .7) %>%
  addLayersControl(baseGroups = c("Hurtos", "Violencia intrafamiliar", "Percepción de inseguridad"),
                   options = layersControlOptions(collapsed = FALSE)) %>%
  addLegend("bottomright", colors = colorRampPalette(c(claro, petroleo))(5),
            labels = c("Q1 · bajo", "Q2", "Q3", "Q4", "Q5 · alto"),
            title = "Hurtos /10.000 hab<br>(quintiles)", opacity = .85) %>%
  addLegend("bottomright", colors = colorRampPalette(c(claro, vino))(5),
            labels = c("Q1 · bajo", "Q2", "Q3", "Q4", "Q5 · alto"),
            title = "Violencia intrafamiliar<br>/10.000 hab (quintiles)", opacity = .85) %>%
  addLegend("bottomright", pal = pal_p, values = m_ok$pct_percibe_inseguridad,
            title = "% percibe<br>inseguridad", opacity = .85, labFormat = labelFormat(suffix = " %")) %>%
  # Mostrar solo la leyenda de la capa que esté activa (se reconoce por su título)
  htmlwidgets::onRender("
    function(el, x) {
      var map = this;
      var clave = {'Hurtos': 'Hurtos', 'Violencia intrafamiliar': 'Violencia', 'Percepción de inseguridad': 'percibe'};
      function mostrar(capa) {
        el.querySelectorAll('.legend').forEach(function(l) {
          l.style.display = l.innerText.indexOf(clave[capa]) >= 0 ? 'block' : 'none';
        });
      }
      mostrar('Hurtos');
      map.on('baselayerchange', function(e) { mostrar(e.name); });
    }")

Los dos mapas son casi un negativo uno del otro. El hurto se concentra en el eje Centro–Chapinero–Teusaquillo–Barrios Unidos, donde está el comercio, la oficina y la universidad. La violencia intrafamiliar se concentra en el sur (Ciudad Bolívar, Usme, San Cristóbal, Bosa) y en el centro histórico (Santa Fe). La inseguridad de Bogotá tiene dirección: depende del tipo de delito.

P2 · ¿Cambia el tipo de delito según el nivel socioeconómico?

¿Cambia el tipo de delito predominante según el salario o el estrato de la zona?

Las 101 UPZ se dividieron en cuatro grupos del mismo tamaño según su salario promedio y se comparó la tasa mediana de cada delito.

por_cuartil <- upz_t %>%
  group_by(`Cuartil salarial` = cuartil_salario) %>%
  summarise(UPZ = n(),
            `Salario mediano` = median(salario_promedio),
            `% estrato 1–2` = median(pct_estrato_1_2),
            `Hurto personas` = median(tasa_hurto_personas),
            `Hurto residencias` = median(tasa_hurto_residencias),
            Lesiones = median(tasa_lesiones),
            Homicidios = median(tasa_homicidios),
            `Violencia intraf.` = median(tasa_violencia_intrafamiliar),
            `% denuncias que son violencia` = median(pct_violencia))
write_csv(por_cuartil, "output/tablas/delitos_por_cuartil_salarial.csv")

kw_h <- kruskal.test(tasa_hurtos ~ cuartil_salario, upz_t)$p.value
kw_v <- kruskal.test(tasa_violencia ~ cuartil_salario, upz_t)$p.value

por_cuartil %>%
  kbl(digits = c(0, 0, 0, 1, 1, 1, 1, 2, 1, 1), format.args = list(big.mark = ".", decimal.mark = ","),
      caption = "Medianas por cuartil de salario · tasas por 10.000 hab") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = TRUE) %>%
  add_header_above(c(" " = 4, "Hurtos" = 2, "Violencia contra las personas" = 3, " " = 1),
                   background = c(crema, petroleo, vino, crema), color = c(tinta, "white", "white", tinta))
Medianas por cuartil de salario · tasas por 10.000 hab
Hurtos
Violencia contra las personas
Cuartil salarial UPZ Salario mediano % estrato 1–2 Hurto personas Hurto residencias Lesiones Homicidios Violencia intraf. % denuncias que son violencia
Q1 · más bajo 26 1.081.558 99,6 45,5 3,6 12,6 1,17 24,4 35,0
Q2 25 1.524.403 55,2 80,6 4,3 11,8 0,65 19,0 16,8
Q3 25 2.262.525 0,8 156,8 7,6 13,9 0,39 16,0 10,5
Q4 · más alto 25 4.927.443 0,1 154,5 8,0 7,8 0,24 10,8 6,6
dat_q <- upz_t %>%
  select(cuartil_salario, `Hurto a personas` = tasa_hurto_personas,
         `Hurto a residencias` = tasa_hurto_residencias,
         `Violencia intrafamiliar` = tasa_violencia_intrafamiliar,
         Homicidios = tasa_homicidios) %>%
  pivot_longer(-cuartil_salario, names_to = "delito", values_to = "tasa") %>%
  group_by(cuartil_salario, delito) %>% summarise(med = median(tasa), .groups = "drop") %>%
  mutate(delito = factor(delito, c("Hurto a personas", "Hurto a residencias",
                                   "Violencia intrafamiliar", "Homicidios")),
         familia = if_else(str_detect(delito, "Hurto"), "hurto", "violencia"))

guardar(
  ggplot(dat_q, aes(cuartil_salario, med, fill = familia)) +
    geom_col(width = .68) +
    geom_text(aes(label = case_when(med < 2 ~ fmt(med, 2), med < 20 ~ fmt(med, 1), TRUE ~ fmt(med, 0))),
              vjust = -.4, size = 3.6, colour = tinta) +
    facet_wrap(~delito, scales = "free_y", nrow = 1) +
    scale_fill_manual(values = c(hurto = petroleo, violencia = vino), guide = "none") +
    scale_y_continuous(expand = expansion(mult = c(0, .14))) +
    labs(title = "Al subir el salario, los hurtos suben (hasta Q3) y la violencia baja",
         subtitle = paste0("Tasa mediana por 10.000 hab según el cuartil de salario promedio de la UPZ\n",
                           "Kruskal-Wallis: hurtos ", fmt_p(kw_h), " · violencia ", fmt_p(kw_v)),
         x = "Cuartil de salario promedio de la UPZ", y = "Tasa mediana /10.000 hab", caption = fuente) +
    theme(axis.text.x = element_text(size = 8.5, angle = 20, hjust = 1), panel.grid.major.x = element_blank()),
  "05_delitos_por_cuartil_salarial", h = 6)

En las UPZ de salario más alto (Q4) la tasa mediana de hurto a personas es 3,4 veces la de las de salario más bajo (Q1). El aumento no es lineal: el gran salto ocurre de Q1 a Q3, y entre Q3 y Q4 el hurto a personas se estabiliza (157 frente a 154). Con la violencia pasa lo contrario: la de homicidios es 4,9 veces mayor en Q1 que en Q4, y la de violencia intrafamiliar, 2,3 veces mayor. En las UPZ más pobres, 35 % de las denuncias son violencia contra personas; en las más ricas, apenas 7 %. El tipo de delito predominante sí cambia con el nivel socioeconómico, y las diferencias son estadísticamente significativas (Kruskal-Wallis, p < 0,001).

P3 · ¿El entorno se relaciona con ciertos delitos?

¿Hay relación entre el alumbrado, los lotes oscuros, las ollas y los bares y ciertos delitos?
entorno <- c(pct_cerca_ollas = "Cerca de ollas", pct_cerca_lotes_oscuros = "Lotes baldíos u oscuros",
             pct_cerca_bares = "Cerca de bares", luminarias_km2 = "Luminarias por km²",
             luminarias_1000hab = "Luminarias por 1.000 hab")
delitos <- c(tasa_hurto_personas = "Hurto\npersonas", tasa_hurto_residencias = "Hurto\nresidencias",
             tasa_hurto_automotor = "Hurto\nautomotor", tasa_lesiones = "Lesiones",
             tasa_homicidios = "Homicidios", tasa_violencia_intrafamiliar = "Violencia\nintrafamiliar")

cor_ent <- expand_grid(e = names(entorno), d = names(delitos)) %>%
  rowwise() %>%
  mutate(ct = list(cor.test(upz_t[[e]], upz_t[[d]], method = "spearman", exact = FALSE)),
         rho = ct$estimate, p = ct$p.value) %>%
  ungroup() %>% select(-ct) %>%
  mutate(E = factor(entorno[e], rev(entorno)), D = factor(delitos[d], delitos),
         lab = paste0(sprintf("%.2f", rho), ifelse(p < .05, "", "\n(n.s.)")))
write_csv(select(cor_ent, e, d, rho, p), "output/tablas/correlaciones_entorno_delitos.csv")

guardar(
  ggplot(cor_ent, aes(D, E, fill = rho)) +
    geom_tile(colour = crema, linewidth = 1.2, aes(alpha = p < .05)) +
    geom_text(aes(label = lab, colour = abs(rho) > .45), size = 3.5, lineheight = .85) +
    geom_vline(xintercept = 3.5, colour = tinta, linewidth = .8) +
    annotate("text", x = c(2, 5), y = 5.75, label = c("HURTOS", "VIOLENCIA"),
             colour = c(petroleo, vino), fontface = "bold", size = 4) +
    scale_fill_gradient2(low = petroleo, mid = medio, high = vino, limits = c(-1, 1), name = "ρ") +
    scale_alpha_manual(values = c(`TRUE` = 1, `FALSE` = .35), guide = "none") +
    scale_colour_manual(values = c(`TRUE` = "white", `FALSE` = tinta), guide = "none") +
    coord_cartesian(ylim = c(.5, 5.9), clip = "off") +
    labs(title = "Las ollas y los lotes oscuros acompañan a la violencia, no al hurto",
         subtitle = "Correlación de Spearman entre el entorno (encuesta y alumbrado 2021) y las tasas de delito (2023)\nn.s. = no significativa (p ≥ 0,05)",
         x = NULL, y = NULL, caption = fuente) +
    theme(panel.grid = element_blank(), legend.position = "right"),
  "06_entorno_vs_delitos", h = 5.6)

rho_ollas <- cor.test(upz_t$pct_cerca_ollas, upz_t$tasa_violencia_intrafamiliar, method = "spearman", exact = FALSE)

guardar(
  ggplot(upz_t, aes(pct_cerca_ollas, tasa_violencia_intrafamiliar)) +
    geom_smooth(method = "loess", se = TRUE, colour = vino, fill = "#E6D3CF", linewidth = 1) +
    geom_point(aes(fill = cuartil_salario), shape = 21, size = 3, colour = "white", stroke = .4) +
    geom_text_repel(data = slice_max(upz_t, tasa_violencia_intrafamiliar, n = 6),
                    aes(label = upz_nombre), size = 3.3, colour = tinta, seed = 3) +
    scale_fill_manual(values = c(vino, "#C47A55", "#9DB8BE", petroleo), name = "Cuartil de salario:") +
    scale_y_log10() +
    labs(title = "Donde más hogares viven cerca de una olla,\nmás violencia intrafamiliar se denuncia",
         subtitle = paste0("ρ de Spearman = ", fmt(rho_ollas$estimate, 2), " (", fmt_p(rho_ollas$p.value),
                           ") · cada punto es una UPZ · línea = tendencia LOESS · eje vertical en escala log"),
         x = "% de viviendas con expendios de droga cerca", y = "Violencia intrafamiliar /10.000 hab (log)",
         caption = fuente),
  "07_ollas_vs_vif", h = 5.8)

Las ollas y los lotes oscuros se asocian positivamente con la violencia (lesiones, homicidios y violencia intrafamiliar) y negativamente con el hurto. El color del gráfico muestra por qué: ese entorno se concentra en las UPZ de menor salario (puntos vino). El entorno deteriorado y la pobreza llegan juntos, y con estos datos no es posible separar el efecto de uno y otro.

El alumbrado es la trampa del ejercicio. Luminarias por 1.000 habitantes tiene la correlación más alta con el hurto (ρ = 0,74), pero no significa que la luz atraiga ladrones. Las UPZ comerciales y de oficinas tienen mucho alumbrado y pocos residentes, así que el mismo denominador infla las dos variables a la vez. Medido por km², la relación con el hurto es más débil, y el % de luminarias LED prácticamente no se relaciona con el delito total (ρ = -0,03).

P4 · ¿La gente se siente insegura donde hay más denuncias?

¿La gente se siente insegura donde de verdad hay más denuncias?
r_ph <- cor.test(upz_t$pct_percibe_inseguridad, upz_t$tasa_hurtos, method = "spearman", exact = FALSE)
r_pv <- cor.test(upz_t$pct_percibe_inseguridad, upz_t$tasa_violencia, method = "spearman", exact = FALSE)

disp <- function(x, col, titulo, rho, etiquetas) {
  ggplot(upz_t, aes(.data[[x]], pct_percibe_inseguridad)) +
    geom_smooth(method = "lm", formula = y ~ x, colour = col, fill = alpha(col, .15)) +
    geom_point(colour = col, alpha = .75, size = 2.3) +
    geom_text_repel(data = filter(upz_t, upz_nombre %in% etiquetas), aes(label = upz_nombre),
                    size = 3.2, colour = tinta, seed = 2, min.segment.length = 0) +
    scale_x_log10(labels = label_number(big.mark = ".", decimal.mark = ",")) +
    scale_y_continuous(limits = c(0, 100), labels = function(x) paste0(x, " %")) +
    labs(title = titulo, subtitle = paste0("ρ = ", fmt(rho$estimate, 2), " · ", fmt_p(rho$p.value)),
         x = "Tasa por 10.000 hab (escala log)", y = "% que percibe inseguridad") +
    theme(plot.title = element_text(colour = col, size = 15))
}

guardar(
  (disp("tasa_hurtos", petroleo, "…vs. tasa de HURTOS", r_ph, c("Chico Lago", "Las Nieves", "El Refugio", "Chapinero")) |
     disp("tasa_violencia", vino, "…vs. tasa de VIOLENCIA", r_pv, c("El Tesoro", "Lucero", "Sagrado Corazon", "Chico Lago"))) +
    plot_annotation(
      title = "La percepción de inseguridad sigue a la violencia, no al hurto",
      subtitle = "Cada punto es una UPZ · % de hogares que reportan problemas de inseguridad en el entorno (EM 2021)",
      caption = fuente, theme = tema_info),
  "08_percepcion_vs_delitos", h = 5.8)

Parece una paradoja. Las UPZ con más hurtos por habitante tienden a sentirse más seguras (ρ = -0,39). En Chicó Lago, con una de las tasas de hurto más altas, solo el 21 % de los hogares percibe inseguridad. En cambio, en El Tesoro (Ciudad Bolívar) la percibe el 87 %. La percepción sube con la violencia (ρ = 0,60), y la relación es todavía más fuerte con la pobreza (ρ = 0,74) y con las ollas (ρ = 0,67). Hay que leerlo con cuidado: la encuesta pregunta por el entorno de la vivienda, y muchos hurtos del centro y el norte les ocurren a personas que no viven allí.

P5 · ¿Qué tan lejos queda la policía?

¿Qué tan lejos queda el CAI en las UPZ con tasas más altas?
r_cai <- cor.test(upz_t$min_cai_policia, upz_t$tasa_total, method = "spearman", exact = FALSE)
cai_q <- upz_t %>% group_by(cuartil_total) %>%
  summarise(med = median(min_cai_policia), n = n())
write_csv(cai_q, "output/tablas/minutos_cai_por_cuartil.csv")

guardar(
  ggplot(upz_t, aes(cuartil_total, min_cai_policia)) +
    geom_boxplot(aes(fill = cuartil_total == "Q4 · tasa más alta"), width = .55,
                 outlier.shape = NA, colour = tinta, alpha = .9) +
    geom_jitter(width = .12, size = 1.7, alpha = .55, colour = tinta) +
    geom_label(data = cai_q, aes(y = med, label = paste0(fmt(med, 1), " min")),
               nudge_x = .43, size = 3.6, fill = crema, label.size = 0, fontface = "bold") +
    scale_fill_manual(values = c(`TRUE` = naranja, `FALSE` = "#E4D9CA"), guide = "none") +
    scale_y_continuous(limits = c(0, NA), labels = function(x) paste0(x, " min")) +
    labs(title = "En las UPZ con más denuncias, el CAI queda más cerca, no más lejos",
         subtitle = paste0("Minutos caminando hasta el CAI según el cuartil de tasa total de delitos · ρ = ",
                           fmt(r_cai$estimate, 2), " (", fmt_p(r_cai$p.value), ")"),
         x = NULL, y = "Minutos caminando al CAI (EM 2021)", caption = fuente) +
    theme(panel.grid.major.x = element_blank()),
  "09_cai_por_cuartil", h = 5.6)

La intuición diría que donde más delitos hay, más lejos está la policía. Los datos dicen lo contrario. En el cuartil de tasa más alta, el CAI queda a 15,6 minutos a pie (mediana), contra 18,9 minutos en el cuartil más bajo. Hay al menos tres explicaciones posibles, y los datos no permiten elegir entre ellas: (a) los CAI se instalan donde se concentra el delito, es decir, en las centralidades comerciales; (b) tener un CAI cerca facilita denunciar, lo que eleva la tasa registrada; (c) ambas zonas son densas y céntricas, y en ellas todo queda cerca. Lo que sí es claro es que la distancia al CAI no explica la violencia en casa: su correlación con la violencia es más débil (ρ = -0,24).

P6 · Ranking: ¿qué UPZ cargan con más violencia intrafamiliar?

¿Qué UPZ cargan con más violencia intrafamiliar y qué tienen en común?

Se eligió la violencia intrafamiliar para el ranking porque es el delito que mejor representa a quienes viven en la UPZ: ocurre en el hogar y se distorsiona poco con la población flotante, al contrario del hurto en el centro.

# ¿Hay empate en el puesto 10? Se detecta para avisarlo bajo la tabla.
corte10   <- sort(upz_t$tasa_violencia_intrafamiliar, decreasing = TRUE)[10]
empatadas <- upz_t %>% filter(round(tasa_violencia_intrafamiliar, 1) == round(corte10, 1))

top10 <- upz_t %>% slice_max(tasa_violencia_intrafamiliar, n = 10, with_ties = FALSE) %>%
  mutate(nombre = fct_reorder(paste0(upz_nombre, "  ·  ", localidad), tasa_violencia_intrafamiliar))
med_vif <- median(upz_t$tasa_violencia_intrafamiliar)

guardar(
  ggplot(top10, aes(tasa_violencia_intrafamiliar, nombre)) +
    geom_segment(aes(x = 0, xend = tasa_violencia_intrafamiliar, yend = nombre), colour = vino, linewidth = 1.1) +
    geom_vline(xintercept = med_vif, colour = naranja, linetype = 2) +
    geom_point(colour = vino, size = 4) +
    annotate("text", x = med_vif, y = 10.45, label = paste0("mediana Bogotá: ", fmt(med_vif, 1)),
             hjust = -.05, colour = naranja, size = 3.4, fontface = "bold") +
    geom_text(aes(label = fmt(tasa_violencia_intrafamiliar, 1)), hjust = -.6, size = 3.6, colour = tinta) +
    scale_x_continuous(expand = expansion(mult = c(0, .12))) +
    labs(title = "Top 10 de violencia intrafamiliar:\nun caso atípico en el centro y, detrás, el sur de la ciudad",
         subtitle = paste0("Denuncias de violencia intrafamiliar por 10.000 hab (ene–jul 2023) · UPZ con ≥ ", fmt(umbral), " hab."),
         x = "Tasa /10.000 hab", y = NULL, caption = fuente) +
    theme(panel.grid.major.y = element_blank()),
  "10_ranking_vif", h = 6)

rank_tab <- top10 %>% arrange(desc(tasa_violencia_intrafamiliar)) %>%
  transmute(`#` = row_number(), UPZ = upz_nombre, Localidad = localidad,
            Habitantes = poblacion, `VIF /10.000` = tasa_violencia_intrafamiliar,
            `% pobreza` = pct_pobreza, `% cerca ollas` = pct_cerca_ollas,
            `% percibe inseg.` = pct_percibe_inseguridad, `Min. al CAI` = min_cai_policia,
            `Salario prom.` = round(salario_promedio, -3))
write_csv(rank_tab, "output/tablas/ranking_top10_vif.csv")

ref <- upz_t %>% summarise(across(c(pct_pobreza, pct_cerca_ollas, pct_percibe_inseguridad), median))

rank_tab %>%
  kbl(digits = c(0, 0, 0, 0, 1, 1, 1, 1, 1, 0), format.args = list(big.mark = ".", decimal.mark = ","),
      caption = paste0("En negrilla vino: valores por encima de la mediana de Bogotá (pobreza ",
                       pct(ref$pct_pobreza, 1), ", ollas ", pct(ref$pct_cerca_ollas, 1),
                       ", percepción ", pct(ref$pct_percibe_inseguridad, 1), ")")) %>%
  kable_styling(bootstrap_options = c("striped", "condensed", "hover"), full_width = TRUE) %>%
  column_spec(5, bold = TRUE, color = "white", background = vino) %>%
  column_spec(6, bold = rank_tab$`% pobreza` > ref$pct_pobreza,
              color = ifelse(rank_tab$`% pobreza` > ref$pct_pobreza, vino, tinta)) %>%
  column_spec(7, bold = rank_tab$`% cerca ollas` > ref$pct_cerca_ollas,
              color = ifelse(rank_tab$`% cerca ollas` > ref$pct_cerca_ollas, vino, tinta)) %>%
  column_spec(8, bold = rank_tab$`% percibe inseg.` > ref$pct_percibe_inseguridad,
              color = ifelse(rank_tab$`% percibe inseg.` > ref$pct_percibe_inseguridad, vino, tinta)) %>%
  footnote(general = if (nrow(empatadas) > 1)
    paste0("Empate en el puesto 10: ", paste(empatadas$upz_nombre, collapse = " y "),
           " tienen la misma tasa (", fmt(corte10, 1), "). Se muestra la primera según el orden de los datos.")
    else "Sin empates en el puesto 10.", general_title = "Nota: ")
En negrilla vino: valores por encima de la mediana de Bogotá (pobreza 2,7 %, ollas 20,9 %, percepción 66,0 %)
# UPZ Localidad Habitantes VIF /10.000 % pobreza % cerca ollas % percibe inseg. Min. al CAI Salario prom.
1 Sagrado Corazon Santa Fe 11.247 232,1 2,2 23,1 59,9 10,1 4.390.000
2 La Sabana Los Mártires 38.856 84,4 6,7 61,4 83,4 16,0 1.914.000
3 El Tesoro Ciudad Bolívar 54.369 53,5 12,9 52,3 86,8 27,4 967.000
4 Sosiego San Cristóbal 44.546 50,5 2,5 14,1 70,3 15,1 1.774.000
5 Lucero Ciudad Bolívar 163.588 50,0 10,5 50,0 77,2 17,5 950.000
6 San Francisco Ciudad Bolívar 72.057 44,4 7,7 29,8 70,0 17,4 1.067.000
7 Puente Aranda Puente Aranda 12.482 40,9 3,6 51,5 66,3 17,2 1.613.000
8 La Candelaria Candelaria 18.409 33,7 4,3 39,1 64,7 12,7 2.530.000
9 Las Nieves Santa Fe 17.203 32,5 2,2 23,1 59,9 10,1 4.390.000
10 Marco Fidel Suarez Rafael Uribe Uribe 47.615 32,3 9,3 24,4 73,4 20,8 1.142.000
Nota:
Empate en el puesto 10: Marco Fidel Suarez y Jerusalem tienen la misma tasa (32,3). Se muestra la primera según el orden de los datos.

Sagrado Corazón (11.247 hab.) tiene una tasa de violencia intrafamiliar 13 veces la mediana de la ciudad. Es poco creíble que refleje solo lo que pasa en sus hogares. Una hipótesis que habría que verificar es que algunas denuncias se registren con la dirección de la entidad que las recibe y no con la del lugar de los hechos, y en el centro se concentran sedes institucionales. Se deja en el ranking, pero se señala como posible artefacto del registro.

De las 10 UPZ con más violencia intrafamiliar, 9 están por encima de la mediana de la ciudad en hogares cerca de ollas, 7 en percepción de inseguridad y 7 en pobreza. Son dos territorios: el centro histórico (Santa Fe, Candelaria, Los Mártires), con deterioro urbano y población vulnerable, y la periferia sur. La violencia intrafamiliar no se reparte al azar: aparece junto con el entorno deteriorado y la sensación de inseguridad.

La tabla completa permite buscar cualquier UPZ:

upz %>%
  transmute(UPZ = upz_nombre, Localidad = localidad, Habitantes = poblacion,
            `Excluida (pob. baja)` = if_else(poblacion_baja, "Sí", "No"),
            `Hurtos /10.000` = round(tasa_hurtos), `Violencia /10.000` = round(tasa_violencia, 1),
            `VIF /10.000` = round(tasa_violencia_intrafamiliar, 1),
            `% percibe inseg.` = pct_percibe_inseguridad, `Min. CAI` = min_cai_policia,
            `Salario prom.` = round(salario_promedio)) %>%
  datatable(rownames = FALSE, filter = "none",   # el buscador general basta; los filtros por columna chocan con leaflet
            options = list(pageLength = 10, scrollX = TRUE, order = list(list(6, "desc")),
                           language = list(search = "Buscar:", lengthMenu = "Mostrar _MENU_ filas",
                                           info = "Filas _START_ a _END_ de _TOTAL_",
                                           paginate = list(previous = "Anterior", `next` = "Siguiente")))) %>%
  formatRound(c("Habitantes", "Salario prom."), digits = 0, mark = ".", dec.mark = ",")

4. Hallazgos e historia

3,4×

El hurto sigue al dinero

Las UPZ de salario alto tienen 3,4 veces más hurto a personas por habitante que las de salario bajo, y se concentran en el eje centro–norte (P1, P2).
4,9×

La violencia se queda en casa y en el sur

La tasa de homicidios es 4,9 veces mayor en las UPZ de salario más bajo, y la violencia intrafamiliar se concentra en la periferia sur y en el centro histórico (P1, P2, P6).
ρ = 0,66

Ollas y violencia van juntas

Cuantos más hogares viven cerca de una olla, más violencia intrafamiliar se denuncia. Con el hurto la relación se invierte (P3).
-0,39 / +0,60

El miedo no sigue al hurto

La percepción de inseguridad baja donde hay más hurtos y sube donde hay más violencia. La gente se siente insegura donde el entorno se deteriora (P4).
16 vs 19 min

La policía está donde está el hurto

Las UPZ con más denuncias tienen el CAI más cerca. La presencia policial coincide con las centralidades, no con los focos de violencia en el hogar (P5).
11 UPZ

Las tasas engañan si no se mira la población

Parques, el aeropuerto y zonas industriales disparan las tasas sin representar el riesgo de nadie. Por eso se excluyeron de los análisis de tasas (§2.2).

La historia completa

Si se mira solo el total de denuncias, la inseguridad de Bogotá parece un fenómeno del centro y del norte, porque ahí está el 84 % de lo que se denuncia: el hurto. El hurto sigue a la gente y al dinero que circulan por las centralidades comerciales, y es justo allí donde la ciudad puso sus CAI.

Pero quienes viven en esas UPZ no son los que más miedo sienten. El miedo, medido como percepción de inseguridad en el entorno de la vivienda, aparece en otra Bogotá: la del sur y la periferia, con más pobreza, más ollas y más lotes oscuros. Allí se denuncian menos hurtos, pero más homicidios, lesiones y violencia intrafamiliar. Es una inseguridad que no se ve en el conteo total, porque son menos casos, aunque mucho más graves.

La consecuencia práctica es que “reducir la inseguridad” exige dos estrategias distintas: una de control y prevención del hurto en las centralidades y otra de intervención social y del entorno (recuperar lotes, desarticular ollas, atender la violencia en el hogar) en el sur y en el centro histórico. Este análisis exploratorio no prueba causas, pero sí muestra con claridad que no se trata de un solo problema.

5. Limitaciones y conclusión

  • Las ollas y la violencia van juntas, pero eso no prueba que una cause la otra: ambas están ligadas a la pobreza.
  • El subregistro varía por zona y por tipo de delito. La violencia intrafamiliar y los hurtos menores se denuncian mucho menos, y es posible que el sur denuncie menos que el norte.
  • La tasa usa residentes, no visitantes. En el centro y en Chapinero la población flotante infla las tasas.
  • La encuesta es una muestra, y en 32 UPZ sus valores son de grupo. Se comprobó que esto no cambia los signos (§2.6), pero sí suaviza la variación.
  • No hay delitos sexuales en la fuente de 2023, así que una parte de la violencia contra las personas queda fuera del análisis.
  • El umbral de 10.000 habitantes es una decisión del equipo. Se justifica con el salto natural en los datos, pero otro umbral puede mover los rankings.

Conclusión. Los datos de las 112 UPZ muestran que en Bogotá conviven dos inseguridades. El hurto, masivo y céntrico, sigue al dinero y a la gente que circula. La violencia contra las personas, menos frecuente pero más grave, se concentra donde hay pobreza, ollas y lotes oscuros, y es la que mejor explica dónde se siente miedo. Para el siguiente taller (segmentación con PCA), esto sugiere que las UPZ deberían agruparse al menos sobre dos ejes: el de hurto y centralidad, y el de violencia y vulnerabilidad, y no sobre un único índice de “inseguridad”.