Indice / Practicas / EJ-5.2
EJ-5.2 · Limpieza integral de clientes
Tenés el padrón de clientes tal como salió del sistema: 120 filas con nombres mal tipeados, correos rotos, edades imposibles y campos vacíos. Hay que dejarlo usable, y dejar constancia de todo lo que se descartó.
Qué hay que hacer
A partir de un archivo de entrada, producir dos archivos de salida:
clientes_limpios.csv- los clientes que sirven, normalizados.clientes_rechazados.csv- una fila por cliente descartado, con la columna que tenía el problema, el valor tal cual venía, y el motivo.
Nadie te va a decir cómo. Lo que sí está fijado, hasta el último detalle, es cómo tiene que quedar el resultado: eso es el contrato de más abajo. Cuando tu archivo coincida con el objetivo, terminaste.
ELI5Explicado facil
Te dan una lista de 120 clientes hecha un desastre y te piden dos listas nuevas: una con los que están bien y otra con los que tiraste, diciendo por qué.
La segunda lista es la parte que más se olvida y la más importante: si tirás 33 fichas y no anotás cuáles ni por qué, nadie puede revisar tu trabajo - ni vos mismo dentro de dos semanas.
Archivos
- Entrada ·
clientes_crudos.csv - Objetivo ·
clientes_limpios.csv - Objetivo ·
clientes_rechazados.csv - Herramienta ·
comparar.py
Cómo saber si te salió
Bajá el objetivo y comparar.py, y corré:
python comparar.py mi_salida.csv clientes_limpios.csv
Si dice OK, tu archivo es idéntico al esperado. Si no, te dice qué
columna, qué fila y qué valor difiere. No hay nota ni entrega: es para vos.
Agregá --laxo para que compare solo los valores y no los tipos. Sirve
para confirmar que tu lógica está bien antes de pelearte con int64 contra
float64. Después sacalo y hacelo coincidir del todo.
Antes de arrancar, mirá los datos
| id | nombre | correo | edad | ciudad | fecha_alta |
|---|---|---|---|---|---|
| C0001 | José Paz | jose.paz@example.com | 62 | Salta | 2025-02-20 |
| C0002 | Lucía Peña | lucia.pena@example.com | 48 | Salta | 2025-01-05 |
| C0003 | Santiago Herrera | santiago.herrera@example.com | 40 | Salta | 2024-05-12 |
| C0004 | José Rodríguez | jose.rodriguez@example.com | 67 | Paraná | 2024-08-11 |
| C0005 | Walter Gutiérrez | walter.gutierrez@example.com | 34 | Rosario | 2026-01-01 |
Fijate que la columna edad tiene de todo: números, la palabra
treinta, un guion, campos vacíos y edades imposibles. Y que hay nombres
con espacios de más y correos en mayúsculas.
El contrato de salida
Todo lo que sigue es obligatorio. Si dos personas resuelven el ejercicio bien, tienen que producir exactamente el mismo archivo; para eso está fijado el orden de las filas, el nombre y el orden de las columnas, y qué se hace con cada defecto.
clientes_limpios.csv · 87 filas
| Columna | Tipo | Que lleva |
|---|---|---|
id | texto | El id original, sin tocar |
nombre | texto | Sin espacios sobrantes y con mayusculas iniciales: .strip().title() |
correo | texto | Sin espacios sobrantes y en minusculas: .strip().lower() |
edad | entero | Entre 18 y 100. Los ausentes ya vienen imputados |
ciudad | texto | La ciudad, o 'Sin dato' si venia vacia |
fecha_alta | texto | La fecha tal cual, o vacio si era 0000-00-00 |
edad_imputada | entero | 1 si la edad la inventamos nosotros, 0 si vino en el archivo |
clientes_rechazados.csv · 33 filas
| Columna | Tipo | Que lleva |
|---|---|---|
id_origen | texto | El id de la fila descartada |
columna | texto | En que columna estaba el problema |
valor_original | texto | El valor tal cual venia en el archivo, sin limpiar |
motivo | texto | Uno del vocabulario cerrado del curso |
Que hacer con cada defecto
| Defecto | Donde | Accion | Adonde va |
|---|---|---|---|
| nombre vacio | C0044, C0091 | descartar descartar | clientes_rechazados.csv con motivo ausente |
| correo vacio | C0058, C0103 | descartar descartar | clientes_rechazados.csv con motivo ausente |
| correo sin arroba | cada 13 filas | descartar descartar | clientes_rechazados.csv con motivo restriccion_violada |
| edad no numerica ('treinta') | cada 31 filas | descartar descartar | clientes_rechazados.csv con motivo no_numerico |
| edad fuera de 18 a 100 (15, 16, 17, 101, 128, 150, -5) | cada 17, 19 y 23 filas | descartar descartar | clientes_rechazados.csv con motivo fuera_de_rango |
| edad ausente (vacio, NaN, N/A, null, -) | cada 29 filas | imputar imputar con la mediana entera de las edades validas de las filas que sobreviven (da 46) | clientes_limpios.csv con edad_imputada = 1 |
| fecha_alta = 0000-00-00 | C0066, C0077 | conservar conservar la fila, vaciar el campo | clientes_limpios.csv con fecha_alta vacia |
| ciudad vacia | C0088 | conservar conservar la fila, reemplazar por 'Sin dato' | clientes_limpios.csv |
| nombre con espacios y en minusculas / correo en mayusculas | cada 7 y cada 11 filas | normalizar, normalizar, no descartar | clientes_limpios.csv |
Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:
- nombre vacio
- correo vacio
- correo sin arroba
- edad no numerica
- edad fuera de rango
Una fila puede tener mas de un problema. Se informa SOLO el primero de esta lista, asi cada fila descartada aparece una unica vez y vale que limpios + rechazados = 120. Cambiar el orden cambia el archivo de rechazos aunque la limpieza sea la misma.
Conteos clave
| filas de entrada | 120 |
|---|---|
| clientes_limpios.csv | 87 |
| clientes_rechazados.csv | 33 |
| edades imputadas | 3 |
| mediana usada para imputar | 46 |
| ciudades 'Sin dato' | 1 |
| fechas vaciadas | 2 |
| por motivo | fuera_de_rango: 18, restriccion_violada: 8, ausente: 4, no_numerico: 3 |
Invariantes
Comprobaciones que tu resultado tiene que cumplir. Corrilas vos mismo antes de comparar los archivos:
len(limpios) + len(rechazados) == 120
limpios['id'].is_unique
limpios['edad'].between(18, 100).all()
limpios['correo'].str.contains('@').all()
(limpios['nombre'].str.strip() != '').all()
limpios['edad_imputada'].sum() == 3
set(rechazados['motivo']) <= {'ausente', 'no_numerico', 'fuera_de_rango', 'restriccion_violada'}
rechazados['id_origen'].is_unique
PISTASSi te trabas, abri esto
- Leer con dtype=str y keep_default_na=False es lo que te deja informar el valor_original tal cual estaba.
- El guion no es un ausente para pandas: hay que declararlo.
- La mediana se calcula sobre las filas que sobreviven, no sobre las 120. Si la sacas antes de descartar te da otro numero.
- edad_imputada vale 1 solo cuando la edad FALTABA, no cuando era invalida: las invalidas se descartan.
Así tiene que quedar
| id | nombre | correo | edad | ciudad | fecha_alta | edad_imputada |
|---|---|---|---|---|---|---|
| C0001 | José Paz | jose.paz@example.com | 62 | Salta | 2025-02-20 | 0 |
| C0002 | Lucía Peña | lucia.pena@example.com | 48 | Salta | 2025-01-05 | 0 |
| C0003 | Santiago Herrera | santiago.herrera@example.com | 40 | Salta | 2024-05-12 | 0 |
| C0004 | José Rodríguez | jose.rodriguez@example.com | 67 | Paraná | 2024-08-11 | 0 |
| C0005 | Walter Gutiérrez | walter.gutierrez@example.com | 34 | Rosario | 2026-01-01 | 0 |
| id_origen | columna | valor_original | motivo |
|---|---|---|---|
| C0013 | correo | ines.herrera.example.com | restriccion_violada |
| C0017 | edad | 17 | fuera_de_rango |
| C0019 | edad | 128 | fuera_de_rango |
| C0023 | edad | -5 | fuera_de_rango |
| C0026 | correo | ines.castro.example.com | restriccion_violada |
La mediana para imputar se calcula sobre las filas que sobreviven, no sobre las 120. Si la sacás antes de descartar, te va a dar otro número y todas las edades imputadas van a estar mal. Es el único paso donde el orden de las operaciones cambia el resultado.