library(tidyverse)   # manipular datos y graficar
library(sf)          # leer y dibujar mapas
library(scales)      # formatear números (puntos de miles, %)
library(patchwork)   # poner dos mapas lado a lado
library(kableExtra)  # tablas con estilo
azul  <- "#2F6DB5"   # hurto (delito patrimonial)
vino  <- "#A6243D"   # violencia contra las personas
gris  <- "#CDD0D5"   # contexto
noche <- "#1F2430"   # títulos

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

fuente <- "Fuente: SDSCJ (delitos ene–jul 2023), DANE (población 2023) y Encuesta Multipropósito 2021."

tabla_bonita <- function(df, resaltar = NULL) {
  k <- df %>%
    kbl() %>%
    kable_styling(bootstrap_options = c("hover", "condensed"), full_width = TRUE) %>%
    row_spec(0, bold = TRUE, color = "white", background = vino)
  if (!is.null(resaltar)) k <- k %>% row_spec(resaltar, bold = TRUE, background = "#F8E9EC")
  k
}
# Tabla de variables y mapa de las UPZ (rutas relativas)
upz     <- 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)

# Nos quedamos solo con las variables de las 8 preguntas
datos <- upz %>%
  select(upz_cod, upz_nombre, localidad,                       # identificación
         poblacion, area_km2,                                   # demografía y territorio
         casos_violencia_intrafamiliar, casos_hurto_personas,   # delitos: casos
         tasa_violencia_intrafamiliar, tasa_hurto_personas,     # delitos: tasas
         tasa_homicidios,
         em_agrupada,                                            # encuesta
         pct_estrato_1_2, pct_estrato_3_4, pct_estrato_5_6,
         pct_percibe_inseguridad, pct_cerca_ollas,
         pct_cerca_lotes_oscuros, min_cai_policia)

dim(datos)   # verificar: 112 filas y 18 columnas
## [1] 112  18

1. Introducción y preguntas

1.1 Contexto

En Bogotá, la inseguridad suele resumirse en un número: cuántos delitos se denuncian. Pero detrás de ese número hay fenómenos muy distintos: no es lo mismo un robo en una calle comercial que una agresión dentro de un hogar.

Este informe explora las 112 UPZ de Bogotá para responder una pregunta de fondo: ¿qué nos dicen los datos sobre la inseguridad en la ciudad y su relación con el territorio y las condiciones de vida? Comparamos dos tipos de inseguridad —el hurto y la violencia contra las personas— y cómo la percibe la ciudadanía.

Código de color: azul = hurto · vino = violencia · gris = contexto.

1.2 Preguntas

  1. ¿La violencia intrafamiliar se concentra predominantemente en las UPZ de estratos bajos, o se presenta de forma heterogénea a través de los estratos medios y altos?
  2. ¿Ocurren más hurtos a personas en UPZ con alta presencia de estratos 5 y 6 o en zonas predominantemente de estratos 1 y 2?
  3. ¿El delito de hurto y la violencia interpersonal se concentran en las mismas UPZ?
  4. ¿La presencia de microtráfico tiene alguna relación con la violencia por homicidios en las UPZ de Bogotá?
  5. ¿Cuáles son las 10 UPZ que encabezan el ranking con menor tasa de denuncias por hurto a personas?
  6. ¿Qué genera más miedo en la ciudadanía: el delito patrimonial de calle o el deterioro del entorno por la cercanía a ollas de microtráfico y espacios desolados?
  7. ¿Cuáles son las 10 UPZ con mayor porcentaje de viviendas cercanas a lotes baldíos u oscuros, y en cuál de ellas la percepción de inseguridad supera el 90 %?
  8. ¿Tener un CAI de policía más cerca se asocia con una mayor percepción de seguridad en la zona?

1.3 Variables elegidas

Para responder las ocho preguntas elegimos 12 variables de tres bloques de la base: demografía, delitos y la Encuesta Multipropósito. La tabla muestra cada variable y la pregunta que ayuda a responder.

tibble(
  Bloque = c("Demografía", "Demografía", "Delitos", "Delitos", "Delitos",
             "Encuesta", "Encuesta", "Encuesta", "Encuesta", "Encuesta"),
  Variable = c("poblacion", "area_km2",
               "tasa_violencia_intrafamiliar", "tasa_hurto_personas", "tasa_homicidios",
               "pct_estrato_1_2 / 3_4 / 5_6", "pct_percibe_inseguridad",
               "pct_cerca_ollas", "pct_cerca_lotes_oscuros", "min_cai_policia"),
  `Para qué la usamos` = c(
    "Filtrar UPZ con poca población",
    "Medir el hurto por km² (P2)",
    "Violencia intrafamiliar (P1, P3)",
    "Hurto a personas (P2, P3, P5, P6)",
    "Homicidios (P4)",
    "Estrato de cada UPZ (P1, P2)",
    "Percepción de inseguridad (P5, P7, P8)",
    "Microtráfico (P4, P5)",
    "Lotes baldíos u oscuros (P5, P7)",
    "Cercanía al CAI (P8)")
) %>%
  tabla_bonita()
Bloque Variable Para qué la usamos
Demografía poblacion Filtrar UPZ con poca población
Demografía area_km2 Medir el hurto por km² (P2)
Delitos tasa_violencia_intrafamiliar Violencia intrafamiliar (P1, P3)
Delitos tasa_hurto_personas Hurto a personas (P2, P3, P5, P6)
Delitos tasa_homicidios Homicidios (P4)
Encuesta pct_estrato_1_2 / 3_4 / 5_6 Estrato de cada UPZ (P1, P2)
Encuesta pct_percibe_inseguridad Percepción de inseguridad (P5, P7, P8)
Encuesta pct_cerca_ollas Microtráfico (P4, P5)
Encuesta pct_cerca_lotes_oscuros Lotes baldíos u oscuros (P5, P7)
Encuesta min_cai_policia Cercanía al CAI (P8)

2. Los datos y su exploración

2.1 Estructura y faltantes

Antes de analizar, revisamos que los datos estén completos y bien construidos: cuántas UPZ y columnas hay, si faltan valores y si cada UPZ aparece una sola vez.

tibble(
  Revisión = c("UPZ (filas) en la base original",
               "Columnas en la base original",
               "Datos faltantes en nuestras variables",
               "UPZ distintas (llave única)",
               "UPZ con valores de encuesta agrupados"),
  Resultado = c(nrow(upz), ncol(upz), sum(is.na(datos)),
                n_distinct(datos$upz_cod), sum(datos$em_agrupada))
) %>%
  tabla_bonita()
Revisión Resultado
UPZ (filas) en la base original 112
Columnas en la base original 67
Datos faltantes en nuestras variables 0
UPZ distintas (llave única) 112
UPZ con valores de encuesta agrupados 32

La base tiene una fila por UPZ, sin datos faltantes y con una llave única. Una advertencia: en 32 UPZ

2.2 UPZ con muy poca población

Antes de confiar en una tasa revisamos su denominador: la población. Una tasa divide los casos entre los habitantes, así que en una UPZ con muy pocos habitantes un solo caso puede dispararla. La tabla muestra las UPZ con menos de 10.000 habitantes.

datos %>%
  filter(poblacion < 10000) %>%
  arrange(poblacion) %>%
  select(UPZ = upz_nombre, Localidad = localidad, `Población` = poblacion,
         `Tasa de hurto` = tasa_hurto_personas,
         `Tasa de VIF` = tasa_violencia_intrafamiliar) %>%
  mutate(across(where(is.numeric), ~ round(.x, 1))) %>%
  tabla_bonita()
UPZ Localidad Población Tasa de hurto Tasa de VIF
El Mochuelo Ciudad Bolívar 30 6333.3 0.0
Aeropuerto El Dorado Fontibón 883 4507.4 11.3
Parque Entrenubes Usme 1169 42.8 17.1
Parque Salitre Barrios Unidos 1442 943.1 27.7
Paseo De Los Libertadores Usaquén 2690 163.6 0.0
Guaymaral Suba 2692 107.7 7.4
Jardin Botanico Engativá 2900 520.7 31.0
Parque Simon Bolivar - Can Teusaquillo 3323 1700.3 81.2
La Academia Suba 5260 142.6 9.5
Monte Blanco Ciudad Bolívar 6575 30.4 50.2
Zona Industrial Puente Aranda 6666 1296.1 229.5
peso <- datos %>%
  summarise(upz     = sum(poblacion < 10000),
            pct_pob = 100 * sum(poblacion[poblacion < 10000]) / sum(poblacion))

Encontramos 11 UPZ con menos de 10.000 habitantes. Casi todas son zonas de paso, como el Aeropuerto El Dorado o los parques Simón Bolívar y Salitre: muchos visitantes y pocos residentes, lo que dispara la tasa (en el aeropuerto supera las 4.500 denuncias por 10.000 habitantes).

Decisión: las excluimos del análisis y las mostramos en gris en el mapa. Son solo el 0,4 % de la población, así que no cambian el panorama de la ciudad.

Este efecto también infla el hurto en el centro y las zonas comerciales, pero casi no afecta a la violencia intrafamiliar, que ocurre dentro del hogar. Por eso, el hurto lo verificamos también por km².

datos   <- datos %>% mutate(pob_baja = poblacion < 10000)
datos_f <- datos %>% filter(!pob_baja)   # tabla de análisis: 101 UPZ

2.3 ¿Cómo se reparte la población?

El histograma muestra cuántas UPZ hay según su número de habitantes. En vino, las que excluimos.

mediana_pob <- median(datos$poblacion)

ggplot(datos, aes(x = poblacion, fill = pob_baja)) +
  geom_histogram(bins = 30, color = "white") +
  # Mediana: línea suave
  geom_vline(xintercept = mediana_pob, color = "grey40", linetype = "dotted", linewidth = 0.6) +
  annotate("text", x = mediana_pob + 4000, y = 16, hjust = 0,
           label = "Mediana: 54.000", color = "grey30", size = 3.5) +
  # Cola hacia la derecha
  annotate("text", x = 250000, y = 6, hjust = 0.5,
           label = "Cola hacia la derecha:\nTibabuyes, El Rincón\ny Bosa Central",
           color = "grey35", size = 3.2) +
  annotate("segment", x = 220000, xend = 300000, y = 3.8, yend = 3.8,
           arrow = arrow(length = unit(0.2, "cm")), color = "grey50") +
  scale_fill_manual(values = c(`TRUE` = vino, `FALSE` = gris),
                    labels = c(`TRUE` = "Excluidas (11)", `FALSE` = "Analizadas (101)"),
                    name = NULL) +
  scale_x_continuous(labels = label_comma(big.mark = ".")) +
  labs(title = "La mayoría de UPZ son medianas; unas pocas son muy pequeñas o muy grandes",
       subtitle = "Número de UPZ según su población · en vino, las 11 UPZ con menos de 10.000 habitantes · línea punteada: mediana",
       x = "Población de la UPZ (2023)", y = "Número de UPZ", caption = fuente) +
  tema_clase +
  theme(legend.position = "top")

La mitad de las UPZ tiene entre 30.000 y 101.000 habitantes, con una mediana de 54.000. La distribución tiene una cola hacia la derecha: unas pocas UPZ residenciales de la periferia, como Tibabuyes y El Rincón en Suba o Bosa Central, superan los 200.000 habitantes. Por eso comparamos las UPZ con tasas por habitante y no con el número de casos, que crecería solo por el tamaño.

Excluimos las UPZ más pequeñas, pero no las más grandes, porque el criterio no es la forma de la distribución sino qué tan confiable es la tasa. En una UPZ con pocos habitantes, un solo caso la mueve bruscamente; en una con cientos de miles, apenas la altera. Las UPZ grandes son, de hecho, las que tienen las tasas más estables.

2.4 ¿Cómo se reparten las tasas?

Revisamos las tres tasas que usan nuestras preguntas en las 101 UPZ.

datos_f %>%
  select(`Hurto a personas` = tasa_hurto_personas,
         `Violencia intrafamiliar` = tasa_violencia_intrafamiliar,
         `Homicidios` = tasa_homicidios) %>%
  pivot_longer(everything(), names_to = "Delito", values_to = "tasa") %>%
  group_by(Delito) %>%
  summarise(
    `Mínimo`     = min(tasa),
    Q1           = quantile(tasa, 0.25),
    Mediana      = median(tasa),
    Media        = mean(tasa),
    Q3           = quantile(tasa, 0.75),
    `Máximo`     = max(tasa),
    `Desv. est.` = sd(tasa)
  ) %>%
  mutate(across(where(is.numeric), ~ round(.x, 1))) %>%
  tabla_bonita()
Delito Mínimo Q1 Mediana Media Q3 Máximo Desv. est.
Homicidios 0.0 0.2 0.6 0.9 1.1 6.2 1.1
Hurto a personas 15.7 54.3 96.0 167.7 159.7 1487.5 223.9
Violencia intrafamiliar 5.4 12.5 17.6 21.7 23.3 232.1 24.1

La UPZ típica (mediana) registra 96 denuncias de hurto, 18 de violencia intrafamiliar y 0,6 homicidios por cada 10.000 habitantes. El hurto es 5 veces la VIF y más de 150 veces los homicidios.

En las tres, la media es mayor que la mediana: la mayoría de las UPZ tiene valores bajos y unas pocas se disparan.

Las tres tasas varían mucho entre UPZ: su desviación estándar es incluso mayor que su media. Pero esa variación no viene de la mayoría, sino de unas pocas UPZ con valores muy altos. En el hurto el efecto es tan fuerte que el promedio (167,7) supera al 75 % de las UPZ: no representa a la UPZ típica, sino que lo arrastran los extremos. Por eso describimos cada tasa con la mediana y medimos las relaciones con la correlación de Spearman, que no se dejan influir por esos valores.

2.5 El estrato predominante de cada UPZ

Para comparar la inseguridad por estrato necesitábamos clasificar cada UPZ, pero la base no asigna un estrato único: reporta qué porcentaje de las viviendas pertenece a los estratos 1–2, 3–4 y 5–6. Por eso construimos una variable nueva, el estrato predominante, que asigna a cada UPZ el rango con la mayor proporción de viviendas.

datos_f <- datos_f %>%
  mutate(
    estrato_dom = case_when(
      pct_estrato_1_2 >= pmax(pct_estrato_3_4, pct_estrato_5_6) ~ "Estrato 1–2",
      pct_estrato_3_4 >= pct_estrato_5_6                         ~ "Estrato 3–4",
      TRUE                                                       ~ "Estrato 5–6"
    ),
    estrato_dom = factor(estrato_dom, levels = c("Estrato 1–2", "Estrato 3–4", "Estrato 5–6"))
  )

datos_f %>%
  count(`Estrato predominante` = estrato_dom, name = "UPZ") %>%
  mutate(`% de las UPZ` = round(100 * UPZ / sum(UPZ), 1)) %>%
  tabla_bonita()
Estrato predominante UPZ % de las UPZ
Estrato 1–2 41 40.6
Estrato 3–4 53 52.5
Estrato 5–6 7 6.9

De las 101 UPZ, el 41 % es predominantemente de estrato 1–2, el 52 % de estrato 3–4 y solo el 7 % de estrato 5–6. Esta clasificación simplifica la realidad, porque una UPZ puede mezclar viviendas de varios estratos, pero permite comparar grupos de forma clara. Hay que tener en cuenta que el grupo de estrato 5–6 tiene apenas 7 UPZ

2.6 ¿Cómo se relacionan las variables entre sí?

Antes de responder cada pregunta, revisamos de forma general cómo se relacionan las variables. Usamos la correlación de Spearman, que va de −1 a 1: en vino, las variables que aumentan juntas; en azul, las que se mueven en sentido contrario.

nombres <- c(
  tasa_hurto_personas          = "Hurto a personas",
  tasa_violencia_intrafamiliar = "Violencia intrafamiliar",
  tasa_homicidios              = "Homicidios",
  pct_estrato_1_2              = "% estrato 1–2",
  pct_percibe_inseguridad      = "% percibe inseguridad",
  pct_cerca_ollas              = "% cerca de ollas",
  pct_cerca_lotes_oscuros      = "% cerca de lotes oscuros",
  min_cai_policia              = "Minutos al CAI"
)

datos_f %>%
  select(all_of(names(nombres))) %>%
  cor(method = "spearman") %>%
  as.data.frame() %>%
  rownames_to_column("var1") %>%
  pivot_longer(-var1, names_to = "var2", values_to = "rho") %>%
  mutate(var1 = factor(nombres[var1], levels = nombres),
         var2 = factor(nombres[var2], levels = nombres)) %>%
  filter(as.integer(var1) > as.integer(var2)) %>%      # solo el triángulo inferior
  ggplot(aes(x = var2, y = fct_rev(var1), fill = rho)) +
  geom_tile(color = "white", linewidth = 0.8) +
  geom_text(aes(label = format(round(rho, 2), nsmall = 2, decimal.mark = ","),
                color = abs(rho) > 0.5), size = 3.2) +
  scale_fill_gradient2(low = azul, mid = "white", high = vino,
                       midpoint = 0, limits = c(-1, 1), name = "Spearman") +
  scale_color_manual(values = c(`TRUE` = "white", `FALSE` = noche), guide = "none") +
  labs(title = "El hurto y la violencia se relacionan con variables opuestas",
       subtitle = "Correlación de Spearman entre las variables del análisis · 101 UPZ",
       x = NULL, y = NULL, caption = fuente) +
  tema_clase +
  theme(axis.text.x = element_text(angle = 45, hjust = 1), panel.grid = element_blank())

La matriz anticipa la historia del informe. La violencia intrafamiliar, los homicidios, las ollas y la percepción de inseguridad aumentan juntos en las UPZ con más estrato 1–2. El hurto hace lo contrario: es menor donde predominan esos estratos (−0,71) y casi no se relaciona con la violencia intrafamiliar (−0,22). Llama la atención que la percepción de inseguridad crece con las ollas (0,67), pero no con el hurto (−0,39), y que los minutos al CAI prácticamente no se relacionan con nada. Estas son asociaciones, no causas: las responderemos con más detalle en cada pregunta.

2.7 ¿Cómo se reparten las variables de la encuesta?

Hasta ahora exploramos los delitos. Las preguntas también usan cuatro variables de la Encuesta Multipropósito, que describen lo que la gente percibe de su entorno.

datos_f %>%
  select(`% que percibe inseguridad`              = pct_percibe_inseguridad,
         `% de viviendas cerca de ollas`          = pct_cerca_ollas,
         `% de viviendas cerca de lotes oscuros`  = pct_cerca_lotes_oscuros,
         `Minutos caminando al CAI`               = min_cai_policia) %>%
  pivot_longer(everything(), names_to = "Variable", values_to = "valor") %>%
  group_by(Variable) %>%
  summarise(`Mínimo` = min(valor),
            Q1       = quantile(valor, 0.25),
            Mediana  = median(valor),
            Q3       = quantile(valor, 0.75),
            `Máximo` = max(valor)) %>%
  mutate(across(where(is.numeric), ~ round(.x, 1))) %>%
  tabla_bonita()
Variable Mínimo Q1 Mediana Q3 Máximo
% de viviendas cerca de lotes oscuros 0.6 11.3 17.9 24.9 59.8
% de viviendas cerca de ollas 0.3 10.1 20.9 30.5 75.6
% que percibe inseguridad 17.4 48.5 66.0 74.3 92.1
Minutos caminando al CAI 10.1 15.1 17.5 20.5 35.1

La percepción de inseguridad es alta en buena parte de la ciudad: en 72 de las 101 UPZ, más de la mitad de las personas reporta problemas de inseguridad en su entorno. En la UPZ típica, una de cada cinco viviendas tiene cerca una olla de microtráfico. En cambio, la distancia al CAI varía poco: la mitad de las UPZ está entre 15 y 20 minutos caminando, lo que podría limitar su capacidad para explicar diferencias entre zonas.

3. Análisis

3.1 ¿La violencia intrafamiliar se concentra en los estratos bajos?

tasa_bogota_vif <- sum(datos$casos_violencia_intrafamiliar) / sum(datos$poblacion) * 10000

datos_f %>%
  group_by(`Estrato predominante` = estrato_dom) %>%
  summarise(
    UPZ                = n(),
    `Mediana de VIF`   = median(tasa_violencia_intrafamiliar),
    `UPZ sobre Bogotá` = sum(tasa_violencia_intrafamiliar > tasa_bogota_vif),
    casos              = sum(casos_violencia_intrafamiliar),
    pob                = sum(poblacion)
  ) %>%
  mutate(`% de los casos`    = 100 * casos / sum(casos),
         `% de la población` = 100 * pob / sum(pob)) %>%
  select(-casos, -pob) %>%
  mutate(across(where(is.double), ~ round(.x, 1))) %>%
  tabla_bonita(resaltar = 1)
Estrato predominante UPZ Mediana de VIF UPZ sobre Bogotá % de los casos % de la población
Estrato 1–2 41 22.6 28 60.3 50.1
Estrato 3–4 53 15.5 11 37.3 45.4
Estrato 5–6 7 9.4 0 2.4 4.5
set.seed(3)   # para que la dispersión horizontal de los puntos sea siempre igual
puntos <- datos_f %>%
  mutate(x     = as.numeric(estrato_dom) + runif(n(), -0.22, 0.22),
         y     = pmin(tasa_violencia_intrafamiliar, 95),     # recorta el valor extremo
         sobre = tasa_violencia_intrafamiliar > tasa_bogota_vif)

medianas <- puntos %>%
  group_by(estrato_dom) %>%
  summarise(mediana = median(tasa_violencia_intrafamiliar)) %>%
  mutate(x = as.numeric(estrato_dom))

ggplot(puntos, aes(x = x, y = y)) +
  geom_hline(yintercept = tasa_bogota_vif, color = azul, linetype = "dashed") +
  geom_point(aes(color = sobre), size = 2.5) +
  geom_segment(data = medianas,
               aes(x = x - 0.32, xend = x + 0.32, y = mediana, yend = mediana),
               color = noche, linewidth = 1.3) +
  geom_text(data = medianas,
            aes(x = x + 0.36, y = mediana, label = format(round(mediana, 1), decimal.mark = ",")),
            hjust = 0, fontface = "bold", color = noche, size = 3.6) +
  annotate("text", x = 3.45, y = tasa_bogota_vif + 2.5, label = "Bogotá: 20,2",
           color = azul, hjust = 1, size = 3.2) +
  scale_color_manual(values = c(`TRUE` = vino, `FALSE` = gris), guide = "none") +
  scale_x_continuous(breaks = 1:3, labels = levels(datos_f$estrato_dom)) +
  coord_cartesian(ylim = c(0, 100)) +
  labs(title = "La violencia intrafamiliar se concentra en los estratos bajos, pero no es exclusiva de ellos",
       subtitle = "Tasa de VIF por UPZ · línea negra: mediana · en vino, UPZ por encima de Bogotá · eje recortado en 100",
       x = NULL, y = "Denuncias de VIF por 10.000 hab", caption = fuente) +
  tema_clase +
  theme(panel.grid.major.x = element_blank())

prueba_p1 <- kruskal.test(tasa_violencia_intrafamiliar ~ estrato_dom, data = datos_f)
prueba_p1
## 
##  Kruskal-Wallis rank sum test
## 
## data:  tasa_violencia_intrafamiliar by estrato_dom
## Kruskal-Wallis chi-squared = 28.884, df = 2, p-value = 5.344e-07

La violencia intrafamiliar se concentra en las UPZ de estratos bajos: la mediana pasa de 22,6 denuncias por 10.000 habitantes en el estrato 1–2 a 9,4 en el 5–6, es decir, 2,4 veces más. La diferencia es estadísticamente significativa (Kruskal-Wallis, p < 0,001). Sin embargo, no es exclusiva de estos estratos: una de cada cinco UPZ de estrato 3–4 también supera la tasa de Bogotá.

3.2 ¿Hay más hurto en las UPZ de estratos altos o bajos?

datos_f %>%
  mutate(hurto_km2 = casos_hurto_personas / area_km2) %>%
  select(estrato_dom,
         `Por cada 10.000 habitantes` = tasa_hurto_personas,
         `Por km²`                    = hurto_km2) %>%
  pivot_longer(-estrato_dom, names_to = "medida", values_to = "valor") %>%
  ggplot(aes(x = estrato_dom, y = valor)) +
  geom_boxplot(fill = azul, alpha = 0.7, width = 0.55, outlier.color = azul) +
  facet_wrap(~ medida, ncol = 1, scales = "free_y") +
  scale_y_continuous(labels = label_comma(big.mark = ".")) +
  labs(title = "Las UPZ de estratos bajos tienen menos hurto, con cualquiera de las dos medidas",
       subtitle = "Denuncias de hurto a personas por UPZ según estrato predominante, ene–jul 2023 · 101 UPZ",
       x = NULL, y = NULL, caption = fuente) +
  tema_clase +
  theme(strip.text = element_text(face = "bold", hjust = 0, size = 11),
        panel.grid.major.x = element_blank())

El hurto sigue el patrón contrario: la mediana es de 52 denuncias por 10.000 habitantes en el estrato 1–2, frente a 147 en el 3–4 y 154 en el 5–6. Al medirlo por km², que no depende de cuántas personas viven en la zona, el estrato 1–2 sigue teniendo menos hurto (126 frente a 271 en el 3–4), pero la ventaja del estrato 5–6 desaparece (160). La conclusión sólida es que en los estratos bajos hay menos hurto; no que en los altos haya más.

3.3 ¿El hurto y la violencia se concentran en las mismas UPZ?

# Unimos los datos al mapa. Las UPZ con menos de 10.000 hab quedan en NA (gris).
mapa <- upz_shp %>%
  left_join(datos %>% select(upz_cod, pob_baja, tasa_hurto_personas,
                             tasa_violencia_intrafamiliar),
            by = "upz_cod") %>%
  mutate(hurto_mapa = if_else(pob_baja, NA_real_, tasa_hurto_personas),
         vif_mapa   = if_else(pob_baja, NA_real_, tasa_violencia_intrafamiliar))

m_hurto <- ggplot(mapa) +
  geom_sf(aes(fill = hurto_mapa), color = "white", linewidth = 0.1) +
  geom_sf(data = loc_shp, fill = NA, color = "grey40", linewidth = 0.25) +
  scale_fill_gradient(low = "#EAF1FA", high = "#173A66", trans = "log10",
                      na.value = "grey90", name = "Tasa de\nhurto",
                      labels = label_number(accuracy = 1)) +
  labs(title = "Hurto a personas") +
  theme_void() +
  theme(plot.title = element_text(face = "bold", color = azul, hjust = 0.5))

m_vif <- ggplot(mapa) +
  geom_sf(aes(fill = vif_mapa), color = "white", linewidth = 0.1) +
  geom_sf(data = loc_shp, fill = NA, color = "grey40", linewidth = 0.25) +
  scale_fill_gradient(low = "#FBEAEC", high = "#5C0F20", trans = "log10",
                      na.value = "grey90", name = "Tasa de\nVIF",
                      labels = label_number(accuracy = 1)) +
  labs(title = "Violencia intrafamiliar") +
  theme_void() +
  theme(plot.title = element_text(face = "bold", color = vino, hjust = 0.5))

m_hurto + m_vif +
  plot_annotation(
    title    = "El hurto y la violencia intrafamiliar dibujan dos mapas distintos de Bogotá",
    subtitle = "Denuncias por cada 10.000 habitantes, ene–jul 2023 · escala logarítmica · en gris, UPZ con menos de 10.000 habitantes",
    caption  = fuente,
    theme = theme(plot.title    = element_text(face = "bold", size = 15, color = noche),
                  plot.subtitle = element_text(color = "grey35"),
                  plot.caption  = element_text(hjust = 0, color = "grey45"))
  )

cor(datos_f$tasa_hurto_personas, datos_f$tasa_violencia_intrafamiliar, method = "spearman")
## [1] -0.2238769

Los dos mapas muestran patrones distintos: el hurto se concentra en el centro y el norte, y la violencia intrafamiliar en el sur y el centro. La correlación entre ambas tasas es baja y negativa (−0,22): las UPZ con más hurto no son las de más violencia. Son dos fenómenos con territorios diferentes.

3.4 ¿El microtráfico se relaciona con los homicidios?

ggplot(datos_f, aes(x = pct_cerca_ollas, y = tasa_homicidios, color = estrato_dom)) +
  geom_point(size = 2.5, alpha = 0.8) +
  geom_smooth(method = "lm", se = FALSE, linewidth = 0.9) +
  scale_color_manual(values = c("Estrato 1–2" = vino, "Estrato 3–4" = "grey55",
                                "Estrato 5–6" = noche), name = NULL) +
  labs(title = "Más ollas, más homicidios, incluso entre UPZ del mismo estrato",
       subtitle = "Cada punto es una UPZ · líneas: tendencia dentro de cada estrato predominante · 101 UPZ",
       x = "% de viviendas con ollas de microtráfico cerca",
       y = "Homicidios por 10.000 hab", caption = fuente) +
  tema_clase +
  theme(legend.position = "top")

# Como los homicidios son pocos, comparamos grupos de UPZ (tercios según ollas)
datos_f %>%
  mutate(nivel_ollas = cut(pct_cerca_ollas,
                           breaks = quantile(pct_cerca_ollas, c(0, 1/3, 2/3, 1)),
                           labels = c("Bajo", "Medio", "Alto"), include.lowest = TRUE)) %>%
  mutate(homicidios = round(tasa_homicidios * poblacion / 10000)) %>%   # casos = tasa × población
  group_by(`Presencia de ollas` = nivel_ollas) %>%
  summarise(UPZ = n(),
            Homicidios = sum(homicidios),
            pob = sum(poblacion)) %>%
  mutate(`Tasa del grupo (por 10.000 hab)` = round(10000 * Homicidios / pob, 2),
         `% de los homicidios` = round(100 * Homicidios / sum(Homicidios), 1)) %>%
  select(-pob) %>%
  tabla_bonita(resaltar = 3)
Presencia de ollas UPZ Homicidios Tasa del grupo (por 10.000 hab) % de los homicidios
Bajo 34 55 0.29 9.3
Medio 33 175 0.61 29.5
Alto 34 364 1.17 61.3

En el tercio de UPZ con más viviendas cerca de ollas, la tasa de homicidios es cuatro veces la del tercio con menos ollas (1,17 frente a 0,29 por 10.000 habitantes), y allí ocurre el 61 % de los homicidios. La relación se mantiene, aunque más débil, al comparar UPZ del mismo estrato. Aun así, es una asociación: los datos no permiten saber si las ollas generan la violencia.

3.5 ¿Cuáles son las 10 UPZ con menor tasa de hurto, y se sienten más seguras que el resto de la ciudad?

top_hurto <- datos_f %>%
  slice_min(tasa_hurto_personas, n = 10) %>%
  mutate(etiqueta = fct_rev(fct_reorder(paste0(upz_nombre, " (", localidad, ")"),
                                        tasa_hurto_personas)))

med_hurto <- median(datos_f$tasa_hurto_personas)
med_perc  <- median(datos_f$pct_percibe_inseguridad)

# Panel izquierdo: hurto
g_hurto <- ggplot(top_hurto, aes(x = tasa_hurto_personas, y = etiqueta)) +
  geom_col(fill = azul, width = 0.65) +
  geom_text(aes(label = format(round(tasa_hurto_personas, 1), decimal.mark = ",")),
            hjust = -0.2, size = 3.2, color = noche) +
  geom_vline(xintercept = med_hurto, color = noche, linetype = "dashed") +
  annotate("text", x = med_hurto, y = 10.4, label = "UPZ típica: 96",
           hjust = 1.05, vjust = 0, size = 3.1, color = noche) +
  scale_x_continuous(limits = c(0, 105)) +
  labs(title = "Tasa de hurto (por 10.000 hab)", x = NULL, y = NULL) +
  tema_clase +
  theme(panel.grid.major.y = element_blank(),
        plot.title = element_text(size = 12, color = azul))

# Panel derecho: percepción de inseguridad
g_perc <- ggplot(top_hurto, aes(x = pct_percibe_inseguridad, y = etiqueta)) +
  geom_col(fill = vino, width = 0.65) +
  geom_text(aes(label = paste0(round(pct_percibe_inseguridad), " %")),
            hjust = -0.2, size = 3.2, color = noche) +
  geom_vline(xintercept = med_perc, color = noche, linetype = "dashed") +
  annotate("text", x = med_perc, y = 10.4, label = "UPZ típica: 66 %",
           hjust = 1.05, vjust = 0, size = 3.1, color = noche) +
  scale_x_continuous(limits = c(0, 100)) +
  labs(title = "% que percibe inseguridad", x = NULL, y = NULL) +
  tema_clase +
  theme(panel.grid.major.y = element_blank(),
        axis.text.y = element_blank(),
        plot.title = element_text(size = 12, color = vino))

g_hurto + g_perc +
  plot_annotation(
    title    = "Las 10 UPZ con menos hurto no son las que se sienten más seguras",
    subtitle = "Todas están muy por debajo del hurto de la UPZ típica, pero 7 de 10 superan su percepción de inseguridad",
    caption  = fuente,
    theme = theme(plot.title    = element_text(face = "bold", size = 15, color = noche),
                  plot.subtitle = element_text(color = "grey35"),
                  plot.caption  = element_text(hjust = 0, color = "grey45"))
  )

No. Las diez UPZ con menor tasa de hurto —nueve de ellas de estrato 1–2, como San Isidro–Patios, Tintal Sur y Alfonso López— no se sienten más seguras que el resto de la ciudad. En siete de ellas, la percepción de inseguridad supera la de la UPZ típica de Bogotá (66 %). En El Tesoro, por ejemplo, la tasa de hurto es baja (36), pero el 87 % de las personas percibe inseguridad en su entorno. Tener poco hurto no significa sentirse seguro, lo que anticipa la siguiente pregunta: si no es el hurto, ¿qué genera el miedo? ## 3.6 ¿Qué genera más miedo: el hurto o el deterioro del entorno?

factores <- c(
  pct_cerca_ollas              = "% cerca de ollas",
  tasa_violencia_intrafamiliar = "Violencia intrafamiliar",
  tasa_homicidios              = "Homicidios",
  pct_cerca_lotes_oscuros      = "% cerca de lotes oscuros",
  min_cai_policia              = "Minutos al CAI",
  tasa_hurto_personas          = "Hurto a personas"
)

tibble(variable = names(factores)) %>%
  mutate(rho = map_dbl(variable, ~ cor(datos_f[[.x]], datos_f$pct_percibe_inseguridad,
                                       method = "spearman")),
         etiqueta = factores[variable]) %>%
  ggplot(aes(x = rho, y = fct_reorder(etiqueta, rho), fill = rho > 0)) +
  geom_col(width = 0.6) +
  geom_vline(xintercept = 0, color = noche) +
  geom_text(aes(label = format(round(rho, 2), decimal.mark = ","),
                hjust = if_else(rho >= 0, -0.2, 1.2)), size = 3.4, color = noche) +
  scale_fill_manual(values = c(`TRUE` = vino, `FALSE` = azul), guide = "none") +
  scale_x_continuous(limits = c(-1, 1)) +
  labs(title = "El miedo no sigue al hurto: sigue a las ollas y a la violencia",
       subtitle = "Correlación de Spearman de cada factor con el % que percibe inseguridad · 101 UPZ",
       x = "Correlación con la percepción de inseguridad", y = NULL, caption = fuente) +
  tema_clase +
  theme(panel.grid.major.y = element_blank())

La percepción de inseguridad aumenta con la presencia de ollas (0,67) y de lotes oscuros (0,35), pero disminuye donde hay más hurto (−0,39). La gente percibe más inseguridad por el deterioro de su entorno que por el delito más frecuente de la ciudad. Esto explica el contraste de la pregunta anterior: las UPZ con poco hurto pueden sentirse inseguras porque allí se concentran las ollas, los lotes oscuros y la violencia.

3.7 ¿Dónde coinciden los lotes oscuros y la percepción de inseguridad?

top_lotes <- datos_f %>%
  slice_max(pct_cerca_lotes_oscuros, n = 10) %>%
  mutate(upz     = fct_reorder(upz_nombre, pct_cerca_lotes_oscuros),
         critica = pct_percibe_inseguridad > 90)

ggplot(top_lotes, aes(y = upz)) +
  geom_segment(aes(x = pct_cerca_lotes_oscuros, xend = pct_percibe_inseguridad,
                   yend = upz, color = critica), linewidth = 1.2) +
  geom_point(aes(x = pct_cerca_lotes_oscuros), color = "grey55", size = 3) +
  geom_point(aes(x = pct_percibe_inseguridad, color = critica), size = 3.5) +
  geom_text(aes(x = pct_percibe_inseguridad, label = paste0(round(pct_percibe_inseguridad), " %"),
                color = critica), hjust = -0.4, size = 3.2, fontface = "bold") +
  geom_vline(xintercept = 90, color = vino, linetype = "dashed") +
  annotate("text", x = 90, y = 10.6, label = "90 %", color = vino, size = 3.2, hjust = 1.1) +
  scale_color_manual(values = c(`TRUE` = vino, `FALSE` = "grey75"), guide = "none") +
  scale_x_continuous(limits = c(0, 105), labels = function(x) paste0(x, " %")) +
  labs(title = "Corabastos es la única UPZ con muchos lotes oscuros\ny más del 90 % de percepción de inseguridad",
       subtitle = "10 UPZ con más viviendas cerca de lotes oscuros · punto gris: % de viviendas expuestas · punto final: % que percibe inseguridad",
       x = NULL, y = NULL, caption = fuente) +
  tema_clase +
  theme(panel.grid.major.y = element_blank(),
        axis.text.y = element_text(color = ifelse(levels(top_lotes$upz) == "Corabastos", vino, "grey30"),
                                   face  = ifelse(levels(top_lotes$upz) == "Corabastos", "bold", "plain")))

top_lotes %>%
  arrange(desc(pct_cerca_lotes_oscuros)) %>%
  transmute(UPZ = upz_nombre, Localidad = localidad,
            `% viviendas cerca de lotes oscuros` = round(pct_cerca_lotes_oscuros, 1),
            `% percibe inseguridad` = round(pct_percibe_inseguridad, 1)) %>%
  tabla_bonita(resaltar = 3)   # Corabastos
UPZ Localidad % viviendas cerca de lotes oscuros % percibe inseguridad
Zona Franca Fontibón 59.8 44.9
Tintal Sur Bosa 49.0 74.4
Corabastos Kennedy 48.9 92.1
Tintal Norte Kennedy 46.4 54.0
Puente Aranda Puente Aranda 45.3 66.3
El Tesoro Ciudad Bolívar 45.0 86.8
Granjas De Techo Fontibón 41.4 40.6
Capellania Fontibón 40.1 32.6
La Sabana Los Mártires 38.7 83.4
Danubio Usme 37.0 58.3

Las UPZ con más viviendas cerca de lotes baldíos u oscuros están en Fontibón, Bosa y Kennedy. Solo Corabastos combina alta exposición (49 %) con una percepción de inseguridad superior al 90 %, aunque El Tesoro (87 %) y La Sabana (83 %) también registran una percepción muy alta. En cambio, Zona Franca tiene la mayor exposición (60 %) y solo 45 % de percepción: los lotes oscuros se asocian con el miedo, pero no lo explican por sí solos.

3.8 ¿Tener un CAI cerca se asocia con mayor percepción de seguridad?

datos_f %>%
  mutate(distancia = cut(min_cai_policia,
                         breaks = quantile(min_cai_policia, c(0, 1/3, 2/3, 1)),
                         labels = c("Cerca", "Media", "Lejos"), include.lowest = TRUE)) %>%
  group_by(distancia) %>%
  summarise(minutos = median(min_cai_policia),
            percepcion = median(pct_percibe_inseguridad)) %>%
  ggplot(aes(x = distancia, y = percepcion)) +
  geom_col(fill = gris, width = 0.6) +
  geom_text(aes(label = paste0(round(percepcion), " %")),
            vjust = -0.5, size = 4, fontface = "bold", color = noche) +
  geom_text(aes(y = 3, label = paste0(round(minutos), " min al CAI")),
            vjust = 0, size = 3.2, color = "grey30") +
  scale_y_continuous(limits = c(0, 100), labels = function(x) paste0(x, " %")) +
  labs(title = "Tener un CAI más cerca no cambia la percepción de inseguridad",
       subtitle = "Mediana del % que percibe inseguridad según la distancia caminando al CAI (tercios de UPZ) · 101 UPZ",
       x = "Distancia al CAI", y = "% que percibe inseguridad", caption = fuente) +
  tema_clase +
  theme(panel.grid.major.x = element_blank())

cor(datos_f$min_cai_policia, datos_f$pct_percibe_inseguridad, method = "spearman")
## [1] 0.01459595

La cercanía al CAI no se relaciona con la percepción de inseguridad (correlación de 0,01): es casi igual cerca (63 %), a distancia media (71 %) o lejos (61 %). Además, la distancia al CAI varía poco entre UPZ (la mitad está entre 15 y 20 minutos), lo que limita su capacidad para explicar diferencias. Tener la policía cerca no se asocia con sentirse más seguro.