Algunos datos sobre Incidencia delictiva en México
Introducción
El análisis de la criminalidad constituye una herramienta fundamental para comprender la dinámica de los delitos y su distribución en el territorio. En México, la diversidad social, económica y demográfica entre las entidades federativas genera patrones diferenciados en la incidencia delictiva, lo que hace necesario estudiar los datos desde una perspectiva comparativa y multidimensional.
En este trabajo se presentan distintas visualizaciones que permiten explorar la distribución de varios delitos, como robo, narcomenudeo, feminicidio y homicidio, así como su relación con variables demográficas y su evolución a lo largo del tiempo. A través de gráficos comparativos, diagramas de dispersión y mapas de calor, se busca identificar tendencias, concentraciones regionales y diferencias respecto al promedio nacional.
El objetivo de este análisis es ofrecer una visión descriptiva y exploratoria de los patrones delictivos en el país, proporcionando elementos que faciliten la interpretación de los datos.
import pandas as pd
import numpy as np
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from numpy.linalg import norm
from dbconnection import MySQLDatabase
sns.set_theme()query = f"""
WITH delitos AS (
SELECT
td.Anio,
td.CVE_ENT,
gm.NOM_ENT,
td.Tipo_delito,
gm.POB_TOTAL AS POB_TOTAL,
td.Total AS total
FROM (
SELECT Anio, CVE_ENT, Tipo_delito, SUM(Total) AS Total
FROM total_delitos
GROUP BY Anio,
CVE_ENT,
Tipo_delito) td
LEFT JOIN (
SELECT CVE_ENT, NOM_ENT, SUM(POB_TOTAL) AS POB_TOTAL
FROM geo_mun
GROUP BY CVE_ENT,
NOM_ENT
) gm
ON td.CVE_ENT = gm.CVE_ENT
WHERE td.Anio = '{anio}'
)
SELECT
Anio,
CVE_ENT,
NOM_ENT,
Tipo_delito,
POB_TOTAL,
total as total_delitos,
(total * 100000.0 / POB_TOTAL) AS tasa_por_100k
FROM delitos
ORDER BY
Anio,
total DESC;
"""
df = db.execute_query(query)## ✅ Conexión exitosa
## Shape: (1280, 7)
## Anio CVE_ENT NOM_ENT ... POB_TOTAL total_delitos tasa_por_100k
## 0 2024 15 México ... 16992418 122206 719.17958
## 1 2024 09 Ciudad de México ... 9209944 67903 737.27918
## 2 2024 15 México ... 16992418 66670 392.35146
## 3 2024 15 México ... 16992418 60713 357.29465
## 4 2024 14 Jalisco ... 8348498 38459 460.66969
##
## [5 rows x 7 columns]
def barplot(delito, col, title):
# Filtramos el delito que queremos analizar
#delito = 'Homicidio'
df_delito = df[df['Tipo_delito'] == delito].sort_values(col, ascending=False)
# Calculamos promedio nacional
promedio_nacional = df_delito[col].mean()
# Colores arriba o abajo del promedio
df_delito['color'] = df_delito[col].apply(lambda x: 'red' if x > promedio_nacional else 'green')
# Gráfico
plt.figure(figsize=(14, 5))
bars = plt.bar(df_delito['NOM_ENT'], df_delito[col], color=df_delito['color'])
plt.axhline(promedio_nacional, color='blue', linestyle='--', label=f'Promedio nacional: {promedio_nacional:.0f}')
plt.xticks(rotation=35, ha='right', fontsize=9)
# Agregar total encima de cada barra
for bar in bars:
height = bar.get_height()
plt.text(
bar.get_x() + bar.get_width() / 2,
height,
f'{int(height):,}',
ha='center',
va='bottom',
fontsize=7
)
plt.title(title)
plt.xlabel('Estado', fontsize=16)
plt.ylabel('Total de delitos', fontsize=16)
plt.legend()
#plt.show()
return pltHomicidios
En el extremo izquierdo del gráfico se observan los estados con mayor incidencia absoluta de homicidios:Guanajuato, Estado de México, Baja California, Chihuahua, Jalisco. Estos estados presentan valores muy superiores al promedio nacional, algunos más del doble. Esto sugiere que la violencia homicida en el país no está distribuida uniformemente, sino que se concentra en ciertos territorios específicos.En particular, el caso de Guanajuato destaca notablemente, con el valor más alto del gráfico, lo que coincide con tendencias recientes donde esta entidad ha sido uno de los principales focos de violencia relacionada con disputas entre grupos criminales y control territorial.
En la zona media del gráfico aparecen estados que superan el promedio nacional pero con menor intensidad, por ejemplo: Ciudad de México, Sonora, Sinaloa, Michoacán. Estos casos indican niveles relevantes de violencia, pero no tan extremos como los del primer grupo. En muchos casos son entidades con alta población o con dinámicas urbanas y delictivas complejas.
En la parte derecha del gráfico se encuentran los estados con valores considerablemente inferiores al promedio nacional, por ejemplo: Yucatán, Campeche, Baja California Sur, Durango, Tlaxcala. Estos estados presentan niveles relativamente bajos de homicidio, lo que sugiere contextos de mayor estabilidad o menor presencia de dinámicas violentas comparados con otras regiones.
delito = 'Homicidio'
col= 'total_delitos'
title = f'Total {delito} por estado comparado con promedio nacional'
# gráfico
plt = barplot(delito, col, title)
plt.show()delito = 'Homicidio'
col= 'tasa_por_100k'
title = f'Tasa {delito} por estado comparado con promedio nacional'
# gráfico
plt = barplot(delito, col, title)
plt.show()Robo
El siguiente gráfico muestra el total de robos por estado comparado con el promedio nacional (16,850 delitos). Se observa una fuerte concentración del delito en pocas entidades, destacando especialmente el Estado de México, que supera ampliamente al resto con más de 120 mil robos, situándose muy por encima del promedio nacional. En segundo lugar aparece Ciudad de México, también con un nivel considerablemente alto, seguido por Jalisco, Puebla y Guanajuato, todos ellos claramente por encima del promedio.
Las barras en rojo representan los estados con valores superiores al promedio nacional, lo que indica focos importantes de incidencia delictiva. En contraste, la mayoría de las entidades aparecen en verde, mostrando niveles de robo por debajo del promedio, lo que sugiere una distribución desigual del delito en el país.
Además, el gráfico evidencia que solo un pequeño grupo de estados concentra gran parte de los robos, mientras que el resto presenta cifras relativamente bajas. Esta asimetría sugiere que los fenómenos delictivos pueden estar asociados a factores urbanos, densidad poblacional y actividad económica, particularmente en grandes zonas metropolitanas.
delito = 'Robo'
col= 'total_delitos'
title = f'Total {delito} por estado comparado con promedio nacional'
# gráfico
plt = barplot(delito, col, title)
plt.show()delito = 'Robo'
col= 'tasa_por_100k'
title = f'Tasa {delito} por estado comparado con promedio nacional'
# gráfico
plt = barplot(delito, col, title)
plt.show()Feminicidio
El siguiente gráfico presenta el total de feminicidios por estado en comparación con el promedio nacional (26 casos). Se observa que Estado de México encabeza la lista con 73 casos, seguido por Nuevo León y Ciudad de México, lo que evidencia una mayor concentración del delito en estas entidades. También destacan Veracruz, Chihuahua y Puebla, todos con cifras claramente por encima del promedio nacional. A diferencia de otros delitos, la distribución aquí es menos concentrada, ya que varios estados se sitúan relativamente cerca del promedio.
Asimismo, el gráfico sugiere que el fenómeno está presente en diversas regiones del país, aunque con mayor intensidad en algunas entidades con mayor población o contextos de violencia de género más visibles. En conjunto, la visualización permite identificar estados prioritarios para el diseño de políticas públicas enfocadas en la prevención de la violencia contra las mujeres y el fortalecimiento de los mecanismos de protección y justicia.
delito = 'Feminicidio'
col= 'total_delitos'
title = f'Total {delito} por estado comparado con promedio nacional'
# gráfico
plt = barplot(delito, col, title)
plt.show()delito = 'Feminicidio'
col= 'tasa_por_100k'
title = f'Tasa {delito} por estado comparado con promedio nacional'
# gráfico
plt = barplot(delito, col, title)
plt.show()Población vs Total Homicidios
El gráfico abajo muestra la relación entre la población total y el número de homicidios por estado en \(2024\), mediante un diagrama de dispersión. En general, se observa una tendencia positiva moderada, lo que sugiere que los estados con mayor población tienden a registrar un mayor número absoluto de homicidios.
Destaca Estado de México, que aparece como el estado con mayor población y un alto número de homicidios, lo cual es consistente con su tamaño demográfico. Sin embargo, Guanajuato sobresale como un caso atípico, ya que presenta uno de los mayores números de homicidios a pesar de tener una población menor que la de otras entidades grandes.
Asimismo, estados como Baja California, Michoacán y Jalisco muestran niveles relativamente altos de homicidios en comparación con su población. Por otro lado, entidades con menor población, como Colima, Nayarit o Campeche, registran menos homicidios en términos absolutos, aunque esto no necesariamente implica menores tasas relativas.
En conjunto, el gráfico sugiere que la población explica parcialmente la cantidad de homicidios, pero también evidencia que factores regionales, sociales y de seguridad influyen significativamente en la variación entre estados.
# delito
delito = 'Homicidio'
# limpiar nulos
df_plot = df.dropna(subset=['POB_TOTAL', 'total_delitos', 'NOM_ENT']).copy()
# filtro tipo delito
df_plot = df_plot[df_plot['Tipo_delito'] == delito].sort_values(col, ascending=False)
# asegurar tipos numéricos
df_plot['POB_TOTAL'] = pd.to_numeric(df_plot['POB_TOTAL'])
df_plot['total_delitos'] = pd.to_numeric(df_plot['total_delitos'])
x = df_plot['POB_TOTAL']
y = df_plot['total_delitos']
plt.figure(figsize=(10,5))
plt.scatter(x, y)
# agregar etiquetas con pequeño desplazamiento
for i in range(len(df_plot)):
plt.annotate(
df_plot['NOM_ENT'].iloc[i],
(x.iloc[i], y.iloc[i]),
xytext=(5,5),
textcoords='offset points',
fontsize=8
)
plt.xlabel('Población Total')
plt.ylabel('Total de Delitos')
plt.title(f'Población vs Total de Homicidios por Estado \n({anio})')
plt.tight_layout()
plt.show()Delitos más frecuentes a nivel nacional
query = f"""
WITH delitos AS (
SELECT
td.Anio,
td.CVE_ENT,
gm.NOM_ENT,
td.Tipo_delito,
gm.POB_TOTAL AS POB_TOTAL,
td.Total AS total
FROM (
SELECT Anio, CVE_ENT, Tipo_delito, SUM(Total) AS Total
FROM total_delitos
GROUP BY Anio,
CVE_ENT,
Tipo_delito) td
LEFT JOIN (
SELECT CVE_ENT, NOM_ENT,SUM(POB_TOTAL) AS POB_TOTAL
FROM geo_mun
GROUP BY CVE_ENT,
NOM_ENT
) gm
ON td.CVE_ENT = gm.CVE_ENT
-- WHERE td.Anio = '{anio}'
)
SELECT
Anio,
Tipo_delito,
SUM(POB_TOTAL) AS pob_total,
SUM(total) as total_delitos
FROM delitos
GROUP BY Anio, Tipo_delito
ORDER BY
Anio,
total_delitos DESC;
"""
df = db.execute_query(query)
print(f"Shape: {df.shape}")## Shape: (440, 4)
df_pivot = df.pivot_table(
index='Tipo_delito',
columns='Anio',
values='total_delitos',
aggfunc='sum'
)
df_pivot = df_pivot.sort_values(2015, ascending=False)
df_pivot = df_pivot.apply(pd.to_numeric, errors='coerce')El heatmap presenta la evolución del total de delitos por tipo entre \(2015\) y \(2025\), donde la intensidad del color verde indica un mayor número de registros. Se observa que el robo es, con gran diferencia, el delito más frecuente durante todo el periodo analizado, manteniendo niveles muy superiores al resto de categorías.
En un segundo nivel aparecen delitos como lesiones, otros delitos del fuero común, violencia familiar y daño a la propiedad, que también muestran una alta incidencia relativa a lo largo de los años. Estos delitos presentan una intensidad de color considerable, lo que sugiere que forman parte importante de la estructura general de la criminalidad.
Por otro lado, delitos como fraude, narcomenudeo y homicidio muestran niveles intermedios, con presencia constante pero menor volumen comparado con los delitos más frecuentes. En contraste, delitos como feminicidio, trata de personas, rapto o tráfico de menores presentan valores mucho más bajos, reflejados por colores más claros en el mapa.
En términos temporales, el gráfico permite observar cierta estabilidad en la estructura de los delitos, ya que las categorías con mayor incidencia permanecen relativamente constantes a lo largo de los años. En conjunto, el heatmap evidencia que la criminalidad se concentra principalmente en un grupo reducido de delitos de alta frecuencia, mientras que el resto ocurre con mucha menor incidencia.
# Convertimos a matriz numérica
data = df_pivot.values
plt.figure(figsize=(12, 8))
plt.imshow(data, aspect='auto', cmap='Greens')
plt.colorbar(label='Total delitos')## <matplotlib.colorbar.Colorbar object at 0x0000016D5804EFF0>
# Etiquetas eje X (años)
plt.xticks(
ticks=np.arange(len(df_pivot.columns)),
labels=df_pivot.columns,
rotation=45
)## ([<matplotlib.axis.XTick object at 0x0000016D5A0E6FC0>, <matplotlib.axis.XTick object at 0x0000016D5A28A5A0>, <matplotlib.axis.XTick object at 0x0000016D54E57860>, <matplotlib.axis.XTick object at 0x0000016D58094B90>, <matplotlib.axis.XTick object at 0x0000016D5802C6B0>, <matplotlib.axis.XTick object at 0x0000016D5802E4E0>, <matplotlib.axis.XTick object at 0x0000016D5802F350>, <matplotlib.axis.XTick object at 0x0000016D5802C1D0>, <matplotlib.axis.XTick object at 0x0000016D58000E90>, <matplotlib.axis.XTick object at 0x0000016D58003A10>, <matplotlib.axis.XTick object at 0x0000016D5802EA50>], [Text(0, 0, '2015'), Text(1, 0, '2016'), Text(2, 0, '2017'), Text(3, 0, '2018'), Text(4, 0, '2019'), Text(5, 0, '2020'), Text(6, 0, '2021'), Text(7, 0, '2022'), Text(8, 0, '2023'), Text(9, 0, '2024'), Text(10, 0, '2025')])
# Etiquetas eje Y (tipo delito)
plt.yticks(
ticks=np.arange(len(df_pivot.index)),
labels=df_pivot.index
)## ([<matplotlib.axis.YTick object at 0x0000016D54FEED20>, <matplotlib.axis.YTick object at 0x0000016D58094F80>, <matplotlib.axis.YTick object at 0x0000016D58076360>, <matplotlib.axis.YTick object at 0x0000016D58000530>, <matplotlib.axis.YTick object at 0x0000016D5AB10740>, <matplotlib.axis.YTick object at 0x0000016D58000B60>, <matplotlib.axis.YTick object at 0x0000016D580027B0>, <matplotlib.axis.YTick object at 0x0000016D5AB10E90>, <matplotlib.axis.YTick object at 0x0000016D58002390>, <matplotlib.axis.YTick object at 0x0000016D5AB11A90>, <matplotlib.axis.YTick object at 0x0000016D5AB13C80>, <matplotlib.axis.YTick object at 0x0000016D580024E0>, <matplotlib.axis.YTick object at 0x0000016D5AB13530>, <matplotlib.axis.YTick object at 0x0000016D5AB12870>, <matplotlib.axis.YTick object at 0x0000016D57FE9550>, <matplotlib.axis.YTick object at 0x0000016D57FE96A0>, <matplotlib.axis.YTick object at 0x0000016D57FEB4D0>, <matplotlib.axis.YTick object at 0x0000016D5AB12510>, <matplotlib.axis.YTick object at 0x0000016D5804F860>, <matplotlib.axis.YTick object at 0x0000016D57FEAFF0>, <matplotlib.axis.YTick object at 0x0000016D57FEA6C0>, <matplotlib.axis.YTick object at 0x0000016D57FE9970>, <matplotlib.axis.YTick object at 0x0000016D57FE9490>, <matplotlib.axis.YTick object at 0x0000016D54FC1FA0>, <matplotlib.axis.YTick object at 0x0000016D5AA299A0>, <matplotlib.axis.YTick object at 0x0000016D5AAF3E30>, <matplotlib.axis.YTick object at 0x0000016D5AAF07D0>, <matplotlib.axis.YTick object at 0x0000016D5AAF3DA0>, <matplotlib.axis.YTick object at 0x0000016D5502ABA0>, <matplotlib.axis.YTick object at 0x0000016D5AAF0E30>, <matplotlib.axis.YTick object at 0x0000016D57FEAB10>, <matplotlib.axis.YTick object at 0x0000016D5AAF3740>, <matplotlib.axis.YTick object at 0x0000016D5B65E540>, <matplotlib.axis.YTick object at 0x0000016D5B65D1F0>, <matplotlib.axis.YTick object at 0x0000016D5B65C800>, <matplotlib.axis.YTick object at 0x0000016D5AAF3CE0>, <matplotlib.axis.YTick object at 0x0000016D5B65C2C0>, <matplotlib.axis.YTick object at 0x0000016D5B666F90>, <matplotlib.axis.YTick object at 0x0000016D5AAD3650>, <matplotlib.axis.YTick object at 0x0000016D5AAD0CB0>], [Text(0, 0, 'Robo'), Text(0, 1, 'Lesiones'), Text(0, 2, 'Otros delitos del Fuero Común'), Text(0, 3, 'Violencia familiar'), Text(0, 4, 'Daño a la propiedad'), Text(0, 5, 'Amenazas'), Text(0, 6, 'Fraude'), Text(0, 7, 'Narcomenudeo'), Text(0, 8, 'Homicidio'), Text(0, 9, 'Incumplimiento de obligaciones de asistencia familiar'), Text(0, 10, 'Despojo'), Text(0, 11, 'Abuso de confianza'), Text(0, 12, 'Falsificación'), Text(0, 13, 'Allanamiento de morada'), Text(0, 14, 'Abuso sexual'), Text(0, 15, 'Delitos cometidos por servidores públicos'), Text(0, 16, 'Otros delitos que atentan contra la libertad personal'), Text(0, 17, 'Violación simple'), Text(0, 18, 'Otros delitos contra la familia'), Text(0, 19, 'Extorsión'), Text(0, 20, 'Otros delitos que atentan contra la libertad y la seguridad sexual'), Text(0, 21, 'Otros delitos contra el patrimonio'), Text(0, 22, 'Otros delitos que atentan contra la vida y la integridad corporal'), Text(0, 23, 'Falsedad'), Text(0, 24, 'Otros delitos contra la sociedad'), Text(0, 25, 'Violación equiparada'), Text(0, 26, 'Corrupción de menores'), Text(0, 27, 'Violencia de género en todas sus modalidades distinta a la violencia familiar'), Text(0, 28, 'Contra el medio ambiente'), Text(0, 29, 'Acoso sexual'), Text(0, 30, 'Secuestro'), Text(0, 31, 'Hostigamiento sexual'), Text(0, 32, 'Electorales'), Text(0, 33, 'Aborto'), Text(0, 34, 'Trata de personas'), Text(0, 35, 'Feminicidio'), Text(0, 36, 'Rapto'), Text(0, 37, 'Evasión de presos'), Text(0, 38, 'Tráfico de menores'), Text(0, 39, 'Incesto')])
plt.title('Heatmap Total de Delitos por Año')
plt.xlabel('Año')
plt.ylabel('Tipo de delito')
plt.tight_layout()
plt.show()Conclusión
En conjunto, las cifras mostradas indican que la criminalidad en México presenta una distribución altamente desigual tanto por tipo de delito como por entidad federativa. Algunos delitos, como robo, lesiones y violencia familiar, concentran la mayor parte de los registros a nivel nacional, mientras que otros presentan una incidencia considerablemente menor. Asimismo, ciertos estados destacan sistemáticamente por registrar niveles superiores al promedio nacional, lo que evidencia concentraciones regionales del fenómeno delictivo. En general, los resultados sugieren que factores demográficos, urbanos y socioeconómicos pueden tener un impacto en la distribución de los delitos, por lo que un análisis territorial y por tipo de delito resulta clave para orientar estrategias de prevención y políticas públicas de seguridad.