Indice / Practicas / EJ-8.1
EJ-8.1 · Comparacion por sucursal con la tabla del grafico
Un gráfico de barras se lee en un segundo y se rompe con una fila. Este ejercicio produce la tabla que hay detrás del gráfico, el informe de lo que quedó afuera, y el gráfico, que no se compara con nada.
Qué hay que hacer
A partir de transacciones_sucursales.csv, producir dos archivos:
ventas_por_sucursal.csv: una fila por sucursal con la cantidad de transacciones válidas, los ingresos, los gastos y el neto.transacciones_rechazadas.csv: una fila por cada transacción que no entró, con su motivo.
Y además, aunque no se compare: el gráfico de barras del neto por sucursal, guardado como PNG, con título, eje rotulado y unidad.
ELI5Explicado facil
Es el resumen de caja de cinco locales. De cada uno querés saber cuánto entró, cuánto salió y con cuánto cerró.
Lo que hace interesante al ejercicio es lo otro: de doscientos movimientos, hay cinco que no se pueden usar. Uno dice que se cobraron cien millones de pesos. Si lo dejás, el gráfico muestra un local gigante y cuatro invisibles, y la conclusión que se saca es falsa.
Archivos
- Entrada ·
transacciones_sucursales.csv - Objetivo ·
ventas_por_sucursal.csv - Objetivo ·
transacciones_rechazadas.csv - Herramienta ·
comparar.py
El PNG no se compara, y eso no lo hace opcional
El gráfico que hagas va a diferir del mío en la fuente, en el ancho y en el color, y ninguna de esas diferencias significa nada. Por eso lo que se compara es la tabla, que es el contenido real del gráfico y sí tiene una única forma correcta.
Hacé el gráfico igual, y hacelo dos veces: una con las cinco filas adentro y otra con las cinco afuera. La diferencia entre esas dos imágenes es todo el ejercicio.
TX0188 tiene un importe de 99999999.99. Es una fila sobre
doscientas, y con ella adentro la sucursal Sur mide más que las otras cuatro juntas
por un factor de setecientos. El gráfico no está mal dibujado: está dibujando bien un
dato que no puede ser cierto.
Cómo saber si te salió
python comparar.py mi_resumen.csv ventas_por_sucursal.csv
Si no dice nada, tu archivo es idéntico al esperado. Si difiere, te dice qué fila y qué columna. No hay nota ni entrega: es para vos.
Antes de arrancar, mirá los datos
| id | fecha | sucursal | tipo | categoria | importe |
|---|---|---|---|---|---|
| TX0001 | 2026-07-25 | Centro | Ingreso | Ventas | 4441.09 |
| TX0002 | 2026-04-17 | Norte | Gasto | Servicios publicos | 3340.4 |
| TX0003 | 2026-06-11 | Centro | Gasto | Marketing | 2686.81 |
| TX0004 | 2026-03-09 | Centro | Ingreso | Servicios | 1741.92 |
| TX0005 | 2026-04-11 | Centro | Ingreso | Servicios | 3832.7 |
El contrato de salida
ventas_por_sucursal.csv · 5 filas
| Columna | Tipo | Que lleva |
|---|---|---|
sucursal | texto | El nombre de la sucursal, tal cual viene en el archivo |
transacciones | entero | Cuantas transacciones validas quedaron de esa sucursal |
ingresos | decimal | Suma de importe de las filas con tipo Ingreso, con 2 decimales |
gastos | decimal | Suma de importe de las filas con tipo Gasto, con 2 decimales |
neto | decimal | ingresos menos gastos, con 2 decimales |
transacciones_rechazadas.csv · 5 filas
| Columna | Tipo | Que lleva |
|---|---|---|
id | texto | El id de la transaccion rechazada |
sucursal | texto | La sucursal de esa transaccion |
motivo | texto | Uno del vocabulario cerrado de motivos |
Que hacer con cada defecto
| Defecto | Donde | Accion | Adonde va |
|---|---|---|---|
| importe vacio | 2 filas: TX0118 y TX0140 | rechazar rechazar la fila entera. No se puede sumar lo que no esta, y poner cero cambiaria el neto de la sucursal | transacciones_rechazadas.csv con motivo ausente |
| importe negativo | 1 fila: TX0090, con -450.0 | rechazar. rechazar. El signo de la operacion lo lleva la columna tipo, asi que un importe negativo es una contradiccion, no una devolucion | transacciones_rechazadas.csv con motivo negativo_incoherente |
| importe de ocho digitos | 1 fila: TX0188, con 99999999.99 | rechazar. rechazar. El dominio declarado del importe es de 0 a 100000 pesos | transacciones_rechazadas.csv con motivo fuera_de_rango |
| tipo escrito Ingresso | 1 fila: TX0067 | rechazar. rechazar. El dominio de tipo es exactamente {Ingreso, Gasto}. Corregirlo a Ingreso es una suposicion sobre la intencion de quien cargo el dato, y este contrato no la hace | transacciones_rechazadas.csv con motivo dominio_desconocido |
| acentos y enie en los nombres de categoria | en toda la columna categoria | no no se toca: esa columna no entra en ninguna de las dos salidas | ninguno |
Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:
- convertir importe a numero con errors='coerce'
- marcar los cuatro motivos de rechazo sobre las 200 filas de entrada
- escribir transacciones_rechazadas.csv con lo marcado
- agrupar lo que quedo por sucursal y tipo
- calcular neto y ordenar
Los rechazos se marcan sobre la tabla completa y antes de agrupar. Si se agrupa primero, las filas rechazadas ya sumaron y no hay forma de saber cuales eran.
Conteos clave
| filas de entrada | 200 |
|---|---|
| ventas_por_sucursal.csv | 5 |
| transacciones_rechazadas.csv | 5 |
| transacciones validas | 195 |
| sucursales con neto positivo | 1 |
| por motivo | ausente: 2, negativo_incoherente: 1, fuera_de_rango: 1, dominio_desconocido: 1 |
Invariantes
Comprobaciones que tu resultado tiene que cumplir. Corrilas vos mismo antes de comparar los archivos:
len(resumen) == 5
resumen['sucursal'].is_unique
int(resumen['transacciones'].sum()) == 195
len(rechazadas) == 5
rechazadas['id'].is_unique
set(rechazadas['motivo']) <= {'ausente', 'negativo_incoherente', 'fuera_de_rango', 'dominio_desconocido'}
(resumen['ingresos'] >= 0).all() and (resumen['gastos'] >= 0).all()
((resumen['neto'] - (resumen['ingresos'] - resumen['gastos'])).abs() < 0.01).all()
resumen['neto'].is_monotonic_decreasing
PISTASSi te trabas, abri esto
- Una fila puede violar mas de una regla. Con estos datos no pasa, y el codigo tiene que decidir igual cual motivo gana: el contrato fija el orden de la lista de politica_defectos.
- pivot_table con index='sucursal', columns='tipo' y aggfunc='sum' resuelve ingresos y gastos de una vez. Con fill_value=0, para que una sucursal sin gastos no quede en NaN.
- El grafico que pide el enunciado no se compara con comparar.py y hay que hacerlo igual: es la unica forma de ver por que sacar una fila de doscientas cambia la conclusion.
- Ojo con el orden de las columnas del pivot: pandas las devuelve alfabeticas, asi que Gasto viene antes que Ingreso.
Así tienen que quedar
| sucursal | transacciones | ingresos | gastos | neto |
|---|---|---|---|---|
| Centro | 36 | 83856.67 | 50618.6 | 33238.07 |
| Este | 38 | 58810.22 | 66023.42 | -7213.2 |
| Norte | 32 | 40521.93 | 61367.48 | -20845.55 |
| Oeste | 40 | 54273.54 | 79515.89 | -25242.35 |
| Sur | 49 | 54813.39 | 90456.92 | -35643.53 |
| id | sucursal | motivo |
|---|---|---|
| TX0067 | Sur | dominio_desconocido |
| TX0090 | Sur | negativo_incoherente |
| TX0118 | Este | ausente |
| TX0140 | Oeste | ausente |
| TX0188 | Sur | fuera_de_rango |
Ingresso no se corrige, y hay que poder defender por qué
Una fila tiene el tipo escrito Ingresso, con dos eses. Parece obvio que
quisieron poner Ingreso, y ese «parece obvio» es una suposición sobre la
intención de una persona que no está para confirmarla.
Este contrato la rechaza y la informa, que es la opción conservadora: se pierde una
transacción y queda registro de que existió. Corregirla también sería defendible
si se declara. Lo que no es defendible es corregirla en silencio, que
es lo que pasa cuando alguien escribe str.lower() sin pensar y el
isin deja de fallar.
El neto de cuatro de las cinco sucursales da negativo. ¿Eso quiere decir que el negocio pierde plata, o que el archivo no tiene todos los ingresos?
El ejercicio no lo puede contestar y el informe tiene que decirlo. Es la diferencia entre «Sur perdió 35.643 pesos» y «con los movimientos registrados en este archivo, Sur cierra en −35.643». La segunda es la que se puede defender.