Al final del módulo anterior dejamos el flujo de trabajo de un proyecto de Machine Learning en la puerta de la fase de exploración: antes de entrenar ningún modelo hay que entender los datos. La estadística descriptiva es precisamente eso: el conjunto de herramientas que resume miles de filas en unos pocos números y gráficos con significado. En esta lección aprenderás a distinguir población y muestra, a clasificar tipos de variables y a calcular e interpretar las medidas de tendencia central, dispersión y posición, aplicándolo todo a los datos de pedidos de MercaFresh con pandas y matplotlib.

Contenido

  1. Población y muestra
  2. Tipos de variables
  3. Medidas de tendencia central: media, mediana y moda
  4. Medidas de dispersión: rango, varianza, desviación estándar e IQR
  5. Medidas de posición: percentiles y cuartiles
  6. Visualizaciones básicas: histograma y boxplot
  7. Caso práctico completo: pedidos de MercaFresh

Población y muestra

  • Población: el conjunto completo de elementos que queremos estudiar. Para MercaFresh, todos los pedidos realizados en su historia (o todos los que se realizarán).
  • Muestra: un subconjunto de la población que sí tenemos a mano. Por ejemplo, los 10.000 pedidos del último trimestre.

En la práctica casi nunca trabajamos con la población completa: o es inabarcable, o incluye datos futuros que aún no existen. Por eso calculamos estadísticos sobre la muestra y los usamos como aproximación de los parámetros de la población.

Concepto Población Muestra
Símbolo del tamaño N n
Media μ (mu) x̄ (x barra)
Desviación estándar σ (sigma) s
¿Se conoce en la práctica? Casi nunca Sí, se calcula de los datos

Esta distinción parecerá un tecnicismo ahora, pero es la base de la inferencia estadística que veremos en la lección 02-04: allí aprenderemos a cuantificar cuánto nos podemos fiar de una muestra para hablar de la población.

En Machine Learning la idea reaparece con otro nombre: el conjunto de entrenamiento es una muestra de todos los datos posibles, y queremos que el modelo generalice a la población (los datos futuros).

Tipos de variables

Cada columna de un dataset es una variable, y su tipo determina qué operaciones y gráficos tienen sentido.

graph TD
    V[Variable] --> N[Numérica]
    V --> C[Categórica]
    N --> NC["Continua<br>(importe del pedido: 47,35 €)"]
    N --> ND["Discreta<br>(nº de artículos: 1, 2, 3...)"]
    C --> CN["Nominal<br>(provincia: Barcelona, Valencia...)"]
    C --> CO["Ordinal<br>(satisfacción: baja < media < alta)"]
  • Numérica continua: puede tomar cualquier valor dentro de un rango. Ejemplo MercaFresh: importe del pedido (47,35 €), tiempo de entrega (38,2 minutos).
  • Numérica discreta: solo valores enteros contables. Ejemplo: número de artículos por pedido, número de pedidos por cliente al mes.
  • Categórica nominal: categorías sin orden. Ejemplo: provincia de entrega, método de pago (tarjeta, paypal, contra_reembolso).
  • Categórica ordinal: categorías con orden pero sin distancia definida entre ellas. Ejemplo: valoración del cliente (baja < media < alta), franja horaria preferente.

Una trampa habitual: que algo esté codificado con números no lo hace numérico. El código postal 08001 es una variable nominal; calcular su media no significa nada. En el módulo 3 (lección 03-04) veremos cómo convertir variables categóricas a números de forma correcta para los modelos.

Medidas de tendencia central: media, mediana y moda

Responden a la pregunta: ¿cuál es el valor "típico"?

  • Media aritmética (x̄): suma de los valores dividida por n. Usa toda la información, pero es sensible a valores extremos.
  • Mediana: el valor central cuando ordenamos los datos (si n es par, la media de los dos centrales). Robusta frente a extremos.
  • Moda: el valor más frecuente. Es la única de las tres que tiene sentido para variables categóricas.

Veámoslo con datos ficticios de MercaFresh:

import numpy as np
import pandas as pd

# Semilla para que los resultados sean reproducibles
rng = np.random.default_rng(42)

# Simulamos 1.000 pedidos: la mayoría entre 20 y 80 €,
# más 15 pedidos enormes de clientes de empresa (hostelería)
importes = np.concatenate([
    rng.gamma(shape=4.0, scale=12.0, size=985),  # pedidos domésticos
    rng.uniform(400, 900, size=15)               # pedidos de hostelería
])

pedidos = pd.DataFrame({"importe": importes.round(2)})

print("Media:   ", pedidos["importe"].mean().round(2))
print("Mediana: ", pedidos["importe"].median().round(2))

Salida aproximada:

Media:    54.79
Mediana:  44.87

Explicación detallada del código:

  • np.random.default_rng(42) crea un generador de números aleatorios con semilla fija: cada ejecución produce los mismos datos, esencial para poder reproducir análisis.
  • rng.gamma(...) genera importes con forma realista (muchos pedidos medianos, cola de pedidos grandes); no necesitas entender esa distribución todavía, la veremos en la lección 02-02.
  • np.concatenate une los pedidos domésticos con 15 pedidos atípicos de empresa.
  • .mean() y .median() son métodos de pandas que calculan media y mediana ignorando valores nulos si los hubiera.

Fíjate en el resultado: la media (≈55 €) es claramente mayor que la mediana (≈45 €). Esos 15 pedidos de hostelería "tiran" de la media hacia arriba, pero apenas mueven la mediana. Si MercaFresh dijera "nuestro pedido típico es de 55 €" estaría exagerando: la mitad de los pedidos no llega a 45 €.

Medida Ventaja Inconveniente Cuándo preferirla
Media Usa todos los datos; buenas propiedades matemáticas Sensible a valores extremos Datos simétricos sin outliers
Mediana Robusta a extremos Ignora la magnitud de los valores Datos asimétricos (importes, salarios, tiempos)
Moda Vale para categóricas Puede no ser única o no existir Variables categóricas; datos discretos

La moda con una variable categórica:

metodos = pd.Series(["tarjeta"] * 620 + ["paypal"] * 290 + ["contra_reembolso"] * 90)
print(metodos.mode()[0])   # tarjeta  -> el método de pago más frecuente

Medidas de dispersión: rango, varianza, desviación estándar e IQR

Dos supermercados pueden tener el mismo importe medio de pedido y comportamientos totalmente distintos: uno con pedidos siempre en torno a 50 €, otro con pedidos de 5 € y de 500 €. La dispersión mide esa variabilidad.

  • Rango: máximo − mínimo. Simple pero frágil: depende solo de dos valores.
  • Varianza (s²): media de las desviaciones al cuadrado respecto a la media. Sus unidades son las unidades originales al cuadrado (¡euros²!), lo que dificulta la interpretación.
  • Desviación estándar (s): raíz cuadrada de la varianza. Vuelve a las unidades originales: "los importes se desvían típicamente unos 30 € de la media".
  • Rango intercuartílico (IQR): Q3 − Q1, la anchura del 50 % central de los datos. Robusto a valores extremos, igual que la mediana.
serie = pedidos["importe"]

print("Rango:               ", (serie.max() - serie.min()).round(2))
print("Varianza:            ", serie.var().round(2))
print("Desviación estándar: ", serie.std().round(2))

q1 = serie.quantile(0.25)
q3 = serie.quantile(0.75)
print("IQR:                 ", (q3 - q1).round(2))

Salida aproximada:

Rango:                885.05
Varianza:             5090.71
Desviación estándar:  71.35
IQR:                  35.09

Puntos importantes del código:

  • .var() y .std() de pandas usan por defecto la versión muestral (dividen entre n−1, parámetro ddof=1). NumPy (np.var, np.std) usa por defecto la poblacional (divide entre n, ddof=0). Con 1.000 datos la diferencia es minúscula, pero conviene saber que existe.
  • .quantile(0.25) devuelve el valor por debajo del cual queda el 25 % de los datos (el primer cuartil, Q1).

Observa el contraste: la desviación estándar (≈71 €) está inflada por los pedidos de hostelería, mientras que el IQR (≈35 €) describe mejor la variabilidad del pedido doméstico típico. Regla práctica: media + desviación estándar para datos simétricos, mediana + IQR para datos asimétricos o con outliers.

Medidas de posición: percentiles y cuartiles

El percentil p es el valor por debajo del cual queda el p % de los datos. Los cuartiles son tres percentiles especiales: Q1 (percentil 25), Q2 (percentil 50 = mediana) y Q3 (percentil 75).

Ejemplo con tiempos de entrega de MercaFresh:

# Tiempos de entrega en minutos: la mayoría rondan los 35-40 min,
# con algunos repartos lentos por tráfico
entregas = pd.Series(rng.gamma(shape=9.0, scale=4.5, size=1000).round(1),
                     name="minutos")

percentiles = entregas.quantile([0.25, 0.50, 0.75, 0.90, 0.95, 0.99])
print(percentiles.round(1))

Salida aproximada:

0.25    30.7
0.50    38.5
0.75    47.9
0.90    57.9
0.95    63.7
0.99    75.7

Lectura de negocio directa: la mitad de los pedidos llega en menos de 39 minutos, pero el 5 % más lento supera los 64. Si MercaFresh promete "entrega en menos de 60 minutos", el percentil 90 (≈58 min) dice que va justa: aproximadamente 1 de cada 10 entregas roza o incumple la promesa. Los percentiles altos (p95, p99) son el estándar en logística y en ingeniería para vigilar los peores casos, no el caso medio.

Visualizaciones básicas: histograma y boxplot

Los números resumen; los gráficos revelan la forma de los datos.

Histograma

Divide el rango de valores en intervalos (bins) y cuenta cuántas observaciones caen en cada uno.

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 4))
ax.hist(pedidos["importe"], bins=50, color="steelblue", edgecolor="white")
ax.axvline(pedidos["importe"].mean(), color="red", linestyle="--", label="Media")
ax.axvline(pedidos["importe"].median(), color="green", linestyle="--", label="Mediana")
ax.set_xlabel("Importe del pedido (€)")
ax.set_ylabel("Número de pedidos")
ax.set_title("Distribución de importes de pedido en MercaFresh")
ax.legend()
plt.tight_layout()
plt.show()
  • bins=50 controla la granularidad: pocos bins ocultan detalles, demasiados generan ruido. Prueba varios valores.
  • axvline dibuja líneas verticales para comparar visualmente media y mediana: en una distribución con cola a la derecha como esta, la media queda a la derecha de la mediana.

El histograma mostrará una masa principal de pedidos entre 20 y 100 € con una cola derecha larga (los pedidos de hostelería). Esa asimetría es la explicación visual de por qué media y mediana difieren.

Boxplot (diagrama de caja)

Resume en un solo dibujo la mediana, los cuartiles y los valores atípicos:

  • La caja va de Q1 a Q3 (su altura es el IQR) con una línea en la mediana.
  • Los bigotes se extienden hasta el último dato dentro de 1,5 × IQR desde la caja.
  • Los puntos fuera de los bigotes se marcan como outliers (candidatos a valores atípicos, no culpables automáticos).
fig, ax = plt.subplots(figsize=(8, 3))
ax.boxplot(pedidos["importe"], vert=False)
ax.set_xlabel("Importe del pedido (€)")
ax.set_title("Boxplot de importes: los pedidos de hostelería aparecen como outliers")
plt.tight_layout()
plt.show()

En este boxplot los 15 pedidos de hostelería aparecerán como puntos aislados a la derecha. Detectarlos es el primer paso; decidir qué hacer con ellos (¿son errores? ¿clientes legítimos de otro segmento?) pertenece a la limpieza de datos del módulo 3 (lección 03-01) y a la detección de anomalías que trataremos más adelante.

Caso práctico completo: pedidos de MercaFresh

Juntemos todo en el flujo típico de una primera exploración:

pedidos_full = pd.DataFrame({
    "importe": pedidos["importe"],
    "minutos_entrega": entregas,
    "n_articulos": rng.poisson(lam=12, size=1000),        # discreta
    "metodo_pago": rng.choice(["tarjeta", "paypal", "contra_reembolso"],
                              size=1000, p=[0.62, 0.29, 0.09])  # nominal
})

# describe() calcula de golpe: n, media, std, mínimo, cuartiles y máximo
print(pedidos_full[["importe", "minutos_entrega", "n_articulos"]].describe().round(2))

# Para la variable categórica: frecuencias, no medias
print(pedidos_full["metodo_pago"].value_counts(normalize=True).round(3))
  • describe() es la navaja suiza de la exploración: una tabla con casi todas las medidas de esta lección.
  • value_counts(normalize=True) da proporciones en lugar de conteos; es el resumen natural de una variable nominal.

Con esta tabla, el equipo de MercaFresh ya puede responder preguntas de negocio ("¿cuál es el pedido típico?", "¿cumplimos la promesa de entrega?") sin haber entrenado ningún modelo.

Errores Comunes y Consejos

  • Usar la media con datos asimétricos u outliers. Importes, tiempos y salarios casi siempre tienen cola derecha: reporta también la mediana, o solo la mediana.
  • Calcular medias de variables categóricas codificadas como números (códigos postales, IDs de producto). Comprueba el significado de la columna, no solo su dtype.
  • Confundir varianza y desviación estándar al interpretar. La varianza está en unidades al cuadrado; para comunicar resultados usa siempre la desviación estándar.
  • Olvidar el parámetro ddof: pandas usa n−1 y NumPy usa n por defecto. Si mezclas librerías y los números "no cuadran", este suele ser el motivo.
  • Eliminar outliers a ciegas. Un punto fuera de los bigotes del boxplot es un candidato a revisión, no un error garantizado: los pedidos de hostelería de MercaFresh son dinero real.
  • Consejo: dibuja siempre un histograma antes de decidir qué estadísticos reportar. Dos datasets pueden compartir media y desviación estándar y tener formas radicalmente distintas.

Ejercicios

Ejercicio 1

Los tiempos de entrega (en minutos) de 9 pedidos de una tarde son: [32, 35, 29, 41, 38, 33, 36, 95, 34]. Calcula a mano (y comprueba con pandas) la media y la mediana. ¿Cuál representa mejor la entrega "típica" y por qué?

Ejercicio 2

Clasifica estas variables de MercaFresh según su tipo (numérica continua/discreta, categórica nominal/ordinal): (a) peso total del pedido en kg, (b) número de productos frescos en el pedido, (c) tipo de cliente (particular, empresa), (d) nivel del programa de fidelidad (bronce, plata, oro), (e) código postal de entrega.

Ejercicio 3

Con el DataFrame pedidos_full del caso práctico, calcula el IQR de minutos_entrega y determina los límites de outliers según la regla 1,5 × IQR (inferior: Q1 − 1,5·IQR; superior: Q3 + 1,5·IQR). ¿Cuántos pedidos quedan fuera?

Soluciones

Solución 1

import pandas as pd
tiempos = pd.Series([32, 35, 29, 41, 38, 33, 36, 95, 34])
print(tiempos.mean().round(2))   # 41.44
print(tiempos.median())          # 35.0

La media (41,4 min) está distorsionada por la entrega de 95 minutos (quizá una incidencia de tráfico). La mediana (35 min) representa mejor la entrega típica: 8 de los 9 pedidos llegaron en menos de 41 minutos. Con pocos datos y un valor extremo, la mediana es la elección robusta.

Solución 2

  • (a) Peso en kg → numérica continua (puede ser 7,35 kg).
  • (b) Número de productos frescos → numérica discreta (0, 1, 2...).
  • (c) Tipo de cliente → categórica nominal (no hay orden entre particular y empresa).
  • (d) Nivel de fidelidad → categórica ordinal (bronce < plata < oro).
  • (e) Código postal → categórica nominal, aunque se escriba con dígitos: ni el orden ni la aritmética entre códigos tienen significado.

Solución 3

q1 = pedidos_full["minutos_entrega"].quantile(0.25)
q3 = pedidos_full["minutos_entrega"].quantile(0.75)
iqr = q3 - q1
lim_inf = q1 - 1.5 * iqr
lim_sup = q3 + 1.5 * iqr
outliers = pedidos_full[(pedidos_full["minutos_entrega"] < lim_inf) |
                        (pedidos_full["minutos_entrega"] > lim_sup)]
print(f"Límites: [{lim_inf:.1f}, {lim_sup:.1f}] -> {len(outliers)} outliers")

Con los datos simulados obtendrás en torno a 5-15 outliers, todos por encima del límite superior (entregas anormalmente lentas): exactamente los puntos que el boxplot dibuja fuera del bigote derecho. El límite inferior saldrá positivo pero por debajo del mínimo real, así que no habrá outliers por abajo.

Conclusión

En esta lección has construido el vocabulario básico de la exploración de datos: la diferencia entre población y muestra, los tipos de variables y las tres familias de medidas (tendencia central, dispersión y posición), junto con el histograma y el boxplot para ver la forma de los datos. Has comprobado con los pedidos de MercaFresh que la elección entre media/desviación estándar y mediana/IQR no es cosmética: cambia las conclusiones de negocio. Precisamente esa "forma" de los datos que revela el histograma tiene nombre y apellidos matemáticos: en la próxima lección estudiaremos las distribuciones de probabilidad, los modelos teóricos (Bernoulli, binomial, Poisson, normal...) que describen cómo se generan datos como los importes y tiempos que acabamos de explorar.

Curso de Machine Learning

Módulo 1: Introducción al Machine Learning

Módulo 2: Fundamentos de Estadística y Probabilidad

Módulo 3: Preprocesamiento de Datos

Módulo 4: Algoritmos de Machine Learning Supervisado

Módulo 5: Algoritmos de Machine Learning No Supervisado

Módulo 6: Evaluación y Validación de Modelos

Módulo 7: Técnicas Avanzadas y Optimización

Módulo 8: Implementación y Despliegue de Modelos

Módulo 9: Proyectos Prácticos

Módulo 10: Recursos Adicionales

© Copyright 2026. Todos los derechos reservados