Indice / Practicas / EJ-6.9
EJ-6.9 · Pedidos diarios con resample
Una serie diaria a partir de 120 pedidos sueltos. Las dos decisiones que la definen no son técnicas: los días sin ventas aparecen igual, y el pedido de un cliente que no existe se conserva.
Qué hay que hacer
A partir de tienda_pedidos.csv y tienda_clientes.csv,
producir pedidos_diarios.csv: una fila por día del período, con la
cantidad de pedidos, el total, los clientes distintos y cuántos pedidos venían de un
cliente desconocido.
ELI5Explicado facil
Es el parte diario del negocio. Y tiene dos reglas que parecen obvias y que casi nadie escribe:
- Un día sin ventas es un día con cero ventas, no un día que no existe. Si lo salteás, el promedio diario sale al doble.
- Una venta a alguien que no está en la lista de clientes igual se vendió. Sacarla del total porque no encontraste el nombre es perder plata de verdad en un informe.
Archivos
- Entrada ·
tienda_pedidos.csv - Entrada ·
tienda_clientes.csv - Objetivo ·
pedidos_diarios.csv - Herramienta ·
comparar.py
Las dos decisiones
| Decisión | Si elegís bien | Si elegís mal |
|---|---|---|
| Días vacíos | resample("D") → 205 filas | groupby → 94 filas, y el promedio diario se duplica |
| Cliente desconocido | how="left" → 120 pedidos | how="inner" → 119, y falta una venta en el total |
Los cuatro programas corren. Solo uno produce el archivo del contrato, y los otros tres dan números que parecen razonables.
Cómo saber si te salió
python comparar.py mi_salida.csv pedidos_diarios.csv
Antes de arrancar, mirá los datos
| id | cliente_id | total | creado | estado |
|---|---|---|---|---|
| 101 | 32 | 374.94 | 2026-07-08 22:39:00 | preparando |
| 102 | 37 | 223.65 | 2026-04-18 02:08:00 | entregado |
| 103 | 26 | 605.95 | 2026-02-22 13:19:00 | nuevo |
| 104 | 25 | 1743.36 | 2026-04-04 12:18:00 | pagado |
| 105 | 8 | 699.82 | 2026-05-20 01:42:00 | pagado |
El contrato de salida
pedidos_diarios.csv · 205 filas
| Columna | Tipo | Que lleva |
|---|---|---|
fecha | texto | El dia, en formato AAAA-MM-DD. Estan TODOS los dias del periodo |
pedidos | entero | Cuantos pedidos hubo ese dia. Puede ser 0 |
total | decimal | Suma de total, redondeada a 2 decimales. 0.00 en los dias sin pedidos |
clientes_distintos | entero | Cuantos cliente_id distintos compraron ese dia |
pedidos_sin_cliente | entero | Pedidos cuyo cliente_id no esta en el padron |
Que hacer con cada defecto
| Defecto | Donde | Accion | Adonde va |
|---|---|---|---|
| 111 dias del periodo sin ningun pedido | de 205 dias, solo 94 tienen pedidos | aparecen aparecen igual, con pedidos = 0 y total = 0.00. Es lo que hace resample y lo que groupby NO puede hacer | pedidos_diarios.csv |
| un pedido cuyo cliente_id no esta en el padron | 1 fila | NO NO se descarta. El cruce es how='left': la venta existio y tiene que contarse. Se informa en pedidos_sin_cliente | pedidos_diarios.csv |
| 2 pedidos sin total | 2 filas | NO NO se descartan: el pedido existio. sum() ignora el ausente, asi que suman 0 al total de su dia pero cuentan en pedidos | pedidos_diarios.csv |
| 1 pedido sin estado | 1 fila | NO NO afecta: el estado no participa de este informe | pedidos_diarios.csv |
Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:
- convertir creado a fecha
- cruzar con clientes usando how='left' y validate='m:1'
- marcar los pedidos sin cliente
- poner la fecha en el indice y ordenarlo
- resample('D') y agregar
El cruce va ANTES del resample: despues de agrupar por dia ya no se puede saber a que cliente pertenecia cada pedido. Y how='left' es la decision central: con inner, el pedido del cliente desconocido desaparece del total del negocio.
Conteos clave
| pedidos de entrada | 120 |
|---|---|
| clientes en el padron | 40 |
| pedidos_diarios.csv | 205 |
| dias con al menos un pedido | 94 |
| dias con 0 pedidos | 111 |
| pedidos sin cliente en el padron | 1 |
| total general | 354824.17 |
| por motivo |
Invariantes
Comprobaciones que tu resultado tiene que cumplir. Corrilas vos mismo antes de comparar los archivos:
len(diario) == 205
94 + 111 == 205
diario['fecha'].is_unique
int(diario['pedidos'].sum()) == 120
int(diario['pedidos_sin_cliente'].sum()) == 1
abs(diario['total'].sum() - pedidos['total'].sum()) < 0.01
(diario['clientes_distintos'] <= diario['pedidos']).all()
diario.notna().all().all()
PISTASSi te trabas, abri esto
- resample crea los dias vacios; groupby por fecha solo devuelve los 94 que existen. La diferencia son 111 filas.
- how='left' conserva el pedido del cliente desconocido. Con inner desaparece del total, y el total del negocio queda mal.
- La suma de la columna pedidos tiene que dar 120: es el assert que confirma que ningun pedido se perdio al agrupar.
- sum() ignora los totales ausentes. Los 2 pedidos sin total cuentan en pedidos y suman 0 en total, que es lo declarado.
Así tiene que quedar
| fecha | pedidos | total | clientes_distintos | pedidos_sin_cliente |
|---|---|---|---|---|
| 2026-01-01 | 1 | 33.97 | 1 | 0 |
| 2026-01-02 | 1 | 1344.24 | 1 | 0 |
| 2026-01-03 | 0 | 0.0 | 0 | 0 |
| 2026-01-04 | 1 | 903.36 | 1 | 0 |
| 2026-01-05 | 0 | 0.0 | 0 | 0 |
resample
Después de agrupar por día ya no se puede saber a qué cliente pertenecía cada pedido: la información se perdió al agregar. El orden es cruzar, marcar, y recién ahí agrupar.
Y validate="m:1" en el cruce: muchos pedidos contra una fila por
cliente. Si el padrón trajera un cliente duplicado, el merge multiplicaría pedidos y
el total del día saldría inflado sin ninguna señal.
assert que cierra el ejercicio
diario["pedidos"].sum() == 120. Si no da, algún pedido se perdió: en el
cruce, al convertir la fecha, o al agrupar. Una línea, y cubre los tres.