Pandas Data Cleaning: Flujo práctico
Updated on
Los datos sucios bloquean el análisis temprano. Parches rápidos con fillna o reemplazos de texto esconden problemas reales: tipos erróneos, duplicados silenciosos o outliers que sesgan métricas.
Marco PAS:
- Problema: Tipos mixtos, nulos y duplicados rompen joins y sesgan estadísticas.
- Agitar: Arreglos puntuales ocultan fallas hasta tarde en el proyecto y generan retrabajo.
- Solución: Un checklist repetible: estandarizar tipos, tratar nulos de forma intencional, manejar outliers, deduplicar y validar antes de exportar.
Workflow de un vistazo
| Paso | Qué hacer | Ejemplo |
|---|---|---|
| 1. Perfilar | Ver columnas, nulos, únicos | df.info(), df.describe(include="all") |
| 2. Normalizar columnas | Limpiar nombres/texto | df.columns = df.columns.str.strip().str.lower() |
| 3. Corregir tipos | to_datetime, to_numeric, astype("category") | df["date"] = pd.to_datetime(df["date"], errors="coerce") |
| 4. Tratar faltantes | Borrar o rellenar con reglas | df["age"] = df["age"].fillna(df["age"].median()) |
| 5. Tratar outliers | Cap/flag | df["rev_cap"] = df["revenue"].clip(upper=df["revenue"].quantile(0.99)) |
| 6. Deduplicar | Eliminar duplicados exactos/parciales | df.drop_duplicates(subset=["id", "date"], keep="last") |
| 7. Validar | Afirmar rangos/categorías | assert df["score"].between(0,100).all() |
1) Perfila rápido
summary = {
"rows": len(df),
"columns": df.shape[1],
"nulls": df.isna().sum(),
"unique_counts": df.nunique(),
}- Detecta tipos mezclados pronto (
df.info()). - Observa sesgos con
df.describe(percentiles=[0.01,0.99]).
2) Normaliza columnas y texto
df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")
df["country"] = df["country"].str.strip().str.title()- Nombres estándar simplifican joins y código.
- Usa
.str.normalize("NFKC")si hay caracteres raros.
3) Corrige tipos
df["date"] = pd.to_datetime(df["date"], errors="coerce", utc=True)
df["amount"] = pd.to_numeric(df["amount"], errors="coerce")
df["segment"] = df["segment"].astype("category")errors="coerce"expone valores malos comoNaT/NaN.convert_dtypes()da tipos con nulos y ahorro de memoria en lote.
4) Maneja valores faltantes
df["age"] = df["age"].fillna(df["age"].median())
df["city"] = df["city"].fillna("Unknown")
df = df.dropna(subset=["id"]) # clave obligatoria- Decide por columna: numérica → mediana/media; categórica → modo/placeholder.
- Conserva señal con flags:
df["age_imputed"] = df["age"].isna().
5) Maneja outliers
upper = df["revenue"].quantile(0.99)
lower = df["revenue"].quantile(0.01)
df["revenue_capped"] = df["revenue"].clip(lower=lower, upper=upper)- Para ratios, usa z-score:
abs(z) < 3. - En finanzas, mejor capear que borrar para no alterar el conteo.
6) Deduplica con cuidado
df = df.drop_duplicates(subset=["id", "date"], keep="last")- Confirma la clave única:
assert df.duplicated(["id"]).sum() == 0. - Para coincidencias borrosas, normaliza (p. ej. correos en minúsculas) antes.
7) Valida antes de exportar
assert df["score"].between(0, 100).all()
valid_segments = {"basic", "pro", "enterprise"}
assert df["segment"].isin(valid_segments).all()- Usa
pd.testing.assert_frame_equalen tests para comparar salidas. - Incluye chequeos ligeros de filas y nulos en pipelines para detectar regresiones.
Mini pipeline end-to-end
def clean(df):
df = df.copy()
df.columns = df.columns.str.strip().str.lower()
df["date"] = pd.to_datetime(df["date"], errors="coerce")
df["amount"] = pd.to_numeric(df["amount"], errors="coerce")
df["amount"] = df["amount"].fillna(df["amount"].median())
df["segment"] = df["segment"].fillna("unknown").str.lower()
df = df.dropna(subset=["id"])
df = df.drop_duplicates(subset=["id", "date"], keep="last")
assert df["amount"].ge(0).all()
return df- Copia primero para no mutar el input.
- Con
df.pipe(clean)el pipeline se lee fácil.
Guías relacionadas
Claves finales
- Estandariza nombres, fuerza tipos, trata nulos con intención.
- Capea o marca outliers en vez de borrar silenciosamente.
- Valida claves y rangos antes de exportar a BI o analítica.