Indice / Practicas / EJ-5.3
EJ-5.3 · Dos pasadas de to_datetime
180 ventas con la fecha escrita de dos formas distintas. Una sola
pasada de to_datetime pierde datos o los interpreta mal; dos pasadas
bien puestas no pierden ninguno de los que se pueden salvar.
Qué hay que hacer
A partir de ventas_mensuales.csv, producir dos archivos:
ventas_fechadas.csv- las ventas con la fecha interpretada, más el año y el mes en columnas propias.ventas_rechazadas.csv- una fila por venta cuya fecha no se pudo interpretar, con el valor original y el motivo.
ELI5Explicado facil
Te dan 180 recibos. Casi todos tienen la fecha escrita 2026-03-15, pero uno la tiene 15/03/2026 y otro dice 30 de febrero, que no existe.
Si leés todos con la misma regla, el que está al revés se pierde. Y si cambiás la regla para todos, corrés el riesgo de leer mal los que estaban bien. La solución es leer primero los que entran con la regla común, y después volver solo sobre los que sobraron con la otra regla.
Archivos
- Entrada ·
ventas_mensuales.csv - Objetivo ·
ventas_fechadas.csv - Objetivo ·
ventas_rechazadas.csv - Herramienta ·
comparar.py
Por qué dos pasadas y no una
La tentación es dejar que to_datetime adivine el formato fila por
fila. El problema es una fecha como 03/04/2026: puede ser el 3 de abril
o el 4 de marzo, y las dos lecturas son válidas. Si pandas elige por
vos, elige en silencio, y no hay forma de darse cuenta después.
Declarar el formato mayoritario en la primera pasada es la decisión que evita eso. La segunda pasada corre solo sobre lo que quedó, así que no puede tocar nada que ya se había interpretado bien.
Cómo saber si te salió
python comparar.py mi_fechadas.csv ventas_fechadas.csv
python comparar.py mi_rechazadas.csv ventas_rechazadas.csv
Antes de arrancar, mirá los datos
| fecha | vendedor | producto | unidades | precio_unitario |
|---|---|---|---|---|
| 2026-03-02 | Carlos Ruiz | Auriculares | 1 | 57.24 |
| 2026-03-26 | Carlos Ruiz | Auriculares | 5 | 57.73 |
| 2026-03-31 | Beatriz Molina | Auriculares | 1 | 59.37 |
| 2026-02-09 | Lucía Vega | Webcam | 6 | 45.42 |
| 2026-03-27 | Carlos Ruiz | Laptop | 7 | 914.44 |
El contrato de salida
ventas_fechadas.csv · 175 filas
| Columna | Tipo | Que lleva |
|---|---|---|
fila_origen | entero | Posicion de la fila en el archivo de entrada, empezando en 0 |
fecha | texto | La fecha ya interpretada, escrita como AAAA-MM-DD |
anio | entero | El anio, con .dt.year |
mes | entero | El mes 1 a 12, con .dt.month |
vendedor | texto | Tal cual vino, solo sin espacios en los extremos |
producto | texto | Tal cual vino, solo sin espacios en los extremos |
unidades | entero | Convertido con to_numeric(errors='coerce'). NO se valida: puede quedar vacio o negativo |
precio_unitario | decimal | Convertido con to_numeric(errors='coerce') y redondeado a 2 decimales. NO se valida |
ventas_rechazadas.csv · 5 filas
| Columna | Tipo | Que lleva |
|---|---|---|
fila_origen | entero | Posicion de la fila descartada en el archivo de entrada |
fecha_original | texto | El valor de fecha tal cual venia, sin limpiar |
motivo | texto | ausente o fecha_invalida |
Que hacer con cada defecto
| Defecto | Donde | Accion | Adonde va |
|---|---|---|---|
| fecha en formato dd/mm/aaaa (15/03/2026) | 1 fila, la 120 | recuperar recuperar con una SEGUNDA pasada de to_datetime, solo sobre lo que la primera no interpreto | ventas_fechadas.csv, con fecha 2026-03-15 |
| fecha que no existe en el calendario (2026-02-30) | 1 fila, la 76 | descartar descartar | ventas_rechazadas.csv con motivo fecha_invalida |
| fecha vacia | 4 filas: 44, 89, 134 y 179 | descartar descartar | ventas_rechazadas.csv con motivo ausente |
| unidades vacia (1 fila) y negativas (2 filas) | unidades | NO NO se validan en este ejercicio: se convierten y pasan | ventas_fechadas.csv, con el vacio como campo vacio |
| precio_unitario vacio y con el token N/A | 2 filas | NO NO se valida en este ejercicio: to_numeric con coerce los deja ausentes | ventas_fechadas.csv, con el ausente como campo vacio |
Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:
- primera pasada con format='%Y-%m-%d'
- segunda pasada con format='%d/%m/%Y', SOLO sobre las que quedaron sin interpretar y no estan vacias
- campo vacio -> ausente
- resto sin interpretar -> fecha_invalida
La segunda pasada tiene que correr solo sobre el resto. Si se aplicara al total, una fecha como 2026-03-02 podria reinterpretarse con el dia y el mes cambiados. Y el vacio se comprueba antes que fecha_invalida para que una fecha que falta no se informe como una fecha rota.
Conteos clave
| filas de entrada | 180 |
|---|---|
| ventas_fechadas.csv | 175 |
| ventas_rechazadas.csv | 5 |
| recuperadas por la segunda pasada | 1 |
| interpretadas en la primera pasada | 174 |
| por motivo | ausente: 4, fecha_invalida: 1 |
Invariantes
Comprobaciones que tu resultado tiene que cumplir. Corrilas vos mismo antes de comparar los archivos:
len(fechadas) + len(rechazadas) == 180
fechadas['fila_origen'].is_unique
rechazadas['fila_origen'].is_unique
fechadas['anio'].eq(2026).all()
fechadas['mes'].between(1, 12).all()
(fechadas['fecha'].str.len() == 10).all()
set(rechazadas['motivo']) <= {'ausente', 'fecha_invalida'}
'2026-03-15' in set(fechadas['fecha'])
PISTASSi te trabas, abri esto
- Sin format, to_datetime adivina. Con dos formatos mezclados eso es exactamente lo que no queres: declara el formato mayoritario y despues ocupate del resto.
- errors='coerce' no levanta excepcion: deja NaT. Eso es lo que permite hacer la segunda pasada sobre lo que quedo.
- 2026-02-30 no existe. Con coerce da NaT en silencio, y por eso hay que contarlo: nadie te va a avisar.
- fillna sobre una Series de fechas rellena con la otra Series alineada por indice. Es la forma corta de juntar las dos pasadas.
- fila_origen no esta en el archivo de entrada: hay que crearla. Sin ella no hay ninguna columna unica para ordenar.
Así tiene que quedar
| fila_origen | fecha | anio | mes | vendedor | producto | unidades | precio_unitario |
|---|---|---|---|---|---|---|---|
| 0 | 2026-03-02 | 2026 | 3 | Carlos Ruiz | Auriculares | 1 | 57.24 |
| 1 | 2026-03-26 | 2026 | 3 | Carlos Ruiz | Auriculares | 5 | 57.73 |
| 2 | 2026-03-31 | 2026 | 3 | Beatriz Molina | Auriculares | 1 | 59.37 |
| 3 | 2026-02-09 | 2026 | 2 | Lucía Vega | Webcam | 6 | 45.42 |
| 4 | 2026-03-27 | 2026 | 3 | Carlos Ruiz | Laptop | 7 | 914.44 |
| fila_origen | fecha_original | motivo |
|---|---|---|
| 44 | vacio | ausente |
| 76 | 2026-02-30 | fecha_invalida |
| 89 | vacio | ausente |
| 134 | vacio | ausente |
| 179 | vacio | ausente |
Una. Una fecha vacía y una fecha que no existe son problemas
distintos, y van con motivos distintos: ausente y
fecha_invalida. Con errors="coerce" las dos terminan como
NaT y se vuelven indistinguibles, así que hay que separarlas
antes de convertir.
Dos. fila_origen no viene en el archivo: hay que
crearla. Sin ella no hay ninguna columna única para ordenar, y dos soluciones
correctas podrían entregar las filas en otro orden.
Hay unidades negativas y precios ausentes en el archivo, y pasan:
se convierten y se escriben tal cual queden. No es un olvido, está en el contrato.
Un ejercicio que decide sobre la fecha decide sobre la fecha; validar los importes es
el trabajo de EJ-5.8.