Cómo analizar datos de logística con Python

La logística es uno de los sectores con más datos disponibles en LATAM y, al mismo tiempo, uno donde el análisis cuantitativo todavía es escaso. En este artículo vamos a trabajar con un dataset real de rutas de entrega de una empresa mexicana de última milla.
Carga y exploración inicial
Lo primero es cargar el dataset con pandas y revisar qué tenemos:
import pandas as pd
df = pd.read_csv("entregas_cdmx.csv")
print(df.shape)
print(df.dtypes)
print(df.head())El dataset tiene las siguientes columnas relevantes:
fecha_entrega— fecha programadafecha_real— fecha en que se realizó la entregazona— zona geográfica (Benito Juárez, Coyoacán, etc.)status—on_time,delayed,faileddelay_min— minutos de retraso (0 si fue a tiempo)
Limpieza de fechas
Las columnas de fecha vienen como strings. Las convertimos:
df["fecha_entrega"] = pd.to_datetime(df["fecha_entrega"])
df["fecha_real"] = pd.to_datetime(df["fecha_real"])
# Calcular retraso real en minutos
df["retraso_calc"] = (df["fecha_real"] - df["fecha_entrega"]).dt.total_seconds() / 60Análisis por zona
Ahora que tenemos los datos limpios, podemos identificar qué zonas tienen más problemas:
retrasos_zona = (
df[df["status"] == "delayed"]
.groupby("zona")["retraso_calc"]
.agg(["mean", "count"])
.rename(columns={"mean": "retraso_promedio_min", "count": "total_retrasos"})
.sort_values("retraso_promedio_min", ascending=False)
)
print(retrasos_zona.head(10))El resultado muestra que las zonas del norte de la ciudad tienen retrasos promedio un 40% mayores que las del sur.
Distribución de retrasos
Para entender si los retrasos son sistémicos o puntuales, miramos la distribución:
import matplotlib.pyplot as plt
delayed = df[df["status"] == "delayed"]["retraso_calc"]
plt.figure(figsize=(10, 4))
plt.hist(delayed, bins=50, color="#2563EB", alpha=0.8)
plt.xlabel("Minutos de retraso")
plt.ylabel("Frecuencia")
plt.title("Distribución de retrasos — CDMX")
plt.tight_layout()
plt.savefig("distribucion_retrasos.png", dpi=150)Cruce con datos de SQL
El dataset de rutas está en un CSV pero los datos históricos de clientes viven en la base de datos. Cruzamos con SQL:
SELECT
z.nombre_zona,
COUNT(e.id) AS total_entregas,
AVG(e.delay_min) AS retraso_promedio,
SUM(CASE WHEN e.status = 'failed' THEN 1 END) AS entregas_fallidas
FROM entregas e
JOIN zonas z ON z.id = e.zona_id
WHERE e.fecha_entrega BETWEEN '2026-01-01' AND '2026-05-31'
GROUP BY z.nombre_zona
ORDER BY retraso_promedio DESC;Esta consulta nos da la misma información pero directamente desde la base de datos, sin necesidad de descargar el CSV completo.
Conclusión
Con menos de 50 líneas de código identificamos las zonas con mayor problema, la distribución de retrasos y cruzamos con datos históricos. Este tipo de análisis es exactamente lo que se pide en entrevistas de Data Analyst en empresas de logística en LATAM.
En el lab de esta lección encontrarás el dataset completo y una guía paso a paso para construir un dashboard operacional con los mismos datos.