Limpieza y Transformación de Datos
Los conjuntos de datos reales llegan con dtypes incorrectos, valores faltantes y cadenas desordenadas. La limpieza hace que los agregados sean confiables antes de fusionar o modelar.
Receta
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
import pandas as pd
df = pd.read_csv("raw.csv", dtype={"region": "category"})
df["revenue"] = pd.to_numeric(df["revenue"], errors="coerce")
df["sku"] = df["sku"].str.strip().str.upper()
df = df.dropna(subset=["revenue"])Cuándo usar esto:
- Primera pasada sobre cualquier CSV, exportación de API o hoja de cálculo ingerida
- Antes de las uniones (las claves deben normalizarse)
- Antes de las divisiones de entrenamiento/prueba en pipelines de ML
- Cuando los agregados parecen imposibles (cadenas que suman cero)
Ejemplo de Trabajo
import pandas as pd
import numpy as np
from io import StringIO
raw = """order_id,region,revenue,sku,ordered_at
1, east , $120 , abc-1 , 01/15/2025
2,WEST,340,def-2,2025-01-16
3,EAST,N/A,abc-1,2025-01-17
"""
df = pd.read_csv(StringIO(raw), dtype={"region": "string[pyarrow]"})
# Eliminar espacios y normalizar claves de texto
df["region"] = df["region"].str.strip().str.title()
df["sku"] = df["sku"].str.upper()
# Analizar dinero y fechas
df["revenue"] = (
df["revenue"]
.str.replace("$", "", regex=False)
.str.replace(",", "", regex=False)
.replace("N/A", np.nan)
)
df["revenue"] = pd.to_numeric(df["revenue"], errors="coerce")
df["ordered_at"] = pd.to_datetime(df["ordered_at"], format="mixed", utc=True)
# Imputar con política documentada: mediana por región
df["revenue"] = df.groupby("region", observed=True)["revenue"].transform(
lambda s: s.fillna(s.median())
)
# Eliminar filas que aún faltan claves críticas
clean = df.dropna(subset=["order_id", "ordered_at"]).astype({"order_id": "int64"})
print(clean.dtypes)
print(clean)Lo que esto demuestra:
- Cadenas respaldadas por Arrow para operaciones de texto eficientes en memoria
- Cadenas de acceso
strpara espacios en blanco y mayúsculas/minúsculas to_numeric(errors="coerce")convirtiendo tokens incorrectos en NA- Imputación por grupo en lugar de una media global
Inmersión Profunda
Cómo Funciona
- Los valores faltantes se mapean a
NaNpara flotantes,pd.NApara dtypes anulables, oNoneen columnas de objeto. - Los métodos de cadena vectorizados se ejecutan por elemento sin bucles de Python.
astypepuede copiar;convert_dtypes()actualiza a tipos de extensión de pandas anulables.- El dtype categórico almacena niveles una vez, ideal para nombres de región repetidos.
Transformaciones Comunes
| Tarea | API |
|---|---|
| Analizar números | pd.to_numeric(..., errors="coerce") |
| Analizar fechas | pd.to_datetime(..., utc=True) |
| Recortar valores atípicos | s.clip(lower, upper) |
| Renombrar columnas | df.rename(columns={"old": "new"}) |
| Deduplicar filas | df.drop_duplicates(subset=[...]) |
Notas de Python
import pandas as pd
# Preferir Int64 anulable cuando NA es posible
df["units"] = pd.array([1, None, 3], dtype="Int64")
# map para reemplazos de dominio pequeño
df["status"] = df["status"].map({"A": "active", "I": "inactive"})Trampas
- fillna(0) global en ingresos faltantes - trata "desconocido" como cero ventas. Solución: política de NA separada por columna; usar medianas de grupo o indicadores.
- Mutación en el lugar en una vista -
df.dropna(inplace=True)en una porción corrompe el padre. Solución: asignar de vuelta:df = df.dropna(). - Caracteres especiales de regex en str.replace -
$y.necesitanregex=Falseo escape. Solución: pasarregex=Falsepara reemplazos literales. - Ambigüedad de fecha local -
01/02/2025se analiza de manera diferente según la configuración regional. Solución: forzar ISO"%Y-%m-%d"en el origen o usarformat="mixed"con auditoría. - Errores tipográficos en categorías -
"East"y"east "se convierten en categorías diferentes antes de eliminar espacios. Solución: normalizar cadenas antes deastype("category").
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| Pandera | Validación de esquemas en CI | Limpieza rápida en notebooks |
| Great Expectations | Contratos de datos de producción | Limpieza de CSV única |
Polars with_columns | Pipelines perezosos grandes | El equipo solo conoce pandas |
| OpenRefine / SQL | Limpieza con intervención humana a escala | ETL repetible y con script |
Preguntas Frecuentes
¿Debo eliminar o imputar valores faltantes?
- Eliminar cuando la falta de valores es rara y aleatoria.
- Imputar cuando la eliminación sesgaría los grupos: documentar la estadística utilizada.
¿Cómo encuentro claves duplicadas antes de una unión?
dupes = df[df.duplicated("order_id", keep=False)]¿Qué hace errors="coerce"?
- Los análisis inválidos se convierten en
NaNen lugar de generar un error. - Seguir con
dropnao una tabla de cuarentena para filas incorrectas.
¿Cómo estandarizo los nombres de las columnas?
df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")¿Cuándo debo usar el dtype de categoría?
- Cadenas de baja cardinalidad utilizadas en
groupby/gráficos. - Evitar cuando los niveles cambian constantemente (alta cardinalidad).
¿Cómo divido una columna en varias?
df[["city", "state"]] = df["location"].str.split(",", expand=True)¿Puedo limpiar en fragmentos para CSV enormes?
for chunk in pd.read_csv("huge.csv", chunksize=100_000):
process(chunk)- Escribir fragmentos limpios en particiones de Parquet.
¿Cómo detecto valores atípicos?
- Regla IQR:
Q3 + 1.5*IQR- marcar, no eliminar silenciosamente sin revisión. - Usar límites de dominio (los ingresos no pueden ser negativos).
¿Qué es pd.NA vs np.nan?
pd.NAes el valor faltante escalar de pandas para dtypes de extensión anulables.np.nanes el valor faltante de float, se propaga en columnas de float.
¿Cómo valido la limpieza en las pruebas?
- Afirmar recuentos de filas, unicidad de claves y
df["revenue"].isna().sum() == 0después de la política. - Capturar dtypes como un diccionario en pytest.
Relacionado
- Series y DataFrames de pandas - conceptos básicos de selección
- Joins y Merges - limpiar claves antes de unir
- Validación y Calidad de Datos - Pandera y Great Expectations
- Rendimiento y Memoria - categóricos y cadenas Arrow
Versiones de la pila: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.