Delito y presencia policial en Lima Metropolitana: patrones y brechas territoriales basadas en análisis geoespacial

Autocorrelación espacial: método global y local

library(readxl)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.2     ✔ tibble    3.3.0
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ✔ purrr     1.0.4     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(spdep)
## Cargando paquete requerido: spData
## To access larger datasets in this package, install the spDataLarge
## package with: `install.packages('spDataLarge',
## repos='https://nowosad.github.io/drat/', type='source')`
## Cargando paquete requerido: sf
## Linking to GEOS 3.13.1, GDAL 3.11.0, PROJ 9.6.0; sf_use_s2() is TRUE
library(sf)
library(leaflet)
library(spdep)
library(dplyr)
library(stringr)
library(ggplot2)
library(spdep)
library(classInt)

1 Distribución espacial de los delitos patrimoniales


Esta sección analiza si los delitos patrimoniales registrados en Lima Metropolitana durante 2024 se distribuyen de forma aleatoria o siguen un patrón territorial definido. A través de indicadores globales y locales de autocorrelación espacial (Moran Global y LISA), se busca identificar la existencia de clústeres delictivos, distinguir entre verdaderos hotspots y zonas en riesgo de expansión, y dimensionar qué proporción del territorio concentra dinámicas criminales estructuradas.


#área sobre la cual trabajo, limite de la ciudad de Lima Metropolitana - Perú (poligono para identificar el mapa del delito patrimonial incluye robos y hurtos) 
lima <- st_read("C:/_ALIDE/Documentos/data_ciencia/MPP-CDD-02/data/lima_metropolitana.shp") 
## Reading layer `lima_metropolitana' from data source 
##   `C:\_ALIDE\Documentos\data_ciencia\MPP-CDD-02\data\lima_metropolitana.shp' 
##   using driver `ESRI Shapefile'
## Simple feature collection with 1 feature and 1 field
## Geometry type: POLYGON
## Dimension:     XY
## Bounding box:  xmin: -77.1988 ymin: -12.51993 xmax: -76.62082 ymax: -11.57241
## Geodetic CRS:  SIRGAS 2000
#Cargo la base de delitos patrimoniales 2024 (con columnas longitud/latitud).
# Cada fila representa un evento puntual georreferenciado.
delitos <- read_excel("C:/_ALIDE/Documentos/data_ciencia/MPP-CDD-02/data/hurtos_lima_2024.xlsx")
# Convierto delitos a sf (asegurándonos de que longitud y latitud estén bien definidos)
delitos_sf <- st_as_sf(delitos, coords = c("longitud", "latitud"), crs = 4326)

# Me aseguro que el polígono de Lima y los delitos compartan el mismo CRS (4326). Esto permite superponerlos correctamente en mapas iniciales.
lima <- st_transform(lima, 4326)
delitos_sf <- st_transform(delitos_sf, 4326)
# Mapa básico para chequear la distribución de puntos dentro del polígono de Lima.
ggplot() +
  geom_sf(data = lima, fill = "grey95", color = "black", linewidth = 0.3) +
  geom_sf(data = delitos_sf, color = "red", size = 1, alpha = 0.6) +
  labs(
    title = "Ubicación de delitos patrimoniales en Lima Metropolitana",
    caption = "Fuente: MINITER |Observatorio Nacional de Seguridad Ciudadana 2024"
  ) +
  theme_minimal()

#Antes hacer el chequeo si esta en metros
st_crs(lima)
## Coordinate Reference System:
##   User input: EPSG:4326 
##   wkt:
## GEOGCRS["WGS 84",
##     ENSEMBLE["World Geodetic System 1984 ensemble",
##         MEMBER["World Geodetic System 1984 (Transit)"],
##         MEMBER["World Geodetic System 1984 (G730)"],
##         MEMBER["World Geodetic System 1984 (G873)"],
##         MEMBER["World Geodetic System 1984 (G1150)"],
##         MEMBER["World Geodetic System 1984 (G1674)"],
##         MEMBER["World Geodetic System 1984 (G1762)"],
##         MEMBER["World Geodetic System 1984 (G2139)"],
##         MEMBER["World Geodetic System 1984 (G2296)"],
##         ELLIPSOID["WGS 84",6378137,298.257223563,
##             LENGTHUNIT["metre",1]],
##         ENSEMBLEACCURACY[2.0]],
##     PRIMEM["Greenwich",0,
##         ANGLEUNIT["degree",0.0174532925199433]],
##     CS[ellipsoidal,2],
##         AXIS["geodetic latitude (Lat)",north,
##             ORDER[1],
##             ANGLEUNIT["degree",0.0174532925199433]],
##         AXIS["geodetic longitude (Lon)",east,
##             ORDER[2],
##             ANGLEUNIT["degree",0.0174532925199433]],
##     USAGE[
##         SCOPE["Horizontal component of 3D system."],
##         AREA["World."],
##         BBOX[-90,-180,90,180]],
##     ID["EPSG",4326]]
# Para medir distancias, crear grillas y hacer estadística espacial,
# se convirtió a metros
lima_metros <- st_transform(lima, 32718)

st_crs(lima_metros)
## Coordinate Reference System:
##   User input: EPSG:32718 
##   wkt:
## PROJCRS["WGS 84 / UTM zone 18S",
##     BASEGEOGCRS["WGS 84",
##         ENSEMBLE["World Geodetic System 1984 ensemble",
##             MEMBER["World Geodetic System 1984 (Transit)"],
##             MEMBER["World Geodetic System 1984 (G730)"],
##             MEMBER["World Geodetic System 1984 (G873)"],
##             MEMBER["World Geodetic System 1984 (G1150)"],
##             MEMBER["World Geodetic System 1984 (G1674)"],
##             MEMBER["World Geodetic System 1984 (G1762)"],
##             MEMBER["World Geodetic System 1984 (G2139)"],
##             MEMBER["World Geodetic System 1984 (G2296)"],
##             ELLIPSOID["WGS 84",6378137,298.257223563,
##                 LENGTHUNIT["metre",1]],
##             ENSEMBLEACCURACY[2.0]],
##         PRIMEM["Greenwich",0,
##             ANGLEUNIT["degree",0.0174532925199433]],
##         ID["EPSG",4326]],
##     CONVERSION["UTM zone 18S",
##         METHOD["Transverse Mercator",
##             ID["EPSG",9807]],
##         PARAMETER["Latitude of natural origin",0,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8801]],
##         PARAMETER["Longitude of natural origin",-75,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8802]],
##         PARAMETER["Scale factor at natural origin",0.9996,
##             SCALEUNIT["unity",1],
##             ID["EPSG",8805]],
##         PARAMETER["False easting",500000,
##             LENGTHUNIT["metre",1],
##             ID["EPSG",8806]],
##         PARAMETER["False northing",10000000,
##             LENGTHUNIT["metre",1],
##             ID["EPSG",8807]]],
##     CS[Cartesian,2],
##         AXIS["(E)",east,
##             ORDER[1],
##             LENGTHUNIT["metre",1]],
##         AXIS["(N)",north,
##             ORDER[2],
##             LENGTHUNIT["metre",1]],
##     USAGE[
##         SCOPE["Navigation and medium accuracy spatial referencing."],
##         AREA["Between 78°W and 72°W, southern hemisphere between 80°S and equator, onshore and offshore. Argentina. Brazil. Chile. Colombia. Ecuador. Peru."],
##         BBOX[-80,-78,0,-72]],
##     ID["EPSG",32718]]
# Creo una grilla de celdas (hexágonos) de ~1000 m.
# Esta grilla será la "unidad de análisis" para contar delitos.
grilla <- lima_metros %>%
st_make_grid(1000, what="polygons", square = FALSE)
#Grilla completa sin recortes
ggplot()+
geom_sf(data=grilla)

#Filtramos con la grilla
class(grilla)
## [1] "sfc_POLYGON" "sfc"
grilla <- grilla %>%
st_sf()
#filtro de la ciudad
grilla <- st_filter(grilla, lima_metros)
ggplot()+
geom_sf(data=grilla)

#Superpongo puntos sobre la grilla para ver densidad visualmente.
ggplot()+
geom_sf(data=grilla)+
geom_sf(data=delitos_sf, alpha=0.1, size=0.5)

# Transformo los delitos a metros (mismo CRS que la grilla) y
# recorto a Lima por seguridad.
delitos_metros <- st_transform(delitos_sf, 32718)
delitos_metros <- st_filter(delitos_metros, lima_metros)   

# Me aseguro que la grilla tenga un ID único para indexar vecinos/conteos.
grilla <- st_sf(grilla)                          
if (!"ID" %in% names(grilla)) grilla$ID <- seq_len(nrow(grilla))

# Cuenta delitos por hexágono (intersección espacial)
# la idea es contar cuantos delitos caen dentro de cada hexágono usando st_intersects().
# 'idx' es una lista: para cada hex, qué puntos caen dentro; luego usamos lengths().
idx <- st_intersects(grilla, delitos_metros)           
grilla$N_DELITOS <- lengths(idx)                   # conteo
# Mapa que muestra el número de delitos por hexágono (transformación sqrt para contraste).
ggplot() +
  geom_sf(data = grilla, aes(fill = N_DELITOS), color = NA) +
  geom_sf(data = st_boundary(lima_metros), color = "grey30", linewidth = 0.3) +
  scale_fill_viridis_c(trans = "sqrt", na.value = "grey90") +
  labs(title = "Delitos patrimoniales por hexágono (1000 mts) \nLima Metropolitana, 2024",
       fill  = "N° delitos patrimoniales") +
  theme_minimal()

``

#identificamos vecinos, la base para calcular autocorrelaciones espaciales.
vecinos <- poly2nb(pl=grilla,
row.names = grilla$ID)
# número de vecinos por hexágono (como vector)
class(vecinos)
## [1] "nb"
vecinos
## Neighbour list object:
## Number of regions: 3326 
## Number of nonzero links: 19110 
## Percentage nonzero weights: 0.1727493 
## Average number of links: 5.74564
# número de vecinos por hexágono (como vector)
vecinos %>%
card()
##    [1] 3 4 3 2 3 6 4 2 6 3 5 3 4 5 5 4 6 4 4 4 6 4 6 4 6 5 6 5 3 4 6 6 4 6 6 6 5
##   [38] 4 4 6 6 3 6 6 6 6 6 5 6 6 5 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 3 3 6 6 6
##   [75] 6 6 6 6 6 5 4 5 6 6 6 6 6 6 6 6 6 3 6 6 6 6 6 6 6 6 6 6 4 6 6 6 6 6 6 6 6
##  [112] 6 5 3 6 6 6 6 6 6 6 6 6 6 4 4 3 5 6 6 6 6 6 6 6 6 6 6 5 6 1 3 6 6 6 6 6 6
##  [149] 6 6 6 6 6 6 4 2 3 3 6 6 6 6 6 6 6 6 6 6 6 6 6 4 4 5 3 5 6 6 6 6 6 6 6 6 6
##  [186] 6 6 6 5 5 5 6 5 4 5 6 6 6 6 6 6 6 6 6 6 6 6 6 3 3 3 6 6 6 4 4 6 6 6 6 6 6
##  [223] 6 6 6 6 6 6 6 6 6 3 4 6 6 6 6 4 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 6 3 3 5
##  [260] 6 6 6 6 4 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 3 5 6 3 4 4 6 6 6 6 6 4 3 4 6
##  [297] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 3 6 5 6 5 6 6 6 6 6 6 5 5 6 6 6 6 6 6 6 6
##  [334] 6 6 6 6 6 6 6 6 6 5 3 6 6 4 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
##  [371] 6 6 6 6 6 5 4 5 6 6 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
##  [408] 6 6 6 4 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
##  [445] 6 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5
##  [482] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 3 6 6
##  [519] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
##  [556] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 3 5 6 6 6 6 6 6
##  [593] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 4 6 6 6 6 6 6 6 6
##  [630] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 6 6 6 6 6 6 6 6 6 6
##  [667] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 6 6 6 6 6 6 6 6 3 3 3 2 3 6 6 6 6 6 6 6 6
##  [704] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 4 6 6 6 6 6 6 6 4 3 5 5 6 6 6 6 6 6 6
##  [741] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 5 6 6 5 6 6 6 5 6 6 5 3 5 6 6 6 6 6
##  [778] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 4 4 6 6 6 5 6 6 5 5 6 6 6 6 6
##  [815] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 2 3 3 4 6 6 3 3 6 6 6 6 6 6 6 6 6
##  [852] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 3 4 6 5 6 6 6 6 6 6 6 6 6 6 6 6 6
##  [889] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 4 6 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
##  [926] 6 6 6 6 6 6 6 6 6 6 6 6 4 3 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
##  [963] 6 6 6 6 6 6 6 4 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [1000] 6 6 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 6 6 6 6
## [1037] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 6 6 6 6 6 6 6 6 6 6
## [1074] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [1111] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [1148] 6 6 6 6 6 6 6 6 6 3 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [1185] 6 6 6 6 6 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [1222] 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 6 6 6
## [1259] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 6 6 6 6 6 6 6 6
## [1296] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 6 6 6 6 6 6 6 6 6 6 6 6
## [1333] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 3 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [1370] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [1407] 6 6 6 5 6 6 6 6 6 6 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 4
## [1444] 6 6 6 6 6 6 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 6 6 6 6 6
## [1481] 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 4 6 6 6 6 6 5 6 6 6 6
## [1518] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 6 6 6 6 5 6 6 6 6 5 4 6 6 6 6 6 6 6 6 6 6
## [1555] 6 6 6 6 6 6 6 6 6 4 4 5 6 6 5 6 6 6 6 6 4 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [1592] 6 6 6 6 5 3 4 5 6 3 4 6 6 6 6 6 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [1629] 3 5 6 6 5 6 6 6 6 3 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 6 4 3 4 4 6
## [1666] 6 6 6 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 3 6 3 5 6 6 6 4 6 6 6 6 6
## [1703] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 4 3 6 6 6 6 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [1740] 6 6 6 6 6 6 3 5 6 6 6 4 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 3 6 6
## [1777] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 3 6 6 6 6 3 5 6 6 6 6 6 6 6
## [1814] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 6 6 6 4 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [1851] 6 6 6 6 4 6 6 6 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 6 6 6 5 6
## [1888] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 6 6 6 4 3 6 6 6 6 6 6 6 6 6 6 6
## [1925] 6 6 6 6 6 6 6 6 6 6 6 3 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [1962] 6 6 6 6 6 4 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 3 6 6 6 4 6 6
## [1999] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 2 4 6 6 4 4 6 6 6 6 6 6 6 6 6 6
## [2036] 6 6 6 6 6 6 6 6 6 6 6 6 6 5 4 6 6 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2073] 6 6 6 6 6 5 6 4 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 6 6 4
## [2110] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 3 4 6 4 3 6 6 6 6 6 6 6
## [2147] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2184] 6 6 6 6 6 6 6 6 6 6 5 3 6 3 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2221] 6 6 6 6 3 4 5 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 6
## [2258] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 2 2 3 3 2 5 6 6 6 6
## [2295] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 4 5 6 6 6 6 6 6 6 6 6 6
## [2332] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 5 6 6 4 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2369] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 3 5 6 6 4 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2406] 6 6 6 6 6 6 6 6 6 6 6 5 4 5 6 6 4 3 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2443] 6 6 6 6 6 6 6 6 6 6 6 3 2 6 6 6 4 4 4 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2480] 6 6 6 6 6 6 6 6 6 6 6 6 6 4 6 6 6 5 6 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2517] 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2554] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 2 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2591] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2628] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 3 6 5 6 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2665] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 4 4 4 4 3 5 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2702] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 3 3 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2739] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2776] 6 6 6 6 6 6 6 6 6 6 6 5 2 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2813] 6 6 6 6 6 6 6 6 4 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2850] 6 6 6 6 6 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [2887] 6 6 2 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4
## [2924] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 3 6 6 6
## [2961] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 6 6 6 6 6 5 6 6 6 6 6 6 5 5 6 6 6 6 6 6 6
## [2998] 6 6 6 6 6 6 6 6 6 6 4 5 6 6 6 6 4 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
## [3035] 6 6 6 6 5 6 6 6 6 4 5 6 6 6 6 6 4 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 6
## [3072] 6 6 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 3 6 6 6 4 4 6 6 6 6 6
## [3109] 3 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 4 6 6 5 6 6 6 6 6 4 6 6 6 6 6 6 6 6
## [3146] 6 6 6 6 6 6 6 5 5 6 6 6 4 6 6 6 6 4 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 4 4 3 6
## [3183] 6 3 6 6 6 6 5 4 6 5 5 6 6 6 6 6 6 6 6 6 6 4 2 4 5 4 3 6 6 6 6 4 4 3 4 6 6
## [3220] 6 6 6 6 6 6 6 5 3 3 5 6 6 6 3 3 4 6 6 6 6 6 6 6 6 6 6 6 6 4 5 6 6 6 6 6 6
## [3257] 6 6 3 3 6 6 4 6 6 6 6 6 6 6 6 4 5 6 4 3 5 6 6 6 6 6 6 4 6 5 3 5 6 6 6 6 6
## [3294] 4 3 5 6 6 6 6 6 4 3 2 3 6 6 6 6 4 4 6 6 6 4 4 6 6 5 4 5 6 3 4 3 3
# Se agrega a la grilla la cantidad de vecinos para visualizar variación

grilla <- grilla %>%
mutate(N_VECINOS=card(vecinos))
ggplot()+
geom_sf(data=grilla, aes(fill=N_VECINOS))

# Mapa del número de vecinos
ggplot()+
geom_sf(data=grilla, aes(fill=N_VECINOS))+
scale_fill_viridis_c(direction=-1)

MATRIZ DE PESOS ESPACIALES

# Se crea la matriz de pesos espaciales (estilo W: cada fila suma 1).
# zero.policy=TRUE evita errores cuando alguna celda no tiene vecinos.

pesos_vecinos <- nb2listw(vecinos,
                          style="W",
                          zero.policy=TRUE)

INDICADOR GLOBAL MORAN

# Se reemplaza NAs en N_DELITOS por 0 (hexágonos sin delitos).

grilla <- grilla %>%
mutate(N_DELITOS=if_else(is.na(N_DELITOS), 0, N_DELITOS))
# Valor de I: >0 indica clustering positivo (zonas similares se agrupan)
i_moran <- moran(x=grilla$N_DELITOS,
listw=pesos_vecinos,
n=nrow(grilla),
S0=Szero(pesos_vecinos))
i_moran$I
## [1] 0.2157629

El resultado de i_moran (0.2157629) dice que hay autocrrelacion espacial positiva, xq es I>0, entonces cuando un delito ocurre en una zona es posible que ocurra en sus alrededores. Los delitos en Lima no están repartidos al azar, no es un fenómeno aislado, sino territorialmente conectado.


Prueba de hipotesis

#Opcion de p-value con el moran test

moran_test <- moran.test(x=grilla$N_DELITOS,
listw=pesos_vecinos)
#Para reducir los digitos del p-value
options(scipen=99)
#si  p-value: si <0.05, evidencia estadística de autocorrelación espacial
moran_test
## 
##  Moran I test under randomisation
## 
## data:  grilla$N_DELITOS  
## weights: pesos_vecinos    
## 
## Moran I statistic standard deviate = 22.907, p-value <
## 0.00000000000000022
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic       Expectation          Variance 
##     0.21576287122    -0.00030075188     0.00008896932

Como el p-value es 0.00000000022 es menor a 0.05 me dice q rechazo la H0 hay una autocorrelacion espacial positiva entre los datos analizados. POr tanto, confirma que la distribución de delitos no es aleatoria y se comprueba estadísticamente la existencia de hotspots delictivos en Lima.


#Especie de Regresion
#Gráfico: N_DELITOS vs. su rezago espacial (promedio de vecinos).

moran.plot(x=grilla$N_DELITOS,
      listw = pesos_vecinos,
      zero.policy=TRUE)


El Moran Scatterplot confirma la relación espacial positiva entre los valores de delito y sus promedios vecinales. La pendiente ascendente indica que las zonas delictivas no están aisladas, sino que tienden a agruparse. La presencia de algunos outliers de alta intensidad también señala áreas críticas con valores fuera de lo normal,que ameritan análisis puntual.


grilla <- grilla %>%
mutate(lag_DELITOS=lag.listw(pesos_vecinos, N_DELITOS))
ggplot()+
geom_point(data=grilla, aes(x=N_DELITOS, y=lag_DELITOS))

ggplot()+
geom_point(data=grilla, aes(x=N_DELITOS, y=lag_DELITOS))+
geom_smooth(data=grilla, aes(x=N_DELITOS, y=lag_DELITOS), method="lm")
## `geom_smooth()` using formula = 'y ~ x'


La pendientes est positivo, indica que Si un sector tiene muchos delitos, sus alrededores también suelen tener muchos. Y si un sector es tranquilo, sus vecinos también lo son.


# Calculamos LISA para identificar "dónde" hay clustering y de qué tipo (HH, LL, HL, LH).
# La idea es buscar los puntos locales, las zonas calientes y zonas frías
moran_local <- localmoran(x=grilla$N_DELITOS,
listw = pesos_vecinos)
nrow(grilla)
## [1] 3326
#Pegar los resultados de LISA a la grilla y renombramos p-value para claridad.
grilla <- grilla %>%
cbind(moran_local)
#rename para que aparezca p-value
grilla <- grilla %>%
rename(p_valor=Pr.z....E.Ii..)
#Exploro Cuántas celdas son estadísticamente significativas (p<0.05)
grilla %>%
filter(p_valor<0.05) %>%
count()

Entonces 198 grillas son significativas, solo el 6% de los hexágonos de Lima Metropolitana muestran un patrón espacial significativo de delitos.


# Mapa binario de significancia
ggplot()+
geom_sf(data=grilla %>%
mutate(significativo=if_else(p_valor<0.05, "Si", "No")), aes(fill=significativo))

# Se normaliza las variables para clasificar cuadrantes (alto/bajo contra vecinos).
# Si en la turquesa es significativo me dice donde es mas peligroso
#Primero tenemos que normalizar la información, para definir que números son hot, frios...

grilla <- grilla %>%
mutate(z_DELITOS= (N_DELITOS-mean(N_DELITOS))/sd(N_DELITOS),
z_lag_DELITOS= (lag_DELITOS-mean(lag_DELITOS))/sd(lag_DELITOS))
# Se asigna al cuadrante LISA clásico (solo significado, la significancia se filtra al graficar)
grilla <- grilla %>%
mutate(cuadrante=case_when(z_DELITOS>0 & z_lag_DELITOS>0 ~ "H-H: Alto-Alto",
z_DELITOS<0 & z_lag_DELITOS<0 ~ "L-L: Bajo-Bajo",
z_DELITOS>0 & z_lag_DELITOS<0 ~ "H-L: Alto-Bajo",
z_DELITOS<0 & z_lag_DELITOS>0 ~ "L-H: Bajo-Alto"))
# Mapas LISA: sólo las celdas significativas (p<0.05) y coloreadas por cuadrante.
ggplot()+
geom_sf(data=grilla %>% filter(p_valor<0.05), aes(fill=cuadrante))

El análisis LISA identifica dos tipos de patrones espaciales significativos en la distribución del delito. Los clústeres H-H (alto–alto) representan las zonas críticas donde el delito es elevado tanto en la celda como en sus vecinas, configurando verdaderos hotspots. Por otro lado, los clústeres L-H (bajo–alto) corresponden a zonas de baja delincuencia interna, pero rodeadas de áreas altamente delictivas, lo que sugiere espacios en riesgo de expansión del fenómeno. No se identificaron clústeres L-L ni H-L significativos.


ggplot()+
geom_sf(data=grilla) +
geom_sf(data=grilla %>% filter(p_valor<0.05), aes(fill=cuadrante))

ggplot()+
geom_sf(data=grilla) +
geom_sf(data=grilla %>% filter(p_valor<0.05), aes(fill=cuadrante))+
scale_fill_manual(values=c("H-H: Alto-Alto"="red",
                        "H-L: Alto-Bajo"="pink",
                        "L-L: Bajo-Bajo"="blue",
                        "L-H: Bajo-Alto"="lightskyblue"))

grilla %>%
st_transform(4326) %>%
filter(p_valor<0.05) %>%
leaflet() %>%
addTiles() %>%
addPolygons(
fillColor = ~case_when(
cuadrante == "H-H: Alto-Alto" ~ "red",
cuadrante == "H-L: Alto-Bajo" ~ "pink",
cuadrante == "L-L: Bajo-Bajo" ~ "blue",
cuadrante == "L-H: Bajo-Alto" ~ "lightskyblue",
cuadrante == "No Significativo" ~ "white",
TRUE ~ "grey80"),
color = "#333333",
weight = 0.3,
fillOpacity = 0.5,
popup = ~paste0("<b>Cuadrante:</b> ", cuadrante)) %>%
addLegend(
"bottomright",
colors = c("red", "pink", "blue", "lightskyblue", "white"),
labels = c("H-H: Alto-Alto", "H-L: Alto-Bajo", "L-L: Bajo-Bajo",
"L-H: Bajo-Alto", "No Significativo"),
title = "Clúster LISA") %>%
addMiniMap()

En el mapa, lo rojo es donde el delito es alto y además se retroalimenta con sus alrededores: son los verdaderos hotspots. Lo celeste es donde el delito todavía es bajo, pero está pegado a zonas peligrosas. Son zonas en riesgo. No todo Lima es hotspot: solo algunas partes muestran estructura delincuencial real.

El análisis espacial realizado demuestra que la distribución de los delitos patrimoniales en Lima Metropolitana no es aleatoria, sino que responde a una lógica territorial clara. El índice de Moran Global confirma una autocorrelación espacial positiva, lo que significa que los delitos tienden a agruparse: cuando un sector presenta alta incidencia delictiva, sus áreas vecinas suelen mostrar niveles similares. Este patrón se refuerza con el análisis LISA, que identifica que, aunque solo el 6% de los hexágonos presenta significancia estadística, esos pocos sectores estructuran el comportamiento delictivo en la ciudad. Los clústeres Alto–Alto representan los verdaderos hotspots, es decir, zonas donde la criminalidad es alta tanto dentro del hexágono como en su entorno inmediato, mientras que los clústeres Bajo–Alto revelan áreas actualmente tranquilas, pero rodeadas de sectores altamente delictivos, lo que las convierte en zonas de riesgo potencial de expansión delictiva.

El 6% de hexágonos significativos refleja que el delito patrimonial en Lima es altamente focalizado y no se distribuye de forma homogénea en el territorio. Ese porcentaje identifica los verdaderos puntos críticos donde el crimen es persistente y espacialmente conectado. Para elevarlo, podría ajustarse la escala del análisis (hexágonos más pequeños), normalizar los datos o incorporar métodos alternativos como KDE o clustering basado en puntos (DBSCAN o HDSCAN).


2 Accesibilidad territorial a comisarías y desigualdad institucional


Aquí se evalúa la distribución espacial de la infraestructura policial y su nivel de alineación con las necesidades del territorio. Mediante el análisis de distancias mínimas a comisarías y su autocorrelación espacial, se identifican áreas bien atendidas, sectores con sobrecobertura y, especialmente, zonas donde el Estado está ausente de forma sistemática. El objetivo es evidenciar si el acceso a la policía es equitativo en la ciudad o si existen brechas territoriales que comprometen la capacidad de respuesta frente al delito.
# Relacionar delitos con accesibilidad a la infraestructura policial.
comisarias <- read_excel("C:/_ALIDE/Documentos/data_ciencia/MPP-CDD-02/data/comisaria_lima.xlsx")
# quitar filas sin coordenadas
comisarias <- comisarias[!is.na(comisarias$longitud) & !is.na(comisarias$latitud), ]

#Llevado a metros llevar a metros y recortar a Lima, igual a lo hecho con delitos.
comisarias <- st_as_sf(comisarias, coords = c("longitud", "latitud"), crs = 4326)
comisarias_metros <- st_transform(comisarias, 32718)

# por seguridad, recortar al polígono de Lima
comisarias_metros <- st_filter(comisarias_metros, lima_metros)
# distancia mínima a comisaría más cercana por hexágono.
# Usamos el centroide del hexágono como referencia.
cent_hex <- st_centroid(grilla)
## Warning: st_centroid assumes attributes are constant over geometries
dist_mat <- st_distance(cent_hex, comisarias_metros)

# si no hay comisarías dentro, evitar error
if (ncol(dist_mat) == 0) stop("No hay comisarías dentro del área de Lima. Revisa la base comisaria_lima.xlsx.")

# Guardamos la distancia mínima (en metros) por hexágono.
grilla$DIST_COMISARIA_M <- apply(dist_mat, 1, min)
summary(grilla$DIST_COMISARIA_M)
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
##    38.67  1914.84  4165.65  6427.21  9424.64 25921.60
# Moran global sobre la variable de distancia a comisaría.


moran_dist <- moran.test(x = grilla$DIST_COMISARIA_M,
                         listw = pesos_vecinos,
                         zero.policy = TRUE)
# Verifica si hay patrón espacial en accesibilidad policial
moran_dist 
## 
##  Moran I test under randomisation
## 
## data:  grilla$DIST_COMISARIA_M  
## weights: pesos_vecinos    
## 
## Moran I statistic standard deviate = 96.238, p-value <
## 0.00000000000000022
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic       Expectation          Variance 
##      0.9931590509     -0.0003007519      0.0001065639

El valor del índice de Moran Global para la distancia a comisarías es I = 0.993 (p < 0.001), lo que confirma una autocorrelación espacial positiva muy fuerte. En términos prácticos, esto significa que la lejanía a las comisarías no ocurre de forma aislada, sino que se concentra en áreas completas de la ciudad.

En Lima Metropolitana existen zonas enteras donde varios sectores están lejos de una comisaría al mismo tiempo, lo que evidencia una distribución desigual de la cobertura policial. La ubicación de las comisarías no sigue un patrón uniforme, sino que muestra una estructura territorial clara, con áreas bien atendidas y otras con evidente déficit de acceso.


# para distancia a comisarías (identifica clústeres de alta/baja distancia).

moran_local_dist <- localmoran(x = grilla$DIST_COMISARIA_M,
                               listw = pesos_vecinos,
                               zero.policy = TRUE)
# se pega resultados y calculamos z-scores para clasificar cuadrantes.
grilla$Ii_dist <- moran_local_dist[, "Ii"]
grilla$Z_Ii_d  <- moran_local_dist[, "Z.Ii"]
grilla$p_dist  <- moran_local_dist[, "Pr(z != E(Ii))"]

lag_dist <- lag.listw(pesos_vecinos, grilla$DIST_COMISARIA_M, zero.policy = TRUE)

z_x  <- (grilla$DIST_COMISARIA_M - mean(grilla$DIST_COMISARIA_M, na.rm = TRUE)) /
         sd(grilla$DIST_COMISARIA_M, na.rm = TRUE)
z_lx <- (lag_dist - mean(lag_dist, na.rm = TRUE)) /
         sd(lag_dist, na.rm = TRUE)

grilla$cuadrante_dist <- ifelse(grilla$p_dist < 0.05,
                         ifelse(z_x > 0 & z_lx > 0, "HH: Distancia alta",
                         ifelse(z_x < 0 & z_lx < 0, "LL: Distancia baja",
                         ifelse(z_x > 0 & z_lx < 0, "HL: Alta-Bajas vecinas",
                         ifelse(z_x < 0 & z_lx > 0, "LH: Baja-Altas vecinas", NA)))),
                         "No sig.")
#
# MAPA:  distancia mínima a comisaría (sqrt para mejorar contraste).

ggplot() +
  geom_sf(data = grilla, aes(fill = DIST_COMISARIA_M), color = NA) +
  geom_sf(data = st_boundary(lima_metros), color = "grey10", linewidth = 0.4) +
  scale_fill_gradientn(
    colours = c("#ffffcc", "#ffcc00", "#ff6600", "#cc0000", "#330000"),
    trans = "sqrt",
    name = "Dist. a comisaría (m)"
  ) +
  labs(title = "Distancia mínima a comisaría — Zonas críticas en rojo oscuro") +
  theme_minimal()


Aquí se observa claramente dónde la policía está lejos. Las zonas en rojo oscuro no son puntos sueltos: son áreas completas donde no hay una comisaría cerca.


# MAPA: clústeres LISA de distancia (dónde hay déficit/exceso relativo de proximidad).

ggplot() +
  geom_sf(data = grilla, aes(fill = cuadrante_dist), color = NA) +
  geom_sf(data = st_boundary(lima_metros), color = "grey30", linewidth = 0.3) +
  scale_fill_manual(values = c("HH: Distancia alta"       = "red",
                               "LL: Distancia baja"       = "blue",
                               "HL: Alta-Bajas vecinas"   = "pink",
                               "LH: Baja-Altas vecinas"   = "lightskyblue",
                               "No sig."                  = "grey90"),
                    drop = FALSE, name = "LISA distancia (p<0.05)") +
  labs(title = "Clústeres LISA — Distancia a comisarías (hexágonos 1 km)") +
  theme_minimal()


Este mapa demuestra que el acceso a comisarías no es equitativo en Lima. Existen clústeres de abandono policial (rojo) y clústeres de sobrecobertura o alta densidad institucional (azul). Eso confirma lo que el Moran Global ya anticipaba: el déficit de presencia policial es espacialmente estructural, no aleatorio.


# Porcentaje de hexágonos donde la LISA fue significativa (p<0.05).

prop_sig_dist <- mean(grilla$p_dist < 0.05, na.rm = TRUE)
prop_sig_dist
## [1] 0.4046903

En el 40% del territorio de Lima la accesibilidad policial presenta una estructura espacial clara y significativa. No es una distribución al azar: hay bloques de barrios bien cubiertos y otros con déficit institucional acumulado.


grilla %>%
  st_transform(4326) %>%
  filter(p_dist < 0.05) %>%   # solo clústeres significativos
  leaflet() %>%
  addTiles() %>%
  addPolygons(
    fillColor = ~case_when(
      cuadrante_dist == "HH: Distancia alta"            ~ "darkred",
      cuadrante_dist == "LL: Distancia baja"            ~ "darkgreen",
      cuadrante_dist == "HL: Alta-Bajas vecinas"        ~ "orange",
      cuadrante_dist == "LH: Baja-Altas vecinas"        ~ "lightblue",
      cuadrante_dist == "No sig."                       ~ "white",
      TRUE ~ "grey80"
    ),
    color = "#333333",
    weight = 0.3,
    fillOpacity = 0.6,
    popup = ~paste0(
      "<b>Clúster:</b> ", cuadrante_dist, "<br>",
      "<b>Dist. mín. a comisaría (m):</b> ", round(DIST_COMISARIA_M, 1)
    )
  ) %>%
  addLegend(
    "bottomright",
    colors = c("darkred", "orange", "darkgreen", "lightblue", "white"),
    labels = c(
      "HH: Distancia alta",
      "HL: Alta - Vecinas bajas",
      "LL: Distancia baja",
      "LH: Baja - Vecinas altas",
      "No significativo"
    ),
    title = "Clúster LISA (distancia)"
  ) %>%
  addMiniMap()

La accesibilidad policial en Lima es territorialmente desigual, hay fuerte presencia en el centro urbano, déficit profundo en periferias. Lo verde muestra los sectores de la ciudad bien cubiertos por comisarías. Lo rojo oscuro muestra zonas enteras donde no hay ninguna comisaria cerca.

El análisis de accesibilidad revela un patrón aún más marcado de desigualdad territorial. El Moran Global alcanza un valor cercano a 1, lo que evidencia que la distancia a la infraestructura policial tampoco es aleatoria, sino fuertemente estructurada en el territorio. Existen áreas completas, especialmente en periferias, donde varios sectores están simultáneamente lejos de una comisaría, mientras que el centro urbano concentra sobrecobertura policial. En total, el 40% del territorio muestra una estructura espacial significativa de acceso desigual a la policía, lo que confirma la existencia de brechas institucionales persistentes.