Regresar al blog

Cómo analizar datos de logística con Python

DataAlias7 min de lectura

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:

python
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 programada
  • fecha_real — fecha en que se realizó la entrega
  • zona — zona geográfica (Benito Juárez, Coyoacán, etc.)
  • statuson_time, delayed, failed
  • delay_min — minutos de retraso (0 si fue a tiempo)

Limpieza de fechas

Las columnas de fecha vienen como strings. Las convertimos:

python
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() / 60

Análisis por zona

Ahora que tenemos los datos limpios, podemos identificar qué zonas tienen más problemas:

python
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:

python
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:

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.