En los últimos meses, la inseguridad ciudadana en el Perú ha ido en aumento.
Quise explorar una manera simple y accesible de visualizar esta problemática usando datos abiertos y herramientas de análisis de datos.
Este es un pequeño proyecto motivado por un dataset de denuncias policiales publicado por la Policía Nacional del Perú en la plataforma oficial de datos abiertos del Estado Peruano.
Dataset utilizado: Denuncias Policiales - Enero 2018 a Junio 2025
(También lo puedes encontrar buscando "Denuncia" en datosabiertos.gob.pe)
El dataset incluye (al corte de agosto 2025) modalidades como:
- Estafa
- Extorsión
- Homicidio
- Hurto
- Robo
- Violencia contra la mujer
- Otros
Link de la app -> https://denuncias-pnp.streamlit.app/
🎯 Motivación
La idea nació como una forma de:
- Analizar cómo han evolucionado las denuncias en el tiempo.
- Ubicarlas en un mapa para identificar zonas con mayor incidencia.
- Darle un formato visual e interactivo, aprovechando herramientas de código abierto.
🛠 Herramientas utilizadas
- Streamlit → Framework en Python que permite crear aplicaciones web interactivas para análisis de datos de forma rápida y sencilla.
- Pandas → Librería para manipulación y análisis de datos en Python. Ideal para trabajar con tablas, filtrar y agrupar datos.
- Folium → Librería para generar mapas interactivos en Python usando Leaflet.js.
🌍 Visualización en mapa
La parte más llamativa del proyecto es la representación geográfica de las denuncias.
A partir de los datos filtrados por año y tipo de modalidad, se agrupan las denuncias por distrito y se dibujan círculos proporcionales a la cantidad de casos.
# --- Mapa por distrito ---
df_map_filt = df[df["ANIO"] == year]
if modalidad != "Todas":
df_map_filt = df_map_filt[df_map_filt["P_MODALIDADES"] == modalidad]
df_map = (
df_map_filt.groupby("UBIGEO_HECHO", as_index=False)["cantidad"].sum()
.merge(
df_ubigeo[["inei", "distrito", "latitude", "longitude"]],
left_on="UBIGEO_HECHO", right_on="inei", how="left"
)
.dropna(subset=["latitude", "longitude"])
)
st.subheader("🌍 Mapa por distrito")
m = folium.Map(location=[-9.19, -75.02], zoom_start=5)
max_c = df_map["cantidad"].max()
for _, r in df_map.iterrows():
radius = 3 + (r["cantidad"] / max_c) * 15 if max_c > 0 else 3
folium.CircleMarker(
location=[r["latitude"], r["longitude"]],
radius=radius,
color="crimson",
fill=True,
fill_opacity=0.6,
popup=f"{r['distrito']}: {r['cantidad']}"
).add_to(m)
st_folium(m, width=800, height=600)
Qué hace este código:
- Filtra las denuncias por año y modalidad.
- Agrupa por distrito (usando el código UBIGEO) y suma la cantidad de casos.
- Une la información con coordenadas geográficas para cada distrito.
- Crea un mapa con círculos rojos cuyo tamaño es proporcional a la cantidad de denuncias.
- Muestra un popup con el nombre del distrito y el número de denuncias.
📈 Gráfico de tendencia por tipo de denuncia
Además del mapa, la aplicación muestra un gráfico de tendencia donde se observa la evolución de cada tipo de denuncia a lo largo de los años, desglosado por departamento.
Esto permite identificar patrones, por ejemplo, si ciertos delitos han ido aumentando o disminuyendo con el tiempo.
📦 Código fuente
El código completo y los datos procesados están disponibles en mi repositorio de GitHub:
🔗 https://github.com/ckomiya/data_science_denuncias_pnp



Top comments (0)