Ir al contenido
wannacode

¿Por qué bajaron los ingresos?

En resumen: Un proyecto de análisis de datos de la pregunta al informe: limpiar, gráficos, una comprobación del ruido, desglosar la caída de ingresos por número de pedidos, categoría, día y canal, y un modelo de la pérdida con un intervalo.

Problema

«¿Por qué septiembre es tan flojo?»

El gerente ha visto el informe: los ingresos de septiembre de 2026 son 7128,80 € y las de agosto 8812,70 €. Eso es un 19,1 % menos. Tiene tres ideas sobre la mesa: parar la publicidad porque «no funciona», cambiar al proveedor de lácteos o hacer descuentos.

Cada idea responde a una causa distinta. Si la caída es ruido, no haces nada. Si viene de un producto, miras al proveedor. Si la gente compra menos, piensas en los precios. Así que la pregunta del análisis no es «qué tan grande es la caída», sino «dónde está la caída, para saber qué arreglar».

En este proyecto recorres todo el camino del curso: los datos, la limpieza, el gráfico, la comprobación del ruido, el desglose, el modelo y el informe. Los números salen del año de pedidos de la tienda, y la respuesta no está donde la esperan las ideas del gerente.

La idea

Los datos y la limpieza

Dos tablas: year_orders.csv (pedido, cliente, hora, canal, estado) y year_items.csv (las líneas de los pedidos). El valor de un pedido es la suma de sus líneas. Los pedidos cancelados no son ingresos:

Pythondatos: las tablas de la tienda

Hay 11 116 pedidos, y sin los 657 cancelados quedan 10 459. Antes de calcular nada, comprueba la limpieza: cada pedido tiene un valor (faltan 0), el menor es de 0,80 €, y los días son 365, del 1 de noviembre de 2025 al 31 de octubre de 2026. Los ingresos del año son 111 186,20 €, el mismo número que en el módulo «Preparar los datos».

new, paid y shipped son pedidos de la última semana que todavía van de camino. Los cuentas: están pagados o lo estarán. La columna channel dice de dónde vino el pedido: el sitio web (web), la app (app) o el teléfono (phone).

Escribe tú la limpieza. orders['ordered_at'] debe ser una fecha, items['revenue'] los ingresos de cada línea, orders['value'] el valor de cada pedido y sold los pedidos sin los cancelados.

Pythondatos: las tablas de la tienda

La idea

Primero el gráfico

Antes de buscar una causa, mira la caída en el contexto del año:

Pythondatos: las tablas de la tienda

Septiembre es 7128,80 € frente a 8812,70 € en agosto: −19,1 %. Octubre es 9920,40 €, así que la caída no continúa. El gráfico muestra además otra cosa: diciembre pasa de 15 000 €, y enero y febrero también son bajos. Septiembre es una caída en medio de meses normales, no parte de una temporada.

Septiembre tiene 30 días y agosto 31. ¿Podría eso explicar una parte de la caída? Guarda en per_day los ingresos por día de cada mes, y en change el cambio de los ingresos por día de agosto a septiembre, en porcentaje.

Pythondatos: las tablas de la tienda

La idea

¿Es ruido?

Los meses siempre se diferencian un poco. Antes de buscar una causa, comprueba si la caída es mayor que el ruido habitual. Comparas los días: 31 días de agosto contra 30 de septiembre, con la prueba de permutación de la lección sobre hipótesis. Abajo está el cambio de los ingresos por día de un mes al siguiente:

Pythondatos: las tablas de la tienda

Un día de septiembre aporta 46,65 € menos de media. De 10 000 mezclas de los días, solo 74 dan una diferencia al menos así de grande: p = 0,0074. Los meses normales de marzo a agosto se mueven entre −6,5 % y +5,8 %, y septiembre está en −16,4 %. Febrero (+16,8 %) es la vuelta después de las fiestas, y octubre (+34,7 %) es la vuelta después de septiembre.

La caída no es ruido. Tiene sentido buscar una causa.

1. La prueba de permutación de los días de agosto contra los días de septiembre da p = 0,0074. ¿Qué se deduce de eso?

La idea

Desglosar la caída

Los ingresos son el número de pedidos por el valor de un pedido. El primer desglose es cuál de los dos baja:

Pythondatos: las tablas de la tienda

Los pedidos bajan de 854 a 701 (−17,9 %), y el pedido medio es casi igual: 10,32 € y 10,17 €. El carrito no es más pequeño; hay menos pedidos. Los descuentos no son la respuesta.

¿Podría ser un producto, por ejemplo los lácteos del proveedor nuevo? Los ingresos por categoría:

Pythondatos: las tablas de la tienda

Todas las categorías bajan, desde −9,0 % (frutas y verduras) hasta −34,3 % (bebidas). La tarjeta de regalo está en +14,3 %, pero son 160 € frente a 140 € en agosto, unas pocas tarjetas. Si un producto tuviera la culpa, las demás categorías estarían normales. La caída está en todas partes: no es el producto.

¿La caída está en un día de la semana, por ejemplo porque la tienda cerró los jueves? Guarda en by_wd el ingreso medio por día de cada día de la semana (filas 0–6) para agosto y septiembre (columnas), y una columna change_% con el cambio en porcentaje.

Pythondatos: las tablas de la tienda

La idea

El canal

Queda una columna que todavía no has mirado: channel. Los ingresos por mes y canal:

Pythondatos: las tablas de la tienda

Ahí está. El sitio web (web) es 4517,00 € en agosto y 4660,80 € en septiembre. El teléfono (phone) es 999,70 € y 1028,70 €. Están normales. La app (app) baja de 3296,00 € a 1439,30 € y vuelve a 3755,40 € en octubre.

La caída de la app es de 1856,70 €, más que toda la caída de la tienda (1683,90 €): los otros dos canales incluso crecen un poco. Por eso bajan todas las categorías y todos los días a la vez: la app vende de todo, todos los días.

¿Qué parte de los ingresos viene de la app en un mes normal? Guarda en share la parte de app en los ingresos de cada mes, en porcentaje.

Pythondatos: las tablas de la tienda

La idea

¿Cuándo exactamente?

«Septiembre» es aproximado. El equipo de la app va a pedir una fecha. Los pedidos de la app día a día, con la media móvil de 7 días, y los días alrededor de los dos extremos:

Pythondatos: las tablas de la tienda

La línea baja a principios de septiembre y vuelve a principios de octubre. La tabla muestra los extremos. El 2 de septiembre la app tiene 9 pedidos, el 3 de septiembre solo 3, y el 5 de septiembre cero, mientras que el sitio web tiene 25 y 15. Al final: 6 pedidos el 1 de octubre, 8 el 2 de octubre y 12 el 3 de octubre.

La media móvil difumina los extremos unos días, así que buscas las fechas en los días sueltos. El comienzo está claro: 3 de septiembre. El final es más borroso: el 2 de octubre ya está a medio camino de vuelta, y desde el 3 de octubre la app es como antes. La caída va del 3 de septiembre al 2 de octubre, con un día de margen.

Encuentra las fechas con una regla, no a ojo. start es el primer día desde el 1 de agosto de 2026 en que la app tiene menos del 20 % de los pedidos del día. back es el primer día después de start en que los pedidos de la app son al menos la mediana de los días normales (hasta el 31 de agosto).

Pythondatos: las tablas de la tienda

La idea

¿Cuánto costó la caída?

El gerente va a preguntar cuánto dinero costó. La respuesta ingenua es «agosto menos septiembre»: 1683,90 €. Pero eso supone que septiembre debía ser como agosto. Octubre es más fuerte que agosto, así que septiembre también pudo haber sido más fuerte.

Los canales que funcionan dan una comparación mejor. En tiempos normales, la app aporta una proporción estable respecto al sitio web y al teléfono. Esa proporción k es un modelo con un solo número: predice los ingresos de la app a partir de los ingresos de los otros dos canales en los mismos días. Lo aprendes con los días anteriores a la caída y lo compruebas con días que no ha visto: octubre, después de la caída.

Pythondatos: las tablas de la tienda

Por cada 1 € de la web y del teléfono, la app aporta 0,641 €. Para octubre, después de la caída, el modelo dice 3688,92 € y la realidad es 3614,60 €: un error del 2,1 %. Para los días del 3 de septiembre al 2 de octubre el modelo espera 3713,41 €, y la app aportó 1292,60 €. La pérdida es de 2420,81 €, 737 € más que con la comparación ingenua.

¿Qué tan exacto es eso? La proporción k en 30 días no es siempre la misma. Con el bootstrap sacas 30 días normales, 10 000 veces, y ves cuánto se mueve la pérdida:

Pythondatos: las tablas de la tienda

En 30 días normales al azar, k está entre 0,457 y 0,906. El intervalo del 95 % para la pérdida va de 1802,46 € a 3142,14 €. En el informe eso es «unos 2400 €, entre 1800 € y 3150 €». La estimación es burda, pero incluso el límite inferior está por encima de los 1683,90 € ingenuos.

Más precisamente, para curiosos

El modelo app = k × (web + phone) tiene una característica y un número, como una regresión lineal sin intercepto. Su comprobación es como en el módulo sobre modelos: lo aprendes con unos días (antes del 3 de septiembre) y lo mides con otros (después del 2 de octubre). Supone que los canales se mueven juntos, y la comprobación con octubre lo confirma. Si los clientes de la app se hubieran pasado al sitio web en septiembre, el modelo sobrestimaría la pérdida: contaría como perdidos pedidos que en realidad llegaron por la web. La web en septiembre está +3,2 % respecto a agosto, dentro del movimiento habitual, así que la mayoría de los pedidos perdidos no se fueron a otro sitio.

¿Cuántos pedidos perdió la app? Construye el mismo modelo para el número de pedidos: k es app / (web + phone) en los días anteriores al 3 de septiembre, expected es cuántos pedidos de la app habría habido del 3 de septiembre al 2 de octubre, actual cuántos hubo, y lost_pct qué parte de los esperados se perdió, en porcentaje.

Pythondatos: las tablas de la tienda

La idea

El informe

Un análisis termina con una historia que alguien puede leer en un minuto. Tres frases, un gráfico, los límites.

Pythondatos: las tablas de la tienda
  1. Qué: los ingresos de septiembre están un 19,1 % por debajo de agosto, porque del 3 de septiembre al 2 de octubre la app tuvo cerca de dos tercios menos de pedidos; el sitio web y el teléfono están normales, y desde el 3 de octubre la app también es normal.
  2. Cuánto: la pérdida es de unos 2400 € (entre 1800 € y 3150 €), unos 240 pedidos.
  3. Qué hacer: revisar qué cambió en la app alrededor del 3 de septiembre (una actualización, el carrito, el pago) y añadir una alerta para cuando baje la proporción de un canal.

Los límites. Los datos dicen cuándo y dónde: en la app, desde el 3 de septiembre. No dicen por qué. Una actualización de la app alrededor del 3 de septiembre es una hipótesis, no un hecho: el equipo de la app la comprueba, con los registros del carrito y los errores de pago de esa fecha. También podría ser otra cosa: la tienda de aplicaciones, un certificado vencido, un cambio en el proveedor de pagos. El gráfico no puede distinguir estas causas. Por eso el informe dice «revisar», no «fue esto».

La publicidad, el proveedor y los precios no son la causa. Ninguna de las tres ideas del gerente habría devuelto los ingresos.

1. ¿Qué frase pertenece al informe?

Ejercicio

Una alerta para la próxima vez

La caída duró un mes porque nadie miraba el canal. Una regla de alerta debe detectar la caída rápido y quedarse callada en tiempos normales. Una alerta que salta todas las semanas, nadie la mira.

Construye la alerta. week es el número de pedidos por canal de los últimos 7 días (rolling(7).sum()), app_share la proporción de app en ellos, y alerts los días en que app_share es menor que 0.25. ¿Cuántas alarmas hay antes del 3 de septiembre, y cuándo es la primera?

Pythondatos: las tablas de la tienda

La idea

Lo que sigue

El curso termina aquí, y el trabajo con datos continúa. Hacia dónde:

  • Notebooks. Haz este proyecto en JupyterLab, en tu propia computadora: celda por celda, con la salida debajo de cada una. El notebook es también el informe: código, gráficos y texto en un solo lugar.
  • Datos más grandes. pandas lo guarda todo en memoria y trabaja en un solo núcleo. Polars tiene las mismas ideas (grupos, uniones, fechas), pero usa todos los núcleos y lee archivos más grandes que la memoria: pl.read_csv('year_orders.csv').group_by('channel').len() cuenta los pedidos por canal.
  • Dashboards. La alerta del ejercicio queda mejor en un dashboard que se actualiza solo. Streamlit y Dash hacen un dashboard web a partir de un script de Python, con gráficos y filtros.
  • Gráficos. seaborn y plotly son capas sobre las ideas de matplotlib: seaborn da gráficos estadísticos en una línea, y plotly gráficos interactivos, con zoom y tooltips.
  • Bases de datos. En una tienda real los pedidos están en una base de datos, no en un CSV. pd.read_sql lee el resultado de una consulta SQL directamente en un DataFrame. SQL se aprende en el curso Bases de datos y SQL.

El camino sigue siendo el mismo: la pregunta, los datos, la limpieza, la exploración, la conclusión con sus límites y la historia.

Test

Ponte a prueba

1. ¿Por qué lo primero en el proyecto es la pregunta «qué vamos a decidir con la respuesta»?
2. La suma de los ingresos de los miércoles es +18,0 % de agosto a septiembre, y el ingreso medio de un miércoles es −5,6 %. ¿En cuál confías?
3. ¿Por qué el modelo predice la app a partir de la web y el teléfono, en lugar de suponer simplemente que septiembre debió ser como agosto?
4. La pérdida es de 2420,81 €, con un intervalo del 95 % de 1802,46 € a 3142,14 €. ¿Cómo lo dices en el informe?
5. La alerta es «la proporción de la app en los pedidos de los últimos 7 días es menor que 0,25». ¿Por qué 7 días y no cada día por separado?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·