Análisis exploratorio de datos en un caso "real"¶
Se usa un conjunto de datos simulado,que contiene información sobre pedidos diarios de comida a domicilio en distintas zonas o barrios. Las variables qson: fecha, zona, cantidad_pedidos, precio_promedio, y categoría_comida (20 entradas).
import pandas as pd
import numpy as np
Conjunto de datos: Pedidos diarios de comida a domicilio en distintas zonas o barrios Fuente: Google Gemini
# Crear el objeto de datos
data = {
'fecha': [
'2025-11-01', '2025-11-01', '2025-11-01', '2025-11-01', '2025-11-01',
'2025-11-02', '2025-11-02', '2025-11-02', '2025-11-02', '2025-11-02',
'2025-11-03', '2025-11-03', '2025-11-03', '2025-11-03', '2025-11-03',
'2025-11-04', '2025-11-04', '2025-11-04', '2025-11-04', '2025-11-04',
'2025-11-05', '2025-11-05', '2025-11-05', '2025-11-05', '2025-11-05'
],
'zona': [
'Centro Ciudad', 'Zona Norte', 'Zona Sur', 'Zona Este', 'Zona Oeste',
'Centro Ciudad', 'Zona Norte', 'Zona Sur', 'Zona Este', 'Zona Oeste',
'Centro Ciudad', 'Zona Norte', 'Zona Sur', 'Zona Este', 'Zona Oeste',
'Centro Ciudad', 'Zona Norte', 'Zona Sur', 'Zona Este', 'Zona Oeste',
'Centro Ciudad', 'Zona Norte', 'Zona Sur', 'Zona Este', 'Zona Oeste'
],
'cantidad_pedidos': [
85, 45, 30, 60, 20,
92, 50, 35, 65, 25,
150, 60, 40, 70, 30,
100, 550, 50, 80, 40,
110, 70, 180, 90, 50
],
'precio_promedio': [
18.50, 15.20, 22.00, 16.80, 19.10,
18.90, 15.50, 22.50, 17.00, 19.50,
18.75, 15.30, 22.10, 16.90, 19.20,
19.00, 15.70, 22.60, 17.10, 19.60,
19.15, 15.80, 22.80, 17.20, 19.70
],
'categoría_comida': [
'Italiana', 'Asiática', 'Saludable', 'Mediterránea', 'Rápida',
'Mexicana', 'Asiática', 'Saludable', 'Mediterránea', 'Italiana',
'Italiana', 'Asiática', 'Rápida', 'Mexicana', 'Saludable',
'Rápida', 'Asiática', 'Italiana', 'Mediterránea', 'Mexicana',
'Italiana', 'Rápida', 'Saludable', 'Asiática', 'Mediterránea'
]
}
#Crear el DataFrame
df = pd.DataFrame(data)
df
| fecha | zona | cantidad_pedidos | precio_promedio | categoría_comida | |
|---|---|---|---|---|---|
| 0 | 2025-11-01 | Centro Ciudad | 85 | 18.50 | Italiana |
| 1 | 2025-11-01 | Zona Norte | 45 | 15.20 | Asiática |
| 2 | 2025-11-01 | Zona Sur | 30 | 22.00 | Saludable |
| 3 | 2025-11-01 | Zona Este | 60 | 16.80 | Mediterránea |
| 4 | 2025-11-01 | Zona Oeste | 20 | 19.10 | Rápida |
| 5 | 2025-11-02 | Centro Ciudad | 92 | 18.90 | Mexicana |
| 6 | 2025-11-02 | Zona Norte | 50 | 15.50 | Asiática |
| 7 | 2025-11-02 | Zona Sur | 35 | 22.50 | Saludable |
| 8 | 2025-11-02 | Zona Este | 65 | 17.00 | Mediterránea |
| 9 | 2025-11-02 | Zona Oeste | 25 | 19.50 | Italiana |
| 10 | 2025-11-03 | Centro Ciudad | 150 | 18.75 | Italiana |
| 11 | 2025-11-03 | Zona Norte | 60 | 15.30 | Asiática |
| 12 | 2025-11-03 | Zona Sur | 40 | 22.10 | Rápida |
| 13 | 2025-11-03 | Zona Este | 70 | 16.90 | Mexicana |
| 14 | 2025-11-03 | Zona Oeste | 30 | 19.20 | Saludable |
| 15 | 2025-11-04 | Centro Ciudad | 100 | 19.00 | Rápida |
| 16 | 2025-11-04 | Zona Norte | 550 | 15.70 | Asiática |
| 17 | 2025-11-04 | Zona Sur | 50 | 22.60 | Italiana |
| 18 | 2025-11-04 | Zona Este | 80 | 17.10 | Mediterránea |
| 19 | 2025-11-04 | Zona Oeste | 40 | 19.60 | Mexicana |
| 20 | 2025-11-05 | Centro Ciudad | 110 | 19.15 | Italiana |
| 21 | 2025-11-05 | Zona Norte | 70 | 15.80 | Rápida |
| 22 | 2025-11-05 | Zona Sur | 180 | 22.80 | Saludable |
| 23 | 2025-11-05 | Zona Este | 90 | 17.20 | Asiática |
| 24 | 2025-11-05 | Zona Oeste | 50 | 19.70 | Mediterránea |
Realizar un análisis inicial con medidas estadísticas básicas como media, mediana, moda y rango.
# En esta celda usamos un lenguaje exportable a archivo .py para ver la diferencia.
# El resto de celdas es formato notebook
# Media
media_pedidos = df['cantidad_pedidos'].mean()
print(f"Media de pedidos: {media_pedidos}")
media_precios = df['precio_promedio'].mean()
print(f"Media de precios: {media_precios}")
# Mediana
mediana_pedidos = df['cantidad_pedidos'].median()
print(f"Mediana de pedidos: {mediana_pedidos}")
mediana_precios = df['precio_promedio'].median()
print(f"Mediana de precios: {mediana_precios}")
# Moda
moda_pedidos = df['cantidad_pedidos'].mode().iloc[0]
print(f"Moda de pedidos: {moda_pedidos}")
moda_precios = df['precio_promedio'].mode().iloc[0]
print(f"Moda de precios: {moda_precios}")
moda_comida = df['categoría_comida'].mode().iloc[0]
print(f"Categoría de comida más repetida: {moda_comida}")
# Rango
print(f"Rango (máximo - mínimo) días: {df['fecha'].max()} - {df['fecha'].min()}")
print(f"Rango (máximo - mínimo) pedidos: {df['cantidad_pedidos'].max()} - {df['cantidad_pedidos'].min()}")
print(f"Rango (máximo - mínimo) precios: {df['precio_promedio'].max()} - {df['precio_promedio'].min()}")
Media de pedidos: 87.08 Media de precios: 18.636 Mediana de pedidos: 60.0 Mediana de precios: 18.9 Moda de pedidos: 50 Moda de precios: 15.2 Categoría de comida más repetida: Asiática Rango (máximo - mínimo) días: 2025-11-05 - 2025-11-01 Rango (máximo - mínimo) pedidos: 550 - 20 Rango (máximo - mínimo) precios: 22.8 - 15.2
La muestra de datos abarca solo cinco días, así que es bastante pequeña y, por tanto, poco representativa. Los precios, que no son especialmente altos, se mantienen bastante estables, en cambio, la cantidad de pedidos es muy variable, con valores que van de 20 a 550 (quizás haya errores), una mediana de 60 y una media claramente superior, de 87.08.
Identificar valores atípicos (outliers) y, si se encuentran, discutir si deberían o no ser eliminados
Para analizar esto vamos a realizar nuevos cálculos relativos a los valores de pedidos y precios
# Eliminamos los valores alfanuméricos para los cálculos (se puede hacer así o mediante parametro en las operaciones)
numericos = df[['cantidad_pedidos', 'precio_promedio']]
numericos
| cantidad_pedidos | precio_promedio | |
|---|---|---|
| 0 | 85 | 18.50 |
| 1 | 45 | 15.20 |
| 2 | 30 | 22.00 |
| 3 | 60 | 16.80 |
| 4 | 20 | 19.10 |
| 5 | 92 | 18.90 |
| 6 | 50 | 15.50 |
| 7 | 35 | 22.50 |
| 8 | 65 | 17.00 |
| 9 | 25 | 19.50 |
| 10 | 150 | 18.75 |
| 11 | 60 | 15.30 |
| 12 | 40 | 22.10 |
| 13 | 70 | 16.90 |
| 14 | 30 | 19.20 |
| 15 | 100 | 19.00 |
| 16 | 550 | 15.70 |
| 17 | 50 | 22.60 |
| 18 | 80 | 17.10 |
| 19 | 40 | 19.60 |
| 20 | 110 | 19.15 |
| 21 | 70 | 15.80 |
| 22 | 180 | 22.80 |
| 23 | 90 | 17.20 |
| 24 | 50 | 19.70 |
# Calcular la desviación estándar
desviacion_e= numericos.std()
print(f"Desviacion estandar:\n{desviacion_e}")
Desviacion estandar: cantidad_pedidos 103.705480 precio_promedio 2.398373 dtype: float64
varianza= numericos.var()
varianza
cantidad_pedidos 10754.826667 precio_promedio 5.752192 dtype: float64
percentil25= numericos.quantile(0.25)
print(f"Percentil 25 (Q1):\n{percentil25}")
percentil50= numericos.quantile(0.50)
print(f"Percentil 50 (Q2):\n{percentil50}")
percentil75= numericos.quantile(0.75)
print(f"Percentil 75 (Q3):\n{percentil75}")
Percentil 25 (Q1): cantidad_pedidos 40.0 precio_promedio 16.9 Name: 0.25, dtype: float64 Percentil 50 (Q2): cantidad_pedidos 60.0 precio_promedio 18.9 Name: 0.5, dtype: float64 Percentil 75 (Q3): cantidad_pedidos 90.0 precio_promedio 19.6 Name: 0.75, dtype: float64
precio_promedio tiene una desviación estándar de 2.39, una media de 18.6 y una mediana de 18.9.
Si aplicamos el criterio de valores situados a más de dos desviaciones estándar de la media, el rango sería 18.9 ± 2.39, es decir, entre 16.51 y 21.29.
Con esta definición aparecerían muchos posibles outliers pero, dado que la muestra es muy pequeña y no sigue una distribución normal, resulta más adecuado usar el método del rango intercuartílico.
Los valores relevantes son Q3 = 19.6 y Q1 = 16.9, de modo que el IQR es 2.70.
Los límites serían:
19.6 + 1.5×2.70 = 23.65
16.9 − 1.5×2.70 = 12.85
Ningún precio se encuentra fuera de este intervalo, por lo que el precio promedio por pedido se mantiene estable entre los distintos días y zonas. La mayoría de valores se concentran de forma bastante uniforme alrededor de la mediana.
#Tenemos una función para realizar todos los cálculos iniciales de una vez
df.describe()
| cantidad_pedidos | precio_promedio | |
|---|---|---|
| count | 25.00000 | 25.000000 |
| mean | 87.08000 | 18.636000 |
| std | 103.70548 | 2.398373 |
| min | 20.00000 | 15.200000 |
| 25% | 40.00000 | 16.900000 |
| 50% | 60.00000 | 18.900000 |
| 75% | 90.00000 | 19.600000 |
| max | 550.00000 | 22.800000 |
cantidad_pedidos tiene una desviación estándar de 103.70, media de 87.08 y mediana de 60.0 La desviación estándar es muy alta en relación con la media. Indica que deben existir valores atípicos.
q1 = numericos.quantile(0.25)
q3 = numericos.quantile(0.75)
iqr = q3-q1
outliers = numericos[((numericos < (q1-1.5*iqr))|(numericos > (q3+1.5*iqr))).any(axis=1)]
outliers
| cantidad_pedidos | precio_promedio | |
|---|---|---|
| 16 | 550 | 15.7 |
| 22 | 180 | 22.8 |
En cantidad_pedidos Q1 es 40, así que cualquier valor muy inferior a 40 es sospechoso de ser atípico. El mínimo es 20, no parece que haya mucha variación pero haremos el mismo cálculo que antes.
Q3: 90 \ Q1: 40
IQR = 50
Limites:
90 + (1.5*50)=165
40 - (1.5*50)=-35
En cantidad_pedidos el límite inferior es negativo lo obviamos. Q3 es 90, así que cualquier valor muy superior a 90, a partir de 165, es sospechoso de ser atípico
# Podemos intentar ver estos outliers en el gráfico de un vistazo
import matplotlib.pyplot as plt
plt.boxplot(numericos)
{'whiskers': [<matplotlib.lines.Line2D at 0x224ea7a8190>,
<matplotlib.lines.Line2D at 0x224eaa08690>,
<matplotlib.lines.Line2D at 0x224eaa08e10>,
<matplotlib.lines.Line2D at 0x224eaa08f50>],
'caps': [<matplotlib.lines.Line2D at 0x224eaa087d0>,
<matplotlib.lines.Line2D at 0x224eaa08910>,
<matplotlib.lines.Line2D at 0x224eaa09090>,
<matplotlib.lines.Line2D at 0x224eaa091d0>],
'boxes': [<matplotlib.lines.Line2D at 0x224ea886e90>,
<matplotlib.lines.Line2D at 0x224eaa08cd0>],
'medians': [<matplotlib.lines.Line2D at 0x224eaa08a50>,
<matplotlib.lines.Line2D at 0x224eaa09310>],
'fliers': [<matplotlib.lines.Line2D at 0x224eaa08b90>,
<matplotlib.lines.Line2D at 0x224eaa09450>],
'means': []}
Outlier 1: 16 El valor es inusualmente alto (550), además tiene una cifra repetida ¿error de introducción de datos?. Podemos pensar que es erroneo ya que la capacidad de preparar pedidos debe ser un valor limitado. Decisión: Eliminar o Corregir.
Outlier 2: 22 Gran volumen, 180, puede deberse a una promoción, oferta especial... No eliminar
# Después de contrastar datos dejamos los outliers por eventos y corregimos el error tipográfico
df.loc[16, 'cantidad_pedidos'] = 55
Visualizar la distribución de los pedidos en cada zona y en cada categoría de comida mediante gráficos de barras e histogramas.
import seaborn as sns
sns.barplot(data=df,y='cantidad_pedidos', x='zona')
<Axes: xlabel='zona', ylabel='cantidad_pedidos'>
sns.barplot(data=df,y='cantidad_pedidos', x='categoría_comida')
<Axes: xlabel='categoría_comida', ylabel='cantidad_pedidos'>
sns.barplot(data=df,y='cantidad_pedidos', x='zona', hue='categoría_comida')
<Axes: xlabel='zona', ylabel='cantidad_pedidos'>
sns.histplot(
data=df,
x='cantidad_pedidos',
hue='zona',
multiple='stack',
bins=10,
palette='viridis'
)
<Axes: xlabel='cantidad_pedidos', ylabel='Count'>
sns.histplot(
data=df,
x='cantidad_pedidos',
hue='categoría_comida',
multiple='stack',
bins=10
)
<Axes: xlabel='cantidad_pedidos', ylabel='Count'>
Vemos por los gráficos de barras que tanto la venta por zonas, como de categorías es muy variada. Los histogramas tampoco nos dan ninguna tendencia de numero de pedidos por zonas o categorías, pero sí podemos detectar algo especial con las zonas Centro y Sur y las categorías italiana y saludable, donde parece haber una cantidad inusual de pedidos. Aunque no los vimos como outliers sí que pueden ser valores especiales de venta.
Evaluar la tendencia de los pedidos en el tiempo (e.g., pedidos semanales o mensuales) y visualizar los resultados mediante una gráfica de líneas.
# Pedidos diarios
sns.lineplot(data=df, x='fecha', y='cantidad_pedidos', hue='zona')
<Axes: xlabel='fecha', ylabel='cantidad_pedidos'>
Todas las zonas muestran una tendencia alcista de ventas
# Pedidos diarios
sns.lineplot(data=df, x='fecha', y='cantidad_pedidos', hue='categoría_comida')
<Axes: xlabel='fecha', ylabel='cantidad_pedidos'>
¿Qué insights se pueden extraer sobre el rendimiento de pedidos por zona y por categoría de comida?
La zona con más pedidos es la zona centro y la que menos es la oeste. La categoría que más aparece es la italiana y la que menos la rápida, aunque en este caso la diferencia no es demasiado grande. Combinando las dos gráficas vemos que la zona este ofrece gran cantidad de pedidos, pero ninguno es de italiana (la más frecuente)
¿Existen patrones de demanda relacionados con el tiempo? ¿Algún mes o día de la semana muestra mayor actividad de pedidos?
En los 5 días examinados la tendencia es creciente en todas las zonas. Con nuestros datos poco más se puede decir. Si ampliamos los datos con el día de la semana, veremos que el día 1 era sábado y el 5 miércoles, así que no sabemos si la tendencia será coyuntural (seguirá subiendo) o es cíclica y el siguiente sabado volverá a bajar. Necesitamos más datos.
¿Se detectan anomalías en la cantidad de pedidos en alguna zona o categoría?
Además de los outliers ya tratados que habría que comprobar, la tendencia es correcta tanto para fecha, como zona o para categoría. Solo podemos observar una pequeña bajada en los pedidos de la zona Norte el día 3
Resolución de preguntas clave a partir de los datos¶
Debes formular preguntas clave y responderlas mediante un análisis exhaustivo de los datos
- Formular tres preguntas clave basadas en los objetivos del conjunto de datos.
- Realizar el análisis correspondiente a cada pregunta que has formulado utilizando las técnicas de AED, como estadísticas descriptivas, visualización y correlación.
- Documentar los métodos, pasos y conclusiones de cada análisis, justificando las decisiones tomadas para responder a cada pregunta.
Preguntas:¶
- ¿Cuál es la beneficio económico real por zona geográfica más allá del volumen de pedidos?
- ¿Existe alguna correlación negativa entre precio y demanda?
- ¿Cómo rinde cada categoría de comida (distribución de pedidos, medias, variabilidad)?
df
| fecha | zona | cantidad_pedidos | precio_promedio | categoría_comida | |
|---|---|---|---|---|---|
| 0 | 2025-11-01 | Centro Ciudad | 85 | 18.50 | Italiana |
| 1 | 2025-11-01 | Zona Norte | 45 | 15.20 | Asiática |
| 2 | 2025-11-01 | Zona Sur | 30 | 22.00 | Saludable |
| 3 | 2025-11-01 | Zona Este | 60 | 16.80 | Mediterránea |
| 4 | 2025-11-01 | Zona Oeste | 20 | 19.10 | Rápida |
| 5 | 2025-11-02 | Centro Ciudad | 92 | 18.90 | Mexicana |
| 6 | 2025-11-02 | Zona Norte | 50 | 15.50 | Asiática |
| 7 | 2025-11-02 | Zona Sur | 35 | 22.50 | Saludable |
| 8 | 2025-11-02 | Zona Este | 65 | 17.00 | Mediterránea |
| 9 | 2025-11-02 | Zona Oeste | 25 | 19.50 | Italiana |
| 10 | 2025-11-03 | Centro Ciudad | 150 | 18.75 | Italiana |
| 11 | 2025-11-03 | Zona Norte | 60 | 15.30 | Asiática |
| 12 | 2025-11-03 | Zona Sur | 40 | 22.10 | Rápida |
| 13 | 2025-11-03 | Zona Este | 70 | 16.90 | Mexicana |
| 14 | 2025-11-03 | Zona Oeste | 30 | 19.20 | Saludable |
| 15 | 2025-11-04 | Centro Ciudad | 100 | 19.00 | Rápida |
| 16 | 2025-11-04 | Zona Norte | 55 | 15.70 | Asiática |
| 17 | 2025-11-04 | Zona Sur | 50 | 22.60 | Italiana |
| 18 | 2025-11-04 | Zona Este | 80 | 17.10 | Mediterránea |
| 19 | 2025-11-04 | Zona Oeste | 40 | 19.60 | Mexicana |
| 20 | 2025-11-05 | Centro Ciudad | 110 | 19.15 | Italiana |
| 21 | 2025-11-05 | Zona Norte | 70 | 15.80 | Rápida |
| 22 | 2025-11-05 | Zona Sur | 180 | 22.80 | Saludable |
| 23 | 2025-11-05 | Zona Este | 90 | 17.20 | Asiática |
| 24 | 2025-11-05 | Zona Oeste | 50 | 19.70 | Mediterránea |
- ¿Cuál es la beneficio económico real por zona geográfica más allá del volumen de pedidos?
df['Total_zona'] = df['precio_promedio'] * df['cantidad_pedidos']
beneficio_zona = df.groupby('zona')[['precio_promedio', 'cantidad_pedidos', 'Total_zona']].sum()
beneficio_zona
| precio_promedio | cantidad_pedidos | Total_zona | |
|---|---|---|---|
| zona | |||
| Centro Ciudad | 94.3 | 537 | 10130.3 |
| Zona Este | 85.0 | 365 | 6212.0 |
| Zona Norte | 77.5 | 280 | 4346.5 |
| Zona Oeste | 97.1 | 165 | 3214.5 |
| Zona Sur | 112.0 | 335 | 7565.5 |
sns.barplot(
data=beneficio_zona,
x='cantidad_pedidos',
y='Total_zona',
hue='zona'
)
<Axes: xlabel='cantidad_pedidos', ylabel='Total_zona'>
Tomando como beneficio el total ingresado por zona, es decir, cantidad de pedidos multiplicada por el precio promedio registrado cada día vemos que:
Centro Ciudad es la principal sucursal, generando 10130.30 € gracias a su gran cantidad de pedidos (537).
Sin embargo, el dato más valioso es que la Zona Este procesa más pedidos (365) que la Sur (335). A pesar de trabajar menos, la Zona Sur facturó considerablemente más (7565.50 €) frente a la Este (6212.00 €).
Esto hace que la Zona Sur sea la más eficiente: requiere menos pedidos para generar más facturación debido a su alto precio_promedio.
Por contrapartida, la Zona Oeste muestra el peor rendimiento tanto en volumen como en facturación total (3214.50 €), esto puede querer decir que necesita una intervención estratégica.
- ¿Existe alguna correlación negativa entre precio y demanda?
Para analizar la relación entre precio y demanda empezaremos con el cálculo del Coeficiente de Correlación de Pearson buscando un valor cercano a -1 (sube el precio, bajan los pedidos). Un valor cercano a 0 implicaría que no hay relación lineal.
df.corr(method='pearson', numeric_only=True)
| cantidad_pedidos | precio_promedio | Total_zona | |
|---|---|---|---|
| cantidad_pedidos | 1.000000 | 0.055939 | 0.977118 |
| precio_promedio | 0.055939 | 1.000000 | 0.243655 |
| Total_zona | 0.977118 | 0.243655 | 1.000000 |
Vemos que no parece haber correlación lineal de precio_promedio ni con la cantidad de pedidos, ni con el total de la zona. Vamos a comprobar otros tipso de correlaciones mediante el coeficiente de Spearman
df.corr(method='spearman', numeric_only=True)
| cantidad_pedidos | precio_promedio | Total_zona | |
|---|---|---|---|
| cantidad_pedidos | 1.000000 | -0.211479 | 0.958399 |
| precio_promedio | -0.211479 | 1.000000 | 0.016154 |
| Total_zona | 0.958399 | 0.016154 | 1.000000 |
Tampoco parece haber correlación de otro tipo entre los datos. Para asegurarnos mostraremos un gráfico de dispersión
sns.scatterplot(data=df, x='precio_promedio', y='cantidad_pedidos')
<Axes: xlabel='precio_promedio', ylabel='cantidad_pedidos'>
El gráfico no muestra tendencia alguna.
Tras ejecutar todos los pasos. El coeficiente de Pearson obtenido es 0.056 y el de Spearman es -0.21, esto es técnicamente una correlación nula. El precio no es la variable que determina el volumen de ventas.
- ¿Cómo rinde cada categoría de comida (distribución de pedidos, medias, variabilidad)?
beneficio_comida = df.groupby('categoría_comida')[['precio_promedio', 'cantidad_pedidos', 'Total_zona']].sum()
beneficio_comida
| precio_promedio | cantidad_pedidos | Total_zona | |
|---|---|---|---|
| categoría_comida | |||
| Asiática | 78.9 | 300 | 4788.5 |
| Italiana | 98.5 | 420 | 8109.0 |
| Mediterránea | 70.6 | 255 | 4466.0 |
| Mexicana | 55.4 | 202 | 3705.8 |
| Rápida | 76.0 | 230 | 4272.0 |
| Saludable | 86.5 | 275 | 6127.5 |
sns.scatterplot(data=beneficio_comida, x='Total_zona', y='cantidad_pedidos', hue='categoría_comida')
<Axes: xlabel='Total_zona', ylabel='cantidad_pedidos'>
El análisis inicial revela que:
Comida Italiana es indiscutiblemente la categoría estrella, generando 8109.00 con 420 pedidos.
El dato más valioso aparece al comparar la categoría Asiática con la Saludable:
La Asiática procesa un volumen superior de pedidos (300) frente a la Saludable (275).
A pesar de tener menor carga de trabajo, la Saludable facturó considerablemente más (6127.50 €) frente a la Asiática (4788.50 €).
Esto convierte a la categoría Saludable en la más rentable del menú: tiene los pedidos más caros. La Asiática sufre muchos pedidos para un margen total más bajo.
La Comida Mexicana es la que peor funciona tanto en volumen (202) como en facturación total (3705.80 €), sugiriendo su posible eliminación si al analizar los costes no sale rentable.
Ahora vamos a asegurar el análisis analizando los datos atendiendo a su eficiencia diaria, para lo que calcularemos medias (por si los totales no son representativos) y el riesgo de venta mediante la varianza del número de pedidos (para ver si un buen dato puede ser engañoso por ser algo puntual).
media_pedidos = df['cantidad_pedidos'].mean()
media_por_categoria = df.groupby('categoría_comida')['cantidad_pedidos'].mean()
diferencial = media_por_categoria - media_pedidos
diferencial
| cantidad_pedidos | |
|---|---|
| categoría_comida | |
| Asiática | -7.280000 |
| Italiana | 16.720000 |
| Mediterránea | -3.530000 |
| Mexicana | 0.053333 |
| Rápida | -9.780000 |
| Saludable | 1.470000 |
varianza_pedidos = df['cantidad_pedidos'].var()
varianza_por_categoria = df.groupby('categoría_comida')['cantidad_pedidos'].var()
diferencial_v = varianza_por_categoria - varianza_pedidos
diferencial_v
| cantidad_pedidos | |
|---|---|
| categoría_comida | |
| Asiática | -1147.876667 |
| Italiana | 957.123333 |
| Mediterránea | -1304.126667 |
| Mexicana | -779.043333 |
| Rápida | -235.376667 |
| Saludable | 4045.873333 |
resumen_final = pd.concat([beneficio_comida, diferencial, diferencial_v], axis=1)
resumen_final.columns = [
'Precio_promedio',
'Total_Pedidos',
'Ingresos_Totales',
'Dif_Media',
'Riesgo_Variabilidad'
]
resumen_final
| Precio_promedio | Total_Pedidos | Ingresos_Totales | Dif_Media | Riesgo_Variabilidad | |
|---|---|---|---|---|---|
| categoría_comida | |||||
| Asiática | 78.9 | 300 | 4788.5 | -7.280000 | -1147.876667 |
| Italiana | 98.5 | 420 | 8109.0 | 16.720000 | 957.123333 |
| Mediterránea | 70.6 | 255 | 4466.0 | -3.530000 | -1304.126667 |
| Mexicana | 55.4 | 202 | 3705.8 | 0.053333 | -779.043333 |
| Rápida | 76.0 | 230 | 4272.0 | -9.780000 | -235.376667 |
| Saludable | 86.5 | 275 | 6127.5 | 1.470000 | 4045.873333 |
Al cruzar los ingresos totales con el rendimiento medio diario y la variabilidad, el diagnóstico cambia respecto a la visión inicial.
La comida mexicana al ser la última en facturación total (3705.8), parecía la candidata a eliminar. Su bajo importe total se debe a su baja frecuencia de aparición, sin embargo, su diferencial es positivo (+0.05) respecto a la media global. El día que se ha vendido, funciona. Quizá con intentar venderla más días se solucione el problema.
Comida Rápida y Asiática, ambas tienen diferenciales muy negativos(-9.78 y -7.28). Sistemáticamente rindieron por debajo del promedio del negocio. La comida Rápida es la menos eficiente: genera un volumen que consume recursos sin aportar margen ni estabilidad.
La comida Saludable parecía la más rentable, pero su variabilidad es extrema. No se pueden basar los costes fijos en ella aunque puede hacer que entren altos importes en caja. Sin embargo, la comida Mediterránea aunque factura menos, su variabilidad es mínima. Es la categoría que permite planificar las ventas.
En lo que sí acertamos fue en que la comida Italiana es la líder indiscutible en todas las métricas.
Preguntas para reflexionar:
- ¿Qué preguntas han podido responderse de manera concluyente, y cuáles no?
- ¿Cómo ayuda cada análisis realizado a comprender mejor el conjunto de datos?
- ¿Qué recomendaciones pueden hacerse basadas en las respuestas obtenidas?
Las tres preguntas han quedado resueltas. Los cálculos y las visualizaciones han revelado patrones que no se apreciaban a simple vista, por ejemplo que la categoría Mexicana, aunque es la última en facturación total, muestra una media diaria superior al promedio del negocio.
Resumiendo las conclusiones principales, Centro Ciudad es el núcleo del flujo de caja, ya que destaca en todos los indicadores, y la comida Italiana lidera tanto en volumen como en media diaria. La combinación de ambos factores es el pilar del negocio.
También se confirma que reducir precios no incrementaría la demanda de forma proporcional, por lo que no es una estrategia recomendable. Al revisar la variabilidad por categorías vimos que el stock de la comida mediterránea puede automatizarse, dado que presenta una varianza muy baja, mientras que en la saludable conviene mantener un stock más alto por los picos de venta que pueden surgir.
En cuanto a la comida Mexicana, aumentar su frecuencia de venta podría mejorar la rentabilidad, aunque solo contamos con tres días de datos y no puede descartarse que exista algún elemento que esté inflando sus métricas. Sería necesario ampliar la muestra.
Del mismo modo, con solo cinco días registrados no es posible determinar si los picos detectados son patrones habituales o eventos puntuales.