Python · Capítulo 8 · Lección de conceptos · 7 min de lectura

Limpiar es lograr que lo igual sea igual

pandas compara los valores de forma exacta y trata NaN como un 'desconocido' especial. Limpiar es lograr que los valores que significan lo mismo se vean igual, y decidir qué significa de verdad un dato ausente.

La idea

pandas no entiende tus datos. Compara los valores de forma exacta, carácter por carácter. 'Dallas', 'dallas' y 'Dallas ' (con un espacio al final) son tres valores distintos para él, y 'Dallas' y 'Boston' no son más diferentes entre sí de lo que lo son esos tres.

Ese es todo el problema. Harbor & Pine tiene 2,018 clientes y quienes los escribieron eran personas. Las ciudades tienen espacios y mayúsculas en sitios raros. Algunos clientes aparecen dos veces. Nueve productos no tienen costo registrado. Dos docenas de líneas de pedido tienen cantidad cero o negativa. Nada de eso da un error: groupby, mean y merge se ejecutan y te dan un número. El número está mal porque los grupos o las filas estaban mal.

Limpiar es, por tanto, una idea en tres formas: lograr que los valores que significan lo mismo se vean igual (texto), decidir qué significa un valor ausente (NaN) y decidir qué filas son en realidad una sola entidad (duplicados).

Figura 1

Cuatro grafías de Dallas se convierten en un solo grupo

customers.city.value_counts()
citycustomers
'Dallas'78
' dallas '3
'Dallas '1
'DALLAS'1
Después de .str.strip().str.title()
citycustomers
Dallas83

Paso 1 de 4 Dallas aparece cuatro veces en los datos. Para pandas son cuatro cadenas distintas, así que groupby('city') crea cuatro grupos y el mayor tiene solo 78 clientes.

  1. Dallas aparece cuatro veces en los datos. Para pandas son cuatro cadenas distintas, así que groupby('city') crea cuatro grupos y el mayor tiene solo 78 clientes.
  2. .str.strip() quita los espacios de los extremos. ' dallas ' pasa a 'dallas' y 'Dallas ' pasa a 'Dallas'.
  3. .str.title() arregla las mayúsculas, así que 'dallas' y 'DALLAS' pasan a 'Dallas'. Ahora todas las variantes son la misma cadena.
  4. Una grafía, un grupo: 83 clientes. El conteo nunca estuvo mal, lo que estaba mal eran los grupos.

Por qué existe

Los datos reales vienen de formularios, exportaciones, importaciones de otros sistemas y personas un viernes por la tarde. Nadie diseñó ' dallas '. Si te saltas la limpieza, el informe se genera igual, y ese es el peligro: un gráfico de ciudades principales que dice que Dallas tiene 78 clientes parece perfectamente razonable. Un colega lo repite en una reunión, y un mes después un conteo real dice 83.

Limpiar también tiene un costo. Cada paso es una decisión sobre lo que significan los datos, y una decisión puede ser equivocada. El hábito que lo hace seguro es limpiar con código, nunca editando el archivo a mano, para que los pasos queden escritos y se puedan repetir con la exportación del mes siguiente.

Cómo funciona por dentro

Texto: normaliza antes de comparar

El acceso .str aplica una operación de texto a todos los valores de una columna a la vez. La receta habitual es strip() para los extremos, lower(), upper() o title() para las mayúsculas y replace() para variantes conocidas. Los valores ausentes pasan sin tocarse: .str.strip() sobre un NaN da NaN, no un error ni la cadena 'nan'. Un método .str sobre una columna que no es texto, como números guardados como texto con un $, necesita que quites el símbolo y uses después astype(float) o pd.to_numeric.

Normaliza una copia de la columna, úsala para agrupar o comparar, y conserva el original si necesitas mostrar el original. Pruébalo como lo haría una persona: df.city.nunique() antes y después te dice cuántas grafías uniste.

Valores ausentes: omitidos o propagados

NaN significa "desconocido". isna() lo encuentra, fillna(x) lo reemplaza, dropna() elimina la fila. La mayoría de los resúmenes de pandas omiten NaN por defecto, así que mean() de una columna con huecos promedia solo lo que hay, y sum() trata los huecos como nada. La aritmética entre columnas funciona distinto: un NaN en unit_cost hace que unit_price - unit_cost sea NaN en esa fila, y el margen de ese producto desaparece.

Figura 2

Costo unitario medio (unit_cost) de los 20 productos de Furniture, de dos formas

$0 $100 $200 $300 $400 Costo unitario medio mean() as it is: $308fillna(0).mean(): $231 mean() as it isfillna(0).mean() 15 costos conocidos

Paso 1 de 3 Cinco de los 20 productos de Furniture no tienen unit_cost registrado. mean() omite los NaN y promedia los 15 costos que existen: $308.11.

  1. Cinco de los 20 productos de Furniture no tienen unit_cost registrado. mean() omite los NaN y promedia los 15 costos que existen: $308.11.
  2. fillna(0) convierte los cinco desconocidos en costos de cero dólares y divide entre 20. La respuesta baja a $231.08, y todo margen calculado con ella parece mejor de lo que es.
  3. Ningún número es correcto automáticamente. Omitir es honesto cuando los costos ausentes se parecen a los conocidos. Rellenar con 0 afirma que cinco sofás y camas no cuestan nada.

Así que hay tres opciones honestas, y cada una afirma algo distinto. Déjalo como NaN y el resumen describe las filas que tienen datos. dropna() hace lo mismo pero de forma explícita. fillna() con un valor afirma que sabes cuál habría sido el número. Usa fillna(0) solo cuando cero es de verdad lo que significa el dato ausente, por ejemplo un discount_pct vacío en una línea de pedido que no tuvo descuento. Todo lo demás es un juicio, y el informe debe decir cuál tomaste.

Tipos: fechas y números guardados como texto

Una columna de fechas leída como texto se ordena alfabéticamente y no se puede agrupar por mes. pd.to_datetime la convierte en marcas de tiempo reales, y entonces funcionan .dt.year, .dt.month y .dt.day_name(). Añade errors='coerce' para convertir las entradas imposibles de leer en NaT en lugar de detenerte, y luego cuenta los NaT: son las filas que aún tienes que explicar. pd.to_numeric(..., errors='coerce') hace lo mismo con los números.

Duplicados: la misma entidad, escrita distinto

df.duplicated() marca una fila como True cuando una fila anterior tiene valores idénticos en las columnas que nombras. Idéntico es la palabra clave, así que los duplicados escritos distinto pasan, salvo que normalices antes.

Figura 3

La misma persona se registró dos veces

customers
customer_idemailsignup_date
1698bjones76@gmail.com2025-10-12
2014BJONES76@GMAIL.COM2025-12-31
2015jack.nguyen@icloud.com2025-12-31
email.str.strip().str.lower()
customer_idemail_key
1698bjones76@gmail.com
2014bjones76@gmail.com
2015jack.nguyen@icloud.com

Paso 1 de 4 El cliente 2014 parece nuevo, con un registro en diciembre. Pero su email es la misma dirección que la del cliente 1698, escrita en mayúsculas.

  1. El cliente 2014 parece nuevo, con un registro en diciembre. Pero su email es la misma dirección que la del cliente 1698, escrita en mayúsculas.
  2. duplicated('email') compara el texto tal cual. 'bjones76@gmail.com' y 'BJONES76@GMAIL.COM' son cadenas distintas, así que pandas no ve ningún duplicado aquí.
  3. Normaliza primero y compara después. Tras strip() y lower() las dos filas tienen la misma clave. En toda la tabla esto encuentra 18 emails repetidos, frente a 6 con el texto original.
  4. Conserva el primer registro (keep='first') y el cliente 2014 desaparece. Sin eso, los 'clientes nuevos' se cuentan de más.

Elige con cuidado la clave que define "lo mismo". El mismo email es una buena clave para clientes. El mismo nombre y ciudad no lo es: dos personas distintas pueden compartir nombre y ciudad. Después decide qué conservar con drop_duplicates(subset=..., keep='first'), y mira unas cuantas filas eliminadas antes de confiar en el resultado.

Filas malas

Una cantidad de 0 o -2 no es un error tipográfico que arreglar, es una fila que no describe una venta. Fíltrala con una máscara booleana y cuenta lo que quitaste: (order_items.quantity <= 0).sum() son 24 filas. Un paso de limpieza que elimina filas en silencio es una fuente de totales que no cuadran, así que una nota breve de qué se fue y por qué debe acompañar al resultado.

Errores comunes

"pandas tratará Dallas y DALLAS como la misma ciudad"

No lo hará. La comparación de cadenas es exacta, en groupby, merge, isin, duplicated y ==. El arreglo es normalizar el texto antes de la operación. Cuando un conteo parece demasiado pequeño, ejecuta nunique() y value_counts() sobre la columna y mira la cola: las variantes suelen estar ahí.

"fillna(0) es la forma segura de deshacerse de NaN"

Es la forma que mejor esconde el problema. Cero es un número, y participa en cada media, suma y razón que viene después. Un producto con costo ausente no cuesta nada. Rellena solo con un valor que puedas defender; si no, deja el NaN a la vista y di que esas filas no están incluidas.

"drop_duplicates() elimina a los clientes duplicados"

Solo elimina copias exactas de la fila completa. Dos registros de la misma persona tienen distinto customer_id y signup_date, así que drop_duplicates() sin argumentos no quita nada. Nombra las columnas que definen la entidad (subset=) después de normalizarlas, y conserva el registro más antiguo.

Ponte a prueba

Una pregunta. Al responderla, la lección queda marcada en el mapa; nunca bloquea un nivel.

customers.city tiene 'Dallas' 78 veces, ' dallas ' 3 veces, 'Dallas ' una vez y 'DALLAS' una vez. ¿Qué te da customers['city'].value_counts().idxmax() y es correcto el conteo de Dallas?

Elige qué puede ejecutarse en este sitio. Puedes cambiarlo cuando quieras desde "Configurar cookies", al pie de cada página.