Saltar al contenido

Indice / Practicas / EJ-6.8

EJ-6.8 · Resumen por cliente en dos niveles

Practica de: 6.4 groupbyDificultad: M

202 líneas de pedido y 15 clientes. El resumen necesita dos agrupaciones encadenadas, porque el ticket promedio de un cliente no es el promedio de sus líneas: es el promedio de sus pedidos.

Qué hay que hacer

A partir de pedidos_lineas.csv, producir resumen_cliente.csv: una fila por cliente, con cuántos pedidos hizo, cuántas líneas, cuántas unidades, cuánto gastó y su ticket promedio.

ELI5Explicado facil

Un cliente hizo 2 pedidos: uno de $100 con cinco productos y otro de $200 con uno solo.

Su ticket promedio es $150 - el promedio de sus dos pedidos. Si promediás las seis líneas, te da $50, que no es el promedio de nada que le importe a nadie. El pedido de cinco productos pesaría cinco veces más solo por tener más renglones.

Archivos

Los dos niveles

  1. Por pedido: agrupar por (cliente_id, pedido) y sumar los importes. Cada pedido pasa a ser una fila.
  2. Por cliente: agrupar el resultado por cliente_id, contar pedidos y sumar totales.

Con una sola agrupación se puede calcular el total gastado, pero no la cantidad de pedidos ni el ticket. Ese es el motivo de la cadena.

Cómo saber si te salió

python comparar.py mi_salida.csv resumen_cliente.csv

Antes de arrancar, mirá los datos

pedidos_lineas.csv (entrada) · 202 filas, se muestran 5
cliente_idclientepedidoproductocantidadprecio
7Horacio Duarte1001Auriculares556.5
7Horacio Duarte1001Ratón215.58
7Horacio Duarte1001Disco SSD586.64
10Ciro Ortiz1002Teclado425.41
14Agustín Bravo1003Docking3138.04

El contrato de salida

resumen_cliente.csv · 15 filas

ColumnaTipoQue lleva
cliente_identeroLa clave del cliente
clientetextoSu nombre, completado desde sus otras lineas si falta
pedidosenteroCuantos pedidos distintos hizo
lineasenteroCuantas lineas de pedido tiene en total
lineas_sin_importeenteroLineas donde falta la cantidad o el precio y no se pudo calcular el importe
unidadesenteroSuma de cantidad de todas sus lineas
totaldecimalSuma de cantidad por precio, redondeada a 2 decimales
ticket_promediodecimaltotal dividido por pedidos, redondeado a 2 decimales

Orden de las filas: cliente_id ascendente. Es unico. Indice: no se escribe: to_csv(index=False)

Que hacer con cada defecto

DefectoDondeAccionAdonde va
el nombre del cliente falta en una linea1 linea del cliente 15completar completar desde las otras lineas del MISMO cliente_id, que si lo tienen. No es imputar: es recuperar un dato que esta en la tablaresumen_cliente.csv
cantidad o precio ausentes3 lineas, en los clientes 5, 11 y 13el el importe de esa linea queda ausente y no suma al total. La linea se cuenta en lineas y en lineas_sin_importeresumen_cliente.csv
una cantidad de 99.999 unidades1 linea del cliente 10NO NO se descarta: este ejercicio agrupa, no valida rangos. El resumen lo deja a la vistaresumen_cliente.csv
un precio de -10.00 y una cantidad de -22 lineasNO NO se validan. Restan del total de su cliente, que es lo que dicen los datosresumen_cliente.csv
Orden de comprobacion

Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:

  1. completar el nombre faltante desde el mismo cliente_id
  2. calcular el importe de cada linea y redondearlo
  3. PRIMER nivel: agrupar por (cliente_id, pedido) y sumar el importe
  4. SEGUNDO nivel: agrupar por cliente_id, contar pedidos y sumar
  5. calcular el ticket promedio como total / pedidos

El ticket promedio es el promedio por PEDIDO, no por linea. Calcularlo como mean() del importe de las lineas da otro numero: un pedido de 5 lineas pesaria cinco veces mas que uno de 1. Y el redondeo del importe va por linea, antes de sumar, para que el total no dependa de cuantos decimales arrastre cada multiplicacion.

Conteos clave

Si estos numeros no te dan, algo se desvio antes de que llegues a comparar los archivos.

lineas de entrada202
resumen_cliente.csv15
pedidos distintos85
lineas sin importe3
cliente con mas pedidos9
cliente con menos pedidos2
por motivo

Invariantes

Comprobaciones que tu resultado tiene que cumplir. Corrilas vos mismo antes de comparar los archivos:

len(resumen) == 15
resumen['cliente_id'].is_unique
int(resumen['lineas'].sum()) == 202
int(resumen['pedidos'].sum()) == 85
int(resumen['lineas_sin_importe'].sum()) == 3
(resumen['lineas_sin_importe'] <= resumen['lineas']).all()
(resumen['pedidos'] <= resumen['lineas']).all()
resumen['cliente'].notna().all()
(abs(resumen['ticket_promedio'] - resumen['total'] / resumen['pedidos']) < 0.01).all()
PISTASSi te trabas, abri esto
  • Dos agrupaciones encadenadas: primero por (cliente, pedido) y despues por cliente. Con una sola no se puede contar pedidos ni sacar el ticket.
  • nunique() sobre la columna pedido cuenta pedidos distintos; size() contaria lineas.
  • El nombre que falta esta en otras lineas del mismo cliente: se recupera con un map desde una tabla de nombres, no se inventa.
  • El cliente 10 tiene 100.042 unidades por una sola linea de 99.999. No hay que descartarla: hay que verla.

Así tiene que quedar

resumen_cliente.csv (objetivo) · 15 filas, se muestran 5
cliente_idclientepedidoslineaslineas_sin_importeunidadestotalticket_promedio
1Fabián Zapata6140403517.96586.33
2Diego Acosta5110313009.01601.8
3Irene Ramos270191104.85552.42
4Rocío Molina590332822.87564.57
5José Acosta25113824.55412.28
El nombre que falta se recupera, no se inventa

Una línea del cliente 15 no tiene el nombre, y las otras 22 líneas del mismo cliente_id sí lo tienen. Completarlo desde ahí no es imputar: es usar un dato que ya está en la tabla.

La diferencia con imputar una mediana es que acá no hay ninguna estimación: el valor correcto está a la vista, en otra fila.

El cliente 10 y sus 100.042 unidades

Una sola línea con 99999 unidades le da a ese cliente un total de 5,7 millones cuando el resto se mueve entre 800 y 5.000. También hay una cantidad negativa y un precio de -10.00.

Ninguno se descarta: este ejercicio agrupa, no valida rangos. Y el resumen los deja a la vista, que es lo que un resumen tiene que hacer. Decidir qué hacer con ellos exige una política, y esa política la escribe otro enunciado.