Saltar al contenido

Indice / Practicas / EJ-8.1

EJ-8.1 · Comparacion por sucursal con la tabla del grafico

Practica de: 8.4 Barras: comparar categoriasDificultad: M

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:

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

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.

La fila que decide

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

transacciones_sucursales.csv (entrada) · 200 filas, se muestran 5
idfechasucursaltipocategoriaimporte
TX00012026-07-25CentroIngresoVentas4441.09
TX00022026-04-17NorteGastoServicios publicos3340.4
TX00032026-06-11CentroGastoMarketing2686.81
TX00042026-03-09CentroIngresoServicios1741.92
TX00052026-04-11CentroIngresoServicios3832.7

El contrato de salida

ventas_por_sucursal.csv · 5 filas

ColumnaTipoQue lleva
sucursaltextoEl nombre de la sucursal, tal cual viene en el archivo
transaccionesenteroCuantas transacciones validas quedaron de esa sucursal
ingresosdecimalSuma de importe de las filas con tipo Ingreso, con 2 decimales
gastosdecimalSuma de importe de las filas con tipo Gasto, con 2 decimales
netodecimalingresos menos gastos, con 2 decimales

Orden de las filas: neto descendente; si dos netos empatan, sucursal ascendente. El desempate no llega a usarse con estos datos y va igual: es lo que hace que dos soluciones correctas den el mismo archivo. Indice: no se escribe: to_csv(index=False)

transacciones_rechazadas.csv · 5 filas

ColumnaTipoQue lleva
idtextoEl id de la transaccion rechazada
sucursaltextoLa sucursal de esa transaccion
motivotextoUno del vocabulario cerrado de motivos

Orden de las filas: id ascendente. Es unico en el archivo de entrada. Indice: no se escribe: to_csv(index=False)

Que hacer con cada defecto

DefectoDondeAccionAdonde va
importe vacio2 filas: TX0118 y TX0140rechazar rechazar la fila entera. No se puede sumar lo que no esta, y poner cero cambiaria el neto de la sucursaltransacciones_rechazadas.csv con motivo ausente
importe negativo1 fila: TX0090, con -450.0rechazar. rechazar. El signo de la operacion lo lleva la columna tipo, asi que un importe negativo es una contradiccion, no una devoluciontransacciones_rechazadas.csv con motivo negativo_incoherente
importe de ocho digitos1 fila: TX0188, con 99999999.99rechazar. rechazar. El dominio declarado del importe es de 0 a 100000 pesostransacciones_rechazadas.csv con motivo fuera_de_rango
tipo escrito Ingresso1 fila: TX0067rechazar. 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 hacetransacciones_rechazadas.csv con motivo dominio_desconocido
acentos y enie en los nombres de categoriaen toda la columna categoriano no se toca: esa columna no entra en ninguna de las dos salidasninguno
Orden de comprobacion

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

  1. convertir importe a numero con errors='coerce'
  2. marcar los cuatro motivos de rechazo sobre las 200 filas de entrada
  3. escribir transacciones_rechazadas.csv con lo marcado
  4. agrupar lo que quedo por sucursal y tipo
  5. 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

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

filas de entrada200
ventas_por_sucursal.csv5
transacciones_rechazadas.csv5
transacciones validas195
sucursales con neto positivo1
por motivoausente: 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

ventas_por_sucursal.csv (objetivo) · 5 filas, se muestran 5
sucursaltransaccionesingresosgastosneto
Centro3683856.6750618.633238.07
Este3858810.2266023.42-7213.2
Norte3240521.9361367.48-20845.55
Oeste4054273.5479515.89-25242.35
Sur4954813.3990456.92-35643.53
transacciones_rechazadas.csv (objetivo) · 5 filas, se muestran 5
idsucursalmotivo
TX0067Surdominio_desconocido
TX0090Surnegativo_incoherente
TX0118Esteausente
TX0140Oesteausente
TX0188Surfuera_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.

Una pregunta para pensar antes de escribir código

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.