Los datos reales rara vez llegan listos para analizar. Es común encontrar celdas vacías, nombres escritos de varias formas, números guardados como texto, fechas inválidas, espacios invisibles, filas duplicadas y valores extremos. Si estos problemas no se corrigen, cualquier gráfico, informe o modelo puede producir conclusiones equivocadas. Python y Pandas ofrecen un conjunto de herramientas muy completo para crear un proceso de limpieza repetible y verificable.
Antes de empezar, conviene conocer los fundamentos de Pandas en Python, el trabajo con archivos CSV, el uso de Pandas y NumPy para análisis y la creación de funciones reutilizables. Una limpieza profesional no consiste en modificar datos al azar, sino en aplicar reglas explícitas y conservar evidencias de cada transformación.
Cargar los datos sin perder información
import pandas as pd
df = pd.read_csv(
"clientes.csv",
dtype={"codigo_postal": "string"},
encoding="utf-8"
)Definir tipos durante la lectura evita que valores como 00123 pierdan ceros iniciales. Para archivos grandes, puedes leer por bloques con chunksize. Antes de cambiar nada, inspecciona el conjunto:
print(df.head())
print(df.info())
print(df.describe(include="all"))
print(df.isna().sum())Estas operaciones revelan columnas vacías, tipos inesperados y distribuciones sospechosas.
Normalizar nombres de columnas
Los encabezados pueden contener mayúsculas, espacios o símbolos. Una convención consistente facilita el resto del trabajo:
df.columns = (
df.columns
.str.strip()
.str.lower()
.str.replace(" ", "_", regex=False)
.str.replace(r"[^a-z0-9_]", "", regex=True)
)Después de normalizar, verifica que no se hayan creado nombres duplicados. Por ejemplo, “Correo electrónico” y “Correo-electrónico” podrían terminar como la misma columna.
Eliminar espacios y unificar texto
columnas_texto = ["nombre", "ciudad", "email"]
for columna in columnas_texto:
df[columna] = df[columna].astype("string").str.strip()Para campos categóricos, unifica mayúsculas y variantes:
df["ciudad"] = df["ciudad"].str.casefold()
reemplazos = {
"sao paulo": "São Paulo",
"são paulo": "São Paulo",
"sp": "São Paulo",
}
df["ciudad"] = df["ciudad"].replace(reemplazos)No apliques una corrección automática sin revisar su significado. Una abreviatura puede representar valores distintos según el contexto.
Tratar valores ausentes
Pandas representa muchos valores faltantes con NaN o pd.NA. La documentación oficial sobre datos ausentes explica cómo se comportan según el tipo de columna.
df["telefono"] = df["telefono"].replace(
["", "N/A", "n/a", "sin datos", "-"],
pd.NA
)Después decide qué hacer. Algunas filas pueden eliminarse si falta un identificador imprescindible:
df = df.dropna(subset=["id_cliente"])Otras columnas pueden rellenarse con un valor explícito:
df["ciudad"] = df["ciudad"].fillna("No informada")No reemplaces automáticamente números faltantes por cero. Cero y ausencia son conceptos diferentes. Para modelos estadísticos, documenta el método de imputación.
Convertir números almacenados como texto
df["importe"] = (
df["importe"]
.astype("string")
.str.replace("€", "", regex=False)
.str.replace(".", "", regex=False)
.str.replace(",", ".", regex=False)
)
df["importe"] = pd.to_numeric(df["importe"], errors="coerce")errors="coerce" convierte entradas inválidas en valores ausentes. Esto es más seguro que ignorar silenciosamente el problema. Guarda las filas que fallaron para revisarlas:
invalidos = df[df["importe"].isna()].copy()Limpiar fechas
df["fecha_registro"] = pd.to_datetime(
df["fecha_registro"],
errors="coerce",
dayfirst=True
)Si los datos mezclan formatos, puede ser necesario limpiarlos previamente o procesar grupos separados. Verifica rangos imposibles:
hoy = pd.Timestamp.today().normalize()
fechas_futuras = df[df["fecha_registro"] > hoy]Una fecha futura puede ser válida en una reserva, pero no en una fecha de nacimiento. Las reglas dependen del dominio.
Detectar y eliminar duplicados
Primero distingue duplicados completos de registros que comparten una clave:
duplicados_completos = df[df.duplicated(keep=False)]
duplicados_email = df[
df.duplicated(subset=["email"], keep=False)
]Después define cuál conservar:
df = (
df.sort_values("fecha_actualizacion")
.drop_duplicates(subset=["email"], keep="last")
)La referencia oficial de drop_duplicates detalla los parámetros. No borres duplicados sin entender si representan errores, compras repetidas o eventos legítimos.
Validar categorías
estados_validos = {"activo", "inactivo", "pendiente"}
mascara = ~df["estado"].isin(estados_validos)
print(df.loc[mascara, ["id_cliente", "estado"]])Puedes convertir valores desconocidos en una categoría de revisión, pero no inventes el dato correcto sin evidencia.
Identificar valores extremos
Un valor extremo no siempre es un error. Una venta muy alta puede ser real. Utiliza reglas de negocio y estadísticas:
q1 = df["importe"].quantile(0.25)
q3 = df["importe"].quantile(0.75)
iqr = q3 - q1
limite_inferior = q1 - 1.5 * iqr
limite_superior = q3 + 1.5 * iqr
outliers = df[
(df["importe"] < limite_inferior) |
(df["importe"] > limite_superior)
]Analiza los casos antes de excluirlos. En muchos proyectos conviene marcar una columna requiere_revision en lugar de eliminar filas.
Validar correos y teléfonos
Una expresión regular sencilla puede detectar errores evidentes, pero no demuestra que la dirección exista:
patron_email = r"^[^@\s]+@[^@\s]+\.[^@\s]+$"
df["email_valido"] = df["email"].str.fullmatch(
patron_email,
na=False
)Para teléfonos, conserva primero el valor original y crea una columna normalizada. No elimines prefijos internacionales ni ceros sin conocer las reglas del país.
Crear una función de limpieza
def limpiar_clientes(df):
resultado = df.copy()
resultado.columns = resultado.columns.str.strip().str.lower()
resultado["email"] = resultado["email"].astype("string").str.strip().str.lower()
resultado["importe"] = pd.to_numeric(resultado["importe"], errors="coerce")
resultado["fecha_registro"] = pd.to_datetime(
resultado["fecha_registro"], errors="coerce"
)
resultado = resultado.dropna(subset=["id_cliente"])
resultado = resultado.drop_duplicates(subset=["id_cliente"], keep="last")
return resultadoTrabajar sobre una copia evita modificar el DataFrame original de forma inesperada. Divide procesos complejos en funciones pequeñas y prueba cada una.
Crear un informe de calidad
informe = {
"filas": len(df),
"duplicados": int(df.duplicated().sum()),
"emails_invalidos": int((~df["email_valido"]).sum()),
"importes_ausentes": int(df["importe"].isna().sum()),
}
print(informe)Comparar el informe antes y después ayuda a verificar que las reglas mejoraron la calidad sin borrar demasiada información.
Guardar resultados de forma segura
df_limpio.to_csv("clientes_limpios.csv", index=False, encoding="utf-8")
invalidos.to_csv("clientes_para_revision.csv", index=False, encoding="utf-8")No sobrescribas el archivo original durante las primeras ejecuciones. Conserva versiones y registra la fecha, las reglas aplicadas y el número de filas afectadas.
Errores frecuentes
Los fallos más comunes son eliminar todas las filas incompletas, rellenar cualquier ausencia con cero, convertir columnas sin revisar errores, borrar duplicados legítimos, aplicar formatos de fecha equivocados y modificar el archivo original sin copia. También es peligroso limpiar únicamente una muestra visual y asumir que el resto tiene los mismos problemas.
Conclusión
La limpieza de datos es un proceso de diagnóstico, transformación y validación. Pandas facilita normalizar texto, convertir tipos, tratar ausencias, detectar duplicados y crear informes de calidad. La parte más importante sigue siendo definir reglas coherentes con el significado de los datos. Conserva los valores originales, separa registros dudosos y automatiza las verificaciones para que el proceso pueda repetirse cada vez que llegue un archivo nuevo.





